28#include <QMutexLocker>
29#include <QPlainTextEdit>
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);
50QString levelToString(LogLevel level) {
56 case LogLevel::Warning:
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");
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_);
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);
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();
96 QAction *clearAction = menu->addAction(tr(
"Clear"));
98 menu->exec(view->viewport()->mapToGlobal(pos));
100 if (chosen == clearAction) {
105 setupContextMenu(allView_);
106 setupContextMenu(errorView_);
107 setupContextMenu(infoView_);
108 setupContextMenu(debugView_);
110 flushTimer_->setInterval(100);
111 connect(flushTimer_, &QTimer::timeout,
this, &LogDock::flushPending);
112 flushTimer_->start();
117 tabs_->setFont(font);
119 for (QPlainTextEdit *view : {allView_, errorView_, infoView_, debugView_}) {
124 view->document()->setDefaultFont(font);
128void LogDock::enqueue(
const LogEntry &entry) {
129 QMutexLocker locker(&mutex_);
131 if (normalized.level == LogLevel::Warning) {
132 normalized.level = LogLevel::Info;
134 pending_.append(normalized);
135 if (pending_.size() > maxPendingSize_) {
136 const int dropCount = pending_.size() - maxPendingSize_;
137 pending_.erase(pending_.begin(), pending_.begin() + dropCount);
141void LogDock::flushPending() {
142 QVector<LogEntry> batch;
145 QMutexLocker locker(&mutex_);
146 if (pending_.isEmpty()) {
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);
158void LogDock::appendBatch(
const QVector<LogEntry> &batch) {
164 allText.reserve(batch.size() * 64);
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";
173 case LogLevel::Warning:
175 infoText += line +
"\n";
177 case LogLevel::Debug:
178 debugText += line +
"\n";
183 appendText(allView_, allText);
184 appendText(errorView_, errorText);
185 appendText(infoView_, infoText);
186 appendText(debugView_, debugText);
189void LogDock::appendText(QPlainTextEdit *view,
const QString &text) {
190 QString clipped = text;
191 if (clipped.endsWith(
'\n')) {
194 if (clipped.isEmpty()) {
198 QScrollBar *scrollBar = view->verticalScrollBar();
199 const int previousValue = scrollBar->value();
200 const bool wasAtBottom = previousValue >= scrollBar->maximum() - 1;
202 view->appendPlainText(clipped);
204 if (autoScroll_ && wasAtBottom) {
205 scrollBar->setValue(scrollBar->maximum());
207 scrollBar->setValue(previousValue);
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);
219 tabs_->setCurrentIndex(0);
220 allView_->setFocus();
223void LogDock::clearAll() {
225 QMutexLocker locker(&mutex_);
Dock widget for displaying logs and diagnostics.
void focusAllTab()
Switch to All tab and set focus.
void applyFont(const QFont &font)
Apply the display font to all log tabs and text editors.