g4dialog
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#include "g4dialog_options.h" // Provides G4DIALOG_LOGGER constant and option definitions
5
6// qt
7#include <QVBoxLayout>
8
9
10GBoard::GBoard(const std::shared_ptr<GOptions>& gopt, QWidget* parent)
11 : QWidget(parent), GBase(gopt, GBOARD_LOGGER) {
12
13 // --- Create top bar widgets ---
14 searchLineEdit = new QLineEdit(this);
15 searchLineEdit->setPlaceholderText("Filter log lines (case insensitive)...");
16 searchLineEdit->setClearButtonEnabled(true); // Nice feature to easily clear search
17
18
19 clearButton = new QToolButton(this);
20 clearButton->setIcon(style()->standardIcon(QStyle::SP_DialogResetButton));
21 clearButton->setToolTip("Clear Log");
22 clearButton->setText("Clear"); // for some reason, the SP_TrashIcon icon is not showing, so using SP_DialogResetButton
23 clearButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // Or other style
24 clearButton->setEnabled(true);
25
26 saveButton = new QToolButton(this);
27 saveButton->setIcon(style()->standardIcon(QStyle::SP_DialogSaveButton));
28 saveButton->setToolTip("Save Log to File");
29 saveButton->setEnabled(true);
30
31
32 // Create a horizontal layout for the top bar.
33 auto* topBarLayout = new QHBoxLayout;
34 topBarLayout->addWidget(searchLineEdit);
35 topBarLayout->addWidget(clearButton);
36 topBarLayout->addWidget(saveButton);
37 topBarLayout->setSpacing(5);
38
39 // Create a QTextEdit for log messages.
40 logTextEdit = new QTextEdit(this);
41 logTextEdit->setReadOnly(true);
42 logTextEdit->setMinimumHeight(200);
43 logTextEdit->setMinimumWidth(400);
44
45 // Set font family that supports the symbols if needed
46 // QFont font("Courier New", 12); // Example: Mono font often good for logs
47 // font.setFamily("Monospace");
48 // logTextEdit->setFont(font);
49
50
51 auto* layout = new QVBoxLayout(this);
52 layout->addLayout(topBarLayout);
53 layout->addWidget(logTextEdit, 1); // 1: stretchable
54 setLayout(layout);
55
56 // --- Connect signals to slots ---
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) { // Renamed param for clarity
65 if (!logTextEdit) return;
66
67
68 // Ensure GUI updates happen in the GUI thread
69 if (QThread::currentThread() != logTextEdit->thread()) {
70 QMetaObject::invokeMethod(this, "appendLog", Qt::QueuedConnection, Q_ARG(QString, htmlFragment));
71 return;
72 }
73
74 // Get document and cursor
75 QTextDocument* doc = logTextEdit->document();
76 QTextCursor cursor = logTextEdit->textCursor();
77
78 // Determine if scrolled to bottom BEFORE inserting
79 bool isAtEnd = (cursor.position() == doc->characterCount() - 1) ||
80 (logTextEdit->verticalScrollBar()->value() == logTextEdit->verticalScrollBar()->maximum());
81
82
83 // Move cursor to the end always
84 cursor.movePosition(QTextCursor::End);
85 logTextEdit->setTextCursor(cursor);
86
87 // Insert the HTML fragment received from GUI_Session
88 // CRITICAL: Do NOT add an extra <br> here if GUI_Session is already sending line-by-line
89 // Let's try inserting just the fragment first. QTextEdit might handle line breaks better itself.
90 // logTextEdit->insertHtml(htmlFragment + "<br>");
91 logTextEdit->insertHtml(htmlFragment); // TRY THIS FIRST
92
93 // --- Force a paragraph break AFTER inserting (alternative to <br>) ---
94 // This explicitly creates a new QTextBlock
95 cursor.insertBlock(); // TRY THIS INSTEAD OF/AFTER insertHtml
96
97 logTextEdit->setTextCursor(cursor); // Ensure cursor is at the very end
98
99 // --- Apply Filter to the Newly Added Block (or the previous one) ---
100 // Since we just inserted a block, the relevant text might be in the *previous* block
101 QTextBlock targetBlock = cursor.block().previous(); // Get the block we likely just finished
102 if (!targetBlock.isValid()) { // If it was the very first block
103 targetBlock = doc->firstBlock();
104 }
105
106 if (targetBlock.isValid() && !currentFilterText.isEmpty()) {
107 bool matches = targetBlock.text().indexOf(currentFilterText, 0, Qt::CaseInsensitive) >= 0;
108 targetBlock.setVisible(matches);
109 }
110 else if (targetBlock.isValid()) {
111 // Ensure visible if filter is empty
112 targetBlock.setVisible(true);
113 }
114
115 // Auto-scroll to the bottom ONLY if the user was already at the bottom before insert
116 if (isAtEnd) { logTextEdit->verticalScrollBar()->setValue(logTextEdit->verticalScrollBar()->maximum()); }
117}
118
119// Slot to Filter the Log View
120void GBoard::filterLog(const QString& searchText) {
121 if (!logTextEdit) return;
122
123 currentFilterText = searchText.trimmed(); // Store trimmed filter text
124 QTextDocument* document = logTextEdit->document();
125
126 // Use Qt::CaseInsensitive for case-insensitive search
127 Qt::CaseSensitivity cs = Qt::CaseInsensitive;
128
129 QTextCursor hideCursor(document); // Use a cursor for block manipulation efficiency
130 hideCursor.beginEditBlock(); // Group visibility changes for performance
131
132 for (QTextBlock block = document->begin(); block.isValid(); block = block.next()) {
133 QString blockText = block.text();
134 bool contains = (block.text().indexOf(currentFilterText, 0, cs) >= 0);
135 bool matches = currentFilterText.isEmpty() || contains;
136 block.setVisible(matches);
137 }
138
139 hideCursor.endEditBlock(); // End grouping
140
141 // Ensure the viewport updates to reflect visibility changes
142 logTextEdit->viewport()->update();
143
144 // Optional: scroll to top after filtering might be helpful sometimes
145 // logTextEdit->verticalScrollBar()->setValue(0);
146
147 //log->debug(NORMAL, "Filter applied: '", currentFilterText.toStdString(), "'");
148}
149
150void GBoard::clearLog() {
151 if (logTextEdit) {
152 logTextEdit->clear();
153 // Optional: Log the action itself (if desired)
154 // appendLog(ansiToHtml("Log cleared by user.")); // Be careful not to cause loops
155 log->info(1, "Log cleared by user."); // Log via GLogger is better
156 }
157}
158
159// Slot to Save the Log
160void GBoard::saveLog() {
161 if (!logTextEdit) return;
162
163 QString defaultFileName = "gboard_log.log"; // Suggest a default name
164 QString fileName = QFileDialog::getSaveFileName(this,
165 tr("Save Log File"),
166 defaultFileName, // Default file/path suggestion
167 tr("Log Files (*.log);;Text Files (*.txt);;All Files (*)"));
168
169 if (fileName.isEmpty()) {
170 return; // User cancelled
171 }
172
173 QFile file(fileName);
174 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
175 QMessageBox::warning(this, tr("Save Log Error"),
176 tr("Could not open file %1 for writing:\n%2.")
177 .arg(QDir::toNativeSeparators(fileName), file.errorString()));
178 log->warning("Failed to save log to ", fileName.toStdString(), ". Error: ", file.errorString().toStdString());
179 return;
180 }
181
182 QTextStream out(&file);
183 // Save the plain text content (most common for logs)
184 // This automatically includes *all* lines, even hidden ones
185 out << logTextEdit->toPlainText();
186 file.close(); // Stream destructor closes it, but explicit is fine
187
188 log->info("Log saved successfully to ", fileName.toStdString());
189 // Optional: Show a status bar message or brief confirmation dialog
190}
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 message to the log tab.
Definition gboard.cc:64
constexpr const char * GBOARD_LOGGER