gboard
Loading...
Searching...
No Matches
gui_session.cc
Go to the documentation of this file.
1#include "gui_session.h"
2
3// geant4
4#include "G4UImanager.hh"
5#include "G4coutDestination.hh"
6
7GUI_Session::GUI_Session(const std::shared_ptr<GOptions>& gopt, GBoard* b) :
8 GBase(gopt, GBOARD_LOGGER),
9 board(b) {
10 // Route Geant4 UI output to this session instance so we can forward it to the GUI board.
11 // SetCoutDestination updates the per-thread stream buffer but does NOT update
12 // masterG4coutDestination, which G4UIQt set to itself in its constructor.
13 // Worker threads use G4MasterForwardcoutDestination -> masterG4coutDestination, so
14 // we must update it here to prevent G4UIQt::ReceiveG4cerr from being called on a
15 // background thread (which would trigger an NSAlert from a non-main thread on macOS).
16 G4UImanager::GetUIpointer()->SetCoutDestination(this);
17 G4coutDestination::masterG4coutDestination = this;
18
19 log->info(1, SFUNCTION_NAME, " g4 dialog : GUI_Session created");
20}
21
22G4int GUI_Session::ReceiveG4cout(const G4String& coutString) {
23 // See header for API docs.
24 if (!board) return 0;
25
26 QString fullQString = QString::fromStdString(coutString);
27 // Split into lines so that the board gets "log-like" incremental entries.
28 fullQString.replace("\r\n", "\n");
29 fullQString.replace('\r', '\n');
30 fullQString.replace(QChar(0x2028), '\n');
31 QStringList lines = fullQString.split('\n', Qt::KeepEmptyParts);
32
33 // Convert ANSI to HTML on the calling thread (no Qt object access needed).
34 QStringList htmlLines;
35 htmlLines.reserve(lines.size());
36 for (const QString& line : lines) { htmlLines << ansiToHtml(line); }
37
38 // Post widget update to the main thread. Qt::AutoConnection calls directly
39 // when already on the main thread, and queues the call otherwise, preventing
40 // NSWindow/NSAlert operations from a Geant4 worker thread on macOS.
41 auto* b = board;
42 QMetaObject::invokeMethod(b, [b, htmlLines]() {
43 for (const QString& htmlLine : htmlLines) { b->appendLog(htmlLine); }
44 });
45 return 0;
46}
47
48
49G4int GUI_Session::ReceiveG4cerr(const G4String& cerrString) {
50 // See header for API docs.
51 if (!board) return 0;
52
53 QString fullQString = QString::fromStdString(cerrString);
54 fullQString.replace("\r\n", "\n");
55 fullQString.replace('\r', '\n');
56 fullQString.replace(QChar(0x2028), '\n');
57 QStringList lines = fullQString.split('\n', Qt::KeepEmptyParts);
58
59 QStringList htmlLines;
60 htmlLines.reserve(lines.size());
61 for (const QString& line : lines) { htmlLines << ansiToHtml(line); }
62
63 auto* b = board;
64 QMetaObject::invokeMethod(b, [b, htmlLines]() {
65 for (const QString& htmlLine : htmlLines) { b->appendLog(htmlLine); }
66 });
67 return 0;
68}
69
70
71// Helper function to convert ANSI escape codes to HTML.
72// This version handles colors (30-37), bold (1), underline (4), and reset (0).
73// It also processes combined codes like [1;31m].
74//
75// Design notes:
76// - Input text is HTML-escaped to ensure it is safe to insert into rich-text widgets.
77// - Formatting state is tracked and translated into minimal tag open/close operations.
78QString GUI_Session::ansiToHtml(const QString& ansiText) {
79 QString htmlText;
80 htmlText.reserve(ansiText.length() * 1.2); // Pre-allocate buffer slightly larger
81
82 // State variables
83 bool isBold = false;
84 bool isUnderlined = false;
85 QString currentColor = ""; // Store the HTML color name/code
86
87 // ANSI color code to the HTML color map
88 QMap<int, QString> colorMap;
89 colorMap[30] = "black";
90 colorMap[31] = "red";
91 colorMap[32] = "green";
92 colorMap[33] = "darkorange"; // Or "yellow" - adjust as needed for visibility
93 colorMap[34] = "blue";
94 colorMap[35] = "magenta";
95 colorMap[36] = "cyan";
96 colorMap[37] = "grey"; // Or "white" - adjust as needed
97
98 // Regex to find ANSI escape sequences: \x1B[ followed by numbers/semicolons, ending in m
99 QRegularExpression ansiRegex("\x1B\\[([0-9;]*)m");
100
101 int lastPos = 0;
102 QRegularExpressionMatchIterator i = ansiRegex.globalMatch(ansiText);
103
104 while (i.hasNext()) {
105 QRegularExpressionMatch match = i.next();
106 int currentPos = match.capturedStart();
107
108 // 1. Append the text segment before the matched ANSI code, escaping it.
109 if (currentPos > lastPos) {
110 QString textSegment = ansiText.mid(lastPos, currentPos - lastPos);
111 // Use toHtmlEscaped for robust escaping of <, >, &
112 htmlText += textSegment.toHtmlEscaped();
113 }
114
115 // 2. Process the ANSI code(s)
116 QString codesStr = match.captured(1); // The part between [ and m
117 QStringList codes = codesStr.split(';', Qt::SkipEmptyParts);
118
119 if (codes.isEmpty()) {
120 // Handle CSI m (equivalent to CSI 0 m - reset)
121 codes.append("0");
122 }
123
124 // Temporary state for processing multiple codes in one sequence
125 bool nextBold = isBold;
126 bool nextUnderlined = isUnderlined;
127 QString nextColor = currentColor;
128 bool resetDetected = false;
129
130 for (const QString& codeStr : codes) {
131 bool ok;
132 int code = codeStr.toInt(&ok);
133 if (!ok) continue; // Skip invalid codes
134
135 if (code == 0) {
136 // Reset all attributes
137 resetDetected = true;
138 nextBold = false;
139 nextUnderlined = false;
140 nextColor = "";
141
142 // Reset applies to all further codes in the same sequence, so stop here.
143 break;
144 }
145 else if (code == 1) {
146 // Bold
147 nextBold = true;
148 }
149 else if (code == 4) {
150 // Underline
151 nextUnderlined = true;
152 }
153 else if (code == 22) {
154 // Normal intensity (neither bold nor faint)
155 nextBold = false; // Turn off bold
156 }
157 else if (code == 24) {
158 // Not underlined
159 nextUnderlined = false; // Turn off underline
160 }
161 else if (code >= 30 && code <= 37) {
162 // Foreground color
163 nextColor = colorMap.value(code, ""); // Get HTML color, empty if unknown
164 }
165 else if (code == 39) {
166 // Default foreground color
167 nextColor = "";
168 }
169 // Ignore other codes (background colors 40-47, faint 2, italic 3, etc.)
170 }
171
172 // 3. Apply state changes by closing/opening tags only if the state changed.
173
174 // Close tags in reverse order if they are being turned off or changed
175 if (isUnderlined && !nextUnderlined) { htmlText += "</u>"; }
176 if (isBold && !nextBold) { htmlText += "</b>"; }
177 if (!currentColor.isEmpty() && currentColor != nextColor) { htmlText += "</font>"; }
178
179 // If reset was detected, ensure all current tags are closed.
180 if (resetDetected) {
181 if (isUnderlined) htmlText += "</u>";
182 if (isBold) htmlText += "</b>";
183 if (!currentColor.isEmpty()) htmlText += "</font>";
184 }
185
186 // Open tags in normal order if they are being turned on or changed
187 if (!currentColor.isEmpty() && currentColor != nextColor) {
188 // Already closed above, now open the new one if needed
189 if (!nextColor.isEmpty()) { htmlText += QString("<font color=\"%1\">").arg(nextColor); }
190 }
191 else if (currentColor.isEmpty() && !nextColor.isEmpty()) {
192 // Was default, now has color
193 htmlText += QString("<font color=\"%1\">").arg(nextColor);
194 }
195
196 if (!isBold && nextBold) { htmlText += "<b>"; }
197 if (!isUnderlined && nextUnderlined) { htmlText += "<u>"; }
198
199 // Update current state
200 isBold = nextBold;
201 isUnderlined = nextUnderlined;
202 currentColor = nextColor;
203
204 // Update position for the next segment
205 lastPos = match.capturedEnd();
206 }
207
208 // Append any remaining text after the last ANSI code.
209 if (lastPos < ansiText.length()) {
210 QString textSegment = ansiText.mid(lastPos);
211 htmlText += textSegment.toHtmlEscaped();
212 }
213
214 // Close any remaining open tags at the very end.
215 if (isUnderlined) { htmlText += "</u>"; }
216 if (isBold) { htmlText += "</b>"; }
217 if (!currentColor.isEmpty()) { htmlText += "</font>"; }
218
219 return htmlText;
220}
221
222
224 // See header for API docs.
225 // Detach Geant4 cout/cerr from our GUI session (avoid dangling callback).
226 if (auto* UIM = G4UImanager::GetUIpointer()) {
227 UIM->SetCoutDestination(nullptr);
228 }
229 // Also clear masterG4coutDestination, which we claimed in the constructor.
230 // Worker threads read this pointer at call time, so nulling it here prevents
231 // any in-flight forwarding from reaching the already-destroyed session.
232 if (G4coutDestination::masterG4coutDestination == this) {
233 G4coutDestination::masterG4coutDestination = nullptr;
234 }
235}
std::shared_ptr< GLogger > log
A Qt widget that displays read-only log text with a compact "top bar" UI.
Definition gboard.h:56
void info(int level, Args &&... args) const
GUI_Session(const std::shared_ptr< GOptions > &gopt, GBoard *board)
Constructs a GUI_Session.
Definition gui_session.cc:7
G4int ReceiveG4cerr(const G4String &cerrString) override
Receives error output from Geant4 and forwards it to the board.
~GUI_Session() override
Destructor.
G4int ReceiveG4cout(const G4String &coutString) override
Receives standard output from Geant4 and forwards it to the board.
constexpr const char * GBOARD_LOGGER
Definition gboard.h:10
int line
#define SFUNCTION_NAME