Files
Jungfraujoch/viewer/JFJochViewerWindow.cpp
T
leonarski_fandClaude Opus 4.8 9f9e13a4e4 viewer: PSI logo + tidy beam-center row
- Show the PSI logo in the menu-bar corner, picking one of four interchangeable
  dot designs at random each launch. Embedded as PNGs (rasterised from the SVG
  sources, kept alongside) so no Qt SVG module dependency is needed.
- Settings dock: put beam center X and Y on a single "Beam center" row to save
  vertical space on laptop screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:26:15 +02:00

615 lines
30 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerWindow.h"
#include <QThread>
#include <QDockWidget>
#include <QKeyEvent>
#include <QGuiApplication>
#include <QScreen>
#include <QScrollArea>
#include <QSettings>
#include <QCloseEvent>
#include <QRandomGenerator>
#include <QLabel>
#include "JFJochImageReadingWorker.h"
#include "image_viewer/JFJochDiffractionImage.h"
#include "JFJochViewerSidePanel.h"
#include "widgets/JFJochViewerSettingsDock.h"
#include "JFJochViewerStatusBar.h"
#include "../common/CUDAWrapper.h"
#include "windows/JFJochViewerImageListWindow.h"
#include "windows/JFJochViewerMetadataWindow.h"
#ifdef JFJOCH_VIEWER_DBUS
#include "dbus/JFJochViewerAdaptor.h"
#endif
#include "windows/JFJochViewerProcessingWindow.h"
#include "windows/JFJochProcessingJobsWindow.h"
#include "windows/JFJochSettingsWindow.h"
#include "windows/JFJochViewerSpotListWindow.h"
#include "windows/JFJochViewerReflectionListWindow.h"
#include "windows/JFJochCalibrationWindow.h"
#include "windows/JFJochViewerReciprocalSpaceWindow.h"
#include "toolbar/JFJochViewerToolbarDisplay.h"
#include "toolbar/JFJochViewerToolbarImage.h"
#include "windows/JFJoch2DAzintImageWindow.h"
#include "windows/JFJochAzIntWindow.h"
#include "windows/JFJochMagnifierWindow.h"
#include "image_viewer/JFJochImage.h"
#include "image_viewer/JFJochSimpleImage.h"
#include <QMessageBox>
JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString &file) : QMainWindow(parent) {
menuBar = new JFJochViewerMenu(this);
setMenuBar(menuBar);
// PSI logo in the menu-bar corner. There are four interchangeable dot designs; pick one at
// random each launch, for fun.
{
const int n = QRandomGenerator::global()->bounded(1, 5); // 1..4
QPixmap logoPixmap(QStringLiteral(":/psi_%1.png").arg(n, 2, 10, QLatin1Char('0')));
auto *logo = new QLabel(this);
logo->setPixmap(logoPixmap.scaledToHeight(22, Qt::SmoothTransformation));
logo->setContentsMargins(6, 0, 8, 0);
menuBar->setCornerWidget(logo, Qt::TopRightCorner);
}
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);
setStatusBar(statusbar);
setDockOptions(dockOptions() & ~QMainWindow::DockOption::AllowTabbedDocks);
setWindowTitle("Jungfraujoch image viewer");
// Start large on a big display but fit within a laptop screen.
const QRect avail = QGuiApplication::primaryScreen()->availableGeometry();
resize(qMin(1200, avail.width() - 100), qMin(1100, avail.height() - 100));
SpotFindingSettings spot_finding_settings = DiffractionExperiment::DefaultDataProcessingSettings();
spot_finding_settings.high_resolution_limit = 1.5;
spot_finding_settings.indexing = true;
IndexingSettings indexing_settings;
indexing_settings.IndexingThreads(1);
indexing_settings.Algorithm(IndexingAlgorithmEnum::Auto);
if (get_gpu_count() == 0) {
indexing_settings.Algorithm(IndexingAlgorithmEnum::FFTW);
indexing_settings.FFT_NumVectors(8 * 1024);
}
indexing_settings.GeomRefinementAlgorithm(GeomRefinementAlgorithmEnum::BeamCenter);
DiffractionExperiment experiment;
experiment.ImportIndexingSettings(indexing_settings);
experiment.DetectIceRings(true);
// 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::Expanding, QSizePolicy::Expanding);
setCentralWidget(viewer);
auto side_panel = new JFJochViewerSidePanel(this);
auto side_panel_scroll = new QScrollArea(this);
side_panel_scroll->setWidget(side_panel);
side_panel_scroll->setWidgetResizable(true);
side_panel_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
side_panel_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
side_panel_scroll->setMinimumWidth(450);
side_panel_scroll->setMaximumWidth(600);
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);
reading_worker->moveToThread(reading_thread);
reading_thread->start();
auto tableWindow = new JFJochViewerImageListWindow(this);
auto metadataWindow = new JFJochViewerMetadataWindow(this);
auto spotWindow = new JFJochViewerSpotListWindow(this);
auto reflectionWindow = new JFJochViewerReflectionListWindow(this);
auto settingsWindow = new JFJochSettingsWindow(spot_finding_settings, indexing_settings,
experiment.GetAzimuthalIntegrationSettings(),
experiment.GetBraggIntegrationSettings(),
experiment.GetScalingSettings(), this);
auto calibrationWindow = new JFJochCalibrationWindow(this);
auto reciprocalWindow = new JFJochViewerReciprocalSpaceWindow(this);
auto azintImageWindow = new JFJoch2DAzintImageWindow(this);
auto magnifierWindow = new JFJochMagnifierWindow(this);
processingJobsWindow = new JFJochProcessingJobsWindow(reading_worker, this);
menuBar->AddWindowEntry(tableWindow, "Image list");
menuBar->AddWindowEntry(spotWindow, "Spot list");
menuBar->AddWindowEntry(reflectionWindow, "Reflection list");
menuBar->AddWindowEntry(metadataWindow, "Image metadata");
menuBar->AddWindowEntry(settingsWindow, "Processing settings");
menuBar->AddWindowEntry(calibrationWindow, "Calibration image viewer");
menuBar->AddWindowEntry(reciprocalWindow, "Reciprocal space viewer");
menuBar->AddWindowEntry(azintImageWindow, "Azimuthal integration 2D image");
menuBar->AddWindowEntry(magnifierWindow, "Magnifier");
// processingJobsWindow is docked (bottom, next to the plots), not a standalone window - see below.
#ifdef JFJOCH_VIEWER_DBUS
if (dbus) {
// Create adaptor attached to this window
new JFJochViewerAdaptor(this);
QDBusConnection connection = QDBusConnection::sessionBus();
if (!connection.registerService("ch.psi.jfjoch_viewer")) {
qWarning("Failed to register D-Bus service: %s", qPrintable(connection.lastError().message()));
} else {
if (!connection.registerObject("/", this, QDBusConnection::ExportAdaptors)) {
qFatal("Failed to register D-Bus object: %s", qPrintable(connection.lastError().message()));
}
}
}
#else
(void) dbus;
#endif
connect(this, &JFJochViewerWindow::LoadFileRequest,
reading_worker, &JFJochImageReadingWorker::LoadFile);
connect(this, &JFJochViewerWindow::LoadImageRequest,
reading_worker, &JFJochImageReadingWorker::LoadImage);
connect(menuBar, &JFJochViewerMenu::fileOpenSelected,
reading_worker, &JFJochImageReadingWorker::LoadFile);
connect(menuBar, &JFJochViewerMenu::fileCloseSelected,
reading_worker, &JFJochImageReadingWorker::CloseFile);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
viewer, &JFJochDiffractionImage::loadImage);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
side_panel, &JFJochViewerSidePanel::loadImage);
connect(reading_worker, &JFJochImageReadingWorker::imageStatsUpdated,
side_panel, &JFJochViewerSidePanel::loadImage);
connect(reading_worker, &JFJochImageReadingWorker::imageNumberChanged, toolBarImage,
&JFJochViewerToolbarImage::setImageNumber);
connect(toolBarImage, &JFJochViewerToolbarImage::loadImage, reading_worker, &JFJochImageReadingWorker::LoadImage);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::setForeground, viewer,
&JFJochDiffractionImage::changeForeground);
connect(viewer, &JFJochDiffractionImage::autoForegroundChanged,
toolBarDisplay, &JFJochViewerToolbarDisplay::updateAutoForeground);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::setAutoForeground, viewer,
&JFJochDiffractionImage::setAutoForeground);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::setHDRMode, viewer,
&JFJochDiffractionImage::setHDRMode);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, viewer,
&JFJochDiffractionImage::setColorMap);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, azintImageWindow,
&JFJoch2DAzintImageWindow::setColorMap);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
azintImageWindow, &JFJoch2DAzintImageWindow::imageLoaded);
connect(viewer, &JFJochDiffractionImage::foregroundChanged,
toolBarDisplay, &JFJochViewerToolbarDisplay::updateForeground);
connect(side_panel, &JFJochViewerSidePanel::roisChanged,
reading_worker, &JFJochImageReadingWorker::SetROIDefinition);
connect(side_panel, &JFJochViewerSidePanel::selectedROIChanged,
viewer, &JFJochDiffractionImage::setSelectedROI);
connect(viewer, &JFJochDiffractionImage::roiGeometryEdited,
reading_worker, &JFJochImageReadingWorker::SetROIDefinition);
connect(viewer, &JFJochDiffractionImage::roiSelected,
side_panel, &JFJochViewerSidePanel::selectROIInList);
connect(side_panel, &JFJochViewerSidePanel::downloadROIs,
reading_worker, &JFJochImageReadingWorker::DownloadROIsFromServer);
connect(side_panel, &JFJochViewerSidePanel::uploadROIs,
reading_worker, &JFJochImageReadingWorker::UploadROIsToServer);
connect(side_panel, &JFJochViewerSidePanel::maskFromROI,
reading_worker, &JFJochImageReadingWorker::MaskFromSelectedROI);
connect(menuBar, &JFJochViewerMenu::clearUserMaskSelected,
reading_worker, &JFJochImageReadingWorker::ClearUserMask);
connect(menuBar, &JFJochViewerMenu::saveUserMaskTiffSelected,
reading_worker, &JFJochImageReadingWorker::SaveUserMaskTIFF);
connect(menuBar, &JFJochViewerMenu::loadUserMaskTiffSelected,
reading_worker, &JFJochImageReadingWorker::LoadUserMaskTIFF);
connect(menuBar, &JFJochViewerMenu::uploadUserMaskSelected,
reading_worker, &JFJochImageReadingWorker::UploadUserMask);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
tableWindow, &JFJochViewerImageListWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
spotWindow, &JFJochViewerSpotListWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
metadataWindow, &JFJochViewerMetadataWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
tableWindow, &JFJochViewerImageListWindow::imageLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
spotWindow, &JFJochViewerSpotListWindow::imageLoaded);
connect(tableWindow, &JFJochViewerImageListWindow::imageSelected,
reading_worker, &JFJochImageReadingWorker::LoadImage);
connect(reading_worker, &JFJochImageReadingWorker::autoloadChanged,
toolBarImage, &JFJochViewerToolbarImage::setAutoloadMode);
connect(toolBarImage, &JFJochViewerToolbarImage::autoLoadButtonPressed,
reading_worker, &JFJochImageReadingWorker::setAutoLoadMode);
connect(toolBarImage, &JFJochViewerToolbarImage::imageJumpChanged,
reading_worker, &JFJochImageReadingWorker::setAutoLoadJump);
connect(toolBarImage, &JFJochViewerToolbarImage::reanalyzeImages,
reading_worker, &JFJochImageReadingWorker::ReanalyzeImages);
connect(side_panel, &JFJochViewerSidePanel::analyze,
reading_worker, &JFJochImageReadingWorker::Analyze);
connect(side_panel, &JFJochViewerSidePanel::findBeamCenter,
reading_worker, &JFJochImageReadingWorker::FindCenter);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
reflectionWindow, &JFJochViewerReflectionListWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
reflectionWindow, &JFJochViewerReflectionListWindow::imageLoaded);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
settingsWindow, &JFJochSettingsWindow::datasetLoaded);
connect(settingsWindow, &JFJochSettingsWindow::spotFindingChanged,
reading_worker, &JFJochImageReadingWorker::UpdateSpotFindingSettings);
connect(settingsWindow, &JFJochSettingsWindow::braggChanged,
reading_worker, &JFJochImageReadingWorker::UpdateBraggIntegrationSettings);
connect(settingsWindow, &JFJochSettingsWindow::scalingChanged,
reading_worker, &JFJochImageReadingWorker::UpdateScalingSettings);
connect(reflectionWindow, &JFJochHelperWindow::zoom, viewer, &JFJochDiffractionImage::centerOnSpot);
connect(spotWindow, &JFJochHelperWindow::zoom, viewer, &JFJochDiffractionImage::centerOnSpot);
connect(side_panel, &JFJochViewerSidePanel::showSpots,
viewer, &JFJochDiffractionImage::showSpots);
connect(side_panel, &JFJochViewerSidePanel::showPredictions,
viewer, &JFJochDiffractionImage::showPredictions);
connect(side_panel, &JFJochViewerSidePanel::showROILabels,
viewer, &JFJochDiffractionImage::showROILabels);
connect(side_panel, &JFJochViewerSidePanel::showROIFill,
viewer, &JFJochDiffractionImage::showROIFill);
connect(side_panel, &JFJochViewerSidePanel::setFeatureColor,
viewer, &JFJochDiffractionImage::setFeatureColor);
connect(side_panel, &JFJochViewerSidePanel::setFeatureColor,
calibrationWindow, &JFJochCalibrationWindow::setFeatureColor);
connect(side_panel, &JFJochViewerSidePanel::setSpotColor,
viewer, &JFJochDiffractionImage::setSpotColor);
connect(side_panel, &JFJochViewerSidePanel::showHighestPixels,
viewer, &JFJochDiffractionImage::showHighestPixels);
connect(side_panel, &JFJochViewerSidePanel::showSaturatedPixels,
viewer, &JFJochDiffractionImage::showSaturation);
connect(viewer, &JFJochDiffractionImage::writeStatusBar,
statusbar, &JFJochViewerStatusBar::display);
connect(side_panel, &JFJochViewerSidePanel::writeStatusBar,
statusbar, &JFJochViewerStatusBar::display);
// Detector connection / broker state / live readouts in the status bar
connect(reading_worker, &JFJochImageReadingWorker::brokerStatusUpdated,
statusbar, &JFJochViewerStatusBar::setBrokerStatus);
connect(reading_worker, &JFJochImageReadingWorker::httpConnectionChanged,
statusbar, &JFJochViewerStatusBar::setHttpConnection);
connect(reading_worker, &JFJochImageReadingWorker::autoloadChanged,
statusbar, &JFJochViewerStatusBar::setAutoloadMode);
connect(reading_worker, &JFJochImageReadingWorker::imageNumberChanged,
statusbar, &JFJochViewerStatusBar::setImageNumber);
connect(reading_worker, &JFJochImageReadingWorker::liveRateChanged,
statusbar, &JFJochViewerStatusBar::setLiveRate);
connect(metadataWindow, &JFJochViewerMetadataWindow::datasetUpdated,
reading_worker, &JFJochImageReadingWorker::UpdateDataset);
connect(reading_worker, &JFJochImageReadingWorker::setRings,
side_panel, &JFJochViewerSidePanel::SetRings);
connect(side_panel, &JFJochViewerSidePanel::resRingsSet,
viewer, &JFJochDiffractionImage::setResolutionRing);
connect(side_panel, &JFJochViewerSidePanel::ringModeSet,
viewer, &JFJochDiffractionImage::setResolutionRingMode);
connect(side_panel, &JFJochViewerSidePanel::highlightIceRings,
viewer, &JFJochDiffractionImage::highlightIceRings);
connect(calibrationWindow, &JFJochCalibrationWindow::loadCalibration,
reading_worker, &JFJochImageReadingWorker::LoadCalibration);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
calibrationWindow, &JFJochCalibrationWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::simpleImageLoaded,
calibrationWindow, &JFJochCalibrationWindow::calibrationLoaded);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::imageLoaded);
connect(reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::loadSpotsRequest,
reading_worker, &JFJochImageReadingWorker::LoadSpots);
connect(reading_worker, &JFJochImageReadingWorker::spotsLoaded,
reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::spotsLoaded);
connect(reciprocalWindow, &JFJochHelperWindow::zoom,
viewer, &JFJochDiffractionImage::centerOnSpot);
connect(side_panel, &JFJochViewerSidePanel::setSpotColor,
reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::setSpotColor);
connect(side_panel, &JFJochViewerSidePanel::setFeatureColor,
reciprocalWindow, &JFJochViewerReciprocalSpaceWindow::setFeatureColor);
connect(settingsWindow, &JFJochSettingsWindow::azintChanged,
reading_worker, &JFJochImageReadingWorker::UpdateAzintSettings);
connect(azintImageWindow, &JFJoch2DAzintImageWindow::zoomOnBin,
viewer, &JFJochDiffractionImage::centerOnSpot);
// --- Magnifier ---
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
magnifierWindow, &JFJochHelperWindow::imageLoaded);
connect(viewer, &JFJochImage::hoverScenePos,
magnifierWindow, &JFJochMagnifierWindow::centerAt);
// Ensure worker is deleted in its own thread when the thread stops
connect(reading_thread, &QThread::finished, reading_worker, &QObject::deleteLater);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded, this,
[this](std::shared_ptr<const JFJochReaderDataset> ds) { lastDataset = std::move(ds); });
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded, this,
[this](std::shared_ptr<const JFJochReaderImage> im) { lastImage = std::move(im); });
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
toolBarDisplay, &JFJochViewerToolbarDisplay::imageLoaded);
connect(reading_worker, &JFJochImageReadingWorker::fileLoadError,
this, &JFJochViewerWindow::OnFileLoadError);
connect(reading_worker, &JFJochImageReadingWorker::fileLoadRetryStatus,
this, &JFJochViewerWindow::OnFileLoadRetryStatus);
connect(menuBar, &JFJochViewerMenu::openDatasetInfo, this, &JFJochViewerWindow::NewDatasetInfo);
NewDatasetInfo();
connect(this, &JFJochViewerWindow::adjustForegroundButton,
viewer, &JFJochDiffractionImage::adjustForeground);
connect(this, &JFJochViewerWindow::setAutoForeground,
viewer, &JFJochDiffractionImage::setAutoForeground);
// Processing jobs: run jfjoch_process locally / copy a cluster command line, and surface
// finished runs as switchable dataset snapshots.
connect(processingJobsWindow, &JFJochProcessingJobsWindow::registerSnapshot,
reading_worker, &JFJochImageReadingWorker::RegisterProcessingSnapshot);
connect(processingJobsWindow, &JFJochProcessingJobsWindow::activateSnapshot,
reading_worker, &JFJochImageReadingWorker::SetActiveSnapshot);
connect(processingJobsWindow, &JFJochProcessingJobsWindow::writeStatusBar,
statusbar, &JFJochViewerStatusBar::display);
connect(processingJobsWindow, &JFJochProcessingJobsWindow::renameRun,
reading_worker, &JFJochImageReadingWorker::RenameRun);
connect(processingJobsWindow, &JFJochProcessingJobsWindow::removeRun,
reading_worker, &JFJochImageReadingWorker::RemoveRun);
connect(reading_worker, &JFJochImageReadingWorker::httpConnectionChanged,
processingJobsWindow, &JFJochProcessingJobsWindow::onHttpConnectionChanged);
connect(reading_worker, &JFJochImageReadingWorker::fileOpened,
processingJobsWindow, &JFJochProcessingJobsWindow::clearJobs);
connect(reading_worker, &JFJochImageReadingWorker::snapshotsChanged, processingJobsWindow,
[pw = processingJobsWindow](QStringList, QString active) { pw->setActiveRun(active); });
connect(reading_worker, &JFJochImageReadingWorker::runsChanged, this,
[this](QVector<RunData> runs, QString active) {
lastRuns = std::move(runs);
lastActiveRunId = std::move(active);
});
// Inline settings dock: the surgical MX/AzInt subset, always visible (left), wired straight
// into the running analysis so edits re-run on the current image / dataset.
auto *settingsPanel = new JFJochViewerSettingsDock(spot_finding_settings, indexing_settings,
experiment.GetAzimuthalIntegrationSettings(), this);
settingsDock = new QDockWidget("Settings", this);
settingsDock->setObjectName("settingsDock");
settingsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
settingsDock->setWidget(settingsPanel);
addDockWidget(Qt::LeftDockWidgetArea, settingsDock);
menuBar->AddDockEntry(settingsDock, "Settings");
connect(settingsPanel, &JFJochViewerSettingsDock::spotFindingChanged,
reading_worker, &JFJochImageReadingWorker::UpdateSpotFindingSettings);
connect(settingsPanel, &JFJochViewerSettingsDock::azintChanged,
reading_worker, &JFJochImageReadingWorker::UpdateAzintSettings);
connect(settingsPanel, &JFJochViewerSettingsDock::experimentChanged,
reading_worker, &JFJochImageReadingWorker::UpdateDataset);
connect(settingsPanel, &JFJochViewerSettingsDock::findBeamCenter,
reading_worker, &JFJochImageReadingWorker::FindCenter);
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
settingsPanel, &JFJochViewerSettingsDock::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
settingsPanel, &JFJochViewerSettingsDock::loadImage);
connect(settingsPanel, &JFJochViewerSettingsDock::ringsFromCalibration,
side_panel, &JFJochViewerSidePanel::SetRings);
// Dock the processing panel in the bottom-right corner, next to the dataset-info plots.
processingDock = new QDockWidget("Processing", this);
processingDock->setObjectName("processingDock");
processingDock->setAllowedAreas(Qt::BottomDockWidgetArea);
processingDock->setFeatures(
QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
processingDock->setWidget(processingJobsWindow);
addDockWidget(Qt::BottomDockWidgetArea, processingDock);
if (lastDatasetInfoDock) {
splitDockWidget(lastDatasetInfoDock, processingDock, Qt::Horizontal);
resizeDocks({lastDatasetInfoDock, processingDock}, {650, 350}, Qt::Horizontal);
// Give the bottom area generous height: the per-dataset plot is a headline feature, so it
// gets real room rather than a thin strip.
resizeDocks({lastDatasetInfoDock, processingDock}, {420, 420}, Qt::Vertical);
}
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);
}
JFJochViewerWindow::~JFJochViewerWindow() {
if (reading_thread && reading_thread->isRunning()) {
reading_thread->quit();
reading_thread->wait();
}
}
void JFJochViewerWindow::ApplyPerspective(Perspective p) {
// Image: just the image + inspector. Processing: also settings, dataset-info plots, jobs panel.
const bool processing = (p == Perspective::Processing);
if (inspectorDock) inspectorDock->setVisible(true);
if (settingsDock) settingsDock->setVisible(processing);
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);
}
void JFJochViewerWindow::LoadImage(qint64 image_number, qint64 summation) {
emit LoadImageRequest(image_number, summation);
}
void JFJochViewerWindow::NewDatasetInfo() {
auto info = new JFJochViewerDatasetInfo(this);
info->datasetLoaded(lastDataset);
info->imageLoaded(lastImage);
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 |
QDockWidget::DockWidgetVerticalTitleBar);
dock->setAttribute(Qt::WA_DeleteOnClose);
dock->setWidget(info);
addDockWidget(Qt::BottomDockWidgetArea, dock);
lastDatasetInfoDock = dock;
// Wire signals like the initial dataset_info
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
info, &JFJochViewerDatasetInfo::datasetLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
info, &JFJochViewerDatasetInfo::imageLoaded);
// All runs (original + reprocessing snapshots) overlay as separate lines.
connect(reading_worker, &JFJochImageReadingWorker::runsChanged,
info, &JFJochViewerDatasetInfo::runsChanged);
// Live processing results: the in-progress run as its own overlay line.
connect(processingJobsWindow, &JFJochProcessingJobsWindow::liveDataset,
info, &JFJochViewerDatasetInfo::liveRunUpdated);
connect(info, &JFJochViewerDatasetInfo::imageSelected,
reading_worker, &JFJochImageReadingWorker::LoadImage);
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged,
info, &JFJochViewerDatasetInfo::setColorMap);
connect(info, &JFJochViewerDatasetInfo::writeStatusBar,
statusbar, &JFJochViewerStatusBar::display);
}
void JFJochViewerWindow::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_F) {
emit adjustForegroundButton(true);
event->accept();
return;
}
if (event->key() == Qt::Key_A && ! event->isAutoRepeat()) {
emit setAutoForeground(true);
event->accept();
return;
}
QMainWindow::keyPressEvent(event);
}
void JFJochViewerWindow::keyReleaseEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_F) {
emit adjustForegroundButton(false);
event->accept();
return;
}
QMainWindow::keyReleaseEvent(event);
}
void JFJochViewerWindow::OnFileLoadError(QString title, QString message) {
QMessageBox::critical(this, title, message);
}
void JFJochViewerWindow::OnFileLoadRetryStatus(bool active, QString message) {
if (active) {
if (!retryDialog) {
retryDialog = new QProgressDialog(this);
retryDialog->setWindowModality(Qt::WindowModal);
retryDialog->setRange(0, 0); // Infinite/Busy indicator
retryDialog->setCancelButton(nullptr); // Disable cancel for now
retryDialog->setMinimumDuration(0); // Show immediately
retryDialog->setWindowTitle("Loading File");
}
retryDialog->setLabelText(message);
retryDialog->show();
} else {
if (retryDialog) {
retryDialog->close();
retryDialog->deleteLater();
retryDialog = nullptr;
}
}
}