g4display
Loading...
Searching...
No Matches
g4displayutilities.cc
Go to the documentation of this file.
1// g4displayutilities.cc
2//
3// Implementation of the G4DisplayUtilities tab.
4// Doxygen documentation for this class is authoritative in g4displayutilities.h (see rule 7).
5
7#include "g4display_options.h"
8
9// c++
10#include <sstream>
11#include <string>
12using namespace std;
13
14namespace {
15void addItemsWithCurrent(QComboBox* combo, const QStringList& items, const QString& current) {
16 combo->addItems(items);
17 if (!current.isEmpty() && combo->findText(current) < 0) {
18 combo->addItem(current);
19 }
20 combo->setCurrentText(current);
21}
22
23// Parse a Geant4 colour string ("r g b" floats or a named CSS colour) into a QColor.
24QColor qcolorFromG4(const std::string& s) {
25 std::istringstream iss(s);
26 double r, g, b;
27 if (iss >> r >> g >> b) return QColor::fromRgbF(r, g, b);
28 QColor c(QString::fromStdString(s));
29 return c.isValid() ? c : Qt::white;
30}
31
32// Build an 18×18 rounded color swatch icon.
33QIcon colorSwatchIcon(const QColor& color) {
34 QPixmap pixmap(18, 18);
35 pixmap.fill(Qt::transparent);
36 QPainter painter(&pixmap);
37 painter.setRenderHint(QPainter::Antialiasing);
38 painter.setBrush(color);
39 painter.setPen(QColor(120, 120, 120));
40 painter.drawRoundedRect(pixmap.rect().adjusted(1, 1, -2, -2), 3, 3);
41 return QIcon(pixmap);
42}
43
44QCheckBox* decorationCheckBox(const QString& text, bool checked, QWidget* parent) {
45 auto* checkBox = new QCheckBox(text, parent);
46 checkBox->setChecked(checked);
47 return checkBox;
48}
49
50QDoubleSpinBox* doubleSpin(double value, double min, double max, double step, QWidget* parent) {
51 auto* spin = new QDoubleSpinBox(parent);
52 spin->setRange(min, max);
53 spin->setSingleStep(step);
54 spin->setValue(value);
55 return spin;
56}
57
58string yamlString(const string& value) {
59 string escaped;
60 for (char c : value) {
61 if (c == '\\' || c == '"') { escaped.push_back('\\'); }
62 escaped.push_back(c);
63 }
64 return "\"" + escaped + "\"";
65}
66}
67
68G4DisplayUtilities::G4DisplayUtilities(const std::shared_ptr<GOptions>& gopt,
69 std::shared_ptr<GLogger> logger,
70 QWidget* parent)
71 : QWidget(parent), log(logger), gopts(gopt) {
72 log->debug(CONSTRUCTOR, "G4DisplayUtilities");
73
74 const auto decorations = g4display::getG4Decorations(gopts);
75 sceneTexts = g4display::getSceneTexts(gopts);
76
77 scaleCheck = decorationCheckBox(tr("Scale"), decorations.scale, this);
78 axesCheck = decorationCheckBox(tr("Axes"), decorations.axes, this);
79 eventIDCheck = decorationCheckBox(tr("Event ID"), decorations.eventID, this);
80 dateCheck = decorationCheckBox(tr("Date"), decorations.date, this);
81 logo2DCheck = decorationCheckBox(tr("Logo 2D"), decorations.logo2D, this);
82 logo3DCheck = decorationCheckBox(tr("Logo 3D"), decorations.logo, this);
83 frameCheck = decorationCheckBox(tr("Frame"), decorations.frame, this);
84
85 scaleLengthSpin = doubleSpin(decorations.scaleLength, 0.0, 100000.0, 1.0, this);
86 scaleUnitEdit = new QLineEdit(QString::fromStdString(decorations.scaleUnit), this);
87 scaleDirectionCombo = new QComboBox(this);
88 addItemsWithCurrent(scaleDirectionCombo, {"x", "y", "z"},
89 QString::fromStdString(decorations.scaleDirection));
90
91 frameLineWidthSpin = doubleSpin(decorations.frameLineWidth, 0.1, 20.0, 0.5, this);
92
93 // Initialise colour members from stored options.
94 scaleColor = qcolorFromG4(decorations.scaleColor);
95 frameColor = qcolorFromG4(decorations.frameColor);
96 textColor = Qt::black;
97
98 // EventID and date font sizes.
99 const QString spinBorderStyle = "QSpinBox { border: 1px solid #888; border-radius: 3px; padding: 1px 3px; }";
100
101 eventIDSizeSpin = new QSpinBox(this);
102 eventIDSizeSpin->setRange(8, 100);
103 eventIDSizeSpin->setValue(decorations.eventIDSize);
104 eventIDSizeSpin->setToolTip(tr("Event ID font size"));
105 eventIDSizeSpin->setStyleSheet(spinBorderStyle);
106
107 dateSizeSpin = new QSpinBox(this);
108 dateSizeSpin->setRange(8, 100);
109 dateSizeSpin->setValue(decorations.dateSize);
110 dateSizeSpin->setToolTip(tr("Date font size"));
111 dateSizeSpin->setStyleSheet(spinBorderStyle);
112
113 // Helper: build a small color-swatch button that opens a color dialog on click.
114 auto makeColorBtn = [this](QColor& colorRef, const QString& title) {
115 auto* btn = new QToolButton(this);
116 btn->setToolTip(title);
117 btn->setAutoRaise(true);
118 btn->setIconSize(QSize(18, 18));
119 btn->setIcon(colorSwatchIcon(colorRef));
120 connect(btn, &QToolButton::clicked, this, [this, btn, &colorRef, title]() {
121 QColor c = QColorDialog::getColor(colorRef, this, title);
122 if (c.isValid()) {
123 colorRef = c;
124 btn->setIcon(colorSwatchIcon(c));
125 }
126 });
127 return btn;
128 };
129
130 auto* scaleColorBtn = makeColorBtn(scaleColor, tr("Scale color"));
131 auto* frameColorBtn = makeColorBtn(frameColor, tr("Frame color"));
132 textColorBtn = makeColorBtn(textColor, tr("Text color"));
133
134 // Helper: pair a checkbox and a small widget with a small gap in a single container.
135 auto makeTightPair = [this](QWidget* left, QWidget* right) {
136 auto* container = new QWidget(this);
137 auto* hbox = new QHBoxLayout(container);
138 hbox->setContentsMargins(0, 0, 0, 0);
139 hbox->setSpacing(8);
140 hbox->addWidget(left);
141 hbox->addWidget(right);
142 hbox->addStretch();
143 return container;
144 };
145
146 // Decoration grid.
147 auto* decorationGrid = new QGridLayout;
148 // Row 0: Scale (checkbox + color swatch tightly paired) | Axes
149 decorationGrid->addWidget(makeTightPair(scaleCheck, scaleColorBtn), 0, 0, 1, 2);
150 decorationGrid->addWidget(axesCheck, 0, 2);
151 // Row 1: Event ID (checkbox + size spinbox) | Date (checkbox + size spinbox)
152 decorationGrid->addWidget(makeTightPair(eventIDCheck, eventIDSizeSpin), 1, 0, 1, 2);
153 decorationGrid->addWidget(makeTightPair(dateCheck, dateSizeSpin), 1, 2, 1, 2);
154 // Row 2: Logo 2D | Logo 3D
155 decorationGrid->addWidget(logo2DCheck, 2, 0);
156 decorationGrid->addWidget(logo3DCheck, 2, 2);
157 // Row 3: Frame (checkbox + color swatch tightly paired)
158 decorationGrid->addWidget(makeTightPair(frameCheck, frameColorBtn), 3, 0, 1, 2);
159 decorationGrid->addWidget(new QLabel(tr("Scale Length:")), 4, 0);
160 decorationGrid->addWidget(scaleLengthSpin, 4, 1, 1, 2);
161 decorationGrid->addWidget(new QLabel(tr("Scale Unit:")), 5, 0);
162 decorationGrid->addWidget(scaleUnitEdit, 5, 1, 1, 2);
163 decorationGrid->addWidget(new QLabel(tr("Scale Direction:")), 6, 0);
164 decorationGrid->addWidget(scaleDirectionCombo, 6, 1, 1, 2);
165 decorationGrid->addWidget(new QLabel(tr("Frame Line Width:")),7, 0);
166 decorationGrid->addWidget(frameLineWidthSpin, 7, 1, 1, 2);
167
168 auto* applyDecorationsButton = new QPushButton(tr("Apply Decorations"), this);
169 connect(applyDecorationsButton, &QPushButton::clicked, this, &G4DisplayUtilities::applyDecorations);
170
171 auto* decorationLayout = new QVBoxLayout;
172 decorationLayout->addLayout(decorationGrid);
173 decorationLayout->addWidget(applyDecorationsButton);
174
175 auto* decorationGroup = new QGroupBox(tr("Scene Decorations"), this);
176 decorationGroup->setLayout(decorationLayout);
177
178 textKindCombo = new QComboBox(this);
179 textKindCombo->addItems({"2D", "3D"});
180
181 textLayoutCombo = new QComboBox(this);
182 textLayoutCombo->addItems({"", "left", "centre", "center", "right"});
183
184 textEdit = new QLineEdit(this);
185 textEdit->setPlaceholderText(tr("Text"));
186
187 textXSpin = doubleSpin(0.9, -100000.0, 100000.0, 0.1, this);
188 textYSpin = doubleSpin(-0.9, -100000.0, 100000.0, 0.1, this);
189 textZSpin = doubleSpin(0.0, -100000.0, 100000.0, 0.1, this);
190 textSizeSpin = new QSpinBox(this);
191 textSizeSpin->setRange(1, 1000);
192 textSizeSpin->setValue(24);
193 textDxSpin = doubleSpin(4.0, -100000.0, 100000.0, 0.5, this);
194 textDySpin = doubleSpin(4.0, -100000.0, 100000.0, 0.5, this);
195 textUnitEdit = new QLineEdit(tr("cm"), this);
196
197 textDxSpin->setToolTip(tr("Pixel offset from the projected text anchor."));
198 textDySpin->setToolTip(tr("Pixel offset from the projected text anchor."));
199
200 auto* textPositionGrid = new QGridLayout;
201 textPositionGrid->addWidget(new QLabel(tr("X:")), 0, 0);
202 textPositionGrid->addWidget(textXSpin, 0, 1);
203 textPositionGrid->addWidget(new QLabel(tr("Y:")), 1, 0);
204 textPositionGrid->addWidget(textYSpin, 1, 1);
205 textPositionGrid->addWidget(new QLabel(tr("Z:")), 2, 0);
206 textPositionGrid->addWidget(textZSpin, 2, 1);
207 textPositionGrid->addWidget(new QLabel(tr("Size:")), 3, 0);
208 textPositionGrid->addWidget(textSizeSpin, 3, 1);
209
210 auto* textSettingsGrid = new QGridLayout;
211 textSettingsGrid->addWidget(new QLabel(tr("Kind:")), 0, 0);
212 textSettingsGrid->addWidget(textKindCombo, 0, 1);
213 textSettingsGrid->addWidget(new QLabel(tr("Text:")), 1, 0);
214 textSettingsGrid->addWidget(textEdit, 1, 1);
215 textSettingsGrid->addWidget(textColorBtn, 1, 2);
216 textSettingsGrid->addWidget(new QLabel(tr("Layout:")), 2, 0);
217 textSettingsGrid->addWidget(textLayoutCombo, 2, 1);
218 textSettingsGrid->addWidget(new QLabel(tr("Unit:")), 3, 0);
219 textSettingsGrid->addWidget(textUnitEdit, 3, 1);
220 textSettingsGrid->addWidget(new QLabel(tr("dX (px):")), 4, 0);
221 textSettingsGrid->addWidget(textDxSpin, 4, 1);
222 textSettingsGrid->addWidget(new QLabel(tr("dY (px):")), 5, 0);
223 textSettingsGrid->addWidget(textDySpin, 5, 1);
224
225 auto* textGrid = new QHBoxLayout;
226 textGrid->addLayout(textPositionGrid);
227 textGrid->addLayout(textSettingsGrid);
228
229 auto* addTextButton = new QPushButton(tr("Add Text"), this);
230 connect(addTextButton, &QPushButton::clicked, this, &G4DisplayUtilities::addText);
231
232 auto* clearTextButton = new QPushButton(tr("Clear Text"), this);
233 connect(clearTextButton, &QPushButton::clicked, this, &G4DisplayUtilities::clearTexts);
234
235 auto* textButtonLayout = new QHBoxLayout;
236 textButtonLayout->addWidget(addTextButton);
237 textButtonLayout->addWidget(clearTextButton);
238
239 auto* textLayout = new QVBoxLayout;
240 textLayout->addLayout(textGrid);
241 textLayout->addLayout(textButtonLayout);
242
243 auto* textGroup = new QGroupBox(tr("Scene Text"), this);
244 textGroup->setLayout(textLayout);
245
246 auto* mainLayout = new QVBoxLayout;
247 mainLayout->addWidget(decorationGroup);
248 mainLayout->addWidget(textGroup);
249 mainLayout->addStretch();
250 setLayout(mainLayout);
251}
252
253void G4DisplayUtilities::applyDecorations() {
254 syncOptionsFromControls();
255 emit sceneOptionsChanged();
256}
257
258void G4DisplayUtilities::syncOptionsFromControls() {
259 auto colorToRGB = [](const QColor& c) {
260 return QString("%1 %2 %3")
261 .arg(c.redF(), 0, 'f', 4)
262 .arg(c.greenF(), 0, 'f', 4)
263 .arg(c.blueF(), 0, 'f', 4)
264 .toStdString();
265 };
266
267 ostringstream decorations;
268 decorations << "{"
269 << "scale: " << (scaleCheck->isChecked() ? "true" : "false") << ", "
270 << "scaleLength: " << scaleLengthSpin->value() << ", "
271 << "scaleUnit: " << yamlString(scaleUnitEdit->text().toStdString()) << ", "
272 << "scaleDirection: " << yamlString(scaleDirectionCombo->currentText().toStdString()) << ", "
273 << "scaleColor: " << yamlString(colorToRGB(scaleColor)) << ", "
274 << "axes: " << (axesCheck->isChecked() ? "true" : "false") << ", "
275 << "eventID: " << (eventIDCheck->isChecked() ? "true" : "false") << ", "
276 << "eventIDSize: " << eventIDSizeSpin->value() << ", "
277 << "date: " << (dateCheck->isChecked() ? "true" : "false") << ", "
278 << "dateSize: " << dateSizeSpin->value() << ", "
279 << "logo2D: " << (logo2DCheck->isChecked() ? "true" : "false") << ", "
280 << "logo: " << (logo3DCheck->isChecked() ? "true" : "false") << ", "
281 << "frame: " << (frameCheck->isChecked() ? "true" : "false") << ", "
282 << "frameColor: " << yamlString(colorToRGB(frameColor)) << ", "
283 << "frameLineWidth: " << frameLineWidthSpin->value()
284 << "}";
285 gopts->setOptionValueFromString("g4decoration", decorations.str());
286
287 ostringstream texts;
288 texts << "[";
289 for (size_t i = 0; i < sceneTexts.size(); ++i) {
290 const auto& text = sceneTexts[i];
291 if (i > 0) { texts << ", "; }
292 texts << "{"
293 << "kind: " << yamlString(text.kind) << ", "
294 << "text: " << yamlString(text.text) << ", "
295 << "color: " << yamlString(text.color) << ", "
296 << "layout: " << yamlString(text.layout) << ", "
297 << "x: " << text.x << ", "
298 << "y: " << text.y << ", "
299 << "z: " << text.z << ", "
300 << "unit: " << yamlString(text.unit) << ", "
301 << "size: " << text.size << ", "
302 << "dx: " << text.dx << ", "
303 << "dy: " << text.dy
304 << "}";
305 }
306 texts << "]";
307 gopts->setOptionValueFromString("g4text", texts.str());
308}
309
310void G4DisplayUtilities::addText() {
311 if (textEdit->text().trimmed().isEmpty()) { return; }
312
314 text.kind = textKindCombo->currentText().toStdString();
315 text.color = QString("%1 %2 %3")
316 .arg(textColor.redF(), 0, 'f', 4)
317 .arg(textColor.greenF(), 0, 'f', 4)
318 .arg(textColor.blueF(), 0, 'f', 4)
319 .toStdString();
320 text.layout = textLayoutCombo->currentText().toStdString();
321 text.text = textEdit->text().toStdString();
322 text.x = textXSpin->value();
323 text.y = textYSpin->value();
324 text.z = textZSpin->value();
325 text.size = textSizeSpin->value();
326 text.unit = textUnitEdit->text().toStdString();
327 text.dx = textDxSpin->value();
328 text.dy = textDySpin->value();
329
330 sceneTexts.emplace_back(text);
331 textEdit->clear();
332 syncOptionsFromControls();
333 emit sceneOptionsChanged();
334}
335
336void G4DisplayUtilities::clearTexts() {
337 sceneTexts.clear();
338 syncOptionsFromControls();
339 emit sceneOptionsChanged();
340}
void sceneOptionsChanged()
G4DisplayUtilities(const std::shared_ptr< GOptions > &gopt, std::shared_ptr< GLogger > logger, QWidget *parent=nullptr)
Construct the utilities tab widget.
void debug(debug_type type, Args &&... args) const
void setOptionValueFromString(const std::string &optionName, const std::string &possibleYamlNode)
Option structures and helpers for g4display configuration.
Declaration of the G4DisplayUtilities widget.
CONSTRUCTOR
vector< G4SceneText > getSceneTexts(const std::shared_ptr< GOptions > &gopts)
Extract scene text entries from the g4text option node.
Definition g4Text.cc:13
G4Decorations getG4Decorations(const std::shared_ptr< GOptions > &gopts)
Read the g4decoration option node and return scene decoration settings.
One text annotation to be inserted into the Geant4 scene.
Definition g4Text.h:31
std::string layout
Optional text layout such as "right"; empty keeps Geant4 default layout.
Definition g4Text.h:42
std::string color
Text color name understood by Geant4 (e.g. "black", "red").
Definition g4Text.h:36
std::string kind
Text kind: "2D" for text2D or "3D" for text attached in scene coordinates.
Definition g4Text.h:39
double z
Z position used by 3D text.
Definition g4Text.h:51
std::string text
Text string to be displayed.
Definition g4Text.h:33
int size
Text size parameter passed to Geant4 visualization command.
Definition g4Text.h:57
std::string unit
Unit used by 3D text positions.
Definition g4Text.h:54
double x
X position.
Definition g4Text.h:45
double dy
3D text Y offset.
Definition g4Text.h:63
double y
Y position.
Definition g4Text.h:48
double dx
3D text X offset.
Definition g4Text.h:60