ACAV f0ba4b7c9529
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
LogDock.cpp
Go to the documentation of this file.
1/*$!{
2* Aurora Clang AST Viewer (ACAV)
3*
4* Copyright (c) 2026 Min Liu
5* Copyright (c) 2026 Michael David Adams
6*
7* SPDX-License-Identifier: GPL-2.0-or-later
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <https://www.gnu.org/licenses/>.
21}$!*/
22
25
26#include "ui/LogDock.h"
27#include <QMenu>
28#include <QMutexLocker>
29#include <QPlainTextEdit>
30#include <QScrollBar>
31#include <QTabWidget>
32#include <QVBoxLayout>
33#include <algorithm>
34
35namespace acav {
36
37namespace {
38
39QPlainTextEdit *createLogView(QWidget *parent) {
40 auto *view = new QPlainTextEdit(parent);
41 view->setReadOnly(true);
42 view->setLineWrapMode(QPlainTextEdit::NoWrap);
43 view->setMaximumBlockCount(5000);
44 view->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
45 view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
46 view->setMinimumHeight(0); // Allow dock to be resized small
47 return view;
48}
49
50QString levelToString(LogLevel level) {
51 switch (level) {
52 case LogLevel::Debug:
53 return "DEBUG";
54 case LogLevel::Info:
55 return "INFO";
56 case LogLevel::Warning:
57 return "WARNING";
58 case LogLevel::Error:
59 return "ERROR";
60 }
61 return "INFO";
62}
63
64} // namespace
65
66LogDock::LogDock(QWidget *parent)
67 : QDockWidget(tr("Logs"), parent), tabs_(new QTabWidget(this)),
68 allView_(createLogView(tabs_)), errorView_(createLogView(tabs_)),
69 infoView_(createLogView(tabs_)), debugView_(createLogView(tabs_)),
70 flushTimer_(new QTimer(this)) {
71 setObjectName("logDock");
72
73 auto *container = new QWidget(this);
74 auto *layout = new QVBoxLayout(container);
75 layout->setContentsMargins(6, 4, 6, 4);
76 layout->setSpacing(4);
77 layout->addWidget(tabs_);
78
79 tabs_->addTab(allView_, tr("All"));
80 tabs_->addTab(errorView_, tr("Errors"));
81 tabs_->addTab(infoView_, tr("Info"));
82 tabs_->addTab(debugView_, tr("Debug"));
83 tabs_->setMinimumHeight(0); // Allow dock to be resized small
84 setWidget(container);
85
86 auto setupContextMenu = [this](QPlainTextEdit *view) {
87 view->setContextMenuPolicy(Qt::CustomContextMenu);
88 connect(view, &QWidget::customContextMenuRequested, this,
89 [this, view](const QPoint &pos) {
90 QMenu *menu = view->createStandardContextMenu();
91 menu->addSeparator();
92 QAction *clearAction = menu->addAction(tr("Clear"));
93 QAction *chosen =
94 menu->exec(view->viewport()->mapToGlobal(pos));
95 menu->deleteLater();
96 if (chosen == clearAction) {
97 clearAll();
98 }
99 });
100 };
101 setupContextMenu(allView_);
102 setupContextMenu(errorView_);
103 setupContextMenu(infoView_);
104 setupContextMenu(debugView_);
105
106 flushTimer_->setInterval(100);
107 connect(flushTimer_, &QTimer::timeout, this, &LogDock::flushPending);
108 flushTimer_->start();
109}
110
111void LogDock::enqueue(const LogEntry &entry) {
112 QMutexLocker locker(&mutex_);
113 LogEntry normalized = entry;
114 if (normalized.level == LogLevel::Warning) {
115 normalized.level = LogLevel::Info;
116 }
117 pending_.append(normalized);
118 if (pending_.size() > maxPendingSize_) {
119 const int dropCount = pending_.size() - maxPendingSize_;
120 pending_.erase(pending_.begin(), pending_.begin() + dropCount);
121 }
122}
123
124void LogDock::flushPending() {
125 QVector<LogEntry> batch;
126 {
127 // Lock when output log contents
128 QMutexLocker locker(&mutex_);
129 if (pending_.isEmpty()) {
130 return;
131 }
132 const int takeCount =
133 std::min(maxBatchSize_, static_cast<int>(pending_.size()));
134 batch = pending_.mid(0, takeCount);
135 pending_.erase(pending_.begin(), pending_.begin() + takeCount);
136 }
137
138 appendBatch(batch);
139}
140
141void LogDock::appendBatch(const QVector<LogEntry> &batch) {
142 QString allText;
143 QString errorText;
144 QString infoText;
145 QString debugText;
146
147 allText.reserve(batch.size() * 64);
148
149 for (const LogEntry &entry : batch) {
150 QString line = formatEntry(entry);
151 allText += line + "\n";
152 switch (entry.level) {
153 case LogLevel::Error:
154 errorText += line + "\n";
155 break;
156 case LogLevel::Warning:
157 case LogLevel::Info:
158 infoText += line + "\n";
159 break;
160 case LogLevel::Debug:
161 debugText += line + "\n";
162 break;
163 }
164 }
165
166 appendText(allView_, allText);
167 appendText(errorView_, errorText);
168 appendText(infoView_, infoText);
169 appendText(debugView_, debugText);
170}
171
172void LogDock::appendText(QPlainTextEdit *view, const QString &text) {
173 QString clipped = text;
174 if (clipped.endsWith('\n')) {
175 clipped.chop(1);
176 }
177 if (clipped.isEmpty()) {
178 return;
179 }
180
181 QScrollBar *scrollBar = view->verticalScrollBar();
182 const int previousValue = scrollBar->value();
183 const bool wasAtBottom = previousValue >= scrollBar->maximum() - 1;
184
185 view->appendPlainText(clipped);
186
187 if (autoScroll_ && wasAtBottom) {
188 scrollBar->setValue(scrollBar->maximum());
189 } else {
190 scrollBar->setValue(previousValue);
191 }
192}
193
194QString LogDock::formatEntry(const LogEntry &entry) {
195 const QString time = entry.timestamp.toString("yyyy-MM-dd HH:mm:ss");
196 const QString level = levelToString(entry.level);
197 const QString source = entry.source.isEmpty() ? "acav" : entry.source;
198 return QString("[%1] [%2] [%3] %4").arg(time, level, source, entry.message);
199}
200
202 tabs_->setCurrentIndex(0); // "All" tab is index 0
203 allView_->setFocus();
204}
205
206void LogDock::clearAll() {
207 {
208 QMutexLocker locker(&mutex_);
209 pending_.clear();
210 }
211
212 allView_->clear();
213 errorView_->clear();
214 infoView_->clear();
215 debugView_->clear();
216}
217
218} // namespace acav
Dock widget for displaying logs and diagnostics.
void focusAllTab()
Switch to All tab and set focus.
Definition LogDock.cpp:201