dbselect
Loading...
Searching...
No Matches
dbselectView.cc
Go to the documentation of this file.
1// dbselect
2#include "dbselectView.h"
3#include "dbselect_options.h"
4
5// gemc
7
8// qt
9#include <QHBoxLayout>
10#include <QStandardItemModel>
11#include <QHeaderView>
12#include <QStringList>
13
14
15// Implementation notes:
16// - Doxygen documentation is authoritative in dbselectView.h.
17// - This file uses short non-Doxygen comments to explain local implementation decisions.
18
19DBSelectView::DBSelectView(const std::shared_ptr<GOptions>& gopts, GDetectorConstruction* dc, QWidget* parent)
20 : QWidget(parent),
21 GBase(gopts, DBSELECT_LOGGER),
22 db(nullptr),
23 gDetectorConstruction(dc),
24 gopt(gopts) {
25
26 // Read database path/key and default experiment from options.
27 dbhost = gopts->getScalarString("sql");
28 experiment = gopts->getScalarString("experiment");
29
30 // Search order for locating the database file:
31 // 1) current directory
32 // 2) GEMC installation root
33 // 3) GEMC examples directory
34 std::vector<std::string> dirs = {
35 ".",
36 gutilities::gemc_root().string(),
37 (gutilities::gemc_root() / "examples").string()
38 };
39
40 auto dbPath = gutilities::searchForFileInLocations(dirs, dbhost);
41 if (!dbPath) {
42 log->error(ERR_GSQLITEERROR, "Failed to find database file. Exiting.");
43 }
44
45 // Open read-only and ensure the expected table exists and is non-empty.
46 if (sqlite3_open_v2(dbPath.value().c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK || !isGeometryTableValid()) {
47 sqlite3_close(db);
48 db = nullptr;
49 log->error(ERR_GSQLITEERROR, " Failed to open or validate database", dbhost);
50 }
51
52 log->info(1, "Opened database: " + dbhost, " found at ", dbPath.value());
53
54 // Create UI widgets and model.
55 setupUI();
56
57 // During initial population we block itemChanged notifications to prevent
58 // the model initialization from marking the view as user-modified.
59 experimentModel->blockSignals(true);
60 loadExperiments();
61
62 // Verify that the default experiment exists and pre-check it.
63 bool expFound = false;
64 for (int i = 0; i < experimentModel->rowCount(); ++i) {
65 QStandardItem* expItem = experimentModel->item(i, 0);
66 if (expItem && expItem->text() == QString::fromStdString(experiment)) {
67 expItem->setCheckState(Qt::Checked);
68 expFound = true;
69 break;
70 }
71 }
72 if (!expFound) {
73 log->error(ERR_EXPERIMENTNOTFOUND, experiment, " not found in database.", dbhost);
74 }
75
76 // Apply selections from configured GSystem objects (if any).
77 applyGSystemSelections();
78
79 // Update system appearances initially so “volumes” and availability icons are correct.
80 for (int i = 0; i < experimentModel->rowCount(); ++i) {
81 QStandardItem* expItem = experimentModel->item(i, 0);
82 for (int j = 0; j < expItem->rowCount(); ++j) {
83 QStandardItem* sysItem = expItem->child(j, 0);
84 updateSystemItemAppearance(sysItem);
85 }
86 }
87
88 // Initialization complete: restore signals.
89 experimentModel->blockSignals(false);
90
91 // Ensure the view starts unmodified.
92 modified = false;
93 updateModifiedUI();
94}
95
96bool DBSelectView::isGeometryTableValid() const {
97 if (!db)
98 return false;
99
100 sqlite3_stmt* stmt = nullptr;
101 const char* sql_query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='geometry'";
102
103 // First check: table existence.
104 if (sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr) != SQLITE_OK) {
105 log->error(ERR_GSQLITEERROR, "SQL Error: Failed to check geometry table existence:", sqlite3_errmsg(db));
106 }
107
108 bool tableExists = false;
109 if (sqlite3_step(stmt) == SQLITE_ROW) {
110 tableExists = sqlite3_column_int(stmt, 0) > 0;
111 }
112 sqlite3_finalize(stmt);
113
114 if (!tableExists)
115 return false;
116
117 // Second check: table contains data.
118 sql_query = "SELECT COUNT(*) FROM geometry";
119 if (sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr) != SQLITE_OK) {
120 log->error(ERR_GSQLITEERROR, "SQL Error: Failed to count rows in geometry table:", sqlite3_errmsg(db));
121 }
122
123 bool hasData = false;
124 if (sqlite3_step(stmt) == SQLITE_ROW) {
125 hasData = sqlite3_column_int(stmt, 0) > 0;
126 }
127 sqlite3_finalize(stmt);
128
129 return hasData;
130}
131
132void DBSelectView::applyGSystemSelections() {
133 // Pull the current system selection from configuration and mirror it into the UI model.
134 auto gsystems = gsystem::getSystems(gopt);
135
136 for (int i = 0; i < experimentModel->rowCount(); ++i) {
137 QStandardItem* expItem = experimentModel->item(i, 0);
138 if (!expItem)
139 continue;
140
141 // Mark the default experiment as checked if it matches.
142 if (expItem->text() == QString::fromStdString(experiment)) {
143 expItem->setCheckState(Qt::Checked);
144 }
145
146 // Process each child system row under this experiment.
147 for (int j = 0; j < expItem->rowCount(); ++j) {
148 QStandardItem* sysItem = expItem->child(j, 0);
149 QStandardItem* varItem = expItem->child(j, 2);
150 QStandardItem* runItem = expItem->child(j, 3);
151 if (!sysItem || !varItem || !runItem)
152 continue;
153
154 std::string sysName = sysItem->text().toStdString();
155 bool systemFound = false;
156
157 for (auto const& gsys : gsystems) {
158 if (gsys->getName() == sysName) {
159 systemFound = true;
160 sysItem->setCheckState(Qt::Checked);
161
162 // Variations: select configured value if present, otherwise default to first.
163 QStringList availableVariations = getAvailableVariations(sysName);
164 QString selectedVar = QString::fromStdString(gsys->getVariation());
165 if (availableVariations.contains(selectedVar))
166 varItem->setData(selectedVar, Qt::EditRole);
167 else if (!availableVariations.isEmpty())
168 varItem->setData(availableVariations.first(), Qt::EditRole);
169 varItem->setData(availableVariations, Qt::UserRole);
170
171 // Runs: select configured value if present, otherwise default to first.
172 QStringList availableRuns = getAvailableRuns(sysName);
173 QString selectedRun = QString::number(gsys->getRunno());
174 if (availableRuns.contains(selectedRun))
175 runItem->setData(selectedRun, Qt::EditRole);
176 else if (!availableRuns.isEmpty())
177 runItem->setData(availableRuns.first(), Qt::EditRole);
178 runItem->setData(availableRuns, Qt::UserRole);
179
180 updateSystemItemAppearance(sysItem);
181 break;
182 }
183 }
184
185 // If no configured system matches, keep it unchecked.
186 if (!systemFound) {
187 sysItem->setCheckState(Qt::Unchecked);
188 }
189 }
190 }
191}
192
193void DBSelectView::setupUI() {
194 auto mainLayout = new QVBoxLayout(this);
195 mainLayout->setContentsMargins(10, 10, 10, 10);
196 mainLayout->setSpacing(10);
197
198 // Header: title + experiment summary on the left, reload button on the right.
199 auto headerLayout = new QHBoxLayout();
200
201 auto labelLayout = new QVBoxLayout();
202
203 titleLabel = new QLabel("Experiment Selection", this);
204 QFont titleFont("Avenir", 20, QFont::Bold);
205 titleLabel->setFont(titleFont);
206 titleLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
207 labelLayout->addWidget(titleLabel);
208
209 experimentHeaderLabel = new QLabel("", this);
210 experimentHeaderLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
211 experimentHeaderLabel->setWordWrap(true);
212 experimentHeaderLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
213 labelLayout->addWidget(experimentHeaderLabel);
214
215 headerLayout->addLayout(labelLayout);
216 headerLayout->addStretch();
217
218 reloadButton = new QPushButton("Reload", this);
219 reloadButton->setEnabled(false);
220 headerLayout->addWidget(reloadButton);
221 connect(reloadButton, &QPushButton::pressed, this, &DBSelectView::reload_geometry);
222
223 mainLayout->addLayout(headerLayout);
224
225 // Tree view and model.
226 experimentTree = new QTreeView(this);
227 experimentTree->setStyleSheet("QTreeView { alternate-background-color: #f0f0f0; }");
228 experimentTree->setSelectionMode(QAbstractItemView::SingleSelection);
229 experimentTree->setSelectionBehavior(QAbstractItemView::SelectRows);
230 experimentTree->header()->show();
231
232 experimentModel = new QStandardItemModel(this);
233 experimentModel->setHorizontalHeaderLabels(QStringList() << "exp/system" << "volumes" << "variation" << "run");
234
235 experimentTree->setModel(experimentModel);
236
237 // Variation/run columns are edited via drop-downs.
238 experimentTree->setItemDelegateForColumn(2, new ComboDelegate(this));
239 experimentTree->setItemDelegateForColumn(3, new ComboDelegate(this));
240
241 mainLayout->addWidget(experimentTree);
242
243 connect(experimentModel, &QStandardItemModel::itemChanged,
244 this, &DBSelectView::onItemChanged);
245}
246
247void DBSelectView::loadExperiments() {
248 experimentModel->clear();
249
250 sqlite3_stmt* stmt = nullptr;
251 const char* sql_query = "SELECT DISTINCT experiment FROM geometry";
252
253 int rc = sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr);
254 if (rc != SQLITE_OK) {
255 log->error(ERR_GSQLITEERROR, "Failed to prepare experiment query:", sqlite3_errmsg(db));
256 }
257
258 // Populate one top-level item per experiment.
259 while (sqlite3_step(stmt) == SQLITE_ROW) {
260 const char* expText = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
261 if (expText) {
262 QString expName = QString::fromUtf8(expText);
263
264 // Note: this assigns the member used by loadSystemsForExperiment().
265 experiment = expName.toStdString();
266
267 auto* expItem = new QStandardItem(expName);
268 expItem->setFlags(expItem->flags() & ~Qt::ItemIsEditable);
269 expItem->setCheckable(true);
270 expItem->setCheckState(Qt::Unchecked);
271
272 // Dummy columns for the experiment row; only column 0 is meaningful.
273 auto* dummyEntries = new QStandardItem("");
274 auto* dummyVar = new QStandardItem("");
275 auto* dummyRun = new QStandardItem("");
276
277 loadSystemsForExperiment(expItem);
278
279 experimentModel->appendRow(QList<QStandardItem*>() << expItem << dummyEntries << dummyVar << dummyRun);
280 }
281 }
282
283 sqlite3_finalize(stmt);
284}
285
286void DBSelectView::loadSystemsForExperiment(QStandardItem* experimentItem) {
287 sqlite3_stmt* stmt = nullptr;
288 const char* sql_query = "SELECT DISTINCT system FROM geometry WHERE experiment = ?";
289
290 if (sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr) == SQLITE_OK) {
291 // Bind current experiment selection (member variable).
292 sqlite3_bind_text(stmt, 1, experiment.c_str(), -1, SQLITE_TRANSIENT);
293
294 while (sqlite3_step(stmt) == SQLITE_ROW) {
295 const char* sysText = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
296 if (sysText) {
297 auto* sysItem = new QStandardItem(QString::fromUtf8(sysText));
298 sysItem->setFlags(sysItem->flags() & ~Qt::ItemIsEditable);
299 sysItem->setCheckable(true);
300 sysItem->setCheckState(Qt::Unchecked);
301
302 // Column 1: count of matching geometry entries (set later).
303 auto* entriesItem = new QStandardItem("");
304
305 // Column 2: variation (editable, backed by UserRole list).
306 auto* varItem = new QStandardItem();
307 QStringList varList = getAvailableVariations(sysText);
308 if (!varList.isEmpty())
309 varItem->setData(varList.first(), Qt::EditRole);
310 else
311 varItem->setData("", Qt::EditRole);
312 varItem->setData(varList, Qt::UserRole);
313 varItem->setBackground(QColor("lightblue"));
314
315 // Column 3: run (editable, backed by UserRole list).
316 auto* runItem = new QStandardItem();
317 QStringList runList = getAvailableRuns(sysText);
318 if (!runList.isEmpty())
319 runItem->setData(runList.first(), Qt::EditRole);
320 else
321 runItem->setData("", Qt::EditRole);
322 runItem->setData(runList, Qt::UserRole);
323 runItem->setBackground(QColor("lightgreen"));
324
325 QList<QStandardItem*> rowItems;
326 rowItems << sysItem << entriesItem << varItem << runItem;
327 experimentItem->appendRow(rowItems);
328 }
329 }
330 }
331
332 sqlite3_finalize(stmt);
333}
334
335int DBSelectView::getGeometryCount(const std::string& system, const std::string& variation, int run) const {
336 int count = 0;
337 std::string query = "SELECT COUNT(*) FROM geometry WHERE experiment = ? AND system = ? AND variation = ? AND run = ?";
338
339 sqlite3_stmt* stmt = nullptr;
340 if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
341 sqlite3_bind_text(stmt, 1, experiment.c_str(), -1, SQLITE_TRANSIENT);
342 sqlite3_bind_text(stmt, 2, system.c_str(), -1, SQLITE_TRANSIENT);
343 sqlite3_bind_text(stmt, 3, variation.c_str(), -1, SQLITE_TRANSIENT);
344 sqlite3_bind_int(stmt, 4, run);
345
346 if (sqlite3_step(stmt) == SQLITE_ROW) {
347 count = sqlite3_column_int(stmt, 0);
348 }
349 }
350 else {
351 log->error(ERR_GSQLITEERROR, "SQL Error: Failed togetGeometryCounte:", sqlite3_errmsg(db));
352 }
353
354 sqlite3_finalize(stmt);
355 return count;
356}
357
358QStringList DBSelectView::getAvailableVariations(const std::string& system) const {
359 QStringList varList;
360 sqlite3_stmt* stmt = nullptr;
361 const char* sql_query = "SELECT DISTINCT variation FROM geometry WHERE system = ?";
362
363 if (sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr) == SQLITE_OK) {
364 sqlite3_bind_text(stmt, 1, system.c_str(), -1, SQLITE_TRANSIENT);
365
366 while (sqlite3_step(stmt) == SQLITE_ROW) {
367 const char* varText = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
368 if (varText) {
369 varList << QString::fromUtf8(varText);
370 }
371 }
372 }
373
374 sqlite3_finalize(stmt);
375 return varList;
376}
377
378QStringList DBSelectView::getAvailableRuns(const std::string& system) {
379 QStringList runList;
380 sqlite3_stmt* stmt = nullptr;
381 const char* sql_query = "SELECT DISTINCT run FROM geometry WHERE system = ?";
382
383 if (sqlite3_prepare_v2(db, sql_query, -1, &stmt, nullptr) == SQLITE_OK) {
384 sqlite3_bind_text(stmt, 1, system.c_str(), -1, SQLITE_TRANSIENT);
385
386 while (sqlite3_step(stmt) == SQLITE_ROW) {
387 int runVal = sqlite3_column_int(stmt, 0);
388 runList << QString::number(runVal);
389 }
390 }
391
392 sqlite3_finalize(stmt);
393 return runList;
394}
395
396bool DBSelectView::systemAvailable(const std::string& system, const std::string& variation, int run) {
397 std::string query = "SELECT COUNT(*) FROM geometry WHERE system = ? AND variation = ? AND run = ?";
398 sqlite3_stmt* stmt = nullptr;
399
400 if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
401 log->error(ERR_GSQLITEERROR, "SQL Error:systemAvailable: prepare failed:e:", sqlite3_errmsg(db));
402 }
403
404 sqlite3_bind_text(stmt, 1, system.c_str(), -1, SQLITE_TRANSIENT);
405 sqlite3_bind_text(stmt, 2, variation.c_str(), -1, SQLITE_TRANSIENT);
406 sqlite3_bind_int(stmt, 3, run);
407
408 bool available = false;
409 if (sqlite3_step(stmt) == SQLITE_ROW) {
410 int count = sqlite3_column_int(stmt, 0);
411 available = (count > 0);
412 }
413
414 sqlite3_finalize(stmt);
415 return available;
416}
417
418QIcon DBSelectView::createStatusIcon(const QColor& color) {
419 QPixmap pixmap(12, 12);
420 pixmap.fill(color);
421 return QIcon(pixmap);
422}
423
424void DBSelectView::updateSystemItemAppearance(QStandardItem* systemItem) {
425 QStandardItem* parentItem = systemItem->parent();
426 if (!parentItem)
427 return;
428
429 // Determine selection tuple from row state.
430 int row = systemItem->row();
431 QStandardItem* varItem = parentItem->child(row, 2);
432 QStandardItem* runItem = parentItem->child(row, 3);
433
434 QString varStr = varItem ? varItem->data(Qt::EditRole).toString() : "";
435 QString runStr = runItem ? runItem->data(Qt::EditRole).toString() : "";
436
437 int run = runStr.toInt();
438 QString expStr = parentItem->text();
439
440 // Note: the member is updated so subsequent queries use the selected experiment.
441 experiment = expStr.toStdString();
442
443 std::string systemName = systemItem->text().toStdString();
444 std::string variation = varStr.toStdString();
445
446 int count = getGeometryCount(systemName, variation, run);
447
448 // Column 1 is the per-row entry count (“volumes”).
449 QStandardItem* entriesItem = parentItem->child(row, 1);
450 if (entriesItem) {
451 entriesItem->setText(QString::number(count));
452 }
453
454 // Update availability icon based on whether any matching geometry entries exist.
455 bool available = (count > 0);
456 QColor statusColor = available ? QColor("green") : QColor("red");
457 systemItem->setIcon(createStatusIcon(statusColor));
458
459 // Keep the system item readable regardless of icon state.
460 systemItem->setData(QColor("white"), Qt::BackgroundRole);
461 systemItem->setData(QColor("black"), Qt::ForegroundRole);
462}
463
464void DBSelectView::updateExperimentHeader() {
465 QStandardItem* selectedExp = nullptr;
466
467 // Find the single checked top-level experiment.
468 for (int i = 0; i < experimentModel->rowCount(); ++i) {
469 QStandardItem* expItem = experimentModel->item(i, 0);
470 if (expItem && expItem->checkState() == Qt::Checked) {
471 selectedExp = expItem;
472 break;
473 }
474 }
475
476 if (selectedExp) {
477 int totalSystems = selectedExp->rowCount();
478 experimentHeaderLabel->setText(QString("Total systems for experiment \"%1\": %2")
479 .arg(selectedExp->text()).arg(totalSystems));
480 }
481 else {
482 experimentHeaderLabel->setText("");
483 }
484
485 // Ensure headers remain visible after model clear/reset patterns.
486 experimentModel->setHorizontalHeaderLabels(QStringList() << "exp/system" << "volumes" << "variation" << "run");
487}
488
489void DBSelectView::onItemChanged(QStandardItem* item) {
490 if (m_ignoreItemChange || !item)
491 return;
492
493 // Guard against recursive updates while changing check states programmatically.
494 m_ignoreItemChange = true;
495
496 // Top-level item: experiment selection.
497 if (!item->parent()) {
498 if (item->checkState() == Qt::Checked) {
499 // Enforce only one experiment checked at a time.
500 for (int i = 0; i < experimentModel->rowCount(); ++i) {
501 QStandardItem* expItem = experimentModel->item(i, 0);
502 if (expItem != item)
503 expItem->setCheckState(Qt::Unchecked);
504 }
505 updateExperimentHeader();
506 }
507 else {
508 // If experiment unchecked, also uncheck its systems.
509 for (int i = 0; i < item->rowCount(); ++i) {
510 QStandardItem* sysItem = item->child(i, 0);
511 if (sysItem)
512 sysItem->setCheckState(Qt::Unchecked);
513 }
514 updateExperimentHeader();
515 }
516 }
517 else {
518 // Child item: system row change.
519 if (item->column() == 0) {
520 updateSystemItemAppearance(item);
521 }
522 else if (item->column() == 2 || item->column() == 3) {
523 QStandardItem* sysItem = item->parent()->child(item->row(), 0);
524 updateSystemItemAppearance(sysItem);
525 }
526 }
527
528 m_ignoreItemChange = false;
529
530 // Mark the view as modified and reflect the state in the header/title and reload button.
531 if (!modified) {
532 modified = true;
533 }
534 updateModifiedUI();
535}
536
538 SystemList updatedSystems;
539
540 // Walk the model and build one GSystem per checked system row.
541 for (int i = 0; i < experimentModel->rowCount(); i++) {
542 QStandardItem* expItem = experimentModel->item(i, 0);
543 if (!expItem)
544 continue;
545
546 for (int j = 0; j < expItem->rowCount(); j++) {
547 QStandardItem* sysItem = expItem->child(j, 0);
548 QStandardItem* varItem = expItem->child(j, 2);
549 QStandardItem* runItem = expItem->child(j, 3);
550
551 if (!sysItem || !varItem || !runItem)
552 continue;
553
554 if (sysItem->checkState() == Qt::Checked) {
555 std::string systemName = sysItem->text().toStdString();
556 std::string variation = varItem->data(Qt::EditRole).toString().toStdString();
557 int run = runItem->data(Qt::EditRole).toInt();
558
559 log->info(2, SFUNCTION_NAME, ": adding systemName: ", systemName, " , variation: ", variation, ", for run:", run);
560
561 updatedSystems.emplace_back(
562 std::make_shared<GSystem>(
563 gopt,
564 dbhost,
565 systemName,
567 experiment,
568 run,
569 variation
570 ));
571 }
572 }
573 }
574
575 return updatedSystems;
576}
577
578void DBSelectView::updateModifiedUI() {
579 // Keep header text and layout in sync with model state.
580 updateExperimentHeader();
581
582 if (modified)
583 titleLabel->setText("Experiment Selection* (modified)");
584 else
585 titleLabel->setText("Experiment Selection");
586
587 reloadButton->setEnabled(modified);
588
589 // Column sizing and tree expansion provide a readable default view after changes.
590 experimentTree->resizeColumnToContents(0);
591 experimentTree->setColumnWidth(1, 100);
592 experimentTree->setColumnWidth(2, 150);
593 experimentTree->setColumnWidth(3, 150);
594 experimentTree->header()->setStretchLastSection(false);
595 experimentTree->expandAll();
596}
597
599 log->info(0, SFUNCTION_NAME, ": Reloading geometry...");
600
601 // Extract selection into a SystemList and provide visibility into what is being reloaded.
602 auto reloaded_system = get_gsystems();
603 for (auto& gsys : reloaded_system) {
604 log->info(2, SFUNCTION_NAME, ": reloaded system: ", gsys->getName());
605 }
606
607 // Delegate the actual reload to detector construction.
608 gDetectorConstruction->reload_geometry(reloaded_system);
609
610 // Reload completes the edit cycle: clear modified state.
611 modified = false;
612 updateModifiedUI();
613}
Item delegate that edits a cell using a QComboBox populated from Qt::UserRole.
SystemList get_gsystems()
Build and return the list of selected systems as a SystemList.
DBSelectView(const std::shared_ptr< GOptions > &gopts, GDetectorConstruction *dc, QWidget *parent=nullptr)
Construct the view and populate the experiment/system model from the database.
void reload_geometry()
Slot invoked by the Reload button to reload geometry based on current selections.
std::shared_ptr< GLogger > log
void reload_geometry(SystemList sl)
void info(int level, Args &&... args) const
void error(int exit_code, Args &&... args) const
constexpr const char * DBSELECT_LOGGER
Logger name used by the dbselect module.
#define SFUNCTION_NAME
#define GSYSTEMSQLITETFACTORYLABEL
#define ERR_EXPERIMENTNOTFOUND
#define ERR_GSQLITEERROR
std::vector< SystemPtr > SystemList
SystemList getSystems(const std::shared_ptr< GOptions > &gopts)
std::filesystem::path gemc_root()
std::optional< std::string > searchForFileInLocations(const std::vector< std::string > &locations, std::string_view filename)