jfjoch_viewer: navigation keys, one-shot auto-contrast, file manager panel, cleanup

* Dataset-info plot hover shows a horizontal crosshair alongside the existing
  vertical one, so a run of hovers reads as a level or a drift at a glance.
* `A` applies auto-contrast once instead of switching on persistent Auto mode
  (a new `oneShotAutoForeground()`, distinct from the toolbar's Auto toggle);
  pressing it a second time on the same image switches Auto on for good, and
  pressing it while Auto is already on leaves it on. The value itself comes
  from one `AutoForegroundValue()` shared with the continuous Auto path.
* `Home`/`End`/`Page Up`/`Page Down` navigate the dataset (first/last image,
  one image forward/back); claimed in the diffraction view's keyPressEvent
  before QGraphicsView's default handling, which otherwise eats them to
  scroll the viewport.
* Alt+wheel dataset navigation is removed - some window managers already
  took it before the app ever saw it, per the caveat that used to sit next
  to its entry in the shortcuts list.
* The image strip dock and its dedicated thumbnail-rendering machinery in
  the reading worker (SetThumbnail*, RenderThumbnail_i) are removed; it cost
  a lot and nothing else used any of it.
* Toolbar text: "Colour" -> "Color".
* Color map, Auto and HDR mode now persist across sessions, the same way the
  window layout already does.
* The Inspector's "Dataset:" path wraps at '/' (a zero-width space after
  each one) instead of being cut off when it doesn't fit one line.
* LoadFile no longer reopens an already-open file - it forwards straight to
  LoadImage instead - which was the likely cause of a D-Bus client's per-image
  navigation lagging behind the same navigation done via the grid-scan hover.
* New file-manager dock (left, tabbed with Settings via tabifyDockWidget):
  a directory tree filtered to *_master.h5/*_process.h5, rooted at
  JUNGFRAUJOCH_DATA_ROOT if set, else the last-browsed root, else the home
  directory. Most beamline users care about opening images, not
  reprocessing settings, so it's the tab raised by default. Its filter box
  narrows files only - a directory always passes - because filtering the
  directories too hid the matching files under any directory whose own name
  did not match, and hid the root's ancestors, which left the tree with no
  valid root index until the root was set again by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PogHY7bWXV4bpctDPdyuDN
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
leonarski_fandClaude Sonnet 5 committed 2026-09-11 18:45:20 +02:00
1 parent 40dd6f25fd
commit d24cdfcff4
20 files changed
+375 -462

No files matched your search

+60 -7
View File
@@ -20,6 +20,7 @@
#include <QScrollBar>
#include <QMenu>
#include <cmath>
#include <limits>
#include <QMouseEvent>
#include "JFJochSimpleImage.h"
@@ -840,6 +841,31 @@ void JFJochDiffractionImage::roiScratchDrawn() {
}
void JFJochDiffractionImage::keyPressEvent(QKeyEvent *event) {
// QGraphicsView would otherwise consume these to scroll the viewport; claim them first so they
// navigate the dataset instead, via the same stepImage() the navigation toolbar already listens
// to. Home/End step by an effectively infinite amount, which stepImage's clamp turns into
// "jump to the first/last image".
switch (event->key()) {
case Qt::Key_Home:
emit stepImage(std::numeric_limits<int>::min());
event->accept();
return;
case Qt::Key_End:
emit stepImage(std::numeric_limits<int>::max());
event->accept();
return;
case Qt::Key_PageUp:
emit stepImage(1);
event->accept();
return;
case Qt::Key_PageDown:
emit stepImage(-1);
event->accept();
return;
default:
break;
}
if (event->key() == Qt::Key_Delete && image && !selected_roi_.isEmpty()) {
ROIDefinition rois = image->Dataset().experiment.ROI().GetROIDefinition();
const std::string sel = selected_roi_.toStdString();
@@ -859,16 +885,22 @@ void JFJochDiffractionImage::keyPressEvent(QKeyEvent *event) {
}
std::optional<float> JFJochDiffractionImage::AutoForegroundValue() const {
if (!image)
return {};
if (!hdr_mode)
return static_cast<float>(image->GetAutoContrastValue());
const auto val_range = image->ValidMinMax();
if (!val_range.has_value())
return {};
return static_cast<float>(val_range->second);
}
void JFJochDiffractionImage::UpdateForeground() {
if (!image || !auto_fg)
return;
if (hdr_mode) {
const auto val_range = image->ValidMinMax();
if (val_range.has_value())
foreground = val_range->second;
} else {
foreground = image->GetAutoContrastValue();
}
if (const auto val = AutoForegroundValue())
foreground = *val;
emit foregroundChanged(foreground);
}
@@ -882,6 +914,7 @@ void JFJochDiffractionImage::setHDRMode(bool input) {
void JFJochDiffractionImage::loadImage(std::shared_ptr<const JFJochReaderImage> in_image) {
live_pending_ = false; // a live ROI edit (if any) has now been recomputed
ring_cache_key_.clear(); // geometry may differ, re-trace the resolution rings
one_shot_auto_ = false; // a new image has its own auto value: `A` is a one-shot again
if (in_image) {
image = in_image;
UpdateForeground();
@@ -902,6 +935,7 @@ void JFJochDiffractionImage::loadImage(std::shared_ptr<const JFJochReaderImage>
void JFJochDiffractionImage::setAutoForeground(bool input) {
auto_fg = input;
one_shot_auto_ = false; // whatever `A` did before, the next press starts as a one-shot again
// If auto_foreground is not set, then view stays with the current settings till these are explicitly changed
UpdateForeground();
RenderImage();
@@ -909,6 +943,25 @@ void JFJochDiffractionImage::setAutoForeground(bool input) {
emit autoForegroundChanged(auto_fg);
}
void JFJochDiffractionImage::oneShotAutoForeground() {
if (auto_fg)
return; // Auto already follows every image: there is nothing to apply, and nothing to undo
if (one_shot_auto_) {
setAutoForeground(true); // second press on the same image: keep it on from now on
return;
}
const auto val = AutoForegroundValue();
if (!val)
return;
// Unlike a manual foreground change this leaves auto_fg alone (it is off here either way).
foreground = *val;
one_shot_auto_ = true;
ScheduleRenderImage();
emit foregroundChanged(foreground);
}
void JFJochDiffractionImage::setResolutionRing(QVector<float> v) {
res_ring = v;
ring_mode = RingMode::Manual;