LogTableModel.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 "LogTableModel.h"
24
25#include <algorithm>
26
27#include <QFont>
28
30
31#include "LogTable.h"
32
33namespace armarx
34{
35 namespace
36 {
37 // Whole-line identity, matching LogMarkerRegistry: two rows are "the same line" iff
38 // they share timestamp, component and message text.
39 bool
40 sameLine(const LogMessage& a, const LogMessage& b)
41 {
42 return a.time == b.time && a.who == b.who && a.what == b.what;
43 }
44
45 // Map a column header name (ARMARX_LOG_*STR) to its LogColumn once, when a filter
46 // is created, so per-message filtering can switch on an enum instead of comparing
47 // strings.
49 columnFromName(const std::string& name)
50 {
51 if (name == ARMARX_LOG_TIMESTR)
52 {
53 return LogColumn::Time;
54 }
55 if (name == ARMARX_LOG_COMPONENTSTR)
56 {
58 }
59 if (name == ARMARX_LOG_TAGSTR)
60 {
61 return LogColumn::Tag;
62 }
63 if (name == ARMARX_LOG_VERBOSITYSTR)
64 {
66 }
67 if (name == ARMARX_LOG_MESSAGESTR)
68 {
69 return LogColumn::Message;
70 }
71 if (name == ARMARX_LOG_FILESTR)
72 {
73 return LogColumn::File;
74 }
75 if (name == ARMARX_LOG_FUNCTIONSTR)
76 {
78 }
80 {
81 return LogColumn::Group;
82 }
83 return LogColumn::Unknown;
84 }
85
86 // Format the time column (0) value the same way the model previously did inline,
87 // but guard the substr against a missing space (which would otherwise throw).
88 QString
89 formatTime(Ice::Long timeMicroSeconds)
90 {
91 IceUtil::Time time = IceUtil::Time::microSeconds(timeMicroSeconds);
92 std::string timeStr = time.toDateTime();
93 const auto spacePos = timeStr.find(' ');
94 if (spacePos != std::string::npos)
95 {
96 timeStr = timeStr.substr(spacePos);
97 }
98 return QString::fromStdString(timeStr);
99 }
100
101 // Build the message column (4) display string (truncated to a maximum number of
102 // lines) and report how many display lines it spans.
103 QString
104 buildDisplayMessage(const LogMessage& entry, int& lineCount)
105 {
106 QString whatStr = entry.backtrace.empty()
107 ? QString::fromStdString(entry.what)
108 : QString::fromStdString(entry.what + "\nBacktrace:\n...");
109
110 constexpr int maxLinesToShow = 50;
111 int lines = 0;
112 int pos = 0;
113 for (int i = 0; i < whatStr.length(); i++)
114 {
115 if (whatStr.at(i) == '\n')
116 {
117 lines++;
118 }
119 if (lines == maxLinesToShow)
120 {
121 break;
122 }
123 pos++;
124 }
125 if (lines >= maxLinesToShow)
126 {
127 whatStr.truncate(pos);
128 whatStr += "\n...";
129 }
130
131 lineCount = whatStr.count('\n') + 1;
132 return whatStr;
133 }
134 } // namespace
135
136 LogTableModel::LogTableModel(QObject* parent) : QAbstractTableModel(parent)
137 {
138 newEntryCount = 0;
139 maxNewLogLevelType = eUNDEFINED;
140 }
141
142 int
143 LogTableModel::rowCount(const QModelIndex& parent) const
144 {
145 return logEntries.size();
146 }
147
148 int
149 LogTableModel::columnCount(const QModelIndex& parent) const
150 {
151 return 8;
152 }
153
154 QVariant
155 LogTableModel::data(const QModelIndex& index, int role) const
156 {
157 const int& row = index.row();
158 const int& column = index.column();
159
160 switch (role)
161 {
162 case (int)UserRoles::FullMsgRole:
163 {
164 std::unique_lock lock(logEntriesMutex);
165 if (row >= (signed int)logEntries.size() || row < 0)
166 {
167 return QString("n/A");
168 }
169
170 const LogMessage& entry = logEntries[row].message;
171 switch (column)
172 {
173
174 case 4:
175 {
176 if (role == Qt::ToolTipRole)
177 {
178 return "";
179 }
180
181 QString whatStr;
182
183 if (!entry.backtrace.empty())
184 {
185 whatStr = QString::fromStdString(entry.what + "\nBacktrace:\n" +
186 entry.backtrace);
187 }
188 else
189 {
190 whatStr = QString::fromStdString(entry.what);
191 }
192
193 return whatStr;
194 }
195 default:
196 return "";
197 }
198 }
199 case Qt::DisplayRole:
200 case Qt::ToolTipRole:
201 {
202 std::unique_lock lock(logEntriesMutex);
203
204 //std::cout << "row " << row << " column:" << column << std::endl;
205 if (row >= (signed int)logEntries.size() || row < 0)
206 {
207 return QString("n/A");
208 }
209
210 const LogEntry& logEntry = logEntries[row];
211 const LogMessage& entry = logEntry.message;
212
213 switch (column)
214 {
215 case 0:
216 {
217 return logEntry.timeString;
218 }
219
220 case 1:
221 {
222 return QString::fromStdString(entry.who);
223 }
224
225 case 2:
226 {
227 return QString::fromStdString(entry.tag);
228 }
229
230 case 3:
231 {
232 if (entry.type != eUNDEFINED)
233 {
234 return QString::fromStdString(
236 }
237 else
238 {
239 return QVariant();
240 }
241 }
242
243 case 4:
244 {
245 if (role == Qt::ToolTipRole)
246 {
247 return "";
248 }
249 return logEntry.displayMessage;
250 }
251
252 case 5:
253 {
254 if (role == Qt::ToolTipRole)
255 {
256 return QString::fromStdString(
257 "Double click to open file in editor: " + entry.file + ":" +
258 QString::number(entry.line).toStdString());
259 }
260 else if (!entry.file.empty())
261 {
262 return QString::fromStdString(
263 entry.file + ":" + QString::number(entry.line).toStdString());
264 }
265 else
266 {
267 return QVariant();
268 }
269 }
270
271 case 6:
272 {
273 return QString::fromStdString(entry.function);
274 }
275
276 case 7:
277 {
278 return QString::fromStdString(entry.group);
279 }
280
281 default:
282 return "";
283 }
284 }
285
286 case Qt::DecorationRole:
287 switch (column)
288 {
289 case 5:
290 return QIcon(":icons/document-open-4.ico");
291
292 default:
293 return QVariant();
294 }
295
296 case Qt::FontRole:
297 {
298 // Injected pins (shown here although they do not match this filter) are
299 // rendered italic so they are visually distinct from native rows.
300 std::unique_lock lock(logEntriesMutex);
301 if (row >= 0 && row < (signed int)logEntries.size() && logEntries[row].injected)
302 {
303 QFont font;
304 font.setItalic(true);
305 return font;
306 }
307 return QVariant();
308 }
309
310 case Qt::BackgroundColorRole:
311 {
312 // Pinned rows: full-row label color, overriding verbosity color and search
313 // highlight (see the concept: "Full-row background").
314 {
315 std::unique_lock lock(logEntriesMutex);
316 if (row >= 0 && row < (signed int)logEntries.size() &&
317 logEntries[row].markerColor.isValid())
318 {
319 return logEntries[row].markerColor;
320 }
321 }
322
323 if (column == 3) // Log Level color
324 {
325 if (row >= (signed int)logEntries.size() || row < 0)
326 {
327 return QVariant();
328 }
329
330 const LogMessage& entry = logEntries[row].message;
331
332 switch (entry.type)
333 {
334 case eVERBOSE:
335 return QColor(200, 200, 250);
336
337 case eIMPORTANT:
338 return QColor(50, 255, 50);
339
340 case eWARN:
341 return QColor(216, 88, 0);
342
343 case eERROR:
344 return QColor(255, 64, 64);
345
346 case eFATAL:
347 return QColor(176, 0, 0);
348
349 default:
350 break;
351 }
352 }
353
354
355 if (activeSearchStr.length() == 0)
356 {
357 return QVariant();
358 }
359
360
361 std::unique_lock lock(logEntriesMutex);
362
363 //std::cout << "row " << row << " column:" << column << std::endl;
364 if (row >= (signed int)logEntries.size() || row < 0)
365 {
366 return QVariant();
367 }
368
369 if (logEntries[row].matchesSearch)
370 {
371 return QColor(255, 244, 127);
372 }
373 else
374 {
375 return QVariant();
376 }
377 }
378
379 // case Qt::SizeHintRole:
380 // {
381 // if(column == 4)
382 // {
383 //// const armarx::LogMessage & entry = logEntries[row];
384 //// int lines = std::count(entry.what.begin(), entry.what.end(), '\n') +1 ;
385 //// if(lines > 5)
386 //// lines = 5;
387 //// std::cout << "SizeHintRole lines: " << lines << "-> " << lines*14 << std::endl;
388 // return QSize(350,logRowSize[row]);
389 // }
390
391 // }
392 }
393
394 return QVariant();
395 }
396
397 bool
398 LogTableModel::rowContainsString(int row, const QString& searchStr) const
399 {
400 if (searchStr.length() == 0)
401 {
402 return true;
403 }
404
405 for (int i = 0; i < columnCount(); i++)
406 {
407 if (data(createIndex(row, i), Qt::DisplayRole)
408 .toString()
409 .contains(searchStr, Qt::CaseInsensitive))
410 {
411 return true;
412 }
413 }
414
415 return false;
416 }
417
418 bool
419 LogTableModel::msgContainsString(const LogMessage& logMsg, QString searchStr) const
420 {
421 if (QString(logMsg.who.c_str()).contains(searchStr, Qt::CaseInsensitive))
422 {
423 return true;
424 }
425
426 if (QString(logMsg.tag.c_str()).contains(searchStr, Qt::CaseInsensitive))
427 {
428 return true;
429 }
430
431 if (QString(logMsg.what.c_str()).contains(searchStr, Qt::CaseInsensitive))
432 {
433 return true;
434 }
435
436 if (QString(logMsg.backtrace.c_str()).contains(searchStr, Qt::CaseInsensitive))
437 {
438 return true;
439 }
440
441 if (QString(logMsg.file.c_str()).contains(searchStr, Qt::CaseInsensitive))
442 {
443 return true;
444 }
445
446 if (QString(logMsg.function.c_str()).contains(searchStr, Qt::CaseInsensitive))
447 {
448 return true;
449 }
450
451 if (QString::number(logMsg.line).contains(searchStr, Qt::CaseInsensitive))
452 {
453 return true;
454 }
455
456 return false;
457 }
458
459 void
460 LogTableModel::search(const QString& searchStr)
461 {
462 activeSearchStr = searchStr;
463
464 // Evaluate the search once per row here instead of on every repaint of every cell
465 // (data() only reads the cached flag afterwards).
466 {
467 std::unique_lock lock(logEntriesMutex);
468 const bool hasSearch = searchStr.length() > 0;
469 for (LogEntry& logEntry : logEntries)
470 {
471 logEntry.matchesSearch =
472 hasSearch && msgContainsString(logEntry.message, searchStr);
473 }
474 }
475
476 if (!logEntries.empty())
477 {
478 emit dataChanged(index(0, 0), index(logEntries.size() - 1, 6));
479 }
480 }
481
482 QVariant
483 LogTableModel::headerData(int section, Qt::Orientation orientation, int role) const
484 {
485 if (role == Qt::DisplayRole)
486 {
487 if (orientation == Qt::Horizontal)
488 {
489 switch (section)
490 {
491 case 0:
492 return QString(ARMARX_LOG_TIMESTR);
493
494 case 1:
495 return QString(ARMARX_LOG_COMPONENTSTR);
496
497 case 2:
498 return QString(ARMARX_LOG_TAGSTR);
499
500 case 3:
501 return QString(ARMARX_LOG_VERBOSITYSTR);
502
503 case 4:
504 return QString(ARMARX_LOG_MESSAGESTR);
505
506 case 5:
507 return QString(ARMARX_LOG_FILESTR);
508
509 case 6:
510 return QString(ARMARX_LOG_FUNCTIONSTR);
511
512 case 7:
513 return QString(ARMARX_LOG_LOGGINGGROUPSTR);
514
515 default:
516 return QString("");
517 }
518 }
519 }
520 else if (role == Qt::ToolTipRole)
521 {
522 if (orientation == Qt::Horizontal)
523 {
524 switch (section)
525 {
526 case 0:
527 return QString(ARMARX_LOG_TIMESTR);
528
529 case 1:
530 return QString(ARMARX_LOG_COMPONENTSTR);
531
532 case 2:
533 return QString(ARMARX_LOG_TAGSTR);
534
535 case 3:
536 return QString(ARMARX_LOG_VERBOSITYSTR);
537
538 case 4:
539 return QString(ARMARX_LOG_MESSAGESTR);
540
541 case 5:
542 return QString::fromStdString(
543 std::string(ARMARX_LOG_FILESTR) +
544 std::string(": Double click cell to open Qtcreator at that location"));
545
546 case 6:
547 return QString(ARMARX_LOG_FUNCTIONSTR);
548
549 default:
550 return QString("");
551 }
552 }
553 }
554
555 // else if(role == Qt::SizeHintRole)
556 // {
557 // QSize size(100,18);
558 // if (orientation == Qt::Horizontal) {
559 // std::cout << "size of header questioned" << std::endl;
560 // switch (section)
561 // {
562 // case 0:
563 // case 1:
564 // case 2:
565 // size.setWidth(90);
566 // return size;
567 // case 3:
568 // size.setWidth(60);
569 // return size;
570 // case 4:
571 // size.setWidth(350);
572 // return size;
573 // case 5:
574 // size.setWidth(150);
575 // return size;
576 // case 6:
577 // size.setWidth(200);
578 // return size;
579 // default:
580 // std::cout << "standard size" << std::endl;
581 // return size;
582 // }
583 // }
584 // }
585
586 return QVariant();
587 }
588
589 bool
590 LogTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
591 {
592 return false;
593 }
594
595 Qt::ItemFlags
596 LogTableModel::flags(const QModelIndex& index) const
597 {
598 if (index.column() == getColumn(ARMARX_LOG_MESSAGESTR))
599 {
600 return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
601 }
602 else
603 {
604 return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
605 }
606 }
607
608 void
610 {
611 // QModelIndex topLeft = createIndex(0,0);
612 // emit dataChanged(topLeft, topLeft);
613 QModelIndex leftTop = index(logEntries.size() - newEntryCount, 0);
614 QModelIndex rightBottom = index(logEntries.size() - 1, 6);
615 // std::cout << "updating " << leftTop.row() << " til " << rightBottom.row() << std::endl;
616 emit dataChanged(leftTop, rightBottom);
617 newEntryCount = 0;
618 maxNewLogLevelType = eUNDEFINED;
619 }
620
621 bool
622 LogTableModel::insertRows(int row, int count, const QModelIndex& parent)
623 {
624 beginInsertRows(QModelIndex(), row, row + count - 1);
625 int newEntries = row + count - logEntries.size();
626
627 for (int i = 0; i < newEntries; i++)
628 {
629 logEntries.push_back(LogEntry());
630 }
631
632 endInsertRows();
633 return true;
634 }
635
636 void
637 LogTableModel::addFilter(const std::string& columnName,
638 const std::string& filter,
639 MatchMode mode)
640 {
641 activeFilters.push_back(LogFilter{columnFromName(columnName), filter, mode});
642 }
643
644 void
646 {
647 // Rebuild the kept entries in one pass instead of erasing in a loop. The old
648 // code did `it = erase(it); it--;`, which steps before begin() when the first
649 // element is dropped (UB), and vector::erase-per-row is O(n^2). A single reset
650 // is correct and cheap for this (non-hot) path.
651 beginResetModel();
652 {
653 std::unique_lock lock(logEntriesMutex);
654 std::vector<LogEntry> kept;
655 kept.reserve(logEntries.size());
656 for (LogEntry& logEntry : logEntries)
657 {
658 if (applyFilters(logEntry.message))
659 {
660 kept.push_back(std::move(logEntry));
661 }
662 }
663 logEntries.swap(kept);
664 }
665 newEntryCount = 0;
666 maxNewLogLevelType = eUNDEFINED;
667 endResetModel();
668
669 // Re-apply drops injected pins (they do not pass the filters) and may change which
670 // native rows exist; rebuild the injected set and re-color accordingly.
672 }
673
674 void
676 {
677 activeFilters.clear();
678 }
679
680 LogTableModel::LogEntry
681 LogTableModel::buildLogEntry(const LogMessage& message) const
682 {
683 LogEntry entry;
684 entry.message = message;
685 entry.timeString = formatTime(message.time);
686 entry.displayMessage = buildDisplayMessage(message, entry.lineCount);
687 entry.matchesSearch =
688 !activeSearchStr.isEmpty() && msgContainsString(message, activeSearchStr);
689 if (markerRegistry)
690 {
691 if (const LogMarker* marker = markerRegistry->markerForMessage(message))
692 {
693 entry.markerColor = marker->color;
694 }
695 }
696 return entry;
697 }
698
699 void
701 {
702 if (markerRegistry == registry)
703 {
704 return;
705 }
706 if (markerRegistry)
707 {
708 disconnect(markerRegistry, nullptr, this, nullptr);
709 }
710 markerRegistry = registry;
711 if (markerRegistry)
712 {
713 connect(markerRegistry,
715 this,
717 }
719 }
720
721 void
723 {
724 // Snapshot the markers before taking logEntriesMutex; the registry has its own lock
725 // (lock order is always logEntriesMutex -> registry mutex, never the reverse).
726 std::vector<LogMarker> markers;
727 if (markerRegistry)
728 {
729 markers = markerRegistry->markers();
730 }
731
732 beginResetModel();
733 {
734 std::unique_lock lock(logEntriesMutex);
735
736 // 1. Drop previously injected pin rows.
737 logEntries.erase(std::remove_if(logEntries.begin(),
738 logEntries.end(),
739 [](const LogEntry& e) { return e.injected; }),
740 logEntries.end());
741
742 // 2. Re-color native rows from the current registry.
743 for (LogEntry& entry : logEntries)
744 {
745 entry.markerColor = QColor();
746 for (const LogMarker& marker : markers)
747 {
748 if (sameLine(marker.snapshot, entry.message))
749 {
750 entry.markerColor = marker.color;
751 break;
752 }
753 }
754 }
755
756 // 3. Inject markers not present as a native row, in time position, so a line
757 // pinned in another filter still appears here.
758 for (const LogMarker& marker : markers)
759 {
760 const bool present = std::any_of(
761 logEntries.begin(),
762 logEntries.end(),
763 [&marker](const LogEntry& e)
764 { return !e.injected && sameLine(marker.snapshot, e.message); });
765 if (present)
766 {
767 continue;
768 }
769
770 LogEntry injected = buildLogEntry(marker.snapshot);
771 injected.injected = true;
772 injected.markerColor = marker.color;
773
774 std::size_t idx = 0;
775 while (idx < logEntries.size() &&
776 logEntries[idx].message.time <= marker.snapshot.time)
777 {
778 ++idx;
779 }
780 logEntries.insert(logEntries.begin() + idx, std::move(injected));
781 }
782 }
783 endResetModel();
784 }
785
786 int
787 LogTableModel::rowForMessage(const LogMessage& message) const
788 {
789 std::unique_lock lock(logEntriesMutex);
790 for (std::size_t row = 0; row < logEntries.size(); ++row)
791 {
792 if (sameLine(logEntries[row].message, message))
793 {
794 return static_cast<int>(row);
795 }
796 }
797 return -1;
798 }
799
800 bool
801 LogTableModel::containsNativeLine(const LogMessage& message) const
802 {
803 std::unique_lock lock(logEntriesMutex);
804 for (const LogEntry& entry : logEntries)
805 {
806 if (!entry.injected && sameLine(entry.message, message))
807 {
808 return true;
809 }
810 }
811 return false;
812 }
813
814 bool
816 {
817 std::unique_lock lock(logEntriesMutex);
818 if (row < 0 || row >= (signed int)logEntries.size())
819 {
820 return false;
821 }
822 return logEntries[row].markerColor.isValid();
823 }
824
825 bool
826 LogTableModel::addEntry(const LogMessage& entry, int* entriesAdded)
827 {
828 if (entriesAdded)
829 {
830 *entriesAdded = 0;
831 }
832
833 if (!applyFilters(entry))
834 {
835 return false;
836 }
837
838 // Precompute the derived display data once, so the hot data() / rendering paths
839 // never recompute time formatting or the message line count per repaint.
840 LogEntry logEntry = buildLogEntry(entry);
841
842 // New row: bracket the container mutation with begin/endInsertRows so the view
843 // stays consistent (previously the push happened outside any begin/end block).
844 // endInsertRows is emitted without logEntriesMutex held, because the view's
845 // rowsInserted handler calls data(), which locks the same mutex.
846 const int insertRow = rowCount();
847 beginInsertRows(QModelIndex(), insertRow, insertRow);
848 {
849 std::unique_lock lock(logEntriesMutex);
850 newEntryCount++;
851 if (entry.type > maxNewLogLevelType)
852 {
853 maxNewLogLevelType = entry.type;
854 }
855 logEntries.push_back(std::move(logEntry));
856 }
857 endInsertRows();
858
859 if (entriesAdded)
860 {
861 (*entriesAdded)++;
862 }
863 return true;
864 }
865
866 int
867 LogTableModel::addEntries(const std::vector<LogMessage>& entryList, const QString& filterStr)
868 {
869 if (entryList.size() == 0)
870 {
871 return 0;
872 }
873
874 int entriesAdded = 0;
875
876 // Each addEntry now brackets its own insertion with begin/endInsertRows, so we
877 // no longer issue a batch begin/endInsertRows here (which used to run *after* the
878 // rows were already appended, violating the model/view contract).
879 for (const LogMessage& entry : entryList)
880 {
881 if (addEntry(entry))
882 {
883 entriesAdded++;
884 }
885 }
886 if (entriesAdded > 0)
887 {
888 updateView();
890 }
891 return entriesAdded;
892 }
893
894 void
896 {
897 maxEntries = max;
899 }
900
901 void
903 {
904 if (maxEntries == 0)
905 {
906 return;
907 }
908
909 // Headroom factor: let the buffer grow to maxEntries * 1.3 before trimming back
910 // to maxEntries, so the O(n) front-erase is amortized over many insertions
911 // instead of running on every new message.
912 constexpr double headroomFactor = 1.3;
913
914 std::size_t currentSize;
915 {
916 std::unique_lock lock(logEntriesMutex);
917 currentSize = logEntries.size();
918 }
919 if (currentSize <= static_cast<std::size_t>(maxEntries * headroomFactor))
920 {
921 return;
922 }
923
924 const int removeCount = static_cast<int>(currentSize - maxEntries);
925 // endRemoveRows is emitted without logEntriesMutex held (the view may call data(),
926 // which takes that mutex).
927 beginRemoveRows(QModelIndex(), 0, removeCount - 1);
928 {
929 std::unique_lock lock(logEntriesMutex);
930 logEntries.erase(logEntries.begin(), logEntries.begin() + removeCount);
931 }
932 endRemoveRows();
933 }
934
935 int
936 LogTableModel::getColumn(const std::string& columnName) const
937 {
938 if (columnName.empty())
939 {
940 return -1;
941 }
942
943 QString qcolumnName = QString::fromStdString(columnName);
944
945 for (int i = 0; i < columnCount(QModelIndex()); i++)
946 {
947 if (headerData(i, Qt::Horizontal, Qt::DisplayRole).toString().compare(qcolumnName) == 0)
948 {
949 return i;
950 }
951 }
952
953 return -1;
954 }
955
956 int
958 {
959 int size = (int)logEntries.size();
960 if (size == 0)
961 {
962 // Nothing to remove; beginRemoveRows(0, -1) would be an invalid range.
963 return 0;
964 }
965 beginRemoveRows(QModelIndex(), 0, size - 1);
966 {
967 std::unique_lock lock(logEntriesMutex);
968 logEntries.clear();
969 }
970 endRemoveRows();
971 newEntryCount = 0;
972 maxNewLogLevelType = eUNDEFINED;
973 return size;
974 }
975
976 const LogMessage&
978 {
979 return logEntries.at(row).message;
980 }
981
982 int
984 {
985 std::unique_lock lock(logEntriesMutex);
986 if (row < 0 || row >= (signed int)logEntries.size())
987 {
988 return 1;
989 }
990 return logEntries[row].lineCount;
991 }
992
993 bool
994 LogTableModel::applyFilter(const LogFilter& filter, const LogMessage& logMsg)
995 {
996 const QString filterStr = QString::fromStdString(filter.value);
997
998 // Substring match for manual filters; whole-value match for exact (auto) filters,
999 // so e.g. the auto-filter for component "bar" does not also match "foo_bar".
1000 const auto matches = [&filter, &filterStr](const std::string& field,
1001 Qt::CaseSensitivity cs)
1002 {
1003 const QString value = QString::fromStdString(field);
1004 return filter.mode == MatchMode::Exact ? value.compare(filterStr, cs) == 0
1005 : value.contains(filterStr, cs);
1006 };
1007
1008 switch (filter.column)
1009 {
1010 case LogColumn::Group:
1011 return matches(logMsg.group, Qt::CaseSensitive);
1013 return matches(logMsg.who, Qt::CaseInsensitive);
1014 case LogColumn::Tag:
1015 return matches(logMsg.tag, Qt::CaseInsensitive);
1017 return !(logMsg.type < filterStr.toInt());
1018 case LogColumn::Message:
1019 return matches(logMsg.what, Qt::CaseInsensitive);
1020 case LogColumn::File:
1021 return matches(logMsg.file, Qt::CaseInsensitive) ||
1022 filterStr.toInt() == logMsg.line;
1024 return matches(logMsg.function, Qt::CaseInsensitive);
1025 case LogColumn::Time:
1026 case LogColumn::Unknown:
1027 break;
1028 }
1029
1030 return true;
1031 }
1032
1033 bool
1034 LogTableModel::applyFilters(const LogMessage& logMsg)
1035 {
1036 for (unsigned int i = 0; i < activeFilters.size(); i++)
1037 {
1038 if (!applyFilter(activeFilters[i], logMsg))
1039 {
1040 return false;
1041 }
1042 }
1043
1044 return true;
1045 }
1046
1047 //bool LogTableModel::applyFilter(std::pair filter, int row)
1048 //{
1049 // int selectedColumn = getColumn(filter.first);
1050
1051 // if(selectedColumn == -1)
1052 // {
1053 // ARMARX_WARNING << "Could not find column " << filter.first << flush;
1054 // return true;
1055 // }
1056
1057 // return true;
1058 //}
1059} // 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_TIMESTR
Definition LogTable.h:40
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:44
#define ARMARX_LOG_LOGGINGGROUPSTR
Definition LogTable.h:47
#define ARMARX_LOG_FUNCTIONSTR
Definition LogTable.h:46
Shared, session-only store of pinned ("labeled") log lines.
const LogMarker * markerForMessage(const LogMessage &message) const
Marker for a message identical to message, or nullptr.
static std::string levelToString(MessageTypeT type)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
void setMarkerRegistry(LogMarkerRegistry *registry)
Attach the shared pin registry.
void addFilter(const std::string &columnName, const std::string &filter, MatchMode mode=MatchMode::Contains)
bool applyFilters(const LogMessage &logMsg)
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
LogTableModel(QObject *parent=0)
bool isRowMarked(int row) const
Whether the given display row is currently pinned (has a label color).
bool setData(const QModelIndex &index, const QVariant &value, int role) override
int addEntries(const std::vector< LogMessage > &entryList, const QString &filterStr)
bool applyFilter(const LogFilter &filter, const LogMessage &logMsg)
bool insertRows(int row, int count, const QModelIndex &parent) override
int lineCountAt(int row) const
Number of display lines of the message column for the given row (precomputed).
int rowForMessage(const LogMessage &message) const
Display row of the (first) line identical to message, or -1 if not shown.
void setMaxEntries(std::size_t maxEntries)
Upper bound on retained log entries.
const LogMessage & getLogEntry(size_t row) const
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void enforceMaxEntries()
Trim the oldest entries once the buffer exceeds its bound (see setMaxEntries).
int getColumn(const std::string &columnName) const
bool addEntry(const LogMessage &entry, int *entriesAdded=NULL)
bool msgContainsString(const LogMessage &logMsg, QString searchStr) const
QVariant data(const QModelIndex &index, int role) const override
bool rowContainsString(int row, const QString &searchStr) const
void refreshMarkers()
Rebuild the set of injected pin rows and re-color native rows (full reset).
void search(const QString &searchStr)
bool containsNativeLine(const LogMessage &message) const
Whether this model holds message as a native (non-injected) row.
double a(double t, double a0, double j)
Definition CtrlUtil.h:45
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
MessageTypeT
Definition LogSender.h:46
std::string value
A single pinned log line: a snapshot of the message plus the color assigned to it.