GCC Code Coverage Report


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