Organize paramtypes (#1919)
* Registered parameter types go in their own files * Moves [int, float] item definitions outside `WidgetParameterItem` * Moves [int, float] parameter definitions outside `WidgetParameterItem` * Allow registering ParameterItems for easy parameter defs * Finalizes moving simple parameters to their own files * removes accidentally committed file * Provides class qualnames in rst * Address docstring build issues * Address recent review comments - `registerParameterItemType`: * added to docs and parametertree.__init__ * Remove unsed PARAM_TYPES global * Hyperlink to `registerParameterType` - parameter tree rst: * Alphabetize entries * Rebuild RST without fully qualified class name * Add note at file header that it is auto generated * Remove spurious space during rst doc creation * Ensure created/modified files end with newline * Address CodeQL warnings * toPlainText also returns str * `QTreeWidgetItem.text` returns str
This commit is contained in:
parent
fb2e684f45
commit
4bf1866c2a
@ -3,6 +3,8 @@ Parameter
|
||||
|
||||
.. autofunction:: pyqtgraph.parametertree.registerParameterType
|
||||
|
||||
.. autofunction:: pyqtgraph.parametertree.registerParameterItemType
|
||||
|
||||
.. autoclass:: pyqtgraph.parametertree.Parameter
|
||||
:members:
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
..
|
||||
This file is auto-generated from pyqtgraph/tools/rebuildPtreeRst.py. Do not modify by hand! Instead, rerun the
|
||||
generation script with `python pyqtgraph/tools/rebuildPtreeRst.py`.
|
||||
|
||||
Built-in Parameter Types
|
||||
========================
|
||||
|
||||
@ -6,7 +10,22 @@ Built-in Parameter Types
|
||||
Parameters
|
||||
----------
|
||||
|
||||
.. autoclass:: SimpleParameter
|
||||
.. autoclass:: ActionParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorMapParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: GroupParameter
|
||||
@ -15,34 +34,43 @@ Parameters
|
||||
.. autoclass:: ListParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ActionParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameter
|
||||
.. autoclass:: PenParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: ProgressBarParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: PenParameter
|
||||
.. autoclass:: SimpleParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: SliderParameter
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameter
|
||||
:members:
|
||||
|
||||
ParameterItems
|
||||
--------------
|
||||
|
||||
.. autoclass:: WidgetParameterItem
|
||||
.. autoclass:: ActionParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: BoolParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorMapParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ColorParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: GroupParameterItem
|
||||
@ -51,23 +79,20 @@ ParameterItems
|
||||
.. autoclass:: ListParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FileParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: CalendarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ProgressBarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: FontParameterItem
|
||||
.. autoclass:: NumericParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: PenParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: ProgressBarParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: SliderParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: StrParameterItem
|
||||
:members:
|
||||
|
||||
.. autoclass:: TextParameterItem
|
||||
:members:
|
||||
|
@ -9,6 +9,24 @@ import warnings
|
||||
PARAM_TYPES = {}
|
||||
PARAM_NAMES = {}
|
||||
|
||||
_PARAM_ITEM_TYPES = {}
|
||||
|
||||
def registerParameterItemType(name, itemCls, parameterCls=None, override=False):
|
||||
"""
|
||||
Similar to :func:`registerParameterType`, but works on ParameterItems. This is useful for Parameters where the
|
||||
`itemClass` does all the heavy lifting, and a redundant Parameter class must be defined just to house `itemClass`.
|
||||
Instead, use `registerParameterItemType`. If this should belong to a subclass of `Parameter`, specify which one
|
||||
in `parameterCls`.
|
||||
"""
|
||||
global _PARAM_ITEM_TYPES
|
||||
if name in _PARAM_ITEM_TYPES and not override:
|
||||
raise Exception("Parameter item type '%s' already exists (use override=True to replace)" % name)
|
||||
|
||||
parameterCls = parameterCls or Parameter
|
||||
_PARAM_ITEM_TYPES[name] = itemCls
|
||||
registerParameterType(name, parameterCls, override)
|
||||
|
||||
|
||||
def registerParameterType(name, cls, override=False):
|
||||
"""Register a parameter type in the parametertree system.
|
||||
|
||||
@ -68,6 +86,8 @@ class Parameter(QtCore.QObject):
|
||||
## name, type, limits, etc.
|
||||
## can also carry UI hints (slider vs spinbox, etc.)
|
||||
|
||||
itemClass = None
|
||||
|
||||
sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited
|
||||
sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited
|
||||
|
||||
@ -545,11 +565,10 @@ class Parameter(QtCore.QObject):
|
||||
to display this Parameter.
|
||||
Most subclasses will want to override this function.
|
||||
"""
|
||||
if hasattr(self, 'itemClass'):
|
||||
#print "Param:", self, "Make item from itemClass:", self.itemClass
|
||||
return self.itemClass(self, depth)
|
||||
else:
|
||||
return ParameterItem(self, depth=depth)
|
||||
# Default to user-specified itemClass. If not present, check for a registered item class. Finally,
|
||||
# revert to ParameterItem if both fail
|
||||
itemClass = self.itemClass or _PARAM_ITEM_TYPES.get(self.opts['type'], ParameterItem)
|
||||
return itemClass(self, depth)
|
||||
|
||||
|
||||
def addChild(self, child, autoIncrementName=None):
|
||||
|
@ -137,7 +137,7 @@ class ParameterItem(QtGui.QTreeWidgetItem):
|
||||
if self.ignoreNameColumnChange:
|
||||
return
|
||||
try:
|
||||
newName = self.param.setName(str(self.text(col)))
|
||||
newName = self.param.setName(self.text(col))
|
||||
except Exception:
|
||||
self.setText(0, self.param.name())
|
||||
raise
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .Parameter import Parameter, registerParameterType
|
||||
from .Parameter import Parameter, registerParameterType, registerParameterItemType
|
||||
from .ParameterTree import ParameterTree
|
||||
from .ParameterItem import ParameterItem
|
||||
from .ParameterSystem import ParameterSystem, SystemSolver
|
||||
|
File diff suppressed because it is too large
Load Diff
37
pyqtgraph/parametertree/parameterTypes/__init__.py
Normal file
37
pyqtgraph/parametertree/parameterTypes/__init__.py
Normal file
@ -0,0 +1,37 @@
|
||||
from .action import ActionParameter, ActionParameterItem
|
||||
from .basetypes import WidgetParameterItem, SimpleParameter, GroupParameter, GroupParameterItem
|
||||
from .bool import BoolParameterItem
|
||||
from .calendar import CalendarParameter, CalendarParameterItem
|
||||
from .color import ColorParameter, ColorParameterItem
|
||||
from .colormap import ColorMapParameter, ColorMapParameterItem
|
||||
from .file import FileParameter, FileParameterItem
|
||||
from .font import FontParameter, FontParameterItem
|
||||
from .list import ListParameter, ListParameterItem
|
||||
from .numeric import NumericParameterItem
|
||||
from .pen import PenParameter, PenParameterItem
|
||||
from .progress import ProgressBarParameter, ProgressBarParameterItem
|
||||
from .qtenum import QtEnumParameter
|
||||
from .slider import SliderParameter, SliderParameterItem
|
||||
from .str import StrParameterItem
|
||||
from .text import TextParameter, TextParameterItem
|
||||
from ..Parameter import registerParameterType, registerParameterItemType
|
||||
|
||||
registerParameterItemType('int', NumericParameterItem, SimpleParameter, override=True)
|
||||
registerParameterItemType('float', NumericParameterItem, SimpleParameter, override=True)
|
||||
registerParameterItemType('bool', BoolParameterItem, SimpleParameter, override=True)
|
||||
registerParameterItemType('str', StrParameterItem, SimpleParameter, override=True)
|
||||
|
||||
registerParameterType('group', GroupParameter, override=True)
|
||||
|
||||
registerParameterType('color', ColorParameter, override=True)
|
||||
registerParameterType('colormap', ColorMapParameter, override=True)
|
||||
registerParameterType('list', ListParameter, override=True)
|
||||
registerParameterType('action', ActionParameter, override=True)
|
||||
registerParameterType('text', TextParameter, override=True)
|
||||
registerParameterType('pen', PenParameter, override=True)
|
||||
registerParameterType('progress', ProgressBarParameter, override=True)
|
||||
registerParameterType('file', FileParameter, override=True)
|
||||
registerParameterType('slider', SliderParameter, override=True)
|
||||
registerParameterType('calendar', CalendarParameter, override=True)
|
||||
registerParameterType('font', FontParameter, override=True)
|
||||
# qtenum is a bit specific, hold off on registering for now
|
57
pyqtgraph/parametertree/parameterTypes/action.py
Normal file
57
pyqtgraph/parametertree/parameterTypes/action.py
Normal file
@ -0,0 +1,57 @@
|
||||
from .. import ParameterItem, Parameter
|
||||
from ...Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class ActionParameterItem(ParameterItem):
|
||||
"""ParameterItem displaying a clickable button."""
|
||||
def __init__(self, param, depth):
|
||||
ParameterItem.__init__(self, param, depth)
|
||||
self.layoutWidget = QtWidgets.QWidget()
|
||||
self.layout = QtWidgets.QHBoxLayout()
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layoutWidget.setLayout(self.layout)
|
||||
self.button = QtWidgets.QPushButton()
|
||||
#self.layout.addSpacing(100)
|
||||
self.layout.addWidget(self.button)
|
||||
self.layout.addStretch()
|
||||
self.button.clicked.connect(self.buttonClicked)
|
||||
self.titleChanged()
|
||||
self.optsChanged(self.param, self.param.opts)
|
||||
|
||||
def treeWidgetChanged(self):
|
||||
ParameterItem.treeWidgetChanged(self)
|
||||
tree = self.treeWidget()
|
||||
if tree is None:
|
||||
return
|
||||
|
||||
self.setFirstColumnSpanned(True)
|
||||
tree.setItemWidget(self, 0, self.layoutWidget)
|
||||
|
||||
def titleChanged(self):
|
||||
self.button.setText(self.param.title())
|
||||
self.setSizeHint(0, self.button.sizeHint())
|
||||
|
||||
def optsChanged(self, param, opts):
|
||||
ParameterItem.optsChanged(self, param, opts)
|
||||
|
||||
if 'enabled' in opts:
|
||||
self.button.setEnabled(opts['enabled'])
|
||||
|
||||
if 'tip' in opts:
|
||||
self.button.setToolTip(opts['tip'])
|
||||
|
||||
def buttonClicked(self):
|
||||
self.param.activate()
|
||||
|
||||
|
||||
class ActionParameter(Parameter):
|
||||
"""Used for displaying a button within the tree.
|
||||
|
||||
``sigActivated(self)`` is emitted when the button is clicked.
|
||||
"""
|
||||
itemClass = ActionParameterItem
|
||||
sigActivated = QtCore.Signal(object)
|
||||
|
||||
def activate(self):
|
||||
self.sigActivated.emit(self)
|
||||
self.emitStateChanged('activated', None)
|
421
pyqtgraph/parametertree/parameterTypes/basetypes.py
Normal file
421
pyqtgraph/parametertree/parameterTypes/basetypes.py
Normal file
@ -0,0 +1,421 @@
|
||||
import builtins
|
||||
|
||||
from .. import ParameterItem, Parameter
|
||||
from ... import functions as fn
|
||||
from ...Qt import QtWidgets, QtCore, QtGui
|
||||
from ... import icons
|
||||
|
||||
class WidgetParameterItem(ParameterItem):
|
||||
"""
|
||||
ParameterTree item with:
|
||||
|
||||
* label in second column for displaying value
|
||||
* simple widget for editing value (displayed instead of label when item is selected)
|
||||
* button that resets value to default
|
||||
|
||||
This class can be subclassed by overriding makeWidget() to provide a custom widget.
|
||||
"""
|
||||
def __init__(self, param, depth):
|
||||
ParameterItem.__init__(self, param, depth)
|
||||
|
||||
self.asSubItem = False # place in a child item's column 0 instead of column 1
|
||||
self.hideWidget = True ## hide edit widget, replace with label when not selected
|
||||
## set this to False to keep the editor widget always visible
|
||||
|
||||
# build widget with a display label and default button
|
||||
w = self.makeWidget()
|
||||
self.widget = w
|
||||
self.eventProxy = EventProxy(w, self.widgetEventFilter)
|
||||
|
||||
if self.asSubItem:
|
||||
self.subItem = QtWidgets.QTreeWidgetItem()
|
||||
self.subItem.depth = self.depth + 1
|
||||
self.subItem.setFlags(QtCore.Qt.ItemFlag.NoItemFlags)
|
||||
self.addChild(self.subItem)
|
||||
|
||||
self.defaultBtn = self.makeDefaultButton()
|
||||
|
||||
self.displayLabel = QtWidgets.QLabel()
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(2)
|
||||
if not self.asSubItem:
|
||||
layout.addWidget(w, 1)
|
||||
layout.addWidget(self.displayLabel, 1)
|
||||
layout.addStretch(0)
|
||||
layout.addWidget(self.defaultBtn)
|
||||
self.layoutWidget = QtWidgets.QWidget()
|
||||
self.layoutWidget.setLayout(layout)
|
||||
|
||||
if w.sigChanged is not None:
|
||||
w.sigChanged.connect(self.widgetValueChanged)
|
||||
|
||||
if hasattr(w, 'sigChanging'):
|
||||
w.sigChanging.connect(self.widgetValueChanging)
|
||||
|
||||
## update value shown in widget.
|
||||
opts = self.param.opts
|
||||
if opts.get('value', None) is not None:
|
||||
self.valueChanged(self, opts['value'], force=True)
|
||||
else:
|
||||
## no starting value was given; use whatever the widget has
|
||||
self.widgetValueChanged()
|
||||
|
||||
self.updateDefaultBtn()
|
||||
|
||||
self.optsChanged(self.param, self.param.opts)
|
||||
|
||||
# set size hints
|
||||
sw = self.widget.sizeHint()
|
||||
sb = self.defaultBtn.sizeHint()
|
||||
# shrink row heights a bit for more compact look
|
||||
sw.setHeight(int(sw.height() * 0.9))
|
||||
sb.setHeight(int(sb.height() * 0.9))
|
||||
if self.asSubItem:
|
||||
self.setSizeHint(1, sb)
|
||||
self.subItem.setSizeHint(0, sw)
|
||||
else:
|
||||
w = sw.width() + sb.width()
|
||||
h = max(sw.height(), sb.height())
|
||||
self.setSizeHint(1, QtCore.QSize(w, h))
|
||||
|
||||
def makeWidget(self):
|
||||
"""
|
||||
Return a single widget whose position in the tree is determined by the
|
||||
value of self.asSubItem. If True, it will be placed in the second tree
|
||||
column, and if False, the first tree column of a child item.
|
||||
|
||||
The widget must be given three attributes:
|
||||
|
||||
========== ============================================================
|
||||
sigChanged a signal that is emitted when the widget's value is changed
|
||||
value a function that returns the value
|
||||
setValue a function that sets the value
|
||||
========== ============================================================
|
||||
|
||||
This function must be overridden by a subclass.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def widgetEventFilter(self, obj, ev):
|
||||
## filter widget's events
|
||||
## catch TAB to change focus
|
||||
## catch focusOut to hide editor
|
||||
if ev.type() == ev.Type.KeyPress:
|
||||
if ev.key() == QtCore.Qt.Key.Key_Tab:
|
||||
self.focusNext(forward=True)
|
||||
return True ## don't let anyone else see this event
|
||||
elif ev.key() == QtCore.Qt.Key.Key_Backtab:
|
||||
self.focusNext(forward=False)
|
||||
return True ## don't let anyone else see this event
|
||||
|
||||
return False
|
||||
|
||||
def makeDefaultButton(self):
|
||||
defaultBtn = QtWidgets.QPushButton()
|
||||
defaultBtn.setAutoDefault(False)
|
||||
defaultBtn.setFixedWidth(20)
|
||||
defaultBtn.setFixedHeight(20)
|
||||
defaultBtn.setIcon(icons.getGraphIcon('default'))
|
||||
defaultBtn.clicked.connect(self.defaultClicked)
|
||||
return defaultBtn
|
||||
|
||||
def setFocus(self):
|
||||
self.showEditor()
|
||||
|
||||
def isFocusable(self):
|
||||
return self.param.opts['visible'] and self.param.opts['enabled'] and self.param.writable()
|
||||
|
||||
def valueChanged(self, param, val, force=False):
|
||||
## called when the parameter's value has changed
|
||||
ParameterItem.valueChanged(self, param, val)
|
||||
if force or not fn.eq(val, self.widget.value()):
|
||||
try:
|
||||
if self.widget.sigChanged is not None:
|
||||
self.widget.sigChanged.disconnect(self.widgetValueChanged)
|
||||
self.param.sigValueChanged.disconnect(self.valueChanged)
|
||||
self.widget.setValue(val)
|
||||
self.param.setValue(self.widget.value())
|
||||
finally:
|
||||
if self.widget.sigChanged is not None:
|
||||
self.widget.sigChanged.connect(self.widgetValueChanged)
|
||||
self.param.sigValueChanged.connect(self.valueChanged)
|
||||
self.updateDisplayLabel() ## always make sure label is updated, even if values match!
|
||||
self.updateDefaultBtn()
|
||||
|
||||
def updateDefaultBtn(self):
|
||||
## enable/disable default btn
|
||||
self.defaultBtn.setEnabled(
|
||||
not self.param.valueIsDefault() and self.param.opts['enabled'] and self.param.writable())
|
||||
|
||||
# hide / show
|
||||
self.defaultBtn.setVisible(self.param.hasDefault() and not self.param.readonly())
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
"""Update the display label to reflect the value of the parameter."""
|
||||
if value is None:
|
||||
value = self.param.value()
|
||||
self.displayLabel.setText(str(value))
|
||||
|
||||
def widgetValueChanged(self):
|
||||
## called when the widget's value has been changed by the user
|
||||
val = self.widget.value()
|
||||
self.param.setValue(val)
|
||||
|
||||
def widgetValueChanging(self, *args):
|
||||
"""
|
||||
Called when the widget's value is changing, but not finalized.
|
||||
For example: editing text before pressing enter or changing focus.
|
||||
"""
|
||||
self.param.sigValueChanging.emit(self.param, self.widget.value())
|
||||
|
||||
def selected(self, sel):
|
||||
"""Called when this item has been selected (sel=True) OR deselected (sel=False)"""
|
||||
ParameterItem.selected(self, sel)
|
||||
|
||||
if self.widget is None:
|
||||
return
|
||||
if sel and self.param.writable():
|
||||
self.showEditor()
|
||||
elif self.hideWidget:
|
||||
self.hideEditor()
|
||||
|
||||
def showEditor(self):
|
||||
self.widget.show()
|
||||
self.displayLabel.hide()
|
||||
self.widget.setFocus(QtCore.Qt.FocusReason.OtherFocusReason)
|
||||
|
||||
def hideEditor(self):
|
||||
self.widget.hide()
|
||||
self.displayLabel.show()
|
||||
|
||||
def limitsChanged(self, param, limits):
|
||||
"""Called when the parameter's limits have changed"""
|
||||
ParameterItem.limitsChanged(self, param, limits)
|
||||
|
||||
def defaultChanged(self, param, value):
|
||||
self.updateDefaultBtn()
|
||||
|
||||
def treeWidgetChanged(self):
|
||||
"""Called when this item is added or removed from a tree."""
|
||||
ParameterItem.treeWidgetChanged(self)
|
||||
|
||||
## add all widgets for this item into the tree
|
||||
if self.widget is not None:
|
||||
tree = self.treeWidget()
|
||||
if tree is None:
|
||||
return
|
||||
if self.asSubItem:
|
||||
self.subItem.setFirstColumnSpanned(True)
|
||||
tree.setItemWidget(self.subItem, 0, self.widget)
|
||||
tree.setItemWidget(self, 1, self.layoutWidget)
|
||||
self.displayLabel.hide()
|
||||
self.selected(False)
|
||||
|
||||
def defaultClicked(self):
|
||||
self.param.setToDefault()
|
||||
|
||||
def optsChanged(self, param, opts):
|
||||
"""Called when any options are changed that are not
|
||||
name, value, default, or limits"""
|
||||
ParameterItem.optsChanged(self, param, opts)
|
||||
|
||||
if 'enabled' in opts:
|
||||
self.updateDefaultBtn()
|
||||
self.widget.setEnabled(opts['enabled'])
|
||||
|
||||
if 'readonly' in opts:
|
||||
self.updateDefaultBtn()
|
||||
if hasattr(self.widget, 'setReadOnly'):
|
||||
self.widget.setReadOnly(opts['readonly'])
|
||||
else:
|
||||
self.widget.setEnabled(self.param.opts['enabled'] and not opts['readonly'])
|
||||
|
||||
if 'tip' in opts:
|
||||
self.widget.setToolTip(opts['tip'])
|
||||
|
||||
|
||||
class EventProxy(QtCore.QObject):
|
||||
def __init__(self, qobj, callback):
|
||||
QtCore.QObject.__init__(self)
|
||||
self.callback = callback
|
||||
qobj.installEventFilter(self)
|
||||
|
||||
def eventFilter(self, obj, ev):
|
||||
return self.callback(obj, ev)
|
||||
|
||||
|
||||
class SimpleParameter(Parameter):
|
||||
"""
|
||||
Parameter representing a single value.
|
||||
|
||||
This parameter is backed by :class:`WidgetParameterItem` to represent the
|
||||
following parameter names through various subclasses:
|
||||
|
||||
- 'int'
|
||||
- 'float'
|
||||
- 'bool'
|
||||
- 'str'
|
||||
- 'color'
|
||||
- 'colormap'
|
||||
"""
|
||||
def __init__(self, *args, **kargs):
|
||||
"""
|
||||
Initialize the parameter.
|
||||
|
||||
This is normally called implicitly through :meth:`Parameter.create`.
|
||||
The keyword arguments avaialble to :meth:`Parameter.__init__` are
|
||||
applicable.
|
||||
"""
|
||||
Parameter.__init__(self, *args, **kargs)
|
||||
|
||||
def _interpretValue(self, v):
|
||||
typ = self.opts['type']
|
||||
def _missing_interp(v):
|
||||
# Assume raw interpretation
|
||||
return v
|
||||
# Or:
|
||||
# raise TypeError(f'No interpreter found for type {typ}')
|
||||
interpreter = getattr(builtins, typ, _missing_interp)
|
||||
return interpreter(v)
|
||||
|
||||
class GroupParameterItem(ParameterItem):
|
||||
"""
|
||||
Group parameters are used mainly as a generic parent item that holds (and groups!) a set
|
||||
of child parameters. It also provides a simple mechanism for displaying a button or combo
|
||||
that can be used to add new parameters to the group.
|
||||
"""
|
||||
def __init__(self, param, depth):
|
||||
ParameterItem.__init__(self, param, depth)
|
||||
self.updateDepth(depth)
|
||||
|
||||
self.addItem = None
|
||||
if 'addText' in param.opts:
|
||||
addText = param.opts['addText']
|
||||
if 'addList' in param.opts:
|
||||
self.addWidget = QtWidgets.QComboBox()
|
||||
self.addWidget.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents)
|
||||
self.updateAddList()
|
||||
self.addWidget.currentIndexChanged.connect(self.addChanged)
|
||||
else:
|
||||
self.addWidget = QtWidgets.QPushButton(addText)
|
||||
self.addWidget.clicked.connect(self.addClicked)
|
||||
w = QtWidgets.QWidget()
|
||||
l = QtWidgets.QHBoxLayout()
|
||||
l.setContentsMargins(0,0,0,0)
|
||||
w.setLayout(l)
|
||||
l.addWidget(self.addWidget)
|
||||
l.addStretch()
|
||||
self.addWidgetBox = w
|
||||
self.addItem = QtWidgets.QTreeWidgetItem([])
|
||||
self.addItem.setFlags(QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.addItem.depth = self.depth + 1
|
||||
ParameterItem.addChild(self, self.addItem)
|
||||
self.addItem.setSizeHint(0, self.addWidgetBox.sizeHint())
|
||||
|
||||
self.optsChanged(self.param, self.param.opts)
|
||||
|
||||
def updateDepth(self, depth):
|
||||
## Change item's appearance based on its depth in the tree
|
||||
## This allows highest-level groups to be displayed more prominently.
|
||||
if depth == 0:
|
||||
for c in [0,1]:
|
||||
self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100)))
|
||||
self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255)))
|
||||
font = self.font(c)
|
||||
font.setBold(True)
|
||||
font.setPointSize(font.pointSize()+1)
|
||||
self.setFont(c, font)
|
||||
else:
|
||||
for c in [0,1]:
|
||||
self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220)))
|
||||
self.setForeground(c, QtGui.QBrush(QtGui.QColor(50,50,50)))
|
||||
font = self.font(c)
|
||||
font.setBold(True)
|
||||
#font.setPointSize(font.pointSize()+1)
|
||||
self.setFont(c, font)
|
||||
self.titleChanged() # sets the size hint for column 0 which is based on the new font
|
||||
|
||||
def addClicked(self):
|
||||
"""Called when "add new" button is clicked
|
||||
The parameter MUST have an 'addNew' method defined.
|
||||
"""
|
||||
self.param.addNew()
|
||||
|
||||
def addChanged(self):
|
||||
"""Called when "add new" combo is changed
|
||||
The parameter MUST have an 'addNew' method defined.
|
||||
"""
|
||||
if self.addWidget.currentIndex() == 0:
|
||||
return
|
||||
typ = self.addWidget.currentText()
|
||||
self.param.addNew(typ)
|
||||
self.addWidget.setCurrentIndex(0)
|
||||
|
||||
def treeWidgetChanged(self):
|
||||
ParameterItem.treeWidgetChanged(self)
|
||||
tw = self.treeWidget()
|
||||
if tw is None:
|
||||
return
|
||||
self.setFirstColumnSpanned(True)
|
||||
if self.addItem is not None:
|
||||
tw.setItemWidget(self.addItem, 0, self.addWidgetBox)
|
||||
self.addItem.setFirstColumnSpanned(True)
|
||||
|
||||
def addChild(self, child): ## make sure added childs are actually inserted before add btn
|
||||
if self.addItem is not None:
|
||||
ParameterItem.insertChild(self, self.childCount()-1, child)
|
||||
else:
|
||||
ParameterItem.addChild(self, child)
|
||||
|
||||
def optsChanged(self, param, opts):
|
||||
ParameterItem.optsChanged(self, param, opts)
|
||||
|
||||
if 'addList' in opts:
|
||||
self.updateAddList()
|
||||
|
||||
if hasattr(self, 'addWidget'):
|
||||
if 'enabled' in opts:
|
||||
self.addWidget.setEnabled(opts['enabled'])
|
||||
|
||||
if 'tip' in opts:
|
||||
self.addWidget.setToolTip(opts['tip'])
|
||||
|
||||
def updateAddList(self):
|
||||
self.addWidget.blockSignals(True)
|
||||
try:
|
||||
self.addWidget.clear()
|
||||
self.addWidget.addItem(self.param.opts['addText'])
|
||||
for t in self.param.opts['addList']:
|
||||
self.addWidget.addItem(t)
|
||||
finally:
|
||||
self.addWidget.blockSignals(False)
|
||||
|
||||
|
||||
class GroupParameter(Parameter):
|
||||
"""
|
||||
Group parameters are used mainly as a generic parent item that holds (and groups!) a set
|
||||
of child parameters.
|
||||
|
||||
It also provides a simple mechanism for displaying a button or combo
|
||||
that can be used to add new parameters to the group. To enable this, the group
|
||||
must be initialized with the 'addText' option (the text will be displayed on
|
||||
a button which, when clicked, will cause addNew() to be called). If the 'addList'
|
||||
option is specified as well, then a dropdown-list of addable items will be displayed
|
||||
instead of a button.
|
||||
"""
|
||||
itemClass = GroupParameterItem
|
||||
|
||||
sigAddNew = QtCore.Signal(object, object) # self, type
|
||||
|
||||
def addNew(self, typ=None):
|
||||
"""
|
||||
This method is called when the user has requested to add a new item to the group.
|
||||
By default, it emits ``sigAddNew(self, typ)``.
|
||||
"""
|
||||
self.sigAddNew.emit(self, typ)
|
||||
|
||||
def setAddList(self, vals):
|
||||
"""Change the list of options available for the user to add to the group."""
|
||||
self.setOpts(addList=vals)
|
15
pyqtgraph/parametertree/parameterTypes/bool.py
Normal file
15
pyqtgraph/parametertree/parameterTypes/bool.py
Normal file
@ -0,0 +1,15 @@
|
||||
from pyqtgraph.Qt import QtWidgets
|
||||
from pyqtgraph.parametertree.parameterTypes import WidgetParameterItem
|
||||
|
||||
|
||||
class BoolParameterItem(WidgetParameterItem):
|
||||
"""
|
||||
Registered parameter type which displays a QCheckBox
|
||||
"""
|
||||
def makeWidget(self):
|
||||
w = QtWidgets.QCheckBox()
|
||||
w.sigChanged = w.toggled
|
||||
w.value = w.isChecked
|
||||
w.setValue = w.setChecked
|
||||
self.hideWidget = False
|
||||
return w
|
55
pyqtgraph/parametertree/parameterTypes/calendar.py
Normal file
55
pyqtgraph/parametertree/parameterTypes/calendar.py
Normal file
@ -0,0 +1,55 @@
|
||||
from ...Qt import QtWidgets, QtCore
|
||||
from .. import Parameter
|
||||
from .basetypes import WidgetParameterItem
|
||||
|
||||
|
||||
class CalendarParameterItem(WidgetParameterItem):
|
||||
def makeWidget(self):
|
||||
self.asSubItem = True
|
||||
w = QtWidgets.QCalendarWidget()
|
||||
w.setMaximumHeight(200)
|
||||
w.sigChanged = w.selectionChanged
|
||||
w.value = w.selectedDate
|
||||
w.setValue = w.setSelectedDate
|
||||
self.hideWidget = False
|
||||
self.param.opts.setdefault('default', QtCore.QDate.currentDate())
|
||||
return w
|
||||
|
||||
|
||||
class CalendarParameter(Parameter):
|
||||
"""
|
||||
Displays a Qt calendar whose date is specified by a 'format' option.
|
||||
|
||||
============== ========================================================
|
||||
**Options:**
|
||||
format Format for displaying the date and converting from a string. Can be any value accepted by
|
||||
`QDate.toString` and `fromString`, or a stringified version of a QDateFormat enum, i.e. 'ISODate',
|
||||
'TextDate' (default), etc.
|
||||
============== ========================================================
|
||||
"""
|
||||
|
||||
itemClass = CalendarParameterItem
|
||||
|
||||
def __init__(self, **opts):
|
||||
opts.setdefault('format', 'TextDate')
|
||||
super().__init__(**opts)
|
||||
|
||||
def _interpretFormat(self, fmt=None):
|
||||
fmt = fmt or self.opts.get('format')
|
||||
if hasattr(QtCore.Qt.DateFormat, fmt):
|
||||
fmt = getattr(QtCore.Qt.DateFormat, fmt)
|
||||
return fmt
|
||||
|
||||
def _interpretValue(self, v):
|
||||
if isinstance(v, str):
|
||||
fmt = self._interpretFormat()
|
||||
if fmt is None:
|
||||
raise ValueError('Cannot parse date string without a set format')
|
||||
v = QtCore.QDate.fromString(v, fmt)
|
||||
return v
|
||||
|
||||
def saveState(self, filter=None):
|
||||
state = super().saveState(filter)
|
||||
fmt = self._interpretFormat()
|
||||
state['value'] = state['value'].toString(fmt)
|
||||
return state
|
31
pyqtgraph/parametertree/parameterTypes/color.py
Normal file
31
pyqtgraph/parametertree/parameterTypes/color.py
Normal file
@ -0,0 +1,31 @@
|
||||
from ...widgets.ColorButton import ColorButton
|
||||
from ... import functions as fn
|
||||
from .basetypes import WidgetParameterItem, SimpleParameter
|
||||
|
||||
|
||||
class ColorParameterItem(WidgetParameterItem):
|
||||
"""Registered parameter type which displays a :class:`ColorButton <pyqtgraph.ColorButton>` """
|
||||
def makeWidget(self):
|
||||
w = ColorButton()
|
||||
w.sigChanged = w.sigColorChanged
|
||||
w.sigChanging = w.sigColorChanging
|
||||
w.value = w.color
|
||||
w.setValue = w.setColor
|
||||
self.hideWidget = False
|
||||
w.setFlat(True)
|
||||
return w
|
||||
|
||||
|
||||
class ColorParameter(SimpleParameter):
|
||||
itemClass = ColorParameterItem
|
||||
|
||||
def _interpretValue(self, v):
|
||||
return fn.mkColor(v)
|
||||
|
||||
def value(self):
|
||||
return fn.mkColor(super().value())
|
||||
|
||||
def saveState(self, filter=None):
|
||||
state = super().saveState(filter)
|
||||
state['value'] = fn.colorTuple(self.value())
|
||||
return state
|
27
pyqtgraph/parametertree/parameterTypes/colormap.py
Normal file
27
pyqtgraph/parametertree/parameterTypes/colormap.py
Normal file
@ -0,0 +1,27 @@
|
||||
from .basetypes import WidgetParameterItem, SimpleParameter
|
||||
from ...Qt import QtCore
|
||||
from ...colormap import ColorMap
|
||||
from ...widgets.GradientWidget import GradientWidget
|
||||
|
||||
|
||||
class ColorMapParameterItem(WidgetParameterItem):
|
||||
"""Registered parameter type which displays a :class:`GradientWidget <pyqtgraph.GradientWidget>`"""
|
||||
def makeWidget(self):
|
||||
w = GradientWidget(orientation='bottom')
|
||||
w.sizeHint = lambda: QtCore.QSize(300, 35)
|
||||
w.sigChanged = w.sigGradientChangeFinished
|
||||
w.sigChanging = w.sigGradientChanged
|
||||
w.value = w.colorMap
|
||||
w.setValue = w.setColorMap
|
||||
self.hideWidget = False
|
||||
self.asSubItem = True
|
||||
return w
|
||||
|
||||
|
||||
class ColorMapParameter(SimpleParameter):
|
||||
itemClass = ColorMapParameterItem
|
||||
|
||||
def _interpretValue(self, v):
|
||||
if v is not None and not isinstance(v, ColorMap):
|
||||
raise TypeError("Cannot set colormap parameter from object %r" % v)
|
||||
return v
|
203
pyqtgraph/parametertree/parameterTypes/file.py
Normal file
203
pyqtgraph/parametertree/parameterTypes/file.py
Normal file
@ -0,0 +1,203 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from .str import StrParameterItem
|
||||
from .. import Parameter
|
||||
from ...Qt import QtWidgets, QtGui, QtCore
|
||||
|
||||
|
||||
def _set_filepicker_kwargs(fileDlg, **kwargs):
|
||||
"""Applies a dict of enum/flag kwarg opts to a file dialog"""
|
||||
NO_MATCH = object()
|
||||
|
||||
for kk, vv in kwargs.items():
|
||||
# Convert string or list representations into true flags
|
||||
# 'fileMode' -> 'FileMode'
|
||||
formattedName = kk[0].upper() + kk[1:]
|
||||
# Edge case: "Options" has enum "Option"
|
||||
if formattedName == 'Options':
|
||||
enumCls = fileDlg.Option
|
||||
else:
|
||||
enumCls = getattr(fileDlg, formattedName, NO_MATCH)
|
||||
setFunc = getattr(fileDlg, f'set{formattedName}', NO_MATCH)
|
||||
if enumCls is NO_MATCH or setFunc is NO_MATCH:
|
||||
continue
|
||||
if enumCls is fileDlg.Option:
|
||||
builder = fileDlg.Option(0)
|
||||
# This is the only flag enum, all others can only take one value
|
||||
if isinstance(vv, str): vv = [vv]
|
||||
for flag in vv:
|
||||
curVal = getattr(enumCls, flag)
|
||||
builder |= curVal
|
||||
# Some Qt implementations turn into ints by this point
|
||||
outEnum = enumCls(builder)
|
||||
else:
|
||||
outEnum = getattr(enumCls, vv)
|
||||
setFunc(outEnum)
|
||||
|
||||
|
||||
def popupFilePicker(parent=None, windowTitle='', nameFilter='', directory=None, selectFile=None, relativeTo=None, **kwargs):
|
||||
"""
|
||||
Thin wrapper around Qt file picker dialog. Used internally so all options are consistent
|
||||
among all requests for external file information
|
||||
|
||||
============== ========================================================
|
||||
**Arguments:**
|
||||
parent Dialog parent
|
||||
windowTitle Title of dialog window
|
||||
nameFilter File filter as required by the Qt dialog
|
||||
directory Where in the file system to open this dialog
|
||||
selectFile File to preselect
|
||||
relativeTo Parent directory that, if provided, will be removed from the prefix of all returned paths. So,
|
||||
if '/my/text/file.txt' was selected, and `relativeTo='/my/text/'`, the return value would be
|
||||
'file.txt'. This uses os.path.relpath under the hood, so expect that behavior.
|
||||
kwargs Any enum value accepted by a QFileDialog and its value. Values can be a string or list of strings,
|
||||
i.e. fileMode='AnyFile', options=['ShowDirsOnly', 'DontResolveSymlinks'], acceptMode='AcceptSave'
|
||||
============== ========================================================
|
||||
|
||||
"""
|
||||
fileDlg = QtWidgets.QFileDialog(parent)
|
||||
_set_filepicker_kwargs(fileDlg, **kwargs)
|
||||
|
||||
fileDlg.setModal(True)
|
||||
if directory is not None:
|
||||
fileDlg.setDirectory(directory)
|
||||
fileDlg.setNameFilter(nameFilter)
|
||||
if selectFile is not None:
|
||||
fileDlg.selectFile(selectFile)
|
||||
|
||||
fileDlg.setWindowTitle(windowTitle)
|
||||
|
||||
if fileDlg.exec():
|
||||
# Append filter type
|
||||
singleExtReg = r'(\.\w+)'
|
||||
# Extensions of type 'myfile.ext.is.multi.part' need to capture repeating pattern of singleExt
|
||||
suffMatch = re.search(rf'({singleExtReg}+)', fileDlg.selectedNameFilter())
|
||||
if suffMatch:
|
||||
# Strip leading '.' if it exists
|
||||
ext = suffMatch.group(1)
|
||||
if ext.startswith('.'):
|
||||
ext = ext[1:]
|
||||
fileDlg.setDefaultSuffix(ext)
|
||||
fList = fileDlg.selectedFiles()
|
||||
else:
|
||||
fList = []
|
||||
if relativeTo is not None:
|
||||
fList = [os.path.relpath(file, relativeTo) for file in fList]
|
||||
# Make consistent to os flavor
|
||||
fList = [os.path.normpath(file) for file in fList]
|
||||
if fileDlg.fileMode() == fileDlg.FileMode.ExistingFiles:
|
||||
return fList
|
||||
elif len(fList) > 0:
|
||||
return fList[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class FileParameterItem(StrParameterItem):
|
||||
def __init__(self, param, depth):
|
||||
self._value = None
|
||||
super().__init__(param, depth)
|
||||
|
||||
button = QtWidgets.QPushButton('...')
|
||||
button.setFixedWidth(25)
|
||||
button.setContentsMargins(0, 0, 0, 0)
|
||||
button.clicked.connect(self._retrieveFileSelection_gui)
|
||||
self.layoutWidget.layout().insertWidget(2, button)
|
||||
self.displayLabel.resizeEvent = self._newResizeEvent
|
||||
# self.layoutWidget.layout().insertWidget(3, self.defaultBtn)
|
||||
|
||||
def makeWidget(self):
|
||||
w = super().makeWidget()
|
||||
w.setValue = self.setValue
|
||||
w.value = self.value
|
||||
# Doesn't make much sense to have a 'changing' signal since filepaths should be complete before value
|
||||
# is emitted
|
||||
delattr(w, 'sigChanging')
|
||||
return w
|
||||
|
||||
def _newResizeEvent(self, ev):
|
||||
ret = type(self.displayLabel).resizeEvent(self.displayLabel, ev)
|
||||
self.updateDisplayLabel()
|
||||
return ret
|
||||
|
||||
def setValue(self, value):
|
||||
self._value = value
|
||||
self.widget.setText(str(value))
|
||||
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
def _retrieveFileSelection_gui(self):
|
||||
curVal = self.param.value()
|
||||
if isinstance(curVal, list) and len(curVal):
|
||||
# All files should be from the same directory, in principle
|
||||
# Since no mechanism exists for preselecting multiple, the most sensible
|
||||
# thing is to select nothing in the preview dialog
|
||||
curVal = curVal[0]
|
||||
if os.path.isfile(curVal):
|
||||
curVal = os.path.dirname(curVal)
|
||||
opts = self.param.opts.copy()
|
||||
useDir = curVal or opts.get('directory') or os.getcwd()
|
||||
startDir = os.path.abspath(useDir)
|
||||
if os.path.isfile(startDir):
|
||||
opts['selectFile'] = os.path.basename(startDir)
|
||||
startDir = os.path.dirname(startDir)
|
||||
if os.path.exists(startDir):
|
||||
opts['directory'] = startDir
|
||||
opts.setdefault('windowTitle', self.param.title())
|
||||
|
||||
fname = popupFilePicker(None, **opts)
|
||||
if not fname:
|
||||
return
|
||||
self.param.setValue(fname)
|
||||
|
||||
def updateDefaultBtn(self):
|
||||
# Override since a readonly label should still allow reverting to default
|
||||
## enable/disable default btn
|
||||
self.defaultBtn.setEnabled(
|
||||
not self.param.valueIsDefault() and self.param.opts['enabled'])
|
||||
|
||||
# hide / show
|
||||
self.defaultBtn.setVisible(self.param.hasDefault())
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
lbl = self.displayLabel
|
||||
if value is None:
|
||||
value = self.param.value()
|
||||
value = str(value)
|
||||
font = lbl.font()
|
||||
metrics = QtGui.QFontMetricsF(font)
|
||||
value = metrics.elidedText(value, QtCore.Qt.TextElideMode.ElideLeft, lbl.width()-5)
|
||||
return super().updateDisplayLabel(value)
|
||||
|
||||
|
||||
class FileParameter(Parameter):
|
||||
"""
|
||||
Interfaces with the myriad of file options available from a QFileDialog.
|
||||
|
||||
Note that the output can either be a single file string or list of files, depending on whether
|
||||
`fileMode='ExistingFiles'` is specified.
|
||||
|
||||
Note that in all cases, absolute file paths are returned unless `relativeTo` is specified as
|
||||
elaborated below.
|
||||
|
||||
============== ========================================================
|
||||
**Options:**
|
||||
parent Dialog parent
|
||||
winTitle Title of dialog window
|
||||
nameFilter File filter as required by the Qt dialog
|
||||
directory Where in the file system to open this dialog
|
||||
selectFile File to preselect
|
||||
relativeTo Parent directory that, if provided, will be removed from the prefix of all returned paths. So,
|
||||
if '/my/text/file.txt' was selected, and `relativeTo='my/text/'`, the return value would be
|
||||
'file.txt'. This uses os.path.relpath under the hood, so expect that behavior.
|
||||
kwargs Any enum value accepted by a QFileDialog and its value. Values can be a string or list of strings,
|
||||
i.e. fileMode='AnyFile', options=['ShowDirsOnly', 'DontResolveSymlinks']
|
||||
============== ========================================================
|
||||
"""
|
||||
itemClass = FileParameterItem
|
||||
|
||||
def __init__(self, **opts):
|
||||
opts.setdefault('readonly', True)
|
||||
super().__init__(**opts)
|
40
pyqtgraph/parametertree/parameterTypes/font.py
Normal file
40
pyqtgraph/parametertree/parameterTypes/font.py
Normal file
@ -0,0 +1,40 @@
|
||||
from .basetypes import WidgetParameterItem
|
||||
from .. import Parameter
|
||||
from ...Qt import QtWidgets, QtGui
|
||||
|
||||
|
||||
class FontParameterItem(WidgetParameterItem):
|
||||
def makeWidget(self):
|
||||
w = QtWidgets.QFontComboBox()
|
||||
w.setMaximumHeight(20)
|
||||
w.sigChanged = w.currentFontChanged
|
||||
w.value = w.currentFont
|
||||
w.setValue = w.setCurrentFont
|
||||
self.hideWidget = False
|
||||
return w
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
if value is None:
|
||||
value = self.widget.currentText()
|
||||
super().updateDisplayLabel(value)
|
||||
|
||||
|
||||
class FontParameter(Parameter):
|
||||
"""
|
||||
Creates and controls a QFont value. Be careful when selecting options from the font dropdown. since not all
|
||||
fonts are available on all systems
|
||||
"""
|
||||
itemClass = FontParameterItem
|
||||
|
||||
def _interpretValue(self, v):
|
||||
if isinstance(v, str):
|
||||
newVal = QtGui.QFont()
|
||||
if not newVal.fromString(v):
|
||||
raise ValueError(f'Error parsing font "{v}"')
|
||||
v = newVal
|
||||
return v
|
||||
|
||||
def saveState(self, filter=None):
|
||||
state = super().saveState(filter)
|
||||
state['value'] = state['value'].toString()
|
||||
return state
|
124
pyqtgraph/parametertree/parameterTypes/list.py
Normal file
124
pyqtgraph/parametertree/parameterTypes/list.py
Normal file
@ -0,0 +1,124 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from .basetypes import WidgetParameterItem
|
||||
from .. import Parameter
|
||||
from ...Qt import QtWidgets
|
||||
|
||||
|
||||
class ListParameterItem(WidgetParameterItem):
|
||||
"""
|
||||
WidgetParameterItem subclass providing comboBox that lets the user select from a list of options.
|
||||
|
||||
"""
|
||||
def __init__(self, param, depth):
|
||||
self.targetValue = None
|
||||
WidgetParameterItem.__init__(self, param, depth)
|
||||
|
||||
def makeWidget(self):
|
||||
w = QtWidgets.QComboBox()
|
||||
w.setMaximumHeight(20) ## set to match height of spin box and line edit
|
||||
w.sigChanged = w.currentIndexChanged
|
||||
w.value = self.value
|
||||
w.setValue = self.setValue
|
||||
self.widget = w ## needs to be set before limits are changed
|
||||
self.limitsChanged(self.param, self.param.opts['limits'])
|
||||
if len(self.forward) > 0:
|
||||
self.setValue(self.param.value())
|
||||
return w
|
||||
|
||||
def value(self):
|
||||
key = self.widget.currentText()
|
||||
|
||||
return self.forward.get(key, None)
|
||||
|
||||
def setValue(self, val):
|
||||
self.targetValue = val
|
||||
if val not in self.reverse[0]:
|
||||
self.widget.setCurrentIndex(0)
|
||||
else:
|
||||
key = self.reverse[1][self.reverse[0].index(val)]
|
||||
ind = self.widget.findText(key)
|
||||
self.widget.setCurrentIndex(ind)
|
||||
|
||||
def limitsChanged(self, param, limits):
|
||||
# set up forward / reverse mappings for name:value
|
||||
|
||||
if len(limits) == 0:
|
||||
limits = [''] ## Can never have an empty list--there is always at least a singhe blank item.
|
||||
|
||||
self.forward, self.reverse = ListParameter.mapping(limits)
|
||||
try:
|
||||
self.widget.blockSignals(True)
|
||||
val = self.targetValue
|
||||
|
||||
self.widget.clear()
|
||||
for k in self.forward:
|
||||
self.widget.addItem(k)
|
||||
if k == val:
|
||||
self.widget.setCurrentIndex(self.widget.count()-1)
|
||||
self.updateDisplayLabel()
|
||||
finally:
|
||||
self.widget.blockSignals(False)
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
if value is None:
|
||||
value = self.widget.currentText()
|
||||
super().updateDisplayLabel(value)
|
||||
|
||||
|
||||
class ListParameter(Parameter):
|
||||
"""Parameter with a list of acceptable values.
|
||||
|
||||
By default, this parameter is represtented by a :class:`ListParameterItem`,
|
||||
displaying a combo box to select a value from the list.
|
||||
|
||||
In addition to the generic :class:`~pyqtgraph.parametertree.Parameter`
|
||||
options, this parameter type accepts a ``limits`` argument specifying the
|
||||
list of allowed values. ``values`` is an alias and may be used instead.
|
||||
|
||||
The values may generally be of any data type, as long as they can be
|
||||
represented as a string. If the string representation provided is
|
||||
undesirable, the values may be given as a dictionary mapping the desired
|
||||
string representation to the value.
|
||||
"""
|
||||
|
||||
itemClass = ListParameterItem
|
||||
|
||||
def __init__(self, **opts):
|
||||
self.forward = OrderedDict() ## {name: value, ...}
|
||||
self.reverse = ([], []) ## ([value, ...], [name, ...])
|
||||
|
||||
# Parameter uses 'limits' option to define the set of allowed values
|
||||
if 'values' in opts:
|
||||
opts['limits'] = opts['values']
|
||||
if opts.get('limits', None) is None:
|
||||
opts['limits'] = []
|
||||
Parameter.__init__(self, **opts)
|
||||
self.setLimits(opts['limits'])
|
||||
|
||||
def setLimits(self, limits):
|
||||
"""Change the list of allowed values."""
|
||||
self.forward, self.reverse = self.mapping(limits)
|
||||
|
||||
Parameter.setLimits(self, limits)
|
||||
if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]:
|
||||
self.setValue(self.reverse[0][0])
|
||||
|
||||
@staticmethod
|
||||
def mapping(limits):
|
||||
# Return forward and reverse mapping objects given a limit specification
|
||||
forward = OrderedDict() ## {name: value, ...}
|
||||
reverse = ([], []) ## ([value, ...], [name, ...])
|
||||
if isinstance(limits, dict):
|
||||
for k, v in limits.items():
|
||||
forward[k] = v
|
||||
reverse[0].append(v)
|
||||
reverse[1].append(k)
|
||||
else:
|
||||
for v in limits:
|
||||
n = str(v)
|
||||
forward[n] = v
|
||||
reverse[0].append(v)
|
||||
reverse[1].append(n)
|
||||
return forward, reverse
|
||||
|
58
pyqtgraph/parametertree/parameterTypes/numeric.py
Normal file
58
pyqtgraph/parametertree/parameterTypes/numeric.py
Normal file
@ -0,0 +1,58 @@
|
||||
from .basetypes import WidgetParameterItem
|
||||
from ...widgets.SpinBox import SpinBox
|
||||
|
||||
class NumericParameterItem(WidgetParameterItem):
|
||||
"""
|
||||
Subclasses `WidgetParameterItem` to provide the following types:
|
||||
|
||||
========================== =============================================================
|
||||
**Registered Types:**
|
||||
int Displays a :class:`SpinBox <pyqtgraph.SpinBox>` in integer
|
||||
mode.
|
||||
float Displays a :class:`SpinBox <pyqtgraph.SpinBox>`.
|
||||
========================== =============================================================
|
||||
"""
|
||||
def makeWidget(self):
|
||||
opts = self.param.opts
|
||||
t = opts['type']
|
||||
defs = {
|
||||
'value': 0, 'min': None, 'max': None,
|
||||
'step': 1.0, 'dec': False,
|
||||
'siPrefix': False, 'suffix': '', 'decimals': 3,
|
||||
}
|
||||
if t == 'int':
|
||||
defs['int'] = True
|
||||
defs['minStep'] = 1.0
|
||||
for k in defs:
|
||||
if k in opts:
|
||||
defs[k] = opts[k]
|
||||
if 'limits' in opts:
|
||||
defs['min'], defs['max'] = opts['limits']
|
||||
w = SpinBox()
|
||||
w.setOpts(**defs)
|
||||
w.sigChanged = w.sigValueChanged
|
||||
w.sigChanging = w.sigValueChanging
|
||||
return w
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
if value is None:
|
||||
value = self.widget.lineEdit().text()
|
||||
super().updateDisplayLabel(value)
|
||||
|
||||
def showEditor(self):
|
||||
super().showEditor()
|
||||
self.widget.selectNumber() # select the numerical portion of the text for quick editing
|
||||
|
||||
def limitsChanged(self, param, limits):
|
||||
self.widget.setOpts(bounds=limits)
|
||||
|
||||
def optsChanged(self, param, opts):
|
||||
super().optsChanged(param, opts)
|
||||
sbOpts = {}
|
||||
if 'units' in opts and 'suffix' not in opts:
|
||||
sbOpts['suffix'] = opts['units']
|
||||
for k, v in opts.items():
|
||||
if k in self.widget.opts:
|
||||
sbOpts[k] = v
|
||||
self.widget.setOpts(**sbOpts)
|
||||
self.updateDisplayLabel()
|
144
pyqtgraph/parametertree/parameterTypes/pen.py
Normal file
144
pyqtgraph/parametertree/parameterTypes/pen.py
Normal file
@ -0,0 +1,144 @@
|
||||
from .basetypes import WidgetParameterItem
|
||||
from .. import Parameter
|
||||
from ... import functions as fn
|
||||
from ...Qt import QtWidgets, QtGui, QtCore
|
||||
from ...widgets.PenSelectorDialog import PenSelectorDialog
|
||||
|
||||
class PenParameterItem(WidgetParameterItem):
|
||||
def __init__(self, param, depth):
|
||||
self.pdialog = PenSelectorDialog(fn.mkPen(param.pen))
|
||||
self.pdialog.setModal(True)
|
||||
self.pdialog.accepted.connect(self.penChangeFinished)
|
||||
super().__init__(param, depth)
|
||||
self.displayLabel.paintEvent = self.displayPaintEvent
|
||||
|
||||
def makeWidget(self):
|
||||
self.button = QtWidgets.QPushButton()
|
||||
#larger button
|
||||
self.button.setFixedWidth(100)
|
||||
self.button.clicked.connect(self.buttonClicked)
|
||||
self.button.paintEvent = self.buttonPaintEvent
|
||||
self.button.value = self.value
|
||||
self.button.setValue = self.setValue
|
||||
self.button.sigChanged = None
|
||||
return self.button
|
||||
|
||||
@property
|
||||
def pen(self):
|
||||
return self.pdialog.pen
|
||||
|
||||
def value(self):
|
||||
return self.pen
|
||||
|
||||
def setValue(self, pen):
|
||||
self.pdialog.updateParamFromPen(self.pdialog.param, pen)
|
||||
|
||||
def updateDisplayLabel(self, value=None):
|
||||
super().updateDisplayLabel('')
|
||||
self.displayLabel.update()
|
||||
self.widget.update()
|
||||
|
||||
def buttonClicked(self):
|
||||
#open up the pen selector dialog
|
||||
# Copy in case of rejection
|
||||
prePen = QtGui.QPen(self.pen)
|
||||
if self.pdialog.exec() != QtWidgets.QDialog.DialogCode.Accepted:
|
||||
self.pdialog.updateParamFromPen(self.pdialog.param, prePen)
|
||||
|
||||
def penChangeFinished(self):
|
||||
self.param.setValue(self.pdialog.pen)
|
||||
|
||||
def penPaintEvent(self, event, item):
|
||||
# draw item as usual
|
||||
type(item).paintEvent(item, event)
|
||||
|
||||
path = QtGui.QPainterPath()
|
||||
displaySize = item.size()
|
||||
w, h = displaySize.width(), displaySize.height()
|
||||
# draw a squiggle with the pen
|
||||
path.moveTo(w * .2, h * .2)
|
||||
path.lineTo(w * .4, h * .8)
|
||||
path.cubicTo(w * .5, h * .1, w * .7, h * .1, w * .8, h * .8)
|
||||
|
||||
painter = QtGui.QPainter(item)
|
||||
painter.setPen(self.pen)
|
||||
painter.drawPath(path)
|
||||
painter.end()
|
||||
|
||||
def buttonPaintEvent(self, event):
|
||||
return self.penPaintEvent(event, self.button)
|
||||
|
||||
def displayPaintEvent(self, event):
|
||||
return self.penPaintEvent(event, self.displayLabel)
|
||||
|
||||
|
||||
class PenParameter(Parameter):
|
||||
"""
|
||||
Controls the appearance of a QPen value.
|
||||
|
||||
When `saveState` is called, the value is encoded as (color, width, style, capStyle, joinStyle, cosmetic)
|
||||
|
||||
============== ========================================================
|
||||
**Options:**
|
||||
color pen color, can be any argument accepted by :func:`~pyqtgraph.mkColor` (defaults to black)
|
||||
width integer width >= 0 (defaults to 1)
|
||||
style String version of QPenStyle enum, i.e. 'SolidLine' (default), 'DashLine', etc.
|
||||
capStyle String version of QPenCapStyle enum, i.e. 'SquareCap' (default), 'RoundCap', etc.
|
||||
joinStyle String version of QPenJoinStyle enum, i.e. 'BevelJoin' (default), 'RoundJoin', etc.
|
||||
cosmetic Boolean, whether or not the pen is cosmetic (defaults to True)
|
||||
============== ========================================================
|
||||
"""
|
||||
|
||||
itemClass = PenParameterItem
|
||||
sigPenChanged = QtCore.Signal(object,object)
|
||||
|
||||
def __init__(self, **opts):
|
||||
self.pen = fn.mkPen()
|
||||
self.penOptsParam = PenSelectorDialog.mkParam(self.pen)
|
||||
super().__init__(**opts)
|
||||
|
||||
def saveState(self, filter=None):
|
||||
state = super().saveState(filter)
|
||||
overrideState = self.penOptsParam.saveState(filter)['children']
|
||||
state['value'] = tuple(s['value'] for s in overrideState.values())
|
||||
return state
|
||||
|
||||
def _interpretValue(self, v):
|
||||
return self.mkPen(v)
|
||||
|
||||
def setValue(self, value, blockSignal=None):
|
||||
if not fn.eq(value, self.pen):
|
||||
value = self.mkPen(value)
|
||||
PenSelectorDialog.updateParamFromPen(self.penOptsParam, value)
|
||||
return super().setValue(self.pen, blockSignal)
|
||||
|
||||
def applyOptsToPen(self, **opts):
|
||||
# Transform opts into a value for the current pen
|
||||
paramNames = set(opts).intersection(self.penOptsParam.names)
|
||||
# Value should be overridden by opts
|
||||
with self.treeChangeBlocker():
|
||||
if 'value' in opts:
|
||||
pen = self.mkPen(opts.pop('value'))
|
||||
if not fn.eq(pen, self.pen):
|
||||
PenSelectorDialog.updateParamFromPen(self.penOptsParam, pen)
|
||||
penOpts = {}
|
||||
for kk in paramNames:
|
||||
penOpts[kk] = opts[kk]
|
||||
self.penOptsParam[kk] = opts[kk]
|
||||
return penOpts
|
||||
|
||||
def setOpts(self, **opts):
|
||||
# Transform opts into a value
|
||||
penOpts = self.applyOptsToPen(**opts)
|
||||
if penOpts:
|
||||
self.setValue(self.pen)
|
||||
return super().setOpts(**opts)
|
||||
|
||||
def mkPen(self, *args, **kwargs):
|
||||
"""Thin wrapper around fn.mkPen which accepts the serialized state from saveState"""
|
||||
if len(args) == 1 and isinstance(args[0], tuple) and len(args[0]) == len(self.penOptsParam.childs):
|
||||
opts = dict(zip(self.penOptsParam.names, args[0]))
|
||||
self.applyOptsToPen(**opts)
|
||||
args = (self.pen,)
|
||||
kwargs = {}
|
||||
return fn.mkPen(*args, **kwargs)
|
19
pyqtgraph/parametertree/parameterTypes/progress.py
Normal file
19
pyqtgraph/parametertree/parameterTypes/progress.py
Normal file
@ -0,0 +1,19 @@
|
||||
from ...Qt import QtWidgets
|
||||
from .. import Parameter
|
||||
from .basetypes import WidgetParameterItem
|
||||
|
||||
|
||||
class ProgressBarParameterItem(WidgetParameterItem):
|
||||
def makeWidget(self):
|
||||
w = QtWidgets.QProgressBar()
|
||||
w.setMaximumHeight(20)
|
||||
w.sigChanged = w.valueChanged
|
||||
self.hideWidget = False
|
||||
return w
|
||||
|
||||
|
||||
class ProgressBarParameter(Parameter):
|
||||
"""
|
||||
Displays a progress bar whose value can be set between 0 and 100
|
||||
"""
|
||||
itemClass = ProgressBarParameterItem
|
67
pyqtgraph/parametertree/parameterTypes/qtenum.py
Normal file
67
pyqtgraph/parametertree/parameterTypes/qtenum.py
Normal file
@ -0,0 +1,67 @@
|
||||
from ...Qt import QtCore, QT_LIB
|
||||
from .list import ListParameter
|
||||
|
||||
|
||||
class QtEnumParameter(ListParameter):
|
||||
def __init__(self, enum, searchObj=QtCore.Qt, **opts):
|
||||
"""
|
||||
Constructs a list of allowed enum values from the enum class provided
|
||||
`searchObj` is only needed for PyQt5 compatibility, where it must be the module holding the enum.
|
||||
For instance, if making a QtEnumParameter out of QtWidgets.QFileDialog.Option, `searchObj` would
|
||||
be QtWidgets.QFileDialog
|
||||
"""
|
||||
self.enum = enum
|
||||
self.searchObj = searchObj
|
||||
opts.setdefault('name', enum.__name__)
|
||||
self.enumMap = self._getAllowedEnums(enum)
|
||||
|
||||
opts.update(limits=self.formattedLimits())
|
||||
super().__init__(**opts)
|
||||
|
||||
def setValue(self, value, blockSignal=None):
|
||||
if isinstance(value, str):
|
||||
value = self.enumMap[value]
|
||||
super().setValue(value, blockSignal)
|
||||
|
||||
def formattedLimits(self):
|
||||
# Title-cased words without the ending substring for brevity
|
||||
substringEnd = None
|
||||
mapping = self.enumMap
|
||||
shortestName = min(len(name) for name in mapping)
|
||||
names = list(mapping)
|
||||
cmpName, *names = names
|
||||
for ii in range(-1, -shortestName-1, -1):
|
||||
if any(cmpName[ii] != curName[ii] for curName in names):
|
||||
substringEnd = ii+1
|
||||
break
|
||||
# Special case of 0: Set to none to avoid null string
|
||||
if substringEnd == 0:
|
||||
substringEnd = None
|
||||
limits = {}
|
||||
for kk, vv in self.enumMap.items():
|
||||
limits[kk[:substringEnd]] = vv
|
||||
return limits
|
||||
|
||||
def saveState(self, filter=None):
|
||||
state = super().saveState(filter)
|
||||
reverseMap = dict(zip(self.enumMap.values(), self.enumMap))
|
||||
state['value'] = reverseMap[state['value']]
|
||||
return state
|
||||
|
||||
def _getAllowedEnums(self, enum):
|
||||
"""Pyside provides a dict for easy evaluation"""
|
||||
if 'PySide' in QT_LIB:
|
||||
vals = enum.values
|
||||
elif 'PyQt5' in QT_LIB:
|
||||
vals = {}
|
||||
for key in dir(self.searchObj):
|
||||
value = getattr(self.searchObj, key)
|
||||
if isinstance(value, enum):
|
||||
vals[key] = value
|
||||
elif 'PyQt6' in QT_LIB:
|
||||
vals = {e.name: e for e in enum}
|
||||
else:
|
||||
raise RuntimeError(f'Cannot find associated enum values for qt lib {QT_LIB}')
|
||||
# Remove "M<enum>" since it's not a real option
|
||||
vals.pop(f'M{enum.__name__}', None)
|
||||
return vals
|
139
pyqtgraph/parametertree/parameterTypes/slider.py
Normal file
139
pyqtgraph/parametertree/parameterTypes/slider.py
Normal file
@ -0,0 +1,139 @@
|
||||
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
|
15
pyqtgraph/parametertree/parameterTypes/str.py
Normal file
15
pyqtgraph/parametertree/parameterTypes/str.py
Normal file
@ -0,0 +1,15 @@
|
||||
from pyqtgraph.Qt import QtWidgets
|
||||
from pyqtgraph.parametertree.parameterTypes import WidgetParameterItem
|
||||
|
||||
|
||||
class StrParameterItem(WidgetParameterItem):
|
||||
"""Registered parameter type which displays a QLineEdit"""
|
||||
|
||||
def makeWidget(self):
|
||||
w = QtWidgets.QLineEdit()
|
||||
w.setStyleSheet('border: 0px')
|
||||
w.sigChanged = w.editingFinished
|
||||
w.value = w.text
|
||||
w.setValue = w.setText
|
||||
w.sigChanging = w.textChanged
|
||||
return w
|
22
pyqtgraph/parametertree/parameterTypes/text.py
Normal file
22
pyqtgraph/parametertree/parameterTypes/text.py
Normal file
@ -0,0 +1,22 @@
|
||||
from .basetypes import WidgetParameterItem
|
||||
from .. import Parameter
|
||||
from ...Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class TextParameterItem(WidgetParameterItem):
|
||||
"""ParameterItem displaying a QTextEdit widget."""
|
||||
|
||||
def makeWidget(self):
|
||||
self.hideWidget = False
|
||||
self.asSubItem = True
|
||||
self.textBox = w = QtWidgets.QTextEdit()
|
||||
w.sizeHint = lambda: QtCore.QSize(300, 100)
|
||||
w.value = w.toPlainText
|
||||
w.setValue = w.setPlainText
|
||||
w.sigChanged = w.textChanged
|
||||
return w
|
||||
|
||||
|
||||
class TextParameter(Parameter):
|
||||
"""Editable string, displayed as large text box in the tree."""
|
||||
itemClass = TextParameterItem
|
48
tools/rebuildPtreeRst.py
Normal file
48
tools/rebuildPtreeRst.py
Normal file
@ -0,0 +1,48 @@
|
||||
import os.path
|
||||
import textwrap
|
||||
|
||||
from pyqtgraph.parametertree.Parameter import PARAM_TYPES, _PARAM_ITEM_TYPES
|
||||
|
||||
|
||||
def mkDocs(typeList):
|
||||
typeNames = sorted([typ.__name__ for typ in typeList])
|
||||
typDocs = [
|
||||
f"""\
|
||||
.. autoclass:: {name}
|
||||
:members:
|
||||
"""
|
||||
for name in typeNames]
|
||||
indented = '\n'.join(typDocs)
|
||||
# There will be two newlines at the end, so remove one
|
||||
return textwrap.dedent(indented)[:-1]
|
||||
|
||||
types = set(PARAM_TYPES.values())
|
||||
items = [typ.itemClass for typ in PARAM_TYPES.values() if typ.itemClass is not None] \
|
||||
+ [item for item in _PARAM_ITEM_TYPES.values()]
|
||||
items = set(items)
|
||||
|
||||
doc = f"""\
|
||||
..
|
||||
This file is auto-generated from pyqtgraph/tools/rebuildPtreeRst.py. Do not modify by hand! Instead, rerun the
|
||||
generation script with `python pyqtgraph/tools/rebuildPtreeRst.py`.
|
||||
|
||||
Built-in Parameter Types
|
||||
========================
|
||||
|
||||
.. currentmodule:: pyqtgraph.parametertree.parameterTypes
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
{mkDocs(types)}
|
||||
|
||||
ParameterItems
|
||||
--------------
|
||||
|
||||
{mkDocs(items)}
|
||||
"""
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
rstFilename = os.path.join(here, '..', 'doc', 'source', 'parametertree', 'parametertypes.rst')
|
||||
with open(rstFilename, 'w') as ofile:
|
||||
ofile.write(doc)
|
Loading…
Reference in New Issue
Block a user