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
6GUI_Session::GUI_Session(const std::shared_ptr<GOptions>& gopt, GBoard* b) :
7 GBase(gopt, GBOARD_LOGGER),
8 board(b) {
9 // Route Geant4 UI output to this session instance, so we can forward it to the GUI board.
10 G4UImanager::GetUIpointer()->SetCoutDestination(this);
11
12 log->info(1, SFUNCTION_NAME, " g4 dialog : GUI_Session created");
13}
14
15G4int GUI_Session::ReceiveG4cout(const G4String& coutString) {
16 // See header for API docs.
17 if (board) {
18 QString fullQString = QString::fromStdString(coutString);
19
20 // Split into lines so that the board gets "log-like" incremental entries.
21 // - KeepEmptyParts preserves blank lines (useful for readability in logs).
22 // - The regex accepts both \n and Windows-style \r\n, plus Unicode line separator.
23 QRegularExpression lineBreakRegex("\r?\n|\u2028");
24 QStringList lines = fullQString.split(lineBreakRegex, Qt::KeepEmptyParts);
25
26 for (const QString& line : lines) {
27 // Convert ANSI attributes (if present) into HTML for rich-text display.
28 QString htmlLine = ansiToHtml(line);
29 board->appendLog(htmlLine);
30 }
31 }
32 return 0;
33}
34
35
36G4int GUI_Session::ReceiveG4cerr(const G4String& cerrString) {
37 // See header for API docs.
38 if (board) {
39 QString fullQString = QString::fromStdString(cerrString);
40
41 // Use the same robust regex for splitting error output.
42 // Note: this string is written with escaping because it is a C++ string literal.
43 QRegularExpression lineBreakRegex("\\r?\\n|\\u2028");
44 QStringList lines = fullQString.split(lineBreakRegex, Qt::KeepEmptyParts);
45
46 for (const QString& line : lines) {
47 QString htmlLine = ansiToHtml(line);
48 board->appendLog(htmlLine);
49 }
50 }
51 return 0;
52}
53
54
55// Helper function to convert ANSI escape codes to HTML.
56// This version handles colors (30-37), bold (1), underline (4), and reset (0).
57// It also processes combined codes like [1;31m].
58//
59// Design notes:
60// - Input text is HTML-escaped to ensure it is safe to insert into rich-text widgets.
61// - Formatting state is tracked and translated into minimal tag open/close operations.
62QString GUI_Session::ansiToHtml(const QString& ansiText) {
63 QString htmlText;
64 htmlText.reserve(ansiText.length() * 1.2); // Pre-allocate buffer slightly larger
65
66 // State variables
67 bool isBold = false;
68 bool isUnderlined = false;
69 QString currentColor = ""; // Store the HTML color name/code
70
71 // ANSI color code to the HTML color map
72 QMap<int, QString> colorMap;
73 colorMap[30] = "black";
74 colorMap[31] = "red";
75 colorMap[32] = "green";
76 colorMap[33] = "darkorange"; // Or "yellow" - adjust as needed for visibility
77 colorMap[34] = "blue";
78 colorMap[35] = "magenta";
79 colorMap[36] = "cyan";
80 colorMap[37] = "grey"; // Or "white" - adjust as needed
81
82 // Regex to find ANSI escape sequences: \x1B[ followed by numbers/semicolons, ending in m
83 QRegularExpression ansiRegex("\x1B\\[([0-9;]*)m");
84
85 int lastPos = 0;
86 QRegularExpressionMatchIterator i = ansiRegex.globalMatch(ansiText);
87
88 while (i.hasNext()) {
89 QRegularExpressionMatch match = i.next();
90 int currentPos = match.capturedStart();
91
92 // 1. Append the text segment before the matched ANSI code, escaping it.
93 if (currentPos > lastPos) {
94 QString textSegment = ansiText.mid(lastPos, currentPos - lastPos);
95 // Use toHtmlEscaped for robust escaping of <, >, &
96 htmlText += textSegment.toHtmlEscaped();
97 }
98
99 // 2. Process the ANSI code(s)
100 QString codesStr = match.captured(1); // The part between [ and m
101 QStringList codes = codesStr.split(';', Qt::SkipEmptyParts);
102
103 if (codes.isEmpty()) {
104 // Handle CSI m (equivalent to CSI 0 m - reset)
105 codes.append("0");
106 }
107
108 // Temporary state for processing multiple codes in one sequence
109 bool nextBold = isBold;
110 bool nextUnderlined = isUnderlined;
111 QString nextColor = currentColor;
112 bool resetDetected = false;
113
114 for (const QString& codeStr : codes) {
115 bool ok;
116 int code = codeStr.toInt(&ok);
117 if (!ok) continue; // Skip invalid codes
118
119 if (code == 0) {
120 // Reset all attributes
121 resetDetected = true;
122 nextBold = false;
123 nextUnderlined = false;
124 nextColor = "";
125
126 // Reset applies to all further codes in the same sequence, so stop here.
127 break;
128 }
129 else if (code == 1) {
130 // Bold
131 nextBold = true;
132 }
133 else if (code == 4) {
134 // Underline
135 nextUnderlined = true;
136 }
137 else if (code == 22) {
138 // Normal intensity (neither bold nor faint)
139 nextBold = false; // Turn off bold
140 }
141 else if (code == 24) {
142 // Not underlined
143 nextUnderlined = false; // Turn off underline
144 }
145 else if (code >= 30 && code <= 37) {
146 // Foreground color
147 nextColor = colorMap.value(code, ""); // Get HTML color, empty if unknown
148 }
149 else if (code == 39) {
150 // Default foreground color
151 nextColor = "";
152 }
153 // Ignore other codes (background colors 40-47, faint 2, italic 3, etc.)
154 }
155
156 // 3. Apply state changes by closing/opening tags only if the state changed.
157
158 // Close tags in reverse order if they are being turned off or changed
159 if (isUnderlined && !nextUnderlined) { htmlText += "</u>"; }
160 if (isBold && !nextBold) { htmlText += "</b>"; }
161 if (!currentColor.isEmpty() && currentColor != nextColor) { htmlText += "</font>"; }
162
163 // If reset was detected, ensure all current tags are closed.
164 if (resetDetected) {
165 if (isUnderlined) htmlText += "</u>";
166 if (isBold) htmlText += "</b>";
167 if (!currentColor.isEmpty()) htmlText += "</font>";
168 }
169
170 // Open tags in normal order if they are being turned on or changed
171 if (!currentColor.isEmpty() && currentColor != nextColor) {
172 // Already closed above, now open the new one if needed
173 if (!nextColor.isEmpty()) { htmlText += QString("<font color=\"%1\">").arg(nextColor); }
174 }
175 else if (currentColor.isEmpty() && !nextColor.isEmpty()) {
176 // Was default, now has color
177 htmlText += QString("<font color=\"%1\">").arg(nextColor);
178 }
179
180 if (!isBold && nextBold) { htmlText += "<b>"; }
181 if (!isUnderlined && nextUnderlined) { htmlText += "<u>"; }
182
183 // Update current state
184 isBold = nextBold;
185 isUnderlined = nextUnderlined;
186 currentColor = nextColor;
187
188 // Update position for the next segment
189 lastPos = match.capturedEnd();
190 }
191
192 // Append any remaining text after the last ANSI code.
193 if (lastPos < ansiText.length()) {
194 QString textSegment = ansiText.mid(lastPos);
195 htmlText += textSegment.toHtmlEscaped();
196 }
197
198 // Close any remaining open tags at the very end.
199 if (isUnderlined) { htmlText += "</u>"; }
200 if (isBold) { htmlText += "</b>"; }
201 if (!currentColor.isEmpty()) { htmlText += "</font>"; }
202
203 return htmlText;
204}
205
206
208 // See header for API docs.
209 // Detach Geant4 cout/cerr from our GUI session (avoid dangling callback).
210 if (auto* UIM = G4UImanager::GetUIpointer()) {
211 // If available in your G4 version, prefer checking the current destination:
212 // if (UIM->GetCoutDestination() == gui_session.get()) { ... }
213 UIM->SetCoutDestination(nullptr);
214 }
215}
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 appendLog(const QString &text)
Appends a log line to the internal history and updates the display.
Definition gboard.cc:73
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:6
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
#define SFUNCTION_NAME