GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModelUnion.cpp
Date: 2026-09-13 04:09:53
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