GCC Code Coverage Report


Directory: ./
File: qtpdcom/src/MessageModel.cpp
Date: 2025-02-23 04:08:29
Exec Total Coverage
Lines: 0 217 0.0%
Branches: 0 378 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 "MessageModel.h"
23 using QtPdCom::MessageModel;
24
25 #include "MessageImpl.h"
26 #include "MessageItem.h"
27 #include "MessageManager.h"
28 #include "MessageModelImpl.h"
29 #include "Process.h"
30
31 #include <QEvent>
32 #include <QFile>
33 #include <QDomDocument>
34 #include <QMetaEnum>
35 #include <QtGlobal>
36
37 /****************************************************************************/
38
39 /** Constructor.
40 */
41 MessageModel::MessageModel(QObject *parent):
42 QAbstractTableModel(parent),
43 impl(std::unique_ptr<Impl>(new MessageModel::Impl(this)))
44 {}
45
46 /****************************************************************************/
47
48 /** Destructor.
49 */
50 MessageModel::~MessageModel()
51 {
52 clear();
53 }
54
55 /****************************************************************************/
56
57 /** Loads messages from an Xml file.
58 */
59 void MessageModel::load(
60 const QString &path, /**< Path to Xml file. */
61 const QString &lang, /**< Language identifier. */
62 const QString &pathPrefix /**< Prefix to path (with leading /). */
63 )
64 {
65 QFile file(path);
66 QDomDocument doc;
67 QDomElement docElem;
68
69 if (!file.open(QIODevice::ReadOnly)) {
70 throw Exception(QtPdCom::MessageModel::tr("Failed to open %1.")
71 .arg(file.fileName()));
72 }
73
74 #if QT_VERSION < 0x060500
75 {
76 QString errorMessage;
77 int errorRow, errorColumn;
78 if (!doc.setContent(&file, &errorMessage, &errorRow, &errorColumn)) {
79 throw Exception(
80 QtPdCom::MessageModel::tr("Failed to parse %1, line %2,"
81 " column %3: %4")
82 .arg(file.fileName())
83 .arg(errorRow)
84 .arg(errorColumn)
85 .arg(errorMessage));
86 }
87 }
88 #else
89 {
90 QDomDocument::ParseResult res = doc.setContent(&file);
91 if (not res) {
92 throw Exception(
93 QtPdCom::MessageModel::tr("Failed to parse %1, line %2,"
94 " column %3: %4")
95 .arg(file.fileName())
96 .arg(res.errorLine)
97 .arg(res.errorColumn)
98 .arg(res.errorMessage));
99 }
100 }
101 #endif
102 file.close();
103
104 docElem = doc.documentElement();
105
106 if (docElem.tagName() != "EtherLabPlainMessages") {
107 throw Exception(
108 QtPdCom::MessageModel::tr("Failed to process %1:"
109 " No plain message file (%2)!"));
110 }
111
112 QDomNodeList children = docElem.childNodes();
113 QDomNode node;
114 QDomElement child;
115
116 for (int i = 0; i < children.size(); i++) {
117 node = children.item(i);
118 if (node.isElement()) {
119 child = node.toElement();
120 if (child.tagName() == "Message") {
121 try {
122 QString path = Message::Impl::pathFromPlainXmlElement(
123 child,
124 pathPrefix);
125 int index =
126 Message::Impl::indexFromPlainXmlElement(child);
127
128 Message *&msg = impl->messageMap[path][index];
129 if (!msg) {
130 msg = new Message();
131 }
132 msg->impl->fromPlainXmlElement(child, pathPrefix);
133 QObject::connect(
134 msg,
135 SIGNAL(stateChanged()),
136 impl.get(),
137 SLOT(stateChanged()));
138 }
139 catch (Message::Exception &e) {
140 qWarning() << e.msg;
141 }
142 }
143 }
144 }
145
146 impl->lang = lang;
147 }
148
149 /****************************************************************************/
150
151 /** Clears the messages.
152 */
153 void MessageModel::clear()
154 {
155 if (impl->announcedMessageItem) {
156 impl->announcedMessageItem = nullptr;
157 #if PD_DEBUG_MESSAGE_MODEL
158 qDebug() << __func__ << "currentMessage null";
159 #endif
160 emit currentMessage(nullptr);
161 }
162
163 if (impl->messageItemList.count()) {
164 beginRemoveRows(QModelIndex(), 0, impl->messageItemList.count() - 1);
165 qDeleteAll(impl->messageItemList);
166 impl->messageItemList.clear();
167 endRemoveRows();
168 }
169
170 for (auto &h : impl->messageMap) {
171 qDeleteAll(h);
172 }
173
174 impl->messageMap.clear();
175 }
176
177 /****************************************************************************/
178
179 /** Sets the maximum number of rows to fetch.
180 */
181 void MessageModel::setRowLimit(int limit /**< Maximum number of rows. */)
182 {
183 impl->rowLimit = limit;
184 }
185
186 /****************************************************************************/
187
188 /** Gets the maxium number of rows to fetch.
189 */
190 int MessageModel::getRowLimit() const
191 {
192 return impl->rowLimit;
193 }
194
195 /****************************************************************************/
196
197 /** Connects messages to the given process.
198 */
199 void MessageModel::connect(QtPdCom::Process *process /**< PdCom process. */
200 )
201 {
202 impl->processReset();
203 impl->messageManager = nullptr;
204 impl->process = process;
205
206 if (!process) {
207 return;
208 }
209
210 impl->messageManager =
211 dynamic_cast<MessageManager *>(process->getMessageManager());
212 if (impl->messageManager) {
213 QObject::connect(
214 impl->messageManager,
215 &MessageManager::processMessageSignal,
216 impl.get(),
217 &Impl::processMessage);
218 QObject::connect(
219 process,
220 &Process::processConnected,
221 impl.get(),
222 &Impl::reloadActiveMessages);
223 QObject::connect(
224 impl->messageManager,
225 &MessageManager::processResetSignal,
226 impl.get(),
227 &Impl::processReset);
228 if (process->isConnected()) {
229 impl->reloadActiveMessages();
230 }
231 }
232 else {
233 qWarning() << QtPdCom::MessageModel::tr(
234 "Failed to connect to message manager.");
235 }
236
237 for (auto h : impl->messageMap) {
238 Impl::MessageHash::iterator i;
239 for (i = h.begin(); i != h.end(); ++i) {
240 if (i.key() != -1) { /* FIXME vector messages */
241 continue;
242 }
243 Message *msg = i.value();
244 PdCom::Selector selector;
245
246 if (msg->getIndex() > -1) {
247 selector = PdCom::ScalarSelector({msg->getIndex()});
248 }
249 #if PD_DEBUG_MESSAGE_MODEL
250 qDebug() << "Subscribing to" << msg->getPath() << msg->getIndex();
251 #endif
252 try {
253 msg->impl->variable.setVariable(
254 process,
255 msg->getPath(),
256 selector);
257 }
258 catch (AbstractScalarVariable::Exception &e) {
259 qWarning() << QtPdCom::MessageModel::tr(
260 "Failed to subscribe to %1: %2")
261 .arg(msg->getPath())
262 .arg(e.msg);
263 }
264 }
265 }
266 }
267
268 /****************************************************************************/
269
270 QtPdCom::Process *MessageModel::getProcess() const
271 {
272 return impl->process;
273 }
274
275 /****************************************************************************/
276
277 /** Sets a new language and notifies views.
278 */
279 void MessageModel::translate(const QString &lang)
280 {
281 impl->lang = lang;
282
283 for (int i = 0; i < impl->messageItemList.count(); i++) {
284 QModelIndex idx = index(i, 0); // only text column
285 emit dataChanged(idx, idx);
286 }
287
288 if (impl->announcedMessageItem) {
289 #if PD_DEBUG_MESSAGE_MODEL
290 qDebug() << __func__ << "currentMessage" << impl->announcedMessageItem
291 << impl->announcedMessageItem->message;
292 #endif
293 emit currentMessage(impl->announcedMessageItem->message);
294 }
295 }
296
297 /****************************************************************************/
298
299 /** Sets an icon for a specific message type.
300 */
301 void MessageModel::setIcon(Message::Type type, const QIcon &icon)
302 {
303 impl->iconHash[type] = icon;
304 if (rowCount({}) > 0) {
305 emit dataChanged(
306 index(0, TextColumn),
307 index(rowCount({}) - 1, TextColumn),
308 {Qt::DecorationRole});
309 }
310 }
311
312 /****************************************************************************/
313
314 /** \return Icon for given message type.
315 */
316 const QIcon &MessageModel::getIcon(Message::Type type) const
317 {
318 return impl->iconHash[type];
319 }
320
321 /****************************************************************************/
322
323 /** Sets the path (url) to an icon for a specific message type.
324 If the model is used with QML views this is the only way
325 to store the icon information.
326 */
327 void MessageModel::setIconPath(Message::Type type, const QString &iconPath)
328 {
329 impl->iconPathHash[type] = iconPath;
330 // and also add it to the iconHash
331 setIcon(type, QIcon(iconPath));
332 /* vice versa would be nice, but does not work because
333 to a QIcon the Path is not known */
334 }
335
336 /****************************************************************************/
337
338 QVariantMap QtPdCom::MessageModel::getIconPathMap() const
339 {
340 const auto metaEnum = QMetaEnum::fromType<Message::Type>();
341 QVariantMap ans;
342 for (auto it = impl->iconPathHash.keyBegin();
343 it != impl->iconPathHash.keyEnd();
344 ++it) {
345 ans[QString(metaEnum.valueToKey(*it))] = impl->iconPathHash[*it];
346 }
347 return ans;
348 }
349
350 /****************************************************************************/
351
352 void QtPdCom::MessageModel::setIconPathMap(QVariantMap const map)
353 {
354 const auto metaEnum = QMetaEnum::fromType<Message::Type>();
355 bool ok = false;
356 for (auto it = map.keyBegin(); it != map.keyEnd(); ++it) {
357 const auto key_enum =
358 metaEnum.keyToValue(it->toStdString().c_str(), &ok);
359 if (ok) {
360 setIconPath(
361 static_cast<QtPdCom::Message::Type>(key_enum),
362 map[*it].toString());
363 }
364 }
365 }
366
367 /****************************************************************************/
368
369 /** Implements the model interface.
370 *
371 * \returns Number of active messages.
372 */
373 int MessageModel::rowCount(const QModelIndex &index) const
374 {
375 if (!index.isValid()) {
376 return impl->messageItemList.count();
377 }
378 else {
379 return 0;
380 }
381 }
382
383 /****************************************************************************/
384
385 /** Implements the model interface.
386 *
387 * \returns Number of columns.
388 */
389 int MessageModel::columnCount(const QModelIndex &index) const
390 {
391 Q_UNUSED(index);
392 return 3;
393 }
394
395 /****************************************************************************/
396
397 /** Implements the Model interface.
398 */
399 QVariant MessageModel::data(const QModelIndex &index, int role) const
400 {
401 if (!index.isValid()) {
402 return QVariant();
403 }
404
405 auto msgItem = impl->messageItemList[index.row()];
406
407 switch (index.column()) {
408 case TextColumn: // text
409 switch (role) {
410 case Qt::DisplayRole:
411 return msgItem->getText(impl->lang);
412 case Qt::DecorationRole:
413 return impl->iconHash[msgItem->getType()];
414 case DecorationPathRole:
415 return impl->iconPathHash[msgItem->getType()];
416 case TimeStringRole:
417 // it is necessary to return other columns under
418 // column 0 because in QML a View can't address
419 // from column but only from roles
420 return msgItem->getTimeString();
421 case ResetTimeStringRole:
422 return msgItem->getResetTimeString();
423 case MessageTypeRole:
424 return msgItem->getType();
425 case Qt::ToolTipRole:
426 return impl->wrapText(
427 msgItem->getDescription(impl->lang));
428 default:
429 return QVariant();
430 }
431 break;
432
433 case TimeOccurredColumn: // set time
434 switch (role) {
435 case Qt::DisplayRole:
436 return msgItem->getTimeString();
437 case Qt::UserRole + 1:
438 return "";
439 default:
440 return QVariant();
441 }
442 break;
443
444 case TimeResetColumn: // reset time
445 switch (role) {
446 case Qt::DisplayRole:
447 return msgItem->getResetTimeString();
448 case Qt::UserRole + 1:
449 return "";
450 default:
451 return QVariant();
452 }
453 break;
454
455 default:
456 return QVariant();
457 }
458 }
459
460 /****************************************************************************/
461
462 /** Implements the Model interface.
463 */
464 QVariant
465 MessageModel::headerData(int section, Qt::Orientation o, int role) const
466 {
467 if (role == Qt::DisplayRole && o == Qt::Horizontal) {
468 switch (section) {
469 case TextColumn:
470 return QtPdCom::MessageModel::tr("Message");
471
472 case TimeOccurredColumn:
473 return QtPdCom::MessageModel::tr("Time");
474
475 case TimeResetColumn:
476 return QtPdCom::MessageModel::tr("Reset");
477
478 default:
479 return QVariant();
480 }
481 }
482 else {
483 return QVariant();
484 }
485 }
486
487 /****************************************************************************/
488
489 /** Implements the Model interface.
490 */
491 Qt::ItemFlags MessageModel::flags(const QModelIndex &index) const
492 {
493 if (!index.isValid()) {
494 return Qt::ItemFlags();
495 }
496
497 auto msgItem {impl->messageItemList[index.row()]};
498 return msgItem->isActive() ? Qt::ItemIsEnabled : Qt::ItemFlags();
499 }
500
501 /****************************************************************************/
502
503 /** Additional Rolename for decoration for use in QML views
504 */
505
506 QHash<int, QByteArray> MessageModel::roleNames() const
507 {
508 // default role names
509 QHash<int, QByteArray> roles = QAbstractTableModel::roleNames();
510
511 // extended role names
512 roles[DecorationPathRole] = "decorationPath";
513 roles[TimeStringRole] = "timeString";
514 roles[ResetTimeStringRole] = "resetTimeString";
515 roles[MessageTypeRole] = "messageTyp";
516 return roles;
517 }
518
519 /****************************************************************************/
520
521 void MessageModel::fetchMore(const QModelIndex &parent)
522 {
523 #if PD_DEBUG_MESSAGE_MODEL
524 qDebug() << __func__ << parent;
525 #endif
526 if (canFetchMore(parent)) {
527 #if PD_DEBUG_MESSAGE_MODEL
528 qDebug() << __func__ << parent << "fetching";
529 #endif
530 impl->getHistoryMessage();
531 }
532 }
533
534 /****************************************************************************/
535
536 bool MessageModel::canFetchMore(const QModelIndex &parent) const
537 {
538 bool ret = !parent.isValid() and impl->canFetchMore
539 and impl->historicSeqNo
540 and impl->messageItemList.count() < impl->rowLimit;
541 #if PD_DEBUG_MESSAGE_MODEL
542 qDebug() << __func__ << parent << "can" << impl->canFetchMore << "hist"
543 << impl->historicSeqNo << "space"
544 << (impl->messageItemList.count() < impl->rowLimit) << "ret"
545 << ret;
546 #endif
547 return ret;
548 }
549
550 /****************************************************************************/
551
552 /** Event handler.
553 */
554 bool MessageModel::event(QEvent *event /**< Paint event flags. */
555 )
556 {
557 if (event->type() == QEvent::LanguageChange) {
558 emit headerDataChanged(Qt::Horizontal, 0, 1);
559 }
560
561 return QAbstractTableModel::event(event);
562 }
563
564 /****************************************************************************/
565