diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..fb25c728 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "frontend/plot"] + path = frontend/plot + url = https://github.com/disorderedmaterials/plot diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index c12a0646..388c0f72 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -33,6 +33,8 @@ find_package( COMPONENTS Core Gui Widgets Network Charts Xml REQUIRED) +add_subdirectory(plot/) + # Build main binary qt6_add_executable( jv2 @@ -46,6 +48,12 @@ qt6_add_executable( journal.h lock.cpp lock.h + logValue.cpp + logValue.h + logValueData.cpp + logValueData.h + logValueGroup.cpp + logValueGroup.h optionalRef.h # Backend backend.cpp @@ -69,7 +77,6 @@ qt6_add_executable( nexusInteraction.cpp searching.cpp settings.cpp - visualisation.cpp version.h # Models genericTreeModel.cpp @@ -82,6 +89,12 @@ qt6_add_executable( journalSourceFilterProxy.h journalSourceModel.cpp journalSourceModel.h + logValueFilterProxy.cpp + logValueFilterProxy.h + logValueGroupModel.cpp + logValueGroupModel.h + logValueModel.cpp + logValueModel.h runDataModel.cpp runDataModel.h runDataFilterProxy.cpp @@ -95,6 +108,9 @@ qt6_add_executable( journalSourcesDialog.cpp journalSourcesDialog.h journalSourcesDialog.ui + plotLogDataWidget.cpp + plotLogDataWidget.h + plotLogDataWidget.ui searchDialog.cpp searchDialog.h searchDialog.ui @@ -108,10 +124,10 @@ qt6_add_executable( set_target_properties(jv2 PROPERTIES WIN32_EXECUTABLE ON) target_link_libraries(jv2 PRIVATE Qt6::Core Qt6::Widgets Qt6::Network - Qt6::Charts Qt6::Xml) + Qt6::Charts Qt6::Xml mildred) target_include_directories( jv2 PRIVATE ${PROJECT_SOURCE_DIR} ${Qt6Widgets_INCLUDE_DIRS} ${Qt6Xml_INCLUDE_DIRS} ${Qt6Network_INCLUDE_DIRS} - ${Qt6Charts_INCLUDE_DIRS}) + ${Qt6Charts_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/plot/src) diff --git a/frontend/args.cpp b/frontend/args.cpp index b7621703..797bde16 100644 --- a/frontend/args.cpp +++ b/frontend/args.cpp @@ -2,7 +2,10 @@ // Copyright (c) 2025 Team JournalViewer and contributors #include "args.h" +#include +namespace JV2 +{ CLIArgs::CLIArgs() : helpOption_(addHelpOption()) { setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); @@ -34,3 +37,4 @@ bool CLIArgs::parseArguments(const QList &arguments) return true; } +} // namespace JV2 diff --git a/frontend/args.h b/frontend/args.h index a959c465..832632fd 100644 --- a/frontend/args.h +++ b/frontend/args.h @@ -7,6 +7,8 @@ #include #include +namespace JV2 +{ class CLIArgs : public QCommandLineParser { public: @@ -31,3 +33,4 @@ class CLIArgs : public QCommandLineParser const inline static QString UseWaitress = QStringLiteral("use-waitress"); const inline static QString DebugBackend = QStringLiteral("debug-backend"); }; +} // namespace JV2 diff --git a/frontend/backend.cpp b/frontend/backend.cpp index 1b11fe86..ddd60cb9 100644 --- a/frontend/backend.cpp +++ b/frontend/backend.cpp @@ -8,6 +8,8 @@ #include #include +namespace JV2 +{ Backend::Backend(const QCommandLineParser &args) : process_() { QStringList backendArgs; @@ -171,9 +173,9 @@ void Backend::acquireAllJournalsStop(const HttpRequestWorker::HttpRequestHandler * NeXuS Endpoints */ -// Get NeXuS log values present in specified run files -void Backend::getNexusFields(const JournalSource *source, const std::vector &runNos, - const HttpRequestWorker::HttpRequestHandler &handler) +// Get all NeXuS log values present over specified run files +void Backend::getNeXuSLogValues(const JournalSource *source, const std::vector &runNos, + const HttpRequestWorker::HttpRequestHandler &handler) { auto data = source->sourceObjectData(); @@ -301,3 +303,4 @@ void Backend::generateFinalise(const JournalSource *source, JournalGenerationSty postRequest(createRoute("generate/finalise"), data, handler); } +} // namespace JV2 diff --git a/frontend/backend.h b/frontend/backend.h index a1af2293..fbf2abf6 100644 --- a/frontend/backend.h +++ b/frontend/backend.h @@ -4,13 +4,15 @@ #pragma once #include "httpRequestWorker.h" +#include #include #include #include +namespace JV2 +{ // Forward-declarations class JournalSource; -class QCommandLineParser; // Backend Process class Backend : public QObject @@ -44,10 +46,21 @@ class Backend : public QObject // Create a request HttpRequestWorker *createRequest(const QString &url, const HttpRequestWorker::HttpRequestHandler &handler = {}); + public: + // Error Codes + const inline static QString NoError = QStringLiteral("NoError"); + const inline static QString QNetworkReplyError = QStringLiteral("QNetworkReplyError"); + const inline static QString InvalidRequestError = QStringLiteral("InvalidRequestError"); + const inline static QString NetworkError = QStringLiteral("NetworkError"); + const inline static QString XMLParseError = QStringLiteral("XMLParseError"); + const inline static QString CollectionNotFoundError = QStringLiteral("CollectionNotFoundError"); + const inline static QString JournalNotFoundError = QStringLiteral("JournalNotFoundError"); + const inline static QString FileNotFoundError = QStringLiteral("FileNotFoundError"); + public slots: // Start the backend process void start(); - // Stop the backend processs + // Stop the backend process void stop(); signals: @@ -88,9 +101,9 @@ class Backend : public QObject * NeXuS Endpoints */ public: - // Get NeXuS log values present in specified run files - void getNexusFields(const JournalSource *source, const std::vector &runNos, - const HttpRequestWorker::HttpRequestHandler &handler = {}); + // Get all NeXuS log values present over specified run files + void getNeXuSLogValues(const JournalSource *source, const std::vector &runNos, + const HttpRequestWorker::HttpRequestHandler &handler = {}); // Get NeXuS log value data for specified run files void getNexusLogValueData(const JournalSource *source, const std::vector &runNos, const QString &logValue, const HttpRequestWorker::HttpRequestHandler &handler = {}); @@ -129,3 +142,4 @@ class Backend : public QObject void generateFinalise(const JournalSource *source, JournalGenerationStyle generationStyle, const HttpRequestWorker::HttpRequestHandler &handler = {}); }; +} // namespace JV2 diff --git a/frontend/chartView.cpp b/frontend/chartView.cpp index 37faac8e..ffeedcc6 100644 --- a/frontend/chartView.cpp +++ b/frontend/chartView.cpp @@ -14,6 +14,8 @@ #include #include +namespace JV2 +{ ChartView::ChartView(QChart *chart, QWidget *parent) : QChartView(chart, parent) { setRubberBand(QChartView::HorizontalRubberBand); @@ -317,3 +319,4 @@ void ChartView::mouseMoveEvent(QMouseEvent *event) QChartView::mouseMoveEvent(event); } +} // namespace JV2 diff --git a/frontend/chartView.h b/frontend/chartView.h index c7c69d56..5e9ca072 100644 --- a/frontend/chartView.h +++ b/frontend/chartView.h @@ -7,6 +7,8 @@ #include #include +namespace JV2 +{ class ChartView : public QChartView { Q_OBJECT @@ -43,3 +45,4 @@ class ChartView : public QChartView QGraphicsSimpleTextItem *coordStartLabelX_; QGraphicsSimpleTextItem *coordStartLabelY_; }; +} // namespace JV2 diff --git a/frontend/data.cpp b/frontend/data.cpp index 1ee29980..bf595d67 100644 --- a/frontend/data.cpp +++ b/frontend/data.cpp @@ -2,6 +2,7 @@ // Copyright (c) 2025 Team JournalViewer and contributors #include "mainWindow.h" +#include "plotLogDataWidget.h" #include #include #include @@ -9,6 +10,8 @@ #include #include +namespace JV2 +{ /* * Private Functions */ @@ -200,8 +203,10 @@ void MainWindow::runDataContextMenuRequested(QPoint pos) } else if (selectedAction == plotSELog) { - backend_.getNexusFields(currentJournalSource(), selectedRunNumbers(), - [=](HttpRequestWorker *worker) { handlePlotSELogValue(worker); }); + auto *plot = new PlotLogDataWidget(this, backend_, currentJournalSource(), selectedRunNumbers()); + auto index = ui_.MainTabs->addTab(plot, plot->summaryText()); + connect(plot, SIGNAL(summaryTextChanged(QString)), this, SLOT(setTabTitle(QString))); + ui_.MainTabs->setCurrentIndex(index); } else if (selectedAction == plotDetector) { @@ -214,3 +219,4 @@ void MainWindow::runDataContextMenuRequested(QPoint pos) [=](HttpRequestWorker *worker) { plotMonSpectra(worker); }); } } +} // namespace JV2 diff --git a/frontend/errorHandling.cpp b/frontend/errorHandling.cpp index 2458746a..5666142c 100644 --- a/frontend/errorHandling.cpp +++ b/frontend/errorHandling.cpp @@ -4,6 +4,8 @@ #include "mainWindow.h" #include +namespace JV2 +{ // Perform check for errors on http request, returning the handled error QString MainWindow::handleRequestError(HttpRequestWorker *worker, const QString &taskDescription) { @@ -51,3 +53,4 @@ void MainWindow::setErrorPage(const QString &errorTitle, const QString &errorTex } void MainWindow::on_ErrorOKButton_clicked(bool checked) { updateForCurrentSource(JournalSource::JournalSourceState::OK); } +} // namespace JV2 diff --git a/frontend/export.cpp b/frontend/export.cpp index 41e03b3c..da71a146 100644 --- a/frontend/export.cpp +++ b/frontend/export.cpp @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ void MainWindow::exportRunDataAsText() { // Save selection or all items? @@ -56,3 +58,4 @@ void MainWindow::exportRunDataAsText() */ void MainWindow::on_actionExportAsText_triggered() { exportRunDataAsText(); } +} // namespace JV2 diff --git a/frontend/filtering.cpp b/frontend/filtering.cpp index fbea5814..ae31da9b 100644 --- a/frontend/filtering.cpp +++ b/frontend/filtering.cpp @@ -5,6 +5,8 @@ #include #include +namespace JV2 +{ /* * UI */ @@ -46,3 +48,4 @@ void MainWindow::on_GroupRunsButton_clicked(bool checked) // Clears filter parameters void MainWindow::on_RunFilterClearButton_clicked(bool checked) { ui_.RunFilterEdit->clear(); } +} // namespace JV2 diff --git a/frontend/finding.cpp b/frontend/finding.cpp index 1f651a53..3f510b2f 100644 --- a/frontend/finding.cpp +++ b/frontend/finding.cpp @@ -4,6 +4,8 @@ #include "mainWindow.h" #include +namespace JV2 +{ /* * Private Functions */ @@ -132,3 +134,4 @@ void MainWindow::on_actionFind_triggered() void MainWindow::on_actionFindNext_triggered() { findDown(); } void MainWindow::on_actionFindPrevious_triggered() { findUp(); } void MainWindow::on_actionSelectAllFound_triggered() { selectAllSearches(); } +} // namespace JV2 diff --git a/frontend/generation.cpp b/frontend/generation.cpp index db607d17..5be077ea 100644 --- a/frontend/generation.cpp +++ b/frontend/generation.cpp @@ -4,6 +4,8 @@ #include "mainWindow.h" #include +namespace JV2 +{ /* * UI */ @@ -197,3 +199,4 @@ void MainWindow::handleGenerateScanStop(HttpRequestWorker *worker) sourceBeingGenerated_ = nullptr; } +} // namespace JV2 diff --git a/frontend/genericTreeModel.cpp b/frontend/genericTreeModel.cpp index 54374ffe..0b826d78 100644 --- a/frontend/genericTreeModel.cpp +++ b/frontend/genericTreeModel.cpp @@ -3,11 +3,16 @@ #include "genericTreeModel.h" +namespace JV2 +{ /* * GenericTreeItem */ -GenericTreeItem::GenericTreeItem(const QList &data) : data_(data) {} +GenericTreeItem::GenericTreeItem(const QList &data, const QList &toolTips, const QList &flags) + : data_(data), toolTips_(toolTips), flags_(flags) +{ +} GenericTreeItem::~GenericTreeItem() { qDeleteAll(children_); } @@ -17,9 +22,10 @@ void GenericTreeItem::appendChild(GenericTreeItem *item) item->setParent(this); } -GenericTreeItem *GenericTreeItem::appendChild(const QList &data) +GenericTreeItem *GenericTreeItem::appendChild(const QList &data, const QList &toolTips, + const QList &flags) { - auto *item = new GenericTreeItem(data); + auto *item = new GenericTreeItem(data, toolTips, flags); item->setParent(this); children_.append(item); return item; @@ -47,10 +53,24 @@ int GenericTreeItem::columnCount() const { return data_.count(); } QVariant GenericTreeItem::data(int column) const { if (column < 0 || column >= data_.count()) - return QVariant(); + return {}; return data_.at(column); } +QVariant GenericTreeItem::toolTip(int column) const +{ + if (column < 0 || column >= toolTips_.count()) + return {}; + return toolTips_.at(column); +} + +Qt::ItemFlags GenericTreeItem::flags(int column) const +{ + if (column < 0 || column >= flags_.count()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return flags_.at(column); +} + void GenericTreeItem::setParent(GenericTreeItem *parent) { parent_ = parent; } GenericTreeItem *GenericTreeItem::parent() { return parent_; } @@ -70,7 +90,7 @@ GenericTreeModel::~GenericTreeModel() QModelIndex GenericTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!rootItem_ || !hasIndex(row, column, parent)) - return QModelIndex(); + return {}; GenericTreeItem *parentItem; @@ -82,19 +102,19 @@ QModelIndex GenericTreeModel::index(int row, int column, const QModelIndex &pare GenericTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); - return QModelIndex(); + return {}; } QModelIndex GenericTreeModel::parent(const QModelIndex &index) const { if (!rootItem_ || !index.isValid()) - return QModelIndex(); + return {}; - GenericTreeItem *childItem = static_cast(index.internalPointer()); - GenericTreeItem *parentItem = childItem->parent(); + auto *childItem = static_cast(index.internalPointer()); + auto *parentItem = childItem->parent(); if (parentItem == rootItem_) - return QModelIndex(); + return {}; return createIndex(parentItem->row(), 0, parentItem); } @@ -123,14 +143,16 @@ int GenericTreeModel::columnCount(const QModelIndex &parent) const QVariant GenericTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) - return QVariant(); + return {}; - if (role != Qt::DisplayRole) - return QVariant(); + auto *item = static_cast(index.internalPointer()); - GenericTreeItem *item = static_cast(index.internalPointer()); + if (role == Qt::DisplayRole) + return item->data(index.column()); + else if (role == Qt::ToolTipRole) + return item->toolTip(index.column()); - return item->data(index.column()); + return {}; } Qt::ItemFlags GenericTreeModel::flags(const QModelIndex &index) const @@ -138,7 +160,9 @@ Qt::ItemFlags GenericTreeModel::flags(const QModelIndex &index) const if (!index.isValid()) return Qt::NoItemFlags; - return QAbstractItemModel::flags(index); + auto *item = static_cast(index.internalPointer()); + + return item->flags(index.column()); } QVariant GenericTreeModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -159,3 +183,4 @@ void GenericTreeModel::setRootItem(GenericTreeItem *rootItem) rootItem_ = rootItem; endResetModel(); } +} // namespace JV2 diff --git a/frontend/genericTreeModel.h b/frontend/genericTreeModel.h index 0a10e1f6..d4126ee4 100644 --- a/frontend/genericTreeModel.h +++ b/frontend/genericTreeModel.h @@ -6,24 +6,32 @@ #include #include +namespace JV2 +{ class GenericTreeItem { public: - explicit GenericTreeItem(const QList &data); + explicit GenericTreeItem(const QList &data, const QList &toolTips = {}, + const QList &flags = {}); ~GenericTreeItem(); private: QList children_; QList data_; + QList toolTips_; + QList flags_; GenericTreeItem *parent_{nullptr}; public: void appendChild(GenericTreeItem *child); - GenericTreeItem *appendChild(const QList &data); + GenericTreeItem *appendChild(const QList &data, const QList &toolTips = {}, + const QList &flags = {}); GenericTreeItem *child(int row); int childCount() const; int columnCount() const; QVariant data(int column) const; + QVariant toolTip(int column) const; + Qt::ItemFlags flags(int column) const; int row() const; void setParent(GenericTreeItem *parent); GenericTreeItem *parent(); @@ -52,3 +60,4 @@ class GenericTreeModel : public QAbstractItemModel int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; }; +} // namespace JV2 diff --git a/frontend/graphWidget.cpp b/frontend/graphWidget.cpp index 0bf347c8..db39cd39 100644 --- a/frontend/graphWidget.cpp +++ b/frontend/graphWidget.cpp @@ -11,6 +11,8 @@ #include #include +namespace JV2 +{ GraphWidget::GraphWidget(QWidget *parent, QChart *chart, QString type) : QWidget(parent) { type_ = type; @@ -338,3 +340,4 @@ void GraphWidget::modifyAgainstWorker(HttpRequestWorker *worker, bool checked) ui_.chartView->chart()->axes()[1]->setMin(min); } } +} // namespace JV2 diff --git a/frontend/graphWidget.h b/frontend/graphWidget.h index 641dd4fc..20c6a6cf 100644 --- a/frontend/graphWidget.h +++ b/frontend/graphWidget.h @@ -10,6 +10,8 @@ #include #include +namespace JV2 +{ class GraphWidget : public QWidget { Q_OBJECT @@ -58,3 +60,4 @@ class GraphWidget : public QWidget void runDivide(QString currentDetector, QString run, bool checked); void monDivide(QString currentRun, QString mon, bool checked); }; +} // namespace JV2 diff --git a/frontend/graphWidget.ui b/frontend/graphWidget.ui index 901c6bea..43407412 100644 --- a/frontend/graphWidget.ui +++ b/frontend/graphWidget.ui @@ -186,7 +186,7 @@ - + @@ -194,14 +194,10 @@ - QChartView - QGraphicsView -
QtCharts
-
- - ChartView - QChartView + JV2::ChartView + QWidget
chartView.h
+ 1
diff --git a/frontend/httpRequestWorker.cpp b/frontend/httpRequestWorker.cpp index af8788ed..43836725 100644 --- a/frontend/httpRequestWorker.cpp +++ b/frontend/httpRequestWorker.cpp @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ HttpRequestWorker::HttpRequestWorker(QNetworkAccessManager &manager, const QString &url, HttpRequestHandler handler) : QObject() { // Set up the request @@ -71,3 +73,4 @@ QNetworkReply::NetworkError HttpRequestWorker::errorType() const { return errorT // Return error string (if available) const QString &HttpRequestWorker::errorString() const { return errorString_; } +} // namespace JV2 diff --git a/frontend/httpRequestWorker.h b/frontend/httpRequestWorker.h index a5d557cb..bc877fd9 100644 --- a/frontend/httpRequestWorker.h +++ b/frontend/httpRequestWorker.h @@ -14,6 +14,8 @@ class QNetworkAccessManager; class QNetworkReply; +namespace JV2 +{ // Object for handling an http request class HttpRequestWorker : public QObject { @@ -68,3 +70,4 @@ class HttpRequestWorker : public QObject // Process request once its complete void requestComplete(); }; +} // namespace JV2 diff --git a/frontend/icons/add.svg b/frontend/icons/add.svg new file mode 100644 index 00000000..7697d382 --- /dev/null +++ b/frontend/icons/add.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/frontend/icons/down.svg b/frontend/icons/down.svg new file mode 100644 index 00000000..1dc8bdc6 --- /dev/null +++ b/frontend/icons/down.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/frontend/icons/down_off.svg b/frontend/icons/down_off.svg new file mode 100644 index 00000000..de3077e2 --- /dev/null +++ b/frontend/icons/down_off.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/frontend/icons/remove.svg b/frontend/icons/remove.svg new file mode 100644 index 00000000..864a96cf --- /dev/null +++ b/frontend/icons/remove.svg @@ -0,0 +1,57 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/frontend/icons/up.svg b/frontend/icons/up.svg new file mode 100644 index 00000000..46cefddd --- /dev/null +++ b/frontend/icons/up.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/frontend/icons/up_off.svg b/frontend/icons/up_off.svg new file mode 100644 index 00000000..e1096986 --- /dev/null +++ b/frontend/icons/up_off.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/frontend/instrument.cpp b/frontend/instrument.cpp index 11d46978..b626b56f 100644 --- a/frontend/instrument.cpp +++ b/frontend/instrument.cpp @@ -5,6 +5,8 @@ #include #include +namespace JV2 +{ // Static Singleton // -- Default columns for instrument types std::map Instrument::defaultColumns_; @@ -162,3 +164,4 @@ QString Instrument::pathComponent(PathType pathType, bool upperCased) const } return upperCased ? result.toUpper() : result.toLower(); } +} // namespace JV2 diff --git a/frontend/instrument.h b/frontend/instrument.h index 509273aa..81fd4b69 100644 --- a/frontend/instrument.h +++ b/frontend/instrument.h @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ // Instrument Definition class Instrument { @@ -87,3 +89,4 @@ class Instrument // Return specified path component for this instrument (lowercased by default) QString pathComponent(PathType pathType, bool upperCased = false) const; }; +} // namespace JV2 diff --git a/frontend/instrumentModel.cpp b/frontend/instrumentModel.cpp index 1ea10768..6860a0d0 100644 --- a/frontend/instrumentModel.cpp +++ b/frontend/instrumentModel.cpp @@ -3,6 +3,8 @@ #include "instrumentModel.h" +namespace JV2 +{ // Model to handle json data in table view InstrumentModel::InstrumentModel() : QAbstractListModel() {} @@ -70,3 +72,4 @@ QVariant InstrumentModel::headerData(int section, Qt::Orientation orientation, i return {}; } } +} // namespace JV2 diff --git a/frontend/instrumentModel.h b/frontend/instrumentModel.h index e40fc056..df19f283 100644 --- a/frontend/instrumentModel.h +++ b/frontend/instrumentModel.h @@ -7,6 +7,8 @@ #include "optionalRef.h" #include +namespace JV2 +{ // Model for Instrument definitions class InstrumentModel : public QAbstractListModel { @@ -36,3 +38,4 @@ class InstrumentModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; }; +} // namespace JV2 diff --git a/frontend/instruments.cpp b/frontend/instruments.cpp index 59d3ed49..296f1876 100644 --- a/frontend/instruments.cpp +++ b/frontend/instruments.cpp @@ -5,6 +5,8 @@ #include #include +namespace JV2 +{ /* * Private Functions */ @@ -103,3 +105,4 @@ OptionalReferenceWrapper MainWindow::currentInstrument() const return currentJournalSource_->currentInstrument(); } +} // namespace JV2 diff --git a/frontend/journal.cpp b/frontend/journal.cpp index e1782c6a..cf77928a 100644 --- a/frontend/journal.cpp +++ b/frontend/journal.cpp @@ -3,6 +3,8 @@ #include "journal.h" +namespace JV2 +{ Journal::Journal(QString name) : name_(name) {} /* @@ -20,3 +22,4 @@ void Journal::setFilename(const QString &filename) { filename_ = filename; } // Return filename const QString &Journal::filename() const { return filename_; } +} // namespace JV2 diff --git a/frontend/journal.h b/frontend/journal.h index c8698e2e..ed8a6a98 100644 --- a/frontend/journal.h +++ b/frontend/journal.h @@ -6,6 +6,9 @@ #include #include +namespace JV2 +{ + // Journal Definition class Journal { @@ -33,3 +36,4 @@ class Journal // Return filename const QString &filename() const; }; +} // namespace JV2 diff --git a/frontend/journalModel.cpp b/frontend/journalModel.cpp index b89df818..254fb264 100644 --- a/frontend/journalModel.cpp +++ b/frontend/journalModel.cpp @@ -3,6 +3,8 @@ #include "journalModel.h" +namespace JV2 +{ // Model to handle json data in table view JournalModel::JournalModel() : QAbstractListModel() {} @@ -70,3 +72,4 @@ QVariant JournalModel::headerData(int section, Qt::Orientation orientation, int return {}; } } +} // namespace JV2 diff --git a/frontend/journalModel.h b/frontend/journalModel.h index 87aec533..a03cf1c5 100644 --- a/frontend/journalModel.h +++ b/frontend/journalModel.h @@ -7,6 +7,8 @@ #include "optionalRef.h" #include +namespace JV2 +{ // Model for Journal definitions class JournalModel : public QAbstractListModel { @@ -36,3 +38,4 @@ class JournalModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; }; +} // namespace JV2 diff --git a/frontend/journalSource.cpp b/frontend/journalSource.cpp index c8f82214..9028e533 100644 --- a/frontend/journalSource.cpp +++ b/frontend/journalSource.cpp @@ -5,6 +5,8 @@ #include "instrument.h" #include +namespace JV2 +{ // Return text string for specified IndexingType type QString JournalSource::indexingType(JournalSource::IndexingType type) { @@ -408,3 +410,4 @@ void JournalSource::fromSettings(const QSettings &settings) .toString()); } } +} // namespace JV2 diff --git a/frontend/journalSource.h b/frontend/journalSource.h index b4e54af7..3a6d85a5 100644 --- a/frontend/journalSource.h +++ b/frontend/journalSource.h @@ -10,6 +10,8 @@ #include #include +namespace JV2 +{ // Forward Declarations class HttpRequestWorker; @@ -233,3 +235,4 @@ class JournalSource // Retrieve data from the supplied QSettings void fromSettings(const QSettings &settings); }; +} // namespace JV2 diff --git a/frontend/journalSourceFilterProxy.cpp b/frontend/journalSourceFilterProxy.cpp index bcec3d4f..bd8ffbae 100644 --- a/frontend/journalSourceFilterProxy.cpp +++ b/frontend/journalSourceFilterProxy.cpp @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ JournalSourceFilterProxy::JournalSourceFilterProxy(JournalSourceModel &journalSourceModel) : journalSourceModel_(journalSourceModel) { @@ -16,3 +18,4 @@ bool JournalSourceFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex { return (!showAvailableOnly_ || journalSourceModel_.getData(sourceRow)->isAvailable()); } +} // namespace JV2 diff --git a/frontend/journalSourceFilterProxy.h b/frontend/journalSourceFilterProxy.h index de6eee9d..4c9a6194 100644 --- a/frontend/journalSourceFilterProxy.h +++ b/frontend/journalSourceFilterProxy.h @@ -3,12 +3,14 @@ #pragma once +#include #include #include +namespace JV2 +{ // Forward Declarations class JournalSourceModel; -class QModelIndex; class JournalSourceFilterProxy : public QSortFilterProxyModel { @@ -26,3 +28,4 @@ class JournalSourceFilterProxy : public QSortFilterProxyModel protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; +} // namespace JV2 diff --git a/frontend/journalSourceModel.cpp b/frontend/journalSourceModel.cpp index 5ccd1df6..b0f2234f 100644 --- a/frontend/journalSourceModel.cpp +++ b/frontend/journalSourceModel.cpp @@ -4,6 +4,8 @@ #include "journalSourceModel.h" #include "uniqueName.h" +namespace JV2 +{ // Model to handle json data in table view JournalSourceModel::JournalSourceModel() : QAbstractListModel() {} @@ -151,3 +153,4 @@ QVariant JournalSourceModel::headerData(int section, Qt::Orientation orientation return {}; } } +} // namespace JV2 diff --git a/frontend/journalSourceModel.h b/frontend/journalSourceModel.h index 06668ced..b6caf8e5 100644 --- a/frontend/journalSourceModel.h +++ b/frontend/journalSourceModel.h @@ -7,6 +7,8 @@ #include "optionalRef.h" #include +namespace JV2 +{ // Model for JournalSource definitions class JournalSourceModel : public QAbstractListModel { @@ -42,3 +44,4 @@ class JournalSourceModel : public QAbstractListModel bool setData(const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; }; +} // namespace JV2 diff --git a/frontend/journalSources.cpp b/frontend/journalSources.cpp index b177a7c5..f76c8a40 100644 --- a/frontend/journalSources.cpp +++ b/frontend/journalSources.cpp @@ -8,6 +8,8 @@ #include #include +namespace JV2 +{ /* * Private Functions */ @@ -326,3 +328,4 @@ void MainWindow::handleJumpToJournal(HttpRequestWorker *worker) backend_.getJournal(currentJournalSource(), [=](HttpRequestWorker *worker) { handleCompleteJournalRunData(worker, runNumber); }); } +} // namespace JV2 diff --git a/frontend/journalSourcesDialog.cpp b/frontend/journalSourcesDialog.cpp index b9850139..6da859ea 100644 --- a/frontend/journalSourcesDialog.cpp +++ b/frontend/journalSourcesDialog.cpp @@ -5,6 +5,8 @@ #include #include +namespace JV2 +{ JournalSourcesDialog::JournalSourcesDialog(QWidget *parent) : QDialog(parent) { ui_.setupUi(this); @@ -208,3 +210,4 @@ void JournalSourcesDialog::go(std::vector> &sourc exec(); } +} // namespace JV2 diff --git a/frontend/journalSourcesDialog.h b/frontend/journalSourcesDialog.h index 2453ad33..aad2e122 100644 --- a/frontend/journalSourcesDialog.h +++ b/frontend/journalSourcesDialog.h @@ -7,6 +7,8 @@ #include "lock.h" #include "ui_journalSourcesDialog.h" +namespace JV2 +{ // Forward Declarations class MainWindow; class JournalSource; @@ -59,3 +61,4 @@ class JournalSourcesDialog : public QDialog // Go! void go(std::vector> &sources); }; +} // namespace JV2 diff --git a/frontend/lock.cpp b/frontend/lock.cpp index 84175e1b..4e13cd33 100644 --- a/frontend/lock.cpp +++ b/frontend/lock.cpp @@ -4,6 +4,8 @@ #include "lock.h" #include +namespace JV2 +{ /* * Lock */ @@ -50,3 +52,4 @@ void Locker::unlock() unlocked_ = true; } +} // namespace JV2 diff --git a/frontend/lock.h b/frontend/lock.h index bec3272c..db1b19a7 100644 --- a/frontend/lock.h +++ b/frontend/lock.h @@ -3,6 +3,8 @@ #pragma once +namespace JV2 +{ class Lock { public: @@ -44,3 +46,4 @@ class Locker // Manually release the lock void unlock(); }; +} // namespace JV2 diff --git a/frontend/logValue.cpp b/frontend/logValue.cpp new file mode 100644 index 00000000..dee723a5 --- /dev/null +++ b/frontend/logValue.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#include "logValue.h" + +namespace JV2 +{ +LogValue::LogValue(const QString &name, const QString &location) : name_(name), neXuSLocation_(location) {} + +/* + * Basic Data + */ + +// Return the name of the property +const QString &LogValue::name() const { return name_; } + +// Return the NeXuS location path of the property +const QString &LogValue::neXuSLocation() const { return neXuSLocation_; } + +// Return whether the property is selected +bool LogValue::isSelected() const { return selected_; } + +// Set whether the property is selected +void LogValue::setSelected(bool selected) { selected_ = selected; } + +/* + * Run Data + */ + +// Add data for specific run +void LogValue::addData(QString id, LogValueData data) { data_[id] = std::move(data); } + +// Return all data +const std::map &LogValue::data() const { return data_; } + +} // namespace JV2 diff --git a/frontend/logValue.h b/frontend/logValue.h new file mode 100644 index 00000000..b5b2bee5 --- /dev/null +++ b/frontend/logValue.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#pragma once + +#include "logValueData.h" +#include + +namespace JV2 +{ +// Log Property Definition +class LogValue +{ + public: + LogValue(const QString &name = {}, const QString &location = {}); + ~LogValue() = default; + + /* + * Basic Data + */ + private: + // Display name of the property + QString name_; + // NeXuS location of the property (if relevant) + QString neXuSLocation_; + // Whether this property is selected + bool selected_{false}; + + public: + // Return the name of the property + const QString &name() const; + // Return the NeXuS location path of the property + const QString &neXuSLocation() const; + // Return whether the property is selected + bool isSelected() const; + // Set whether the property is selected + void setSelected(bool selected); + + /* + * Run Data + */ + private: + // Log value data per-run + std::map data_; + + public: + // Add data for specific run + void addData(QString id, LogValueData data); + // Return all data + const std::map &data() const; +}; +} // namespace JV2 diff --git a/frontend/logValueData.cpp b/frontend/logValueData.cpp new file mode 100644 index 00000000..b0defeb8 --- /dev/null +++ b/frontend/logValueData.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#include "logValueData.h" + +namespace JV2 +{ +LogValueData::LogValueData() {} +LogValueData::LogValueData(const QDateTime &start, const QDateTime &end, const std::vector ×, + const std::vector &values) + : startTime_(start), endTime_(end), times_(times), values_(values) +{ +} + +// Return time points in seconds since startTime_ +const std::vector &LogValueData::times() const { return times_; } + +// Return values +const std::vector &LogValueData::values() const { return values_; } + +} // namespace JV2 diff --git a/frontend/logValueData.h b/frontend/logValueData.h new file mode 100644 index 00000000..8be1550b --- /dev/null +++ b/frontend/logValueData.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#pragma once + +#include "logValueData.h" +#include + +namespace JV2 +{ +// Log value data +class LogValueData +{ + public: + LogValueData(); + LogValueData(const QDateTime &start, const QDateTime &end, const std::vector ×, + const std::vector &values); + + private: + // Start and end times + QDateTime startTime_, endTime_; + // Time points in seconds relative to startTime_ + std::vector times_; + // Values + std::vector values_; + + public: + // Return time points in seconds relative to startTime_ + const std::vector ×() const; + // Return values + const std::vector &values() const; +}; +} // namespace JV2 diff --git a/frontend/logValueFilterProxy.cpp b/frontend/logValueFilterProxy.cpp new file mode 100644 index 00000000..39645d31 --- /dev/null +++ b/frontend/logValueFilterProxy.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#include "logValueFilterProxy.h" +#include "logValueModel.h" +#include +#include + +namespace JV2 +{ +LogValueFilterProxy::LogValueFilterProxy(LogValueModel &logValueModel) : logValueModel_(logValueModel) +{ + setSourceModel(&logValueModel_); +} + +bool LogValueFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const auto &logValue = logValueModel_.getData(sourceRow)->get(); + + // Deal with selected state first + if (selectedValueBehaviour_ == SelectedStateBehaviour::HideSelected && logValue.isSelected()) + return false; + else if (selectedValueBehaviour_ == SelectedStateBehaviour::ShowOnlySelected && !logValue.isSelected()) + return false; + + return (filterString_.isEmpty() || + logValueModel_.getData(sourceRow)->get().name().contains(filterString_, Qt::CaseSensitivity::CaseInsensitive)); +} + +// Set filter string, or empty string to disable +void LogValueFilterProxy::setFilterString(const QString &search) +{ + filterString_ = search; + invalidate(); +} + +// Set selected value behaviour +void LogValueFilterProxy::setSelectedStateBehaviour(SelectedStateBehaviour behaviour) +{ + selectedValueBehaviour_ = behaviour; + invalidate(); +} + +} // namespace JV2 diff --git a/frontend/logValueFilterProxy.h b/frontend/logValueFilterProxy.h new file mode 100644 index 00000000..d794f154 --- /dev/null +++ b/frontend/logValueFilterProxy.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#pragma once + +#include +#include +#include + +namespace JV2 +{ +// Forward Declarations +class LogValueModel; + +class LogValueFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + LogValueFilterProxy(LogValueModel &journalSourceModel); + // Selected Sate Behaviour + enum class SelectedStateBehaviour + { + Ignore, + HideSelected, + ShowOnlySelected + }; + + private: + // Target model + LogValueModel &logValueModel_; + // Search string + QString filterString_; + // Behaviour for selected log values in the model + SelectedStateBehaviour selectedValueBehaviour_{SelectedStateBehaviour::Ignore}; + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + public: + // Set filter string, or empty string to disable + void setFilterString(const QString &search); + // Set selected value behaviour + void setSelectedStateBehaviour(SelectedStateBehaviour behaviour); +}; +} // namespace JV2 diff --git a/frontend/logValueGroup.cpp b/frontend/logValueGroup.cpp new file mode 100644 index 00000000..ce65def8 --- /dev/null +++ b/frontend/logValueGroup.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#include "logValueGroup.h" + +namespace JV2 +{ +LogValueGroup::LogValueGroup(const QString &name, bool selected, Mildred::DisplayGroup *displayGroup) + : name_(name), selected_(selected), displayGroup_(displayGroup) +{ +} + +/* + * Basic Data + */ + +// Return the name of the property +const QString &LogValueGroup::name() const { return name_; } + +// Return the associated DisplayGroup (if set) +Mildred::DisplayGroup *LogValueGroup::displayGroup() const { return displayGroup_; } + +// Return whether the property is selected +bool LogValueGroup::isSelected() const { return selected_; } + +// Set whether the property is selected +void LogValueGroup::setSelected(bool selected) { selected_ = selected; } + +} // namespace JV2 diff --git a/frontend/logValueGroup.h b/frontend/logValueGroup.h new file mode 100644 index 00000000..a4e1c9e4 --- /dev/null +++ b/frontend/logValueGroup.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#pragma once + +#include "logValueData.h" +#include "plot/src/displaygroup.h" +#include + +namespace JV2 +{ +// Log Value Group +class LogValueGroup +{ + public: + LogValueGroup(const QString &name, bool selected = false, Mildred::DisplayGroup *displayGroup = nullptr); + ~LogValueGroup() = default; + + /* + * Basic Data + */ + private: + // Display name of the group + QString name_; + // Associated DisplayGroup in the plot + Mildred::DisplayGroup *displayGroup_{nullptr}; + // Whether this group is selected + bool selected_{false}; + + public: + // Return the name of the property + const QString &name() const; + // Return the associated DisplayGroup (if set) + Mildred::DisplayGroup *displayGroup() const; + // Return whether the property is selected + bool isSelected() const; + // Set whether the property is selected + void setSelected(bool selected); +}; +} // namespace JV2 diff --git a/frontend/logValueGroupModel.cpp b/frontend/logValueGroupModel.cpp new file mode 100644 index 00000000..72fdcbd5 --- /dev/null +++ b/frontend/logValueGroupModel.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team LogValueGroupViewer and contributors + +#include "logValueGroupModel.h" +#include +#include + +namespace JV2 +{ +// Model to handle json data in table view +LogValueGroupModel::LogValueGroupModel() : QAbstractListModel() {} + +/* + * Public Functions + */ + +// Set the source data for the model +void LogValueGroupModel::setData(OptionalReferenceWrapper> properties) +{ + beginResetModel(); + data_ = properties; + endResetModel(); +} + +// Get LogValueGroup row specified +OptionalReferenceWrapper LogValueGroupModel::getData(int row) const +{ + if (!data_ || row == -1 || row >= rowCount()) + return {}; + + return data_->get()[row]; +} + +// Get LogValueGroup at index specified +OptionalReferenceWrapper LogValueGroupModel::getData(const QModelIndex &index) const +{ + return getData(index.row()); +} + +/* + * QAbstractListModel Overrides + */ + +int LogValueGroupModel::rowCount(const QModelIndex &parent) const { return data_ ? data_->get().size() : 0; } + +int LogValueGroupModel::columnCount(const QModelIndex &parent) const { return 1; } + +Qt::ItemFlags LogValueGroupModel::flags(const QModelIndex &index) const +{ + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; +} + +QVariant LogValueGroupModel::data(const QModelIndex &index, int role) const +{ + if (!data_) + return {}; + + // Column zero is the only relevant one + if (index.column() != 0) + return {}; + + auto optData = getData(index); + if (!optData) + return {}; + auto &data = optData->get(); + + switch (role) + { + case (Qt::DisplayRole): + case (Qt::EditRole): + return data.name(); + case (Qt::DecorationRole): + if (data.displayGroup()) + return data.displayGroup()->colourPolicyIcon({16, 16}); + else + return {}; + case (Qt::CheckStateRole): + return data.isSelected() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked; + default: + return {}; + } +} + +bool LogValueGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!data_) + return false; + + // Column zero is the only relevant one + if (index.column() != 0) + return false; + + auto optData = getData(index); + if (!optData) + return {}; + auto &data = optData->get(); + + if (role != Qt::CheckStateRole) + return false; + + data.setSelected(value.value() == Qt::Checked); + + emit(dataChanged(index, index)); + + return true; +} + +QVariant LogValueGroupModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return {}; + + return "Log Value"; +} +} // namespace JV2 diff --git a/frontend/logValueGroupModel.h b/frontend/logValueGroupModel.h new file mode 100644 index 00000000..1ba84f02 --- /dev/null +++ b/frontend/logValueGroupModel.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team LogValueGroupViewer and contributors + +#pragma once + +#include "logValueGroup.h" +#include "optionalRef.h" +#include + +namespace JV2 +{ +// Model for LogValueGroup definitions +class LogValueGroupModel : public QAbstractListModel +{ + public: + LogValueGroupModel(); + + private: + // LogValueGroup data for the model + OptionalReferenceWrapper> data_; + // Whether to show availability as checkboxes + bool showAvailability_{false}; + + public: + // Set the source data for the model + void setData(OptionalReferenceWrapper> properties); + // Get LogValueGroup at row specified + OptionalReferenceWrapper getData(int row) const; + // Get LogValueGroup at index specified + OptionalReferenceWrapper getData(const QModelIndex &index) const; + + /* + * QAbstractTableModel Overrides + */ + public: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; +}; +} // namespace JV2 diff --git a/frontend/logValueModel.cpp b/frontend/logValueModel.cpp new file mode 100644 index 00000000..34b7273b --- /dev/null +++ b/frontend/logValueModel.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team LogValueViewer and contributors + +#include "logValueModel.h" + +namespace JV2 +{ +// Model to handle json data in table view +LogValueModel::LogValueModel() : QAbstractListModel() {} + +/* + * Public Functions + */ + +// Set the source data for the model +void LogValueModel::setData(OptionalReferenceWrapper> properties) +{ + beginResetModel(); + data_ = properties; + endResetModel(); +} + +// Get LogValue row specified +OptionalReferenceWrapper LogValueModel::getData(int row) const +{ + if (!data_ || row == -1 || row >= rowCount()) + return {}; + + return data_->get()[row]; +} + +// Get LogValue at index specified +OptionalReferenceWrapper LogValueModel::getData(const QModelIndex &index) const { return getData(index.row()); } + +// Set selected status of all supplied indices +void LogValueModel::setSelected(const QModelIndexList &indices, bool selectedState) +{ + for (const auto &index : indices) + { + auto optData = getData(index); + if (!optData) + continue; + auto &data = optData->get(); + + data.setSelected(selectedState); + } + + emit(dataChanged(indices.front(), indices.back())); +} + +/* + * QAbstractListModel Overrides + */ + +int LogValueModel::rowCount(const QModelIndex &parent) const { return data_ ? data_->get().size() : 0; } + +int LogValueModel::columnCount(const QModelIndex &parent) const { return 1; } + +Qt::ItemFlags LogValueModel::flags(const QModelIndex &index) const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } + +QVariant LogValueModel::data(const QModelIndex &index, int role) const +{ + if (!data_) + return {}; + + // Column zero is the only relevant one + if (index.column() != 0) + return {}; + + auto optData = getData(index); + if (!optData) + return {}; + auto &data = optData->get(); + + switch (role) + { + case (Qt::DisplayRole): + case (Qt::EditRole): + return data.name(); + case (Qt::ToolTipRole): + return data.neXuSLocation(); + default: + return {}; + } +} + +bool LogValueModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!data_) + return false; + + // Column zero is the only relevant one + if (index.column() != 0) + return false; + + auto optData = getData(index); + if (!optData) + return {}; + auto &data = optData->get(); + + if (role != Qt::CheckStateRole) + return false; + + data.setSelected(value.value() == Qt::Checked); + + emit(dataChanged(index, index)); + + return true; +} + +QVariant LogValueModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return {}; + + return "Log Value"; +} +} // namespace JV2 diff --git a/frontend/logValueModel.h b/frontend/logValueModel.h new file mode 100644 index 00000000..23757a97 --- /dev/null +++ b/frontend/logValueModel.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2024 Team LogValueViewer and contributors + +#pragma once + +#include "logValue.h" +#include "optionalRef.h" +#include + +namespace JV2 +{ +// Model for LogValue definitions +class LogValueModel : public QAbstractListModel +{ + public: + LogValueModel(); + + private: + // LogValue data for the model + OptionalReferenceWrapper> data_; + // Whether to show availability as checkboxes + bool showAvailability_{false}; + + public: + // Set the source data for the model + void setData(OptionalReferenceWrapper> properties); + // Get LogValue at row specified + OptionalReferenceWrapper getData(int row) const; + // Get LogValue at index specified + OptionalReferenceWrapper getData(const QModelIndex &index) const; + // Set selected status of all supplied indices + void setSelected(const QModelIndexList &indices, bool selectedState); + + /* + * QAbstractTableModel Overrides + */ + public: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; +}; +} // namespace JV2 diff --git a/frontend/main.cpp b/frontend/main.cpp index 9e4de128..93b3dac7 100644 --- a/frontend/main.cpp +++ b/frontend/main.cpp @@ -12,11 +12,11 @@ int main(int argc, char *argv[]) QApplication::setWindowIcon(QIcon(":/icon")); // Set up and parse command-line arguments - CLIArgs parser; + JV2::CLIArgs parser; if (!parser.parseArguments(QApplication::arguments())) return 1; - MainWindow window(parser); + JV2::MainWindow window(parser); window.show(); return QApplication::exec(); } diff --git a/frontend/mainWindow.cpp b/frontend/mainWindow.cpp index 75b93b42..1a4c9cba 100644 --- a/frontend/mainWindow.cpp +++ b/frontend/mainWindow.cpp @@ -8,6 +8,8 @@ #include #include +namespace JV2 +{ MainWindow::MainWindow(QCommandLineParser &cliParser) : QMainWindow(), backend_(cliParser), journalSourceFilterProxy_(journalSourceModel_), runDataFilterProxy_(runDataModel_) { @@ -132,6 +134,15 @@ void MainWindow::updateForCurrentSource(std::optionalsetEnabled(currentJournalSource_->type() == JournalSource::IndexingType::Generated); } +// Set tab title +void MainWindow::setTabTitle(const QString &title) +{ + // Get sender widget and find tab index + auto index = ui_.MainTabs->indexOf(dynamic_cast(sender())); + if (index != -1) + ui_.MainTabs->setTabText(index, title); +} + void MainWindow::removeTab(int index) { delete ui_.MainTabs->widget(index); } /* @@ -191,3 +202,4 @@ void MainWindow::waitForBackend() }); pingTimer->start(); } +} // namespace JV2 diff --git a/frontend/mainWindow.h b/frontend/mainWindow.h index c335913b..0a5e7d58 100644 --- a/frontend/mainWindow.h +++ b/frontend/mainWindow.h @@ -22,6 +22,8 @@ #include #include +namespace JV2 +{ class MainWindow : public QMainWindow { Q_OBJECT @@ -47,6 +49,9 @@ class MainWindow : public QMainWindow void updateForCurrentSource(std::optional newState = {}); private slots: + // Set tab title + void setTabTitle(const QString &title); + // Remove tab void removeTab(int index); // Notification point for backend startup void backendStarted(const QString &result); @@ -202,14 +207,16 @@ class MainWindow : public QMainWindow const inline static QString FileNotFoundError = QStringLiteral("FileNotFoundError"); private: - // Perform check for errors on http request, returning the handled error - QString handleRequestError(HttpRequestWorker *worker, const QString &taskDescription); // Update the error page void setErrorPage(const QString &errorTitle, const QString &errorText); private slots: void on_ErrorOKButton_clicked(bool checked); + public: + // Perform check for errors on http request, returning the handled error + QString handleRequestError(HttpRequestWorker *worker, const QString &taskDescription); + /* * Settings */ @@ -278,15 +285,6 @@ class MainWindow : public QMainWindow // Handle search result void handleSearchResult(HttpRequestWorker *worker); - /* - * Visualisation - */ - private: - // Handle extracted SE log values for plotting - void handlePlotSELogValue(HttpRequestWorker *worker); - // Handle plotting of SE log data - void handleCreateSELogPlot(HttpRequestWorker *worker); - /* * Nexus Interaction Stuff To Be Organised */ @@ -305,3 +303,4 @@ class MainWindow : public QMainWindow void runDivide(QString currentDetector, QString run, bool checked); void monDivide(QString currentRun, QString mon, bool checked); }; +} // namespace JV2 diff --git a/frontend/nexusInteraction.cpp b/frontend/nexusInteraction.cpp index 1139cb14..93b5c7bd 100644 --- a/frontend/nexusInteraction.cpp +++ b/frontend/nexusInteraction.cpp @@ -17,6 +17,8 @@ #include #include +namespace JV2 +{ void MainWindow::toggleAxis(int state) { auto *toggleBox = qobject_cast(sender()); @@ -279,3 +281,4 @@ void MainWindow::monDivide(QString currentRun, QString mon, bool checked) backend_.getNexusSpectrum(currentJournalSource(), "monitor", mon.toInt(), {currentRun.toInt()}, [=](HttpRequestWorker *worker) { window->modifyAgainstWorker(worker, checked); }); } +} // namespace JV2 diff --git a/frontend/optionalRef.h b/frontend/optionalRef.h index 173001da..c06fb18e 100644 --- a/frontend/optionalRef.h +++ b/frontend/optionalRef.h @@ -6,4 +6,7 @@ #include #include +namespace JV2 +{ template using OptionalReferenceWrapper = std::optional>; +} diff --git a/frontend/plot b/frontend/plot new file mode 160000 index 00000000..dbbe1a93 --- /dev/null +++ b/frontend/plot @@ -0,0 +1 @@ +Subproject commit dbbe1a9358844b8a15e45c1d92aaa6f2d5f707ff diff --git a/frontend/plotLogDataWidget.cpp b/frontend/plotLogDataWidget.cpp new file mode 100644 index 00000000..9f3eb80b --- /dev/null +++ b/frontend/plotLogDataWidget.cpp @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#include "plotLogDataWidget.h" +#include +#include + +namespace JV2 +{ +PlotLogDataWidget::PlotLogDataWidget(MainWindow *parent, Backend &backend, const JournalSource *journalSource, + const std::vector &runNumbers) + : QWidget(parent), mainWindow_(parent), backend_(backend), journalSource_(journalSource), runNumbers_(runNumbers), + availableLogValueFilterProxy_(logValueModel_), shownLogValueFilterProxy_(logValueModel_) +{ + ui_.setupUi(this); + + connect(&logValueModel_, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QList &)), this, + SLOT(logValueChanged(const QModelIndex &, const QModelIndex &, const QList &))); + + // Create LogValueGroups for each run we've been given and set up the relevant list model + auto count = 0; + for (auto runNumber : runNumbers_) + { + // Create a DisplayGroup in the plot so we colourise each associated log value dataset the same + auto *group = ui_.Plot->getDisplayGroup(QString::number(runNumber)); + group->setColourPolicy(Mildred::DisplayGroup::ColourPolicy::Stock); + group->setStockColour(Mildred::DisplayGroup::stockColourForIndex(count++)); + group->setTranslationPolicyX(Mildred::DisplayGroup::TranslationPolicy::Constant); + + logValueGroups_.emplace_back(QString::number(runNumber), true, group); + } + ui_.RunNumberList->setModel(&logValueGroupModel_); + logValueGroupModel_.setData(logValueGroups_); + + connect(&logValueGroupModel_, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QList &)), this, + SLOT(logValueGroupChanged(const QModelIndex &, const QModelIndex &, const QList &))); + + // Set up the shown log value list and model + shownLogValueFilterProxy_.setSelectedStateBehaviour(LogValueFilterProxy::SelectedStateBehaviour::ShowOnlySelected); + shownLogValueFilterProxy_.setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + shownLogValueFilterProxy_.sort(0); + ui_.ShownLogValueList->setModel(&shownLogValueFilterProxy_); + connect(ui_.ShownLogValueList->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + this, SLOT(shownLogValuesSelectionChanged(const QItemSelection &, const QItemSelection &))); + + // Acquire the available log data + backend_.getNeXuSLogValues(journalSource_, runNumbers_, + [=](HttpRequestWorker *worker) { handleRetrieveSELogValues(worker); }); + + // Set up the available log value list and model + availableLogValueFilterProxy_.setSelectedStateBehaviour(LogValueFilterProxy::SelectedStateBehaviour::HideSelected); + availableLogValueFilterProxy_.setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + availableLogValueFilterProxy_.sort(0); + ui_.AvailableLogValueList->setModel(&availableLogValueFilterProxy_); + connect(ui_.AvailableLogValueList->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), this, + SLOT(availableLogValuesSelectionChanged(const QItemSelection &, const QItemSelection &))); + + // Acquire the available log data + backend_.getNeXuSLogValues(journalSource_, runNumbers_, + [=](HttpRequestWorker *worker) { handleRetrieveSELogValues(worker); }); +} + +/* + * Private Functions + */ + +void PlotLogDataWidget::handleRetrieveSELogValues(HttpRequestWorker *worker) +{ + // Check for errors + if (mainWindow_->handleRequestError(worker, "retrieving log values from run") != Backend::NoError) + return; + + // Iterate over log values extracted from the target run data and create a unique set of those available + std::set uniqueValues; + foreach (const auto &log, worker->jsonResponse().array()) + { + auto logArray = log.toArray(); + if (logArray.size() < 2) + continue; + + // Remove the name item and proceed to iterate over log values + logArray.removeFirst(); + + auto logArrayVar = logArray.toVariantList(); + foreach (const auto &block, logArrayVar) + uniqueValues.insert(block.toString()); + } + + // Copy the set to our vector + logValues_.clear(); + logValues_.resize(uniqueValues.size()); + std::transform(uniqueValues.begin(), uniqueValues.end(), logValues_.begin(), + [](const auto &blockPath) { return LogValue(blockPath.split("/").last(), blockPath); }); + + logValueModel_.setData(logValues_); +} + +// Handle retrieved log value data +void PlotLogDataWidget::handleRetrieveSELogValueData(HttpRequestWorker *worker) +{ + // Check network reply + if (mainWindow_->handleRequestError(worker, "trying to retrieve log value data") != Backend::NoError) + { + ui_.AvailableLogValueList->setEnabled(true); + return; + } + + /* The expected result from the backend is as follows: + * + * result = { + * logValue: "name_of_log_value", + * runNumbers: { run1, run2, run3 ... runN } + * data: { + * run1: { + * timeRange: [ datetime, datetime ], + * data: [ (x,y), (x2,y2), ..., (xn,yn) ] + * }, + * ... + * runN: { + * ... + * } + * } + */ + + const auto responseData = worker->jsonResponse().object(); + auto logValueName = responseData["logValue"].toString().section('/', -1); + qDebug() << logValueName; + + // Find the associated LogValue + auto valueIt = std::find_if(logValues_.begin(), logValues_.end(), + [logValueName](auto &value) { return value.name() == logValueName; }); + if (valueIt == logValues_.end()) + { + ui_.AvailableLogValueList->setEnabled(true); + return; + } + auto &logValue = *valueIt; + + const auto data = responseData["data"].toObject(); + + foreach (const auto &run, data) + { + // Get the data name (run number) + const auto dataName = run[QString("runNumber")].toString(); + auto groupIt = std::find_if(logValueGroups_.begin(), logValueGroups_.end(), [dataName](const auto &group) { return group.name() == dataName; }); + if (groupIt == logValueGroups_.end()) + { + qDebug() << QString("Error: LogValueGroup missing for run '%1'.").arg(dataName); + continue; + } + auto &group = *groupIt; + + // Extract the time range data + const auto timeRange = run[QString("timeRange")].toArray(); + + // Store start and end times in the LogValueGroup object for the run + + auto startTime = QDateTime::fromString(timeRange.first()[0].toString(), "yyyy-MM-dd'T'HH:mm:ss"); + auto endTime = QDateTime::fromString(timeRange.first()[1].toString(), "yyyy-MM-dd'T'HH:mm:ss"); + // group.setTimeRange(startTime, endTime); + + // group->setTranslationX(); + + // Get time / value vectors + // TODO Need to check / detect enumerated data here + const auto fieldDataArray = run[QString("data")].toArray(); + std::vector epochTimes; + epochTimes.reserve(1024); + std::vector values; + values.reserve(1024); + foreach (const auto &dataPair, fieldDataArray) + { + auto dataPairArray = dataPair.toArray(); + epochTimes.push_back(dataPairArray[0].toDouble()); + values.push_back(dataPairArray[1].toDouble()); + } + + // Push the new data + logValue.addData(dataName, {startTime, endTime, epochTimes, values}); + } + + // Add the data to the plot + showData(logValue); + + ui_.AvailableLogValueList->setEnabled(true); +} + +// Show data from the supplied LogValue on the plot +void PlotLogDataWidget::showData(const LogValue &logValue) +{ + // Record whether the plot is currently empty + auto plotEmpty = ui_.Plot->nDataEntities() == 0; + + // Add each contained per-run dataset to the plot + for (auto &&[dataName, data] : logValue.data()) + { + + // Create a renderable and add it to the group for the run number (dataName) + auto *renderable = ui_.Plot->addData1D((dataName + "/" + logValue.name())); + renderable->setData(data.times(), data.values()); + + ui_.Plot->getDisplayGroup(dataName)->addTarget(renderable); + } + + // If the plot was empty when we started, auto-scale it now + if (plotEmpty) + ui_.Plot->showAllData(); + + emit(summaryTextChanged(summaryText(logValue.name()))); +} + +// Hide data from the supplied LogValue from the plot +void PlotLogDataWidget::hideData(const LogValue &logValue) +{ + for (auto &&[dataName, data] : logValue.data()) + { + ui_.Plot->removeData1D((dataName + "/" + logValue.name())); + } +} + +/* + * Private Slots + */ + +// Log value model data changed +void PlotLogDataWidget::logValueChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) +{ + auto optLogValue = logValueModel_.getData(topLeft); + auto &logValue = optLogValue->get(); + + // If the logValue has been selected, then either redisplay or retrieve the data + if (logValue.isSelected()) + { + // We might already have the data, so check before we go off retrieving it again... + if (!logValue.data().empty()) + { + showData(logValue); + return; + } + + // Disable the property list for now + ui_.AvailableLogValueList->setDisabled(true); + + // Request the log value data + backend_.getNexusLogValueData(journalSource_, runNumbers_, logValue.neXuSLocation(), + [=](HttpRequestWorker *worker) { handleRetrieveSELogValueData(worker); }); + } + else + { + // Just hide the data as this value is no longer selected + hideData(logValue); + } +} + +// Log value group model data changed +void PlotLogDataWidget::logValueGroupChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QList &roles) +{ + auto optGroup = logValueGroupModel_.getData(topLeft); + auto &group = optGroup->get(); + + // We have tagged every data entity on the plot as "RunNumber/Property" so we just need to request that those + // with a matching tag are shown / hidden + ui_.Plot->setDataEnabled(QRegularExpression(QString("^%1/.*").arg(group.name())), group.isSelected()); +} + +// Log values selection changed +void PlotLogDataWidget::availableLogValuesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + ui_.ShowLogValueButton->setDisabled(selected.isEmpty()); +} + +void PlotLogDataWidget::shownLogValuesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + ui_.HideLogValueButton->setDisabled(selected.isEmpty()); +} + +void PlotLogDataWidget::on_ShowLogValueButton_clicked(bool checked) +{ + logValueModel_.setSelected( + availableLogValueFilterProxy_.mapSelectionToSource(ui_.AvailableLogValueList->selectionModel()->selection()).indexes(), + true); +} + +void PlotLogDataWidget::on_HideLogValueButton_clicked(bool checked) +{ + logValueModel_.setSelected( + shownLogValueFilterProxy_.mapSelectionToSource(ui_.ShownLogValueList->selectionModel()->selection()).indexes(), false); +} + +void PlotLogDataWidget::on_LogValueFilterEdit_textChanged(const QString &text) +{ + availableLogValueFilterProxy_.setFilterString(text); +} + +void PlotLogDataWidget::on_LogValueFilterClearButton_clicked(bool checked) { ui_.LogValueFilterEdit->clear(); } + +/* + * Public + */ + +// Create summary text for the plot +QString PlotLogDataWidget::summaryText(const QString &lastProperty) const +{ + if (runNumbers_.empty()) + return "Nothing"; + + // Run number (count) + auto result = QString("%1").arg(runNumbers_.front()); + if (runNumbers_.size() > 1) + result += QString("(+%1)").arg(runNumbers_.size() - 1); + + // Last Property + if (!lastProperty.isEmpty()) + result += QString(" / %1").arg(lastProperty); + + return result; +} + +} // namespace JV2 diff --git a/frontend/plotLogDataWidget.h b/frontend/plotLogDataWidget.h new file mode 100644 index 00000000..0db42465 --- /dev/null +++ b/frontend/plotLogDataWidget.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2025 Team JournalViewer and contributors + +#pragma once + +#include "backend.h" +#include "logValue.h" +#include "logValueFilterProxy.h" +#include "logValueGroup.h" +#include "logValueGroupModel.h" +#include "logValueModel.h" +#include "mainWindow.h" +#include "ui_plotLogDataWidget.h" +#include + +namespace JV2 +{ +// Forward Declarations +class JournalSource; + +class PlotLogDataWidget : public QWidget +{ + Q_OBJECT + + public: + PlotLogDataWidget(MainWindow *parent, Backend &backend, const JournalSource *journalSource, + const std::vector &runNumbers); + ~PlotLogDataWidget() = default; + + private: + // User interface object + Ui::PlotLogDataWidget ui_; + // Model and proxies for log values + LogValueModel logValueModel_; + LogValueFilterProxy availableLogValueFilterProxy_; + LogValueFilterProxy shownLogValueFilterProxy_; + // Model and proxy for log value groups + LogValueGroupModel logValueGroupModel_; + // Main Window parent + MainWindow *mainWindow_{nullptr}; + // Main backend + Backend &backend_; + // Journal source from which the run numbers came + const JournalSource *journalSource_{nullptr}; + // Log values available for plotting + std::vector logValues_; + // Run numbers to display on the plot + std::vector runNumbers_; + // Log value groups (per run-number) + std::vector logValueGroups_; + + private: + // Handle retrieved log values + void handleRetrieveSELogValues(HttpRequestWorker *worker); + // Handle retrieved log value data + void handleRetrieveSELogValueData(HttpRequestWorker *worker); + // Show data from the supplied LogValue on the plot + void showData(const LogValue &logValue); + // Hide data from the supplied LogValue from the plot + void hideData(const LogValue &logValue); + + private slots: + // Log value model data changed + void logValueChanged(const QModelIndex &, const QModelIndex &, const QList &); + // Log value group model data changed + void logValueGroupChanged(const QModelIndex &, const QModelIndex &, const QList &); + // Log values selection changed + void availableLogValuesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void shownLogValuesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void on_ShowLogValueButton_clicked(bool checked); + void on_HideLogValueButton_clicked(bool checked); + void on_LogValueFilterEdit_textChanged(const QString &text); + void on_LogValueFilterClearButton_clicked(bool checked); + + public: + // Create summary text for the plot + QString summaryText(const QString &lastProperty = {}) const; + + signals: + // Summary text updated + void summaryTextChanged(QString); +}; +} // namespace JV2 diff --git a/frontend/plotLogDataWidget.ui b/frontend/plotLogDataWidget.ui new file mode 100644 index 00000000..b793910c --- /dev/null +++ b/frontend/plotLogDataWidget.ui @@ -0,0 +1,317 @@ + + + PlotLogDataWidget + + + true + + + + 0 + 0 + 1041 + 592 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Qt::Horizontal + + + + + 10 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + 0 + 0 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + Run Numbers + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 1 + + + + + + + + + + + + 0 + 3 + + + + Log Values + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 1 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + 14 + 14 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Show + + + + :/icons/icons/up.svg + :/icons/icons/up_off.svg:/icons/icons/up.svg + + + + + + + false + + + Hide + + + + :/icons/icons/down.svg + :/icons/icons/down_off.svg:/icons/icons/down.svg + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 4 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + 14 + 14 + + + + + + + + + + + 0 + 0 + + + + Filter log values + + + + + + + + 0 + 0 + + + + + 20 + 16777215 + + + + + + + + :/icons/cross.svg:/icons/cross.svg + + + + + + + + + + + + + + + + + Mildred::MildredWidget + QWidget +
plot/src/widget.h
+ 1 +
+
+ + + + +
diff --git a/frontend/resources.qrc b/frontend/resources.qrc index e3214225..19b8a0fb 100644 --- a/frontend/resources.qrc +++ b/frontend/resources.qrc @@ -1,9 +1,15 @@ + icons/down.svg + icons/up.svg icons/group.svg icons/cross.svg + icons/up_off.svg + icons/down_off.svg icons/filter.svg icons/open.svg + icons/add.svg + icons/remove.svg icons/jv2.svg diff --git a/frontend/runDataFilterProxy.cpp b/frontend/runDataFilterProxy.cpp index 477278e8..20f9aa8a 100644 --- a/frontend/runDataFilterProxy.cpp +++ b/frontend/runDataFilterProxy.cpp @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ RunDataFilterProxy::RunDataFilterProxy(RunDataModel &runDataModel) : runDataModel_(runDataModel) { setSourceModel(&runDataModel_); @@ -56,3 +58,4 @@ QString RunDataFilterProxy::getData(const QString &targetData, const QModelIndex { return runDataModel_.getData(targetData, mapToSource(index)); } +} // namespace JV2 diff --git a/frontend/runDataFilterProxy.h b/frontend/runDataFilterProxy.h index 0a4c29f1..2335bcfa 100644 --- a/frontend/runDataFilterProxy.h +++ b/frontend/runDataFilterProxy.h @@ -3,12 +3,14 @@ #pragma once +#include #include #include +namespace JV2 +{ // Forward Declarations class RunDataModel; -class QModelIndex; class RunDataFilterProxy : public QSortFilterProxyModel { @@ -36,3 +38,4 @@ class RunDataFilterProxy : public QSortFilterProxyModel protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; }; +} // namespace JV2 diff --git a/frontend/runDataModel.cpp b/frontend/runDataModel.cpp index 8ebf05f0..d8e3a5b9 100644 --- a/frontend/runDataModel.cpp +++ b/frontend/runDataModel.cpp @@ -6,6 +6,8 @@ #include #include +namespace JV2 +{ // Model to handle json data in table view RunDataModel::RunDataModel() : QAbstractTableModel() {} @@ -169,3 +171,4 @@ QVariant RunDataModel::headerData(int section, Qt::Orientation orientation, int return {}; } } +} // namespace JV2 diff --git a/frontend/runDataModel.h b/frontend/runDataModel.h index 272b36a1..db79511e 100644 --- a/frontend/runDataModel.h +++ b/frontend/runDataModel.h @@ -12,6 +12,8 @@ #include #include +namespace JV2 +{ // JSON Run Data Model class RunDataModel : public QAbstractTableModel { @@ -52,3 +54,4 @@ class RunDataModel : public QAbstractTableModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; }; +} // namespace JV2 diff --git a/frontend/seLogChooserDialog.cpp b/frontend/seLogChooserDialog.cpp index d1efb9b2..28f2e141 100644 --- a/frontend/seLogChooserDialog.cpp +++ b/frontend/seLogChooserDialog.cpp @@ -3,6 +3,8 @@ #include "seLogChooserDialog.h" +namespace JV2 +{ SELogChooserDialog::SELogChooserDialog(QWidget *parent, GenericTreeItem *rootItem) : QDialog(parent) { ui_.setupUi(this); @@ -62,3 +64,4 @@ QStringList SELogChooserDialog::getValues() return result; } +} // namespace JV2 diff --git a/frontend/seLogChooserDialog.h b/frontend/seLogChooserDialog.h index 284f58f7..e7b7305f 100644 --- a/frontend/seLogChooserDialog.h +++ b/frontend/seLogChooserDialog.h @@ -7,6 +7,8 @@ #include "optionalRef.h" #include "ui_seLogChooserDialog.h" +namespace JV2 +{ class SELogChooserDialog : public QDialog { Q_OBJECT @@ -32,3 +34,4 @@ class SELogChooserDialog : public QDialog QString getValue(); QStringList getValues(); }; +} // namespace JV2 diff --git a/frontend/searchDialog.cpp b/frontend/searchDialog.cpp index 8b4804a8..6a225e0b 100644 --- a/frontend/searchDialog.cpp +++ b/frontend/searchDialog.cpp @@ -4,6 +4,8 @@ #include "searchDialog.h" #include +namespace JV2 +{ SearchDialog::SearchDialog(QWidget *parent) : QDialog(parent) { ui_.setupUi(this); @@ -63,3 +65,4 @@ std::map SearchDialog::getQuery() return parameters; } +} // namespace JV2 diff --git a/frontend/searchDialog.h b/frontend/searchDialog.h index b738de99..7ec9734c 100644 --- a/frontend/searchDialog.h +++ b/frontend/searchDialog.h @@ -5,6 +5,8 @@ #include "ui_searchDialog.h" +namespace JV2 +{ // Forward Declarations class MainWindow; @@ -32,3 +34,4 @@ class SearchDialog : public QDialog // Get search query std::map getQuery(); }; +} // namespace JV2 diff --git a/frontend/searching.cpp b/frontend/searching.cpp index f0552141..1812be71 100644 --- a/frontend/searching.cpp +++ b/frontend/searching.cpp @@ -5,6 +5,8 @@ #include "searchDialog.h" #include +namespace JV2 +{ /* * UI */ @@ -158,3 +160,4 @@ void MainWindow::handleSearchResult(HttpRequestWorker *worker) updateForCurrentSource(JournalSource::JournalSourceState::OK); } +} // namespace JV2 diff --git a/frontend/settings.cpp b/frontend/settings.cpp index ecea20e1..f5e9f436 100644 --- a/frontend/settings.cpp +++ b/frontend/settings.cpp @@ -11,6 +11,8 @@ #include #include +namespace JV2 +{ /* * Private Functions */ @@ -162,3 +164,4 @@ void MainWindow::getJournalSourcesFromSettings(QCommandLineParser &cliParser) if (cliParser.isSet(CLIArgs::HideIDAaaS)) idaaasDataCache->setAvailable(false); } +} // namespace JV2 diff --git a/frontend/uniqueName.h b/frontend/uniqueName.h index 426579f6..bedd5d71 100644 --- a/frontend/uniqueName.h +++ b/frontend/uniqueName.h @@ -3,6 +3,8 @@ #include +namespace JV2 +{ // Return unique name for object template static QString uniqueName(const QString &baseName, const Range &objects, NameFunction nameFunction) @@ -20,3 +22,4 @@ static QString uniqueName(const QString &baseName, const Range &objects, NameFun return uniqueName; } +} // namespace JV2 diff --git a/frontend/version.h b/frontend/version.h index 5c99a87b..48ec17be 100644 --- a/frontend/version.h +++ b/frontend/version.h @@ -3,5 +3,8 @@ #pragma once +namespace JV2 +{ #define JV2VERSION "1.99.3" #define JV2URL "https://github.com/disorderedmaterials/jv2" +} // namespace JV2 diff --git a/frontend/visualisation.cpp b/frontend/visualisation.cpp deleted file mode 100644 index fed40345..00000000 --- a/frontend/visualisation.cpp +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2025 Team JournalViewer and contributors - -#include "chartView.h" -#include "mainWindow.h" -#include "seLogChooserDialog.h" -#include -#include -#include -#include -#include -#include - -// Handle extracted SE log values for plotting -void MainWindow::handlePlotSELogValue(HttpRequestWorker *worker) -{ - // Check for errors - if (handleRequestError(worker, "retrieving log values from run") != NoError) - return; - - // Iterate over logs extracted from the target run data and construct our mapped values - auto *rootItem = new GenericTreeItem({"Log Value", "Full Path"}); - foreach (const auto &log, worker->jsonResponse().array()) - { - auto logArray = log.toArray(); - if (logArray.size() < 2) - continue; - - // First item in the array is the name of the log value set / section - auto *sectionItem = rootItem->appendChild({logArray.first().toString(), ""}); - - // Remove the name item and proceed to iterate over log values - logArray.removeFirst(); - - auto logArrayVar = logArray.toVariantList(); - std::sort(logArrayVar.begin(), logArrayVar.end(), - [](QVariant &v1, QVariant &v2) { return v1.toString() < v2.toString(); }); - - foreach (const auto &block, logArrayVar) - sectionItem->appendChild({block.toString().split("/").last(), block.toString()}); - } - - // Create the dialog - SELogChooserDialog chooserDialog(this, rootItem); - - auto logValue = chooserDialog.getValue(); - if (logValue.isEmpty()) - return; - - // Request the log value data - backend_.getNexusLogValueData(currentJournalSource(), selectedRunNumbers(), logValue, - [=](HttpRequestWorker *worker) { handleCreateSELogPlot(worker); }); -} - -// Handle plotting of SE log data -void MainWindow::handleCreateSELogPlot(HttpRequestWorker *worker) -{ - auto *window = new QWidget; - auto *dateTimeChart = new QChart(); - auto *dateTimeChartView = new ChartView(dateTimeChart, window); - auto *relTimeChart = new QChart(); - auto *relTimeChartView = new ChartView(relTimeChart, window); - - // Check network reply - if (handleRequestError(worker, "trying to graph a log value") != NoError) - return; - - // The expected result from the backend is as follows: - // - // result = { - // logValue: "name_of_log_value", - // runNumbers: { run1, run2, run3 ... runN } - // data: { - // run1: { - // timeRange: [ datetime, datetime ], - // data: [ (x,y), (x2,y2), ..., (xn,yn) ] - // }, - // ... - // runN: { - // ... - // } - // } - - const auto receivedData = worker->jsonResponse().object(); - auto logValueName = receivedData["logValue"].toString().section('/', -1); - qDebug() << logValueName; - - const auto logValueData = receivedData["data"].toObject(); - - auto *timeAxis = new QDateTimeAxis(); - timeAxis->setFormat("yyyy-MM-dd
H:mm:ss"); - dateTimeChart->addAxis(timeAxis, Qt::AlignBottom); - - auto *dateTimeYAxis = new QValueAxis(); - dateTimeYAxis->setRange(0, 0); - - auto *dateTimeStringAxis = new QCategoryAxis(); - QStringList categoryValues; - - auto *relTimeXAxis = new QValueAxis(); - relTimeXAxis->setTitleText("Relative Time (s)"); - relTimeChart->addAxis(relTimeXAxis, Qt::AlignBottom); - - auto *relTimeYAxis = new QValueAxis(); - - auto *relTimeStringAxis = new QCategoryAxis(); - - QList chartFields; - bool firstRun = true; - foreach (const auto &run, logValueData) - { - // Get the data name (run number) - const auto dataName = run[QString("runNumber")].toString(); - qDebug() << run[QString("runNumber")].toString(); - - // Extract the time range data - const auto timeRange = run[QString("timeRange")].toArray(); - - auto startTime = QDateTime::fromString(timeRange.first()[0].toString(), "yyyy-MM-dd'T'HH:mm:ss"); - auto endTime = QDateTime::fromString(timeRange.first()[1].toString(), "yyyy-MM-dd'T'HH:mm:ss"); - - if (firstRun) - { - timeAxis->setRange(startTime, endTime); - relTimeXAxis->setRange(0, 0); - } - - const auto fieldDataArray = run[QString("data")].toArray(); - - // foreach (const auto &fieldData, runFieldsArray) - // { - // auto fieldDataArray = fieldData.toArray(); - // fieldDataArray.removeFirst(); - // if (!fieldDataArray.first()[1].isString()) - // break; - // foreach (const auto &dataPair, fieldDataArray) - // { - // auto dataPairArray = dataPair.toArray(); - // categoryValues.append(dataPairArray[1].toString()); - // } - // } - - if (!categoryValues.isEmpty()) - { - categoryValues.removeDuplicates(); - categoryValues.sort(); - } - if (firstRun) - { - if (!categoryValues.isEmpty()) - { - dateTimeChart->addAxis(dateTimeStringAxis, Qt::AlignLeft); - relTimeChart->addAxis(relTimeStringAxis, Qt::AlignLeft); - } - else - { - dateTimeChart->addAxis(dateTimeYAxis, Qt::AlignLeft); - relTimeChart->addAxis(relTimeYAxis, Qt::AlignLeft); - } - firstRun = false; - } - - // For each plot point - auto *dateSeries = new QLineSeries(); - auto *relSeries = new QLineSeries(); - - connect(dateSeries, &QLineSeries::hovered, - [=](const QPointF point, bool hovered) { dateTimeChartView->setHovered(point, hovered, dateSeries->name()); }); - connect(dateTimeChartView, SIGNAL(showCoordinates(qreal, qreal, QString)), this, - SLOT(showStatus(qreal, qreal, QString))); - connect(dateTimeChartView, SIGNAL(clearCoordinates()), statusBar(), SLOT(clearMessage())); - connect(relSeries, &QLineSeries::hovered, - [=](const QPointF point, bool hovered) { relTimeChartView->setHovered(point, hovered, relSeries->name()); }); - connect(relTimeChartView, SIGNAL(showCoordinates(qreal, qreal, QString)), this, - SLOT(showStatus(qreal, qreal, QString))); - connect(relTimeChartView, SIGNAL(clearCoordinates()), statusBar(), SLOT(clearMessage())); - - // Set dateSeries ID - if (!chartFields.contains(logValueName)) - chartFields.append(logValueName); - dateSeries->setName(dataName); - relSeries->setName(dataName); - - if (fieldDataArray.first()[1].isString()) - { - foreach (const auto &dataPair, fieldDataArray) - { - auto dataPairArray = dataPair.toArray(); - dateSeries->append(startTime.addSecs(dataPairArray[0].toDouble()).toMSecsSinceEpoch(), - categoryValues.indexOf(dataPairArray[1].toString())); - relSeries->append(dataPairArray[0].toDouble(), categoryValues.indexOf(dataPairArray[1].toString())); - } - } - else - { - foreach (const auto &dataPair, fieldDataArray) - { - auto dataPairArray = dataPair.toArray(); - dateSeries->append(startTime.addSecs(dataPairArray[0].toDouble()).toMSecsSinceEpoch(), - dataPairArray[1].toDouble()); - relSeries->append(dataPairArray[0].toDouble(), dataPairArray[1].toDouble()); - if (dateTimeYAxis->min() == 0 && dateTimeYAxis->max() == 0) - dateTimeYAxis->setRange(dataPairArray[1].toDouble(), dataPairArray[1].toDouble()); - if (dataPairArray[1].toDouble() < dateTimeYAxis->min()) - dateTimeYAxis->setMin(dataPairArray[1].toDouble()); - if (dataPairArray[1].toDouble() > dateTimeYAxis->max()) - dateTimeYAxis->setMax(dataPairArray[1].toDouble()); - } - } - if (startTime.addSecs(startTime.secsTo(QDateTime::fromMSecsSinceEpoch(dateSeries->at(0).x()))) < timeAxis->min()) - timeAxis->setMin(startTime.addSecs(startTime.secsTo(QDateTime::fromMSecsSinceEpoch(dateSeries->at(0).x())))); - if (endTime > timeAxis->max()) - timeAxis->setMax(endTime); - - if (relSeries->at(0).x() < relTimeXAxis->min()) - relTimeXAxis->setMin(relSeries->at(0).x()); - if (relSeries->at(relSeries->count() - 1).x() > relTimeXAxis->max()) - relTimeXAxis->setMax(relSeries->at(relSeries->count() - 1).x()); - - dateTimeChart->addSeries(dateSeries); - dateSeries->attachAxis(timeAxis); - relTimeChart->addSeries(relSeries); - relSeries->attachAxis(relTimeXAxis); - if (categoryValues.isEmpty()) - { - dateSeries->attachAxis(dateTimeYAxis); - relSeries->attachAxis(relTimeYAxis); - } - else - { - dateSeries->attachAxis(dateTimeStringAxis); - relSeries->attachAxis(relTimeStringAxis); - } - } - - if (!categoryValues.isEmpty()) - { - dateTimeStringAxis->setRange(0, categoryValues.count() - 1); - relTimeStringAxis->setRange(0, categoryValues.count() - 1); - for (auto i = 0; i < categoryValues.count(); i++) - { - dateTimeStringAxis->append(categoryValues[i], i); - relTimeStringAxis->append(categoryValues[i], i); - } - dateTimeStringAxis->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); - relTimeStringAxis->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue); - } - - relTimeYAxis->setRange(dateTimeYAxis->min(), dateTimeYAxis->max()); - - auto *gridLayout = new QGridLayout(window); - auto *axisToggleCheck = new QCheckBox("Plot relative to run start times", window); - connect(axisToggleCheck, SIGNAL(stateChanged(int)), this, SLOT(toggleAxis(int))); - - gridLayout->addWidget(dateTimeChartView, 1, 0, -1, -1); - gridLayout->addWidget(relTimeChartView, 1, 0, -1, -1); - relTimeChartView->hide(); - gridLayout->addWidget(axisToggleCheck, 0, 0); - QString tabName; - for (auto i = 0; i < chartFields.size(); i++) - { - tabName += chartFields[i]; - if (i < chartFields.size() - 1) - tabName += ","; - } - if (!categoryValues.isEmpty()) - { - dateTimeStringAxis->setTitleText(tabName); - relTimeStringAxis->setTitleText(tabName); - } - else - { - dateTimeYAxis->setTitleText(tabName); - relTimeYAxis->setTitleText(tabName); - } - ui_.MainTabs->addTab(window, tabName); - QString runs; - for (auto series : dateTimeChart->series()) - runs.append(series->name() + ", "); - runs.chop(2); - QString toolTip = tabName + "\n" + runs; - ui_.MainTabs->setTabToolTip(ui_.MainTabs->count() - 1, toolTip); - ui_.MainTabs->setCurrentIndex(ui_.MainTabs->count() - 1); - dateTimeChartView->setFocus(); -}