viewer: the Help menu says what the mouse does and who is credited, and a spot can be hidden by what it is
The mouse bindings were only in the code and in one bullet of the viewer page: the wheel and its four modifiers, the two ROI drags, and the hover and double-click behaviour of the grid scan, the plots and the auxiliary views. They are now a window under Help, and the same table is a section of docs/JFJOCH_VIEWER.md. docs/ACKNOWLEDGEMENT.md is shipped in the viewer's resources and shown under Help beside the third-party licenses, so the credit travels with the binary rather than only with the docs, and it ends with a declaration of how generative AI was used in developing this code. The image overlay draws every spot it has, which makes a frame with ice or with a large unindexed population hard to read. The Inspector can now leave out the spots that were not assigned to a lattice, and the spots that fall on an ice ring, independently of each other and of the ice-ring highlight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdFBP5TjGsJ5Kvy2H8P9L9
This commit is contained in:
@@ -335,3 +335,10 @@ as in both that procedure and REFMAC5: G. N. Murshudov, P. Skubak, A. A. Lebedev
|
||||
R. A. Steiner, R. A. Nicholls, M. D. Winn, F. Long and A. A. Vagin, "REFMAC5 for the refinement of
|
||||
macromolecular crystal structures" (2011), Acta Cryst. D67, 355-367
|
||||
[doi:10.1107/S0907444911001314](https://doi.org/10.1107/S0907444911001314).
|
||||
|
||||
## Generative AI usage declaration
|
||||
|
||||
Large language models were used extensively in developing this code. Jungfraujoch development was
|
||||
supported with JetBrains AI (mostly GPT models) to refactor and verify particular code fragments.
|
||||
rugnux was developed with the assistance of Claude Code (mostly the Opus model). This documentation
|
||||
was written with the assistance of Claude Opus and Fable models.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
* `rugnux --mode scale` reports the detector tilt and direct beam of the geometry it re-scaled at, instead of zeros that read as a flat detector, and no longer warns that no image was indexed on a run whose lattice came from its input file.
|
||||
* Every rotation run that determined a space group and merged reports what the mounting cost: `SPINDLE_LOST_UNIQUE_FRACTION=` is the fraction (0-1) of unique reflections the mounting made unmeasurable under the measured point group, also written to the master as `/entry/MX/spindleLostUniqueFraction` and what the mounting warning fires on; `SPINDLE_SYMMETRY_AXIS_ANGLE_DEG=` / `SPINDLE_SYMMETRY_AXIS_ORDER=` describe the mounting in the `--developer` report.
|
||||
* Stills and grid scans carry a per-image `spindle_blind_fraction` - how much of a rotation sweep's blind cone this orientation would make unrecoverable, 0.5 and above calling for a second orientation - through the CBOR stream, HDF5 (`/entry/MX/spindleBlindFraction`), the plot and scan-result APIs, and the viewer and frontend plots; an absent value means the frame could not be assessed and is not a 0.
|
||||
* `jfjoch_viewer` gains Help entries for the mouse shortcuts and the acknowledgements, and Inspector toggles that hide non-indexed spots and spots on ice rings.
|
||||
* The results report's `REPORT_VERSION` is 7.
|
||||
|
||||
### 1.0.0-rc.166
|
||||
|
||||
+55
-4
@@ -59,12 +59,16 @@ install it.
|
||||
viewer, 2D azimuthal-integration image, calibration-image viewer and a magnifier; plus the
|
||||
*Inspector* (per-image statistics, image features, resolution rings, ROI statistics), the
|
||||
*Image strip* thumbnail feed and dataset-info charts.
|
||||
- The *Inspector*'s **Image features** section decides what the overlay draws — spots, predictions,
|
||||
saturated and highest pixels, the beam stop — including whether the non-indexed spots and the
|
||||
spots that fall on an ice ring are drawn at all.
|
||||
- User-mask editing: build a user mask interactively, load one from TIFF (replacing or adding to the
|
||||
current one), save it as TIFF, clear it, or upload it to a connected server.
|
||||
- Mouse wheel on the diffraction image: **zoom** on its own, move the foreground with `Ctrl` or
|
||||
`Shift`, and **step through the dataset one image per notch** with `Alt` (wheel up goes forward).
|
||||
Some window managers grab `Alt`-modified mouse events before the application sees them; that is a
|
||||
window-manager setting, not something the viewer can take back.
|
||||
- Mouse-driven navigation of the image, the grid scan and the plots — see
|
||||
[Mouse shortcuts](#mouse-shortcuts) below, which the viewer also shows under *Help ▸ Mouse
|
||||
Shortcuts*.
|
||||
- *Help* shows the mouse shortcuts, the [acknowledgements](ACKNOWLEDGEMENT.md) and the third-party
|
||||
licenses.
|
||||
- Layout presets (*View ▸ Image layout / Processing layout / Reset layout*) rearrange the docks for
|
||||
looking at images or at processing results.
|
||||
|
||||
@@ -83,6 +87,53 @@ installer and the `.tgz` are CUDA 13 and CUDA 12 respectively, which also decide
|
||||
they run on — a V100 needs the CUDA 12 `.tgz`. See
|
||||
[Release contents ▸ GPU generations and the NVIDIA driver](RELEASE_CONTENTS.md#gpu-generations-and-the-nvidia-driver).
|
||||
|
||||
## Mouse shortcuts
|
||||
|
||||
The same list is available in the application under **Help ▸ Mouse Shortcuts**.
|
||||
|
||||
### Diffraction image
|
||||
|
||||
| Action | Effect |
|
||||
| --- | --- |
|
||||
| Wheel | Zoom in / out, centred on the cursor |
|
||||
| `Shift` + wheel | Move the foreground (upper contrast limit) in linear steps |
|
||||
| `Ctrl` + wheel | Move the foreground in multiplicative steps (×1.15 per notch) |
|
||||
| `Alt` + wheel | Step through the dataset, one image per notch (up = next image) |
|
||||
| `F` held + wheel | Same as `Shift` + wheel, for as long as `F` is held |
|
||||
| `A` | Switch on the automatic foreground |
|
||||
| Hover | Status bar shows the pixel position, its value and the resolution |
|
||||
| Drag | Pan the image |
|
||||
| `Shift` + drag | Draw a rectangular ROI |
|
||||
| `Shift` + `Ctrl` + drag | Draw a circular ROI |
|
||||
| Drag an ROI or its handle | Move or resize the selected ROI |
|
||||
| Right click | Copy / save the image, fit to view, clear the ROI |
|
||||
|
||||
Some window managers take `Alt`-modified mouse events for themselves before the application sees
|
||||
them; that is a window-manager setting, not something the viewer can take back.
|
||||
|
||||
### Grid scan
|
||||
|
||||
| Action | Effect |
|
||||
| --- | --- |
|
||||
| Hover | Status bar shows the image number, the grid position and its value |
|
||||
| `Shift` + hover | Load the image under the cursor while moving over the grid |
|
||||
| Double click | Load the image under the cursor |
|
||||
|
||||
### Other views
|
||||
|
||||
| Action | Effect |
|
||||
| --- | --- |
|
||||
| 2D azimuthal image: double click | Zoom the diffraction image on the corresponding detector position |
|
||||
| Dataset-info plot: hover | Status bar shows the image number and the plotted value |
|
||||
| Dataset-info plot: `Shift` + hover | Load the hovered image |
|
||||
| Spot / reflection list: double click | Zoom the diffraction image on that spot or prediction |
|
||||
| Image list: double click | Load that image |
|
||||
| Reciprocal space: drag | Rotate the view |
|
||||
| Reciprocal space: right drag | Pan the view |
|
||||
| Reciprocal space: wheel | Zoom the view |
|
||||
| Reciprocal space: double click | Zoom the diffraction image on the nearest spot |
|
||||
| Magnifier: wheel | Zoom the magnifier; it follows the cursor on the main image |
|
||||
|
||||
## Opening data
|
||||
|
||||
- **File ▸ Open** (`Ctrl+O`) — open a local HDF5 file, or any frame of a miniCBF sweep.
|
||||
|
||||
@@ -89,6 +89,10 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView
|
||||
windows/JFJochHelperWindow.h
|
||||
windows/JFJochLicenseWindow.cpp
|
||||
windows/JFJochLicenseWindow.h
|
||||
windows/JFJochMouseShortcutsWindow.cpp
|
||||
windows/JFJochMouseShortcutsWindow.h
|
||||
windows/JFJochAcknowledgementWindow.cpp
|
||||
windows/JFJochAcknowledgementWindow.h
|
||||
image_viewer/JFJochImage.cpp
|
||||
image_viewer/JFJochImage.h
|
||||
image_viewer/JFJochFollowerImage.cpp
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include "JFJochViewerWindow.h"
|
||||
#include "windows/JFJochLicenseWindow.h"
|
||||
#include "windows/JFJochMouseShortcutsWindow.h"
|
||||
#include "windows/JFJochAcknowledgementWindow.h"
|
||||
#include "../common/GitInfo.h"
|
||||
#include "../common/CUDAWrapper.h"
|
||||
|
||||
@@ -81,6 +83,12 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) {
|
||||
const QAction *aboutAction = helpMenu->addAction("About");
|
||||
connect(aboutAction, &QAction::triggered, this, &JFJochViewerMenu::aboutSelected);
|
||||
|
||||
const QAction *mouseShortcutsAction = helpMenu->addAction("Mouse Shortcuts");
|
||||
connect(mouseShortcutsAction, &QAction::triggered, this, &JFJochViewerMenu::mouseShortcutsSelected);
|
||||
|
||||
const QAction *acknowledgementAction = helpMenu->addAction("Acknowledgements");
|
||||
connect(acknowledgementAction, &QAction::triggered, this, &JFJochViewerMenu::acknowledgementsSelected);
|
||||
|
||||
const QAction *licensesAction = helpMenu->addAction("Third-party Licenses");
|
||||
connect(licensesAction, &QAction::triggered, this, &JFJochViewerMenu::licensesSelected);
|
||||
}
|
||||
@@ -148,6 +156,24 @@ void JFJochViewerMenu::licensesSelected() {
|
||||
licenseWindow->activateWindow();
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::mouseShortcutsSelected() {
|
||||
// Same pattern as the license window: modeless, created once, raised if already open.
|
||||
if (mouseShortcutsWindow == nullptr)
|
||||
mouseShortcutsWindow = new JFJochMouseShortcutsWindow(window());
|
||||
mouseShortcutsWindow->show();
|
||||
mouseShortcutsWindow->raise();
|
||||
mouseShortcutsWindow->activateWindow();
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::acknowledgementsSelected() {
|
||||
// Same pattern as the license window: modeless, created once, raised if already open.
|
||||
if (acknowledgementWindow == nullptr)
|
||||
acknowledgementWindow = new JFJochAcknowledgementWindow(window());
|
||||
acknowledgementWindow->show();
|
||||
acknowledgementWindow->raise();
|
||||
acknowledgementWindow->activateWindow();
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::openSelected() {
|
||||
QString fileName = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "windows/JFJochHelperWindow.h"
|
||||
|
||||
class JFJochLicenseWindow;
|
||||
class JFJochMouseShortcutsWindow;
|
||||
class JFJochAcknowledgementWindow;
|
||||
class QDockWidget;
|
||||
|
||||
class JFJochViewerMenu : public QMenuBar {
|
||||
@@ -21,6 +23,8 @@ class JFJochViewerMenu : public QMenuBar {
|
||||
|
||||
QMenu *windowMenu = nullptr;
|
||||
JFJochLicenseWindow *licenseWindow = nullptr;
|
||||
JFJochMouseShortcutsWindow *mouseShortcutsWindow = nullptr;
|
||||
JFJochAcknowledgementWindow *acknowledgementWindow = nullptr;
|
||||
public:
|
||||
explicit JFJochViewerMenu(QWidget *parent = nullptr);
|
||||
~JFJochViewerMenu() override = default;
|
||||
@@ -50,6 +54,8 @@ signals:
|
||||
private slots:
|
||||
void aboutSelected();
|
||||
void licensesSelected();
|
||||
void mouseShortcutsSelected();
|
||||
void acknowledgementsSelected();
|
||||
void quitSelected();
|
||||
void closeSelected();
|
||||
|
||||
|
||||
@@ -43,6 +43,14 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent)
|
||||
highlightIceRingToggleCheckBox->setCheckState(Qt::CheckState::Checked);
|
||||
connect(highlightIceRingToggleCheckBox, &QCheckBox::toggled, this,&JFJochViewerSidePanel::highlightIceRingsToggled);
|
||||
|
||||
auto hideUnindexedCheckBox = new QCheckBox("Hide non-indexed spots", this);
|
||||
hideUnindexedCheckBox->setToolTip("Draw only the spots that were assigned to a lattice.");
|
||||
connect(hideUnindexedCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::hideUnindexedSpotsToggled);
|
||||
|
||||
auto hideIceRingSpotsCheckBox = new QCheckBox("Hide spots on ice rings", this);
|
||||
hideIceRingSpotsCheckBox->setToolTip("Leave the spots that fall on an ice ring out of the overlay.");
|
||||
connect(hideIceRingSpotsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::hideIceRingSpotsToggled);
|
||||
|
||||
auto predictionsToggleCheckBox = new QCheckBox("Show predictions", this);
|
||||
predictionsToggleCheckBox->setCheckState(Qt::CheckState::Unchecked);
|
||||
|
||||
@@ -100,13 +108,15 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent)
|
||||
auto image_feature_grid = new QGridLayout();
|
||||
image_feature_grid->addWidget(spotToggleCheckBox, 0, 0);
|
||||
image_feature_grid->addWidget(highlightIceRingToggleCheckBox, 0, 1);
|
||||
image_feature_grid->addWidget(predictionsToggleCheckBox, 1, 0);
|
||||
image_feature_grid->addWidget(beamStopCheckBox, 1, 1);
|
||||
image_feature_grid->addWidget(hideUnindexedCheckBox, 1, 0);
|
||||
image_feature_grid->addWidget(hideIceRingSpotsCheckBox, 1, 1);
|
||||
image_feature_grid->addWidget(predictionsToggleCheckBox, 2, 0);
|
||||
image_feature_grid->addWidget(beamStopCheckBox, 2, 1);
|
||||
|
||||
image_feature_grid->addWidget(saturatedPixelsCheckBox, 2, 0);
|
||||
image_feature_grid->addWidget(highestPixelsComboBox, 2, 1);
|
||||
image_feature_grid->addWidget(colorSelectButton, 3, 0);
|
||||
image_feature_grid->addWidget(spotColorSelectButton, 3, 1);
|
||||
image_feature_grid->addWidget(saturatedPixelsCheckBox, 3, 0);
|
||||
image_feature_grid->addWidget(highestPixelsComboBox, 3, 1);
|
||||
image_feature_grid->addWidget(colorSelectButton, 4, 0);
|
||||
image_feature_grid->addWidget(spotColorSelectButton, 4, 1);
|
||||
|
||||
// Legend: what each overlay colour means (matches JFJochDiffractionImage draw colours).
|
||||
legend_spot_swatch_ = MakeColorSwatch(this, Qt::green);
|
||||
@@ -130,8 +140,8 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent)
|
||||
legend_row++;
|
||||
}
|
||||
legendLayout->setColumnStretch(1, 1);
|
||||
image_feature_grid->addWidget(new QLabel(tr("Legend"), this), 4, 0, 1, 2);
|
||||
image_feature_grid->addLayout(legendLayout, 5, 0, 1, 2);
|
||||
image_feature_grid->addWidget(new QLabel(tr("Legend"), this), 5, 0, 1, 2);
|
||||
image_feature_grid->addLayout(legendLayout, 6, 0, 1, 2);
|
||||
|
||||
featuresSection->setContentLayout(image_feature_grid);
|
||||
layout->addWidget(featuresSection);
|
||||
@@ -210,6 +220,14 @@ void JFJochViewerSidePanel::highlightIceRingsToggled(bool input) {
|
||||
emit highlightIceRings(input);
|
||||
}
|
||||
|
||||
void JFJochViewerSidePanel::hideUnindexedSpotsToggled(bool input) {
|
||||
emit hideUnindexedSpots(input);
|
||||
}
|
||||
|
||||
void JFJochViewerSidePanel::hideIceRingSpotsToggled(bool input) {
|
||||
emit hideIceRingSpots(input);
|
||||
}
|
||||
|
||||
void JFJochViewerSidePanel::selectROIInList(QString name) {
|
||||
roi_list->setSelected(name);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ signals:
|
||||
void showBeamStop(bool input);
|
||||
void showPredictions(bool input);
|
||||
void highlightIceRings(bool input);
|
||||
void hideUnindexedSpots(bool input);
|
||||
void hideIceRingSpots(bool input);
|
||||
void showROILabels(bool input);
|
||||
void showROIFill(bool input);
|
||||
void roisChanged(ROIDefinition rois);
|
||||
@@ -62,6 +64,8 @@ private slots:
|
||||
void spotsToggled(bool input);
|
||||
void predictionsToggled(bool input);
|
||||
void highlightIceRingsToggled(bool input);
|
||||
void hideUnindexedSpotsToggled(bool input);
|
||||
void hideIceRingSpotsToggled(bool input);
|
||||
void saturatedPixelsToggled(bool input);
|
||||
};
|
||||
|
||||
|
||||
@@ -378,6 +378,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
viewer, &JFJochDiffractionImage::setResolutionRingMode);
|
||||
connect(side_panel, &JFJochViewerSidePanel::highlightIceRings,
|
||||
viewer, &JFJochDiffractionImage::highlightIceRings);
|
||||
connect(side_panel, &JFJochViewerSidePanel::hideUnindexedSpots,
|
||||
viewer, &JFJochDiffractionImage::hideUnindexedSpots);
|
||||
connect(side_panel, &JFJochViewerSidePanel::hideIceRingSpots,
|
||||
viewer, &JFJochDiffractionImage::hideIceRingSpots);
|
||||
|
||||
connect(calibrationWindow, &JFJochCalibrationWindow::loadCalibration,
|
||||
reading_worker, &JFJochImageReadingWorker::LoadCalibration);
|
||||
|
||||
@@ -162,6 +162,11 @@ void JFJochDiffractionImage::DrawSpots() {
|
||||
if (!visibleRect.contains(QPointF{s.x, s.y}))
|
||||
continue;
|
||||
|
||||
if (hide_unindexed_spots && !s.indexed)
|
||||
continue;
|
||||
if (hide_ice_ring_spots && s.ice_ring)
|
||||
continue;
|
||||
|
||||
const qreal desired_half_px = 8.0;
|
||||
const qreal spot_size = desired_half_px / std::sqrt(std::max(0.0001, scale_factor));
|
||||
|
||||
@@ -990,6 +995,16 @@ void JFJochDiffractionImage::highlightIceRings(bool input) {
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::hideUnindexedSpots(bool input) {
|
||||
hide_unindexed_spots = input;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::hideIceRingSpots(bool input) {
|
||||
hide_ice_ring_spots = input;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::setResolutionRingMode(RingMode mode) {
|
||||
ring_mode = mode;
|
||||
updateOverlay();
|
||||
|
||||
@@ -113,6 +113,9 @@ private:
|
||||
|
||||
bool highlight_ice_rings = true;
|
||||
|
||||
bool hide_unindexed_spots = false;
|
||||
bool hide_ice_ring_spots = false;
|
||||
|
||||
void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) override;
|
||||
|
||||
signals:
|
||||
@@ -139,6 +142,8 @@ public slots:
|
||||
void showBeamStop(bool input);
|
||||
|
||||
void highlightIceRings(bool input);
|
||||
void hideUnindexedSpots(bool input);
|
||||
void hideIceRingSpots(bool input);
|
||||
void setHDRMode(bool input);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<qresource prefix="/">
|
||||
<file>jfjoch.png</file>
|
||||
<file>third_party_licenses.html</file>
|
||||
<file alias="ACKNOWLEDGEMENT.md">../../docs/ACKNOWLEDGEMENT.md</file>
|
||||
<file>psi_01.png</file>
|
||||
<file>psi_02.png</file>
|
||||
<file>psi_03.png</file>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochAcknowledgementWindow.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextBrowser>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
JFJochAcknowledgementWindow::JFJochAcknowledgementWindow(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle("Acknowledgements");
|
||||
resize(800, 600);
|
||||
|
||||
auto *browser = new QTextBrowser(this);
|
||||
browser->setOpenExternalLinks(true); // DOI and repository links open in the system browser
|
||||
|
||||
QFile file(":/ACKNOWLEDGEMENT.md");
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
browser->setMarkdown(QString::fromUtf8(file.readAll()));
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addWidget(browser);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
// Modeless window showing the acknowledgements (from :/ACKNOWLEDGEMENT.md, the same file the
|
||||
// documentation is built from).
|
||||
class JFJochAcknowledgementWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JFJochAcknowledgementWindow(QWidget *parent = nullptr);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochMouseShortcutsWindow.h"
|
||||
|
||||
#include <QTextBrowser>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
struct Shortcut { QString action; QString effect; };
|
||||
struct Section { QString title; QVector<Shortcut> rows; };
|
||||
|
||||
QString BuildHtml() {
|
||||
const QVector<Section> sections = {
|
||||
// JFJochImage / JFJochDiffractionImage mouse handling
|
||||
{"Diffraction image", {
|
||||
{"Wheel", "Zoom in / out, centred on the cursor"},
|
||||
{"Shift + wheel", "Move the foreground (upper contrast limit) in linear steps"},
|
||||
{"Ctrl + wheel", "Move the foreground in multiplicative steps (×1.15 per notch)"},
|
||||
{"Alt + wheel", "Step through the dataset, one image per notch (up = next image)"},
|
||||
{"F held + wheel", "Same as Shift + wheel, for as long as F is held"},
|
||||
{"A", "Switch on the automatic foreground"},
|
||||
{"Hover", "Status bar shows the pixel position, its value and the resolution"},
|
||||
{"Drag", "Pan the image"},
|
||||
{"Shift + drag", "Draw a rectangular ROI"},
|
||||
{"Shift + Ctrl + drag", "Draw a circular ROI"},
|
||||
{"Drag an ROI or its handle", "Move or resize the selected ROI"},
|
||||
{"Right click", "Copy / save the image, fit to view, clear the ROI"},
|
||||
}},
|
||||
// JFJochGridScanImage
|
||||
{"Grid scan", {
|
||||
{"Hover", "Status bar shows the image number, the grid position and its value"},
|
||||
{"Shift + hover", "Load the image under the cursor while moving over the grid"},
|
||||
{"Double click", "Load the image under the cursor"},
|
||||
}},
|
||||
{"Other views", {
|
||||
{"2D azimuthal image: double click", "Zoom the diffraction image on the corresponding detector position"},
|
||||
{"Dataset-info plot: hover", "Status bar shows the image number and the plotted value"},
|
||||
{"Dataset-info plot: Shift + hover", "Load the hovered image"},
|
||||
{"Spot / reflection list: double click", "Zoom the diffraction image on that spot or prediction"},
|
||||
{"Image list: double click", "Load that image"},
|
||||
{"Reciprocal space: drag", "Rotate the view"},
|
||||
{"Reciprocal space: right drag", "Pan the view"},
|
||||
{"Reciprocal space: wheel", "Zoom the view"},
|
||||
{"Reciprocal space: double click", "Zoom the diffraction image on the nearest spot"},
|
||||
{"Magnifier: wheel", "Zoom the magnifier; it follows the cursor on the main image"},
|
||||
}},
|
||||
};
|
||||
|
||||
QString html = "<html><body>";
|
||||
for (const auto §ion : sections) {
|
||||
html += "<h3>" + section.title + "</h3>";
|
||||
html += "<table cellspacing='0' cellpadding='4' width='100%'>";
|
||||
for (const auto &row : section.rows)
|
||||
html += "<tr><td width='38%'><b>" + row.action + "</b></td><td>" + row.effect + "</td></tr>";
|
||||
html += "</table>";
|
||||
}
|
||||
html += "<p><i>Note:</i> some window managers take Alt-modified mouse events for themselves "
|
||||
"before the application sees them. That is a window-manager setting, not something "
|
||||
"the viewer can take back.</p>";
|
||||
html += "</body></html>";
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
JFJochMouseShortcutsWindow::JFJochMouseShortcutsWindow(QWidget *parent) : QDialog(parent) {
|
||||
setWindowTitle("Mouse Shortcuts");
|
||||
resize(620, 620);
|
||||
|
||||
auto *browser = new QTextBrowser(this);
|
||||
browser->setHtml(BuildHtml());
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addWidget(browser);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
// Modeless cheat sheet of the mouse (and mouse-modifying key) shortcuts of the viewer.
|
||||
class JFJochMouseShortcutsWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JFJochMouseShortcutsWindow(QWidget *parent = nullptr);
|
||||
};
|
||||
Reference in New Issue
Block a user