viewer: Phase 1a — dockable shell with saved perspectives
Make the layout reconfigurable, the foundation for the redesign: - The diffraction image becomes the central widget; the right-hand side panel is now a dockable "Inspector" (QDockWidget, right area). - Every dock and toolbar gets a stable objectName so QMainWindow saveState / restoreState round-trips. Dataset-info docks are numbered uniquely. - Persist geometry + dock state to QSettings on close and restore on launch, so the user's arrangement resumes. - New "View" menu with Image / Processing perspectives (show/hide the bottom plots + jobs panel) and "Reset layout" (back to the as-built arrangement, captured at startup). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -39,6 +39,10 @@ signals:
|
||||
void clearUserMaskSelected();
|
||||
void openDatasetInfo();
|
||||
|
||||
void imageLayoutSelected();
|
||||
void processingLayoutSelected();
|
||||
void resetLayoutSelected();
|
||||
|
||||
private slots:
|
||||
void aboutSelected();
|
||||
void licensesSelected();
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
#include "JFJochViewerWindow.h"
|
||||
|
||||
#include <QSplitter>
|
||||
#include <QThread>
|
||||
#include <QDockWidget>
|
||||
#include <QKeyEvent>
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
#include <QCloseEvent>
|
||||
|
||||
#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<QDockWidget *>())
|
||||
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 |
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user