diff --git a/viewer/JFJochViewerMenu.cpp b/viewer/JFJochViewerMenu.cpp index 4f766c06..ef60b2b4 100644 --- a/viewer/JFJochViewerMenu.cpp +++ b/viewer/JFJochViewerMenu.cpp @@ -65,6 +65,15 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) { const QAction *dockCalibration = dockMenu->addAction("New dataset info plot"); connect(dockCalibration, &QAction::triggered, [this] { emit openDatasetInfo();}); + QMenu *viewMenu = addMenu("View"); + const QAction *imageLayout = viewMenu->addAction("Image layout"); + connect(imageLayout, &QAction::triggered, this, &JFJochViewerMenu::imageLayoutSelected); + const QAction *processingLayout = viewMenu->addAction("Processing layout"); + connect(processingLayout, &QAction::triggered, this, &JFJochViewerMenu::processingLayoutSelected); + viewMenu->addSeparator(); + const QAction *resetLayout = viewMenu->addAction("Reset layout"); + connect(resetLayout, &QAction::triggered, this, &JFJochViewerMenu::resetLayoutSelected); + QMenu *helpMenu = addMenu("Help"); // Add "About" action const QAction *aboutAction = helpMenu->addAction("About"); diff --git a/viewer/JFJochViewerMenu.h b/viewer/JFJochViewerMenu.h index 04d87754..2a07ef2c 100644 --- a/viewer/JFJochViewerMenu.h +++ b/viewer/JFJochViewerMenu.h @@ -39,6 +39,10 @@ signals: void clearUserMaskSelected(); void openDatasetInfo(); + void imageLayoutSelected(); + void processingLayoutSelected(); + void resetLayoutSelected(); + private slots: void aboutSelected(); void licensesSelected(); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index acfc4633..3befd9a9 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -3,12 +3,14 @@ #include "JFJochViewerWindow.h" -#include #include #include #include #include #include +#include +#include +#include #include "JFJochImageReadingWorker.h" #include "image_viewer/JFJochDiffractionImage.h" @@ -43,10 +45,12 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString setFocusPolicy(Qt::StrongFocus); auto toolBarImage = new JFJochViewerToolbarImage(this); + toolBarImage->setObjectName("toolBarImage"); // objectName required for saveState/restoreState addToolBar(Qt::TopToolBarArea, toolBarImage); addToolBarBreak(Qt::TopToolBarArea); toolBarDisplay = new JFJochViewerToolbarDisplay(this); + toolBarDisplay->setObjectName("toolBarDisplay"); addToolBar(Qt::TopToolBarArea, toolBarDisplay); statusbar = new JFJochViewerStatusBar(this); @@ -77,14 +81,11 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString experiment.ImportIndexingSettings(indexing_settings); experiment.DetectIceRings(true); - // Central area: only the main horizontal splitter (image + side panel) - auto h_splitter = new QSplitter(this); - h_splitter->setOrientation(Qt::Horizontal); - setCentralWidget(h_splitter); - + // Central area: the diffraction image. Everything else (inspector, plots, processing) is a + // dock, so the layout can be rearranged, saved, and switched between perspectives. auto viewer = new JFJochDiffractionImage(this); - viewer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - h_splitter->addWidget(viewer); + viewer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setCentralWidget(viewer); auto side_panel = new JFJochViewerSidePanel(this); auto side_panel_scroll = new QScrollArea(this); @@ -94,9 +95,13 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString side_panel_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); side_panel_scroll->setMinimumWidth(450); side_panel_scroll->setMaximumWidth(600); - side_panel_scroll->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - h_splitter->addWidget(side_panel_scroll); + inspectorDock = new QDockWidget("Inspector", this); + inspectorDock->setObjectName("inspectorDock"); + inspectorDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + inspectorDock->setWidget(side_panel_scroll); + addDockWidget(Qt::RightDockWidgetArea, inspectorDock); + menuBar->AddDockEntry(inspectorDock, "Inspector"); reading_worker = new JFJochImageReadingWorker(spot_finding_settings, experiment); reading_thread = new QThread(this); @@ -417,7 +422,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString }); // Dock the processing panel in the bottom-right corner, next to the dataset-info plots. - auto processingDock = new QDockWidget("Processing", this); + processingDock = new QDockWidget("Processing", this); + processingDock->setObjectName("processingDock"); processingDock->setAllowedAreas(Qt::BottomDockWidgetArea); processingDock->setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); @@ -432,6 +438,20 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString } menuBar->AddDockEntry(processingDock, "Processing"); + connect(menuBar, &JFJochViewerMenu::imageLayoutSelected, this, + [this] { ApplyPerspective(Perspective::Image); }); + connect(menuBar, &JFJochViewerMenu::processingLayoutSelected, this, + [this] { ApplyPerspective(Perspective::Processing); }); + connect(menuBar, &JFJochViewerMenu::resetLayoutSelected, this, + [this] { if (!defaultLayoutState.isEmpty()) restoreState(defaultLayoutState); }); + + // Remember the freshly-built layout so "Reset layout" can return to it, then restore the + // user's last-used arrangement if they have one. + defaultLayoutState = saveState(); + QSettings settings("PSI", "jfjoch_viewer"); + restoreGeometry(settings.value("geometry").toByteArray()); + restoreState(settings.value("windowState").toByteArray()); + if (!file.isEmpty()) LoadFile(file, 0, 1, false); } @@ -443,6 +463,24 @@ JFJochViewerWindow::~JFJochViewerWindow() { } } +void JFJochViewerWindow::ApplyPerspective(Perspective p) { + // Image: just the image + inspector. Processing: also the dataset-info plots and jobs panel. + const bool processing = (p == Perspective::Processing); + if (inspectorDock) inspectorDock->setVisible(true); + if (processingDock) processingDock->setVisible(processing); + for (auto *d : findChildren()) + if (d->objectName().startsWith("datasetInfoDock")) + d->setVisible(processing); +} + +void JFJochViewerWindow::closeEvent(QCloseEvent *event) { + // Persist the dock/toolbar arrangement and window geometry so the next launch resumes it. + QSettings settings("PSI", "jfjoch_viewer"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + QMainWindow::closeEvent(event); +} + void JFJochViewerWindow::LoadFile(const QString &filename, qint64 image_number, qint64 summation, bool retry) { emit LoadFileRequest(filename, image_number, summation, true); } @@ -458,6 +496,7 @@ void JFJochViewerWindow::NewDatasetInfo() { info->runsChanged(lastRuns, lastActiveRunId); auto dock = new QDockWidget(QString("Dataset info"), this); + dock->setObjectName(QStringLiteral("datasetInfoDock%1").arg(datasetInfoCounter++)); dock->setAllowedAreas(Qt::BottomDockWidgetArea); dock->setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable | diff --git a/viewer/JFJochViewerWindow.h b/viewer/JFJochViewerWindow.h index cc655dcd..ea19a1d3 100644 --- a/viewer/JFJochViewerWindow.h +++ b/viewer/JFJochViewerWindow.h @@ -39,11 +39,20 @@ private: QProgressDialog *retryDialog = nullptr; QDockWidget *lastDatasetInfoDock = nullptr; // most recent dataset-info dock, for docking layout + QDockWidget *inspectorDock = nullptr; // image inspector (former right-hand side panel) + QDockWidget *processingDock = nullptr; // processing jobs panel + QByteArray defaultLayoutState; // captured after construction, for "Reset layout" + int datasetInfoCounter = 0; // gives each dataset-info dock a unique objectName QThread *reading_thread; + // Named layouts. Image = just the image + inspector; Processing = also the plots + jobs panel. + enum class Perspective { Image, Processing }; + void ApplyPerspective(Perspective p); + void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; + void closeEvent(QCloseEvent *event) override; public slots: void LoadFile(const QString &filename, qint64 image_number, qint64 summation, bool retry); void LoadImage(qint64 image_number, qint64 summation);