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#include <cctype>
27
28#include <QFont>
29
31
32#include "LogTable.h"
33
34namespace armarx
35{
36 namespace
37 {
38 // ASCII case-insensitive equality. Used for the exact (auto-generated) component
39 // filters, which run per message per filter and previously converted both sides to
40 // QString just to compare them.
41 bool
42 iEquals(const std::string& a, const std::string& b)
43 {
44 return a.size() == b.size() &&
45 std::equal(a.begin(),
46 a.end(),
47 b.begin(),
48 [](unsigned char x, unsigned char y)
49 { return std::tolower(x) == std::tolower(y); });
50 }
51
52 // Map a column header name (ARMARX_LOG_*STR) to its LogColumn once, when a filter
53 // is created, so per-message filtering can switch on an enum instead of comparing
54 // strings.
56 columnFromName(const std::string& name)
57 {
58 if (name == ARMARX_LOG_TIMESTR)
59 {
60 return LogColumn::Time;
61 }
62 if (name == ARMARX_LOG_COMPONENTSTR)
63 {
65 }
66 if (name == ARMARX_LOG_TAGSTR)
67 {
68 return LogColumn::Tag;
69 }
70 if (name == ARMARX_LOG_VERBOSITYSTR)
71 {
73 }
74 if (name == ARMARX_LOG_MESSAGESTR)
75 {
76 return LogColumn::Message;
77 }
78 if (name == ARMARX_LOG_FILESTR)
79 {
80 return LogColumn::File;
81 }
82 if (name == ARMARX_LOG_FUNCTIONSTR)
83 {
85 }
87 {
88 return LogColumn::Group;
89 }
90 return LogColumn::Unknown;
91 }
92 } // namespace
93
94 LogTableModel::LogTableModel(QObject* parent) : QAbstractTableModel(parent)
95 {
96 maxNewLogLevelType = eUNDEFINED;
97 }
98
99 void
101 {
102 this->store = store;
103 }
104
105 int
106 LogTableModel::rowCount(const QModelIndex& parent) const
107 {
108 return rows.size();
109 }
110
111 int
112 LogTableModel::columnCount(const QModelIndex& parent) const
113 {
114 return 8;
115 }
116
117 const StoredLogEntry*
118 LogTableModel::entryAt(int row) const
119 {
120 // Caller holds rowsMutex.
121 if (!store || row < 0 || row >= (signed int)rows.size())
122 {
123 return nullptr;
124 }
125 const LogSeq seq = rows[row].seq;
126 if (!store->contains(seq))
127 {
128 return nullptr;
129 }
130 return &store->at(seq);
131 }
132
133 QVariant
134 LogTableModel::data(const QModelIndex& index, int role) const
135 {
136 const int& row = index.row();
137 const int& column = index.column();
138
139 switch (role)
140 {
141 case (int)UserRoles::FullMsgRole:
142 {
143 std::unique_lock lock(rowsMutex);
144 const StoredLogEntry* stored = entryAt(row);
145 if (!stored)
146 {
147 return QString("n/A");
148 }
149
150 const LogMessage& entry = stored->message;
151 switch (column)
152 {
153
154 case 4:
155 {
156 if (role == Qt::ToolTipRole)
157 {
158 return "";
159 }
160
161 QString whatStr;
162
163 if (!entry.backtrace.empty())
164 {
165 whatStr = QString::fromStdString(entry.what + "\nBacktrace:\n" +
166 entry.backtrace);
167 }
168 else
169 {
170 whatStr = QString::fromStdString(entry.what);
171 }
172
173 return whatStr;
174 }
175 default:
176 return "";
177 }
178 }
179 case Qt::DisplayRole:
180 case Qt::ToolTipRole:
181 {
182 std::unique_lock lock(rowsMutex);
183
184 const StoredLogEntry* stored = entryAt(row);
185 if (!stored)
186 {
187 return QString("n/A");
188 }
189
190 const LogMessage& entry = stored->message;
191
192 switch (column)
193 {
194 case 0:
195 {
196 return stored->timeString;
197 }
198
199 case 1:
200 {
201 return QString::fromStdString(entry.who);
202 }
203
204 case 2:
205 {
206 return QString::fromStdString(entry.tag);
207 }
208
209 case 3:
210 {
211 if (entry.type != eUNDEFINED)
212 {
213 return QString::fromStdString(
215 }
216 else
217 {
218 return QVariant();
219 }
220 }
221
222 case 4:
223 {
224 if (role == Qt::ToolTipRole)
225 {
226 return "";
227 }
228 return stored->displayMessage;
229 }
230
231 case 5:
232 {
233 if (role == Qt::ToolTipRole)
234 {
235 return QString::fromStdString(
236 "Double click to open file in editor: " + entry.file + ":" +
237 QString::number(entry.line).toStdString());
238 }
239 else if (!entry.file.empty())
240 {
241 return QString::fromStdString(
242 entry.file + ":" + QString::number(entry.line).toStdString());
243 }
244 else
245 {
246 return QVariant();
247 }
248 }
249
250 case 6:
251 {
252 return QString::fromStdString(entry.function);
253 }
254
255 case 7:
256 {
257 return QString::fromStdString(entry.group);
258 }
259
260 default:
261 return "";
262 }
263 }
264
265 case Qt::DecorationRole:
266 switch (column)
267 {
268 case 5:
269 return QIcon(":icons/document-open-4.ico");
270
271 default:
272 return QVariant();
273 }
274
275 case Qt::FontRole:
276 {
277 // Injected pins (shown here although they do not match this filter) are
278 // rendered italic so they are visually distinct from matching rows.
279 std::unique_lock lock(rowsMutex);
280 if (row >= 0 && row < (signed int)rows.size() && rows[row].injected)
281 {
282 QFont font;
283 font.setItalic(true);
284 return font;
285 }
286 return QVariant();
287 }
288
289 case Qt::BackgroundColorRole:
290 {
291 std::unique_lock lock(rowsMutex);
292
293 if (row < 0 || row >= (signed int)rows.size())
294 {
295 return QVariant();
296 }
297
298 // Pinned rows: full-row label color, overriding verbosity color and search
299 // highlight (see the concept: "Full-row background").
300 if (rows[row].markerColor.isValid())
301 {
302 return rows[row].markerColor;
303 }
304
305 if (column == 3) // Log Level color
306 {
307 const StoredLogEntry* stored = entryAt(row);
308 if (!stored)
309 {
310 return QVariant();
311 }
312
313 switch (stored->message.type)
314 {
315 case eVERBOSE:
316 return QColor(200, 200, 250);
317
318 case eIMPORTANT:
319 return QColor(50, 255, 50);
320
321 case eWARN:
322 return QColor(216, 88, 0);
323
324 case eERROR:
325 return QColor(255, 64, 64);
326
327 case eFATAL:
328 return QColor(176, 0, 0);
329
330 default:
331 break;
332 }
333 }
334
335 if (activeSearchStr.length() == 0)
336 {
337 return QVariant();
338 }
339
340 if (rows[row].matchesSearch)
341 {
342 return QColor(255, 244, 127);
343 }
344 return QVariant();
345 }
346 }
347
348 return QVariant();
349 }
350
351 bool
352 LogTableModel::rowContainsString(int row, const QString& searchStr) const
353 {
354 if (searchStr.length() == 0)
355 {
356 return true;
357 }
358
359 for (int i = 0; i < columnCount(); i++)
360 {
361 if (data(createIndex(row, i), Qt::DisplayRole)
362 .toString()
363 .contains(searchStr, Qt::CaseInsensitive))
364 {
365 return true;
366 }
367 }
368
369 return false;
370 }
371
372 bool
373 LogTableModel::msgContainsString(const LogMessage& logMsg, QString searchStr) const
374 {
375 if (QString(logMsg.who.c_str()).contains(searchStr, Qt::CaseInsensitive))
376 {
377 return true;
378 }
379
380 if (QString(logMsg.tag.c_str()).contains(searchStr, Qt::CaseInsensitive))
381 {
382 return true;
383 }
384
385 if (QString(logMsg.what.c_str()).contains(searchStr, Qt::CaseInsensitive))
386 {
387 return true;
388 }
389
390 if (QString(logMsg.backtrace.c_str()).contains(searchStr, Qt::CaseInsensitive))
391 {
392 return true;
393 }
394
395 if (QString(logMsg.file.c_str()).contains(searchStr, Qt::CaseInsensitive))
396 {
397 return true;
398 }
399
400 if (QString(logMsg.function.c_str()).contains(searchStr, Qt::CaseInsensitive))
401 {
402 return true;
403 }
404
405 if (QString::number(logMsg.line).contains(searchStr, Qt::CaseInsensitive))
406 {
407 return true;
408 }
409
410 return false;
411 }
412
413 void
414 LogTableModel::search(const QString& searchStr)
415 {
416 activeSearchStr = searchStr;
417
418 // Evaluate the search once per row here instead of on every repaint of every cell
419 // (data() only reads the cached flag afterwards).
420 std::size_t rowCount = 0;
421 {
422 std::unique_lock lock(rowsMutex);
423 const bool hasSearch = searchStr.length() > 0;
424 for (std::size_t i = 0; i < rows.size(); ++i)
425 {
426 const StoredLogEntry* stored = entryAt(static_cast<int>(i));
427 rows[i].matchesSearch =
428 hasSearch && stored && msgContainsString(stored->message, searchStr);
429 }
430 rowCount = rows.size();
431 }
432
433 if (rowCount > 0)
434 {
435 emit dataChanged(index(0, 0), index(static_cast<int>(rowCount) - 1, 6));
436 }
437 }
438
439 QVariant
440 LogTableModel::headerData(int section, Qt::Orientation orientation, int role) const
441 {
442 if (role == Qt::DisplayRole)
443 {
444 if (orientation == Qt::Horizontal)
445 {
446 switch (section)
447 {
448 case 0:
449 return QString(ARMARX_LOG_TIMESTR);
450
451 case 1:
452 return QString(ARMARX_LOG_COMPONENTSTR);
453
454 case 2:
455 return QString(ARMARX_LOG_TAGSTR);
456
457 case 3:
458 return QString(ARMARX_LOG_VERBOSITYSTR);
459
460 case 4:
461 return QString(ARMARX_LOG_MESSAGESTR);
462
463 case 5:
464 return QString(ARMARX_LOG_FILESTR);
465
466 case 6:
467 return QString(ARMARX_LOG_FUNCTIONSTR);
468
469 case 7:
470 return QString(ARMARX_LOG_LOGGINGGROUPSTR);
471
472 default:
473 return QString("");
474 }
475 }
476 }
477 else if (role == Qt::ToolTipRole)
478 {
479 if (orientation == Qt::Horizontal)
480 {
481 switch (section)
482 {
483 case 0:
484 return QString(ARMARX_LOG_TIMESTR);
485
486 case 1:
487 return QString(ARMARX_LOG_COMPONENTSTR);
488
489 case 2:
490 return QString(ARMARX_LOG_TAGSTR);
491
492 case 3:
493 return QString(ARMARX_LOG_VERBOSITYSTR);
494
495 case 4:
496 return QString(ARMARX_LOG_MESSAGESTR);
497
498 case 5:
499 return QString::fromStdString(
500 std::string(ARMARX_LOG_FILESTR) +
501 std::string(": Double click cell to open Qtcreator at that location"));
502
503 case 6:
504 return QString(ARMARX_LOG_FUNCTIONSTR);
505
506 default:
507 return QString("");
508 }
509 }
510 }
511
512 return QVariant();
513 }
514
515 bool
516 LogTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
517 {
518 return false;
519 }
520
521 Qt::ItemFlags
522 LogTableModel::flags(const QModelIndex& index) const
523 {
524 if (index.column() == getColumn(ARMARX_LOG_MESSAGESTR))
525 {
526 return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
527 }
528 else
529 {
530 return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
531 }
532 }
533
534 void
535 LogTableModel::addFilter(const std::string& columnName,
536 const std::string& filter,
537 MatchMode mode)
538 {
539 LogFilter entry;
540 entry.column = columnFromName(columnName);
541 entry.value = filter;
542 // Derive the comparison forms once here rather than per message in applyFilter().
543 entry.qValue = QString::fromStdString(filter);
544 entry.intValue = entry.qValue.toInt();
545 entry.mode = mode;
546 activeFilters.push_back(std::move(entry));
547 }
548
549 void
551 {
552 beginResetModel();
553 {
554 std::unique_lock lock(rowsMutex);
555 if (materialised)
556 {
557 rebuildRowsFromStore();
558 applyMarkersLocked();
559 }
560 else
561 {
562 // Rows are rebuilt from the store, with the new filters, on the next
563 // showEvent. Editing a filter does not make its table visible.
564 rows.clear();
565 }
566 }
567 maxNewLogLevelType = eUNDEFINED;
568 endResetModel();
569 }
570
571 void
573 {
574 activeFilters.clear();
575 }
576
577 void
579 {
580 if (markerRegistry == registry)
581 {
582 return;
583 }
584 if (markerRegistry)
585 {
586 disconnect(markerRegistry, nullptr, this, nullptr);
587 }
588 markerRegistry = registry;
589 if (markerRegistry)
590 {
591 connect(markerRegistry,
593 this,
595 }
597 }
598
599 void
601 {
602 if (!materialised)
603 {
604 // Rows are rebuilt (with markers applied) when the table becomes visible.
605 return;
606 }
607
608 beginResetModel();
609 {
610 std::unique_lock lock(rowsMutex);
611 applyMarkersLocked();
612 }
613 endResetModel();
614 }
615
616 void
617 LogTableModel::applyMarkersLocked()
618 {
619 // Caller holds rowsMutex and brackets this with begin/endResetModel.
620
621 // 1. Drop previously injected pin rows.
622 rows.erase(std::remove_if(rows.begin(),
623 rows.end(),
624 [](const Row& r) { return r.injected; }),
625 rows.end());
626
627 for (Row& row : rows)
628 {
629 row.markerColor = QColor();
630 }
631
632 if (!markerRegistry || !store)
633 {
634 return;
635 }
636
637 const std::vector<LogMarker> markers = markerRegistry->markers();
638 if (markers.empty())
639 {
640 return;
641 }
642
643 // 2. Re-color the rows this filter holds itself.
644 for (Row& row : rows)
645 {
646 for (const LogMarker& marker : markers)
647 {
648 if (marker.seq == row.seq)
649 {
650 row.markerColor = marker.color;
651 break;
652 }
653 }
654 }
655
656 // 3. Inject markers that are not present as a row of this filter, in sequence
657 // position, so a line pinned in another filter still appears here.
658 for (const LogMarker& marker : markers)
659 {
660 if (!store->contains(marker.seq) || marker.seq < clearedBeforeSeq)
661 {
662 continue;
663 }
664 const bool present = std::any_of(rows.begin(),
665 rows.end(),
666 [&marker](const Row& r)
667 { return !r.injected && r.seq == marker.seq; });
668 if (present)
669 {
670 continue;
671 }
672
673 Row injected;
674 injected.seq = marker.seq;
675 injected.injected = true;
676 injected.markerColor = marker.color;
677 injected.matchesSearch =
678 !activeSearchStr.isEmpty() &&
679 msgContainsString(store->at(marker.seq).message, activeSearchStr);
680
681 // Sequence numbers are assigned in arrival order, so ordering by seq is
682 // ordering by time.
683 const auto pos = std::lower_bound(rows.begin(),
684 rows.end(),
685 marker.seq,
686 [](const Row& r, LogSeq seq)
687 { return r.seq < seq; });
688 rows.insert(pos, std::move(injected));
689 }
690 }
691
692 void
693 LogTableModel::rebuildRowsFromStore()
694 {
695 // Caller holds rowsMutex and brackets this with begin/endResetModel.
696 rows.clear();
697
698 if (!store)
699 {
700 return;
701 }
702
703 // Walk backwards from the newest entry so the scan can stop as soon as the display
704 // window is full, instead of filtering the whole store every time.
705 const LogSeq begin = std::max(store->firstSeq(), clearedBeforeSeq);
706 std::vector<Row> collected;
707 for (LogSeq seq = store->endSeq(); seq > begin;)
708 {
709 --seq;
710 if (maxEntries != 0 && collected.size() >= maxEntries)
711 {
712 break;
713 }
714 const StoredLogEntry& stored = store->at(seq);
715 if (!applyFilters(stored.message))
716 {
717 continue;
718 }
719 Row row;
720 row.seq = seq;
721 row.matchesSearch = !activeSearchStr.isEmpty() &&
722 msgContainsString(stored.message, activeSearchStr);
723 collected.push_back(std::move(row));
724 }
725
726 rows.assign(collected.rbegin(), collected.rend());
727 }
728
729 void
731 {
732 if (this->materialised == materialised)
733 {
734 return;
735 }
736 this->materialised = materialised;
737
738 beginResetModel();
739 {
740 std::unique_lock lock(rowsMutex);
741 if (materialised)
742 {
743 rebuildRowsFromStore();
744 applyMarkersLocked();
745 }
746 else
747 {
748 // A hidden table is never painted; releasing its rows is what keeps the
749 // memory and the per-tick work independent of the number of filters.
750 rows.clear();
751 rows.shrink_to_fit();
752 }
753 }
754 endResetModel();
755 }
756
757 int
758 LogTableModel::addEntries(const std::vector<LogSeq>& seqs, MessageType* batchMaxLevelOut)
759 {
760 if (batchMaxLevelOut)
761 {
762 *batchMaxLevelOut = eUNDEFINED;
763 }
764 if (seqs.empty() || !store)
765 {
766 return 0;
767 }
768
769 // Snapshot the pins once per batch rather than locking the registry per row.
770 std::vector<LogMarker> markers;
771 if (markerRegistry)
772 {
773 markers = markerRegistry->markers();
774 }
775
776 std::vector<Row> accepted;
777 accepted.reserve(seqs.size());
778 MessageType batchMaxLevel = eUNDEFINED;
779 for (const LogSeq seq : seqs)
780 {
781 if (!store->contains(seq))
782 {
783 continue;
784 }
785 const LogMessage& message = store->at(seq).message;
786 if (!applyFilters(message))
787 {
788 continue;
789 }
790 if (message.type > batchMaxLevel)
791 {
792 batchMaxLevel = message.type;
793 }
794
795 Row row;
796 row.seq = seq;
797 row.matchesSearch =
798 !activeSearchStr.isEmpty() && msgContainsString(message, activeSearchStr);
799 for (const LogMarker& marker : markers)
800 {
801 if (marker.seq == seq)
802 {
803 row.markerColor = marker.color;
804 break;
805 }
806 }
807 accepted.push_back(std::move(row));
808 }
809
810 if (accepted.empty())
811 {
812 return 0;
813 }
814
815 const int addedCount = static_cast<int>(accepted.size());
816
817 if (batchMaxLevel > maxNewLogLevelType)
818 {
819 maxNewLogLevelType = batchMaxLevel;
820 }
821 if (batchMaxLevelOut)
822 {
823 *batchMaxLevelOut = maxNewLogLevelType;
824 }
825
826 // A table that is not visible keeps only the count: its rows are rebuilt from the
827 // store when it is shown again. Clear the level here too -- it is reported through
828 // batchMaxLevelOut, and letting it accumulate would leave a hidden filter's badge
829 // stuck at the highest level it ever saw.
830 if (!materialised)
831 {
832 maxNewLogLevelType = eUNDEFINED;
833 return addedCount;
834 }
835
836 const int firstRow = rowCount();
837 // endInsertRows is emitted without rowsMutex held, because the view's rowsInserted
838 // handler calls data(), which locks the same mutex.
839 beginInsertRows(QModelIndex(), firstRow, firstRow + addedCount - 1);
840 {
841 std::unique_lock lock(rowsMutex);
842 rows.insert(rows.end(),
843 std::make_move_iterator(accepted.begin()),
844 std::make_move_iterator(accepted.end()));
845 }
846 endInsertRows();
847
848 // The views read getMaxNewLogLevelType() from their rowsInserted handler (emitted
849 // by endInsertRows above), so it describes the batch that was just inserted and is
850 // cleared again here.
851 maxNewLogLevelType = eUNDEFINED;
852
854 return addedCount;
855 }
856
857 void
859 {
860 maxEntries = max;
862 }
863
864 void
866 {
867 if (maxEntries == 0 || !materialised)
868 {
869 return;
870 }
871
872 // Headroom factor: let the window grow to maxEntries * 1.3 before trimming back to
873 // maxEntries, so the O(n) front-erase is amortized over many insertions instead of
874 // running on every new message.
875 constexpr double headroomFactor = 1.3;
876
877 std::size_t currentSize;
878 {
879 std::unique_lock lock(rowsMutex);
880 currentSize = rows.size();
881 }
882 if (currentSize <= static_cast<std::size_t>(maxEntries * headroomFactor))
883 {
884 return;
885 }
886
887 const int removeCount = static_cast<int>(currentSize - maxEntries);
888 // endRemoveRows is emitted without rowsMutex held (the view may call data(), which
889 // takes that mutex).
890 beginRemoveRows(QModelIndex(), 0, removeCount - 1);
891 {
892 std::unique_lock lock(rowsMutex);
893 rows.erase(rows.begin(), rows.begin() + removeCount);
894 }
895 endRemoveRows();
896 }
897
898 void
900 {
901 if (!materialised)
902 {
903 return;
904 }
905
906 int removeCount = 0;
907 {
908 std::unique_lock lock(rowsMutex);
909 const auto end = std::lower_bound(rows.begin(),
910 rows.end(),
911 firstSeq,
912 [](const Row& r, LogSeq seq)
913 { return r.seq < seq; });
914 removeCount = static_cast<int>(std::distance(rows.begin(), end));
915 }
916 if (removeCount <= 0)
917 {
918 return;
919 }
920
921 beginRemoveRows(QModelIndex(), 0, removeCount - 1);
922 {
923 std::unique_lock lock(rowsMutex);
924 rows.erase(rows.begin(), rows.begin() + removeCount);
925 }
926 endRemoveRows();
927 }
928
929 LogSeq
930 LogTableModel::seqAt(int row) const
931 {
932 std::unique_lock lock(rowsMutex);
933 if (row < 0 || row >= (signed int)rows.size())
934 {
935 return 0;
936 }
937 return rows[row].seq;
938 }
939
940 int
942 {
943 std::unique_lock lock(rowsMutex);
944 for (std::size_t row = 0; row < rows.size(); ++row)
945 {
946 if (rows[row].seq == seq)
947 {
948 return static_cast<int>(row);
949 }
950 }
951 return -1;
952 }
953
954 bool
956 {
957 std::unique_lock lock(rowsMutex);
958 if (row < 0 || row >= (signed int)rows.size())
959 {
960 return false;
961 }
962 return rows[row].markerColor.isValid();
963 }
964
965 int
966 LogTableModel::getColumn(const std::string& columnName) const
967 {
968 if (columnName.empty())
969 {
970 return -1;
971 }
972
973 QString qcolumnName = QString::fromStdString(columnName);
974
975 for (int i = 0; i < columnCount(QModelIndex()); i++)
976 {
977 if (headerData(i, Qt::Horizontal, Qt::DisplayRole).toString().compare(qcolumnName) == 0)
978 {
979 return i;
980 }
981 }
982
983 return -1;
984 }
985
986 int
988 {
989 // The store is shared, so clearing one filter can only clear this view. The
990 // watermark keeps the cleared lines from reappearing when the rows are rebuilt
991 // from the store (on hide/show, or on a filter change).
992 if (store)
993 {
994 clearedBeforeSeq = store->endSeq();
995 }
996
997 int size = (int)rows.size();
998 if (size == 0)
999 {
1000 // Nothing to remove; beginRemoveRows(0, -1) would be an invalid range.
1001 return 0;
1002 }
1003 beginRemoveRows(QModelIndex(), 0, size - 1);
1004 {
1005 std::unique_lock lock(rowsMutex);
1006 rows.clear();
1007 }
1008 endRemoveRows();
1009 maxNewLogLevelType = eUNDEFINED;
1010 return size;
1011 }
1012
1013 const LogMessage&
1015 {
1016 std::unique_lock lock(rowsMutex);
1017 const StoredLogEntry* stored = entryAt(static_cast<int>(row));
1018 if (!stored)
1019 {
1020 static const LogMessage empty{};
1021 return empty;
1022 }
1023 return stored->message;
1024 }
1025
1026 int
1028 {
1029 std::unique_lock lock(rowsMutex);
1030 const StoredLogEntry* stored = entryAt(row);
1031 return stored ? stored->lineCount : 1;
1032 }
1033
1034 bool
1035 LogTableModel::applyFilter(const LogFilter& filter, const LogMessage& logMsg)
1036 {
1037 // Substring match for manual filters; whole-value match for exact (auto) filters,
1038 // so e.g. the auto-filter for component "bar" does not also match "foo_bar".
1039 // Exact matches stay on std::string entirely; only the (rare, user-created)
1040 // substring filters convert the field, and never the filter value itself.
1041 const auto matches = [&filter](const std::string& field, Qt::CaseSensitivity cs)
1042 {
1043 if (filter.mode == MatchMode::Exact)
1044 {
1045 return cs == Qt::CaseSensitive ? field == filter.value
1046 : iEquals(field, filter.value);
1047 }
1048 return QString::fromStdString(field).contains(filter.qValue, cs);
1049 };
1050
1051 switch (filter.column)
1052 {
1053 case LogColumn::Group:
1054 return matches(logMsg.group, Qt::CaseSensitive);
1056 return matches(logMsg.who, Qt::CaseInsensitive);
1057 case LogColumn::Tag:
1058 return matches(logMsg.tag, Qt::CaseInsensitive);
1060 return !(logMsg.type < filter.intValue);
1061 case LogColumn::Message:
1062 return matches(logMsg.what, Qt::CaseInsensitive);
1063 case LogColumn::File:
1064 return matches(logMsg.file, Qt::CaseInsensitive) ||
1065 filter.intValue == logMsg.line;
1067 return matches(logMsg.function, Qt::CaseInsensitive);
1068 case LogColumn::Time:
1069 case LogColumn::Unknown:
1070 break;
1071 }
1072
1073 return true;
1074 }
1075
1076 bool
1077 LogTableModel::applyFilters(const LogMessage& logMsg)
1078 {
1079 for (unsigned int i = 0; i < activeFilters.size(); i++)
1080 {
1081 if (!applyFilter(activeFilters[i], logMsg))
1082 {
1083 return false;
1084 }
1085 }
1086
1087 return true;
1088 }
1089} // 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_TIMESTR
Definition LogTable.h:41
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:45
#define ARMARX_LOG_LOGGINGGROUPSTR
Definition LogTable.h:48
#define ARMARX_LOG_FUNCTIONSTR
Definition LogTable.h:47
Shared, session-only store of pinned ("labeled") log lines.
static std::string levelToString(MessageTypeT type)
The single bounded buffer holding every log line the viewer retains.
Definition LogStore.h:63
bool contains(LogSeq seq) const
Definition LogStore.cpp:123
std::size_t size() const
Definition LogStore.cpp:135
void setMaterialised(bool materialised)
Build rows (materialised) or drop them and only keep counting (not).
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
void setMarkerRegistry(LogMarkerRegistry *registry)
Attach the shared pin registry.
void dropRowsBefore(LogSeq firstSeq)
Discard rows whose store entry has been dropped. Called after the store trims.
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
bool applyFilter(const LogFilter &filter, const LogMessage &logMsg)
int lineCountAt(int row) const
Number of display lines of the message column for the given row.
void setMaxEntries(std::size_t maxEntries)
Upper bound on displayed rows.
int rowForSeq(LogSeq seq) const
Display row showing seq, or -1 if it is not shown.
int addEntries(const std::vector< LogSeq > &seqs, MessageType *batchMaxLevelOut=nullptr)
Append those of seqs that pass this model's filters, in one insertion.
const LogMessage & getLogEntry(size_t row) const
The message shown in the given row. Only valid while the row exists.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
LogSeq seqAt(int row) const
Store sequence number of the given display row, or 0 if the row is invalid.
void enforceMaxEntries()
Trim the oldest rows once the display window is exceeded.
int getColumn(const std::string &columnName) const
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 rows (full reset).
void setStore(const LogStore *store)
Attach the shared backing store. Must be called before any ingestion.
void search(const QString &searchStr)
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.
quint64 LogSeq
Identifies a stored log entry for as long as it is retained.
Definition LogStore.h:37
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
MessageTypeT
Definition LogSender.h:46
int intValue
value as int, for the verbosity / file-line comparisons
std::string value
QString qValue
value as QString, for substring (Contains) matching
A single pinned log line: the store entry it refers to plus the color assigned to it.
A retained log line: the message plus the display data derived from it.
Definition LogStore.h:43
QString displayMessage
truncated message column (4)
Definition LogStore.h:46
int lineCount
number of display lines in displayMessage
Definition LogStore.h:47
QString timeString
formatted time column (0)
Definition LogStore.h:45