gboard
Loading...
Searching...
No Matches
gboard.cc
Go to the documentation of this file.
1// G4Dialog
2#include "gboard.h"
3#include "gui_session.h"
4
5// qt
6#include <QVBoxLayout>
7#include <QRegularExpression>
8
9
10GBoard::GBoard(const std::shared_ptr<GOptions>& gopt, QWidget* parent)
11 : QWidget(parent),
12 GBase(gopt, GBOARD_LOGGER) {
13 // --- Create top bar widgets ---
14 // The top bar provides a lightweight "console" UX: filter, clear, save.
15 searchLineEdit = new QLineEdit(this);
16 searchLineEdit->setObjectName("searchLineEdit");
17 searchLineEdit->setPlaceholderText(tr("Filter log lines (case insensitive)..."));
18 searchLineEdit->setClearButtonEnabled(true); // Allows quickly removing the filter text.
19
20 clearButton = new QToolButton(this);
21 clearButton->setObjectName("clearButton");
22 clearButton->setIcon(style()->standardIcon(QStyle::SP_DialogResetButton));
23 clearButton->setToolTip(tr("Clear Log"));
24 clearButton->setText(tr("Clear"));
25 // for some reason, the SP_TrashIcon icon is not showing, so using SP_DialogResetButton
26 clearButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // Or other style
27 clearButton->setEnabled(true);
28
29 saveButton = new QToolButton(this);
30 saveButton->setIcon(style()->standardIcon(QStyle::SP_DialogSaveButton));
31 saveButton->setToolTip(tr("Save Log to File"));
32 saveButton->setEnabled(true);
33
34
35 // Create a horizontal layout for the top bar.
36 auto* topBarLayout = new QHBoxLayout;
37 topBarLayout->addWidget(searchLineEdit);
38 topBarLayout->addWidget(clearButton);
39 topBarLayout->addWidget(saveButton);
40 topBarLayout->setSpacing(5);
41
42 // Create a QTextEdit for log messages.
43 // Rich text is enabled so that HTML fragments (for example ANSI-to-HTML conversions) render correctly.
44 logTextEdit = new QTextEdit(this);
45 logTextEdit->setAcceptRichText(true);
46 logTextEdit->setReadOnly(true);
47 logTextEdit->setMinimumHeight(200);
48 logTextEdit->setMinimumWidth(400);
49
50 auto* layout = new QVBoxLayout(this);
51 layout->addLayout(topBarLayout);
52 layout->addWidget(logTextEdit, 1); // 1: stretchable
53 setLayout(layout);
54
55 // --- Connect signals to slots ---
56 // UI changes (typing, clicking) are translated into operations on the stored log history.
57 connect(searchLineEdit, &QLineEdit::textChanged, this, &GBoard::filterLog);
58 connect(clearButton, &QToolButton::clicked, this, &GBoard::clearLog);
59 connect(saveButton, &QToolButton::clicked, this, &GBoard::saveLog);
60
61 log->info(1, "GBoard initialized");
62}
63
64void GBoard::appendLog(const QString& htmlFragment) {
65 // See header for API docs.
66 if (QThread::currentThread() != thread()) {
67 QMetaObject::invokeMethod(this, [this, htmlFragment]() { appendLog(htmlFragment); });
68 return;
69 }
70 if (htmlFragment.trimmed().isEmpty()) { return; }
71
72 // Append to the source of truth, the full log line list.
73 // NOTE: We intentionally store all lines (even when filtered out) so the user can change filters later.
74 fullLogLines.append(htmlFragment);
75
76 // Coalesce output bursts, including synchronous Geant4 initialization, into one incremental update.
77 if (!displayUpdatePending) {
78 displayUpdatePending = true;
79 QMetaObject::invokeMethod(this, "updateDisplay", Qt::QueuedConnection);
80 }
81}
82
83void GBoard::updateDisplay() {
84 // See header for API docs.
85 if (!logTextEdit) return;
86
87 // Ensure QTextEdit manipulation occurs on the GUI thread.
88 // This protects typical usage where log lines arrive from worker threads or external callbacks.
89 if (QThread::currentThread() != logTextEdit->thread()) {
90 QMetaObject::invokeMethod(this, "updateDisplay", Qt::QueuedConnection);
91 return;
92 }
93
94 displayUpdatePending = false;
95
96 const bool filtering = !currentFilterText.isEmpty();
97 Qt::CaseSensitivity cs = Qt::CaseInsensitive;
98
99 // Previously displayed history remains in place; only visit newly received lines.
100 while (displayedLineCount < fullLogLines.size()) {
101 const auto& line = fullLogLines[displayedLineCount++];
102 bool matches = true;
103
104 if (filtering) { matches = (line.indexOf(currentFilterText, 0, cs) >= 0); }
105
106 if (matches) { logTextEdit->append(line); }
107 }
108
109 // Auto-scroll to the bottom after updating the display.
110 logTextEdit->verticalScrollBar()->setValue(logTextEdit->verticalScrollBar()->maximum());
111}
112
113void GBoard::filterLog(const QString& searchText) {
114 // See header for API docs.
115 // Keep the filter string normalized so repeated updates remain stable.
116 currentFilterText = searchText.trimmed();
117 logTextEdit->clear();
118 displayedLineCount = 0;
119 updateDisplay();
120}
121
122void GBoard::clearLog() {
123 // See header for API docs.
124 if (logTextEdit) {
125 fullLogLines.clear();
126 logTextEdit->clear();
127 displayedLineCount = 0;
128 updateDisplay();
129 log->info(1, "Log cleared by user.");
130 }
131}
132
133// Slot to Save the Log
134void GBoard::saveLog() {
135 // See header for API docs.
136 if (!logTextEdit) return;
137 updateDisplay(); // Include queued output in the exported view.
138
139 QString defaultFileName = "gboard_log.log"; // Suggest a default name
140 QString fileName = QFileDialog::getSaveFileName(this,
141 tr("Save Log File"),
142 defaultFileName, // Default file/path suggestion
143 tr("Log Files (*.log);;Text Files (*.txt);;All Files (*)"));
144
145 if (fileName.isEmpty()) {
146 return; // User cancelled
147 }
148
149 QFile file(fileName);
150 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
151 QMessageBox::warning(this, tr("Save Log Error"),
152 tr("Could not open file %1 for writing:\n%2.")
153 .arg(QDir::toNativeSeparators(fileName), file.errorString()));
154 log->warning(
155 "Failed to save log to %1. Error: %2", fileName.toStdString(), file.errorString().toStdString());
156 return;
157 }
158
159 QTextStream out(&file);
160 // Save the plain text content (most common for logs).
161 // This captures exactly what the user sees (including filtering) in a portable log-friendly format.
162 out << logTextEdit->toPlainText();
163 file.close(); // Stream destructor closes it, but explicit is fine
164
165 log->info("Log saved successfully to %1", fileName.toStdString());
166 // Optional: Show a status bar message or brief confirmation dialog
167}
GBase(const std::shared_ptr< GOptions > &gopt, std::string logger_name="")
std::shared_ptr< GLogger > log
GBoard(const std::shared_ptr< GOptions > &gopt, QWidget *parent=nullptr)
Constructs a new GBoard widget.
Definition gboard.cc:10
void appendLog(const QString &text)
Appends a log line to the internal history and updates the display.
Definition gboard.cc:64
constexpr const char * GBOARD_LOGGER
Definition gboard.h:10