LogTable.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#include "LogTable.h"
23
24#include <algorithm>
25#include <filesystem>
26
27#include <QApplication>
28#include <QHeaderView>
29#include <QMenu>
30#include <QScrollBar>
31#include <QTimer>
32
33#include "LogMessageDelegate.h"
34#include "LogTableModel.h"
35
36namespace armarx
37{
38 LogTable::LogTable(QWidget* parent) :
39 QTableView(parent), newMessageCount(0), maxNewLogLevelType(eUNDEFINED)
40 {
41 autoscrollActive = true;
43
44 this->setVerticalScrollMode(ScrollMode::ScrollPerPixel);
45
46 setObjectName(QString::fromUtf8("tableLog"));
47 QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Expanding);
48 sizePolicy2.setHorizontalStretch(10);
49 sizePolicy2.setVerticalStretch(10);
50 sizePolicy2.setHeightForWidth(sizePolicy().hasHeightForWidth());
51 setSizePolicy(sizePolicy2);
52 setSizeIncrement(QSize(1, 0));
53 setEditTriggers(QAbstractItemView::DoubleClicked); //QAbstractItemView::NoEditTriggers
54 setAlternatingRowColors(true);
55 setShowGrid(true);
56 setGridStyle(Qt::SolidLine);
57 setSortingEnabled(false);
58 setWordWrap(true);
59 setCornerButtonEnabled(true);
60
61 setSelectionMode(QAbstractItemView::SingleSelection);
62 setSelectionBehavior(QAbstractItemView::SelectRows);
63
64 horizontalHeader()->setVisible(true);
65 horizontalHeader()->setCascadingSectionResizes(true);
66 horizontalHeader()->setDefaultSectionSize(100);
67 horizontalHeader()->setMinimumSectionSize(20);
68 horizontalHeader()->setStretchLastSection(true);
69 verticalHeader()->setVisible(false);
70 verticalHeader()->setDefaultSectionSize(20);
71
72 // verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
73
74 setModel(new LogTableModel(this));
75 //connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
76 setColumnWidth(0, 90);
77 setColumnWidth(1, 90);
78 setColumnWidth(2, 90);
79 setColumnWidth(3, 60);
80 setColumnWidth(4, 700);
81 setColumnWidth(5, 150);
82 setColumnWidth(6, 200);
83 hideColumn(7);
84
85 QFont font;
86 font.setPointSize(8);
87 setFont(font);
88
89
90 setItemDelegateForColumn(getModel()->getColumn(ARMARX_LOG_MESSAGESTR),
91 new LogMessageDelegate());
92
93 QAbstractItemModel* absmodel = qobject_cast<QAbstractItemModel*>(model());
94 connect(absmodel,
95 SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)),
96 this,
97 SLOT(checkAutoScroll(QModelIndex, int, int)));
98 connect(
99 this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClickOnCell(QModelIndex)));
100
101 setContextMenuPolicy(Qt::CustomContextMenu);
102 connect(this,
103 SIGNAL(customContextMenuRequested(QPoint)),
104 this,
105 SLOT(showContextMenu(QPoint)));
106 connect(absmodel,
107 SIGNAL(dataChanged(QModelIndex, QModelIndex)),
108 this,
109 SLOT(itemsAdded(QModelIndex, QModelIndex)));
110 connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(checkAutoScroll()));
111
112 // connect(this, SIGNAL(scrollToEnd()), this, SLOT(scrollToBottom()));
113 }
114
116 {
117 }
118
119 void
120 LogTable::showContextMenu(const QPoint& pos)
121 {
122 const QModelIndex index = indexAt(pos);
123 if (!index.isValid())
124 {
125 return;
126 }
127
128 const int row = index.row();
129 // Capture the sequence number, not the row: rows shift as the log grows and trims,
130 // the sequence number does not.
131 const LogSeq seq = getModel()->seqAt(row);
132
133 QMenu menu(this);
134 if (getModel()->isRowMarked(row))
135 {
136 QAction* removeAction = menu.addAction(tr("Remove label"));
137 connect(removeAction,
138 &QAction::triggered,
139 this,
140 [this, seq] { emit removeLabelRequested(seq); });
141 }
142 else
143 {
144 QAction* addAction = menu.addAction(tr("Add label"));
145 connect(
146 addAction, &QAction::triggered, this, [this, seq] { emit addLabelRequested(seq); });
147 }
148 menu.exec(viewport()->mapToGlobal(pos));
149 }
150
151 void
152 LogTable::notifyNewMessages(int count, MessageType maxLevel)
153 {
154 newMessageCount += count;
155 if (maxLevel > maxNewLogLevelType)
156 {
157 maxNewLogLevelType = maxLevel;
158 }
159 }
160
161 QString
163 {
164 return currentLiveFilter;
165 }
166
167 void
168 LogTable::liveFilterRow(const QString& filterStr, int row)
169 {
170 LogTableModel* logModel = getModel();
171 bool contains = logModel->rowContainsString(row, filterStr);
172 if (!isRowHidden(row) && !contains)
173 {
174 setRowHidden(row, true);
175 }
176 else if (isRowHidden(row) && contains)
177 {
178 setRowHidden(row, false);
179 }
180 }
181
182 void
183 LogTable::liveFilter(const QString& filterStr, int startRow)
184 {
185 LogTableModel* logModel = getModel();
186 currentLiveFilter = filterStr;
187 if (filterStr.length() && filterStr.contains(lastLiveFilter))
188 {
189 // incremental filter - Only remove not fitting entries
190 int rowCount = model()->rowCount();
191
192 for (int i = startRow; i < rowCount; i++)
193 {
194 if (!isRowHidden(i) && !logModel->rowContainsString(i, filterStr))
195 {
196 setRowHidden(i, true);
197 }
198 if (i % 1000 == 0)
199 {
200 qApp->processEvents();
201 }
202 if (filterStr != currentLiveFilter)
203 {
204 break; // filterstring already changed again -> cancel
205 }
206 }
207 }
208 else
209 {
210 // filter from scratch
211 // clear();
212 // setRowCount(0);
213 // QList<QTableWidgetItem*>results = findItems(filterStr,Qt::MatchContains);
214 // foreach(QTableWidgetItem*item, results)
215 // {
216 // if(applyFilters(item))
217 // item->
218 // }
219 int rowCount = model()->rowCount();
220
221 for (int i = startRow; i < rowCount; i++)
222 {
223 if (isRowHidden(i))
224 {
225 if (logModel->rowContainsString(i, filterStr))
226 {
227 setRowHidden(i, false);
228 }
229 }
230 else if (!logModel->rowContainsString(i, filterStr))
231 {
232 setRowHidden(i, true);
233 }
234 if (i % 1000 == 0)
235 {
236 qApp->processEvents();
237 }
238 if (filterStr != currentLiveFilter)
239 {
240 break; // filterstring already changed again -> cancel
241 }
242 }
243
244 // std::cout << "logBuffer: " << logBuffer.size() << std::endl;
245 // for(unsigned int i=0; i < logBuffer.size(); i++)
246 // {
247 // if(msgContainsString(logBuffer[i], filterStr) && applyFilters(logBuffer[i]) )
248 // addEntry(logBuffer[i], true);
249 // }
250 }
251
252 lastLiveFilter = filterStr;
253 }
254
255 bool
256 LogTable::liveSearch(const QString& search)
257 {
258 getModel()->search(search);
259 // scrollToBottom();
260 clearSelection();
261 return selectNextSearchResult(true, true);
262 }
263
264 void
266 {
267 // ARMARX_WARNING_S << "LiveFilterReseted";
269 lastLiveFilter = "";
270 liveFilter("");
271 }
272
273 void
275 {
276 getModel()->search("");
277 // repaint();
278 }
279
282 {
283 return dynamic_cast<LogTableModel*>(model());
284 }
285
286 bool
287 LogTable::checkAutoScroll(const QModelIndex& parent, int start, int end)
288 {
289 return checkAutoScroll();
290 }
291
292 bool
294 {
295 if (verticalScrollBar()->value() == verticalScrollBar()->maximum())
296 {
297 autoscrollActive = true;
298 }
299 else
300 {
301 autoscrollActive = false;
302 }
303
304
305 return autoscrollActive;
306 }
307
308 void
309 LogTable::itemsAdded(QModelIndex leftTop, QModelIndex bottomRight)
310 {
311 }
312
313 void
314 LogTable::showEvent(QShowEvent* event)
315 {
316 QTableView::showEvent(event);
318
319 // Materialise: while hidden the model keeps no rows at all, only a count of what
320 // it accepted. Rebuilding them from the shared store is what makes ingestion
321 // independent of the number of filters.
322 getModel()->setMaterialised(true);
323 applyRowHeights(0, getModel()->rowCount() - 1);
324
326 {
327 QTimer::singleShot(
328 50,
329 this,
330 SLOT(
331 scrollToBottom())); // delayed because something is inserting one line after this function or something
332 }
333 }
334
335 void
337 {
338 if (verticalScrollBar()->value() == verticalScrollBar()->maximum())
339 {
340 autoscrollActive = true;
341 }
342 else
343 {
344 autoscrollActive = false;
345 }
346
347 // Release the rows; they are rebuilt from the shared store on the next showEvent.
348 getModel()->setMaterialised(false);
349 }
350
351 void
352 LogTable::applyRowHeights(int start, int end)
353 {
354 LogTableModel* logModel = getModel();
355 const int fontHeight = QFontMetrics(font()).height();
356 const int rowCount = logModel->rowCount();
357 for (int i = std::max(0, start); i <= end && i < rowCount; ++i)
358 {
359 // Use the precomputed line count instead of re-fetching and splitting the
360 // message string (which the model already built once at insert time).
361 const int stringRows = logModel->lineCountAt(i);
362 if (stringRows > 1)
363 {
364 setRowHeight(i, fontHeight * stringRows + 1);
365 }
366 }
367 }
368
369 void
370 LogTable::rowsInserted(const QModelIndex& parent, int start, int end)
371 {
372 // The base implementation keeps the view's geometries, scroll ranges and editor
373 // positions in sync. It was previously skipped entirely.
374 QTableView::rowsInserted(parent, start, end);
375
376 // Only a materialised (i.e. visible) model inserts rows at all -- a hidden one
377 // just counts, and feeds that count in via notifyNewMessages().
378 applyRowHeights(start, end);
379
380 // Autoscrolling is done once per batch by LogViewer::insertPendingEntries, rather
381 // than once per inserted row here.
382 }
383
384 void
385 LogTable::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
386 {
387 // The base implementation drops selections and persistent indexes for the rows
388 // that are about to disappear. It was previously skipped entirely, which only
389 // began to matter once the model gained a bounded history and actually started
390 // removing rows at runtime.
391 QTableView::rowsAboutToBeRemoved(parent, start, end);
392
393 if (end - start >= model()->rowCount() - 1)
394 {
396 }
397 }
398
399 bool
400 LogTable::selectNextSearchResult(bool backwards, bool keepSelectionIfPossible)
401 {
402
403 int checkCounter = 0; // just a counter for avoiding inifite loops
404 int tempSelectedSearchIndex = indexAt(QPoint(10, 10)).row();
405
406 if (selectedIndexes().size() > 0)
407 {
408 tempSelectedSearchIndex = selectedIndexes()[0].row();
409 }
410
411 int oldSelectedSearchIndex = tempSelectedSearchIndex;
412
413 do // search until we reach old line again
414 {
415
416 if ((tempSelectedSearchIndex != oldSelectedSearchIndex || keepSelectionIfPossible) &&
417 getModel()->rowContainsString(tempSelectedSearchIndex,
418 getModel()->getCurrentSearchStr()))
419 {
420 selectRow(tempSelectedSearchIndex);
421 return true;
422 }
423
424 if (backwards)
425 {
426 tempSelectedSearchIndex--;
427 }
428 else
429 {
430 tempSelectedSearchIndex++;
431 }
432
433 if (tempSelectedSearchIndex >= model()->rowCount())
434 {
435 tempSelectedSearchIndex = 0;
436 }
437
438 if (tempSelectedSearchIndex < 0)
439 {
440 tempSelectedSearchIndex = model()->rowCount() - 1;
441 }
442
443
444 checkCounter++;
445
446 if (checkCounter > model()->rowCount())
447 {
448 break;
449 }
450 } while (tempSelectedSearchIndex != oldSelectedSearchIndex);
451
452 return false;
453 }
454
455 void
457 {
458 if (index.column() != getModel()->getColumn(ARMARX_LOG_FILESTR))
459 {
460 return;
461 }
462
463 std::string fileWithLineNumber = model()->data(index).toString().toStdString();
464 const auto colonPos = fileWithLineNumber.rfind(':');
465 if (colonPos == std::string::npos)
466 {
467 // No "file:line" content (e.g. an entry without a source location).
468 return;
469 }
470 std::string file = fileWithLineNumber.substr(0, colonPos);
471 std::string line = fileWithLineNumber.substr(colonPos + 1);
472
473 if (!std::filesystem::exists(file))
474 {
475 ARMARX_INFO << "File '" << file << "' does not exists - cannot open it.";
476 return;
477 }
478
479 fileOpener.openFileWithDefaultEditor(file, atoi(line.c_str()));
480 // std::string command = "qtcreator -client " + fileWithLineNumber.toStdString() + "&";
481 // if(system(command.c_str())){}
482 }
483} // namespace armarx
uint8_t index
#define ARMARX_LOG_FILESTR
Definition LogTable.h:46
#define ARMARX_LOG_MESSAGESTR
Definition LogTable.h:45
A filtered view onto the shared LogStore.
void setMaterialised(bool materialised)
Build rows (materialised) or drop them and only keep counting (not).
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int lineCountAt(int row) const
Number of display lines of the message column for the given row.
LogSeq seqAt(int row) const
Store sequence number of the given display row, or 0 if the row is invalid.
bool rowContainsString(int row, const QString &searchStr) const
void search(const QString &searchStr)
void liveFilter(const QString &search, int startRow=0)
Definition LogTable.cpp:183
void doubleClickOnCell(const QModelIndex &index)
Definition LogTable.cpp:456
void showContextMenu(const QPoint &pos)
Build the right-click menu ("Add label" / "Remove label") for the row under pos.
Definition LogTable.cpp:120
bool selectNextSearchResult(bool backwards=true, bool keepSelectionIfPossible=false)
Definition LogTable.cpp:400
void showEvent(QShowEvent *event) override
Definition LogTable.cpp:314
bool autoscrollActive
Definition LogTable.h:137
QString lastLiveFilter
Definition LogTable.h:140
QString currentLiveFilter
Definition LogTable.h:140
void liveFilterRow(const QString &filterStr, int row)
Definition LogTable.cpp:168
bool liveSearch(const QString &search)
Definition LogTable.cpp:256
LogTableModel * getModel()
Definition LogTable.cpp:281
~LogTable() override
Definition LogTable.cpp:115
void removeLabelRequested(LogSeq seq)
Emitted from the context menu to remove the pin on the given line.
void resetLiveFilter()
Definition LogTable.cpp:265
MessageType maxNewLogLevelType
Definition LogTable.h:139
void hideEvent(QHideEvent *) override
Definition LogTable.cpp:336
QString getCurrentLiveFilter() const
Definition LogTable.cpp:162
LogTable(QWidget *parent=0)
Definition LogTable.cpp:38
bool checkAutoScroll(const QModelIndex &parent, int start, int end)
Definition LogTable.cpp:287
void applyRowHeights(int start, int end)
Give rows [start, end] the height their message needs.
Definition LogTable.cpp:352
void itemsAdded(QModelIndex leftTop, QModelIndex bottomRight)
Definition LogTable.cpp:309
void resetLiveSearch()
Definition LogTable.cpp:274
void resetNewMessageCount()
Clear the new-message badge (count and highest severity seen).
Definition LogTable.h:86
void notifyNewMessages(int count, MessageType maxLevel)
Account for messages this filter accepted while it was hidden.
Definition LogTable.cpp:152
void addLabelRequested(LogSeq seq)
Emitted from the context menu to pin the given line (assign a label).
int selectedSearchIndex
Definition LogTable.h:141
EditorFileOpener fileOpener
Definition LogTable.h:142
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override
Definition LogTable.cpp:385
void rowsInserted(const QModelIndex &parent, int start, int end) override
Definition LogTable.cpp:370
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
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