Files
Jungfraujoch/viewer/widgets/JFJochViewerFileBrowser.cpp
T
leonarski_fandClaude Fable 5 7d872fd762 viewer: plot choices persist per dataset kind, and settings reset live
The dataset-info plot remembers the selected quantity and the Grid/Per-image
toggle states across restarts, with grid scans and standard scans each
keeping their own favourite (QSettings groups datasetInfoPlot/gridScan and
/standard). Choices are stored as the combo item's data code, not its index,
so a saved choice survives the plot list changing and a code no longer
offered falls back to the first item. Favourites are saved on every explicit
user action and applied whenever a dataset of the other kind loads; loads of
the same kind keep the in-session state, and the live-source default still
wins until the user picks. The unconditional Grid auto-check on grid scans
moved into the favourite's default, so unchecking Grid on a grid dataset now
sticks.

View > "Reset all settings to defaults" clears the whole settings store after
confirmation and brings the running session to a first-start state in place:
default layout, window size, display settings (Indigo, Auto off, HDR off),
plot choices, and the file manager root. Because the live state then equals
the defaults, saving on close stays enabled and later changes persist as
usual.

File > Quit now closes the main window instead of calling
QApplication::quit(), so closeEvent runs and the persistent settings are
saved on that path too - previously they were saved only when the window was
closed by the window manager.

The file manager root persistence was audited and left unchanged: saved on
every successful change, restored behind JUNGFRAUJOCH_DATA_ROOT, and a
vanished directory falls back to the home directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbjnQhutnboy4qbxwmgDAX
2026-09-13 10:16:55 +02:00

136 lines
5.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerFileBrowser.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QToolButton>
#include <QTreeView>
#include <QHeaderView>
#include <QFileDialog>
#include <QSettings>
#include <QDir>
namespace {
constexpr auto kDataRootEnvVar = "JUNGFRAUJOCH_DATA_ROOT";
constexpr auto kSettingsKey = "fileBrowserRoot";
// The filter box narrows files only; a directory always passes. Filtering directories too would
// hide everything below one whose own name does not match - including the files that do match -
// and would hide the root's ancestors, which leaves the tree with no valid root index at all.
class FileOnlyFilterProxy : public QSortFilterProxyModel {
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
protected:
bool filterAcceptsRow(int row, const QModelIndex &parent) const override {
const auto *fs = qobject_cast<const QFileSystemModel *>(sourceModel());
if (fs && fs->isDir(fs->index(row, 0, parent)))
return true;
return QSortFilterProxyModel::filterAcceptsRow(row, parent);
}
};
}
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.
const QString env_root = qEnvironmentVariable(kDataRootEnvVar);
if (!env_root.isEmpty() && QDir(env_root).exists())
return env_root;
const QString last_root = QSettings("PSI", "jfjoch_viewer").value(kSettingsKey).toString();
if (!last_root.isEmpty() && QDir(last_root).exists())
return last_root;
return QDir::homePath();
}
JFJochViewerFileBrowser::JFJochViewerFileBrowser(QWidget *parent) : QWidget(parent) {
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(4, 4, 4, 4);
auto *root_row = new QHBoxLayout();
root_edit_ = new QLineEdit(this);
root_edit_->setToolTip("Data root - type a path or use Browse…");
auto *browse_button = new QToolButton(this);
browse_button->setText("…");
browse_button->setToolTip("Browse for a data root…");
root_row->addWidget(root_edit_);
root_row->addWidget(browse_button);
layout->addLayout(root_row);
filter_edit_ = new QLineEdit(this);
filter_edit_->setPlaceholderText("Filter…");
filter_edit_->setClearButtonEnabled(true);
layout->addWidget(filter_edit_);
model_ = new QFileSystemModel(this);
model_->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
// Directories are always listed regardless of these; only *_master.h5 / *_process.h5 and
// miniCBF frames among files. The many _data_NNNNNN.h5 shards of a dataset are not meant to be
// opened directly; a .cbf is one frame per file, and opening any of them opens its whole sweep.
model_->setNameFilters({"*_master.h5", "*_process.h5", "*.cbf"});
model_->setNameFilterDisables(false); // hide non-matching files rather than just greying them out
proxy_ = new FileOnlyFilterProxy(this);
proxy_->setSourceModel(model_);
proxy_->setFilterKeyColumn(0);
proxy_->setFilterCaseSensitivity(Qt::CaseInsensitive);
// Deliberately not recursive: it only ever narrows names already listed at an expanded level,
// never forces fetching a collapsed subtree just to search inside it.
tree_ = new QTreeView(this);
tree_->setModel(proxy_);
tree_->setHeaderHidden(true);
for (int col = 1; col < model_->columnCount(); ++col)
tree_->hideColumn(col);
tree_->setSortingEnabled(false);
layout->addWidget(tree_);
connect(browse_button, &QToolButton::clicked, this, &JFJochViewerFileBrowser::onBrowseClicked);
connect(root_edit_, &QLineEdit::editingFinished, this, &JFJochViewerFileBrowser::onRootEditingFinished);
connect(filter_edit_, &QLineEdit::textChanged, proxy_, &QSortFilterProxyModel::setFilterFixedString);
connect(tree_, &QTreeView::doubleClicked, this, &JFJochViewerFileBrowser::onDoubleClicked);
SetRoot(DefaultRoot());
}
void JFJochViewerFileBrowser::SetRoot(const QString &path) {
QDir dir(path);
if (!dir.exists())
return;
const QString canonical = QDir::toNativeSeparators(dir.absolutePath());
root_edit_->setText(canonical);
model_->setRootPath(canonical);
tree_->setRootIndex(proxy_->mapFromSource(model_->index(canonical)));
QSettings("PSI", "jfjoch_viewer").setValue(kSettingsKey, canonical);
}
void JFJochViewerFileBrowser::ResetToDefaultRoot() {
SetRoot(DefaultRoot());
}
void JFJochViewerFileBrowser::onBrowseClicked() {
const QString dir = QFileDialog::getExistingDirectory(this, "Select data root", root_edit_->text());
if (!dir.isEmpty())
SetRoot(dir);
}
void JFJochViewerFileBrowser::onRootEditingFinished() {
SetRoot(root_edit_->text());
}
void JFJochViewerFileBrowser::onDoubleClicked(const QModelIndex &index) {
const QModelIndex source = proxy_->mapToSource(index);
if (!source.isValid() || model_->isDir(source))
return; // directories: let the tree's own expand/collapse handle the double click
emit openDataset(QDir::toNativeSeparators(model_->filePath(source)), 0, 1, false);
}