GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/TableColumn.cpp
Date: 2026-09-27 04:07:30
Exec Total Coverage
Lines: 0 327 0.0%
Branches: 0 634 0.0%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * Copyright (C) 2012-2022 Florian Pose <fp@igh.de>
4 * 2013 Dr. Wilhelm Hagemeister <hm@igh-essen.com>
5 *
6 * This file is part of the QtPdCom library.
7 *
8 * The QtPdCom library is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * The QtPdCom library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16 * License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with the QtPdCom Library. If not, see <http://www.gnu.org/licenses/>.
20 *
21 ****************************************************************************/
22
23 #include "TableColumn.h"
24 using QtPdCom::TableColumn;
25
26 #include "TableModelImpl.h"
27
28 #include <pdcom5/Exception.h>
29 #include <pdcom5/Subscriber.h>
30 #include <pdcom5/Subscription.h>
31
32 #include <QDebug>
33 #include <QLocale>
34 #include <QVariant>
35 #include <QBrush>
36
37 #include <limits>
38
39 /****************************************************************************/
40
41 ✗ class QtPdCom::TableColumn::Impl::Subscription:
42 public PdCom::Subscriber,
43 PdCom::Subscription
44 {
45 friend class TableColumn;
46
47 public:
48 ✗ Subscription(
49 TableColumn::Impl *impl,
50 PdCom::Variable pv,
51 ✗ const Transmission &transmission):
52 ✗ Subscriber(transmission.toPdCom()),
53 PdCom::Subscription(*this, pv),
54 ✗ impl(impl)
55 {}
56
57 ✗ Subscription(
58 TableColumn::Impl *impl,
59 PdCom::Process *process,
60 const std::string &path,
61 ✗ const Transmission &transmission):
62 ✗ Subscriber(transmission.toPdCom()),
63 PdCom::Subscription(*this, *process, path),
64 ✗ impl(impl)
65 {}
66
67 ✗ void copyData(double *dest, size_t nelem)
68 {
69 ✗ PdCom::details::copyData(
70 dest,
71 ✗ PdCom::details::TypeInfoTraits<double>::type_info.type,
72 ✗ getData(),
73 ✗ getVariable().getTypeInfo().type,
74 nelem);
75 }
76
77 private:
78 TableColumn::Impl *impl;
79
80 ✗ void stateChanged(const PdCom::Subscription &) override
81 {
82 ✗ if (getState() == PdCom::Subscription::State::Active) {
83 // changed to active. If event mode, poll once.
84 ✗ if (getTransmission() == PdCom::event_mode) {
85 ✗ poll(); // poll once to get initial value
86 }
87 }
88
89 ✗ impl->stateChanged(getState());
90 }
91
92 ✗ void newValues(std::chrono::nanoseconds ts) override
93 {
94 ✗ impl->newValues(ts);
95 }
96 };
97
98 /****************************************************************************/
99
100 ✗ TableColumn::Impl::Impl(TableColumn *parent, const QString &header):
101 parent(parent),
102 header(header),
103 scale(1.0),
104 offset(0.0),
105 dataPresent(false),
106 editData(NULL),
107 enabled(true),
108 highlightRow(-1),
109 decimals(DEFAULT_DECIMALS),
110 ✗ lowerLimit(std::numeric_limits<double>::lowest()),
111 ✗ upperLimit(std::numeric_limits<double>::max()),
112 highlightColor(DEFAULT_HIGHLIGHT_COLOR),
113 ✗ disabledColor(DEFAULT_DISABLED_COLOR)
114 {}
115
116 /****************************************************************************/
117
118 ✗ TableColumn::Impl::~Impl()
119 {
120 ✗ if (editData) {
121 ✗ delete[] editData;
122 }
123 }
124
125 /****************************************************************************/
126
127 ✗ void TableColumn::Impl::stateChanged(PdCom::Subscription::State state)
128 {
129 ✗ if (state == PdCom::Subscription::State::Active) {
130 ✗ emit parent->dimensionChanged();
131 }
132
133 ✗ if (state != PdCom::Subscription::State::Active) {
134 ✗ dataPresent = false;
135 ✗ if (editData) {
136 ✗ delete[] editData;
137 ✗ editData = NULL;
138 }
139 ✗ emit parent->dimensionChanged();
140 }
141 }
142
143 /****************************************************************************/
144
145 /** Constructor.
146 */
147 ✗ TableColumn::TableColumn(const QString &header, QObject *parent):
148 QObject(parent),
149 ✗ impl(std::unique_ptr<TableColumn::Impl>(new Impl(this, header)))
150 {}
151
152 /****************************************************************************/
153
154 /** Destructor.
155 */
156 ✗ TableColumn::~TableColumn()
157 {}
158
159 /****************************************************************************/
160
161 /** Sets the column header.
162 */
163 ✗ void TableColumn::setHeader(const QString &h)
164 {
165 ✗ impl->header = h;
166
167 ✗ emit headerChanged();
168 }
169
170 /****************************************************************************/
171
172 ✗ const QString &TableColumn::getHeader() const
173 {
174 ✗ return impl->header;
175 }
176
177 /****************************************************************************/
178
179 /** Subscribes to a ProcessVariable.
180 */
181 ✗ void TableColumn::setVariable(
182 PdCom::Variable pv,
183 const Transmission &transmission,
184 double scale,
185 double offset)
186 {
187 ✗ clearVariable();
188
189 ✗ if (pv.empty()) {
190 ✗ return;
191 }
192
193 ✗ impl->scale = scale;
194 ✗ impl->offset = offset;
195
196 try {
197 ✗ impl->subscription = std::unique_ptr<Impl::Subscription>(
198 ✗ new Impl::Subscription(impl.get(), pv, transmission));
199 }
200 ✗ catch (PdCom::Exception &e) {
201 ✗ qCritical() << QString("Failed to subscribe to variable"
202 " \"%1\" with transmission %2: %3")
203 ✗ .arg(QString(pv.getPath().c_str()))
204 ✗ .arg(transmission.toString())
205 ✗ .arg(e.what());
206 ✗ return;
207 }
208
209 ✗ pv.getProcess()->callPendingCallbacks();
210
211 ✗ emit dimensionChanged();
212 ✗ emit valueChanged();
213 }
214
215 /****************************************************************************/
216
217 /** Subscribes to a ProcessVariable.
218 */
219 ✗ void TableColumn::setVariable(
220 PdCom::Process *process,
221 const QString &path,
222 const Transmission &transmission,
223 double scale,
224 double offset)
225 {
226 ✗ clearVariable();
227
228 ✗ if (path.isEmpty() or not process) {
229 ✗ return;
230 }
231
232 ✗ impl->scale = scale;
233 ✗ impl->offset = offset;
234
235 try {
236 ✗ impl->subscription =
237 ✗ std::unique_ptr<Impl::Subscription>(new Impl::Subscription(
238 ✗ impl.get(),
239 process,
240 ✗ path.toStdString(),
241 ✗ transmission));
242 }
243 ✗ catch (PdCom::Exception &e) {
244 ✗ qCritical() << QString("Failed to subscribe to variable"
245 " \"%1\" with transmission %2: %3")
246 ✗ .arg(path)
247 ✗ .arg(transmission.toString())
248 ✗ .arg(e.what());
249 ✗ return;
250 }
251
252 ✗ process->callPendingCallbacks();
253
254 ✗ emit dimensionChanged();
255 ✗ emit valueChanged();
256 }
257
258 /****************************************************************************/
259
260 /** Unsubscribe from a Variable.
261 */
262 ✗ void TableColumn::clearVariable()
263 {
264 ✗ if (impl->subscription) {
265 ✗ impl->subscription.reset();
266 ✗ impl->dataPresent = false;
267 ✗ if (impl->editData) {
268 ✗ delete[] impl->editData;
269 ✗ impl->editData = NULL;
270 }
271 ✗ impl->stateChanged(PdCom::Subscription::State::Invalid);
272 ✗ emit dimensionChanged();
273 ✗ emit valueChanged();
274 }
275 }
276
277 /****************************************************************************/
278
279 ✗ void TableColumn::clearData()
280 {
281 ✗ impl->dataPresent = false;
282 ✗ emit valueChanged();
283 }
284
285 /****************************************************************************/
286
287 /** Returns the number of decimals.
288 */
289 ✗ quint32 TableColumn::getDecimals() const
290 {
291 ✗ return impl->decimals;
292 }
293
294 /****************************************************************************/
295
296 /** Sets the number of decimals.
297 */
298 ✗ void TableColumn::setDecimals(quint32 value)
299 {
300 ✗ if (value != impl->decimals) {
301 ✗ impl->decimals = value;
302 ✗ emit valueChanged();
303 }
304 }
305
306 /****************************************************************************/
307
308 /** Returns the lowerlimit for the values of the col
309 */
310 ✗ double TableColumn::getLowerLimit() const
311 {
312 ✗ return impl->lowerLimit;
313 }
314
315 /****************************************************************************/
316
317 /** Sets the lowerlimit for the values of the col
318 */
319 ✗ void TableColumn::setLowerLimit(double value)
320 {
321 ✗ if (value != impl->lowerLimit) {
322 ✗ impl->lowerLimit = value;
323 ✗ emit valueChanged();
324 }
325 }
326
327 /****************************************************************************/
328
329 /** Returns the upperlimit for the values of the col
330 */
331 ✗ double TableColumn::getUpperLimit() const
332 {
333 ✗ return impl->upperLimit;
334 }
335
336 /****************************************************************************/
337
338 /** Sets the upperlimit for the values of the col
339 */
340 ✗ void TableColumn::setUpperLimit(double value)
341 {
342 ✗ if (value != impl->upperLimit) {
343 ✗ impl->upperLimit = value;
344 ✗ emit valueChanged();
345 }
346 }
347
348 /****************************************************************************/
349
350 /** Get number of rows.
351 */
352 ✗ unsigned int TableColumn::getRows() const
353 {
354 ✗ if (impl->subscription and !impl->subscription->getVariable().empty()) {
355 ✗ PdCom::Variable pv(impl->subscription->getVariable());
356 ✗ return pv.getSizeInfo().totalElements();
357 }
358 else {
359 ✗ return 0U;
360 }
361 }
362
363 /****************************************************************************/
364
365 /** Get display text.
366 */
367 ✗ QVariant TableColumn::data(unsigned int row, int role) const
368 {
369 ✗ switch (role) {
370 ✗ case Qt::DisplayRole:
371 case Qt::EditRole:
372 ✗ if (impl->subscription
373 ✗ and !impl->subscription->getVariable().empty()
374 ✗ and impl->dataPresent) {
375 ✗ PdCom::Variable pv(impl->subscription->getVariable());
376 ✗ unsigned int nelem = pv.getSizeInfo().totalElements();
377 ✗ if (row < nelem) {
378 double val;
379
380 ✗ if (impl->editData) {
381 ✗ val = impl->editData[row];
382 }
383 else {
384 ✗ double v[nelem];
385 ✗ impl->subscription->copyData(v, nelem); // FIXME one
386 ✗ val = v[row] * impl->scale + impl->offset;
387 }
388
389 ✗ return QLocale().toString(val, 'f', impl->decimals);
390 }
391 else {
392 ✗ return "";
393 }
394 }
395 else {
396 ✗ return "";
397 }
398 break;
399
400 ✗ case Qt::TextAlignmentRole:
401 ✗ return Qt::AlignRight;
402
403 ✗ case Qt::BackgroundRole:
404 ✗ if (impl->subscription
405 ✗ and !impl->subscription->getVariable().empty()
406 ✗ and impl->dataPresent) {
407 ✗ PdCom::Variable pv(impl->subscription->getVariable());
408 ✗ unsigned int nelem = pv.getSizeInfo().totalElements();
409
410 ✗ if (!impl->enabled || !impl->enabledRows.value(row, true)) {
411 // FIXME also, if variable is not writable.
412 ✗ return QBrush(impl->disabledColor);
413 }
414 ✗ else if (impl->editData) {
415 ✗ return QBrush(Qt::yellow);
416 }
417 ✗ else if ((int) row == impl->highlightRow) {
418 ✗ return QBrush(impl->highlightColor);
419 }
420 ✗ else if (row >= nelem) {
421 ✗ return QBrush(Qt::darkGray);
422 }
423 }
424 ✗ return QBrush();
425
426 // qml compatible !!
427 ✗ case HighlightRole:
428 case ValidRole:
429 case IsEnabledRole:
430 case IsEditingRole:
431 ✗ if (impl->subscription
432 ✗ and !impl->subscription->getVariable().empty()
433 ✗ and impl->dataPresent) {
434 ✗ PdCom::Variable pv(impl->subscription->getVariable());
435 ✗ unsigned int nelem = pv.getSizeInfo().totalElements();
436
437 ✗ if (role == HighlightRole) {
438 ✗ return QVariant(((int) row == impl->highlightRow));
439 }
440 ✗ if (role == ValidRole) {
441 ✗ return QVariant(row < nelem);
442 }
443
444 ✗ if (role == IsEnabledRole) {
445 ✗ return QVariant(isEnabled());
446 }
447
448 ✗ if (role == IsEditingRole) {
449 ✗ return QVariant(isEditing());
450 }
451 }
452 ✗ return QVariant(false);
453
454 ✗ case DecimalsRole:
455 ✗ return QVariant(getDecimals());
456
457 ✗ case LowerLimitRole:
458 ✗ return QVariant(getLowerLimit());
459
460 ✗ case UpperLimitRole:
461 ✗ return QVariant(getUpperLimit());
462
463 ✗ default:
464 ✗ return QVariant();
465 }
466 }
467
468 /****************************************************************************/
469
470 /** Get header data.
471 */
472 ✗ QVariant TableColumn::headerData(int role) const
473 {
474 ✗ switch (role) {
475 ✗ case Qt::DisplayRole:
476 ✗ return impl->header;
477
478 ✗ default:
479 ✗ return QVariant();
480 }
481 }
482
483 /****************************************************************************/
484
485 ✗ QString TableColumn::Impl::getRow(int row, const QLocale &locale) const
486 {
487 ✗ if (subscription and !subscription->getVariable().empty()
488 ✗ and dataPresent) {
489 ✗ PdCom::Variable pv(subscription->getVariable());
490 ✗ int nelem = pv.getSizeInfo().totalElements();
491 ✗ if (row < nelem) {
492 ✗ double val;
493 ✗ subscription->getValue(val, row);
494 ✗ val = val * scale + offset;
495 ✗ return locale.toString(val, 'f', decimals);
496 }
497 }
498 ✗ return "";
499 }
500
501 /****************************************************************************/
502
503 ✗ bool TableColumn::Impl::setRow(
504 QString valueStr,
505 int row,
506 const QLocale &locale)
507 {
508 ✗ bool ok;
509 ✗ double value = locale.toDouble(valueStr, &ok);
510
511 ✗ if (!subscription or subscription->getVariable().empty() or !dataPresent
512 ✗ or !ok) {
513 ✗ return false;
514 }
515
516 ✗ PdCom::Variable pv(subscription->getVariable());
517 ✗ int nelem(pv.getSizeInfo().totalElements());
518
519 ✗ if (row < 0 || row >= nelem) {
520 ✗ return false;
521 }
522 ✗ ensureEditData();
523
524 ✗ editData[row] = value;
525 ✗ return true;
526 }
527
528 /****************************************************************************/
529
530 /** Implements the Model interface.
531 */
532 ✗ Qt::ItemFlags TableColumn::flags(unsigned int row) const
533 {
534 ✗ Qt::ItemFlags f = Qt::ItemFlags();
535
536 ✗ if (!impl->subscription or impl->subscription->getVariable().empty()
537 ✗ or !impl->dataPresent) {
538 ✗ return f;
539 }
540
541 ✗ f |= Qt::ItemIsEnabled;
542
543 ✗ if (impl->enabled and impl->enabledRows.value(row, true)
544 ✗ and impl->subscription->getVariable().isWriteable()) {
545 ✗ f |= Qt::ItemIsEditable;
546 }
547
548 ✗ return f;
549 }
550
551 /****************************************************************************/
552
553 /** Set an edit value.
554 */
555 ✗ bool TableColumn::setData(
556 unsigned int row,
557 const QString &valueString,
558 int role)
559 {
560 // FIXME maybe use lowerLimit and upperLimit here to
561 // limit the value and not only in the view delegate?
562 Q_UNUSED(role);
563
564 ✗ bool ok;
565 ✗ double value = QLocale().toDouble(valueString, &ok);
566
567 ✗ if (!impl->subscription or impl->subscription->getVariable().empty()
568 ✗ or !impl->dataPresent or !ok) {
569 ✗ qCritical() << "Failed to edit variable";
570 ✗ return false;
571 }
572
573 ✗ PdCom::Variable pv(impl->subscription->getVariable());
574 ✗ auto nelem(pv.getSizeInfo().totalElements());
575
576 ✗ if (!pv.isWriteable()) {
577 ✗ return false;
578 }
579
580 ✗ if (row >= nelem) {
581 ✗ qCritical() << "row" << row << "does not exist";
582 ✗ return false;
583 }
584
585 ✗ double data[nelem];
586 ✗ impl->subscription->copyData(data, nelem);
587 ✗ for (size_t i = 0; i < nelem; i++) {
588 ✗ data[i] = data[i] * impl->scale + impl->offset;
589 }
590
591 ✗ impl->ensureEditData();
592
593 ✗ impl->editData[row] = value;
594
595 ✗ for (size_t i = 0; i < nelem; i++) {
596 ✗ if (data[i] != impl->editData[i]) {
597 // data differ from process
598 ✗ emit valueChanged();
599 ✗ return true;
600 }
601 }
602
603 // data are equal to process (again)
604 ✗ delete[] impl->editData;
605 ✗ impl->editData = NULL;
606 ✗ emit valueChanged();
607 ✗ return true;
608 }
609
610 /****************************************************************************/
611
612 /** Set enabled for a column
613 */
614 ✗ void TableColumn::setEnabled(bool value, int row)
615 {
616 ✗ if (row < 0) {
617 ✗ impl->enabled = value;
618 }
619 else {
620 ✗ impl->enabledRows.insert(row, value);
621 }
622
623 ✗ emit valueChanged(); // FIXME, gibt es auch ein redraw
624 }
625
626 /****************************************************************************/
627
628 ✗ bool TableColumn::isEditing() const
629 {
630 ✗ return impl->editData != NULL;
631 }
632
633 /****************************************************************************/
634
635 ✗ bool TableColumn::isEnabled() const
636 {
637 ✗ return impl->enabled;
638 }
639
640 /****************************************************************************/
641
642 /** Write edited data to the process.
643 */
644 ✗ void TableColumn::commit()
645 {
646 ✗ if (!impl->editData or !impl->subscription
647 ✗ or impl->subscription->getVariable().empty()) {
648 ✗ return;
649 }
650
651 ✗ PdCom::Variable pv(impl->subscription->getVariable());
652 ✗ if (!pv.isWriteable()) {
653 ✗ return;
654 }
655 ✗ auto nelem(pv.getSizeInfo().totalElements());
656
657 ✗ for (size_t i = 0; i < nelem; i++) {
658 ✗ if (impl->scale) {
659 ✗ impl->editData[i] =
660 ✗ (impl->editData[i] - impl->offset) / impl->scale;
661 }
662 else {
663 ✗ impl->editData[i] = 0.0;
664 }
665 }
666 ✗ pv.setValue(
667 ✗ impl->editData,
668 ✗ PdCom::details::TypeInfoTraits<double>::type_info.type,
669 nelem);
670
671 ✗ delete[] impl->editData;
672 ✗ impl->editData = NULL;
673 ✗ emit valueChanged();
674 }
675
676 /****************************************************************************/
677
678 /** Reverts all edited values.
679 */
680 ✗ void TableColumn::revert()
681 {
682 ✗ if (impl->editData) {
683 ✗ delete[] impl->editData;
684 ✗ impl->editData = NULL;
685 ✗ emit valueChanged();
686 }
687 }
688
689 /****************************************************************************/
690
691 ✗ void TableColumn::setHighlightRow(int value)
692 {
693 ✗ impl->highlightRow = value;
694 ✗ emit valueChanged();
695 }
696
697 /****************************************************************************/
698
699 ✗ void TableColumn::setHighlightColor(QColor hc)
700 {
701 ✗ impl->highlightColor = hc;
702 ✗ emit valueChanged();
703 }
704
705 /****************************************************************************/
706
707 ✗ void TableColumn::setDisabledColor(QColor dc)
708 {
709 ✗ impl->disabledColor = dc;
710 ✗ emit valueChanged();
711 }
712
713 /****************************************************************************/
714
715 ✗ void TableColumn::Impl::ensureEditData()
716 {
717 ✗ if (!subscription or subscription->getVariable().empty()
718 ✗ or !dataPresent) {
719 ✗ qCritical() << "Failed to edit variable";
720 ✗ return;
721 }
722
723 ✗ PdCom::Variable pv(subscription->getVariable());
724 ✗ if (!pv.isWriteable()) {
725 ✗ return;
726 }
727 ✗ auto nelem(pv.getSizeInfo().totalElements());
728 ✗ if (!editData) {
729 ✗ editData = new double[nelem];
730 ✗ subscription->copyData(editData, nelem);
731 ✗ for (size_t i = 0; i < nelem; i++) {
732 ✗ editData[i] = editData[i] * scale + offset;
733 }
734 }
735 }
736
737 /****************************************************************************/
738
739 ✗ void TableColumn::Impl::insertRow(int position, int count)
740 {
741 ✗ if (!subscription or subscription->getVariable().empty()
742 ✗ or !dataPresent) {
743 ✗ return;
744 }
745 ✗ if (!subscription->getVariable().isWriteable()) {
746 ✗ return;
747 }
748 const int nelem =
749 ✗ subscription->getVariable().getSizeInfo().totalElements();
750 ✗ if (position < 0 || position + count >= nelem) {
751 ✗ return;
752 }
753 ✗ ensureEditData();
754 ✗ std::copy_backward(
755 ✗ editData + position,
756 ✗ editData + nelem - count,
757 ✗ editData + nelem);
758 }
759
760 /****************************************************************************/
761
762 ✗ void TableColumn::Impl::deleteRow(int position, int count)
763 {
764 ✗ if (!subscription or subscription->getVariable().empty()
765 ✗ or !dataPresent) {
766 ✗ return;
767 }
768 ✗ if (!subscription->getVariable().isWriteable()) {
769 ✗ return;
770 }
771 const int nelem =
772 ✗ subscription->getVariable().getSizeInfo().totalElements();
773 ✗ if (position < 0 || position + count >= nelem) {
774 ✗ return;
775 }
776 ✗ ensureEditData();
777 ✗ std::copy(
778 ✗ editData + position + count,
779 ✗ editData + nelem,
780 ✗ editData + position);
781 }
782
783 /****************************************************************************/
784