GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModelImpl.cpp
Date: 2026-11-08 04:08:44
Exec Total Coverage
Lines: 0 196 0.0%
Branches: 0 370 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 else {
129 // the server text may have changed, e. g. after a server restart
130 ✗ if (not pdComMsg.text.empty()) {
131 ✗ msg->impl->serverText = pdComMsg.text.c_str();
132 }
133 }
134
135 ✗ auto msgItem {msg->impl->currentItem};
136 #if PD_DEBUG_MESSAGE_MODEL
137 qDebug() << __func__ << "msgItem" << msgItem;
138 if (msgItem) {
139 qDebug() << __func__ << "currentItem seqNo" << msgItem->seqNo;
140 }
141 #endif
142
143 ✗ if (pdComMsg.level != PdCom::LogLevel::Reset) { // set
144
145 ✗ if (not msg->impl->announced) {
146 ✗ emit parent->anyMessage(msg);
147 ✗ msg->impl->announced = true;
148 }
149
150 ✗ if (msgItem) {
151 // already has current item
152 ✗ if (!msgItem->seqNo) {
153 // Current item has zero seqNo -> from mixed mode.
154 // Just attach the seqNo.
155 ✗ msgItem->seqNo = pdComMsg.seqNo;
156 }
157 ✗ else if (msgItem->seqNo != pdComMsg.seqNo) {
158 // Current item has different seqNo.
159 // Not ours, thus create a new one.
160 ✗ msgItem = new MessageItem(msg, this, pdComMsg.time.count());
161 ✗ msgItem->seqNo = pdComMsg.seqNo;
162 }
163 }
164 else { // no current item
165 ✗ msgItem = new MessageItem(msg, this, pdComMsg.time.count());
166 ✗ msgItem->seqNo = pdComMsg.seqNo;
167 ✗ msg->impl->currentItem = msgItem;
168 }
169 ✗ insertItem(msgItem);
170 }
171 else { // reset
172 #if PD_DEBUG_MESSAGE_MODEL
173 qDebug() << __func__ << "reset msgItem" << msgItem;
174 #endif
175 ✗ msg->impl->announced = false;
176
177 ✗ if (msgItem) {
178 ✗ msgItem->resetTime = pdComMsg.time.count();
179
180 // notify views
181 ✗ int row = messageItemList.indexOf(msgItem);
182 #if PD_DEBUG_MESSAGE_MODEL
183 qDebug() << __func__ << "reset row" << row;
184 #endif
185 ✗ if (row >= 0) {
186 ✗ QModelIndex idx0 = parent->index(row, 0);
187 ✗ QModelIndex idx1 = parent->index(row, 2);
188 ✗ emit parent->dataChanged(idx0, idx1);
189 }
190 ✗ msg->impl->currentItem = nullptr;
191 ✗ if (msgItem == announcedMessageItem) {
192 ✗ announce();
193 }
194 }
195 }
196 }
197
198 /****************************************************************************/
199
200 /** Called from the PdCom interface, if a historic message appears via
201 * getMessageReply().
202 */
203 ✗ void MessageModel::Impl::addHistoricMessage(
204 const PdCom::Message &pdComMsg,
205 const PdCom::Message &resetMsg)
206 {
207 ✗ QString path {QString::fromStdString(pdComMsg.path)};
208 #if PD_DEBUG_MESSAGE_MODEL
209 qDebug() << __func__ << pdComMsg.seqNo << resetMsg.seqNo;
210 #endif
211
212 ✗ Message *&msg = messageMap[path][pdComMsg.index];
213 ✗ if (!msg) {
214 ✗ msg = new Message();
215 ✗ msg->impl->fromPdComMessage(pdComMsg);
216 }
217 else {
218 ✗ if (not pdComMsg.text.empty()) {
219 ✗ msg->impl->serverText = pdComMsg.text.c_str();
220 }
221 }
222
223 ✗ auto msgItem = new MessageItem(msg, this, pdComMsg.time.count());
224 ✗ msgItem->seqNo = pdComMsg.seqNo;
225 ✗ msgItem->resetTime = resetMsg.time.count();
226 ✗ insertItem(msgItem);
227 }
228
229 /****************************************************************************/
230
231 /** Returns a wrapped version of a string.
232 */
233 ✗ QString MessageModel::Impl::wrapText(const QString &text, unsigned int width)
234 {
235 ✗ QString ret;
236 int lineOffset, i;
237
238 ✗ lineOffset = 0;
239 ✗ while (lineOffset + (int) width < text.length()) {
240 // search last space before line end
241 ✗ for (i = width; i >= 0; i--) {
242 ✗ if (text[lineOffset + i].isSpace()) {
243 ✗ break; // break at whitespace
244 }
245 }
246 ✗ if (i < 0) { // no whitespace found
247 ✗ i = width; // "hard" break at line end
248 }
249
250 ✗ ret += text.mid(lineOffset, i) + QChar(QChar::LineSeparator);
251 ✗ lineOffset += i + 1; // skip line and whitespace
252 }
253
254 ✗ ret += text.mid(lineOffset); // append remaining string
255 ✗ return ret;
256 }
257
258 /****************************************************************************/
259
260 ✗ void MessageModel::Impl::getHistoryMessage()
261 {
262 ✗ if (!messageManager) {
263 ✗ qWarning() << __func__ << "no message manager";
264 ✗ return;
265 }
266
267 #if PD_DEBUG_MESSAGE_MODEL
268 qDebug() << __func__ << "setting canFetchMore to false";
269 #endif
270 ✗ canFetchMore = false; // avoid fetchMore called twice for same seqNo
271
272 ✗ uint32_t prevSeqNo = historicSeqNo - 1;
273 #if PD_DEBUG_MESSAGE_MODEL
274 qDebug() << __func__ << "fetching" << prevSeqNo;
275 #endif
276
277 try {
278 ✗ messageManager->getMessage(prevSeqNo, this, &Impl::getMessageReply);
279 }
280 ✗ catch (PdCom::Exception &e) {
281 ✗ qDebug() << __func__ << e.what();
282 }
283 }
284
285 /****************************************************************************/
286
287 ✗ void MessageModel::Impl::announce()
288 {
289 ✗ MessageItemList sortedList(messageItemList);
290 ✗ std::sort(
291 ✗ sortedList.begin(),
292 ✗ sortedList.end(),
293 MessageItem::levelNoLessThan);
294
295 // if there is no active message, announce a nullptr
296 ✗ if (not sortedList.front()->isActive()) {
297 ✗ if (announcedMessageItem) {
298 ✗ announcedMessageItem = nullptr;
299 #if PD_DEBUG_MESSAGE_MODEL
300 qDebug() << __func__ << "currentMessage null";
301 #endif
302 ✗ emit parent->currentMessage(nullptr);
303 }
304 ✗ return;
305 }
306
307 // the first active message in the sorted list determines the type
308 ✗ auto type = sortedList.front()->getType();
309
310 // find the earliest message with this type
311 ✗ MessageItem *candidate {nullptr};
312 ✗ for (MessageItemList::const_iterator it = sortedList.begin();
313 ✗ (it != sortedList.end() and (*it)->isActive()
314 ✗ and (*it)->getType() == type);
315 ++it) {
316 ✗ candidate = *it;
317 }
318
319 ✗ if (announcedMessageItem == candidate) {
320 // still the same message, no new announcement needed
321 ✗ return;
322 }
323 ✗ announcedMessageItem = candidate;
324
325 ✗ const QtPdCom::Message *msg {nullptr};
326 ✗ if (announcedMessageItem) {
327 ✗ msg = announcedMessageItem->message;
328 }
329 #if PD_DEBUG_MESSAGE_MODEL
330 qDebug() << __func__ << "currentMessage" << announcedMessageItem << msg;
331 #endif
332 ✗ emit parent->currentMessage(msg);
333 }
334
335 /*****************************************************************************
336 * private slots
337 ****************************************************************************/
338
339 /** Reacts on process values changes of all messages to watch.
340 */
341 ✗ void MessageModel::Impl::stateChanged()
342 {
343 ✗ Message *msg = (Message *) sender();
344 ✗ DoubleVariable &var = msg->impl->variable;
345 ✗ double time {var.hasData() ? var.getValue() : 0.0};
346
347 #if PD_DEBUG_MESSAGE_MODEL
348 qDebug() << __func__ << msg->getPath() << msg->getIndex() << time;
349 #endif
350
351 ✗ if (time and not msg->impl->announced) {
352 ✗ emit parent->anyMessage(msg);
353 ✗ msg->impl->announced = true;
354 }
355
356 ✗ MessageItem *msgItem {nullptr};
357
358 ✗ if (time) { // set
359 ✗ MessageItem *msgItem {msg->impl->currentItem};
360 ✗ if (!msgItem) {
361 ✗ msgItem = new MessageItem(msg, this, time * 1e9);
362 #if PD_DEBUG_MESSAGE_MODEL
363 qDebug() << __func__ << "new msgItem" << msgItem;
364 #endif
365 ✗ msg->impl->currentItem = msgItem;
366 ✗ insertItem(msgItem);
367 }
368 }
369 else { // reset
370 ✗ msg->impl->announced = false;
371 ✗ msgItem = msg->impl->currentItem;
372 ✗ if (msgItem) {
373 ✗ auto now {QDateTime::currentDateTime()};
374 ✗ msgItem->resetTime = now.toMSecsSinceEpoch() * 1000000U;
375
376 // notify views
377 ✗ int row = messageItemList.indexOf(msgItem);
378 #if PD_DEBUG_MESSAGE_MODEL
379 qDebug() << __func__ << "reset row" << row;
380 #endif
381 ✗ if (row >= 0) {
382 ✗ QModelIndex idx0 = parent->index(row, 0);
383 ✗ QModelIndex idx1 = parent->index(row, 2);
384 ✗ emit parent->dataChanged(idx0, idx1);
385 }
386
387 ✗ msg->impl->currentItem = nullptr;
388 ✗ if (msgItem == announcedMessageItem) {
389 ✗ announce();
390 }
391 }
392 }
393 }
394
395 /****************************************************************************/
396
397 ✗ void MessageModel::Impl::processMessage(PdCom::Message message)
398 {
399 #if PD_DEBUG_MESSAGE_MODEL
400 auto path = QString::fromStdString(message.path);
401 auto text = QString::fromStdString(message.text);
402 qDebug() << __func__;
403 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
404 << "path" << path << "time" << message.time.count() << "index"
405 << message.index << "text" << text;
406 #endif
407
408 ✗ addProcessMessage(message);
409 }
410
411 /****************************************************************************/
412
413 ✗ void MessageModel::Impl::getMessageReply(PdCom::Message message)
414 {
415 ✗ auto path = QString::fromStdString(message.path);
416
417 #if PD_DEBUG_MESSAGE_MODEL
418 auto text = QString::fromStdString(message.text);
419 qDebug() << __func__;
420 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
421 << "path" << path << "time" << message.time.count() << "index"
422 << message.index << "text" << text;
423 #endif
424
425 ✗ if (path.isEmpty()) {
426 // EOF marker - no more messages from process
427 ✗ return;
428 }
429
430 ✗ historicSeqNo = message.seqNo;
431
432 ✗ canFetchMore = message.level != PdCom::LogLevel::Reset;
433 #if PD_DEBUG_MESSAGE_MODEL
434 qDebug() << __func__ << "setting canFetchMore to" << canFetchMore;
435 #endif
436
437 ✗ bool stillActive {false};
438 ✗ if (canFetchMore) {
439 // found a message that was set in the past. try to find the reset.
440 #if PD_DEBUG_MESSAGE_MODEL
441 bool found {false};
442 qDebug() << __func__ << "reset msg list size is"
443 << resetMessagesList.size();
444 #endif
445 ✗ for (auto r = resetMessagesList.begin(); r != resetMessagesList.end();
446 r++) {
447 ✗ if (r->path == message.path and r->index == message.index) {
448 ✗ addHistoricMessage(message, *r);
449 ✗ resetMessagesList.erase(r);
450 #if PD_DEBUG_MESSAGE_MODEL
451 found = true;
452 #endif
453 ✗ break;
454 }
455 }
456 #if PD_DEBUG_MESSAGE_MODEL
457 if (!found) {
458 qDebug() << __func__ << "reset message not found for"
459 << message.seqNo;
460 // found a message that seems to be still active. Go on with
461 // reading, otherwise views won't ask for more data
462 stillActive = true;
463 }
464 #endif
465 }
466 else {
467 ✗ resetMessagesList.append(message);
468 }
469
470 ✗ if ((!canFetchMore or stillActive) and historicSeqNo) {
471 ✗ getHistoryMessage();
472 }
473 }
474
475 /****************************************************************************/
476
477 ✗ void MessageModel::Impl::activeMessagesReply(
478 std::vector<PdCom::Message> messageList)
479 {
480 ✗ quint32 maxSeqNo {0};
481
482 #if PD_DEBUG_MESSAGE_MODEL
483 qDebug().nospace() << __func__ << "(" << messageList.size() << ")";
484 #endif
485 ✗ for (auto message : messageList) {
486 #if PD_DEBUG_MESSAGE_MODEL
487 auto path = QString::fromStdString(message.path);
488 auto text = QString::fromStdString(message.text);
489 qDebug() << "seqNo" << message.seqNo << "level" << (int) message.level
490 << "path" << path << "time" << message.time.count()
491 << "index" << message.index << "text" << text;
492 #endif
493 ✗ if (message.level != PdCom::LogLevel::Reset) {
494 ✗ addProcessMessage(message);
495 }
496 else {
497 // one entry in the list of active messages can be a reset
498 // message, to announce the current sequence number
499 ✗ resetMessagesList.append(message);
500 }
501
502 ✗ if (message.seqNo > maxSeqNo) {
503 ✗ maxSeqNo = message.seqNo;
504 }
505 }
506
507 ✗ if (maxSeqNo > 0) {
508 // now fetch one historic message
509 ✗ historicSeqNo = maxSeqNo;
510 ✗ getHistoryMessage();
511 }
512 }
513
514 /****************************************************************************/
515
516 ✗ void MessageModel::Impl::processReset()
517 {
518 #if PD_DEBUG_MESSAGE_MODEL
519 qDebug() << __func__;
520 #endif
521
522 ✗ canFetchMore = false;
523 ✗ historicSeqNo = 0;
524 ✗ resetMessagesList.clear();
525
526 ✗ parent->beginResetModel();
527 ✗ qDeleteAll(messageItemList);
528 ✗ messageItemList.clear();
529 ✗ parent->endResetModel();
530
531 // reset current item pointers
532 ✗ for (auto hash : messageMap) {
533 ✗ for (auto msg : hash) {
534 ✗ if (msg->impl->currentItem) {
535 ✗ msg->impl->currentItem = nullptr;
536 }
537 }
538 }
539
540 ✗ if (announcedMessageItem) {
541 ✗ announcedMessageItem = nullptr;
542 #if PD_DEBUG_MESSAGE_MODEL
543 qDebug() << __func__ << "currentMessage null";
544 #endif
545 ✗ emit parent->currentMessage(nullptr);
546 }
547 }
548
549 /****************************************************************************/
550
551 ✗ void MessageModel::Impl::reloadActiveMessages()
552 {
553 ✗ if (!messageManager) {
554 ✗ return;
555 }
556
557 ✗ messageManager->activeMessages(this, &Impl::activeMessagesReply);
558 }
559
560 /****************************************************************************/
561