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 <sstream>
35
36// Qt includes
37#include <boost/algorithm/string/regex.hpp>
38
39#include <QColorDialog>
40#include <QComboBox>
41#include <QInputDialog>
42#include <QListWidget>
43#include <QMainWindow>
44#include <QMenu>
45#include <QPixmap>
46#include <QScrollBar>
47#include <QShortcut>
48#include <QStatusBar>
49#include <QTimer>
50#include <QToolBar>
51
52
53#define USERROLE_LOGTABLEID Qt::UserRole + 1
54#define USERROLE_LOGTABLEPTR Qt::UserRole + 2
55#define USERROLE_BASENAME Qt::UserRole + 3
56#define REGEX_COLORS "\033\\[(\\d|\\w?)[;]?(\\d+)m"
57#define ALL_MESSAGES_FILTER "All Messages"
58
59namespace armarx
60{
62 logTable(NULL), loggingPaused(false), verbosityLevel(1), customToolbar(0)
63
64 {
65 qRegisterMetaType<Qt::Orientation>("Qt::Orientation");
66 qRegisterMetaType<std::string>("std::string");
67 ui.setupUi(getWidget());
68
69 for (int i = 0; i < eLogLevelCount; i++)
70 {
71 ui.cbVerbosityLevel->addItem(LogSender::levelToString((MessageTypeT)i).c_str());
72 }
73
74 ui.cbVerbosityLevel->setCurrentIndex(1);
75 // Keep the atomic mirror of the verbosity level in sync so the Ice-thread
76 // writeLog can read it without touching the QComboBox.
77 verbosityLevel = ui.cbVerbosityLevel->currentIndex();
78 connect(ui.cbVerbosityLevel,
79 QOverload<int>::of(&QComboBox::currentIndexChanged),
80 this,
81 [this](int index) { verbosityLevel = index; });
83 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
84 ui.lvFilters->setSortingEnabled(true);
85 ui.lvFilters->sortByColumn(0, Qt::AscendingOrder);
86 pendingEntriesTimer = new QTimer(getWidget());
87 pendingEntriesTimer->setInterval(50);
88
89 connect(pendingEntriesTimer, SIGNAL(timeout()), this, SLOT(insertPendingEntries()));
90 connect(this, SIGNAL(componentConnected()), pendingEntriesTimer, SLOT(start()));
91
92
93 QList<int> sizes;
94 sizes.push_back(80);
95 sizes.push_back(400);
96 ui.splitter->setSizes(sizes);
98 "Warning+",
99 "Warning+",
100 "",
101 "",
102 "",
103 eWARN,
104 "",
105 "",
106 ""); // add additional filters after splitter->setSizes-> otherwise they have a width of NULL
107
108
109 qRegisterMetaType<LogMessage>("LogMessage");
110
111 // SIGNALS AND SLOTS CONNECTIONS
112 connect(
113 ui.edtLiveFilter, SIGNAL(textChanged(QString)), this, SLOT(performLiveFilter(QString)));
114 connect(
115 ui.edtLiveSearch, SIGNAL(textChanged(QString)), this, SLOT(performLiveSearch(QString)));
116 connect(ui.btnAddFilter, SIGNAL(clicked()), this, SLOT(addFilter()));
117 connect(ui.btnRemoveFilter, SIGNAL(clicked()), this, SLOT(removeSelectedFilter()));
118 connect(ui.btnPause, SIGNAL(toggled(bool)), this, SLOT(pauseLogging(bool)));
119 connect(ui.btnClearLog, SIGNAL(clicked()), this, SLOT(clearSelectedLog()));
120 connect(ui.btnClearAllLogs, SIGNAL(clicked()), this, SLOT(clearAllLogs()));
121 connect(ui.lvFilters,
122 SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)),
123 this,
124 SLOT(filterSelectionChanged(QTreeWidgetItem*, QTreeWidgetItem*)));
125 connect(ui.lvFilters,
126 SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
127 this,
128 SLOT(editFilter(QTreeWidgetItem*, int)));
129 connect(
130 ui.cbSearchType, SIGNAL(currentIndexChanged(int)), this, SLOT(searchTypeChanged(int)));
131 connect(this, SIGNAL(updateFilterListSignal()), this, SLOT(updateFilterList()));
132 connect(ui.btnNextItem, SIGNAL(clicked()), this, SLOT(selectNextSearchResult()));
133 connect(ui.btnPreviousItem, SIGNAL(clicked()), this, SLOT(selectPreviousSearchResult()));
134
135 // Labels panel: mirror the pin registry, navigate on click, edit via context menu.
136 connect(&markerRegistry, &LogMarkerRegistry::markersChanged, this, &LogViewer::refreshLabelsPanel);
137 connect(ui.lvLabels, &QListWidget::itemClicked, this, &LogViewer::labelClicked);
138 connect(ui.lvLabels,
139 &QWidget::customContextMenuRequested,
140 this,
142 QShortcut* deleteLabelShortcut = new QShortcut(QKeySequence::Delete, ui.lvLabels);
143 deleteLabelShortcut->setContext(Qt::WidgetShortcut);
144 connect(deleteLabelShortcut, &QShortcut::activated, this, &LogViewer::removeSelectedLabel);
146
147 ui.edtLiveFilter->hide();
148 ui.edtLiveSearch->setFocus();
149 }
150
152 {
153 // ARMARX_VERBOSE << "~LogViewer";
154 }
155
156 void
158 {
159 ui.cbVerbosityLevel->setCurrentIndex(settings->value("verbosityLevel", 2).toInt());
160 ui.cbAutoComponentFilters->setChecked(settings->value("autoFilterAdding", true).toBool());
161 }
162
163 void
165 {
166
167 settings->setValue("verbosityLevel", ui.cbVerbosityLevel->currentIndex());
168
169 settings->setValue("autoFilterAdding", ui.cbAutoComponentFilters->isChecked());
170
171
172 // // saving filters
173
174 // QString filterName = ui.lvFilters->items()->data(USERROLE_LOGTABLEID).toString();
175 // for(unsigned int i= 0; i< logTable->getModel()->getFilters().size(); i++)
176 // {
177 // std::string columnName = logTable->getModel()->getFilters()[i].first;
178 // std::string filter = logTable->getModel()->getFilters()[i].second;
179 // if(columnName == ARMARX_LOG_COMPONENTSTR)
180 // filterDialog.ui->editComponent->setText(filter.c_str());
181 // else if(columnName == ARMARX_LOG_TAGSTR)
182 // filterDialog.ui->edtTag->setText(filter.c_str());
183 // else if(columnName == ARMARX_LOG_VERBOSITYSTR){
184
185 // filterDialog.ui->cbVerbosity->setCurrentIndex(QString(filter.c_str()).toInt());
186 // }
187 // else if(columnName == ARMARX_LOG_MESSAGESTR)
188 // filterDialog.ui->edtMessage->setText(filter.c_str());
189 // else if(columnName == ARMARX_LOG_FILESTR)
190 // filterDialog.ui->edtFile->setText(filter.c_str());
191 // else if(columnName == ARMARX_LOG_FUNCTIONSTR)
192 // filterDialog.ui->edtFunction->setText(filter.c_str());
193 // }
194 }
195
196 void
201
202 void
207
208 bool
210 {
211 // QInputDialog dialog;
212 // if(dialog.exec() == QDialog::Rejected)
213 // return false;
214
216 }
217
218 QString
219 LogViewer::loggingGroupNameToFilterName(const QString& loggingGroupName) const
220 {
221 return loggingGroupName.isEmpty() ? "" : ("#" + loggingGroupName);
222 }
223
224 void
228
229 void
230 LogViewer::write(const std::string& who,
231 Ice::Long time,
232 const std::string& tag,
233 MessageType severity,
234 const std::string& message,
235 const std::string& file,
236 Ice::Int line,
237 const std::string& function,
238 const Ice::Current&)
239 {
240 LogMessage msg;
241 msg.who = who;
242 msg.time = time;
243 msg.tag = tag;
244 msg.type = severity;
245 msg.what = message;
246 msg.file = file;
247 msg.line = line;
248 msg.function = function;
249
250 writeLog(msg);
251 }
252
253 void
254 LogViewer::writeLog(const LogMessage& msg, const Ice::Current&)
255 {
256 if (loggingPaused)
257 {
258 return;
259 }
260
261 // Runs on an Ice dispatch thread: use the mirrored verbosity level instead of
262 // reading the (GUI-thread-only) QComboBox. insertPendingEntries re-checks the
263 // level on the GUI thread anyway.
264 if (verbosityLevel.load() <= msg.type)
265 {
266 std::unique_lock lock(pendingEntriesMutex);
267 pendingEntries.push_back(msg);
268 }
269 }
270
271 void
272 LogViewer::performLiveFilter(QString filterStr, int startRow)
273 {
274 // if((IceUtil::Time::now() - lastLiveSearchEditChangeTime).toSeconds() < 1)
275 // return;
276 if (filterStr.length() == 0)
277 {
278 logTable->resetLiveFilter();
279 }
280 else if (filterStr.length() < 3)
281 {
282 return;
283 }
284
285 // LogSearch search(logTable);
286 // search.search(searchStr);
287 logTable->liveFilter(filterStr, startRow);
288 lastLiveSearchEditChangeTime = IceUtil::Time::now();
289 }
290
291 void
293 {
294 if (searchStr.length() == 0)
295 {
296 logTable->resetLiveSearch();
297 ui.btnNextItem->setEnabled(false);
298 ui.btnPreviousItem->setEnabled(false);
299 }
300 // else if(searchStr.length() < 3)
301 // return;
302 else
303 {
304 if (!logTable->liveSearch(searchStr))
305 {
306 getMainWindow()->statusBar()->showMessage(
307 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
308 "' in the log!",
309 5000);
310 ui.btnNextItem->setEnabled(false);
311 ui.btnPreviousItem->setEnabled(false);
312 }
313 else
314 {
315 ui.btnNextItem->setEnabled(true);
316 ui.btnPreviousItem->setEnabled(true);
317 }
318 }
319 }
320
321 void
323 {
324 if (logTable)
325 {
326 logTable->getModel()->clearData();
327 // The cleared line may have been a pin's last native home.
328 expireOrphanedMarkers();
329 }
330 }
331
332 void
334 {
335 std::map<QString, LogTable*>::iterator it = filterMap.begin();
336
337 for (; it != filterMap.end(); it++)
338 {
339 it->second->getModel()->clearData();
340 }
341
342 // Pins are log-scoped: clearing all logs drops them all.
343 markerRegistry.clear();
344
346 }
347
348 void
349 LogViewer::expireOrphanedMarkers()
350 {
351 // markers() returns a copy, so removing while iterating is safe.
352 for (const LogMarker& marker : markerRegistry.markers())
353 {
354 bool nativeSomewhere = false;
355 for (const auto& [filterId, table] : filterMap)
356 {
357 if (table->getModel()->containsNativeLine(marker.snapshot))
358 {
359 nativeSomewhere = true;
360 break;
361 }
362 }
363 if (!nativeSomewhere)
364 {
365 markerRegistry.remove(marker.id);
366 }
367 }
368 }
369
370 void
372 {
373 loggingPaused = pause;
374 }
375
376 void
377 LogViewer::setupLogTable(LogTable* logTable)
378 {
379 // Share the pin registry so this filter's model injects/colors pinned lines and
380 // stays in sync as pins are added or removed anywhere.
381 logTable->getModel()->setMarkerRegistry(&markerRegistry);
382
383 // Route the table's context-menu actions to the shared registry.
384 connect(logTable,
386 this,
387 [this](const LogMessage& message) { markerRegistry.add(message); });
388 connect(logTable,
390 this,
391 [this](const LogMessage& message)
392 {
393 if (const LogMarker* marker = markerRegistry.markerForMessage(message))
394 {
395 markerRegistry.remove(marker->id);
396 }
397 });
398 }
399
400 LogTable*
402 {
403 LogTable* newLogTable = new LogTable();
404 setupLogTable(newLogTable);
405 // newLogTable->setColumns(standardColumns);
406 QTreeWidgetItem* item = new QTreeWidgetItem(ui.lvFilters);
407 QString standardFilterStr = ALL_MESSAGES_FILTER;
408 item->setText(0, standardFilterStr);
409 item->setData(0, USERROLE_LOGTABLEID, standardFilterStr);
410 item->setData(0, USERROLE_BASENAME, standardFilterStr);
411 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
412 // ui.lvFilters->addItem(item);
413 filterMap[standardFilterStr] = newLogTable;
414 ui.splitter->addWidget(newLogTable);
415
416 return newLogTable;
417 }
418
419 LogTable*
420 LogViewer::addFilter(QString filterId,
421 QString filterName,
422 QString loggingGroup,
423 QString componentFilter,
424 QString tagFilter,
425 MessageType minimumVerbosity,
426 QString messageFilter,
427 QString fileFilter,
428 QString functionFilter)
429 {
430
431 if (filterMap.find(filterId) != filterMap.end())
432 {
433 QString msg = "A filter with the id " + filterId + " exists already";
434 showMessageBox(msg);
435 return NULL;
436 }
437
438 LogTable* newLogTable = new LogTable();
439 setupLogTable(newLogTable);
440 newLogTable->hide();
441
442 // This helper builds the auto-generated per-group / per-component filters, which
443 // must isolate exactly one group/component -- so match the group and component
444 // values exactly rather than as substrings (otherwise "bar" would also collect
445 // "foo_bar").
446 if (loggingGroup.length())
447 {
448 newLogTable->getModel()->addFilter(
449 ARMARX_LOG_LOGGINGGROUPSTR, loggingGroup.toStdString(), MatchMode::Exact);
450 }
451
452 // newLogTable->setColumns(standardColumns);
453 if (componentFilter.length())
454 {
455 newLogTable->getModel()->addFilter(
456 ARMARX_LOG_COMPONENTSTR, componentFilter.toStdString(), MatchMode::Exact);
457 }
458
459 if (tagFilter.length())
460 {
461 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR, tagFilter.toStdString());
462 }
463
464 if (messageFilter.length())
465 {
466 newLogTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR, messageFilter.toStdString());
467 }
468
469 if (fileFilter.length())
470 {
471 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR, fileFilter.toStdString());
472 }
473
474 if (functionFilter.length())
475 {
477 functionFilter.toStdString());
478 }
479
481 QString::number(minimumVerbosity).toStdString());
482
483 QTreeWidgetItem* item = new QTreeWidgetItem();
484 item->setText(0, filterName);
485 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
486 item->setData(0, USERROLE_LOGTABLEID, filterId);
487 item->setData(0, USERROLE_BASENAME, filterName);
488 filterMap[filterId] = newLogTable;
489 Ice::StringSeq children;
490 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
491 {
492 children.push_back(ui.lvFilters->invisibleRootItem()->child(i)->text(0).toStdString());
493 }
494
495 QTreeWidgetItem* grpItem = NULL;
496 auto grpFilterId = loggingGroupNameToFilterName(loggingGroup);
497 for (int i = 0; i < ui.lvFilters->invisibleRootItem()->childCount(); i++)
498 {
499 if (ui.lvFilters->invisibleRootItem()
500 ->child(i)
501 ->data(0, USERROLE_BASENAME)
502 .toString() == grpFilterId)
503 {
504 grpItem = ui.lvFilters->invisibleRootItem()->child(i);
505 break;
506 }
507 }
508 if (grpItem)
509 {
510 grpItem->addChild(item);
511 // items.at(0)->sortChildren(0, Qt::AscendingOrder);
512 }
513 else
514 {
515 // ARMARX_INFO << loggingGroup.toStdString() << " group not found - adding " << filterId.toStdString() << " as toplevel\n" << children;
516 ui.lvFilters->insertTopLevelItem(0, item);
517 // ui.lvFilters->invisibleRootItem()->sortChildren(0, Qt::AscendingOrder);
518 }
519
520 ui.splitter->addWidget(newLogTable);
521 return newLogTable;
522 }
523
524 bool
525 LogViewer::checkAndAddNewFilter(const QString& loggingGroupName, const QString& componentName)
526 {
527 if (componentName.length() == 0 && loggingGroupName.length() == 0)
528 {
529 return false;
530 }
531
532 if (!ui.cbAutoComponentFilters->isChecked())
533 {
534 return false;
535 }
536 QString filler = (loggingGroupName.length() > 0 && componentName.length() > 0) ? "_" : "";
537 auto filterId = loggingGroupNameToFilterName(loggingGroupName) + filler + componentName;
538 auto filterName = componentName.length() > 0
539 ? componentName
540 : loggingGroupNameToFilterName(loggingGroupName);
541 ARMARX_CHECK_EXPRESSION(!filterName.isEmpty());
542 if (filterMap.count(filterId))
543 {
544 return false;
545 }
546
547 addFilter(filterId, filterName, loggingGroupName, componentName, "", eDEBUG, "", "", "");
548 return true;
549 }
550
551 void
553 {
554 FilterDialog filterDialog;
555
556 if (!filterDialog.exec())
557 {
558 return;
559 }
560
561 QString filterName = filterDialog.ui->edtFilterName->text();
562
563 if (filterMap.find(filterName) != filterMap.end())
564 {
565 QString msg = "A filter with this name already exists";
566 showMessageBox(msg);
567 return;
568 }
569
570 LogTable* newLogTable = new LogTable();
571 setupLogTable(newLogTable);
572 newLogTable->hide();
573
574 // newLogTable->setColumns(standardColumns);
575 if (filterDialog.ui->editComponent->text().length())
576 {
577 newLogTable->getModel()->addFilter(
578 ARMARX_LOG_COMPONENTSTR, filterDialog.ui->editComponent->text().toStdString());
579 }
580
581 if (filterDialog.ui->edtTag->text().length())
582 {
583 newLogTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
584 filterDialog.ui->edtTag->text().toStdString());
585 }
586
587 if (filterDialog.ui->edtMessage->text().length())
588 {
590 filterDialog.ui->edtMessage->text().toStdString());
591 }
592
593 if (filterDialog.ui->edtFile->text().length())
594 {
595 newLogTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
596 filterDialog.ui->edtFile->text().toStdString());
597 }
598
599 if (filterDialog.ui->edtFunction->text().length())
600 {
602 filterDialog.ui->edtFunction->text().toStdString());
603 }
604
605 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
606 {
607 newLogTable->getModel()->addFilter(
609 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
610 }
611
612 QTreeWidgetItem* item = new QTreeWidgetItem();
613 item->setText(0, filterName);
614 // item->setData(USERROLE_LOGTABLEPTR, qVariantFromValue((void*)newLogTable));
615 item->setData(0, USERROLE_LOGTABLEID, filterName);
616 item->setData(0, USERROLE_BASENAME, filterName);
617 filterMap[filterName] = newLogTable;
618 ui.lvFilters->insertTopLevelItem(ui.lvFilters->invisibleRootItem()->childCount(), item);
619 // ui.lvFilters->invisibleRootItem()->sortChildren(0, Qt::AscendingOrder);
620 ui.splitter->addWidget(newLogTable);
621 }
622
623 void
624 LogViewer::editFilter(QTreeWidgetItem* item, int column)
625 {
626 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
627 FilterDialog filterDialog;
628 filterDialog.ui->edtFilterName->setText(item->data(0, USERROLE_BASENAME).toString());
629
630 if (filterMap.find(filterId) == filterMap.end())
631 {
632 ARMARX_WARNING << "filter " << filterId.toStdString() << " does not exist" << flush;
633 return;
634 }
635
636 LogTable* logTable = filterMap.find(filterId)->second;
637
638 if (!logTable)
639 {
640 ARMARX_WARNING << "logtable ptr is NULL " << flush;
641 return;
642 }
643
644 for (const LogFilter& activeFilter : logTable->getModel()->getFilters())
645 {
646 const QString value = QString::fromStdString(activeFilter.value);
647
648 switch (activeFilter.column)
649 {
651 filterDialog.ui->editComponent->setText(value);
652 break;
653 case LogColumn::Tag:
654 filterDialog.ui->edtTag->setText(value);
655 break;
657 filterDialog.ui->cbVerbosity->setCurrentIndex(value.toInt());
658 break;
660 filterDialog.ui->edtMessage->setText(value);
661 break;
662 case LogColumn::File:
663 filterDialog.ui->edtFile->setText(value);
664 break;
666 filterDialog.ui->edtFunction->setText(value);
667 break;
668 default:
669 break;
670 }
671 }
672
673
674 if (!filterDialog.exec())
675 {
676 return;
677 }
678 item->setText(0, filterDialog.ui->edtFilterName->text());
679 item->setData(0, USERROLE_BASENAME, filterDialog.ui->edtFilterName->text());
680 logTable->getModel()->resetFilters();
681
682 if (filterDialog.ui->editComponent->text().length())
683 {
684 logTable->getModel()->addFilter(ARMARX_LOG_COMPONENTSTR,
685 filterDialog.ui->editComponent->text().toStdString());
686 }
687
688 if (filterDialog.ui->edtTag->text().length())
689 {
690 logTable->getModel()->addFilter(ARMARX_LOG_TAGSTR,
691 filterDialog.ui->edtTag->text().toStdString());
692 }
693
694 if (filterDialog.ui->edtMessage->text().length())
695 {
696 logTable->getModel()->addFilter(ARMARX_LOG_MESSAGESTR,
697 filterDialog.ui->edtMessage->text().toStdString());
698 }
699
700 if (filterDialog.ui->edtFile->text().length())
701 {
702 logTable->getModel()->addFilter(ARMARX_LOG_FILESTR,
703 filterDialog.ui->edtFile->text().toStdString());
704 }
705
706 if (filterDialog.ui->edtFunction->text().length())
707 {
708 logTable->getModel()->addFilter(ARMARX_LOG_FUNCTIONSTR,
709 filterDialog.ui->edtFunction->text().toStdString());
710 }
711
712 if (filterDialog.ui->cbVerbosity->currentIndex() != -1)
713 {
714 logTable->getModel()->addFilter(
716 QString::number(filterDialog.ui->cbVerbosity->currentIndex()).toStdString());
717 }
718
719 logTable->getModel()->reapplyAllFilters();
720 logTable->update();
721 }
722
723 void
725 {
726 if (ui.lvFilters->invisibleRootItem()->childCount() == 1)
727 {
728 return;
729 }
730
731 auto item = ui.lvFilters->currentItem();
732 removeFilter(item);
733 }
734
735 void
736 LogViewer::removeFilter(QTreeWidgetItem* item)
737 {
738 if (!item)
739 {
740 return;
741 }
742
743 std::map<QString, LogTable*>::iterator it =
744 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
745
746 if (it == filterMap.end())
747 {
748 return;
749 }
750 QList<QTreeWidgetItem*> itemsToDelete, iterationList({item});
751 while (!iterationList.isEmpty())
752 {
753 auto curItem = iterationList.front();
754 itemsToDelete << curItem;
755 iterationList.pop_front();
756 iterationList << curItem->takeChildren();
757 }
758 ui.lvFilters->setCurrentItem(ui.lvFilters->invisibleRootItem()->child(0));
759 for (auto& item : itemsToDelete)
760 {
761 auto parent = item->parent();
762 if (parent)
763 {
764 parent->removeChild(item);
765 }
766 else
767 {
768 ui.lvFilters->invisibleRootItem()->removeChild(item);
769 }
770 std::map<QString, LogTable*>::iterator it =
771 filterMap.find(item->data(0, USERROLE_LOGTABLEID).toString());
772 delete item;
773
774 if (it == filterMap.end())
775 {
776 continue;
777 }
778 LogTable* logTable = it->second;
779 delete logTable;
780 filterMap.erase(it);
781 }
782 }
783
784 void
785 LogViewer::filterSelectionChanged(QTreeWidgetItem* item, QTreeWidgetItem* previous)
786 {
787 if (!item)
788 {
789 return;
790 }
791 LogTable* oldLogTable = logTable;
792 QString filterId = item->data(0, USERROLE_LOGTABLEID).toString();
793 // item->setText(0, filterId); Why?
794 QFont font;
795 font.setBold(false);
796 item->setFont(0, font);
797
798 if (filterMap.find(filterId) == filterMap.end())
799 {
800 showMessageBox("Filtername " + filterId + " not found.");
801 return;
802 }
803
804 logTable = filterMap[filterId];
805
806 if (!logTable)
807 {
808 ARMARX_ERROR << "logTable ptr is NULL" << flush;
809 return;
810 }
811
812
813 if (oldLogTable)
814 {
815 oldLogTable->hide();
816 }
817
818 logTable->show();
819 ui.edtLiveFilter->setText(logTable->getLiveFilterStr());
820 ui.edtLiveSearch->setText(logTable->getModel()->getCurrentSearchStr());
821 }
822
823 void
825 {
826 static QFont font;
827 QList<QTreeWidgetItem*> itemsToUpdate, iterationList({ui.lvFilters->invisibleRootItem()});
828 while (!iterationList.isEmpty())
829 {
830 auto curItem = iterationList.front();
831 itemsToUpdate << curItem;
832 iterationList.pop_front();
833 for (int i = 0; i < curItem->childCount(); ++i)
834 {
835 iterationList << curItem->child(i);
836 }
837 }
838 // Update new message count in filter list box
839 for (auto item : itemsToUpdate)
840 {
841 auto filterId = item->data(0, USERROLE_LOGTABLEID).toString();
842 auto filterIt = filterMap.find(filterId);
843 if (filterIt == filterMap.end())
844 {
845 // e.g. the invisible root item, whose id is empty and not in filterMap
846 continue;
847 }
848 LogTable* curLogtable = filterIt->second;
849 if (!curLogtable)
850 {
851 continue;
852 }
853 QString newContent;
854
855 if (curLogtable == logTable || curLogtable->getNewMessageCount() == 0)
856 {
857 font.setBold(false);
858 item->setFont(0, font);
859 newContent = item->data(0, USERROLE_BASENAME).toString();
860 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
861 }
862 else
863 {
864 font.setBold(true);
865 item->setFont(0, font);
866 newContent = item->data(0, USERROLE_BASENAME).toString() + "(" +
867 QString::number(curLogtable->getNewMessageCount()) + ")";
868
869 if (curLogtable->getMaxNewLogLevelType() == eWARN)
870 {
871 item->setBackgroundColor(0, QColor(216, 120, 50));
872 }
873 else if (curLogtable->getMaxNewLogLevelType() == eERROR)
874 {
875 item->setBackgroundColor(0, QColor(255, 90, 80));
876 }
877 else if (curLogtable->getMaxNewLogLevelType() == eFATAL)
878 {
879 item->setBackgroundColor(0, QColor(255, 60, 50));
880 }
881 else
882 {
883 item->setBackgroundColor(0, ui.lvFilters->palette().base().color());
884 }
885 }
886
887 item->setText(0, newContent);
888 item->setToolTip(0, newContent);
889 }
890 }
891
892 void
894 {
895 throw LocalException() << "Not yet implemented";
896 }
897
898 void
900 {
901 ui.lvLabels->clear();
902 for (const LogMarker& marker : markerRegistry.markers())
903 {
904 // Color swatch.
905 QPixmap swatch(12, 12);
906 swatch.fill(marker.color);
907
908 // Short, single-line description: time . component . message.
909 IceUtil::Time time = IceUtil::Time::microSeconds(marker.snapshot.time);
910 std::string timeStr = time.toDateTime();
911 const auto spacePos = timeStr.find(' ');
912 if (spacePos != std::string::npos)
913 {
914 timeStr = timeStr.substr(spacePos + 1);
915 }
916
917 QString what = QString::fromStdString(marker.snapshot.what);
918 const int newlinePos = what.indexOf('\n');
919 if (newlinePos >= 0)
920 {
921 what.truncate(newlinePos);
922 }
923 constexpr int maxWhatLength = 60;
924 if (what.length() > maxWhatLength)
925 {
926 what.truncate(maxWhatLength);
927 what += "...";
928 }
929
930 const QString text = QString::fromStdString(timeStr) + " " +
931 QString::fromStdString(marker.snapshot.who) + " " + what;
932
933 QListWidgetItem* item = new QListWidgetItem(QIcon(swatch), text, ui.lvLabels);
934 item->setData(Qt::UserRole, static_cast<qulonglong>(marker.id));
935 item->setToolTip(text);
936 }
937 }
938
939 void
940 LogViewer::labelClicked(QListWidgetItem* item)
941 {
942 if (!item || !logTable)
943 {
944 return;
945 }
946 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
947
948 for (const LogMarker& marker : markerRegistry.markers())
949 {
950 if (marker.id != id)
951 {
952 continue;
953 }
954 // The pinned line is present (native or injected) in every filter, so it resolves
955 // in whichever table is currently shown.
956 const int row = logTable->getModel()->rowForMessage(marker.snapshot);
957 if (row >= 0)
958 {
959 const QModelIndex index = logTable->getModel()->index(row, 0);
960 logTable->scrollTo(index, QAbstractItemView::PositionAtCenter);
961 logTable->selectRow(row);
962 logTable->setFocus();
963 }
964 break;
965 }
966 }
967
968 void
970 {
971 QListWidgetItem* item = ui.lvLabels->itemAt(pos);
972 if (!item)
973 {
974 return;
975 }
976 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
977
978 QMenu menu;
979 QAction* recolorAction = menu.addAction(tr("Change color..."));
980 QAction* removeAction = menu.addAction(tr("Remove label"));
981 QAction* chosen = menu.exec(ui.lvLabels->viewport()->mapToGlobal(pos));
982
983 if (chosen == removeAction)
984 {
985 markerRegistry.remove(id);
986 }
987 else if (chosen == recolorAction)
988 {
989 QColor initial;
990 for (const LogMarker& marker : markerRegistry.markers())
991 {
992 if (marker.id == id)
993 {
994 initial = marker.color;
995 break;
996 }
997 }
998 const QColor color =
999 QColorDialog::getColor(initial, getWidget(), tr("Choose label color"));
1000 if (color.isValid())
1001 {
1002 markerRegistry.recolor(id, color);
1003 }
1004 }
1005 }
1006
1007 void
1009 {
1010 QListWidgetItem* item = ui.lvLabels->currentItem();
1011 if (!item)
1012 {
1013 return;
1014 }
1015 const LogMarkerId id = static_cast<LogMarkerId>(item->data(Qt::UserRole).toULongLong());
1016 markerRegistry.remove(id);
1017 }
1018
1019 void
1021 {
1022
1023 if (getState() >= eManagedIceObjectExiting)
1024 {
1025 return;
1026 }
1027
1028 std::vector<LogMessage> pendingEntriesTemp;
1029 {
1030 std::unique_lock lock(pendingEntriesMutex);
1031 pendingEntriesTemp.swap(pendingEntries);
1032 }
1033
1034
1035 unsigned int size = pendingEntriesTemp.size();
1036 // Compile the ANSI-color regex once, not on every timer tick.
1037 static const boost::regex re(REGEX_COLORS);
1038 // check for new components, that are sending log messages and perform auto scroll
1039 for (unsigned int i = 0; i < size; i++)
1040 {
1041 LogMessage& msg = pendingEntriesTemp[i];
1042 // Only run the regex if an escape character is actually present.
1043 if (msg.what.find('\033') != std::string::npos)
1044 {
1045 msg.what = boost::regex_replace(msg.what, re, "");
1046 }
1047
1048 if (!getWidget() || ui.cbVerbosityLevel->currentIndex() > msg.type)
1049 {
1050 continue;
1051 }
1052 checkAndAddNewFilter(msg.group.c_str(), "");
1053 checkAndAddNewFilter(msg.group.c_str(), msg.who.c_str());
1054 }
1055
1056 bool autoScroll = false;
1057
1058 if (logTable->verticalScrollBar()->value() == logTable->verticalScrollBar()->maximum())
1059 {
1060 autoScroll = true;
1061 }
1062
1063 std::map<QString, LogTable*>::iterator it = filterMap.begin();
1064
1065 for (; it != filterMap.end(); ++it)
1066 {
1067 LogTable* log = it->second;
1068 int row = log->getModel()->rowCount();
1069 int rowsAdded =
1070 log->getModel()->addEntries(pendingEntriesTemp, log->getCurrentLiveFilter());
1071 if (rowsAdded > 0)
1072 {
1073 int count = log->getModel()->rowCount();
1074 int r;
1075 for (r = row; r < count; r++)
1076 {
1077 log->liveFilterRow(log->getCurrentLiveFilter(), r);
1078 }
1079 }
1080 bool autoScroll = false;
1081
1082 if (log->verticalScrollBar()->value() == log->verticalScrollBar()->maximum())
1083 {
1084 autoScroll = true;
1085 }
1086
1087
1088 if (autoScroll)
1089 {
1090 log->scrollToBottom();
1091 }
1092 }
1093
1094
1095 if (autoScroll)
1096 {
1097 logTable->scrollToBottom();
1098 }
1099
1100 // Buffer trimming (enforceMaxEntries) may have dropped a pinned line's last native
1101 // row; expire such pins so they stay bound to the live log.
1102 expireOrphanedMarkers();
1103
1105 }
1106
1107 void
1109 {
1110 if (index == 0)
1111 {
1112 ui.edtLiveSearch->show();
1113 ui.btnPreviousItem->show();
1114 ui.btnNextItem->show();
1115 ui.edtLiveFilter->hide();
1116 }
1117 else
1118 {
1119 ui.edtLiveSearch->hide();
1120 ui.btnPreviousItem->hide();
1121 ui.btnNextItem->hide();
1122 ui.edtLiveFilter->show();
1123 }
1124 }
1125
1126 void
1128 {
1129
1130 if (logTable)
1131 {
1132 if (!logTable->selectNextSearchResult(false))
1133 {
1134 getMainWindow()->statusBar()->showMessage(
1135 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1136 "' in the log!",
1137 5000);
1138 }
1139 }
1140 }
1141
1142 void
1144 {
1145 if (logTable)
1146 if (!logTable->selectNextSearchResult(true))
1147 {
1148 getMainWindow()->statusBar()->showMessage(
1149 "Could not find '" + logTable->getModel()->getCurrentSearchStr() +
1150 "' in the log!",
1151 5000);
1152 }
1153 }
1154
1155 QPointer<QWidget>
1157 {
1158 if (customToolbar)
1159 {
1160 if (parent != customToolbar->parent())
1161 {
1162 customToolbar->setParent(parent);
1163 }
1164
1165 return customToolbar;
1166 }
1167
1168 customToolbar = new QToolBar(parent);
1169 customToolbar->setIconSize(QSize(16, 16));
1170 customToolbar->addAction(
1171 QIcon(":/icons/configure-3.png"), "Configure", this, SLOT(OpenConfigureDialog()));
1172
1173 return customToolbar;
1174 }
1175} // namespace armarx
uint8_t index
#define ARMARX_LOG_VERBOSITYSTR
Definition LogTable.h:43
#define ARMARX_LOG_COMPONENTSTR
Definition LogTable.h:41
#define ARMARX_LOG_FILESTR
Definition LogTable.h:45
#define ARMARX_LOG_TAGSTR
Definition LogTable.h:42
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:44
#define ARMARX_LOG_LOGGINGGROUPSTR
Definition LogTable.h:47
#define ARMARX_LOG_FUNCTIONSTR
Definition LogTable.h:46
#define USERROLE_BASENAME
Definition LogViewer.cpp:55
#define ALL_MESSAGES_FILTER
Definition LogViewer.cpp:57
#define USERROLE_LOGTABLEID
Definition LogViewer.cpp:53
#define REGEX_COLORS
Definition LogViewer.cpp:56
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
const LogMarker * markerForMessage(const LogMessage &message) const
Marker for a message identical to message, or nullptr.
const LogMarker * add(const LogMessage &snapshot)
Pin the given message.
std::vector< LogMarker > markers() const
Ordered (insertion-order) view of all markers.
void remove(LogMarkerId id)
Remove the marker with the given id (no-op if unknown).
static std::string levelToString(MessageTypeT type)
void setMarkerRegistry(LogMarkerRegistry *registry)
Attach the shared pin registry.
void addFilter(const std::string &columnName, const std::string &filter, MatchMode mode=MatchMode::Contains)
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int addEntries(const std::vector< LogMessage > &entryList, const QString &filterStr)
MessageType getMaxNewLogLevelType()
Definition LogTable.h:80
void liveFilterRow(const QString &filterStr, int row)
Definition LogTable.cpp:158
LogTableModel * getModel()
Definition LogTable.cpp:271
QString getCurrentLiveFilter() const
Definition LogTable.cpp:152
void removeLabelRequested(const LogMessage &message)
Emitted from the context menu to remove the pin on the given line.
int getNewMessageCount()
Definition LogTable.h:74
void addLabelRequested(const LogMessage &message)
Emitted from the context menu to pin the given line (assign a label).
void pauseLogging(bool pause=false)
LogTable * logTable
Definition LogViewer.h:156
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:155
void OpenConfigureDialog()
~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:196
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::uint64_t LogMarkerId
const LogSender::manipulator flush
Definition LogSender.h:251
MessageTypeT
Definition LogSender.h:46
A single pinned log line: a snapshot of the message plus the color assigned to it.