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 |