feature More parameter item types (#1844)
* feature More parameter item types Pen: Pops up a dialouge that allows the user to customize a pen. Setting pen value is not working yet. Progress bar: For indication things. Slider: Easier way to set values that dont require precision. Fonts: Picking font types. Next thing could be a Font dialog. Calendar: For picking dates or intervals Open/save file/files/directory: Pops up an open/save file/directory dialog to select a file/directory. Filter string and caption can be defined too. A PenSelectorDialog widget was created for the pen parameter item too. Also added these parameter items to the example. * PyQt/Side6 compatibility fixup * Revisions from intial PR Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialog.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/parametertree/parameterTypes.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/parametertree/parameterTypes.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialog.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Update pyqtgraph/widgets/PenSelectorDialogbox.py Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> Apply suggestions from code review Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com> * Bugfix: module instead of class import on param tree example * Enrich the slider parameter * Address pijyoi comments on pen style parameter * Different file picker for easier porting * Better organization and formatting, minor refactoring * PyQt6/PySide6 fixup for file dialog * Minor adjustment to file picker * Bugfix: for 'None' sigChanged 'None' is explicitly allowed for a WidgetParameterItem's `sigChanged` value. However, this raises an error on a changed value unless the commit's fix is applied * Calendar works better as sub item * Fixes bugs in pen parameter's dialog + makes it resizable * more bugfixes and recommended changes, lets pen serialize its options * better pen save state * Fixes file parameter qualms * Fixes font parameter qualms * Fixes calendar parameter qualms * Fixes multiply-defined slider optsChanged * Fixes pen parameter qualms * ptree example minor bugfix * Pen dialog bugfixes * File dialog bugfixes / mild improvements * unto ptree save state regression * file fixup * Adds parameter descriptions to docstrings * Improved parameter documentation * adds 'relativeTo' option for file parameter * Less abuse of Qt enums during or-operations * More uniform handling of relative paths * More cleanup of enum setting * better name for window title (matches qt name) * Favor os.path over pathlib * Exposes 'directory', 'windowTitle' to file parameter * Fixup and add comparison to parameter tree state restoration * Exposes "cosmetic" in pen parameter * Indicate defaults in parameter documentation * QtEnumParameter works for enums outside QtCore.Qt * see if altering pytest report fixes ci bug * Cleanup unused import and redundant `self.widget` assignments Co-authored-by: Fekete Imre <feketeimre87@gmail.com> Co-authored-by: ChristophRose <42769515+ChristophRose@users.noreply.github.com>
This commit is contained in:
parent
8f96c78715
commit
81823768c0
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
@ -101,8 +101,7 @@ jobs:
|
|||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
mkdir $SCREENSHOT_DIR
|
mkdir $SCREENSHOT_DIR
|
||||||
pytest tests -v \
|
pytest tests -v
|
||||||
--junitxml pytest.xml
|
|
||||||
pytest examples -v
|
pytest examples -v
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Upload Screenshots
|
- name: Upload Screenshots
|
||||||
|
@ -21,6 +21,24 @@ Parameters
|
|||||||
.. autoclass:: ActionParameter
|
.. autoclass:: ActionParameter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: FileParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: CalendarParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: ProgressBarParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: FontParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PenParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: SliderParameter
|
||||||
|
:members:
|
||||||
|
|
||||||
ParameterItems
|
ParameterItems
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@ -36,5 +54,20 @@ ParameterItems
|
|||||||
.. autoclass:: TextParameterItem
|
.. autoclass:: TextParameterItem
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: ActionParameterItem
|
.. autoclass:: FileParameterItem
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: CalendarParameterItem
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: ProgressBarParameterItem
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: FontParameterItem
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PenParameterItem
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: SliderParameterItem
|
||||||
:members:
|
:members:
|
||||||
|
@ -6,7 +6,7 @@ demonstrates a variety of different parameter types (int, float, list, etc.)
|
|||||||
as well as some customized parameter types
|
as well as some customized parameter types
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
import initExample ## Add path to library (just for examples; you do not need this)
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
@ -78,6 +78,15 @@ params = [
|
|||||||
{'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'},
|
{'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'},
|
||||||
{'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'},
|
{'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'},
|
||||||
]},
|
]},
|
||||||
|
{'name': 'Custom Parameter Options', 'type': 'group', 'children': [
|
||||||
|
{'name': 'Pen', 'type': 'pen', 'value': pg.mkPen(color=(255,0,0), width=2)},
|
||||||
|
{'name': 'Progress bar', 'type': 'progress', 'value':50, 'limits':(0,100)},
|
||||||
|
{'name': 'Slider', 'type': 'slider', 'value':50, 'limits':(0,100)},
|
||||||
|
{'name': 'Font', 'type': 'font', 'value':QtGui.QFont("Inter")},
|
||||||
|
{'name': 'Calendar', 'type': 'calendar', 'value':QtCore.QDate.currentDate().addMonths(1)},
|
||||||
|
{'name': 'Open python file', 'type': 'file', 'fileMode': 'ExistingFile', 'nameFilter': 'Python file (*.py);;',
|
||||||
|
'value': 'parametertree.py', 'relativeTo': os.getcwd(), 'options': ['DontResolveSymlinks']}
|
||||||
|
]},
|
||||||
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
|
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
|
||||||
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
|
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
|
||||||
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
|
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
|
||||||
@ -145,11 +154,10 @@ for child in p.children():
|
|||||||
ch2.sigValueChanging.connect(valueChanging)
|
ch2.sigValueChanging.connect(valueChanging)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save():
|
def save():
|
||||||
global state
|
global state
|
||||||
state = p.saveState()
|
state = p.saveState()
|
||||||
|
|
||||||
def restore():
|
def restore():
|
||||||
global state
|
global state
|
||||||
add = p['Save/Restore functionality', 'Restore State', 'Add missing items']
|
add = p['Save/Restore functionality', 'Restore State', 'Add missing items']
|
||||||
@ -175,8 +183,10 @@ layout.addWidget(t2, 1, 1, 1, 1)
|
|||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
## test save/restore
|
## test save/restore
|
||||||
s = p.saveState()
|
state = p.saveState()
|
||||||
p.restoreState(s)
|
p.restoreState(state)
|
||||||
|
compareState = p.saveState()
|
||||||
|
assert pg.eq(compareState, state)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pg.exec()
|
pg.exec()
|
||||||
|
File diff suppressed because it is too large
Load Diff
179
pyqtgraph/widgets/PenSelectorDialog.py
Normal file
179
pyqtgraph/widgets/PenSelectorDialog.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
from ..Qt import QtCore, QtGui, QtWidgets
|
||||||
|
from ..parametertree import Parameter, ParameterTree
|
||||||
|
from ..functions import mkPen
|
||||||
|
|
||||||
|
import re
|
||||||
|
from contextlib import ExitStack
|
||||||
|
|
||||||
|
class PenPreviewArea(QtWidgets.QLabel):
|
||||||
|
def __init__(self, pen):
|
||||||
|
super().__init__()
|
||||||
|
self.penLocs = []
|
||||||
|
self.lastPos = None
|
||||||
|
self.pen = pen
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
self.penLocs.clear()
|
||||||
|
super().mousePressEvent(ev)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
ret =super().mouseMoveEvent(ev)
|
||||||
|
if not (ev.buttons() & QtCore.Qt.MouseButton.LeftButton):
|
||||||
|
return ret
|
||||||
|
pos = ev.position() if hasattr(ev, 'position') else ev.localPos()
|
||||||
|
if pos != self.lastPos:
|
||||||
|
self.penLocs.append(pos)
|
||||||
|
self.lastPos = QtCore.QPointF(pos)
|
||||||
|
self.update()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def paintEvent(self, *args):
|
||||||
|
painter = QtGui.QPainter(self)
|
||||||
|
# draw a squigly line to show what the pen looks like.
|
||||||
|
if len(self.penLocs) < 1:
|
||||||
|
path = self.getDefaultPath()
|
||||||
|
else:
|
||||||
|
path = QtGui.QPainterPath()
|
||||||
|
path.moveTo(self.penLocs[0])
|
||||||
|
for pos in self.penLocs[1:]:
|
||||||
|
path.lineTo(pos)
|
||||||
|
|
||||||
|
painter.setPen(self.pen)
|
||||||
|
painter.drawPath(path)
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
def getDefaultPath(self):
|
||||||
|
w, h = self.width(), self.height()
|
||||||
|
path = QtGui.QPainterPath()
|
||||||
|
path.moveTo(w * .2, h * .2)
|
||||||
|
path.lineTo(w * .8, h * .2)
|
||||||
|
path.lineTo(w * .2, h * .5)
|
||||||
|
path.cubicTo(w * .1, h * 1, w * .5, h * .25, w * .8, h * .8)
|
||||||
|
return path
|
||||||
|
|
||||||
|
class PenSelectorDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, initialPen='k'):
|
||||||
|
super().__init__()
|
||||||
|
self.pen = mkPen(initialPen)
|
||||||
|
self.param = self.mkParam(self.pen)
|
||||||
|
self.tree = ParameterTree(showHeader=False)
|
||||||
|
self.tree.setParameters(self.param, showTop=False)
|
||||||
|
self.setupUi()
|
||||||
|
self.setModal(True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mkParam(boundPen=None):
|
||||||
|
# Import here to avoid cyclic dependency
|
||||||
|
from ..parametertree.parameterTypes import QtEnumParameter
|
||||||
|
cs = QtCore.Qt.PenCapStyle
|
||||||
|
js = QtCore.Qt.PenJoinStyle
|
||||||
|
ps = QtCore.Qt.PenStyle
|
||||||
|
param = Parameter.create(name='Params', type='group', children=[
|
||||||
|
dict(name='color', type='color', value='k'),
|
||||||
|
dict(name='width', value=1, type='int', limits=[0, None]),
|
||||||
|
QtEnumParameter(ps, name='style', value='SolidLine'),
|
||||||
|
QtEnumParameter(cs, name='capStyle'),
|
||||||
|
QtEnumParameter(js, name='joinStyle'),
|
||||||
|
dict(name='cosmetic', type='bool', value=True)
|
||||||
|
])
|
||||||
|
|
||||||
|
for p in param:
|
||||||
|
name = p.name()
|
||||||
|
replace = r'\1 \2'
|
||||||
|
name = re.sub(r'(\w)([A-Z])', replace, name)
|
||||||
|
name = name.title().strip()
|
||||||
|
p.setOpts(title=name)
|
||||||
|
|
||||||
|
def setterWrapper(setter):
|
||||||
|
"""Ignores the 'param' argument of sigValueChanged"""
|
||||||
|
def newSetter(_, value):
|
||||||
|
return setter(value)
|
||||||
|
return newSetter
|
||||||
|
|
||||||
|
if boundPen is not None:
|
||||||
|
PenSelectorDialog.updateParamFromPen(param, boundPen)
|
||||||
|
for p in param:
|
||||||
|
setter, setName = PenSelectorDialog._setterForParam(p.name(), boundPen, returnName=True)
|
||||||
|
# Instead, set the parameter which will signal the old setter
|
||||||
|
setattr(boundPen, setName, p.setValue)
|
||||||
|
p.sigValueChanged.connect(setterWrapper(setter))
|
||||||
|
# Populate initial value
|
||||||
|
return param
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updatePenFromParam(penOptsParam, pen=None):
|
||||||
|
if pen is None:
|
||||||
|
pen = mkPen()
|
||||||
|
for param in penOptsParam:
|
||||||
|
setter = PenSelectorDialog._setterForParam(param.name(), pen)
|
||||||
|
setter(param.value())
|
||||||
|
return pen
|
||||||
|
|
||||||
|
def updatePenFromOpts(self, penOpts, pen=None):
|
||||||
|
if pen is None:
|
||||||
|
pen = mkPen()
|
||||||
|
useKeys = set(penOpts).intersection(self.param.names)
|
||||||
|
for kk in useKeys:
|
||||||
|
setter = self._setterForParam(kk, pen)
|
||||||
|
setter(penOpts[kk])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _setterForParam(paramName, obj, returnName=False):
|
||||||
|
formatted = paramName[0].upper() + paramName[1:]
|
||||||
|
setter = getattr(obj, f'set{formatted}')
|
||||||
|
if returnName:
|
||||||
|
return setter, formatted
|
||||||
|
return setter
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateParamFromPen(param, pen):
|
||||||
|
"""
|
||||||
|
Applies settings from a pen to either a Parameter or dict. The Parameter or dict must already
|
||||||
|
be populated with the relevant keys that can be found in `PenSelectorDialog.mkParam`.
|
||||||
|
"""
|
||||||
|
stack = ExitStack()
|
||||||
|
if isinstance(param, Parameter):
|
||||||
|
names = param.names
|
||||||
|
# Block changes until all are finalized
|
||||||
|
stack.enter_context(param.treeChangeBlocker())
|
||||||
|
else:
|
||||||
|
names = param
|
||||||
|
for opt in names:
|
||||||
|
# Booleans have different naming convention
|
||||||
|
if isinstance(param[opt], bool):
|
||||||
|
attrName = f'is{opt.title()}'
|
||||||
|
else:
|
||||||
|
attrName = opt
|
||||||
|
param[opt] = getattr(pen, attrName)()
|
||||||
|
stack.close()
|
||||||
|
|
||||||
|
def setupUi(self):
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(self.tree)
|
||||||
|
|
||||||
|
self.buttonBoxAcceptCancel = QtWidgets.QDialogButtonBox(self)
|
||||||
|
self.buttonBoxAcceptCancel.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||||
|
self.buttonBoxAcceptCancel.setStandardButtons(
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Cancel | QtWidgets.QDialogButtonBox.StandardButton.Ok)
|
||||||
|
self.buttonBoxAcceptCancel.accepted.connect(self.accept)
|
||||||
|
self.buttonBoxAcceptCancel.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.labelPenPreview = PenPreviewArea(self.pen)
|
||||||
|
def maybeUpdatePreview(_, changes):
|
||||||
|
if any('value' in c[1] for c in changes):
|
||||||
|
self.labelPenPreview.update()
|
||||||
|
self.param.sigTreeStateChanged.connect(maybeUpdatePreview)
|
||||||
|
infoLbl = QtWidgets.QLabel('Click and drag below to test the pen')
|
||||||
|
infoLbl.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)
|
||||||
|
policy = QtGui.QSizePolicy.Policy
|
||||||
|
infoLbl.setSizePolicy(policy.Expanding, policy.Fixed)
|
||||||
|
self.labelPenPreview.setMinimumSize(10,30)
|
||||||
|
self.tree.setMinimumSize(240, 135)
|
||||||
|
self.tree.setMaximumHeight(135)
|
||||||
|
|
||||||
|
layout.addWidget(infoLbl)
|
||||||
|
layout.addWidget(self.labelPenPreview)
|
||||||
|
layout.addWidget(self.buttonBoxAcceptCancel)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
self.resize(240, 300)
|
Loading…
Reference in New Issue
Block a user