// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "JFJochViewerWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #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 namespace { // Paired "reanalyze" glyphs: a single diffraction frame (image) vs a stack of frames (dataset), // drawn white so they read on the navy hero buttons. QIcon FramesIcon(int frames) { const int S = 28; QPixmap pm(S, S); pm.fill(Qt::transparent); QPainter p(&pm); p.setRenderHint(QPainter::Antialiasing); p.setPen(QPen(Qt::white, 2.0)); p.setBrush(Qt::NoBrush); const double side = 13.0, step = 4.0; for (int i = frames - 1; i >= 0; --i) p.drawRoundedRect(QRectF(4 + i * step, 4 + i * step, side, side), 2.5, 2.5); p.setBrush(Qt::white); // a diffraction "spot" in the front frame p.drawEllipse(QPointF(4 + side / 2.0, 4 + side / 2.0), 2.0, 2.0); p.end(); return QIcon(pm); } } 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); // Two hero actions — the capabilities that set Jungfraujoch apart — promoted as prominent navy // buttons at the right of the display toolbar: re-analyse the current image, or the whole dataset. { const QString heroStyle = "QPushButton { background-color:#1F3A5F; color:white; font-weight:bold;" " padding:3px 10px; border:none; border-radius:3px; }" " QPushButton:hover { background-color:#16314f; }" " QPushButton:disabled { background-color:#9aa6b3; }"; auto *reanalyzeImageBtn = new QPushButton(FramesIcon(1), " Reanalyze image", this); reanalyzeImageBtn->setStyleSheet(heroStyle); reanalyzeImageBtn->setToolTip("Re-run the analysis pipeline on the current image"); auto *reanalyzeDatasetBtn = new QPushButton(FramesIcon(3), " Reanalyze dataset", this); reanalyzeDatasetBtn->setStyleSheet(heroStyle); reanalyzeDatasetBtn->setToolTip("Re-process the whole dataset (opens a new processing job)"); toolBarDisplay->addWidget(reanalyzeImageBtn); toolBarDisplay->addWidget(reanalyzeDatasetBtn); connect(reanalyzeImageBtn, &QPushButton::clicked, reading_worker, &JFJochImageReadingWorker::Analyze); connect(reanalyzeDatasetBtn, &QPushButton::clicked, processingJobsWindow, &JFJochProcessingJobsWindow::newJob); } 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 ds) { lastDataset = std::move(ds); }); connect(reading_worker, &JFJochImageReadingWorker::imageLoaded, this, [this](std::shared_ptr 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 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()) 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; } } }