GCC Code Coverage Report


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

Line Branch Exec Source
1 /*****************************************************************************
2 *
3 * Copyright (C) 2009 - 2022 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 "MessageModelImpl.h"
23
24 #include "MessageImpl.h"
25 #include "MessageItem.h"
26
27 #include <QDateTime>
28 #include <QFutureWatcher>
29 #include <QtPdCom1/Process.h>
30
31 using QtPdCom::MessageModel;
32
33 /****************************************************************************/
34
35 /** Constructor.
36 */
37 ✗ MessageModel::Impl::Impl(MessageModel *model):
38 parent {model},
39 announcedMessageItem {nullptr},
40 messageManager {nullptr},
41 rowLimit {1000},
42 canFetchMore {false},
43 historicSeqNo {0},
44 ✗ lessThan {MessageItem::levelNoLessThan}
45 {}
46
47 /****************************************************************************/
48
49 /** Destructor.
50 */
51 ✗ MessageModel::Impl::~Impl()
52 {}
53
54 /****************************************************************************/
55
56 /** Insert a message item.
57 */
58 ✗ void MessageModel::Impl::insertItem(MessageItem *msgItem)
59 {
60 ✗ int row = messageItemList.indexOf(msgItem);
61
62 ✗ if (row >= 0) {
63 ✗ parent->beginRemoveRows(QModelIndex(), row, row);
64 ✗ messageItemList.removeAt(row);
65 ✗ parent->endRemoveRows();
66 }
67
68 ✗ row = (messageItemList.empty()
69 ✗ or MessageItem::lessThan(msgItem, messageItemList.first()))
70 ✗ ? 0
71 ✗ : (std::lower_bound(
72 ✗ messageItemList.begin(),
73 ✗ messageItemList.end(),
74 msgItem,
75 ✗ lessThan)
76 ✗ - messageItemList.begin());
77
78 ✗ parent->beginInsertRows(QModelIndex(), row, row);
79 ✗ messageItemList.insert(row, msgItem);
80 ✗ parent->endInsertRows();
81
82 // Is this a candidate for the current/announced message?
83 //
84 // Announce this message as current message, if
85 // - there is either no message announced yet,
86 // - the message type is more important than the type of the announced one
87 // - the sequence number is earlier
88 ✗ if (msgItem->isActive()
89 ✗ and (!announcedMessageItem
90 ✗ or msgItem->getType() > announcedMessageItem->getType()
91 ✗ or (msgItem->getType() == announcedMessageItem->getType()
92 ✗ and seqNoLessThan(
93 msgItem->seqNo,
94 ✗ announcedMessageItem->seqNo)))) {
95 ✗ announcedMessageItem = msgItem;
96 #if PD_DEBUG_MESSAGE_MODEL
97 qDebug() << __func__ << "currentMessage" << msgItem
98 << msgItem->message;
99 #endif
100 ✗ emit parent->currentMessage(announcedMessageItem->message);
101 }
102 ✗ else if (msgItem->resetTime and msgItem == announcedMessageItem) {
103 // search for a new message to announce
104 ✗ announce();
105 }
106 }
107
108 /****************************************************************************/
109
110 /** Called from the PdCom interface, if a new message appears via
111 * processMessage() or in context of activeMessagesReply().
112 */
113 ✗ void MessageModel::Impl::addProcessMessage(const PdCom::Message &pdComMsg)
114 {
115 ✗ QString path {QString::fromStdString(pdComMsg.path)};
116 #if PD_DEBUG_MESSAGE_MODEL
117 qDebug() << __func__ << "seqno" << pdComMsg.seqNo;
118 #endif
119
120 ✗ Message *&msg = messageMap[path][pdComMsg.index];
121 ✗ if (!msg) {
122 #if PD_DEBUG_MESSAGE_MODEL
123 qDebug() << __func__ << "not in map. creating new message.";
124 #endif
125 ✗ msg = new Message();
126 ✗ msg->impl->fromPdComMessage(pdComMsg);
127 }
128
129 ✗ auto msgItem {msg->impl->currentItem};
130 #if PD_DEBUG_MESSAGE_MODEL
131 qDebug() << __func__ << "msgItem" << msgItem;
132 if (msgItem) {
133 qDebug() << __func__ << "currentItem seqNo" << msgItem->seqNo;
134 }
135 #endif
136
137 ✗ if (pdComMsg.level != PdCom::LogLevel::Reset) { // set
138
139 ✗ if (not msg->impl->announced) {
140 ✗ emit parent->anyMessage(msg);
141 ✗ msg->impl->announced = true;
142 }
143
144 ✗ if (msgItem) {
145 // already has current item
146 ✗ if (!msgItem->seqNo) {
147 // Current item has zero seqNo -> from mixed mode.
148 // Just attach the seqNo.
149 ✗ msgItem->seqNo = pdComMsg.seqNo;
150 }
151 ✗ else if (msgItem->seqNo != pdComMsg.seqNo) {
152 // Current item has different seqNo.
153 // Not ours, thus create a new one.
154 ✗ msgItem = new MessageItem(msg, this, pdComMsg.time.count());
155 ✗ msgItem->seqNo = pdComMsg.seqNo;
156 }
157 }
158 else { // no current item
159 ✗ msgItem = new MessageItem(msg, this, pdComMsg.time.count());
160 ✗ msgItem->seqNo = pdComMsg.seqNo;
161 ✗ msg->impl->currentItem = msgItem;
162 }
163 ✗ insertItem(msgItem);
164 }
165 else { // reset
166 #if PD_DEBUG_MESSAGE_MODEL
167 qDebug() << __func__ << "reset msgItem" << msgItem;
168 #endif
169 ✗ msg->impl->announced = false;
170
171 ✗ if (msgItem) {
172 ✗ msgItem->resetTime = pdComMsg.time.count();
173
174 // notify views
175 ✗ int row = messageItemList.indexOf(msgItem);
176 #if PD_DEBUG_MESSAGE_MODEL
177 qDebug() << __func__ << "reset row" << row;
178 #endif
179 ✗ if (row >= 0) {
180 ✗ QModelIndex idx0 = parent->index(row, 0);
181 ✗ QModelIndex idx1 = parent->index(row, 2);
182 ✗ emit parent->dataChanged(idx0, idx1);
183 }
184 ✗ msg->impl->currentItem = nullptr;
185 ✗ if (msgItem == announcedMessageItem) {
186 ✗ announce();
187 }
188 }
189 }
190 }
191
192 /****************************************************************************/
193
194 /** Called from the PdCom interface, if a historic message appears via
195 * getMessageReply().
196 */
197 ✗ void MessageModel::Impl::addHistoricMessage(
198 const PdCom::Message &pdComMsg,
199 const PdCom::Message &resetMsg)
200 {
201 ✗ QString path {QString::fromStdString(pdComMsg.path)};
202 #if PD_DEBUG_MESSAGE_MODEL
203 qDebug() << __func__ << pdComMsg.seqNo << resetMsg.seqNo;
204 #endif
205
206 ✗ Message *&msg = messageMap[path][pdComMsg.index];
207 ✗ if (!msg) {
208 ✗ msg = new Message();
209 ✗ msg->impl->fromPdComMessage(pdComMsg);
210 }
211
212 ✗ auto msgItem = new MessageItem(msg, this, pdComMsg.time.count());
213 ✗ msgItem->seqNo = pdComMsg.seqNo;
214 ✗ msgItem->resetTime = resetMsg.time.count();
215 ✗ insertItem(msgItem);
216 }
217
218 /****************************************************************************/
219
220 /** Returns a wrapped version of a string.
221 */
222 ✗ QString MessageModel::Impl::wrapText(const QString &text, unsigned int width)
223 {
224 ✗ QString ret;
225 int lineOffset, i;
226
227 ✗ lineOffset = 0;
228 ✗ while (lineOffset + (int) width < text.length()) {
229 // search last space before line end
230 ✗ for (i = width; i >= 0; i--) {
231 ✗ if (text[lineOffset + i].isSpace()) {
232 ✗ break; // break at whitespace
233 }
234 }
235 ✗ if (i < 0) { // no whitespace found
236 ✗ i = width; // "hard" break at line end
237 }
238
239 ✗ ret += text.mid(lineOffset, i) + QChar(QChar::LineSeparator);
240 ✗ lineOffset += i + 1; // skip line and whitespace
241 }
242
243 ✗ ret += text.mid(lineOffset); // append remaining string
244 ✗ return ret;
245 }
246
247 /****************************************************************************/
248
249 ✗ void MessageModel::Impl::getHistoryMessage()
250 {
251 ✗ if (!messageManager) {
252 ✗ qWarning() << __func__ << "no message manager";
253 ✗ return;
254 }
255
256 #if PD_DEBUG_MESSAGE_MODEL
257 qDebug() << __func__ << "setting canFetchMore to false";
258 #endif
259 ✗ canFetchMore = false; // avoid fetchMore called twice for same seqNo
260
261 ✗ uint32_t prevSeqNo = historicSeqNo - 1;
262 #if PD_DEBUG_MESSAGE_MODEL
263 qDebug() << __func__ << "fetching" << prevSeqNo;
264 #endif
265
266 try {
267 ✗ messageManager->getMessage(prevSeqNo, this, &Impl::getMessageReply);
268 }
269 ✗ catch (PdCom::Exception &e) {
270 ✗ qDebug() << __func__ << e.what();
271 }
272 }
273
274 /****************************************************************************/
275
276 ✗ void MessageModel::Impl::announce()
277 {
278 ✗ MessageItemList sortedList(messageItemList);
279 ✗ std::sort(
280 ✗ sortedList.begin(),
281 ✗ sortedList.end(),
282 MessageItem::levelNoLessThan);
283
284 // if there is no active message, announce a nullptr
285 ✗ if (not sortedList.front()->isActive()) {
286 ✗ if (announcedMessageItem) {
287 ✗ announcedMessageItem = nullptr;
288 #if PD_DEBUG_MESSAGE_MODEL
289 qDebug() << __func__ << "currentMessage null";
290 #endif
291 ✗ emit parent->currentMessage(nullptr);
292 }
293 ✗ return;
294 }
295
296 // the first active message in the sorted list determines the type
297 ✗ auto type = sortedList.front()->getType();
298
299 // find the earliest message with this type
300 ✗ MessageItem *candidate {nullptr};
301 ✗ for (MessageItemList::const_iterator it = sortedList.begin();
302 ✗ (it != sortedList.end() and (*it)->isActive()
303 ✗ and (*it)->getType() == type);
304 ++it) {
305 ✗ candidate = *it;
306 }
307
308 ✗ if (announcedMessageItem == candidate) {
309 // still the same message, no new announcement needed
310 ✗ return;
311 }
312 ✗ announcedMessageItem = candidate;
313
314 ✗ const QtPdCom::Message *msg {nullptr};
315 ✗ if (announcedMessageItem) {
316 ✗ msg = announcedMessageItem->message;
317 }
318 #if PD_DEBUG_MESSAGE_MODEL
319 qDebug() << __func__ << "currentMessage" << announcedMessageItem << msg;
320 #endif
321 ✗ emit parent->currentMessage(msg);
322 }
323
324 /*****************************************************************************
325 * private slots
326 ****************************************************************************/
327
328 /** Reacts on process values changes of all messages to watch.
329 */
330 ✗ void MessageModel::Impl::stateChanged()
331 {
332 ✗ Message *msg = (Message *) sender();
333 ✗ DoubleVariable &var = msg->impl->variable;
334 ✗ double time {var.hasData() ? var.getValue() : 0.0};
335
336 #if PD_DEBUG_MESSAGE_MODEL
337 qDebug() << __func__ << msg->getPath() << msg->getIndex() << time;
338 #endif
339
340 ✗ if (time and not msg->impl->announced) {
341 ✗ emit parent->anyMessage(msg);
342 ✗ msg->impl->announced = true;
343 }
344
345 ✗ MessageItem *msgItem {nullptr};
346
347 ✗ if (time) { // set
348 ✗ MessageItem *msgItem {msg->impl->currentItem};
349 ✗ if (!msgItem) {
350 ✗ msgItem = new MessageItem(msg, this, time * 1e9);
351 #if PD_DEBUG_MESSAGE_MODEL
352 qDebug() << __func__ << "new msgItem" << msgItem;
353 #endif
354 ✗ msg->impl->currentItem = msgItem;
355 ✗ insertItem(msgItem);
356 }
357 }
358 else { // reset
359 ✗ msg->impl->announced = false;
360 ✗ msgItem = msg->impl->currentItem;
361 ✗ if (msgItem) {
362 ✗ auto now {QDateTime::currentDateTime()};
363 ✗ msgItem->resetTime = now.toMSecsSinceEpoch() * 1000000U;
364
365 // notify views
366 ✗ int row = messageItemList.indexOf(msgItem);
367 #if PD_DEBUG_MESSAGE_MODEL
368 qDebug() << __func__ << "reset row" << row;
369 #endif
370 ✗ if (row >= 0) {
371 ✗ QModelIndex idx0 = parent->index(row, 0);
372 ✗ QModelIndex idx1 = parent->index(row, 2);
373 ✗ emit parent->dataChanged(idx0, idx1);
374 }
375
376 ✗ msg->impl->currentItem = nullptr;
377 ✗ if (msgItem == announcedMessageItem) {
378 ✗ announce();
379 }
380 }
381 }
382 }
383
384 /****************************************************************************/
385
386 ✗ void MessageModel::Impl::processMessage(PdCom::Message message)
387 {
388 #if PD_DEBUG_MESSAGE_MODEL
389 auto path = QString::fromStdString(message.path);
390 auto text = QString::fromStdString(message.text);
391 qDebug() << __func__;
392 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
393 << "path" << path << "time" << message.time.count() << "index"
394 << message.index << "text" << text;
395 #endif
396
397 ✗ addProcessMessage(message);
398 }
399
400 /****************************************************************************/
401
402 ✗ void MessageModel::Impl::getMessageReply(PdCom::Message message)
403 {
404 ✗ auto path = QString::fromStdString(message.path);
405
406 #if PD_DEBUG_MESSAGE_MODEL
407 auto text = QString::fromStdString(message.text);
408 qDebug() << __func__;
409 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
410 << "path" << path << "time" << message.time.count() << "index"
411 << message.index << "text" << text;
412 #endif
413
414 ✗ if (path.isEmpty()) {
415 // EOF marker - no more messages from process
416 ✗ return;
417 }
418
419 ✗ historicSeqNo = message.seqNo;
420
421 ✗ canFetchMore = message.level != PdCom::LogLevel::Reset;
422 #if PD_DEBUG_MESSAGE_MODEL
423 qDebug() << __func__ << "setting canFetchMore to" << canFetchMore;
424 #endif
425
426 ✗ bool stillActive {false};
427 ✗ if (canFetchMore) {
428 // found a message that was set in the past. try to find the reset.
429 #if PD_DEBUG_MESSAGE_MODEL
430 bool found {false};
431 qDebug() << __func__ << "reset msg list size is"
432 << resetMessagesList.size();
433 #endif
434 ✗ for (auto r = resetMessagesList.begin(); r != resetMessagesList.end();
435 r++) {
436 ✗ if (r->path == message.path and r->index == message.index) {
437 ✗ addHistoricMessage(message, *r);
438 ✗ resetMessagesList.erase(r);
439 #if PD_DEBUG_MESSAGE_MODEL
440 found = true;
441 #endif
442 ✗ break;
443 }
444 }
445 #if PD_DEBUG_MESSAGE_MODEL
446 if (!found) {
447 qDebug() << __func__ << "reset message not found for"
448 << message.seqNo;
449 // found a message that seems to be still active. Go on with
450 // reading, otherwise views won't ask for more data
451 stillActive = true;
452 }
453 #endif
454 }
455 else {
456 ✗ resetMessagesList.append(message);
457 }
458
459 ✗ if ((!canFetchMore or stillActive) and historicSeqNo) {
460 ✗ getHistoryMessage();
461 }
462 }
463
464 /****************************************************************************/
465
466 ✗ void MessageModel::Impl::activeMessagesReply(
467 std::vector<PdCom::Message> messageList)
468 {
469 ✗ quint32 maxSeqNo {0};
470
471 #if PD_DEBUG_MESSAGE_MODEL
472 qDebug().nospace() << __func__ << "(" << messageList.size() << ")";
473 #endif
474 ✗ for (auto message : messageList) {
475 #if PD_DEBUG_MESSAGE_MODEL
476 auto path = QString::fromStdString(message.path);
477 auto text = QString::fromStdString(message.text);
478 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
479 << "path" << path << "time" << message.time.count()
480 << "index" << message.index << "text" << text;
481 #endif
482 ✗ if (message.level != PdCom::LogLevel::Reset) {
483 ✗ addProcessMessage(message);
484 }
485 else {
486 // one entry in the list of active messages can be a reset
487 // message, to announce the current sequence number
488 ✗ resetMessagesList.append(message);
489 }
490
491 ✗ if (message.seqNo > maxSeqNo) {
492 ✗ maxSeqNo = message.seqNo;
493 }
494 }
495
496 ✗ if (maxSeqNo > 0) {
497 // now fetch one historic message
498 ✗ historicSeqNo = maxSeqNo;
499 ✗ getHistoryMessage();
500 }
501 }
502
503 /****************************************************************************/
504
505 ✗ void MessageModel::Impl::processReset()
506 {
507 #if PD_DEBUG_MESSAGE_MODEL
508 qDebug() << __func__;
509 #endif
510
511 ✗ canFetchMore = false;
512 ✗ historicSeqNo = 0;
513 ✗ resetMessagesList.clear();
514
515 ✗ parent->beginResetModel();
516 ✗ qDeleteAll(messageItemList);
517 ✗ messageItemList.clear();
518 ✗ parent->endResetModel();
519
520 // reset current item pointers
521 ✗ for (auto hash : messageMap) {
522 ✗ for (auto msg : hash) {
523 ✗ if (msg->impl->currentItem) {
524 ✗ msg->impl->currentItem = nullptr;
525 }
526 }
527 }
528
529 ✗ if (announcedMessageItem) {
530 ✗ announcedMessageItem = nullptr;
531 #if PD_DEBUG_MESSAGE_MODEL
532 qDebug() << __func__ << "currentMessage null";
533 #endif
534 ✗ emit parent->currentMessage(nullptr);
535 }
536 }
537
538 /****************************************************************************/
539
540 ✗ void MessageModel::Impl::reloadActiveMessages()
541 {
542 ✗ if (!messageManager) {
543 ✗ return;
544 }
545
546 ✗ messageManager->activeMessages(this, &Impl::activeMessagesReply);
547 }
548
549 /****************************************************************************/
550