From d9dfd3641371f05e9dbf03681331d7f09a59a91a Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Fri, 19 Jun 2026 13:33:04 +0200 Subject: [PATCH] viewer: click to select an ROI, with live move and a hand cursor Clicking a box/circle on the image now selects it (syncing the side-panel combobox) and grabs it for moving, with a closed-hand cursor during the drag. The move recomputes ROI statistics live rather than only on release, throttled to at most one in-flight recompute so the worker is not flooded. Co-Authored-By: Claude Opus 4.8 --- viewer/JFJochViewerSidePanel.cpp | 4 ++ viewer/JFJochViewerSidePanel.h | 1 + viewer/JFJochViewerWindow.cpp | 2 + .../image_viewer/JFJochDiffractionImage.cpp | 57 ++++++++++++------- viewer/image_viewer/JFJochDiffractionImage.h | 3 + viewer/widgets/JFJochViewerROIList.cpp | 6 ++ viewer/widgets/JFJochViewerROIList.h | 1 + 7 files changed, 54 insertions(+), 20 deletions(-) diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index c78fec8a..b1f61618 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -189,6 +189,10 @@ void JFJochViewerSidePanel::highlightIceRingsToggled(bool input) { emit highlightIceRings(input); } +void JFJochViewerSidePanel::selectROIInList(QString name) { + roi_list->setSelected(name); +} + void JFJochViewerSidePanel::SetROIBox(QRect box) { roi->SetROIBox(box); } diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 0af1f790..8d95f257 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -56,6 +56,7 @@ public slots: void SetROICircle(double x, double y, double radius); void SetROIResult(ROIMessage msg); void SetRings(QVector v); + void selectROIInList(QString name); private slots: void spotsToggled(bool input); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 57215cd6..0e3da6a2 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -210,6 +210,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString viewer, &JFJochDiffractionImage::setSelectedROI); connect(viewer, &JFJochDiffractionImage::roiGeometryEdited, reading_worker, &JFJochImageReadingWorker::SetROIDefinition); + connect(viewer, &JFJochDiffractionImage::roiSelected, + side_panel, &JFJochViewerSidePanel::selectROIInList); connect(side_panel, &JFJochViewerSidePanel::ROIBoxConfigured, reading_worker, &JFJochImageReadingWorker::SetROIBox); diff --git a/viewer/image_viewer/JFJochDiffractionImage.cpp b/viewer/image_viewer/JFJochDiffractionImage.cpp index ec1165cb..6414de8f 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.cpp +++ b/viewer/image_viewer/JFJochDiffractionImage.cpp @@ -466,35 +466,39 @@ void JFJochDiffractionImage::setSelectedROI(QString name) { } bool JFJochDiffractionImage::roiEditPress(const QPointF &scenePos) { - if (!image || selected_roi_.isEmpty()) + if (!image) return false; const auto &rois = image->Dataset().experiment.ROI().GetROIDefinition(); - const std::string sel = selected_roi_.toStdString(); - for (const auto &b : rois.boxes) - if (b.GetName() == sel) { - const QRectF r(QPointF(b.GetXMin(), b.GetYMin()), QPointF(b.GetXMax(), b.GetYMax())); - if (!r.contains(scenePos)) - return false; + // Pick the first box/circle under the cursor, so an ROI can be grabbed by clicking it. + for (const auto &b : rois.boxes) { + const QRectF r(QPointF(b.GetXMin(), b.GetYMin()), QPointF(b.GetXMax(), b.GetYMax())); + if (r.contains(scenePos)) { roi_edit_ = RoiEdit::MoveBox; - edit_name_ = selected_roi_; + edit_name_ = QString::fromStdString(b.GetName()); edit_box_ = r; move_last_ = scenePos; + selected_roi_ = edit_name_; + emit roiSelected(edit_name_); + setCursor(Qt::ClosedHandCursor); return true; } - for (const auto &c : rois.circles) - if (c.GetName() == sel) { - const QPointF center(c.GetX(), c.GetY()); - if (QLineF(center, scenePos).length() > c.GetRadius_pxl()) - return false; + } + for (const auto &c : rois.circles) { + const QPointF center(c.GetX(), c.GetY()); + if (QLineF(center, scenePos).length() <= c.GetRadius_pxl()) { roi_edit_ = RoiEdit::MoveCircle; - edit_name_ = selected_roi_; + edit_name_ = QString::fromStdString(c.GetName()); edit_center_ = center; edit_radius_ = c.GetRadius_pxl(); move_last_ = scenePos; + selected_roi_ = edit_name_; + emit roiSelected(edit_name_); + setCursor(Qt::ClosedHandCursor); return true; } + } return false; // azimuthal editing is not yet supported } @@ -506,15 +510,28 @@ void JFJochDiffractionImage::roiEditMove(const QPointF &scenePos) { else if (roi_edit_ == RoiEdit::MoveCircle) edit_center_ += delta; updateOverlay(); + + // Live recompute, but keep at most one in flight (cleared in loadImage) so the + // worker is not flooded with edits faster than it can recompute them. + if (!live_pending_) { + live_pending_ = true; + emit roiGeometryEdited(BuildEditedROIDefinition()); + } } void JFJochDiffractionImage::roiEditRelease() { - if (roi_edit_ == RoiEdit::None || !image) { - roi_edit_ = RoiEdit::None; + if (roi_edit_ == RoiEdit::None) return; - } + const ROIDefinition rois = BuildEditedROIDefinition(); + roi_edit_ = RoiEdit::None; + setCursor(Qt::ArrowCursor); + emit roiGeometryEdited(rois); // final, exact position +} - ROIDefinition rois = image->Dataset().experiment.ROI().GetROIDefinition(); +ROIDefinition JFJochDiffractionImage::BuildEditedROIDefinition() const { + ROIDefinition rois; + if (image) + rois = image->Dataset().experiment.ROI().GetROIDefinition(); const std::string sel = edit_name_.toStdString(); if (roi_edit_ == RoiEdit::MoveBox) { for (auto &b : rois.boxes) @@ -530,8 +547,7 @@ void JFJochDiffractionImage::roiEditRelease() { break; } } - roi_edit_ = RoiEdit::None; - emit roiGeometryEdited(rois); + return rois; } @@ -556,6 +572,7 @@ void JFJochDiffractionImage::setHDRMode(bool input) { } void JFJochDiffractionImage::loadImage(std::shared_ptr in_image) { + live_pending_ = false; // a live ROI edit (if any) has now been recomputed if (in_image) { image = in_image; UpdateForeground(); diff --git a/viewer/image_viewer/JFJochDiffractionImage.h b/viewer/image_viewer/JFJochDiffractionImage.h index dc068b00..ab00c8ea 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.h +++ b/viewer/image_viewer/JFJochDiffractionImage.h @@ -37,6 +37,7 @@ private: bool roiEditPress(const QPointF &scenePos) override; void roiEditMove(const QPointF &scenePos) override; void roiEditRelease() override; + [[nodiscard]] ROIDefinition BuildEditedROIDefinition() const; // current ROIs with the edit applied QString selected_roi_; enum class RoiEdit { None, MoveBox, MoveCircle }; @@ -46,6 +47,7 @@ private: QPointF edit_center_; double edit_radius_ = 0; QPointF move_last_; + bool live_pending_ = false; // a live edit is being recomputed; throttles emissions void DrawSpots(); void DrawPredictions(); @@ -81,6 +83,7 @@ private: signals: void roiGeometryEdited(ROIDefinition rois); + void roiSelected(QString name); // user picked an ROI by clicking it on the image public slots: void setSelectedROI(QString name); void loadImage(std::shared_ptr image); diff --git a/viewer/widgets/JFJochViewerROIList.cpp b/viewer/widgets/JFJochViewerROIList.cpp index 250da1b8..efaa8b13 100644 --- a/viewer/widgets/JFJochViewerROIList.cpp +++ b/viewer/widgets/JFJochViewerROIList.cpp @@ -69,6 +69,12 @@ QString JFJochViewerROIList::SelectedName() const { return combo_->currentData().toString(); } +void JFJochViewerROIList::setSelected(QString name) { + const int idx = combo_->findData(name); + if (idx >= 0 && idx != combo_->currentIndex()) + combo_->setCurrentIndex(idx); // updates the stats and selection highlight +} + void JFJochViewerROIList::ShowSelectedResult() { const QString name = SelectedName(); if (image_ && !name.isEmpty()) { diff --git a/viewer/widgets/JFJochViewerROIList.h b/viewer/widgets/JFJochViewerROIList.h index b8332062..f3b0650a 100644 --- a/viewer/widgets/JFJochViewerROIList.h +++ b/viewer/widgets/JFJochViewerROIList.h @@ -35,6 +35,7 @@ public: explicit JFJochViewerROIList(QWidget *parent = nullptr); public slots: void loadImage(std::shared_ptr image); + void setSelected(QString name); // select an ROI by name (e.g. clicked on the canvas) private slots: void OnSelectionChanged(); void OnAdd();