ACAV 69797fb42a2b
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 allView_->setObjectName("logAllView");
84 errorView_->setObjectName("logErrorView");
85 infoView_->setObjectName("logInfoView");
86 debugView_->setObjectName("logDebugView");
87 tabs_->setMinimumHeight(0); // Allow dock to be resized small
88 setWidget(container);
89
90 auto setupContextMenu = [this](QPlainTextEdit *view) {
91 view->setContextMenuPolicy(Qt::CustomContextMenu);
92 connect(view, &QWidget::customContextMenuRequested, this,
93 [this, view](const QPoint &pos) {
94 QMenu *menu = view->createStandardContextMenu();
95 menu->addSeparator();
96 QAction *clearAction = menu->addAction(tr("Clear"));
97 QAction *chosen =
98 menu->exec(view->viewport()->mapToGlobal(pos));
99 menu->deleteLater();
100 if (chosen == clearAction) {
101 clearAll();
102 }
103 });
104 };
105 setupContextMenu(allView_);
106 setupContextMenu(errorView_);
107 setupContextMenu(infoView_);
108 setupContextMenu(debugView_);
109
110 flushTimer_->setInterval(100);
111 connect(flushTimer_, &QTimer::timeout, this, &LogDock::flushPending);
112 flushTimer_->start();
113}
114
115void LogDock::applyFont(const QFont &font) {
116 setFont(font);
117 tabs_->setFont(font);
118
119 for (QPlainTextEdit *view : {allView_, errorView_, infoView_, debugView_}) {
120 if (!view) {
121 continue;
122 }
123 view->setFont(font);
124 view->document()->setDefaultFont(font);
125 }
126}
127
128void LogDock::enqueue(const LogEntry &entry) {
129 QMutexLocker locker(&mutex_);
130 LogEntry normalized = entry;
131 if (normalized.level == LogLevel::Warning) {
132 normalized.level = LogLevel::Info;
133 }
134 pending_.append(normalized);
135 if (pending_.size() > maxPendingSize_) {
136 const int dropCount = pending_.size() - maxPendingSize_;
137 pending_.erase(pending_.begin(), pending_.begin() + dropCount);
138 }
139}
140
141void LogDock::flushPending() {
142 QVector<LogEntry> batch;
143 {
144 // Lock when output log contents
145 QMutexLocker locker(&mutex_);
146 if (pending_.isEmpty()) {
147 return;
148 }
149 const int takeCount =
150 std::min(maxBatchSize_, static_cast<int>(pending_.size()));
151 batch = pending_.mid(0, takeCount);
152 pending_.erase(pending_.begin(), pending_.begin() + takeCount);
153 }
154
155 appendBatch(batch);
156}
157
158void LogDock::appendBatch(const QVector<LogEntry> &batch) {
159 QString allText;
160 QString errorText;
161 QString infoText;
162 QString debugText;
163
164 allText.reserve(batch.size() * 64);
165
166 for (const LogEntry &entry : batch) {
167 QString line = formatEntry(entry);
168 allText += line + "\n";
169 switch (entry.level) {
170 case LogLevel::Error:
171 errorText += line + "\n";
172 break;
173 case LogLevel::Warning:
174 case LogLevel::Info:
175 infoText += line + "\n";
176 break;
177 case LogLevel::Debug:
178 debugText += line + "\n";
179 break;
180 }
181 }
182
183 appendText(allView_, allText);
184 appendText(errorView_, errorText);
185 appendText(infoView_, infoText);
186 appendText(debugView_, debugText);
187}
188
189void LogDock::appendText(QPlainTextEdit *view, const QString &text) {
190 QString clipped = text;
191 if (clipped.endsWith('\n')) {
192 clipped.chop(1);
193 }
194 if (clipped.isEmpty()) {
195 return;
196 }
197
198 QScrollBar *scrollBar = view->verticalScrollBar();
199 const int previousValue = scrollBar->value();
200 const bool wasAtBottom = previousValue >= scrollBar->maximum() - 1;
201
202 view->appendPlainText(clipped);
203
204 if (autoScroll_ && wasAtBottom) {
205 scrollBar->setValue(scrollBar->maximum());
206 } else {
207 scrollBar->setValue(previousValue);
208 }
209}
210
211QString LogDock::formatEntry(const LogEntry &entry) {
212 const QString time = entry.timestamp.toString("yyyy-MM-dd HH:mm:ss");
213 const QString level = levelToString(entry.level);
214 const QString source = entry.source.isEmpty() ? "acav" : entry.source;
215 return QString("[%1] [%2] [%3] %4").arg(time, level, source, entry.message);
216}
217
219 tabs_->setCurrentIndex(0); // "All" tab is index 0
220 allView_->setFocus();
221}
222
223void LogDock::clearAll() {
224 {
225 QMutexLocker locker(&mutex_);
226 pending_.clear();
227 }
228
229 allView_->clear();
230 errorView_->clear();
231 infoView_->clear();
232 debugView_->clear();
233}
234
235} // namespace acav
Dock widget for displaying logs and diagnostics.
void focusAllTab()
Switch to All tab and set focus.
Definition LogDock.cpp:218
void applyFont(const QFont &font)
Apply the display font to all log tabs and text editors.
Definition LogDock.cpp:115