mirror of
https://github.com/pyapp-kit/superqt.git
synced 2025-07-21 04:01:07 +02:00
Compare commits
4 Commits
v0.7.4
...
17fd211740
Author | SHA1 | Date | |
---|---|---|---|
|
17fd211740 | ||
|
3b83a8a1e2 | ||
|
13e033e4a2 | ||
|
55b66393c3 |
16
.github/workflows/test_and_deploy.yml
vendored
16
.github/workflows/test_and_deploy.yml
vendored
@@ -87,22 +87,6 @@ jobs:
|
||||
uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v2
|
||||
secrets: inherit
|
||||
|
||||
test_napari_old:
|
||||
uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v2
|
||||
with:
|
||||
dependency-repo: napari/napari
|
||||
dependency-ref: ${{ matrix.napari-version }}
|
||||
dependency-extras: "testing"
|
||||
qt: ${{ matrix.qt }}
|
||||
pytest-args: 'napari/_qt -k "not async and not qt_dims_2 and not qt_viewer_console_focus and not keybinding_editor and not preferences_dialog_not_dismissed"'
|
||||
python-version: "3.10"
|
||||
post-install-cmd: "pip install lxml_html_clean"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
napari-version: ["v0.4.19.post1"]
|
||||
qt: ["pyqt5", "pyside2"]
|
||||
|
||||
test_napari:
|
||||
uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v2
|
||||
with:
|
||||
|
@@ -5,7 +5,7 @@ ci:
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.12
|
||||
rev: v0.12.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --unsafe-fixes]
|
||||
@@ -17,7 +17,7 @@ repos:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.16.0
|
||||
rev: v1.17.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
exclude: tests|examples
|
||||
|
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [v0.7.5](https://github.com/pyapp-kit/superqt/tree/v0.7.5) (2025-06-18)
|
||||
|
||||
[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.7.4...v0.7.5)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- feat: Use scientific notation for big values in labeled slider [\#226](https://github.com/pyapp-kit/superqt/pull/226) ([Czaki](https://github.com/Czaki))
|
||||
|
||||
## [v0.7.4](https://github.com/pyapp-kit/superqt/tree/v0.7.4) (2025-06-10)
|
||||
|
||||
[Full Changelog](https://github.com/pyapp-kit/superqt/compare/v0.7.3...v0.7.4)
|
||||
|
@@ -27,11 +27,12 @@ qlds.setValue(0.5)
|
||||
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))
|
||||
|
@@ -51,7 +51,7 @@ test = [
|
||||
"pint",
|
||||
"pytest",
|
||||
"pytest-cov",
|
||||
"pytest-qt",
|
||||
"pytest-qt==4.4.0",
|
||||
"numpy",
|
||||
"cmap",
|
||||
"pyconify",
|
||||
|
@@ -70,7 +70,7 @@ class _GenericEliding:
|
||||
text = self._wrappedText()
|
||||
last_line = fm.elidedText("".join(text[nlines:]), self._elide_mode, width)
|
||||
# join them
|
||||
return "".join(text[:nlines] + [last_line])
|
||||
return "".join([*text[:nlines], last_line])
|
||||
|
||||
def _wrappedText(self) -> list[str]:
|
||||
return _GenericEliding.wrapText(self._text, self.width(), self.font())
|
||||
|
@@ -1,20 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from enum import IntEnum, IntFlag, auto
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, overload
|
||||
|
||||
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,
|
||||
@@ -660,7 +658,7 @@ class QLabeledDoubleRangeSlider(QLabeledRangeSlider):
|
||||
"""The color of the bar between the first and last handle."""
|
||||
|
||||
|
||||
class SliderLabel(QDoubleSpinBox):
|
||||
class SliderLabel(QLineEdit):
|
||||
def __init__(
|
||||
self,
|
||||
slider: QSlider,
|
||||
@@ -670,52 +668,139 @@ class SliderLabel(QDoubleSpinBox):
|
||||
) -> 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._decimals = -1
|
||||
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._editing_finished)
|
||||
self.editingFinished.connect(self._silent_clear_focus)
|
||||
self._update_size()
|
||||
|
||||
def _editing_finished(self):
|
||||
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 self._mode == EdgeLabelMode.LabelIsRange:
|
||||
max_val = max(abs(min_), abs(max_))
|
||||
n_digits = max(len(str(int(max_val))), 7)
|
||||
upper_bound = int("9" * n_digits)
|
||||
self._min = -upper_bound
|
||||
self._max = upper_bound
|
||||
self._update_size()
|
||||
else:
|
||||
max_ = max(max_, min_)
|
||||
self._min = min_
|
||||
self._max = max_
|
||||
|
||||
def setDecimals(self, prec: int) -> None:
|
||||
super().setDecimals(prec)
|
||||
# super().setDecimals(prec)
|
||||
self._decimals = prec
|
||||
self._update_size()
|
||||
|
||||
def decimals(self) -> int:
|
||||
"""Return the number of decimals used in the label."""
|
||||
return self._decimals
|
||||
|
||||
def value(self) -> float:
|
||||
return self._value
|
||||
|
||||
def setValue(self, val: Any) -> None:
|
||||
super().setValue(val)
|
||||
if val < self._min:
|
||||
val = self._min
|
||||
elif val > self._max:
|
||||
val = self._max
|
||||
self._value = val
|
||||
self.updateText()
|
||||
|
||||
def updateText(self) -> None:
|
||||
val = float(self._value)
|
||||
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 self._decimals < 0:
|
||||
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(".")
|
||||
else:
|
||||
if use_scientific:
|
||||
mantissa, exponent = f"{val:.{self._decimals}e}".split("e")
|
||||
mantissa = mantissa.rstrip("0").rstrip(".")
|
||||
text = f"{mantissa}e{exponent}"
|
||||
else:
|
||||
text = f"{val:.{self._decimals}f}"
|
||||
if text == "":
|
||||
text = "0"
|
||||
self.setText(text)
|
||||
if self._mode == EdgeLabelMode.LabelIsRange:
|
||||
self._update_size()
|
||||
|
||||
def setMaximum(self, max: float) -> None:
|
||||
super().setMaximum(max)
|
||||
if self._mode == EdgeLabelMode.LabelIsValue:
|
||||
self._update_size()
|
||||
def minimum(self):
|
||||
return self._min
|
||||
|
||||
def setMinimum(self, min: float) -> None:
|
||||
super().setMinimum(min)
|
||||
if self._mode == EdgeLabelMode.LabelIsValue:
|
||||
self._update_size()
|
||||
def setMaximum(self, max_: float) -> None:
|
||||
self.setRange(self._min, max_)
|
||||
|
||||
def maximum(self):
|
||||
return self._max
|
||||
|
||||
def setMinimum(self, min_: float) -> None:
|
||||
self.setRange(min_, self._max)
|
||||
|
||||
def setMode(self, opt: EdgeLabelMode) -> None:
|
||||
# when the edge labels are controlling slider range,
|
||||
# we want them to have a big range, but not have a huge label
|
||||
self._mode = opt
|
||||
if opt == EdgeLabelMode.LabelIsRange:
|
||||
self.setMinimum(-9999999)
|
||||
self.setMaximum(9999999)
|
||||
with contextlib.suppress(Exception):
|
||||
self._slider.rangeChanged.disconnect(self.setRange)
|
||||
else:
|
||||
self.setMinimum(self._slider.minimum())
|
||||
self.setMaximum(self._slider.maximum())
|
||||
self._slider.rangeChanged.connect(self.setRange)
|
||||
self.setRange(self._slider.minimum(), self._slider.maximum())
|
||||
self._update_size()
|
||||
|
||||
def prefix(self) -> str:
|
||||
return self._prefix
|
||||
|
||||
def setPrefix(self, prefix: str) -> None:
|
||||
self._prefix = prefix
|
||||
self._update_size()
|
||||
|
||||
def suffix(self) -> str:
|
||||
return self._suffix
|
||||
|
||||
def setSuffix(self, suffix: str) -> None:
|
||||
self._suffix = suffix
|
||||
self._update_size()
|
||||
|
||||
# --------------- private ----------------
|
||||
@@ -732,21 +817,19 @@ class SliderLabel(QDoubleSpinBox):
|
||||
|
||||
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
|
||||
)
|
||||
|
Reference in New Issue
Block a user