GCC Code Coverage Report


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