Files
Jungfraujoch/viewer/toolbar/JFJochViewerToolbarImage.cpp
T
Filip LeonarskiandClaude Opus 5.5 0be8eb2bca Viewer: dark theme, switchable live from the View menu
View > Theme offers Follow system / Light / Dark, stored in the settings.
Follow system uses the desktop's colour scheme where Qt reports it (6.5+)
and is light otherwise. The dark theme is a deep indigo (#1A1D3A panels,
#12142B entry fields and charts).

The palettes live in the new ViewerTheme module. A switch applies at once:
the application stylesheet is set again after the palette so every widget
is re-polished (with a stylesheet active, polished widgets otherwise keep
their old palette); widgets that bake colours into stylesheets use
SetThemedStyleSheet, which re-applies them on ThemeNotifier::changed;
toolbar icons draw their glyph at paint time through an icon engine, so the
ink follows the theme (and stays sharp at any size); charts re-theme and
rebuild. Hard-coded white backgrounds on entry fields and tables are
removed in favour of the palette's base colour.

The idle detector-status badge in the status bar is now transparent instead
of an empty default-styled progress bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:51:50 +02:00

263 lines
10 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerToolbarImage.h"
#include "../ViewerTheme.h"
#include <algorithm>
#include <QTimer>
#include "../RemoteDisplayMode.h"
#include "../widgets/ToolbarIcons.h"
JFJochViewerToolbarImage::JFJochViewerToolbarImage(QWidget *parent) : QToolBar(parent) {
setWindowTitle("Navigation"); // shown in the toolbar/dock context menu
image_count_in_dataset = 0;
auto makeButton = [this](const QIcon &icon, const QString &tip, bool checkable) {
auto *b = new QToolButton(this);
b->setIcon(icon);
b->setIconSize(QSize(20, 20));
b->setToolTip(tip);
b->setCheckable(checkable);
b->setCursor(Qt::PointingHandCursor);
SetThemedStyleSheet(b, ToolbarIcons::buttonStyle);
return b;
};
// --- create widgets ---
open_button = makeButton(ToolbarIcons::openFile(), "Open dataset…", false);
open_button->setShortcut(QKeySequence::Open);
autoload_button = makeButton(ToolbarIcons::httpSync(ToolbarIcons::HttpState::Disconnected),
"Connect to a live source…", false);
autoload_button->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
movie_button = makeButton(ToolbarIcons::movie(), "Play movie", true);
movie_button->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_M));
// The scrub slider is the prominent way to move across the dataset, so it expands to fill.
image_number_slider = new QSlider(Qt::Horizontal, this);
image_number_slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
image_number_slider->setMinimumWidth(140);
image_number_slider->setToolTip("Scrub through the dataset");
SetThemedStyleSheet(image_number_slider, SliderStyle);
image_number_slider_timer = new QTimer(this);
image_number_slider_timer->setSingleShot(true);
number_tail_timer_ = new QTimer(this);
number_tail_timer_->setSingleShot(true);
connect(number_tail_timer_, &QTimer::timeout, this, [this] {
number_rate_.restart();
updateButtons();
});
leftmost_button = makeButton(ToolbarIcons::first(), "First image", false);
left_button = makeButton(ToolbarIcons::prev(), "Previous (by jump)", false);
current_image_edit_validator = new QIntValidator(0, 1000, this);
current_image_edit = new QLineEdit("0", this);
current_image_edit->setFixedWidth(54);
current_image_edit->setAlignment(Qt::AlignmentFlag::AlignRight);
current_image_edit->setValidator(current_image_edit_validator);
current_image_edit->setToolTip("Current image — type a number to jump");
total_number_label = new QLabel("/0", this);
right_button = makeButton(ToolbarIcons::next(), "Next (by jump)", false);
rightmost_button = makeButton(ToolbarIcons::last(), "Last image", false);
jump = new NumericComboBox({1, 2, 5, 10, 20, 100, 500}, 1, 999, this);
connect(jump, &NumericComboBox::valueChanged, this, &JFJochViewerToolbarImage::setImageJump);
auto sum_box = new NumericComboBox({1, 2, 5, 10, 20, 50, 100}, 1, 10000, this);
connect(sum_box, &NumericComboBox::valueChanged, this, &JFJochViewerToolbarImage::setSummation);
// --- lay out: source actions, then the scrubber, then precise navigation ---
addWidget(open_button);
addWidget(autoload_button);
addWidget(movie_button);
addSeparator();
addWidget(image_number_slider);
addSeparator();
addWidget(leftmost_button);
addWidget(left_button);
addWidget(current_image_edit);
addWidget(total_number_label);
addWidget(right_button);
addWidget(rightmost_button);
addWidget(new QLabel(" Jump ", this));
addWidget(jump);
addWidget(new QLabel(" Sum ", this));
addWidget(sum_box);
connect(movie_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::movieButtonPressed);
connect(open_button, &QToolButton::clicked, this, [this] { emit requestOpenFile(); });
connect(autoload_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::autoloadButtonPressed);
setImageNumber(0, 0);
connect(left_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::leftButtonPressed);
connect(right_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::rightButtonPressed);
connect(leftmost_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::leftmostButtonPressed);
connect(rightmost_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::rightmostButtonPressed);
connect(current_image_edit, &QLineEdit::editingFinished, this, &JFJochViewerToolbarImage::editFinalized);
connect(image_number_slider, &QSlider::sliderPressed, this, &JFJochViewerToolbarImage::imageNumberSliderPressed);
connect(image_number_slider, &QSlider::sliderReleased, this, &JFJochViewerToolbarImage::imageNumberSliderReleased);
connect(image_number_slider, &QSlider::sliderMoved, this, &JFJochViewerToolbarImage::imageNumberSliderMoved);
}
void JFJochViewerToolbarImage::updateButtons() {
if (image_count_in_dataset > 0) {
current_image_edit->setDisabled(false);
current_image_edit->setText(QString::number(curr_image + 1));
current_image_edit_validator->setRange(1, image_count_in_dataset);
left_button->setDisabled(curr_image < jump_value);
right_button->setDisabled(curr_image >= image_count_in_dataset - jump_value);
leftmost_button->setDisabled(curr_image == 0);
rightmost_button->setDisabled(curr_image == image_count_in_dataset - 1);
image_number_slider->setRange(0, image_count_in_dataset - 1);
image_number_slider->setEnabled(true);
if (!image_number_slider_manual)
image_number_slider->setValue(curr_image);
} else {
left_button->setDisabled(true);
right_button->setDisabled(true);
leftmost_button->setDisabled(true);
rightmost_button->setDisabled(true);
current_image_edit->setText(QString::number(0));
image_number_slider->setValue(0);
image_number_slider->setDisabled(true);
}
if (image_count_in_dataset == 0)
total_number_label->setText("/ - ");
else
total_number_label->setText("/" + QString::number(image_count_in_dataset));
}
void JFJochViewerToolbarImage::updateHttpButton() {
ToolbarIcons::HttpState s;
QString tip;
if (!http_connected) {
s = ToolbarIcons::HttpState::Disconnected;
tip = "Connect to a live source…";
} else if (autoload_mode == JFJochImageReadingWorker::AutoloadMode::HTTPSync) {
s = ToolbarIcons::HttpState::Live;
tip = "Following live (image + data) — click to stop";
} else {
s = ToolbarIcons::HttpState::Frozen;
tip = "Connected, not following — click to follow live";
}
autoload_button->setIcon(ToolbarIcons::httpSync(s));
autoload_button->setToolTip(tip);
}
void JFJochViewerToolbarImage::setImageNumber(int64_t total_images, int64_t current_image) {
image_count_in_dataset = total_images;
curr_image = current_image;
if (RemoteDisplayMode() && number_rate_.isValid() && number_rate_.elapsed() < kRemoteRepaintIntervalMs) {
number_tail_timer_->start(kRemoteRepaintIntervalMs);
return;
}
number_tail_timer_->stop();
number_rate_.restart();
updateButtons();
}
void JFJochViewerToolbarImage::setHttpConnection(bool connected, QString) {
http_connected = connected;
updateHttpButton();
}
void JFJochViewerToolbarImage::leftButtonPressed() {
if (curr_image >= jump_value)
emit loadImage(curr_image - jump_value, sum);
}
void JFJochViewerToolbarImage::rightButtonPressed() {
if (curr_image < image_count_in_dataset - jump_value)
emit loadImage(curr_image + jump_value, sum);
}
void JFJochViewerToolbarImage::rightmostButtonPressed() {
emit loadImage(image_count_in_dataset - 1, sum);
}
void JFJochViewerToolbarImage::leftmostButtonPressed() {
emit loadImage(0, sum);
}
void JFJochViewerToolbarImage::stepImage(int steps) {
if (image_count_in_dataset == 0)
return;
const auto target = std::clamp<int64_t>(curr_image + steps, 0,
static_cast<int64_t>(image_count_in_dataset) - 1);
if (target != curr_image)
emit loadImage(target, sum);
}
void JFJochViewerToolbarImage::imageNumberSliderPressed() {
image_number_slider_manual = true;
}
void JFJochViewerToolbarImage::imageNumberSliderReleased() {
image_number_slider_manual = false;
emit loadImage(image_number_slider->value(), sum);
}
void JFJochViewerToolbarImage::imageNumberSliderMoved(int val) {
if (!image_number_slider_timer->isActive()) {
emit loadImage(val, sum);
image_number_slider_timer->start(300);
}
}
void JFJochViewerToolbarImage::editFinalized() {
bool ok;
int editedValue = current_image_edit->text().toInt(&ok);
if (ok && editedValue >= 1 && editedValue <= image_count_in_dataset)
emit loadImage(editedValue - 1, sum);
}
void JFJochViewerToolbarImage::setImageJump(int val) {
jump_value = val;
updateButtons();
emit imageJumpChanged(jump_value);
}
void JFJochViewerToolbarImage::setSummation(int val) {
sum = val;
jump_value = val;
jump->setValue(val);
updateButtons();
emit loadImage(curr_image, sum);
}
void JFJochViewerToolbarImage::autoloadButtonPressed() {
// Not connected: ask the window to open the connection dialog. Connected: toggle live following.
if (!http_connected) {
emit requestHttpConnect();
return;
}
if (autoload_mode == JFJochImageReadingWorker::AutoloadMode::HTTPSync)
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::None);
else
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::HTTPSync);
}
void JFJochViewerToolbarImage::movieButtonPressed() {
if (movie_button->isChecked())
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::Movie);
else
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::None);
}
void JFJochViewerToolbarImage::setAutoloadMode(JFJochImageReadingWorker::AutoloadMode input) {
autoload_mode = input;
const bool playing = (input == JFJochImageReadingWorker::AutoloadMode::Movie);
movie_button->setChecked(playing); // checked (navy) = movie running; icon stays the camera
movie_button->setToolTip(playing ? "Stop movie" : "Play movie");
updateHttpButton();
}