GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/TableModel.cpp
Date: 2026-10-18 04:09:42
Exec Total Coverage
Lines: 0 311 0.0%
Branches: 0 414 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 "TableModel.h"
24 #include "TableModelImpl.h"
25
26 using QtPdCom::TableModel;
27
28 #include <QDebug>
29
30 /****************************************************************************/
31
32 /** Constructor.
33 */
34 ✗ TableModel::TableModel(QObject *parent):
35 QAbstractTableModel(parent),
36 ✗ impl(new TableModel::Impl(this))
37 {
38 ✗ connect(&impl->valueHighlightRow,
39 SIGNAL(valueChanged()),
40 this,
41 SLOT(highlightRowChanged()));
42 ✗ connect(&impl->visibleRowCount,
43 SIGNAL(valueChanged()),
44 this,
45 SLOT(visibleRowCountChanged()));
46 }
47
48 /****************************************************************************/
49
50 /** Destructor.
51 */
52 ✗ TableModel::~TableModel()
53 {
54 ✗ impl->valueHighlightRow.clearVariable();
55 ✗ clearColumns();
56 }
57
58 /****************************************************************************/
59
60 ✗ void TableModel::insertColumn(TableColumn *col, int position)
61 {
62 ✗ if (position < 0 || position > impl->columnVector.count()) {
63 ✗ position = impl->columnVector.count();
64 }
65
66 ✗ beginInsertColumns(QModelIndex(), position, position);
67 ✗ impl->columnVector.insert(position, col);
68 ✗ endInsertColumns();
69
70 ✗ QObject::connect(
71 col,
72 SIGNAL(dimensionChanged()),
73 this,
74 SLOT(dimensionChanged()));
75 ✗ QObject::connect(
76 col,
77 SIGNAL(headerChanged()),
78 this,
79 SLOT(columnHeaderChanged()));
80 ✗ QObject::connect(col, SIGNAL(valueChanged()), this, SLOT(valueChanged()));
81
82 ✗ impl->updateRows();
83 }
84
85 /****************************************************************************/
86
87 /** Adds a column.
88 */
89 ✗ void TableModel::addColumn(TableColumn *column)
90 {
91 ✗ insertColumn(column, -1);
92 }
93
94 /****************************************************************************/
95
96 ✗ void QtPdCom::TableModel::removeColumn(TableColumn *column)
97 {
98 ✗ const auto idx = impl->columnVector.indexOf(column);
99 ✗ if (idx == -1) {
100 ✗ return;
101 }
102
103 ✗ beginRemoveColumns(QModelIndex(), idx, idx);
104 ✗ impl->columnVector.remove(idx);
105 ✗ endRemoveColumns();
106
107 ✗ QObject::disconnect(
108 column,
109 SIGNAL(dimensionChanged()),
110 this,
111 SLOT(dimensionChanged()));
112 ✗ QObject::disconnect(
113 column,
114 SIGNAL(headerChanged()),
115 this,
116 SLOT(columnHeaderChanged()));
117 ✗ QObject::disconnect(
118 column,
119 SIGNAL(valueChanged()),
120 this,
121 SLOT(valueChanged()));
122
123 ✗ impl->updateRows();
124 }
125
126 /****************************************************************************/
127
128 /** Clears the Columns.
129 */
130 ✗ void TableModel::clearColumns()
131 {
132 ✗ beginRemoveColumns(QModelIndex(), 0, impl->columnVector.count() - 1);
133 ✗ impl->columnVector.clear();
134 ✗ endRemoveColumns();
135
136 ✗ for (const auto column : impl->columnVector) {
137 ✗ QObject::disconnect(
138 column,
139 SIGNAL(dimensionChanged()),
140 this,
141 SLOT(dimensionChanged()));
142 ✗ QObject::disconnect(
143 column,
144 SIGNAL(headerChanged()),
145 this,
146 SLOT(columnHeaderChanged()));
147 ✗ QObject::disconnect(
148 column,
149 SIGNAL(valueChanged()),
150 this,
151 SLOT(valueChanged()));
152 }
153
154 ✗ impl->updateRows();
155 }
156
157 /****************************************************************************/
158
159 ✗ bool TableModel::isEditing() const
160 {
161 ✗ bool editing = false;
162
163 ✗ for (const auto column : impl->columnVector) {
164 ✗ if (column->isEditing()) {
165 ✗ editing = true;
166 ✗ break;
167 }
168 }
169
170 ✗ return editing;
171 }
172
173 /****************************************************************************/
174
175 ✗ unsigned int TableModel::getRowCapacity() const
176 {
177 ✗ return impl->rowCapacity;
178 }
179
180 /****************************************************************************/
181
182 ✗ bool TableModel::hasVisibleRowsVariable() const
183 {
184 ✗ return impl->visibleRowCount.hasVariable();
185 }
186
187 /****************************************************************************/
188
189 /** Implements the model interface.
190 *
191 * \returns Number of rows.
192 */
193 ✗ int TableModel::rowCount(const QModelIndex &index) const
194 {
195 ✗ if (!index.isValid()) {
196 ✗ return impl->rows;
197 }
198 else {
199 ✗ return 0;
200 }
201 }
202
203 /****************************************************************************/
204
205 /** Implements the model interface.
206 *
207 * \returns Number of columns.
208 */
209 ✗ int TableModel::columnCount(const QModelIndex &index) const
210 {
211 ✗ if (!index.isValid()) {
212 ✗ return impl->columnVector.count();
213 }
214 else {
215 ✗ return 0;
216 }
217 }
218
219 /****************************************************************************/
220
221 /** Implements the Model interface.
222 */
223 ✗ QVariant TableModel::data(const QModelIndex &index, int role) const
224 {
225 ✗ if (!index.isValid()) {
226 ✗ return QVariant();
227 }
228
229 ✗ return impl->columnVector[index.column()]->data(index.row(), role);
230 }
231
232 /****************************************************************************/
233
234 /** Implements the Model interface.
235 */
236 QVariant
237 ✗ TableModel::headerData(int section, Qt::Orientation o, int role) const
238 {
239 ✗ if (o != Qt::Horizontal) {
240 ✗ return QVariant();
241 }
242
243 ✗ return impl->columnVector[section]->headerData(role);
244 }
245
246 /****************************************************************************/
247
248 /** Implements the Model interface.
249 */
250 ✗ Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
251 {
252 ✗ Qt::ItemFlags f;
253
254 ✗ if (index.isValid()) {
255 ✗ f = impl->columnVector[index.column()]->flags(index.row());
256 ✗ f |= Qt::ItemIsSelectable;
257 }
258
259 ✗ return f;
260 }
261
262 /****************************************************************************/
263
264 ✗ bool TableModel::setData(
265 const QModelIndex &index,
266 const QVariant &value,
267 int role)
268 {
269 ✗ if (!index.isValid()) {
270 ✗ return false;
271 }
272
273 ✗ bool ret = impl->columnVector[index.column()]->setData(
274 ✗ index.row(),
275 ✗ value.toString(),
276 ✗ role);
277
278 ✗ emit editingChanged(isEditing());
279
280 ✗ return ret;
281 }
282
283 /****************************************************************************/
284
285 ✗ bool TableModel::removeRows(int row, int count, const QModelIndex &parent)
286 {
287 ✗ if (row < 0 || count <= 0 || parent.isValid()) {
288 ✗ return false;
289 }
290
291 ✗ if (!hasVisibleRowsVariable()) {
292 ✗ return false;
293 }
294
295 ✗ if ((unsigned int) (row + count) > impl->visibleRows) {
296 ✗ return false;
297 }
298
299 ✗ impl->ensureEditing();
300
301 ✗ beginRemoveRows(parent, row, row + count - 1);
302 ✗ for (const auto column : impl->columnVector) {
303 ✗ column->impl->deleteRow(row, count);
304 }
305 ✗ endRemoveRows();
306 ✗ impl->visibleRows -= count;
307 ✗ impl->updateRows();
308 ✗ return true;
309 }
310
311 /****************************************************************************/
312
313 ✗ void TableModel::setHighlightRowVariable(
314 PdCom::Variable pv,
315 const PdCom::Selector &selector,
316 const Transmission &transmission)
317 {
318 ✗ clearHighlightRowVariable();
319
320 ✗ if (pv.empty()) {
321 ✗ return;
322 }
323
324 ✗ impl->valueHighlightRow.setVariable(pv, selector, transmission);
325 }
326
327 /****************************************************************************/
328
329 ✗ void TableModel::setHighlightRowVariable(
330 PdCom::Process *process,
331 const QString &path,
332 const PdCom::Selector &selector,
333 const Transmission &transmission)
334 {
335 ✗ clearHighlightRowVariable();
336
337 ✗ if (not process or path.isEmpty()) {
338 ✗ return;
339 }
340
341 ✗ impl->valueHighlightRow
342 ✗ .setVariable(process, path, selector, transmission);
343 }
344
345 /****************************************************************************/
346
347 ✗ void TableModel::clearHighlightRowVariable()
348 {
349 ✗ impl->valueHighlightRow.clearVariable();
350
351 ✗ for (const auto column : impl->columnVector) {
352 ✗ column->setHighlightRow(-1);
353 }
354 }
355
356 /****************************************************************************/
357
358 ✗ int TableModel::fromCsv(
359 const QString csvData,
360 QChar separator,
361 int startRow,
362 int startColumn,
363 const QLocale &locale)
364 {
365 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
366 const auto lines = csvData.split(QChar('\n'), Qt::SkipEmptyParts);
367 #else
368 ✗ const auto lines = csvData.split(QChar('\n'), QString::SkipEmptyParts);
369 #endif
370 ✗ if (startRow < 0 || startRow >= lines.size() || startColumn < 0) {
371 ✗ return -ERANGE;
372 }
373 ✗ int row = 0;
374 ✗ const bool wasEditing = isEditing();
375
376 ✗ for (; row < (lines.size() - startRow); ++row) {
377 ✗ const auto line = lines[row + startRow].split(separator);
378 ✗ if (startColumn >= line.size()) {
379 ✗ return -ERANGE;
380 }
381 const auto columnEnd = std::min<int>(
382 ✗ impl->columnVector.size(),
383 ✗ line.size() - startColumn);
384 ✗ for (int column = 0; column < columnEnd; ++column) {
385 ✗ impl->columnVector[column]->impl->setRow(
386 line[column + startColumn],
387 row,
388 locale);
389 }
390 }
391 ✗ if (!wasEditing && isEditing()) {
392 ✗ emit editingChanged(true);
393 }
394
395 ✗ impl->visibleRows =
396 ✗ std::min<int>(row, impl->visibleRows + impl->rowCapacity);
397 ✗ impl->updateRows();
398
399 ✗ emit dataChanged(
400 ✗ index(0, 0),
401 ✗ index(rowCount({}) - 1, impl->columnVector.size() - 1));
402 ✗ return row;
403 }
404
405 /****************************************************************************/
406
407 ✗ void TableModel::setVisibleRowsVariable(
408 PdCom::Variable pv,
409 const PdCom::Selector &selector,
410 const Transmission &transmission)
411 {
412 ✗ clearVisibleRowsVariable();
413
414 ✗ if (pv.empty()) {
415 ✗ return;
416 }
417
418 ✗ impl->visibleRowCount.setVariable(pv, selector, transmission);
419 }
420
421 /****************************************************************************/
422
423 ✗ void TableModel::setVisibleRowsVariable(
424 PdCom::Process *process,
425 const QString &path,
426 const PdCom::Selector &selector,
427 const Transmission &transmission)
428 {
429 ✗ clearVisibleRowsVariable();
430
431 ✗ if (not process or path.isEmpty()) {
432 ✗ return;
433 }
434
435 ✗ impl->visibleRowCount.setVariable(process, path, selector, transmission);
436 }
437
438 /****************************************************************************/
439
440 ✗ void TableModel::setHighlightColor(QColor hc, int idx)
441 {
442 ✗ if (idx <= -1) {
443 ✗ for (const auto column : impl->columnVector) {
444 ✗ column->setHighlightColor(hc);
445 }
446 }
447 ✗ else if (idx < impl->columnVector.size()) {
448 ✗ impl->columnVector[idx]->setHighlightColor(hc);
449 }
450 }
451
452 /****************************************************************************/
453
454 ✗ void TableModel::setDisabledColor(QColor dc, int idx)
455 {
456 ✗ if (idx <= -1) {
457 ✗ for (const auto column : impl->columnVector) {
458 ✗ column->setDisabledColor(dc);
459 }
460 }
461 ✗ else if (idx < impl->columnVector.size()) {
462 ✗ impl->columnVector[idx]->setDisabledColor(dc);
463 }
464 }
465
466 /****************************************************************************/
467
468 ✗ void TableModel::clearVisibleRowsVariable()
469 {
470 ✗ impl->visibleRowCount.clearVariable();
471 ✗ impl->visibleRows = UINT_MAX;
472 ✗ impl->updateRows();
473 }
474
475 /****************************************************************************/
476
477 /** Commits all edited data.
478 */
479 ✗ void TableModel::commit()
480 {
481 ✗ for (const auto column : impl->columnVector) {
482 ✗ column->commit();
483 }
484
485 ✗ if (impl->visibleRowCount.hasVariable()
486 ✗ && impl->visibleRowCount.getVariable().isWriteable()) {
487 ✗ impl->visibleRowCount.writeValue(impl->visibleRows);
488 }
489
490 ✗ emit editingChanged(false);
491 }
492
493 /****************************************************************************/
494
495 /** Reverts all edited data.
496 */
497 ✗ void TableModel::revert()
498 {
499 ✗ for (const auto column : impl->columnVector) {
500 ✗ column->revert();
501 }
502
503 ✗ if (impl->visibleRowCount.hasData()) {
504 ✗ impl->visibleRows = impl->visibleRowCount.getValue();
505 }
506 else {
507 ✗ impl->visibleRows = UINT_MAX;
508 }
509 ✗ impl->updateRows();
510
511 ✗ emit editingChanged(false);
512 }
513
514 /****************************************************************************/
515
516 /** updates the visibleRowCount variable.
517
518 */
519 ✗ void TableModel::addRow()
520 {
521 ✗ if (impl->rowCapacity > 0) {
522 ✗ ++impl->visibleRows;
523 ✗ impl->updateRows();
524
525 ✗ impl->ensureEditing();
526 }
527 }
528
529 /** updates the visibleRowCount variable with initialization.
530 if value is a scalar all columns will be initialized with the same value
531 otherwise must be a QVariantList
532 */
533 ✗ void TableModel::addRowAndCopyLast()
534 {
535 ✗ if (impl->rowCapacity <= 0) {
536 ✗ return;
537 }
538
539 ✗ if (impl->visibleRows <= 0) {
540 // no row available, so nothing to copy
541 ✗ addRow();
542 ✗ return;
543 }
544
545 ✗ const bool wasEditing = isEditing();
546
547 /* and initialize with prev row data */
548 ✗ for (const auto &column : impl->columnVector) {
549 ✗ const auto columnRows = column->getRows();
550 ✗ if (columnRows == 0 || impl->visibleRows >= columnRows) {
551 ✗ continue;
552 }
553 ✗ const auto v = column->data(impl->visibleRows - 1, Qt::DisplayRole)
554 ✗ .toString();
555 ✗ column->setData(impl->visibleRows, v, Qt::EditRole);
556 }
557 ✗ ++impl->visibleRows;
558 ✗ impl->updateRows();
559
560 ✗ if (!wasEditing) {
561 ✗ emit dataChanged(
562 ✗ index(0, 0),
563 ✗ index(impl->visibleRows - 1, impl->columnVector.count() - 1),
564 {Qt::BackgroundRole});
565
566 ✗ emit editingChanged(true);
567 }
568 }
569
570 /****************************************************************************/
571
572 /** updates the visibleRowCount variable.
573
574 */
575 ✗ bool TableModel::insertRows(
576 int position,
577 int count,
578 const QModelIndex &parent)
579 {
580 ✗ if (count < 0 || impl->rowCapacity < (unsigned int) count
581 ✗ || impl->columnVector.empty()) {
582 ✗ return false;
583 }
584 ✗ if (parent.isValid() || !hasVisibleRowsVariable()) {
585 ✗ return false;
586 }
587 ✗ const bool wasEditing = isEditing();
588 ✗ for (const auto column : impl->columnVector) {
589 ✗ column->impl->insertRow(position, count);
590 }
591
592 ✗ QModelIndex topLeft = index(position, 0);
593 ✗ QModelIndex bottomRight =
594 ✗ index(impl->visibleRows - 1, impl->columnVector.count() - 1);
595 ✗ emit dataChanged(topLeft, bottomRight);
596 ✗ impl->visibleRowCount.writeValue(impl->visibleRows + 1);
597
598 ✗ if (!wasEditing) {
599 ✗ emit dataChanged(
600 ✗ index(0, 0),
601 ✗ index(impl->visibleRows - 1, impl->columnVector.count() - 1),
602 {Qt::BackgroundRole});
603
604 ✗ emit editingChanged(true);
605 }
606 ✗ return true;
607 }
608
609 /****************************************************************************/
610
611 ✗ void TableModel::remRow()
612 {
613 ✗ removeRows(impl->visibleRows - 1, 1, {});
614 }
615
616 /****************************************************************************/
617
618 /** Calculates the number of table rows.
619 */
620 ✗ void TableModel::Impl::updateRows()
621 {
622 ColumnVector::const_iterator it;
623 ✗ unsigned int maxRows = 0;
624
625 ✗ for (it = columnVector.begin(); it != columnVector.end(); it++) {
626 ✗ unsigned int r = (*it)->getRows();
627 ✗ if (r > maxRows) {
628 ✗ maxRows = r;
629 }
630 }
631
632 ✗ if (maxRows > visibleRows) {
633 ✗ rowCapacity = maxRows - visibleRows;
634 ✗ maxRows = visibleRows;
635 }
636 else {
637 ✗ rowCapacity = 0;
638 }
639
640 ✗ if (maxRows > rows) {
641 ✗ parent->beginInsertRows(QModelIndex(), rows, maxRows - 1);
642 ✗ rows = maxRows;
643 ✗ parent->endInsertRows();
644 }
645 ✗ else if (maxRows < rows) {
646 ✗ parent->beginRemoveRows(QModelIndex(), maxRows, rows - 1);
647 ✗ rows = maxRows;
648 ✗ parent->endRemoveRows();
649 }
650 }
651
652 /****************************************************************************/
653
654 ✗ void QtPdCom::TableModel::Impl::ensureEditing()
655 {
656 ✗ if (parent->isEditing()) {
657 ✗ return;
658 }
659
660 ✗ for (const auto column : columnVector) {
661 ✗ column->impl->ensureEditData();
662 }
663
664 ✗ emit parent->dataChanged(
665 ✗ parent->index(0, 0),
666 ✗ parent->index(visibleRows - 1, columnVector.count() - 1),
667 {Qt::BackgroundRole});
668
669 ✗ emit parent->editingChanged(true);
670 }
671
672 /****************************************************************************/
673
674 /** Reacts on process variable dimension changes.
675 */
676 ✗ void TableModel::dimensionChanged()
677 {
678 ✗ impl->updateRows();
679 }
680
681 /****************************************************************************/
682
683 /** Reacts on header data changes.
684 */
685 ✗ void TableModel::columnHeaderChanged()
686 {
687 ✗ TableColumn *col = dynamic_cast<TableColumn *>(sender());
688 ✗ int j = impl->columnVector.indexOf(col);
689
690 ✗ if (j > -1) {
691 ✗ headerDataChanged(Qt::Horizontal, j, j);
692 }
693 }
694
695 /****************************************************************************/
696
697 /** Reacts on process variable changes.
698 */
699 ✗ void TableModel::valueChanged()
700 {
701 ✗ TableColumn *col = dynamic_cast<TableColumn *>(sender());
702
703 ✗ int j = impl->columnVector.indexOf(col);
704 ✗ if (j > -1) {
705 ✗ QModelIndex topLeft = index(0, j);
706 ✗ QModelIndex bottomRight =
707 ✗ index(qMin(col->getRows(), impl->rows) - 1, j);
708 ✗ emit dataChanged(topLeft, bottomRight);
709 /* qDebug() << "Table changes: " << topLeft.row()
710 * << topLeft.column() << bottomRight.row()
711 * << bottomRight.column(); */
712 }
713 }
714
715 /****************************************************************************/
716
717 ✗ void TableModel::highlightRowChanged()
718 {
719 ✗ unsigned int row = -1;
720
721 ✗ if (impl->valueHighlightRow.hasData()) {
722 ✗ row = impl->valueHighlightRow.getValue();
723 }
724
725 ✗ for (const auto column : impl->columnVector) {
726 ✗ column->setHighlightRow(row);
727 }
728
729 ✗ if ((impl->columnVector.count() > 0) && (row < impl->rows)) {
730 ✗ QModelIndex topLeft = index(row, 0);
731 ✗ QModelIndex bottomRight = index(row, impl->columnVector.count() - 1);
732 ✗ emit dataChanged(topLeft, bottomRight);
733 }
734 }
735
736 /****************************************************************************/
737
738 ✗ void TableModel::visibleRowCountChanged()
739 {
740 ✗ if (impl->visibleRowCount.hasData() && !isEditing()) {
741 ✗ impl->visibleRows = impl->visibleRowCount.getValue();
742 ✗ impl->updateRows();
743 }
744 }
745
746 /****************************************************************************/
747
748 ✗ QHash<int, QByteArray> TableModel::roleNames() const
749 {
750 // default role names
751 ✗ auto roles = QAbstractTableModel::roleNames();
752
753 // extended role names
754 ✗ roles[TableColumn::HighlightRole] = "highlight";
755 ✗ roles[TableColumn::ValidRole] = "valid";
756 ✗ roles[TableColumn::IsEnabledRole] = "isEnabled";
757 ✗ roles[TableColumn::IsEditingRole] = "isEditing";
758 ✗ roles[TableColumn::DecimalsRole] = "decimals";
759 ✗ roles[TableColumn::LowerLimitRole] = "lowerLimit";
760 ✗ roles[TableColumn::UpperLimitRole] = "upperLimit";
761 ✗ return roles;
762 }
763
764 /****************************************************************************/
765
766 ✗ QString TableModel::toCsv(
767 bool includeHeader,
768 QChar seperator,
769 const QLocale &locale) const
770 {
771 ✗ QString ans;
772
773 ✗ if (impl->columnVector.empty()) {
774 ✗ return "";
775 }
776
777 ✗ const auto sanitziedHeader = [this](int idx) {
778 ✗ QString ans = impl->columnVector[idx]->getHeader();
779 ✗ ans.replace('\n', ' ');
780 ✗ ans.replace('\r', QString());
781 ✗ return ans;
782 ✗ };
783
784 ✗ if (includeHeader) {
785 ✗ ans += sanitziedHeader(0);
786 ✗ for (int column = 1; column < impl->columnVector.count(); ++column) {
787 ✗ ans += seperator + sanitziedHeader(column);
788 }
789 ✗ ans += '\n';
790 }
791
792 ✗ for (int row = 0; row < rowCount({}); ++row) {
793 ✗ ans += impl->columnVector[0]->impl->getRow(row, locale);
794 ✗ for (int column = 1; column < impl->columnVector.count(); ++column) {
795 ✗ ans += seperator
796 ✗ + impl->columnVector[column]->impl->getRow(row, locale);
797 }
798 ✗ ans += '\n';
799 }
800 ✗ return ans;
801 }
802
803 /****************************************************************************/
804