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
+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())