pyqtgraph/pyqtgraph/parametertree/parameterTypes/slider.py

140 lines
4.6 KiB
Python

import numpy as np
from .basetypes import WidgetParameterItem
from .. import Parameter
from ...Qt import QtCore, QtWidgets
class Emitter(QtCore.QObject):
"""
WidgetParameterItem is not a QObject, and the slider's value needs to be converted before
emitting. So, create an emitter class here that can be used instead
"""
sigChanging = QtCore.Signal(object, object)
sigChanged = QtCore.Signal(object, object)
class SliderParameterItem(WidgetParameterItem):
slider: QtWidgets.QSlider
span: np.ndarray
charSpan: np.ndarray
def __init__(self, param, depth):
# Bind emitter to self to avoid garbage collection
self.emitter = Emitter()
self.sigChanging = self.emitter.sigChanging
self._suffix = None
super().__init__(param, depth)
def updateDisplayLabel(self, value=None):
if value is None:
value = self.param.value()
value = str(value)
if self._suffix is None:
suffixTxt = ''
else:
suffixTxt = f' {self._suffix}'
self.displayLabel.setText(value + suffixTxt)
def setSuffix(self, suffix):
self._suffix = suffix
self._updateLabel(self.slider.value())
def makeWidget(self):
param = self.param
opts = param.opts
self._suffix = opts.get('suffix')
self.slider = QtWidgets.QSlider()
self.slider.setOrientation(QtCore.Qt.Orientation.Horizontal)
lbl = QtWidgets.QLabel()
lbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
w = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout()
w.setLayout(layout)
layout.addWidget(lbl)
layout.addWidget(self.slider)
def setValue(v):
self.slider.setValue(self.spanToSliderValue(v))
def getValue():
return self.span[self.slider.value()].item()
def vChanged(v):
lbl.setText(self.prettyTextValue(v))
self.slider.valueChanged.connect(vChanged)
def onMove(pos):
self.sigChanging.emit(self, self.span[pos].item())
self.slider.sliderMoved.connect(onMove)
w.setValue = setValue
w.value = getValue
w.sigChanged = self.slider.valueChanged
w.sigChanging = self.sigChanging
self.optsChanged(param, opts)
return w
def spanToSliderValue(self, v):
return int(np.argmin(np.abs(self.span - v)))
def prettyTextValue(self, v):
if self._suffix is None:
suffixTxt = ''
else:
suffixTxt = f' {self._suffix}'
format_ = self.param.opts.get('format', None)
cspan = self.charSpan
if format_ is None:
format_ = f'{{0:>{cspan.dtype.itemsize}}}{suffixTxt}'
return format_.format(cspan[v].decode())
def optsChanged(self, param, opts):
try:
super().optsChanged(param, opts)
except AttributeError:
# This may trigger while building the parameter before the widget is fully constructed.
# This is fine, since errors are from the parent scope which will stabilize after the widget is
# constructed anyway
pass
span = opts.get('span', None)
if span is None:
step = opts.get('step', 1)
start, stop = opts['limits']
# Add a bit to 'stop' since python slicing excludes the last value
span = np.arange(start, stop + step, step)
precision = opts.get('precision', 2)
if precision is not None:
span = span.round(precision)
self.span = span
self.charSpan = np.char.array(span)
w = self.slider
w.setMinimum(0)
w.setMaximum(len(span) - 1)
if 'suffix' in opts:
self.setSuffix(opts['suffix'])
self.slider.valueChanged.emit(self.slider.value())
def limitsChanged(self, param, limits):
self.optsChanged(param, dict(limits=limits))
class SliderParameter(Parameter):
"""
============== ========================================================
**Options**
limits [start, stop] numbers
step: Defaults to 1, the spacing between each slider tick
span: Instead of limits + step, span can be set to specify
the range of slider options (e.g. np.linspace(-pi, pi, 100))
format: Format string to determine number of decimals to show, etc.
Defaults to display based on span dtype
precision: int number of decimals to keep for float tick spaces
============== ========================================================
"""
itemClass = SliderParameterItem