LogViewer.cpp
Go to the documentation of this file.
1/*
2* This file is part of ArmarX.
3*
4* ArmarX is free software; you can redistribute it and/or modify
5* it under the terms of the GNU General Public License version 2 as
6* published by the Free Software Foundation.
7*
8* ArmarX is distributed in the hope that it will be useful, but
9* WITHOUT ANY WARRANTY; without even the implied warranty of
10* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11* GNU General Public License for more details.
12*
13* You should have received a copy of the GNU General Public License
14* along with this program. If not, see <http://www.gnu.org/licenses/>.
15*
16* @package ArmarX::
17* @author Mirko Waechter ( mirko.waechter at kit dot edu)
18* @date 2012
19* @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20* GNU General Public License
21*/
22
23#include "LogViewer.h"
24
25#include <ArmarXCore/interface/core/ManagedIceObjectDefinitions.h>
26
27#include <ArmarXGui/gui-plugins/LoggingPlugin/ui_FilterDialog.h>
28
29#include "FilterDialog.h"
30#include "LogTable.h"
31#include "LogTableModel.h"
32
33// C++ includes
34#include <algorithm>
35#include <iterator>
36#include <sstream>
37
38// Qt includes
39#include <boost/algorithm/string/regex.hpp>
40
41#include <QColorDialog>
42#include <QComboBox>
43#include <QInputDialog>
44#include <QListWidget>
45#include <QMainWindow>
46#include <QMenu>
47#include <QPixmap>
48#include <QScrollBar>
49#include <QShortcut>
50#include <QStatusBar>
51#include <QTimer>
52#include <QToolBar>
53
54
55#define USERROLE_LOGTABLEID Qt::UserRole + 1
56#define USERROLE_LOGTABLEPTR Qt::UserRole + 2
57#define USERROLE_BASENAME Qt::UserRole + 3
58#define REGEX_COLORS "\033\\[(\\d|\\w?)[;]?(\\d+)m"
59#define ALL_MESSAGES_FILTER "All Messages"
60
61namespace armarx
62{
64 logTable(NULL), loggingPaused(false), verbosityLevel(1), customToolbar(0)
65
66 {
67 qRegisterMetaType<Qt::Orientation>("Qt::Orientation");
68 qRegisterMetaType<std::string>("std::string");
69 ui.setupUi(getWidget());
70
71 for (int i = 0; i < eLogLevelCount; i++)
72 {
73 ui.cbVerbosityLevel->addItem(LogSender::levelToString((MessageTypeT)i).c_str());
74 }
75
76 ui.cbVerbosityLevel->setCurrentIndex(1);
77 // Keep the atomic mirror of the verbosity level in sync so the Ice-thread
78 // writeLog can read it without touching the QComboBox.
79 verbosityLevel = ui.cbVerbosityLevel->currentIndex();
80 connect(ui.cbVerbosityLevel,
81 QOverload<int>::of(&QComboBox::currentIndexChanged),
82 this,
83 [this](int index) { verbosityLevel = index; });
85 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
86 // Sorting stays off here: with it enabled, every setText() on an item -- i.e.
87 // every new-message badge update -- re-sorts the entire tree. The filter set only
88 // changes when a filter is added, so sortFilterList() is called explicitly there.
89 ui.lvFilters->setSortingEnabled(false);
90 sortFilterList();
91 pendingEntriesTimer = new QTimer(getWidget());
92 pendingEntriesTimer->setInterval(50);
93
94 connect(pendingEntriesTimer, SIGNAL(timeout()), this, SLOT(insertPendingEntries()));
95 connect(this, SIGNAL(componentConnected()), pendingEntriesTimer, SLOT(start()));
96
97 // The new-message badges do not need the ingest rate; refreshing them relayouts
98 // and repaints the filter tree.
99 filterBadgeTimer = new QTimer(getWidget());
100 filterBadgeTimer->setInterval(250);
101 connect(filterBadgeTimer, SIGNAL(timeout()), this, SLOT(updateFilterList()));
102 connect(this, SIGNAL(componentConnected()), filterBadgeTimer, SLOT(start()));
103
104
105 QList<int> sizes;
106 sizes.push_back(80);
107 sizes.push_back(400);
108 ui.splitter->setSizes(sizes);
109 addFilter(
110 "Warning+",
111 "Warning+",
112 "",
113 "",
114 "",
115 eWARN,
116 "",
117 "",
118 ""); // add additional filters after splitter->setSizes-> otherwise they have a width of NULL
119
120
121 qRegisterMetaType<LogMessage>("LogMessage");
122
123 // SIGNALS AND SLOTS CONNECTIONS
124 connect(
125 ui.edtLiveFilter, SIGNAL(textChanged(QString)), this, SLOT(performLiveFilter(QString)));
126 connect(
127 ui.edtLiveSearch, SIGNAL(textChanged(QString)), this, SLOT(performLiveSearch(QString)));
128 connect(ui.btnAddFilter, SIGNAL(clicked()), this, SLOT(addFilter()));
129 connect(ui.btnRemoveFilter, SIGNAL(clicked()), this, SLOT(removeSelectedFilter()));
130 connect(ui.btnPause, SIGNAL(toggled(bool)), this, SLOT(pauseLogging(bool)));
131 connect(ui.btnClearLog, SIGNAL(clicked()), this, SLOT(clearSelectedLog()));
132 connect(ui.btnClearAllLogs, SIGNAL(clicked()), this, SLOT(clearAllLogs()));
133 connect(ui.lvFilters,
134 SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
135 this,
136 SLOT(filterSelectionChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
137 connect(ui.lvFilters,
138 SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
139 this,
140 SLOT(editFilter(QTreeWidgetItem*, int)));
141 connect(
142 ui.cbSearchType, SIGNAL(currentIndexChanged(int)), this, SLOT(searchTypeChanged(int)));
143 connect(this, SIGNAL(updateFilterListSignal()), this, SLOT(updateFilterList()));
144 connect(ui.btnNextItem, SIGNAL(clicked()), this, SLOT(selectNextSearchResult()));
145 connect(ui.btnPreviousItem, SIGNAL(clicked()), this, SLOT(selectPreviousSearchResult()));
146
147 // Labels panel: mirror the pin registry, navigate on click, edit via context menu.
148 connect(&markerRegistry, &LogMarkerRegistry::markersChanged, this, &LogViewer::refreshLabelsPanel);
149 connect(ui.lvLabels, &QListWidget::itemClicked, this, &LogViewer::labelClicked);
150 connect(ui.lvLabels,
151 &QWidget::customContextMenuRequested,
152 this,
154 QShortcut* deleteLabelShortcut = new QShortcut(QKeySequence::Delete, ui.lvLabels);
155 deleteLabelShortcut->setContext(Qt::WidgetShortcut);
156 connect(deleteLabelShortcut, &QShortcut::activated, this, &LogViewer::removeSelectedLabel);
158
159 ui.edtLiveFilter->hide();
160 ui.edtLiveSearch->setFocus();
161 }
162
164 {
165 // ARMARX_VERBOSE << "~LogViewer";
166 }
167
168 void
170 {
171 ui.cbVerbosityLevel->setCurrentIndex(settings->value("verbosityLevel", 2).toInt());
172 ui.cbAutoComponentFilters->setChecked(settings->value("autoFilterAdding", true).toBool());
173 }
174
175 void
177 {
178
179 settings->setValue("verbosityLevel", ui.cbVerbosityLevel->currentIndex());
180
181 settings->setValue("autoFilterAdding", ui.cbAutoComponentFilters->isChecked());
182
183
184 // // saving filters
185
186 // QString filterName = ui.lvFilters->items()->data(USERROLE_LOGTABLEID).toString();
187 // for(unsigned int i= 0; i< logTable->getModel()->getFilters().size(); i++)
188 // {
189 // std::string columnName = logTable->getModel()->getFilters()[i].first;
190 // std::string filter = logTable->getModel()->getFilters()[i].second;
191 // if(columnName == ARMARX_LOG_COMPONENTSTR)
192 // filterDialog.ui->editComponent->setText(filter.c_str());
193 // else if(columnName == ARMARX_LOG_TAGSTR)
194 // filterDialog.ui->edtTag->setText(filter.c_str());
195 // else if(columnName == ARMARX_LOG_VERBOSITYSTR){
196
197 // filterDialog.ui->cbVerbosity->setCurrentIndex(QString(filter.c_str()).toInt());
198 // }
199 // else if(columnName == ARMARX_LOG_MESSAGESTR)
200 // filterDialog.ui->edtMessage->setText(filter.c_str());
201 // else if(columnName == ARMARX_LOG_FILESTR)
202 // filterDialog.ui->edtFile->setText(filter.c_str());
203 // else if(columnName == ARMARX_LOG_FUNCTIONSTR)
204 // filterDialog.ui->edtFunction->setText(filter.c_str());
205 // }
206 }
207
208 void
213
214 void
219
220 bool
222 {
223 // QInputDialog dialog;
224 // if(dialog.exec() == QDialog::Rejected)
225 // return false;
226
228 }
229
230 QString
231 LogViewer::loggingGroupNameToFilterName(const QString& loggingGroupName) const
232 {
233 return loggingGroupName.isEmpty() ? "" : ("#" + loggingGroupName);
234 }
235
236 void
237 LogViewer::sortFilterList()
238 {
239 ui.lvFilters->sortItems(0, Qt::AscendingOrder);
240 }
241
242 QString
243 LogViewer::autoFilterId(const QString& loggingGroupName, const QString& componentName) const
244 {
245 const QString filler =
246 (loggingGroupName.length() > 0 && componentName.length() > 0) ? "_" : "";
247 return loggingGroupNameToFilterName(loggingGroupName) + filler + componentName;
248 }
249
250 LogTable*
251 LogViewer::ensureAutoFilter(const QString& filterId,
252 const QString& loggingGroupName,
253 const QString& componentName,
254 bool mayCreate)
255 {
256 if (filterId.isEmpty())
257 {
258 return nullptr;
259 }
260
261 const auto it = autoFilterMap.find(filterId);
262 if (it != autoFilterMap.end())
263 {
264 return it.value();
265 }
266
267 if (!mayCreate || !ui.cbAutoComponentFilters->isChecked() || filterMap.count(filterId))
268 {
269 // Auto filters are switched off, or the id is already taken by a user-created
270 // filter -- which is a general filter and therefore already sees this message.
271 return nullptr;
272 }
273
274 const QString filterName = componentName.length() > 0
275 ? componentName
276 : loggingGroupNameToFilterName(loggingGroupName);
277 ARMARX_CHECK_EXPRESSION(!filterName.isEmpty());
278
279 LogTable* table = addFilter(
280 filterId, filterName, loggingGroupName, componentName, "", eDEBUG, "", "", "");
281 if (table)
282 {
283 // addFilter registers every new table as a general filter; this one can be
284 // routed to, so move it into the dispatch map.
285 generalFilters.erase(std::remove(generalFilters.begin(), generalFilters.end(), table),
286 generalFilters.end());
287 autoFilterMap.insert(filterId, table);
288 }
289 return table;
290 }
291
292 void
293 LogViewer::ingestBatch(LogTable* table, const std::vector<LogSeq>& seqs)
294 {
295 if (!table || seqs.empty())
296 {
297 return;
298 }
299
300 const QString liveFilter = table->getCurrentLiveFilter();
301 MessageType batchMaxLevel = eUNDEFINED;
302 const int rowsAdded = table->getModel()->addEntries(seqs, &batchMaxLevel);
303
304 if (!table->getModel()->isMaterialised())
305 {
306 // A hidden table builds no rows, so its rowsInserted never fires; feed the
307 // new-message badge from here instead.
308 table->notifyNewMessages(rowsAdded, batchMaxLevel);
309 return;
310 }
311
312 if (rowsAdded > 0 && !liveFilter.isEmpty())
313 {
314 // The new rows are always the last rowsAdded ones. The row count is re-read
315 // here because addEntries() may have trimmed the buffer, which shifts every
316 // index down.
317 const int count = table->getModel()->rowCount();
318 for (int r = std::max(0, count - rowsAdded); r < count; r++)
319 {
320 table->liveFilterRow(liveFilter, r);
321 }
322 }
323 }
324
325 void
329
330 void
331 LogViewer::write(const std::string& who,
332 Ice::Long time,
333 const std::string& tag,
334 MessageType severity,
335 const std::string& message,
336 const std::string& file,
337 Ice::Int line,
338 const std::string& function,
339 const Ice::Current&)
340 {
341 LogMessage msg;
342 msg.who = who;
343 msg.time = time;
344 msg.tag = tag;
345 msg.type = severity;
346 msg.what = message;
347 msg.file = file;
348 msg.line = line;
349 msg.function = function;
350
351 writeLog(msg);
352 }
353
354 void
355 LogViewer::writeLog(const LogMessage& msg, const Ice::Current&)
356 {
357 if (loggingPaused)
358 {
359 return;
360 }
361
362 // Runs on an Ice dispatch thread: use the mirrored verbosity level instead of
363 // reading the (GUI-thread-only) QComboBox. insertPendingEntries re-checks the
364 // level on the GUI thread anyway.
365 if (verbosityLevel.load() <= msg.type)
366 {
367 std::unique_lock lock(pendingEntriesMutex);
368 if (pendingEntries.size() >= maxPendingEntries)
369 {
370 // The GUI thread cannot keep up. Drop rather than let the backlog grow
371 // without bound, which would only make the next tick slower still. The
372 // count is surfaced in the status bar by updateFilterList().
373 ++droppedEntryCount;
374 return;
375 }
376 pendingEntries.push_back(msg);
377 }
378 }
379
380 void
381 LogViewer::performLiveFilter(QString filterStr, int startRow)
382 {
383 // if((IceUtil::Time::now() - lastLiveSearchEditChangeTime).toSeconds() < 1)
384 // return;
385 if (filterStr.length() == 0)
386 {
387 logTable->resetLiveFilter();
388 }
389 else if (filterStr.length() < 3)
390 {
391 return;
392 }
393
394 // LogSearch search(logTable);
395 // search.search(searchStr);
396 logTable->liveFilter(filterStr, startRow);
397 lastLiveSearchEditChangeTime = IceUtil::Time::now();
398 }
399
400 void
402 {
403 if (searchStr.length() == 0)
404 {
405 logTable->resetLiveSearch();
406 ui.btnNextItem->setEnabled(false);
407 ui.btnPreviousItem->setEnabled(false);
408 }
409 // else if(searchStr.length() < 3)
410 // return;
411 else
412 {
413 if (!logTable->liveSearch(searchStr))
414 {
415 getMainWindow()->statusBar()->showMessage(
416 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
417 "' in the log!",
418 5000);
419 ui.btnNextItem->setEnabled(false);
420 ui.btnPreviousItem->setEnabled(false);
421 }
422 else
423 {
424 ui.btnNextItem->setEnabled(true);
425 ui.btnPreviousItem->setEnabled(true);
426 }
427 }
428 }
429
430 void
432 {
433 if (logTable)
434 {
435 logTable->getModel()->clearData();
436 // The cleared line may have been a pin's last native home.
437 expireOrphanedMarkers();
438 }
439 }
440
441 void
443 {
444 // Drop the shared history first, so that clearData() below records a watermark past
445 // everything and no filter can bring old lines back when it is next materialised.
446 store.clear();
447
448 std::map<QString, LogTable*>::iterator it = filterMap.begin();
449
450 for (; it != filterMap.end(); it++)
451 {
452 it->second->getModel()->clearData();
453 }
454
455 // Pins are log-scoped: clearing all logs drops them all.
456 markerRegistry.clear();
457
459 }
460
461 void
462 LogViewer::expireOrphanedMarkers()
463 {
464 // A pin lives exactly as long as the store keeps the line it refers to. With a
465 // shared store that is a single range check per marker, instead of searching every
466 // filter's buffer for a matching message.
467 markerRegistry.expireBefore(store.firstSeq());
468 }
469
470 void
472 {
473 loggingPaused = pause;
474 }
475
476 void
477 LogViewer::setupLogTable(LogTable* logTable)
478 {
479 // Every filter is a view onto the one shared buffer; it keeps sequence numbers into
480 // it rather than its own copies of the messages.
481 logTable->getModel()->setStore(&store);
482
483 // Share the pin registry so this filter's model injects/colors pinned lines and
484 // stays in sync as pins are added or removed anywhere.
485 logTable->getModel()->setMarkerRegistry(&markerRegistry);
486
487 // Route the table's context-menu actions to the shared registry.
488 connect(logTable,
490 this,
491 [this](LogSeq seq) { markerRegistry.add(seq); });
492 connect(logTable,
494 this,
495 [this](LogSeq seq)
496 {
497 if (const LogMarkerId id = markerRegistry.markerIdForSeq(seq))
498 {
499 markerRegistry.remove(id);
500 }
501 });
502 }
503
504 LogTable*
506 {
507 LogTable* newLogTable = new LogTable();
508 setupLogTable(newLogTable);
509 // newLogTable->setColumns(standardColumns);
510 QTreeWidgetItem* item = new QTreeWidgetItem(ui.lvFilters);
511 QString standardFilterStr = ALL_MESSAGES_FILTER;
512 item->setText(0, standardFilterStr);
513 item->setData(0, USERROLE_LOGTABLEID, standardFilterStr);
514 item->setData(0, USERROLE_BASENAME, standardFilterStr);
515 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
516 // ui.lvFilters->addItem(item);
517 filterMap[standardFilterStr] = newLogTable;
518 // "All Messages" has no filters at all, so it can never be routed on.
519 generalFilters.push_back(newLogTable);
520 ui.splitter->addWidget(newLogTable);
521
522 return newLogTable;
523 }
524
525 LogTable*
526 LogViewer::addFilter(QString filterId,
527 QString filterName,
528 QString loggingGroup,
529 QString componentFilter,
530 QString tagFilter,
531 MessageType minimumVerbosity,
532 QString messageFilter,
533 QString fileFilter,
534 QString functionFilter)
535 {
536
537 if (filterMap.find(filterId) != filterMap.end())
538 {
539 QString msg = "A filter with the id " + filterId + " exists already";
540 showMessageBox(msg);
541 return NULL;
542 }
543
544 LogTable* newLogTable = new LogTable();
545 setupLogTable(newLogTable);
546 newLogTable->hide();
547
548 // This helper builds the auto-generated per-group / per-component filters, which
549 // must isolate exactly one group/component -- so match the group and component
550 // values exactly rather than as substrings (otherwise "bar" would also collect
551 // "foo_bar").
552 if (loggingGroup.length())
553 {
554 newLogTable->getModel()->addFilter(
555 ARMARX_LOG_LOGGINGGROUPSTR, loggingGroup.toStdString(), MatchMode::Exact);
556 }
557
558 // newLogTable->setColumns(standardColumns);
559 if (componentFilter.length())
560 {
561 newLogTable->getModel()->addFilter(
562 ARMARX_LOG_COMPONENTSTR, componentFilter.toStdString(), MatchMode::Exact);
563 }
564
565 if (tagFilter.length())
566 {
567 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR, tagFilter.toStdString());
568 }
569
570 if (messageFilter.length())
571 {
572 newLogTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR, messageFilter.toStdString());
573 }
574
575 if (fileFilter.length())
576 {
577 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR, fileFilter.toStdString());
578 }
579
580 if (functionFilter.length())
581 {
583 functionFilter.toStdString());
584 }
585
587 QString::number(minimumVerbosity).toStdString());
588
589 QTreeWidgetItem* item = new QTreeWidgetItem();
590 item->setText(0, filterName);
591 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
592 item->setData(0, USERROLE_LOGTABLEID, filterId);
593 item->setData(0, USERROLE_BASENAME, filterName);
594 filterMap[filterId] = newLogTable;
595 // Register as a general filter by default, so that any caller gets a table that
596 // actually receives messages. ensureAutoFilter() promotes the auto-generated ones
597 // into the dispatch map afterwards.
598 generalFilters.push_back(newLogTable);
599 Ice::StringSeq children;
600 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
601 {
602 children.push_back(ui.lvFilters->invisibleRootItem()->child(i)->text(0).toStdString());
603 }
604
605 QTreeWidgetItem* grpItem = NULL;
606 auto grpFilterId = loggingGroupNameToFilterName(loggingGroup);
607 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
608 {
609 if (ui.lvFilters->invisibleRootItem()
610 ->child(i)
611 ->data(0, USERROLE_BASENAME)
612 .toString() == grpFilterId)
613 {
614 grpItem = ui.lvFilters->invisibleRootItem()->child(i);
615 break;
616 }
617 }
618 if (grpItem)
619 {
620 grpItem->addChild(item);
621 // items.at(0)->sortChildren(0, Qt::AscendingOrder);
622 }
623 else
624 {
625 // ARMARX_INFO << loggingGroup.toStdString() << " group not found - adding " << filterId.toStdString() << " as toplevel\n" << children;
626 ui.lvFilters->insertTopLevelItem(0, item);
627 }
628 // The tree's own sorting is off, so re-sort explicitly now that the set changed.
629 sortFilterList();
630
631 ui.splitter->addWidget(newLogTable);
632 return newLogTable;
633 }
634
635 bool
636 LogViewer::checkAndAddNewFilter(const QString& loggingGroupName, const QString& componentName)
637 {
638 if (componentName.length() == 0 && loggingGroupName.length() == 0)
639 {
640 return false;
641 }
642
643 const QString filterId = autoFilterId(loggingGroupName, componentName);
644 const bool existedBefore = autoFilterMap.contains(filterId);
645 return ensureAutoFilter(filterId, loggingGroupName, componentName, true) != nullptr &&
646 !existedBefore;
647 }
648
649 void
651 {
652 FilterDialog filterDialog;
653
654 if (!filterDialog.exec())
655 {
656 return;
657 }
658
659 QString filterName = filterDialog.ui->edtFilterName->text();
660
661 if (filterMap.find(filterName) != filterMap.end())
662 {
663 QString msg = "A filter with this name already exists";
664 showMessageBox(msg);
665 return;
666 }
667
668 LogTable* newLogTable = new LogTable();
669 setupLogTable(newLogTable);
670 newLogTable->hide();
671
672 // newLogTable->setColumns(standardColumns);
673 if (filterDialog.ui->editComponent->text().length())
674 {
675 newLogTable->getModel()->addFilter(
676 ARMARX_LOG_COMPONENTSTR, filterDialog.ui->editComponent->text().toStdString());
677 }
678
679 if (filterDialog.ui->edtTag->text().length())
680 {
681 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
682 filterDialog.ui->edtTag->text().toStdString());
683 }
684
685 if (filterDialog.ui->edtMessage->text().length())
686 {
688 filterDialog.ui->edtMessage->text().toStdString());
689 }
690
691 if (filterDialog.ui->edtFile->text().length())
692 {
693 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
694 filterDialog.ui->edtFile->text().toStdString());
695 }
696
697 if (filterDialog.ui->edtFunction->text().length())
698 {
700 filterDialog.ui->edtFunction->text().toStdString());
701 }
702
703 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
704 {
705 newLogTable->getModel()->addFilter(
707 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
708 }
709
710 QTreeWidgetItem* item = new QTreeWidgetItem();
711 item->setText(0, filterName);
712 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
713 item->setData(0, USERROLE_LOGTABLEID, filterName);
714 item->setData(0, USERROLE_BASENAME, filterName);
715 filterMap[filterName] = newLogTable;
716 // User-created filters use arbitrary substring criteria, so they cannot be routed
717 // on and have to see every message.
718 generalFilters.push_back(newLogTable);
719 ui.lvFilters->insertTopLevelItem(ui.lvFilters->invisibleRootItem()->childCount(), item);
720 // The tree's own sorting is off, so re-sort explicitly now that the set changed.
721 sortFilterList();
722 ui.splitter->addWidget(newLogTable);
723 }
724
725 void
726 LogViewer::editFilter(QTreeWidgetItem* item, int column)
727 {
728 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
729 FilterDialog filterDialog;
730 filterDialog.ui->edtFilterName->setText(item->data(0, USERROLE_BASENAME).toString());
731
732 if (filterMap.find(filterId) == filterMap.end())
733 {
734 ARMARX_WARNING << "filter " << filterId.toStdString() << " does not exist" << flush;
735 return;
736 }
737
738 LogTable* logTable = filterMap.find(filterId)->second;
739
740 if (!logTable)
741 {
742 ARMARX_WARNING << "logtable ptr is NULL " << flush;
743 return;
744 }
745
746 for (const LogFilter& activeFilter : logTable->getModel()->getFilters())
747 {
748 const QString value = QString::fromStdString(activeFilter.value);
749
750 switch (activeFilter.column)
751 {
753 filterDialog.ui->editComponent->setText(value);
754 break;
755 case LogColumn::Tag:
756 filterDialog.ui->edtTag->setText(value);
757 break;
759 filterDialog.ui->cbVerbosity->setCurrentIndex(value.toInt());
760 break;
762 filterDialog.ui->edtMessage->setText(value);
763 break;
764 case LogColumn::File:
765 filterDialog.ui->edtFile->setText(value);
766 break;
768 filterDialog.ui->edtFunction->setText(value);
769 break;
770 default:
771 break;
772 }
773 }
774
775
776 if (!filterDialog.exec())
777 {
778 return;
779 }
780 item->setText(0, filterDialog.ui->edtFilterName->text());
781 item->setData(0, USERROLE_BASENAME, filterDialog.ui->edtFilterName->text());
782 logTable->getModel()->resetFilters();
783
784 if (filterDialog.ui->editComponent->text().length())
785 {
786 logTable->getModel()->addFilter(ARMARX_LOG_COMPONENTSTR,
787 filterDialog.ui->editComponent->text().toStdString());
788 }
789
790 if (filterDialog.ui->edtTag->text().length())
791 {
792 logTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
793 filterDialog.ui->edtTag->text().toStdString());
794 }
795
796 if (filterDialog.ui->edtMessage->text().length())
797 {
798 logTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR,
799 filterDialog.ui->edtMessage->text().toStdString());
800 }
801
802 if (filterDialog.ui->edtFile->text().length())
803 {
804 logTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
805 filterDialog.ui->edtFile->text().toStdString());
806 }
807
808 if (filterDialog.ui->edtFunction->text().length())
809 {
810 logTable->getModel()->addFilter(ARMARX_LOG_FUNCTIONSTR,
811 filterDialog.ui->edtFunction->text().toStdString());
812 }
813
814 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
815 {
816 logTable->getModel()->addFilter(
818 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
819 }
820
821 // An edited filter no longer matches the (group, component) pair that its id
822 // encodes, so it must not be routed on any more -- otherwise it would only ever be
823 // offered the messages of the component it was originally generated for, and its
824 // new criteria would silently never see anything else.
825 if (autoFilterMap.remove(filterId) > 0)
826 {
827 generalFilters.push_back(logTable);
828 }
829
830 logTable->getModel()->reapplyAllFilters();
831 logTable->update();
832 }
833
834 void
836 {
837 if (ui.lvFilters->invisibleRootItem()->childCount() == 1)
838 {
839 return;
840 }
841
842 auto item = ui.lvFilters->currentItem();
843 removeFilter(item);
844 }
845
846 void
847 LogViewer::removeFilter(QTreeWidgetItem* item)
848 {
849 if (!item)
850 {
851 return;
852 }
853
854 std::map<QString, LogTable*>::iterator it =
855 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
856
857 if (it == filterMap.end())
858 {
859 return;
860 }
861 QList<QTreeWidgetItem*> itemsToDelete, iterationList({item});
862 while (!iterationList.isEmpty())
863 {
864 auto curItem = iterationList.front();
865 itemsToDelete << curItem;
866 iterationList.pop_front();
867 iterationList << curItem->takeChildren();
868 }
869 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
870 for (auto& item : itemsToDelete)
871 {
872 auto parent = item->parent();
873 if (parent)
874 {
875 parent->removeChild(item);
876 }
877 else
878 {
879 ui.lvFilters->invisibleRootItem()->removeChild(item);
880 }
881 std::map<QString, LogTable*>::iterator it =
882 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
883 delete item;
884
885 if (it == filterMap.end())
886 {
887 continue;
888 }
889 LogTable* logTable = it->second;
890 // Drop every routing reference before the table is destroyed.
891 generalFilters.erase(
892 std::remove(generalFilters.begin(), generalFilters.end(), logTable),
893 generalFilters.end());
894 autoFilterMap.remove(it->first);
895 delete logTable;
896 filterBadgeCache.erase(it->first);
897 filterMap.erase(it);
898 }
899 }
900
901 void
902 LogViewer::filterSelectionChanged(QTreeWidgetItem* item, QTreeWidgetItem* previous)
903 {
904 if (!item)
905 {
906 return;
907 }
908 LogTable* oldLogTable = logTable;
909 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
910 // item->setText(0, filterId); Why?
911 QFont font;
912 font.setBold(false);
913 item->setFont(0, font);
914
915 if (filterMap.find(filterId) == filterMap.end())
916 {
917 showMessageBox("Filtername " + filterId + " not found.");
918 return;
919 }
920
921 logTable = filterMap[filterId];
922
923 if (!logTable)
924 {
925 ARMARX_ERROR << "logTable ptr is NULL" << flush;
926 return;
927 }
928
929
930 if (oldLogTable)
931 {
932 oldLogTable->hide();
933 }
934
935 logTable->show();
936 ui.edtLiveFilter->setText(logTable->getLiveFilterStr());
937 ui.edtLiveSearch->setText(logTable->getModel()->getCurrentSearchStr());
938 }
939
940 void
942 {
943 static QFont font;
944 QList<QTreeWidgetItem*> itemsToUpdate, iterationList({ui.lvFilters->invisibleRootItem()});
945 while (!iterationList.isEmpty())
946 {
947 auto curItem = iterationList.front();
948 itemsToUpdate << curItem;
949 iterationList.pop_front();
950 for (int i = 0; i < curItem->childCount(); ++i)
951 {
952 iterationList << curItem->child(i);
953 }
954 }
955 // Update new message count in filter list box
956 for (auto item : itemsToUpdate)
957 {
958 auto filterId = item->data(0, USERROLE_LOGTABLEID).toString();
959 auto filterIt = filterMap.find(filterId);
960 if (filterIt == filterMap.end())
961 {
962 // e.g. the invisible root item, whose id is empty and not in filterMap
963 continue;
964 }
965 LogTable* curLogtable = filterIt->second;
966 if (!curLogtable)
967 {
968 continue;
969 }
970
971 // Skip items whose badge has not changed. Every setText() / setFont() /
972 // setBackgroundColor() on the tree costs a relayout and a repaint, and in
973 // steady state almost every item is unchanged.
974 const int newCount =
975 (curLogtable == logTable) ? 0 : curLogtable->getNewMessageCount();
976 const MessageType newLevel = curLogtable->getMaxNewLogLevelType();
977 const auto cached = filterBadgeCache.find(filterId);
978 if (cached != filterBadgeCache.end() && cached->second.count == newCount &&
979 cached->second.level == newLevel)
980 {
981 continue;
982 }
983 filterBadgeCache[filterId] = FilterBadge{newCount, newLevel};
984
985 QString newContent;
986
987 if (newCount == 0)
988 {
989 font.setBold(false);
990 item->setFont(0, font);
991 newContent = item->data(0, USERROLE_BASENAME).toString();
992 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
993 }
994 else
995 {
996 font.setBold(true);
997 item->setFont(0, font);
998 newContent = item->data(0, USERROLE_BASENAME).toString() + "(" +
999 QString::number(newCount) + ")";
1000
1001 if (newLevel == eWARN)
1002 {
1003 item->setBackgroundColor(0, QColor(216, 120, 50));
1004 }
1005 else if (newLevel == eERROR)
1006 {
1007 item->setBackgroundColor(0, QColor(255, 90, 80));
1008 }
1009 else if (newLevel == eFATAL)
1010 {
1011 item->setBackgroundColor(0, QColor(255, 60, 50));
1012 }
1013 else
1014 {
1015 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
1016 }
1017 }
1018
1019 item->setText(0, newContent);
1020 item->setToolTip(0, newContent);
1021 }
1022
1023 // Surface ingest overload: writeLog drops messages once the staging queue is
1024 // saturated, and silently losing log lines would be worse than a slow GUI.
1025 const unsigned long long dropped = droppedEntryCount.load();
1026 if (dropped != lastReportedDropCount)
1027 {
1028 lastReportedDropCount = dropped;
1029 if (getMainWindow() && getMainWindow()->statusBar())
1030 {
1031 getMainWindow()->statusBar()->showMessage(
1032 QString("LogViewer: dropped %1 log messages (ingest overloaded)")
1033 .arg(dropped),
1034 5000);
1035 }
1036 }
1037 }
1038
1039 void
1041 {
1042 throw LocalException() << "Not yet implemented";
1043 }
1044
1045 void
1047 {
1048 ui.lvLabels->clear();
1049 for (const LogMarker& marker : markerRegistry.markers())
1050 {
1051 if (!store.contains(marker.seq))
1052 {
1053 continue;
1054 }
1055 const LogMessage& snapshot = store.at(marker.seq).message;
1056
1057 // Color swatch.
1058 QPixmap swatch(12, 12);
1059 swatch.fill(marker.color);
1060
1061 // Short, single-line description: time . component . message.
1062 IceUtil::Time time = IceUtil::Time::microSeconds(snapshot.time);
1063 std::string timeStr = time.toDateTime();
1064 const auto spacePos = timeStr.find(' ');
1065 if (spacePos != std::string::npos)
1066 {
1067 timeStr = timeStr.substr(spacePos + 1);
1068 }
1069
1070 QString what = QString::fromStdString(snapshot.what);
1071 const int newlinePos = what.indexOf('\n');
1072 if (newlinePos >= 0)
1073 {
1074 what.truncate(newlinePos);
1075 }
1076 constexpr int maxWhatLength = 60;
1077 if (what.length() > maxWhatLength)
1078 {
1079 what.truncate(maxWhatLength);
1080 what += "...";
1081 }
1082
1083 const QString text = QString::fromStdString(timeStr) + " " +
1084 QString::fromStdString(snapshot.who) + " " + what;
1085
1086 QListWidgetItem* item = new QListWidgetItem(QIcon(swatch), text, ui.lvLabels);
1087 item->setData(Qt::UserRole, static_cast<qulonglong>(marker.id));
1088 item->setToolTip(text);
1089 }
1090 }
1091
1092 void
1093 LogViewer::labelClicked(QListWidgetItem* item)
1094 {
1095 if (!item || !logTable)
1096 {
1097 return;
1098 }
1099 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1100
1101 for (const LogMarker& marker : markerRegistry.markers())
1102 {
1103 if (marker.id != id)
1104 {
1105 continue;
1106 }
1107 // The pinned line is present (matched or injected) in every filter, so it
1108 // resolves in whichever table is currently shown.
1109 const int row = logTable->getModel()->rowForSeq(marker.seq);
1110 if (row >= 0)
1111 {
1112 const QModelIndex index = logTable->getModel()->index(row, 0);
1113 logTable->scrollTo(index, QAbstractItemView::PositionAtCenter);
1114 logTable->selectRow(row);
1115 logTable->setFocus();
1116 }
1117 break;
1118 }
1119 }
1120
1121 void
1123 {
1124 QListWidgetItem* item = ui.lvLabels->itemAt(pos);
1125 if (!item)
1126 {
1127 return;
1128 }
1129 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1130
1131 QMenu menu;
1132 QAction* recolorAction = menu.addAction(tr("Change color..."));
1133 QAction* removeAction = menu.addAction(tr("Remove label"));
1134 QAction* chosen = menu.exec(ui.lvLabels->viewport()->mapToGlobal(pos));
1135
1136 if (chosen == removeAction)
1137 {
1138 markerRegistry.remove(id);
1139 }
1140 else if (chosen == recolorAction)
1141 {
1142 QColor initial;
1143 for (const LogMarker& marker : markerRegistry.markers())
1144 {
1145 if (marker.id == id)
1146 {
1147 initial = marker.color;
1148 break;
1149 }
1150 }
1151 const QColor color =
1152 QColorDialog::getColor(initial, getWidget(), tr("Choose label color"));
1153 if (color.isValid())
1154 {
1155 markerRegistry.recolor(id, color);
1156 }
1157 }
1158 }
1159
1160 void
1162 {
1163 QListWidgetItem* item = ui.lvLabels->currentItem();
1164 if (!item)
1165 {
1166 return;
1167 }
1168 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1169 markerRegistry.remove(id);
1170 }
1171
1172 void
1174 {
1175
1176 if (getState() >= eManagedIceObjectExiting)
1177 {
1178 return;
1179 }
1180
1181 std::vector<LogMessage> pendingEntriesTemp;
1182 {
1183 std::unique_lock lock(pendingEntriesMutex);
1184 if (pendingEntries.size() <= maxEntriesPerTick)
1185 {
1186 pendingEntriesTemp.swap(pendingEntries);
1187 }
1188 else
1189 {
1190 // Carry the remainder over to the next tick instead of letting a single
1191 // burst hold the GUI thread for an unbounded time.
1192 const auto batchEnd = pendingEntries.begin() + maxEntriesPerTick;
1193 pendingEntriesTemp.assign(std::make_move_iterator(pendingEntries.begin()),
1194 std::make_move_iterator(batchEnd));
1195 pendingEntries.erase(pendingEntries.begin(), batchEnd);
1196 }
1197 }
1198
1199
1200 // One pass over the batch: strip ANSI colours, create any missing auto-filter for
1201 // this group/component, and bucket the message by the auto-filters it can reach.
1202 // Bucketing here is what makes ingestion independent of the number of filters: a
1203 // message is afterwards only offered to the general filters plus the (at most two)
1204 // tables that can accept it, instead of to every table in filterMap.
1205 std::vector<LogSeq> wholeBatch;
1206 wholeBatch.reserve(pendingEntriesTemp.size());
1207 QHash<LogTable*, std::vector<LogSeq>> autoBuckets;
1208
1209 // Compile the ANSI-color regex once, not on every timer tick.
1210 static const boost::regex re(REGEX_COLORS);
1211 for (LogMessage& msg : pendingEntriesTemp)
1212 {
1213 // Only run the regex if an escape character is actually present.
1214 if (msg.what.find('\033') != std::string::npos)
1215 {
1216 msg.what = boost::regex_replace(msg.what, re, "");
1217 }
1218
1219 // The message is stored exactly once here; every filter that accepts it only
1220 // keeps this sequence number.
1221 const LogSeq seq = store.append(msg);
1222 wholeBatch.push_back(seq);
1223
1224 if (!getWidget())
1225 {
1226 continue;
1227 }
1228
1229 const QString group = QString::fromStdString(msg.group);
1230 const QString component = QString::fromStdString(msg.who);
1231 // Creating a filter stays gated on the selected verbosity, as before. Routing
1232 // is deliberately not gated: a message below the current level still reaches an
1233 // already existing filter, which is what testing it against every filter did.
1234 const bool mayCreate = ui.cbVerbosityLevel->currentIndex() <= msg.type;
1235
1236 const QString groupKey = autoFilterId(group, QString());
1237 const QString componentKey = autoFilterId(group, component);
1238
1239 if (LogTable* groupTable = ensureAutoFilter(groupKey, group, QString(), mayCreate))
1240 {
1241 autoBuckets[groupTable].push_back(seq);
1242 }
1243 if (componentKey != groupKey)
1244 {
1245 if (LogTable* componentTable =
1246 ensureAutoFilter(componentKey, group, component, mayCreate))
1247 {
1248 autoBuckets[componentTable].push_back(seq);
1249 }
1250 }
1251 }
1252
1253 // Sampled before inserting, because inserting rows moves the scrollbar maximum away
1254 // from the current value.
1255 const bool autoScroll =
1256 logTable &&
1257 logTable->verticalScrollBar()->value() == logTable->verticalScrollBar()->maximum();
1258
1259 for (LogTable* log : generalFilters)
1260 {
1261 ingestBatch(log, wholeBatch);
1262 }
1263 for (auto it = autoBuckets.begin(); it != autoBuckets.end(); ++it)
1264 {
1265 ingestBatch(it.key(), it.value());
1266 }
1267
1268 // Only the visible table is painted, so it is the only one that has to follow the
1269 // tail here; hidden tables scroll to the bottom in their showEvent.
1270 if (autoScroll && logTable)
1271 {
1272 logTable->scrollToBottom();
1273 }
1274
1275 // Bound the shared history, then let every filter discard the rows whose entry has
1276 // just been dropped. This has to happen after ingestion, so the sequence numbers of
1277 // the batch stay valid while the models consume them.
1278 if (store.trim())
1279 {
1280 const LogSeq firstSeq = store.firstSeq();
1281 for (auto& filterEntry : filterMap)
1282 {
1283 filterEntry.second->getModel()->dropRowsBefore(firstSeq);
1284 }
1285 // A pin whose line has just left the store is now orphaned.
1286 expireOrphanedMarkers();
1287 }
1288 }
1289
1290 void
1292 {
1293 if (index == 0)
1294 {
1295 ui.edtLiveSearch->show();
1296 ui.btnPreviousItem->show();
1297 ui.btnNextItem->show();
1298 ui.edtLiveFilter->hide();
1299 }
1300 else
1301 {
1302 ui.edtLiveSearch->hide();
1303 ui.btnPreviousItem->hide();
1304 ui.btnNextItem->hide();
1305 ui.edtLiveFilter->show();
1306 }
1307 }
1308
1309 void
1311 {
1312
1313 if (logTable)
1314 {
1315 if (!logTable->selectNextSearchResult(false))
1316 {
1317 getMainWindow()->statusBar()->showMessage(
1318 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1319 "' in the log!",
1320 5000);
1321 }
1322 }
1323 }
1324
1325 void
1327 {
1328 if (logTable)
1329 if (!logTable->selectNextSearchResult(true))
1330 {
1331 getMainWindow()->statusBar()->showMessage(
1332 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1333 "' in the log!",
1334 5000);
1335 }
1336 }
1337
1338 QPointer<QWidget>
1340 {
1341 if (customToolbar)
1342 {
1343 if (parent != customToolbar->parent())
1344 {
1345 customToolbar->setParent(parent);
1346 }
1347
1348 return customToolbar;
1349 }
1350
1351 customToolbar = new QToolBar(parent);
1352 customToolbar->setIconSize(QSize(16, 16));
1353 customToolbar->addAction(
1354 QIcon(":/icons/configure-3.png"), "Configure", this, SLOT(OpenConfigureDialog()));
1355
1356 return customToolbar;
1357 }
1358} // namespace armarx
uint8_t index
#define ARMARX_LOG_VERBOSITYSTR
Definition LogTable.h:44
#define ARMARX_LOG_COMPONENTSTR
Definition LogTable.h:42
#define ARMARX_LOG_FILESTR
Definition LogTable.h:46
#define ARMARX_LOG_TAGSTR
Definition LogTable.h:43
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:45
#define ARMARX_LOG_LOGGINGGROUPSTR
Definition LogTable.h:48
#define ARMARX_LOG_FUNCTIONSTR
Definition LogTable.h:47
#define USERROLE_BASENAME
Definition LogViewer.cpp:57
#define ALL_MESSAGES_FILTER
Definition LogViewer.cpp:59
#define USERROLE_LOGTABLEID
Definition LogViewer.cpp:55
#define REGEX_COLORS
Definition LogViewer.cpp:58
bool onClose() override
If you overwrite this method, make sure to call this implementation at the end of your implementation...
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
virtual QMainWindow * getMainWindow()
Returns the ArmarX MainWindow.
static int showMessageBox(const QString &msg)
Ui::FilterDialog * ui
bool expireBefore(LogSeq firstSeq)
Remove every marker whose line has been dropped from the store, i.e.
LogMarkerId markerIdForSeq(LogSeq seq) const
Id of the marker pinning seq, or 0 if it is not pinned.
void add(LogSeq seq)
Pin the store entry seq, assigning it the next Glasbey color.
void remove(LogMarkerId id)
Remove the marker with the given id (no-op if unknown).
static std::string levelToString(MessageTypeT type)
LogSeq firstSeq() const
Sequence number of the oldest retained entry.
Definition LogStore.cpp:111
void setMarkerRegistry(LogMarkerRegistry *registry)
Attach the shared pin registry.
void addFilter(const std::string &columnName, const std::string &filter, MatchMode mode=MatchMode::Contains)
void setStore(const LogStore *store)
Attach the shared backing store. Must be called before any ingestion.
MessageType getMaxNewLogLevelType()
Definition LogTable.h:81
LogTableModel * getModel()
Definition LogTable.cpp:281
void removeLabelRequested(LogSeq seq)
Emitted from the context menu to remove the pin on the given line.
void addLabelRequested(LogSeq seq)
Emitted from the context menu to pin the given line (assign a label).
int getNewMessageCount()
Definition LogTable.h:75
void pauseLogging(bool pause=false)
LogTable * logTable
Definition LogViewer.h:158
void componentConnected()
void selectPreviousSearchResult()
void onInitComponent() override
Pure virtual hook for the subclass.
void editFilter(QTreeWidgetItem *item, int column)
void insertPendingEntries()
bool onClose() override
If you overwrite this method, make sure to call this implementation at the end of your implementation...
void removeSelectedLabel()
Remove the pin currently selected in the Labels panel.
void refreshLabelsPanel()
Rebuild the Labels panel from the pin registry.
QPointer< QWidget > getCustomTitlebarWidget(QWidget *parent) override
getTitleToolbar returns a pointer to the a toolbar widget of this controller.
void selectNextSearchResult()
void removeSelectedFilter()
LogTable * addEmptyFilter()
void removeFilter(QTreeWidgetItem *item)
void loadSettings(QSettings *settings) override
Implement to load the settings that are part of the GUI configuration.
void saveSettings(QSettings *settings) override
Implement to save the settings as part of the GUI configuration.
void updateFilterListSignal()
void performLiveSearch(QString searchStr)
Ui_LogViewer ui
Definition LogViewer.h:157
~LogViewer() override
void onConnectComponent() override
Pure virtual hook for the subclass.
void write(const std::string &who, Ice::Long time, const std::string &tag, MessageType severity, const std::string &message, const std::string &file, Ice::Int line, const std::string &function, const Ice::Current &=Ice::emptyCurrent)
void filterSelectionChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous)
bool checkAndAddNewFilter(const QString &loggingGroupName, const QString &componentName)
This function checks, if there are new components in the log and if so, it creates new filters for th...
void onExitComponent() override
Hook for subclass.
void writeLog(const LogMessage &msg, const Ice::Current &=Ice::emptyCurrent) override
void performLiveFilter(QString searchStr, int startRow=0)
void labelClicked(QListWidgetItem *item)
Focus the clicked label's line in the currently shown message view.
void showLabelsContextMenu(const QPoint &pos)
Recolor / remove menu for the Labels panel.
void searchTypeChanged(int index)
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
int getState() const
Retrieve current state of the ManagedIceObject.
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::uint64_t LogMarkerId
quint64 LogSeq
Identifies a stored log entry for as long as it is retained.
Definition LogStore.h:37
const LogSender::manipulator flush
Definition LogSender.h:251
MessageTypeT
Definition LogSender.h:46
A single pinned log line: the store entry it refers to plus the color assigned to it.