GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModelUnion.cpp
Date: 2025-07-06 04:09:33
Exec Total Coverage
Lines: 0 149 0.0%
Branches: 0 183 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
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 void rebuildMergedRows()
67 {
68 parentModel->beginResetModel();
69 mergedRows.clear();
70
71 for (auto &sourceModel : sourceModels) {
72 int rows = sourceModel.model->rowCount();
73 for (int row = 0; row < rows; ++row) {
74 mergedRows.append({&sourceModel, row});
75 }
76 }
77
78 std::sort(
79 mergedRows.begin(),
80 mergedRows.end(),
81 [this](const SourceEntry &a, const SourceEntry &b) {
82 QVariant va = a.sourceModel->model->data(
83 a.sourceModel->model->index(
84 a.row,
85 SortColumn));
86 QVariant vb = b.sourceModel->model->data(
87 b.sourceModel->model->index(
88 b.row,
89 SortColumn));
90 return vb.toString() < va.toString();
91 });
92
93 #if PD_DEBUG_MESSAGE_MODEL
94 qDebug() << __func__ << "(): Now " << mergedRows.size()
95 << " rows.";
96 #endif
97 parentModel->endResetModel();
98 }
99
100 void currentMessage(
101 SourceModel *sourceModel,
102 const QtPdCom::Message *message)
103 {
104 #if PD_DEBUG_MESSAGE_MODEL
105 qDebug() << __func__ << sourceModel << message;
106 #endif
107 sourceModel->announcedMessage = message;
108 updateCurrentMessage();
109 }
110
111 void updateCurrentMessage()
112 {
113 const QtPdCom::Message *candidate {nullptr};
114 for (auto &sourceModel : sourceModels) {
115 if (sourceModel.announcedMessage) {
116 if (not candidate
117 or sourceModel.announcedMessage->getType()
118 > candidate->getType()) {
119 candidate = sourceModel.announcedMessage;
120 }
121 }
122 }
123 #if PD_DEBUG_MESSAGE_MODEL
124 qDebug() << __func__ << candidate;
125 #endif
126 emit parentModel->currentMessage(candidate);
127 }
128 };
129
130 /*****************************************************************************
131 * MessageModelUnion
132 ****************************************************************************/
133
134 /** Constructor.
135 */
136 MessageModelUnion::MessageModelUnion(QObject *parent):
137 QAbstractTableModel(parent),
138 impl(std::unique_ptr<Impl>(new MessageModelUnion::Impl(this)))
139 {}
140
141 /****************************************************************************/
142
143 /** Destructor.
144 */
145 MessageModelUnion::~MessageModelUnion()
146 {
147 clearSourceModels();
148 }
149
150 /****************************************************************************/
151
152 void MessageModelUnion::addSourceModel(
153 QAbstractItemModel *model,
154 QString sourceName)
155 {
156 impl->sourceModels.push_back(
157 {model,
158 sourceName,
159 nullptr, // announced message
160 QMetaObject::Connection()});
161 auto *sourceModel = &impl->sourceModels.last();
162
163 connect(model, &QAbstractTableModel::rowsInserted, [this]() {
164 impl->rebuildMergedRows();
165 });
166 connect(model, &QAbstractTableModel::rowsRemoved, [this]() {
167 impl->rebuildMergedRows();
168 });
169 connect(model, &QAbstractTableModel::dataChanged, [this]() {
170 impl->rebuildMergedRows();
171 });
172 connect(model, &QAbstractTableModel::layoutChanged, [this]() {
173 impl->rebuildMergedRows();
174 });
175 connect(model, &QAbstractTableModel::modelReset, [this]() {
176 impl->rebuildMergedRows();
177 });
178
179 // find a QtPdCom::MessageModel after a chain of QAbstractProxyModels
180 QtPdCom::MessageModel *messageModel {nullptr};
181 while (true) {
182 auto *proxy {qobject_cast<QAbstractProxyModel *>(model)};
183 if (not proxy) {
184 messageModel = qobject_cast<QtPdCom::MessageModel *>(model);
185 break;
186 }
187 model = proxy->sourceModel();
188 }
189
190 if (messageModel) {
191 sourceModel->connection =
192 connect(messageModel,
193 &MessageModel::currentMessage,
194 [this, sourceModel](const QtPdCom::Message *message) {
195 impl->currentMessage(sourceModel, message);
196 });
197 }
198
199 impl->rebuildMergedRows();
200 }
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 void MessageModelUnion::clearSourceModels()
218 {
219 beginResetModel();
220 impl->sourceModels.clear();
221 endResetModel();
222 }
223
224 /****************************************************************************/
225
226 int MessageModelUnion::rowCount(const QModelIndex &) const
227 {
228 return impl->mergedRows.count();
229 }
230
231 /****************************************************************************/
232
233 int MessageModelUnion::columnCount(const QModelIndex &) const
234 {
235 return 4;
236 }
237
238 /****************************************************************************/
239
240 QVariant MessageModelUnion::data(const QModelIndex &index, int role) const
241 {
242 QVariant ret;
243
244 if (index.isValid() and index.row() < impl->mergedRows.size()) {
245 auto entry {impl->mergedRows[index.row()]};
246 QModelIndex sourceIndex =
247 entry.sourceModel->model->index(entry.row, index.column());
248 if (index.column() >= TextColumn and index.column() < SourceColumn) {
249 ret = entry.sourceModel->model->data(sourceIndex, role);
250 }
251 else if (index.column() == SourceColumn and role == Qt::DisplayRole) {
252 ret = entry.sourceModel->name;
253 }
254 }
255
256 #if PD_DEBUG_MESSAGE_MODEL
257 qDebug() << __func__ << index << role << ret;
258 #endif
259 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 Qt::ItemFlags MessageModelUnion::flags(const QModelIndex &index) const
293 {
294 Qt::ItemFlags ret;
295
296 if (index.isValid() and index.row() < impl->mergedRows.size()) {
297 auto entry {impl->mergedRows[index.row()]};
298 QModelIndex sourceIndex =
299 entry.sourceModel->model->index(entry.row, index.column());
300 if (index.column() >= TextColumn and index.column() < SourceColumn) {
301 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 qDebug() << __func__ << index << ret;
312 #endif
313 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