// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #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"); if (m_scaleType == Logarithmic && m_min <= 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Minimum value must be greater than zero for logarithmic scale"); 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_doubleSpinBox->setStyleSheet("background-color: rgb(255, 255, 255);"); 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::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(m_min / m_step), static_cast(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 // Map from slider position (0-1000) to logarithmic value range double logMin = std::log10(m_min); double logMax = std::log10(m_max); double normalizedPos = static_cast(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(value / m_step); } else { // Logarithmic // Map from value to slider position (0-1000) double logMin = std::log10(m_min); double logMax = std::log10(m_max); double logValue = std::log10(std::max(value, m_min)); // Ensure we don't go below min double normalizedPos = (logValue - logMin) / (logMax - logMin); return static_cast(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::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); }