GCC Code Coverage Report


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