GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/TableColumn.cpp
Date: 2024-12-15 04:08:34
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 unsigned 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 auto 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 auto 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 auto 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