viewer: the file manager starts where the data is, and follows a dataset opened elsewhere
Build Packages / Create release (push) Successful in 18s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m41s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m36s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m51s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m28s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m37s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m40s
Build Packages / build:windows:nocuda (push) Successful in 16m50s
Build Packages / build:windows:cuda (push) Successful in 19m6s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m20s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m57s
Build Packages / build:rugnux:windows (push) Successful in 10m23s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m21s
Build Packages / Generate python client (push) Successful in 48s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m11s
Build Packages / Build documentation (push) Successful in 1m23s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m45s
Build Packages / build:rpm (rocky9) (push) Successful in 19m12s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m20s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 15m11s
Build Packages / Unit tests (push) Successful in 1h42m33s

Two things a beamline user did every session by hand. The browser's default root
now resolves JUNGFRAUJOCH_DATA_ROOT, then the root last used, then - for an SLS
account, which is eNNNNN against a pNNNNN group - /sls/mx/data/pNNNNN/raw, then
home. The environment variable stays the general mechanism and keeps precedence;
the account rule is there because it needs nothing rolled out to benefit from it.
Both go through DefaultRoot(), so a reset resolves them the same way a first run
does.

A dataset opened from the File menu or over D-Bus now expands and selects itself
in the browser when it lies inside the current root. Containment is tested on
resolved paths, so a sibling directory sharing a prefix is not inside it.

Neither says anything when it does not apply: an account that is not a beamline
one, a directory that is not there, a dataset outside the root - each is a bare
return, because for everyone who is not at this one facility the condition should
be invisible. Following never writes the root, so the location the user chose is
still the location they get back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
This commit is contained in:
2026-09-13 12:21:59 +02:00
co-authored by Claude Opus 5
parent f4a838afb9
commit 54968a245f
5 changed files with 56 additions and 2 deletions
+2
View File
@@ -11,6 +11,8 @@
* The rugnux report header records the exact build: the git commit (stamped at build time, so it cannot go stale; `-dirty` marks uncommitted changes), the compiler flags, and the download page of the release it came from.
* The rugnux report opens by stating who wrote the program and its terms of use: GPLv3, free for academic institutions and commercial companies alike.
* The viewer's file manager lists CBF frames beside HDF5 datasets.
* The viewer's file manager starts at the beamline's raw data directory for an SLS account, and at the home directory for everyone else.
* The viewer's file manager expands and selects a dataset opened from the File menu or over D-Bus, when it lies inside the current root.
* The viewer's image statistics state the collection time, detector distance, beam centre and wavelength as entries of their own rather than in a tooltip, and no longer report a B-factor.
* The viewer plots spot count and background together on one plot, which is what a connection to a live source now shows by default.
* The viewer's combined spots + background plot scales each quantity on its own axis - background on the left, spot count on the right, each axis coloured like its curve - and keeps the two curves in separate halves of the plot.
+8
View File
@@ -194,6 +194,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
connect(menuBar, &JFJochViewerMenu::fileOpenSelected,
reading_worker, &JFJochImageReadingWorker::LoadFile);
connect(menuBar, &JFJochViewerMenu::fileOpenSelected,
this, &JFJochViewerWindow::FollowInFileBrowser);
connect(menuBar, &JFJochViewerMenu::fileCloseSelected,
reading_worker, &JFJochImageReadingWorker::CloseFile);
@@ -665,6 +667,12 @@ void JFJochViewerWindow::closeEvent(QCloseEvent *event) {
void JFJochViewerWindow::LoadFile(const QString &filename, qint64 image_number, qint64 summation, bool retry) {
emit LoadFileRequest(filename, image_number, summation, true);
FollowInFileBrowser(filename);
}
void JFJochViewerWindow::FollowInFileBrowser(const QString &filename) {
for (auto *browser : findChildren<JFJochViewerFileBrowser *>())
browser->FollowPath(filename);
}
void JFJochViewerWindow::LoadImage(qint64 image_number, qint64 summation) {
+4
View File
@@ -59,6 +59,10 @@ private:
void ResetAllSettings();
// Both ways of opening a dataset from outside the file manager - the File menu and D-Bus -
// come through here, so the browser follows the same way for either.
void FollowInFileBrowser(const QString &filename);
QThread *reading_thread;
// Named layouts. Image = just the image + inspector; Processing = also the plots + jobs panel.
+35 -2
View File
@@ -12,6 +12,8 @@
#include <QFileDialog>
#include <QSettings>
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
namespace {
constexpr auto kDataRootEnvVar = "JUNGFRAUJOCH_DATA_ROOT";
@@ -34,11 +36,19 @@ namespace {
};
}
QString JFJochViewerFileBrowser::SlsDataRoot(const QString &user) {
const auto match = QRegularExpression("\\Ae(\\d{5})\\z").match(user);
if (!match.hasMatch())
return {};
return "/sls/mx/data/p" + match.captured(1) + "/raw";
}
QString JFJochViewerFileBrowser::DefaultRoot() {
// Site default (e.g. a beamline launcher setting it to /sls/mx/data/p<group>), then whatever
// root was last browsed to (so re-launching without the variable set doesn't start over from
// scratch), then the user's home - never the current directory, which for an installed binary
// is typically the install location rather than anywhere the user would keep data.
// scratch), then - for an SLS beamline account - that proposal's raw data, then the user's home
// - never the current directory, which for an installed binary is typically the install
// location rather than anywhere the user would keep data.
const QString env_root = qEnvironmentVariable(kDataRootEnvVar);
if (!env_root.isEmpty() && QDir(env_root).exists())
return env_root;
@@ -47,6 +57,12 @@ QString JFJochViewerFileBrowser::DefaultRoot() {
if (!last_root.isEmpty() && QDir(last_root).exists())
return last_root;
// USER is the portable spelling Qt exposes; it is simply empty where the beamline directory
// could not exist anyway, so everyone else silently gets home, as before.
const QString sls_root = SlsDataRoot(qEnvironmentVariable("USER"));
if (!sls_root.isEmpty() && QDir(sls_root).exists())
return sls_root;
return QDir::homePath();
}
@@ -117,6 +133,23 @@ void JFJochViewerFileBrowser::ResetToDefaultRoot() {
SetRoot(DefaultRoot());
}
void JFJochViewerFileBrowser::FollowPath(const QString &path) {
const QString absolute = QFileInfo(path).absoluteFilePath();
// Containment by path components, not by string prefix, so /data/foo2 is not inside /data/foo.
// relativeFilePath hands back an absolute path when there is no relative route at all (a file on
// another Windows drive).
const QString relative = QDir(model_->rootPath()).relativeFilePath(absolute);
if (QDir::isAbsolutePath(relative) || relative == ".." || relative.startsWith("../"))
return;
const QModelIndex index = proxy_->mapFromSource(model_->index(absolute));
if (!index.isValid())
return;
tree_->setCurrentIndex(index);
tree_->scrollTo(index);
}
void JFJochViewerFileBrowser::onBrowseClicked() {
const QString dir = QFileDialog::getExistingDirectory(this, "Select data root", root_edit_->text());
if (!dir.isEmpty())
+7
View File
@@ -28,6 +28,9 @@ class JFJochViewerFileBrowser : public QWidget {
void SetRoot(const QString &path);
[[nodiscard]] static QString DefaultRoot();
// An SLS account eNNNNN browses its proposal group's raw data, which is pNNNNN with the
// same five digits; empty for any other user name, on any platform.
[[nodiscard]] static QString SlsDataRoot(const QString &user);
public slots:
// "Reset all settings to defaults": back to the root a fresh start would pick.
@@ -44,4 +47,8 @@ signals:
public:
explicit JFJochViewerFileBrowser(QWidget *parent = nullptr);
// A dataset opened elsewhere (menu, D-Bus) and lying inside the current root moves the tree's
// selection to it; one outside the root leaves the browser exactly as it was.
void FollowPath(const QString &path);
};