GCC Code Coverage Report


Directory: ./
File: src/MessageModelUnion.cpp
Date: 2025-07-04 12:28:41
Exec Total Coverage
Lines: 106 168 63.1%
Branches: 79 267 29.6%

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * Copyright (C) 2009 - 2025 Florian Pose <fp@igh.de>
4 *
5 * This file is part of the QtPdCom library.
6 *
7 * The QtPdCom library is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * The QtPdCom library is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with the QtPdCom Library. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ****************************************************************************/
21
22 #include "MessageModelUnion.h"
23 using QtPdCom::MessageModelUnion;
24
25 #include "MessageModel.h"
26
27 #include <QAbstractProxyModel>
28
29 #if PD_DEBUG_MESSAGE_MODEL
30 #include <QDebug>
31 #endif
32
33 /*****************************************************************************
34 * MessageModelUnion::Impl
35 ****************************************************************************/
36
37 2 struct MessageModelUnion::Impl
38 {
39 Impl() = delete;
40 2 Impl(MessageModelUnion *parent):
41 2 parentModel {parent}
42 2 {}
43
44 MessageModelUnion *const parentModel;
45
46
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 struct SourceModel
47 {
48 6 ~SourceModel() { QObject::disconnect(connection); }
49
50 QAbstractItemModel *model;
51 QString name;
52 const QtPdCom::Message *announcedMessage;
53 QMetaObject::Connection connection;
54 };
55 QList<SourceModel> sourceModels;
56
57 struct SourceEntry
58 {
59 SourceModel *sourceModel;
60 int row; // original row in source model
61 };
62 QVector<SourceEntry> mergedRows;
63
64 enum { SortColumn = 1 };
65
66 13 void rebuildMergedRows()
67 {
68 13 parentModel->beginResetModel();
69 13 mergedRows.clear();
70
71
5/8
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 16 times.
✓ Branch 10 taken 13 times.
✓ Branch 12 taken 16 times.
✗ Branch 13 not taken.
29 for (auto &sourceModel : sourceModels) {
72
1/2
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
16 int rows = sourceModel.model->rowCount();
73
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 16 times.
21 for (int row = 0; row < rows; ++row) {
74
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 mergedRows.append({&sourceModel, row});
75 }
76 }
77
78 13 std::sort(
79 mergedRows.begin(),
80 mergedRows.end(),
81 2 [this](const SourceEntry &a, const SourceEntry &b) {
82 2 QVariant va = a.sourceModel->model->data(
83
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
8 a.sourceModel->model->index(
84 2 a.row,
85
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
12 SortColumn));
86 2 QVariant vb = b.sourceModel->model->data(
87
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
8 b.sourceModel->model->index(
88 2 b.row,
89
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
12 SortColumn));
90
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
4 return vb.toString() < va.toString();
91 });
92
93 #if PD_DEBUG_MESSAGE_MODEL
94
4/8
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 13 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 13 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 13 times.
✗ Branch 14 not taken.
26 qDebug() << __func__ << "(): Now " << mergedRows.size()
95
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
26 << " rows.";
96 #endif
97 13 parentModel->endResetModel();
98 13 }
99
100 6 void currentMessage(
101 SourceModel *sourceModel,
102 const QtPdCom::Message *message)
103 {
104 #if PD_DEBUG_MESSAGE_MODEL
105
4/8
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
✓ Branch 16 taken 6 times.
✗ Branch 17 not taken.
6 qDebug() << __func__ << sourceModel << message;
106 #endif
107 6 sourceModel->announcedMessage = message;
108 6 updateCurrentMessage();
109 6 }
110
111 6 void updateCurrentMessage()
112 {
113 6 const QtPdCom::Message *candidate {nullptr};
114
5/8
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 10 times.
✓ Branch 10 taken 6 times.
✓ Branch 12 taken 10 times.
✗ Branch 13 not taken.
16 for (auto &sourceModel : sourceModels) {
115
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (sourceModel.announcedMessage) {
116
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (not candidate
117
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
6 or sourceModel.announcedMessage->getType()
118
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 > candidate->getType()) {
119 4 candidate = sourceModel.announcedMessage;
120 }
121 }
122 }
123 #if PD_DEBUG_MESSAGE_MODEL
124
3/6
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
6 qDebug() << __func__ << candidate;
125 #endif
126 6 emit parentModel->currentMessage(candidate);
127 6 }
128 };
129
130 /*****************************************************************************
131 * MessageModelUnion
132 ****************************************************************************/
133
134 /** Constructor.
135 */
136 2 MessageModelUnion::MessageModelUnion(QObject *parent):
137 QAbstractTableModel(parent),
138
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 impl(std::unique_ptr<Impl>(new MessageModelUnion::Impl(this)))
139 2 {}
140
141 /****************************************************************************/
142
143 /** Destructor.
144 */
145 6 MessageModelUnion::~MessageModelUnion()
146 {
147 2 clearSourceModels();
148 4 }
149
150 /****************************************************************************/
151
152 3 void MessageModelUnion::addSourceModel(
153 QAbstractItemModel *model,
154 QString sourceName)
155 {
156
1/2
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
3 impl->sourceModels.push_back(
157 {model,
158 sourceName,
159 nullptr, // announced message
160 QMetaObject::Connection()});
161 3 auto *sourceModel = &impl->sourceModels.last();
162
163 6 connect(model, &QAbstractTableModel::rowsInserted, [this]() {
164 3 impl->rebuildMergedRows();
165 3 });
166 3 connect(model, &QAbstractTableModel::rowsRemoved, [this]() {
167 impl->rebuildMergedRows();
168 });
169 3 connect(model, &QAbstractTableModel::dataChanged, [this]() {
170 impl->rebuildMergedRows();
171 });
172 3 connect(model, &QAbstractTableModel::layoutChanged, [this]() {
173 impl->rebuildMergedRows();
174 });
175 10 connect(model, &QAbstractTableModel::modelReset, [this]() {
176 7 impl->rebuildMergedRows();
177 7 });
178
179 // find a QtPdCom::MessageModel after a chain of QAbstractProxyModels
180 3 QtPdCom::MessageModel *messageModel {nullptr};
181 while (true) {
182 3 auto *proxy {qobject_cast<QAbstractProxyModel *>(model)};
183
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (not proxy) {
184 3 messageModel = qobject_cast<QtPdCom::MessageModel *>(model);
185 3 break;
186 }
187 model = proxy->sourceModel();
188 }
189
190
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (messageModel) {
191 6 sourceModel->connection =
192 6 connect(messageModel,
193 &MessageModel::currentMessage,
194 12 [this, sourceModel](const QtPdCom::Message *message) {
195 12 impl->currentMessage(sourceModel, message);
196 9 });
197 }
198
199 3 impl->rebuildMergedRows();
200 3 }
201
202 /****************************************************************************/
203
204 void MessageModelUnion::removeSourceModel(QAbstractItemModel *model)
205 {
206 auto it = std::remove_if(
207 impl->sourceModels.begin(),
208 impl->sourceModels.end(),
209 [model](const Impl::SourceModel &s) { return s.model == model; });
210 impl->sourceModels.erase(it, impl->sourceModels.end());
211
212 impl->rebuildMergedRows();
213 }
214
215 /****************************************************************************/
216
217 3 void MessageModelUnion::clearSourceModels()
218 {
219 3 beginResetModel();
220 3 impl->sourceModels.clear();
221 3 endResetModel();
222 3 }
223
224 /****************************************************************************/
225
226 22 int MessageModelUnion::rowCount(const QModelIndex &) const
227 {
228 22 return impl->mergedRows.count();
229 }
230
231 /****************************************************************************/
232
233 14 int MessageModelUnion::columnCount(const QModelIndex &) const
234 {
235 14 return 4;
236 }
237
238 /****************************************************************************/
239
240 14 QVariant MessageModelUnion::data(const QModelIndex &index, int role) const
241 {
242 14 QVariant ret;
243
244
3/6
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 14 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 14 times.
✗ Branch 9 not taken.
14 if (index.isValid() and index.row() < impl->mergedRows.size()) {
245
1/2
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
14 auto entry {impl->mergedRows[index.row()]};
246 14 QModelIndex sourceIndex =
247
1/2
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
14 entry.sourceModel->model->index(entry.row, index.column());
248
5/6
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 2 times.
14 if (index.column() >= TextColumn and index.column() < SourceColumn) {
249
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 ret = entry.sourceModel->model->data(sourceIndex, role);
250 }
251
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 else if (index.column() == SourceColumn and role == Qt::DisplayRole) {
252
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 ret = entry.sourceModel->name;
253 }
254 }
255
256 #if PD_DEBUG_MESSAGE_MODEL
257
5/10
✓ Branch 8 taken 14 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 14 times.
✗ Branch 12 not taken.
✓ Branch 15 taken 14 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 14 times.
✗ Branch 19 not taken.
✓ Branch 22 taken 14 times.
✗ Branch 23 not taken.
14 qDebug() << __func__ << index << role << ret;
258 #endif
259 14 return ret;
260 }
261
262 /****************************************************************************/
263
264 QVariant
265 MessageModelUnion::headerData(int section, Qt::Orientation o, int role) const
266 {
267 if (role == Qt::DisplayRole && o == Qt::Horizontal) {
268 switch (section) {
269 case TextColumn:
270 return tr("Message");
271
272 case TimeOccurredColumn:
273 return tr("Time");
274
275 case TimeResetColumn:
276 return tr("Reset");
277
278 case SourceColumn:
279 return tr("Source");
280
281 default:
282 return QVariant();
283 }
284 }
285 else {
286 return QVariant();
287 }
288 }
289
290 /****************************************************************************/
291
292 4 Qt::ItemFlags MessageModelUnion::flags(const QModelIndex &index) const
293 {
294 4 Qt::ItemFlags ret;
295
296
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
4 if (index.isValid() and index.row() < impl->mergedRows.size()) {
297
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 auto entry {impl->mergedRows[index.row()]};
298 4 QModelIndex sourceIndex =
299
1/2
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 entry.sourceModel->model->index(entry.row, index.column());
300
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 if (index.column() >= TextColumn and index.column() < SourceColumn) {
301
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ret = entry.sourceModel->model->flags(sourceIndex);
302 }
303 else if (index.column() == SourceColumn) {
304 QModelIndex textIndex =
305 entry.sourceModel->model->index(entry.row, TextColumn);
306 ret = entry.sourceModel->model->flags(textIndex);
307 }
308 }
309
310 #if PD_DEBUG_MESSAGE_MODEL
311
4/8
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 4 times.
✗ Branch 18 not taken.
4 qDebug() << __func__ << index << ret;
312 #endif
313 4 return ret;
314 }
315
316 /****************************************************************************/
317
318 bool MessageModelUnion::canFetchMore(const QModelIndex &index) const
319 {
320 #if PD_DEBUG_MESSAGE_MODEL
321 qDebug() << __func__ << index;
322 #endif
323 if (not index.isValid()) {
324 // root layer - return true if *any* model can fetch more rows
325 for (auto &sourceModel : impl->sourceModels) {
326 if (sourceModel.model->canFetchMore(index)) {
327 #if PD_DEBUG_MESSAGE_MODEL
328 qDebug() << sourceModel.model << sourceModel.name << "yes";
329 #endif
330 return true;
331 }
332 }
333 #if PD_DEBUG_MESSAGE_MODEL
334 qDebug() << "no";
335 #endif
336 return false;
337 }
338 else if (index.row() < impl->mergedRows.size()) {
339 auto entry {impl->mergedRows[index.row()]};
340 auto sourceIndex {
341 entry.sourceModel->model->index(entry.row, index.column())};
342 auto canFetch {entry.sourceModel->model->canFetchMore(sourceIndex)};
343 #if PD_DEBUG_MESSAGE_MODEL
344 qDebug() << "specific" << canFetch;
345 #endif
346 return canFetch;
347 }
348 else {
349 #if PD_DEBUG_MESSAGE_MODEL
350 qDebug() << "invalid";
351 #endif
352 return false;
353 }
354 }
355
356 /****************************************************************************/
357
358 void MessageModelUnion::fetchMore(const QModelIndex &index)
359 {
360 #if PD_DEBUG_MESSAGE_MODEL
361 qDebug() << __func__ << index;
362 #endif
363 if (not index.isValid()) {
364 // root layer - pass on to *any* model that can fetch more rows
365 for (auto &sourceModel : impl->sourceModels) {
366 if (sourceModel.model->canFetchMore(index)) {
367 #if PD_DEBUG_MESSAGE_MODEL
368 qDebug() << __func__ << sourceModel.model << sourceModel.name;
369 #endif
370 sourceModel.model->fetchMore(index);
371 }
372 }
373 }
374 else if (index.row() < impl->mergedRows.size()) {
375 auto entry {impl->mergedRows[index.row()]};
376 auto sourceIndex {
377 entry.sourceModel->model->index(entry.row, index.column())};
378 entry.sourceModel->model->fetchMore(sourceIndex);
379 }
380 }
381
382 /****************************************************************************/
383