Skip to content

feat: Use scientific notation for big values in labeled slider #226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/labeled_sliders.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@
qlds.setSingleStep(0.1)

qlrs = QLabeledRangeSlider(ORIENTATION)
qlrs.valueChanged.connect(lambda e: print("QLabeledRangeSlider valueChanged", e))
qlrs.setValue((20, 60))
qlrs.valueChanged.connect(lambda e: print("qlrs valueChanged", e))
qlrs.setRange(0, 10**11)
qlrs.setValue((20, 60 * 10**9))

qldrs = QLabeledDoubleRangeSlider(ORIENTATION)
qldrs.valueChanged.connect(lambda e: print("qlrs valueChanged", e))
qldrs.valueChanged.connect(lambda e: print("qldrs valueChanged", e))
qldrs.setRange(0, 1)
qldrs.setSingleStep(0.01)
qldrs.setValue((0.2, 0.7))
Expand Down
113 changes: 94 additions & 19 deletions src/superqt/sliders/_labeled.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@

from qtpy import QtGui
from qtpy.QtCore import Property, QPoint, QSize, Qt, Signal
from qtpy.QtGui import QFontMetrics, QValidator
from qtpy.QtGui import QDoubleValidator, QFontMetrics, QValidator
from qtpy.QtWidgets import (
QAbstractSlider,
QBoxLayout,
QDoubleSpinBox,
QHBoxLayout,
QLineEdit,
QSlider,
QSpinBox,
QStyle,
QStyleOptionSpinBox,
QVBoxLayout,
Expand Down Expand Up @@ -629,7 +628,7 @@
"""The color of the bar between the first and last handle."""


class SliderLabel(QDoubleSpinBox):
class SliderLabel(QLineEdit):
def __init__(
self,
slider: QSlider,
Expand All @@ -639,36 +638,100 @@
) -> None:
super().__init__(parent=parent)
self._slider = slider
self._prefix = ""
self._suffix = ""
self._min = slider.minimum()
self._max = slider.maximum()
self._value = self._min
self._callback = connect
self.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
self.setMode(EdgeLabelMode.LabelIsValue)
self.setDecimals(0)
self.setText(str(self._value))
validator = QDoubleValidator(self)
validator.setNotation(QDoubleValidator.Notation.ScientificNotation)
self.setValidator(validator)

self.setRange(slider.minimum(), slider.maximum())
slider.rangeChanged.connect(self._update_size)
self.setAlignment(alignment)
self.setButtonSymbols(QSpinBox.ButtonSymbols.NoButtons)
self.setStyleSheet("background:transparent; border: 0;")
if connect is not None:
self.editingFinished.connect(lambda: connect(self.value()))
self.editingFinished.connect(self._editig_finished)
self.editingFinished.connect(self._silent_clear_focus)
self._update_size()

def _editig_finished(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo _editig_finished -> _editing_finished

self._silent_clear_focus()
self.setValue(float(self.text()))
if self._callback:
self._callback(self.value())

def setRange(self, min_: float, max_: float) -> None:
if max_ < min_:
max_ = min_

Check warning on line 671 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L671

Added line #L671 was not covered by tests
self._min = min_
self._max = max_

def setDecimals(self, prec: int) -> None:
super().setDecimals(prec)
# super().setDecimals(prec)
self._update_size()

def value(self) -> float:
return self._value

def setValue(self, val: Any) -> None:
super().setValue(val)
if val < self._min:
val = self._min

Check warning on line 684 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L684

Added line #L684 was not covered by tests
elif val > self._max:
val = self._max
self._value = val
val = float(val)
use_scientific = (abs(val) < 0.0001 or abs(val) > 9999999.0) and val != 0.0
font_metrics = QFontMetrics(self.font())
eight_len = _fm_width(font_metrics, "8")

available_chars = self.width() // eight_len

total, _fraction = f"{val:.<f}".split(".")

if len(total) > available_chars:
use_scientific = True

if use_scientific:
mantissa, exponent = f"{val:.{available_chars}e}".split("e")
mantissa = mantissa.rstrip("0").rstrip(".")
if len(mantissa) + len(exponent) + 1 < available_chars:
text = f"{mantissa}e{exponent}"
else:
decimals = max(available_chars - len(exponent) - 3, 2)
text = f"{val:.{decimals}e}"

else:
decimals = max(available_chars - len(total) - 1, 2)
text = f"{val:.{decimals}f}"
text = text.rstrip("0").rstrip(".")

self.setText(text)
if self._mode == EdgeLabelMode.LabelIsRange:
self._update_size()

def setMaximum(self, max: float) -> None:
super().setMaximum(max)
def minimum(self):
return self._min

def setMaximum(self, max_: float) -> None:
if max_ < self._min:
max_ = self._min

Check warning on line 723 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L723

Added line #L723 was not covered by tests
self._max = max_
if self._mode == EdgeLabelMode.LabelIsValue:
self._update_size()

def setMinimum(self, min: float) -> None:
super().setMinimum(min)
def maximum(self):
return self._max

def setMinimum(self, min_: float) -> None:
if min_ > self._max:
min_ = self._max

Check warning on line 733 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L733

Added line #L733 was not covered by tests
self._min = min_
if self._mode == EdgeLabelMode.LabelIsValue:
self._update_size()

Expand All @@ -687,6 +750,20 @@
self._slider.rangeChanged.connect(self.setRange)
self._update_size()

def prefix(self) -> str:
return self._prefix

def setPrefix(self, prefix: str) -> None:
self._prefix = prefix
self._update_size()

Check warning on line 758 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L757-L758

Added lines #L757 - L758 were not covered by tests

def suffix(self) -> str:
return self._suffix

def setSuffix(self, suffix: str) -> None:
self._suffix = suffix
self._update_size()

Check warning on line 765 in src/superqt/sliders/_labeled.py

View check run for this annotation

Codecov / codecov/patch

src/superqt/sliders/_labeled.py#L764-L765

Added lines #L764 - L765 were not covered by tests

# --------------- private ----------------

def _silent_clear_focus(self) -> None:
Expand All @@ -701,21 +778,19 @@

if self._mode & EdgeLabelMode.LabelIsValue:
# determine width based on min/max/specialValue
mintext = self.textFromValue(self.minimum())[:18]
maxtext = self.textFromValue(self.maximum())[:18]
mintext = str(self.minimum())[:18]
maxtext = str(self.maximum())[:18]
w = max(0, _fm_width(fm, mintext + fixed_content))
w = max(w, _fm_width(fm, maxtext + fixed_content))
if self.specialValueText():
w = max(w, _fm_width(fm, self.specialValueText()))
if self._mode & EdgeLabelMode.LabelIsRange:
w += 8 # it seems as thought suffix() is not enough
else:
w = max(0, _fm_width(fm, self.textFromValue(self.value()))) + 3
w = max(0, _fm_width(fm, str(self.value()))) + 3

w += 3 # cursor blinking space
# get the final size hint
opt = QStyleOptionSpinBox()
self.initStyleOption(opt)
# self.initStyleOption(opt)
size = self.style().sizeFromContents(
QStyle.ContentsType.CT_SpinBox, opt, QSize(w, h), self
)
Expand Down
Loading