diff --git a/viewer/JFJochViewerDatasetInfo.cpp b/viewer/JFJochViewerDatasetInfo.cpp index 4ffed3d36..410f99415 100644 --- a/viewer/JFJochViewerDatasetInfo.cpp +++ b/viewer/JFJochViewerDatasetInfo.cpp @@ -99,6 +99,13 @@ JFJochViewerDatasetInfo::JFJochViewerDatasetInfo(QWidget *parent) : QWidget(pare connect(chart_view2, &JFJochDatasetInfoChartView::imageSelected, this, &JFJochViewerDatasetInfo::imageSelectedInChart); + // An image range picked on either plot is shown on both and handed up, for a processing job. + for (auto *view: {chart_view, chart_view2}) { + connect(view, &JFJochDatasetInfoChartView::rangeSelected, this, &JFJochViewerDatasetInfo::rangeSelected); + connect(view, &JFJochDatasetInfoChartView::processRangeRequested, + this, &JFJochViewerDatasetInfo::processRangeRequested); + } + connect(chart_view2, &JFJochDatasetInfoChartView::writeStatusBar, [this](QString string, int timeout_ms) { emit writeStatusBar(string, timeout_ms); @@ -366,6 +373,11 @@ void JFJochViewerDatasetInfo::imageLoaded(std::shared_ptrloadImage(image); // per-image profile follows the displayed image } +void JFJochViewerDatasetInfo::setSelectedRange(int64_t first, int64_t last) { + chart_view->setSelectedRange(first, last); + chart_view2->setSelectedRange(first, last); +} + void JFJochViewerDatasetInfo::imageSelectedInChart(int64_t number) { emit imageSelected(number, 1); } diff --git a/viewer/JFJochViewerDatasetInfo.h b/viewer/JFJochViewerDatasetInfo.h index 3cedce864..1febb59bf 100644 --- a/viewer/JFJochViewerDatasetInfo.h +++ b/viewer/JFJochViewerDatasetInfo.h @@ -71,6 +71,8 @@ signals: void imageSelected(int64_t number, int64_t summation); void writeStatusBar(QString string, int timeout_ms = 0); void addPlot(); // request another dataset-info plot dock + void rangeSelected(int64_t first, int64_t last); // an image range picked on the plot; -1, -1 = cleared + void processRangeRequested(); public: explicit JFJochViewerDatasetInfo(QWidget *parent = nullptr); @@ -90,6 +92,8 @@ public slots: void setHttpConnection(bool connected, QString addr = QString()); // "Reset all settings to defaults": back to the state a first load of this dataset would show. void resetPlotDefaults(); + // Show an image range picked on this or another plot; -1, -1 clears it. + void setSelectedRange(int64_t first, int64_t last); }; diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index dec091923..555a8c6aa 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -65,7 +65,7 @@ // Dock-layout version for saveState/restoreState: bump whenever the dock set / structure changes so a // stale saved layout (which can restore a dock as a broken, non-responsive floating window) is rejected // and the default layout applies instead. -static constexpr int kLayoutVersion = 5; +static constexpr int kLayoutVersion = 6; JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString &file) : QMainWindow(parent) { image_tail_timer_ = new QTimer(this); @@ -589,20 +589,18 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString settingsDock->setObjectName("settingsDock"); settingsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); settingsDock->setWidget(settingsScroll); // scrollable: don't force the window taller than the screen - addDockWidget(Qt::LeftDockWidgetArea, settingsDock); menuBar->AddDockEntry(settingsDock, "Processing"); - // Files panel: an alternative to the processing panel in the same left-hand slot, tabbed together (the - // canonical Qt way to offer two panels one on top of the other without doubling the width) - most - // beamline users care about opening images, not reprocessing settings, so this one starts on top. + // Files panel: in the same left-hand column as the processing panel, Files on top and Processing + // below with a splitter between them, so both are in view from the start without doubling the width. auto *fileBrowser = new JFJochViewerFileBrowser(this); fileBrowserDock = new QDockWidget("Files", this); fileBrowserDock->setObjectName("fileBrowserDock"); fileBrowserDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); fileBrowserDock->setWidget(fileBrowser); - addDockWidget(Qt::LeftDockWidgetArea, fileBrowserDock); - tabifyDockWidget(settingsDock, fileBrowserDock); - fileBrowserDock->raise(); + addDockWidget(Qt::LeftDockWidgetArea, fileBrowserDock, Qt::Vertical); + addDockWidget(Qt::LeftDockWidgetArea, settingsDock, Qt::Vertical); + resizeDocks({fileBrowserDock, settingsDock}, {350, 650}, Qt::Vertical); menuBar->AddDockEntry(fileBrowserDock, "Files"); connect(fileBrowser, &JFJochViewerFileBrowser::openDataset, reading_worker, &JFJochImageReadingWorker::LoadFile); @@ -620,6 +618,16 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString reading_worker, &JFJochImageReadingWorker::ReanalyzeImages); connect(settingsPanel, &JFJochViewerSettingsDock::analyzeDataset, processingJobsWindow, &JFJochProcessingJobsWindow::newJob); + connect(this, &JFJochViewerWindow::imageRangeSelected, this, [this](int64_t first, int64_t last) { + rangeFirst = first; + rangeLast = last; + }); + connect(this, &JFJochViewerWindow::imageRangeSelected, + processingJobsWindow, &JFJochProcessingJobsWindow::setImageRange); + // A range belongs to the file it was picked on. + connect(reading_worker, &JFJochImageReadingWorker::fileOpened, this, [this] { emit imageRangeSelected(-1, -1); }); + connect(this, &JFJochViewerWindow::processRangeRequested, + settingsPanel, &JFJochViewerSettingsDock::requestAnalyzeDataset); // Grey out "Analyze dataset" on a live HTTP source (re-processing needs a stored file). connect(reading_worker, &JFJochImageReadingWorker::httpConnectionChanged, settingsPanel, &JFJochViewerSettingsDock::setHttpConnection); @@ -720,14 +728,12 @@ JFJochViewerWindow::~JFJochViewerWindow() { } void JFJochViewerWindow::ApplyPerspective(Perspective p) { - // Image: just the image + inspector (+ file manager, on top of its tab with Processing). - // Processing: also dataset-info plots and the jobs panel, with the processing panel raised instead. + // Image: just the image + inspector (+ the file manager and the processing panel). + // Processing: also the dataset-info plots. const bool processing = (p == Perspective::Processing); if (inspectorDock) inspectorDock->setVisible(true); - // Processing and the file manager stay tabbed together either way; only which one is on top changes. if (settingsDock) settingsDock->setVisible(true); if (fileBrowserDock) fileBrowserDock->setVisible(true); - (processing ? settingsDock : fileBrowserDock)->raise(); if (processingDock && !processing) processingDock->hide(); // shows only when a job starts for (auto *d : findChildren()) if (d->objectName().startsWith("datasetInfoDock")) @@ -900,13 +906,13 @@ void JFJochViewerWindow::NewDatasetInfo() { info->datasetLoaded(lastDataset); info->imageLoaded(lastImage); info->runsChanged(lastRuns, lastActiveRunId); + info->setSelectedRange(rangeFirst, rangeLast); 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); + QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); dock->setAttribute(Qt::WA_DeleteOnClose); dock->setWidget(info); addDockWidget(Qt::BottomDockWidgetArea, dock); @@ -932,6 +938,10 @@ void JFJochViewerWindow::NewDatasetInfo() { connect(info, &JFJochViewerDatasetInfo::imageSelected, reading_worker, &JFJochImageReadingWorker::LoadImage); connect(info, &JFJochViewerDatasetInfo::addPlot, this, &JFJochViewerWindow::NewDatasetInfo); + // An image range picked on any plot shows on every plot and becomes the range of the next job. + connect(info, &JFJochViewerDatasetInfo::rangeSelected, this, &JFJochViewerWindow::imageRangeSelected); + connect(this, &JFJochViewerWindow::imageRangeSelected, info, &JFJochViewerDatasetInfo::setSelectedRange); + connect(info, &JFJochViewerDatasetInfo::processRangeRequested, this, &JFJochViewerWindow::processRangeRequested); connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged, info, &JFJochViewerDatasetInfo::setColorMap); connect(info, &JFJochViewerDatasetInfo::writeStatusBar, diff --git a/viewer/JFJochViewerWindow.h b/viewer/JFJochViewerWindow.h index 84d5a49a9..5e16d58dc 100644 --- a/viewer/JFJochViewerWindow.h +++ b/viewer/JFJochViewerWindow.h @@ -53,6 +53,7 @@ private: QProgressDialog *retryDialog = nullptr; QDockWidget *lastDatasetInfoDock = nullptr; // most recent dataset-info dock, for docking layout + int64_t rangeFirst = -1, rangeLast = -1; // image range picked on a plot, for a plot opened later QDockWidget *inspectorDock = nullptr; // image inspector (former right-hand side panel) QScrollArea *inspectorScroll = nullptr; // its scroll area; width bounds follow the font QDockWidget *settingsDock = nullptr; // inline MX/AzInt settings panel @@ -131,5 +132,8 @@ signals: void oneShotAutoForeground(); // Re-emitted by the shortcut filter; the navigation toolbar applies the step, clamped. void stepImage(int steps); + // An image range picked on a plot (-1, -1 = cleared), and a request to process it. + void imageRangeSelected(int64_t first, int64_t last); + void processRangeRequested(); }; diff --git a/viewer/charts/JFJochDatasetInfoChartView.cpp b/viewer/charts/JFJochDatasetInfoChartView.cpp index 5d9a9c9b9..5a3dc3c44 100644 --- a/viewer/charts/JFJochDatasetInfoChartView.cpp +++ b/viewer/charts/JFJochDatasetInfoChartView.cpp @@ -45,6 +45,12 @@ JFJochDatasetInfoChartView::JFJochDatasetInfoChartView(QWidget *parent) m_hoverLoadTimer = new QTimer(this); m_hoverLoadTimer->setSingleShot(true); connect(m_hoverLoadTimer, &QTimer::timeout, this, &JFJochDatasetInfoChartView::onHoverLoadTimeout); + + // The shaded bands and the picked range are drawn in chart coordinates: follow the plot area. + connect(chart(), &QChart::plotAreaChanged, this, [this](const QRectF &) { + placeBands(); + placeSelection(); + }); } @@ -101,54 +107,147 @@ void JFJochDatasetInfoChartView::setImage(int64_t val) { } -void JFJochDatasetInfoChartView::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - if (values.empty()) { - QChartView::mousePressEvent(event); - return; - } +int64_t JFJochDatasetInfoChartView::imageAt(const QPoint &pos) const { + if (!series) + return -1; + const double x = chart()->mapToValue(pos, series).x(); + if (!std::isfinite(x)) + return -1; + const int64_t n = std::max(full_range_, static_cast(values.size())); + return std::clamp(std::llround(x), 0, std::max(n - 1, 0)); +} - const QPointF clickedPoint = event->pos(); - const QPointF chartCoord = chart()->mapToValue(clickedPoint, series); - const double xVal = chartCoord.x(); +void JFJochDatasetInfoChartView::setSelectedRange(int64_t first, int64_t last) { + sel_first_ = std::min(first, last); + sel_last_ = std::max(first, last); + placeSelection(); +} - if (!std::isfinite(xVal) || xVal < 0.0 || - xVal > static_cast(values.size() - 1)) { - QChartView::mousePressEvent(event); +void JFJochDatasetInfoChartView::placeSelection() { + if (sel_first_ < 0 || !series || m_showFFT) { + if (selection_item_) + selection_item_->hide(); + return; + } + if (!selection_item_) { + selection_item_ = new QGraphicsRectItem(chart()); + selection_item_->setZValue(1.6); // above the disposition bands, below the grid and the curves + selection_item_->setPen(Qt::NoPen); + } + // A clear light blue - the navy accent at low alpha read as grey over the light background + selection_item_->setBrush(DarkTheme() ? QColor(90, 150, 230, 70) : QColor(60, 140, 240, 55)); + const QRectF plot = chart()->plotArea(); + const double x0 = std::max(plot.left(), chart()->mapToPosition(QPointF(sel_first_ - 0.5, 0), series).x()); + const double x1 = std::min(plot.right(), chart()->mapToPosition(QPointF(sel_last_ + 0.5, 0), series).x()); + selection_item_->setRect(QRectF(x0, plot.top(), std::max(0.0, x1 - x0), plot.height())); + selection_item_->show(); +} + +void JFJochDatasetInfoChartView::fillBands(const JFJochReaderDataset *dataset) { + bands_.clear(); + if (dataset == nullptr) + return; + const auto &disposition = dataset->frame_disposition; + const auto &codes = dataset->frame_disposition_codes; + // 0 = merged, 1 = downgraded, 2 = rejected + auto kind = [&codes](uint8_t code) { + if (code >= codes.size()) return 0; + if (codes[code] == "rejected") return 2; + if (codes[code] == "downgraded") return 1; + return 0; + }; + auto xpos = [this](size_t i) { + return i < primary_x_.size() ? static_cast(primary_x_[i]) : static_cast(i); + }; + for (size_t i = 0; i < disposition.size();) { + const int k = kind(disposition[i]); + size_t j = i + 1; + while (j < disposition.size() && kind(disposition[j]) == k) + j++; + if (k > 0) + bands_.push_back({xpos(i), xpos(j - 1), k == 2}); + i = j; + } +} + +void JFJochDatasetInfoChartView::placeBands() { + if (!series) + return; + const QRectF plot = chart()->plotArea(); + for (size_t i = 0; i < band_items_.size() && i < bands_.size(); i++) { + const double x0 = std::max(plot.left(), chart()->mapToPosition(QPointF(bands_[i].first - 0.5, 0), series).x()); + const double x1 = std::min(plot.right(), chart()->mapToPosition(QPointF(bands_[i].last + 0.5, 0), series).x()); + band_items_[i]->setRect(QRectF(x0, plot.top(), std::max(0.0, x1 - x0), plot.height())); + } +} + +QString JFJochDatasetInfoChartView::bandText(int64_t image) const { + for (const auto &b: bands_) + if (image >= b.first - 0.5 && image <= b.last + 0.5) + return b.rejected ? QStringLiteral(" (rejected from the merge)") + : QStringLiteral(" (down-weighted in the merge)"); + return {}; +} + +void JFJochDatasetInfoChartView::mousePressEvent(QMouseEvent *event) { + // The left button picks an image on release; a drag in between picks a range of images instead. + if (event->button() == Qt::LeftButton && series && !m_showFFT && !values.empty()) { + drag_anchor_pos_ = event->pos(); + drag_anchor_x_ = static_cast(imageAt(event->pos())); + dragging_ = false; + } + QChartView::mousePressEvent(event); +} + +void JFJochDatasetInfoChartView::mouseReleaseEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton && std::isfinite(drag_anchor_x_)) { + drag_anchor_x_ = NAN; + if (dragging_) { + dragging_ = false; + emit rangeSelected(sel_first_, sel_last_); + emit writeStatusBar(QStringLiteral("Images %1-%2 selected - right-click to process them") + .arg(sel_first_).arg(sel_last_), 6000); + } else + selectImageAt(event->pos()); + } + QChartView::mouseReleaseEvent(event); +} + +void JFJochDatasetInfoChartView::selectImageAt(const QPoint &pos) { + const QPointF chartCoord = chart()->mapToValue(pos, series); + const double xVal = chartCoord.x(); + + if (!std::isfinite(xVal) || xVal < 0.0 || + xVal > static_cast(values.size() - 1)) + return; + + int64_t selectedIdx = 0; + + if (binning <= 1) { + // Original behavior: pick nearest frame index + selectedIdx = std::lround(xVal); + } else { + // Binned mode: pick bin index from x, then representative frame + const int64_t nBins = + static_cast(values.size()) / binning; + if (nBins <= 0) return; - } - - int64_t selectedIdx = 0; - - if (binning <= 1) { - // Original behavior: pick nearest frame index - selectedIdx = std::lround(xVal); - } else { - // Binned mode: pick bin index from x, then representative frame - const int64_t nBins = - static_cast(values.size()) / binning; - if (nBins <= 0) { - QChartView::mousePressEvent(event); - return; - } - - int64_t binIdx = - static_cast(std::floor(xVal / static_cast(binning))); - binIdx = std::clamp(binIdx, 0, nBins - 1); - - int64_t centerIdx = binIdx * binning + binning / 2; - if (centerIdx >= static_cast(values.size())) - centerIdx = static_cast(values.size()) - 1; - - selectedIdx = centerIdx; - } - - if (selectedIdx >= 0 && - selectedIdx < static_cast(values.size())) { - emit imageSelected(selectedIdx); - } + + int64_t binIdx = + static_cast(std::floor(xVal / static_cast(binning))); + binIdx = std::clamp(binIdx, 0, nBins - 1); + + int64_t centerIdx = binIdx * binning + binning / 2; + if (centerIdx >= static_cast(values.size())) + centerIdx = static_cast(values.size()) - 1; + + selectedIdx = centerIdx; + } + + if (selectedIdx >= 0 && + selectedIdx < static_cast(values.size())) { + emit imageSelected(selectedIdx); } - QChartView::mousePressEvent(event); // Call the base implementation } void JFJochDatasetInfoChartView::resetZoom() { @@ -190,6 +289,8 @@ void JFJochDatasetInfoChartView::loadValues(const std::vector &input, int image_time_s = {}; } + fillBands(dataset); + curr_image = image; updateChart(); } @@ -255,6 +356,10 @@ void JFJochDatasetInfoChartView::updateChart() { m_hoverLineHorizontal = nullptr; } + for (auto *item: band_items_) + delete item; + band_items_.clear(); + #ifdef JFJOCH_USE_FFTW if (m_showFFT) buildFFTChart(); @@ -264,6 +369,20 @@ void JFJochDatasetInfoChartView::updateChart() { buildTimeDomainChart(); #endif + // Light red behind the images the merge rejected, light yellow behind the ones it down-weighted. + // Above the plot background, below the grid and the curves. + if (series && !m_showFFT) { + for (const auto &band: bands_) { + auto *item = new QGraphicsRectItem(chart()); + item->setZValue(1.5); + item->setPen(Qt::NoPen); + item->setBrush(band.rejected ? QColor(230, 40, 40, 45) : QColor(240, 190, 0, 60)); + band_items_.push_back(item); + } + } + placeBands(); + placeSelection(); + // Qt Charts elides any axis label that does not fit its allotted box down to "..." (the default // since 6.2). In a dock this narrow that is every label, so show them in full instead. for (auto *axis: chart()->axes()) @@ -714,8 +833,22 @@ void JFJochDatasetInfoChartView::contextMenuEvent(QContextMenuEvent *event) { actShowFFT->setEnabled(!values.empty() && image_time_s > 0.0); #endif + menu.addSeparator(); + const bool has_range = sel_first_ >= 0; + QAction *actProcess = menu.addAction(has_range + ? QStringLiteral("Process images %1-%2...").arg(sel_first_).arg(sel_last_) + : QStringLiteral("Process images... (drag over the plot to pick a range)")); + actProcess->setEnabled(has_range); + QAction *actClearRange = menu.addAction("Clear image range"); + actClearRange->setEnabled(has_range); + QAction *chosen = menu.exec(event->globalPos()); - if (chosen == copyXY) { + if (chosen == actProcess) { + emit processRangeRequested(); + } else if (chosen == actClearRange) { + setSelectedRange(-1, -1); + emit rangeSelected(-1, -1); + } else if (chosen == copyXY) { QString out; out.reserve(static_cast(values.size() * 16)); // rough prealloc for (size_t i = 0; i < values.size(); ++i) { @@ -753,6 +886,17 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) { if (!series || values.empty()) return; + if (std::isfinite(drag_anchor_x_) && (event->buttons() & Qt::LeftButton)) { + if (!dragging_ && std::abs(event->pos().x() - drag_anchor_pos_.x()) >= QApplication::startDragDistance()) + dragging_ = true; + if (dragging_) { + setSelectedRange(static_cast(drag_anchor_x_), imageAt(event->pos())); + emit writeStatusBar(QStringLiteral("Images %1-%2 (%3 images)") + .arg(sel_first_).arg(sel_last_).arg(sel_last_ - sel_first_ + 1), 6000); + return; + } + } + #ifdef JFJOCH_USE_FFTW if (m_showFFT && !m_fftFrequenciesHz.empty()) { // FFT mode: x is frequency in Hz @@ -909,6 +1053,7 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) { text = QString("image = %1, no value").arg(idx); } } + text += bandText(idx); emit writeStatusBar(text, 6000); // Debounced image load on hover when Shift is pressed diff --git a/viewer/charts/JFJochDatasetInfoChartView.h b/viewer/charts/JFJochDatasetInfoChartView.h index 798441c5f..5c1c82794 100644 --- a/viewer/charts/JFJochDatasetInfoChartView.h +++ b/viewer/charts/JFJochDatasetInfoChartView.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -63,6 +64,27 @@ class JFJochDatasetInfoChartView : public QChartView { // already within a few pixels of the curve. A non-finite snap_to_y never snaps. void setHoverLineY(double y, double snap_to_y); + // Images the merge rejected or down-weighted (the run's frame disposition), as runs of consecutive + // images in x, shaded behind the curve. + struct DispositionBand { double first; double last; bool rejected; }; + std::vector bands_; + std::vector band_items_; + void fillBands(const JFJochReaderDataset *dataset); + void placeBands(); + QString bandText(int64_t image) const; + + // An image range picked by dragging over the plot, for processing; -1 = none. The drag anchor is + // where the left button went down (NaN = no drag), so a press-and-release in place stays a click. + int64_t sel_first_ = -1; + int64_t sel_last_ = -1; + double drag_anchor_x_ = NAN; + QPoint drag_anchor_pos_; + bool dragging_ = false; + QGraphicsRectItem *selection_item_ = nullptr; + int64_t imageAt(const QPoint &pos) const; // clamped to the dataset; -1 off the plot + void selectImageAt(const QPoint &pos); // a click: the image (or bin) under it + void placeSelection(); + std::optional goniometer_axis; std::optional image_time_s; @@ -96,6 +118,9 @@ private: signals: void imageSelected(int64_t number); + // A range of images [first, last] picked on the plot (-1, -1 = cleared), and a request to process it. + void rangeSelected(int64_t first, int64_t last); + void processRangeRequested(); void writeStatusBar(QString string, int timeout_ms = 0); private slots: @@ -106,6 +131,7 @@ private: void changeEvent(QEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; @@ -124,6 +150,8 @@ public: public slots: void resetZoom(); + // Shows a range picked here or on another plot; -1, -1 clears it. + void setSelectedRange(int64_t first, int64_t last); public: // Load the primary (active) run's values plus any overlay runs. one_over_d2 applies the same diff --git a/viewer/widgets/JFJochViewerSettingsDock.cpp b/viewer/widgets/JFJochViewerSettingsDock.cpp index 0b66f75ca..2b0364779 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.cpp +++ b/viewer/widgets/JFJochViewerSettingsDock.cpp @@ -94,8 +94,7 @@ JFJochViewerSettingsDock::JFJochViewerSettingsDock(const SpotFindingSettings &sp layout->addLayout(analyzeRow); layout->addSpacing(10); connect(analyzeImageBtn, &QPushButton::toggled, this, &JFJochViewerSettingsDock::reanalyzeImage); - connect(analyzeDatasetBtn_, &QPushButton::clicked, this, - [this] { emit analyzeDataset(mode_, powder_->Selection()); }); + connect(analyzeDatasetBtn_, &QPushButton::clicked, this, &JFJochViewerSettingsDock::requestAnalyzeDataset); // Segmented MX / AzInt / Calib toggle: each community picks its page; pages never share a screen. // The page is also the mode "Analyze dataset" runs, so there is one control, not two. @@ -1013,6 +1012,11 @@ void JFJochViewerSettingsDock::UpdateSpaceGroupName() { } } +void JFJochViewerSettingsDock::requestAnalyzeDataset() { + if (analyzeDatasetBtn_->isEnabled()) + emit analyzeDataset(mode_, powder_->Selection()); +} + void JFJochViewerSettingsDock::datasetLoaded(std::shared_ptr dataset) { if (!dataset) return; diff --git a/viewer/widgets/JFJochViewerSettingsDock.h b/viewer/widgets/JFJochViewerSettingsDock.h index 3bed5212f..0bd494d0f 100644 --- a/viewer/widgets/JFJochViewerSettingsDock.h +++ b/viewer/widgets/JFJochViewerSettingsDock.h @@ -41,6 +41,8 @@ public: const ScalingSettings &scaling, QWidget *parent = nullptr); public slots: + // "Analyze dataset", as if the button were pressed (asked for from a plot's image range). + void requestAnalyzeDataset(); void datasetLoaded(std::shared_ptr dataset); // A new file, or a run's results shown: the crystal they store is shown but not used until "Known // unit cell" is ticked, as a bare `rugnux` forgets it (ClearStoredCrystal). diff --git a/viewer/windows/JFJochProcessingJobsWindow.cpp b/viewer/windows/JFJochProcessingJobsWindow.cpp index 369710075..1a1a1262c 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.cpp +++ b/viewer/windows/JFJochProcessingJobsWindow.cpp @@ -153,13 +153,15 @@ int JFJochProcessingJobsWindow::askJob(const ReprocessingInputs &inputs, JobSpec dlg.setWindowTitle(calibration ? "New detector-calibration job" : azint ? "New azimuthal-integration job" : "New full-analysis job"); + // A range picked on the plot is inclusive; end_image is one past the last image. + const bool has_range = range_first_ >= 0; auto *start_image = new QSpinBox(&dlg); start_image->setRange(0, 1'000'000'000); - start_image->setValue(0); + start_image->setValue(has_range ? static_cast(range_first_) : 0); auto *end_image = new QSpinBox(&dlg); end_image->setRange(0, 1'000'000'000); - end_image->setValue(0); + end_image->setValue(has_range ? static_cast(range_last_ + 1) : 0); end_image->setSpecialValueText("end"); // 0 => to the last image auto *threads = new QSpinBox(&dlg); @@ -402,6 +404,11 @@ ProcessConfig JFJochProcessingJobsWindow::buildConfig(const JobSpec &spec, const return config; } +void JFJochProcessingJobsWindow::setImageRange(int64_t first, int64_t last) { + range_first_ = first; + range_last_ = last; +} + void JFJochProcessingJobsWindow::newJob(ProcessMode mode, CalibrationSelection calibration) { const ReprocessingInputs inputs = worker_->GetReprocessingInputs(); if (!inputs.valid) { diff --git a/viewer/windows/JFJochProcessingJobsWindow.h b/viewer/windows/JFJochProcessingJobsWindow.h index 1b529803e..e602194ef 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.h +++ b/viewer/windows/JFJochProcessingJobsWindow.h @@ -48,6 +48,7 @@ public slots: // The panel's "Analyze dataset" action; the mode is the panel's selected page. The calibrant // selection is only used by ProcessMode::Calibration. void newJob(ProcessMode mode = ProcessMode::FullAnalysis, CalibrationSelection calibration = {}); + void setImageRange(int64_t first, int64_t last); private slots: void cancelJob(); @@ -120,5 +121,7 @@ private: QElapsedTimer job_timer_; // running job wall-clock, for rate + ETA int running_row_ = -1; int job_counter_ = 0; + // The image range picked on a plot, offered as the next job's start/end image; -1 = none. + int64_t range_first_ = -1, range_last_ = -1; std::vector jobs_; };