Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports. * jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls. * Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results. * Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable. * Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate. * Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do. * Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence. * Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags. * Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check. * Rugnux: Clear error messages when a data set needs more GPU or host memory than is available. Reviewed-on: #83 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
165 lines
5.8 KiB
C++
165 lines
5.8 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <cmath>
|
|
|
|
#include "SliderPlusBox.h"
|
|
#include "../common/JFJochException.h"
|
|
SliderPlusBox::SliderPlusBox(double min, double max, double step, int decimals, QWidget *parent, ScaleType scaleType)
|
|
: QWidget(parent), v(min), m_step(step), m_min(min), m_max(max), m_decimals(decimals),
|
|
m_pendingSliderValue(0), m_sliderDragging(false), m_scaleType(scaleType) {
|
|
if (step <= 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Scale factor for SliderPlusBox must be positive");
|
|
|
|
// A logarithmic scale accepts a minimum of exactly zero: slider position 0 means 0 ("off"),
|
|
// and the rest of the track spans [1, max] logarithmically.
|
|
if (m_scaleType == Logarithmic && m_min < 0)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Minimum value must not be negative for logarithmic scale");
|
|
|
|
if (m_max <= m_min)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Maximum value must be greater than minimum");
|
|
|
|
QHBoxLayout* layout = new QHBoxLayout(this);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_slider = new QSlider(Qt::Horizontal, this);
|
|
updateSliderRange();
|
|
|
|
m_doubleSpinBox = new QDoubleSpinBox(this);
|
|
m_doubleSpinBox->setRange(min, max);
|
|
m_doubleSpinBox->setDecimals(decimals);
|
|
m_doubleSpinBox->setSingleStep(step);
|
|
m_doubleSpinBox->setValue(v);
|
|
|
|
m_updateTimer = new QTimer(this);
|
|
m_updateTimer->setInterval(500); // 500ms throttle
|
|
m_updateTimer->setSingleShot(false);
|
|
|
|
layout->addWidget(m_slider);
|
|
layout->addWidget(m_doubleSpinBox);
|
|
|
|
connect(m_slider, &QSlider::valueChanged, [this](int value) {
|
|
const QSignalBlocker blocker(m_doubleSpinBox);
|
|
m_pendingSliderValue = value;
|
|
double realValue = sliderToValue(value);
|
|
m_doubleSpinBox->setValue(realValue);
|
|
if (!m_updateTimer->isActive() && m_sliderDragging)
|
|
m_updateTimer->start();
|
|
}
|
|
);
|
|
|
|
connect(m_updateTimer, &QTimer::timeout, [this]() {
|
|
updateFromSlider(m_pendingSliderValue);
|
|
});
|
|
|
|
connect(m_slider, &QSlider::sliderPressed, [this]() {
|
|
m_sliderDragging = true;
|
|
});
|
|
|
|
connect(m_slider, &QSlider::sliderReleased, [this]() {
|
|
m_sliderDragging = false;
|
|
m_updateTimer->stop();
|
|
updateFromSlider(m_slider->value());
|
|
});
|
|
|
|
connect(m_doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
|
|
[this](double value) {
|
|
const QSignalBlocker blocker(m_slider);
|
|
int sliderValue = valueToSlider(value);
|
|
m_slider->setValue(sliderValue);
|
|
v = value;
|
|
emit valueChanged(v);
|
|
});
|
|
|
|
setLayout(layout);
|
|
}
|
|
|
|
void SliderPlusBox::updateSliderRange() {
|
|
if (m_scaleType == Linear) {
|
|
m_slider->setRange(static_cast<int>(m_min / m_step), static_cast<int>(m_max / m_step));
|
|
m_slider->setValue(valueToSlider(v));
|
|
} else { // Logarithmic
|
|
// Use a higher resolution for logarithmic scale (1000 steps)
|
|
m_slider->setRange(0, 1000);
|
|
m_slider->setValue(valueToSlider(v));
|
|
}
|
|
}
|
|
|
|
double SliderPlusBox::sliderToValue(int sliderValue) const {
|
|
if (m_scaleType == Linear) {
|
|
return sliderValue * m_step;
|
|
} else { // Logarithmic
|
|
const double lo = (m_min > 0.0) ? m_min : 1.0;
|
|
if (m_min <= 0.0 && sliderValue <= 0)
|
|
return 0.0;
|
|
// Map from slider position (0-1000) to logarithmic value range
|
|
double logMin = std::log10(lo);
|
|
double logMax = std::log10(m_max);
|
|
double normalizedPos = static_cast<double>(sliderValue) / 1000.0;
|
|
double logValue = logMin + normalizedPos * (logMax - logMin);
|
|
return std::pow(10.0, logValue);
|
|
}
|
|
}
|
|
|
|
int SliderPlusBox::valueToSlider(double value) const {
|
|
if (m_scaleType == Linear) {
|
|
return static_cast<int>(value / m_step);
|
|
} else { // Logarithmic
|
|
const double lo = (m_min > 0.0) ? m_min : 1.0;
|
|
if (m_min <= 0.0 && value < lo)
|
|
return 0;
|
|
// Map from value to slider position (0-1000)
|
|
double logMin = std::log10(lo);
|
|
double logMax = std::log10(m_max);
|
|
double logValue = std::log10(std::max(value, lo)); // Ensure we don't go below min
|
|
double normalizedPos = (logValue - logMin) / (logMax - logMin);
|
|
return static_cast<int>(normalizedPos * 1000.0);
|
|
}
|
|
}
|
|
|
|
void SliderPlusBox::updateFromSlider(int sliderValue) {
|
|
// Update our internal value and emit the signal
|
|
v = sliderToValue(sliderValue);
|
|
emit valueChanged(v);
|
|
}
|
|
|
|
void SliderPlusBox::setValue(double value) {
|
|
m_doubleSpinBox->setValue(value);
|
|
}
|
|
|
|
void SliderPlusBox::setMax(double value) {
|
|
if (value <= m_min)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Maximum value must be greater than minimum");
|
|
if (value >= v) {
|
|
// if current value is smaller than new max, don't do anything
|
|
m_max = value;
|
|
m_doubleSpinBox->setRange(m_min, m_max);
|
|
updateSliderRange();
|
|
}
|
|
}
|
|
|
|
void SliderPlusBox::setScaleType(ScaleType type) {
|
|
if (m_scaleType == type)
|
|
return;
|
|
|
|
if (type == Logarithmic && m_min < 0) {
|
|
qWarning() << "Cannot switch to logarithmic scale with min < 0";
|
|
return;
|
|
}
|
|
|
|
m_scaleType = type;
|
|
|
|
// Preserve the current value during scale type change
|
|
double currentValue = v;
|
|
|
|
// Update the slider range for the new scale type
|
|
updateSliderRange();
|
|
|
|
// Reset the value
|
|
setValue(currentValue);
|
|
}
|