GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModelUnion.cpp
Date: 2026-10-18 04:09:42
Exec Total Coverage
Lines: 0 154 0.0%
Branches: 0 187 0.0%

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 ✗ struct MessageModelUnion::Impl
38 {
39 Impl() = delete;
40 ✗ Impl(MessageModelUnion *parent):
41 ✗ parentModel {parent}
42 {}
43
44 MessageModelUnion *const parentModel;
45
46 ✗ struct SourceModel
47 {
48 ✗ ~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 const QtPdCom::Message *lastAnnounced {nullptr};
57
58 struct SourceEntry
59 {
60 SourceModel *sourceModel;
61 int row; // original row in source model
62 };
63 QVector<SourceEntry> mergedRows;
64
65 enum { SortColumn = 1 };
66
67 ✗ void rebuildMergedRows()
68 {
69 ✗ parentModel->beginResetModel();
70 ✗ mergedRows.clear();
71
72 ✗ for (auto &sourceModel : sourceModels) {
73 ✗ int rows = sourceModel.model->rowCount();
74 ✗ for (int row = 0; row < rows; ++row) {
75 ✗ mergedRows.append({&sourceModel, row});
76 }
77 }
78
79 ✗ std::sort(
80 ✗ mergedRows.begin(),
81 ✗ mergedRows.end(),
82 ✗ [this](const SourceEntry &a, const SourceEntry &b) {
83 ✗ QVariant va = a.sourceModel->model->data(
84 ✗ a.sourceModel->model->index(
85 ✗ a.row,
86 ✗ SortColumn));
87 ✗ QVariant vb = b.sourceModel->model->data(
88 ✗ b.sourceModel->model->index(
89 ✗ b.row,
90 ✗ SortColumn));
91 ✗ return vb.toString() < va.toString();
92 });
93
94 #if PD_DEBUG_MESSAGE_MODEL
95 qDebug() << __func__ << "(): Now " << mergedRows.size()
96 << " rows.";
97 #endif
98 ✗ parentModel->endResetModel();
99 }
100
101 ✗ void currentMessage(
102 SourceModel *sourceModel,
103 const QtPdCom::Message *message)
104 {
105 #if PD_DEBUG_MESSAGE_MODEL
106 qDebug() << __func__ << sourceModel << message;
107 #endif
108 ✗ sourceModel->announcedMessage = message;
109 ✗ updateCurrentMessage();
110 }
111
112 ✗ void updateCurrentMessage()
113 {
114 ✗ const QtPdCom::Message *candidate {nullptr};
115 ✗ for (auto &sourceModel : sourceModels) {
116 ✗ if (sourceModel.announcedMessage) {
117 ✗ if (not candidate
118 ✗ or sourceModel.announcedMessage->getType()
119 ✗ > candidate->getType()) {
120 ✗ candidate = sourceModel.announcedMessage;
121 }
122 }
123 }
124 #if PD_DEBUG_MESSAGE_MODEL
125 qDebug() << __func__ << candidate;
126 #endif
127 ✗ if (candidate == lastAnnounced) {
128 ✗ return;
129 }
130 ✗ lastAnnounced = candidate;
131 ✗ emit parentModel->currentMessage(candidate);
132 }
133 };
134
135 /*****************************************************************************
136 * MessageModelUnion
137 ****************************************************************************/
138
139 /** Constructor.
140 */
141 ✗ MessageModelUnion::MessageModelUnion(QObject *parent):
142 QAbstractTableModel(parent),
143 ✗ impl(std::unique_ptr<Impl>(new MessageModelUnion::Impl(this)))
144 {}
145
146 /****************************************************************************/
147
148 /** Destructor.
149 */
150 ✗ MessageModelUnion::~MessageModelUnion()
151 {
152 ✗ clearSourceModels();
153 }
154
155 /****************************************************************************/
156
157 ✗ void MessageModelUnion::addSourceModel(
158 QAbstractItemModel *model,
159 QString sourceName)
160 {
161 ✗ impl->sourceModels.push_back(
162 {model,
163 sourceName,
164 nullptr, // announced message
165 QMetaObject::Connection()});
166 ✗ auto *sourceModel = &impl->sourceModels.last();
167
168 ✗ connect(model, &QAbstractTableModel::rowsInserted, [this]() {
169 ✗ impl->rebuildMergedRows();
170 ✗ });
171 ✗ connect(model, &QAbstractTableModel::rowsRemoved, [this]() {
172 ✗ impl->rebuildMergedRows();
173 ✗ });
174 ✗ connect(model, &QAbstractTableModel::dataChanged, [this]() {
175 ✗ impl->rebuildMergedRows();
176 ✗ });
177 ✗ connect(model, &QAbstractTableModel::layoutChanged, [this]() {
178 ✗ impl->rebuildMergedRows();
179 ✗ });
180 ✗ connect(model, &QAbstractTableModel::modelReset, [this]() {
181 ✗ impl->rebuildMergedRows();
182 ✗ });
183
184 // find a QtPdCom::MessageModel after a chain of QAbstractProxyModels
185 ✗ QtPdCom::MessageModel *messageModel {nullptr};
186 while (true) {
187 ✗ auto *proxy {qobject_cast<QAbstractProxyModel *>(model)};
188 ✗ if (not proxy) {
189 ✗ messageModel = qobject_cast<QtPdCom::MessageModel *>(model);
190 ✗ break;
191 }
192 ✗ model = proxy->sourceModel();
193 }
194
195 ✗ if (messageModel) {
196 ✗ sourceModel->connection =
197 ✗ connect(messageModel,
198 &MessageModel::currentMessage,
199 ✗ [this, sourceModel](const QtPdCom::Message *message) {
200 ✗ impl->currentMessage(sourceModel, message);
201 ✗ });
202 }
203
204 ✗ impl->rebuildMergedRows();
205 ✗ impl->updateCurrentMessage();
206 }
207
208 /****************************************************************************/
209
210 ✗ void MessageModelUnion::removeSourceModel(QAbstractItemModel *model)
211 {
212 ✗ auto it = std::remove_if(
213 ✗ impl->sourceModels.begin(),
214 ✗ impl->sourceModels.end(),
215 ✗ [model](const Impl::SourceModel &s) { return s.model == model; });
216 ✗ impl->sourceModels.erase(it, impl->sourceModels.end());
217
218 ✗ impl->rebuildMergedRows();
219 ✗ impl->updateCurrentMessage();
220 }
221
222 /****************************************************************************/
223
224 ✗ void MessageModelUnion::clearSourceModels()
225 {
226 ✗ beginResetModel();
227 ✗ impl->sourceModels.clear();
228 ✗ endResetModel();
229 }
230
231 /****************************************************************************/
232
233 ✗ int MessageModelUnion::rowCount(const QModelIndex &) const
234 {
235 ✗ return impl->mergedRows.count();
236 }
237
238 /****************************************************************************/
239
240 ✗ int MessageModelUnion::columnCount(const QModelIndex &) const
241 {
242 ✗ return 4;
243 }
244
245 /****************************************************************************/
246
247 ✗ QVariant MessageModelUnion::data(const QModelIndex &index, int role) const
248 {
249 ✗ QVariant ret;
250
251 ✗ if (index.isValid() and index.row() < impl->mergedRows.size()) {
252 ✗ auto entry {impl->mergedRows[index.row()]};
253 ✗ QModelIndex sourceIndex =
254 ✗ entry.sourceModel->model->index(entry.row, index.column());
255 ✗ if (index.column() >= TextColumn and index.column() < SourceColumn) {
256 ✗ ret = entry.sourceModel->model->data(sourceIndex, role);
257 }
258 ✗ else if (index.column() == SourceColumn and role == Qt::DisplayRole) {
259 ✗ ret = entry.sourceModel->name;
260 }
261 }
262
263 #if PD_DEBUG_MESSAGE_MODEL
264 qDebug() << __func__ << index << role << ret;
265 #endif
266 ✗ return ret;
267 }
268
269 /****************************************************************************/
270
271 QVariant
272 ✗ MessageModelUnion::headerData(int section, Qt::Orientation o, int role) const
273 {
274 ✗ if (role == Qt::DisplayRole && o == Qt::Horizontal) {
275 ✗ switch (section) {
276 ✗ case TextColumn:
277 ✗ return tr("Message");
278
279 ✗ case TimeOccurredColumn:
280 ✗ return tr("Time");
281
282 ✗ case TimeResetColumn:
283 ✗ return tr("Reset");
284
285 ✗ case SourceColumn:
286 ✗ return tr("Source");
287
288 ✗ default:
289 ✗ return QVariant();
290 }
291 }
292 else {
293 ✗ return QVariant();
294 }
295 }
296
297 /****************************************************************************/
298
299 ✗ Qt::ItemFlags MessageModelUnion::flags(const QModelIndex &index) const
300 {
301 ✗ Qt::ItemFlags ret;
302
303 ✗ if (index.isValid() and index.row() < impl->mergedRows.size()) {
304 ✗ auto entry {impl->mergedRows[index.row()]};
305 ✗ QModelIndex sourceIndex =
306 ✗ entry.sourceModel->model->index(entry.row, index.column());
307 ✗ if (index.column() >= TextColumn and index.column() < SourceColumn) {
308 ✗ ret = entry.sourceModel->model->flags(sourceIndex);
309 }
310 ✗ else if (index.column() == SourceColumn) {
311 ✗ QModelIndex textIndex =
312 ✗ entry.sourceModel->model->index(entry.row, TextColumn);
313 ✗ ret = entry.sourceModel->model->flags(textIndex);
314 }
315 }
316
317 #if PD_DEBUG_MESSAGE_MODEL
318 qDebug() << __func__ << index << ret;
319 #endif
320 ✗ return ret;
321 }
322
323 /****************************************************************************/
324
325 ✗ bool MessageModelUnion::canFetchMore(const QModelIndex &index) const
326 {
327 #if PD_DEBUG_MESSAGE_MODEL
328 qDebug() << __func__ << index;
329 #endif
330 ✗ if (not index.isValid()) {
331 // root layer - return true if *any* model can fetch more rows
332 ✗ for (auto &sourceModel : impl->sourceModels) {
333 ✗ if (sourceModel.model->canFetchMore(index)) {
334 #if PD_DEBUG_MESSAGE_MODEL
335 qDebug() << sourceModel.model << sourceModel.name << "yes";
336 #endif
337 ✗ return true;
338 }
339 }
340 #if PD_DEBUG_MESSAGE_MODEL
341 qDebug() << "no";
342 #endif
343 ✗ return false;
344 }
345 ✗ else if (index.row() < impl->mergedRows.size()) {
346 ✗ auto entry {impl->mergedRows[index.row()]};
347 ✗ auto sourceIndex {
348 ✗ entry.sourceModel->model->index(entry.row, index.column())};
349 ✗ auto canFetch {entry.sourceModel->model->canFetchMore(sourceIndex)};
350 #if PD_DEBUG_MESSAGE_MODEL
351 qDebug() << "specific" << canFetch;
352 #endif
353 ✗ return canFetch;
354 }
355 else {
356 #if PD_DEBUG_MESSAGE_MODEL
357 qDebug() << "invalid";
358 #endif
359 ✗ return false;
360 }
361 }
362
363 /****************************************************************************/
364
365 ✗ void MessageModelUnion::fetchMore(const QModelIndex &index)
366 {
367 #if PD_DEBUG_MESSAGE_MODEL
368 qDebug() << __func__ << index;
369 #endif
370 ✗ if (not index.isValid()) {
371 // root layer - pass on to *any* model that can fetch more rows
372 ✗ for (auto &sourceModel : impl->sourceModels) {
373 ✗ if (sourceModel.model->canFetchMore(index)) {
374 #if PD_DEBUG_MESSAGE_MODEL
375 qDebug() << __func__ << sourceModel.model << sourceModel.name;
376 #endif
377 ✗ sourceModel.model->fetchMore(index);
378 }
379 }
380 }
381 ✗ else if (index.row() < impl->mergedRows.size()) {
382 ✗ auto entry {impl->mergedRows[index.row()]};
383 ✗ auto sourceIndex {
384 ✗ entry.sourceModel->model->index(entry.row, index.column())};
385 ✗ entry.sourceModel->model->fetchMore(sourceIndex);
386 }
387 }
388
389 /****************************************************************************/
390