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 tabs_->setMinimumHeight(0);
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();
92 QAction *clearAction = menu->addAction(tr(
"Clear"));
94 menu->exec(view->viewport()->mapToGlobal(pos));
96 if (chosen == clearAction) {
101 setupContextMenu(allView_);
102 setupContextMenu(errorView_);
103 setupContextMenu(infoView_);
104 setupContextMenu(debugView_);
106 flushTimer_->setInterval(100);
107 connect(flushTimer_, &QTimer::timeout,
this, &LogDock::flushPending);
108 flushTimer_->start();
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;
117 pending_.append(normalized);
118 if (pending_.size() > maxPendingSize_) {
119 const int dropCount = pending_.size() - maxPendingSize_;
120 pending_.erase(pending_.begin(), pending_.begin() + dropCount);
124void LogDock::flushPending() {
125 QVector<LogEntry> batch;
128 QMutexLocker locker(&mutex_);
129 if (pending_.isEmpty()) {
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);
141void LogDock::appendBatch(
const QVector<LogEntry> &batch) {
147 allText.reserve(batch.size() * 64);
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";
156 case LogLevel::Warning:
158 infoText += line +
"\n";
160 case LogLevel::Debug:
161 debugText += line +
"\n";
166 appendText(allView_, allText);
167 appendText(errorView_, errorText);
168 appendText(infoView_, infoText);
169 appendText(debugView_, debugText);
172void LogDock::appendText(QPlainTextEdit *view,
const QString &text) {
173 QString clipped = text;
174 if (clipped.endsWith(
'\n')) {
177 if (clipped.isEmpty()) {
181 QScrollBar *scrollBar = view->verticalScrollBar();
182 const int previousValue = scrollBar->value();
183 const bool wasAtBottom = previousValue >= scrollBar->maximum() - 1;
185 view->appendPlainText(clipped);
187 if (autoScroll_ && wasAtBottom) {
188 scrollBar->setValue(scrollBar->maximum());
190 scrollBar->setValue(previousValue);
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);
202 tabs_->setCurrentIndex(0);
203 allView_->setFocus();
206void LogDock::clearAll() {
208 QMutexLocker locker(&mutex_);
Dock widget for displaying logs and diagnostics.
void focusAllTab()
Switch to All tab and set focus.