From 02079aec8ac9734b613fc4e7be4ca9b1549f0827 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 01:07:19 -0800 Subject: [PATCH 01/26] Handle text with WidgetParameterItem and SimpleParameter --- pyqtgraph/parametertree/parameterTypes.py | 52 ++++++++++++++++++----- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 7742357f..9e0aa850 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -1,3 +1,6 @@ +import os +import warnings + from ..Qt import QtCore, QtGui from ..python2_3 import asUnicode from .Parameter import Parameter, registerParameterType @@ -7,7 +10,6 @@ from ..widgets.ColorButton import ColorButton from ..colormap import ColorMap from .. import pixmaps as pixmaps from .. import functions as fn -import os, sys from ..pgcollections import OrderedDict @@ -34,7 +36,8 @@ class WidgetParameterItem(ParameterItem): """ def __init__(self, param, depth): ParameterItem.__init__(self, param, depth) - + + self.asSubItem = False self.hideWidget = True ## hide edit widget, replace with label when not selected ## set this to False to keep the editor widget always visible @@ -42,7 +45,11 @@ class WidgetParameterItem(ParameterItem): w = self.makeWidget() self.widget = w self.eventProxy = EventProxy(w, self.widgetEventFilter) - + + if self.asSubItem: + self.subItem = QtGui.QTreeWidgetItem() + self.addChild(self.subItem) + self.defaultBtn = QtGui.QPushButton() self.defaultBtn.setAutoDefault(False) self.defaultBtn.setFixedWidth(20) @@ -128,6 +135,15 @@ class WidgetParameterItem(ParameterItem): w.value = lambda: asUnicode(w.text()) w.setValue = lambda v: w.setText(asUnicode(v)) w.sigChanging = w.textChanged + elif t == 'text': + w = QtGui.QTextEdit() + w.setMaximumHeight(100) + w.setReadOnly(opts.get('readonly', False)) + w.value = lambda: str(w.toPlainText()) + w.setValue = w.setPlainText + w.sigChanged = w.textChanged + self.hideWidget = False + self.asSubItem = True elif t == 'color': w = ColorButton() w.sigChanged = w.sigColorChanged @@ -259,9 +275,13 @@ class WidgetParameterItem(ParameterItem): tree = self.treeWidget() if tree is None: return - tree.setItemWidget(self, 1, self.layoutWidget) - self.displayLabel.hide() - self.selected(False) + if self.asSubItem: + tree.setFirstItemColumnSpanned(self.subItem, True) + tree.setItemWidget(self.subItem, 0, self.widget) + else: + tree.setItemWidget(self, 1, self.layoutWidget) + self.displayLabel.hide() + self.selected(False) def defaultClicked(self): self.param.setToDefault() @@ -332,6 +352,7 @@ class SimpleParameter(Parameter): 'float': float, 'bool': bool, 'str': asUnicode, + 'text': asUnicode, 'color': self._interpColor, 'colormap': self._interpColormap, }[self.opts['type']] @@ -350,6 +371,7 @@ registerParameterType('int', SimpleParameter, override=True) registerParameterType('float', SimpleParameter, override=True) registerParameterType('bool', SimpleParameter, override=True) registerParameterType('str', SimpleParameter, override=True) +registerParameterType('text', SimpleParameter, override=True) registerParameterType('color', SimpleParameter, override=True) registerParameterType('colormap', SimpleParameter, override=True) @@ -633,6 +655,10 @@ registerParameterType('action', ActionParameter, override=True) class TextParameterItem(WidgetParameterItem): def __init__(self, param, depth): + warnings.warn( + "This functionality is now handled directly by WidgetParameterItem.", + DeprecationWarning, stacklevel=2 + ) WidgetParameterItem.__init__(self, param, depth) self.hideWidget = False self.subItem = QtGui.QTreeWidgetItem() @@ -661,10 +687,16 @@ class TextParameterItem(WidgetParameterItem): self.textBox.setValue = self.textBox.setPlainText self.textBox.sigChanged = self.textBox.textChanged return self.textBox - + + class TextParameter(Parameter): """Editable string; displayed as large text box in the tree.""" itemClass = TextParameterItem - - -registerParameterType('text', TextParameter, override=True) + def __init__(self, *args, **kwargs): + warnings.warn( + "This functionality is now handled directly by SimpleParameter.", + DeprecationWarning, stacklevel=2 + ) + super().__init__(*args, **kwargs) + +# registerParameterType('text', TextParameter, override=True) From edf42114f84cc4736674f7f4bb7a5c034f96d489 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 01:08:46 -0800 Subject: [PATCH 02/26] Show colormap as sub item --- pyqtgraph/parametertree/parameterTypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 9e0aa850..1f1cd98d 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -161,6 +161,7 @@ class WidgetParameterItem(ParameterItem): w.value = w.colorMap w.setValue = w.setColorMap self.hideWidget = False + self.asSubItem = True else: raise Exception("Unknown type '%s'" % asUnicode(t)) return w From db43c9447c15ddbf951a9bd12d93a4be87725bd7 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 09:52:51 -0800 Subject: [PATCH 03/26] Allow default button for text and colormap parameters --- pyqtgraph/parametertree/parameterTypes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 1f1cd98d..1f6c4647 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -63,8 +63,9 @@ class WidgetParameterItem(ParameterItem): layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) - layout.addWidget(w) - layout.addWidget(self.displayLabel) + layout.addWidget(w, 1) + layout.addWidget(self.displayLabel, 1) + layout.addStretch(0) layout.addWidget(self.defaultBtn) self.layoutWidget = QtGui.QWidget() self.layoutWidget.setLayout(layout) @@ -279,10 +280,9 @@ class WidgetParameterItem(ParameterItem): if self.asSubItem: tree.setFirstItemColumnSpanned(self.subItem, True) tree.setItemWidget(self.subItem, 0, self.widget) - else: - tree.setItemWidget(self, 1, self.layoutWidget) - self.displayLabel.hide() - self.selected(False) + tree.setItemWidget(self, 1, self.layoutWidget) + self.displayLabel.hide() + self.selected(False) def defaultClicked(self): self.param.setToDefault() From ba304faa05c659f247a1278dac36f0d32424c887 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 17:45:04 -0800 Subject: [PATCH 04/26] Provide size hints for nicer looking trees that don't need to be manually sized --- examples/parametertree.py | 1 - pyqtgraph/parametertree/ParameterItem.py | 5 +++ pyqtgraph/parametertree/ParameterTree.py | 29 ++++++++++++++++ pyqtgraph/parametertree/parameterTypes.py | 42 +++++++++++++++++++---- 4 files changed, 69 insertions(+), 8 deletions(-) diff --git a/examples/parametertree.py b/examples/parametertree.py index acfeac4d..e3d83ad1 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -171,7 +171,6 @@ layout.addWidget(QtGui.QLabel("These are two views of the same data. They should layout.addWidget(t, 1, 0, 1, 1) layout.addWidget(t2, 1, 1, 1, 1) win.show() -win.resize(800,800) ## test save/restore s = p.saveState() diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py index 3c5c3013..ee24d927 100644 --- a/pyqtgraph/parametertree/ParameterItem.py +++ b/pyqtgraph/parametertree/ParameterItem.py @@ -159,6 +159,11 @@ class ParameterItem(QtGui.QTreeWidgetItem): def titleChanged(self): # called when the user-visble title has changed (either opts['title'], or name if title is None) self.setText(0, self.param.title()) + fm = QtGui.QFontMetrics(self.font(0)) + size = fm.size(QtCore.Qt.TextSingleLine, self.text(0)) + size.setHeight(size.height() * 1.35) + size.setWidth(size.width() * 1.15) + self.setSizeHint(0, size) def limitsChanged(self, param, limits): """Called when the parameter's limits have changed""" diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py index de6ab126..a9c9c3e1 100644 --- a/pyqtgraph/parametertree/ParameterTree.py +++ b/pyqtgraph/parametertree/ParameterTree.py @@ -162,3 +162,32 @@ class ParameterTree(TreeWidget): def wheelEvent(self, ev): self.clearSelection() return TreeWidget.wheelEvent(self, ev) + + def sizeHint(self): + w, h = 0, 0 + ind = self.indentation() + for x in self.listAllItems(): + if x.isHidden(): + continue + try: + depth = x.depth + except AttributeError: + depth = 0 + + s0 = x.sizeHint(0) + s1 = x.sizeHint(1) + w = max(w, depth * ind + max(0, s0.width()) + max(0, s1.width())) + h += max(0, s0.height(), s1.height()) + # typ = x.param.opts['type'] if isinstance(x, ParameterItem) else x + # print(typ, depth * ind, (s0.width(), s0.height()), (s1.width(), s1.height()), (w, h)) + + # todo: find out if this alternative can be made to work (currently fails when color or colormap are present) + # print('custom', (w, h)) + # w = self.sizeHintForColumn(0) + self.sizeHintForColumn(1) + # h = self.viewportSizeHint().height() + # print('alternative', (w, h)) + + if not self.header().isHidden(): + h += self.header().height() + + return QtCore.QSize(w, h) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 1f6c4647..55406a5f 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -48,6 +48,7 @@ class WidgetParameterItem(ParameterItem): if self.asSubItem: self.subItem = QtGui.QTreeWidgetItem() + self.subItem.depth = self.depth + 1 self.addChild(self.subItem) self.defaultBtn = QtGui.QPushButton() @@ -63,7 +64,8 @@ class WidgetParameterItem(ParameterItem): layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(2) - layout.addWidget(w, 1) + if not self.asSubItem: + layout.addWidget(w, 1) layout.addWidget(self.displayLabel, 1) layout.addStretch(0) layout.addWidget(self.defaultBtn) @@ -88,6 +90,30 @@ class WidgetParameterItem(ParameterItem): 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(sw.height() * 0.9) + sb.setHeight(sb.height() * 0.9) + # manually override size hints for certain parameter types + t = self.param.opts['type'] + if t == 'text': + sw.setWidth(100) + sw.setHeight(100) + elif t == 'color': + sw.setWidth(100) + elif t == 'colormap': + sw.setWidth(100) + sw.setHeight(35) + 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 that should be placed in the second tree column. @@ -407,7 +433,9 @@ class GroupParameterItem(ParameterItem): self.addWidgetBox = w self.addItem = QtGui.QTreeWidgetItem([]) self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) + self.addItem.depth = self.depth + 1 ParameterItem.addChild(self, self.addItem) + self.addItem.setSizeHint(0, self.addWidgetBox.sizeHint()) def updateDepth(self, depth): ## Change item's appearance based on its depth in the tree @@ -420,7 +448,6 @@ class GroupParameterItem(ParameterItem): font.setBold(True) font.setPointSize(font.pointSize()+1) self.setFont(c, font) - self.setSizeHint(0, QtCore.QSize(0, 25)) else: for c in [0,1]: self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) @@ -429,8 +456,8 @@ class GroupParameterItem(ParameterItem): font.setBold(True) #font.setPointSize(font.pointSize()+1) self.setFont(c, font) - self.setSizeHint(0, QtCore.QSize(0, 20)) - + 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. @@ -620,12 +647,13 @@ class ActionParameterItem(ParameterItem): self.layout = QtGui.QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layoutWidget.setLayout(self.layout) - self.button = QtGui.QPushButton(param.title()) + self.button = QtGui.QPushButton() #self.layout.addSpacing(100) self.layout.addWidget(self.button) self.layout.addStretch() self.button.clicked.connect(self.buttonClicked) - + self.titleChanged() + def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) tree = self.treeWidget() @@ -637,7 +665,7 @@ class ActionParameterItem(ParameterItem): def titleChanged(self): self.button.setText(self.param.title()) - ParameterItem.titleChanged(self) + self.setSizeHint(0, self.button.sizeHint()) def buttonClicked(self): self.param.activate() From d9c6a2764a354b1f6b294ccf8f4dbe26a832caa3 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 18:09:51 -0800 Subject: [PATCH 05/26] Removed pointless subclass method --- pyqtgraph/parametertree/parameterTypes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 55406a5f..63a62e35 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -359,11 +359,6 @@ class SimpleParameter(Parameter): if self.opts['type'] == 'color': self.value = self.colorValue self.saveState = self.saveColorState - - def setValue(self, value, blockSignal=None): - if self.opts['type'] == 'int': - value = int(value) - Parameter.setValue(self, value, blockSignal) def colorValue(self): return fn.mkColor(Parameter.value(self)) From 5a48b6879540747b760d1732c8080c2777132710 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 18:17:06 -0800 Subject: [PATCH 06/26] Bugfix --- pyqtgraph/widgets/TreeWidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/widgets/TreeWidget.py b/pyqtgraph/widgets/TreeWidget.py index 8c55ae2f..4540915f 100644 --- a/pyqtgraph/widgets/TreeWidget.py +++ b/pyqtgraph/widgets/TreeWidget.py @@ -132,7 +132,7 @@ class TreeWidget(QtGui.QTreeWidget): def listAllItems(self, item=None): items = [] - if item != None: + if item is not None: items.append(item) else: item = self.invisibleRootItem() From 6313bb8ae373c5609013c2ada4dd7d3e4bb752c4 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 19:00:22 -0800 Subject: [PATCH 07/26] Avoid deprecation warnings --- pyqtgraph/parametertree/ParameterItem.py | 4 ++-- pyqtgraph/parametertree/parameterTypes.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py index ee24d927..d5a58077 100644 --- a/pyqtgraph/parametertree/ParameterItem.py +++ b/pyqtgraph/parametertree/ParameterItem.py @@ -161,8 +161,8 @@ class ParameterItem(QtGui.QTreeWidgetItem): self.setText(0, self.param.title()) fm = QtGui.QFontMetrics(self.font(0)) size = fm.size(QtCore.Qt.TextSingleLine, self.text(0)) - size.setHeight(size.height() * 1.35) - size.setWidth(size.width() * 1.15) + size.setHeight(int(size.height() * 1.35)) + size.setWidth(int(size.width() * 1.15)) self.setSizeHint(0, size) def limitsChanged(self, param, limits): diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 63a62e35..ea28d25b 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -94,8 +94,8 @@ class WidgetParameterItem(ParameterItem): sw = self.widget.sizeHint() sb = self.defaultBtn.sizeHint() # shrink row heights a bit for more compact look - sw.setHeight(sw.height() * 0.9) - sb.setHeight(sb.height() * 0.9) + sw.setHeight(int(sw.height() * 0.9)) + sb.setHeight(int(sb.height() * 0.9)) # manually override size hints for certain parameter types t = self.param.opts['type'] if t == 'text': From 0703e3db9b91149cf048113774b7b43e2a44a697 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Thu, 17 Dec 2020 21:42:43 -0800 Subject: [PATCH 08/26] Ensure synchronization of parameter and widget's values --- examples/parametertree.py | 2 +- pyqtgraph/colormap.py | 7 +++- pyqtgraph/functions.py | 19 +++++++---- pyqtgraph/parametertree/Parameter.py | 7 ++-- pyqtgraph/parametertree/parameterTypes.py | 15 +++++---- .../tests/test_parametertypes.py | 33 ++++++++++++++++--- pyqtgraph/widgets/SpinBox.py | 9 +++-- 7 files changed, 65 insertions(+), 27 deletions(-) diff --git a/examples/parametertree.py b/examples/parametertree.py index e3d83ad1..01fc13bf 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -64,7 +64,7 @@ class ScalableGroup(pTypes.GroupParameter): params = [ {'name': 'Basic parameter data types', 'type': 'group', 'children': [ {'name': 'Integer', 'type': 'int', 'value': 10}, - {'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1}, + {'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False}, {'name': 'String', 'type': 'str', 'value': "hi"}, {'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2}, {'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2}, diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py index eb423634..b34264d4 100644 --- a/pyqtgraph/colormap.py +++ b/pyqtgraph/colormap.py @@ -1,7 +1,7 @@ import numpy as np from .Qt import QtGui, QtCore from .python2_3 import basestring -from .functions import mkColor +from .functions import mkColor, eq class ColorMap(object): @@ -257,3 +257,8 @@ class ColorMap(object): pos = repr(self.pos).replace('\n', '') color = repr(self.color).replace('\n', '') return "ColorMap(%s, %s)" % (pos, color) + + def __eq__(self, other): + if other is None: + return False + return eq(self.pos, other.pos) and eq(self.color, other.color) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index a2949ef7..478525dc 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -420,18 +420,25 @@ def eq(a, b): This function has some important differences from the == operator: - 1. Returns True if a IS b, even if a==b still evaluates to False, such as with nan values. - 2. Tests for equivalence using ==, but silently ignores some common exceptions that can occur + 1. Returns True if a IS b, even if a==b still evaluates to False. + 2. While a is b will catch the case with np.nan values, special handling is done for distinct + float('nan') instances using np.isnan. + 3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur (AtrtibuteError, ValueError). - 3. When comparing arrays, returns False if the array shapes are not the same. - 4. When comparing arrays of the same shape, returns True only if all elements are equal (whereas + 4. When comparing arrays, returns False if the array shapes are not the same. + 5. When comparing arrays of the same shape, returns True only if all elements are equal (whereas the == operator would return a boolean array). - 5. Collections (dict, list, etc.) must have the same type to be considered equal. One - consequence is that comparing a dict to an OrderedDict will always return False. + 6. Collections (dict, list, etc.) must have the same type to be considered equal. One + consequence is that comparing a dict to an OrderedDict will always return False. """ if a is b: return True + # The above catches np.nan, but not float('nan') + if isinstance(a, float) and isinstance(b, float): + if np.isnan(a) and np.isnan(b): + return True + # Avoid comparing large arrays against scalars; this is expensive and we know it should return False. aIsArr = isinstance(a, (np.ndarray, MetaArray)) bIsArr = isinstance(b, (np.ndarray, MetaArray)) diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py index 4a2149b1..b44280bb 100644 --- a/pyqtgraph/parametertree/Parameter.py +++ b/pyqtgraph/parametertree/Parameter.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from .. import functions as fn from ..Qt import QtGui, QtCore import os, weakref, re from ..pgcollections import OrderedDict @@ -283,15 +284,15 @@ class Parameter(QtCore.QObject): if blockSignal is not None: self.sigValueChanged.disconnect(blockSignal) value = self._interpretValue(value) - if self.opts['value'] == value: + if fn.eq(self.opts['value'], value): return value self.opts['value'] = value - self.sigValueChanged.emit(self, value) + self.sigValueChanged.emit(self, value) # value might change after signal is received by tree item finally: if blockSignal is not None: self.sigValueChanged.connect(blockSignal) - return value + return self.opts['value'] def _interpretValue(self, v): return v diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index ea28d25b..9ebae1e3 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -216,13 +216,16 @@ class WidgetParameterItem(ParameterItem): def valueChanged(self, param, val, force=False): ## called when the parameter's value has changed ParameterItem.valueChanged(self, param, val) - self.widget.sigChanged.disconnect(self.widgetValueChanged) - try: - if force or val != self.widget.value(): + if force or not fn.eq(val, self.widget.value()): + try: + self.widget.sigChanged.disconnect(self.widgetValueChanged) + self.param.sigValueChanged.disconnect(self.valueChanged) self.widget.setValue(val) - self.updateDisplayLabel(val) ## always make sure label is updated, even if values match! - finally: - self.widget.sigChanged.connect(self.widgetValueChanged) + self.param.setValue(self.widget.value()) + finally: + 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): diff --git a/pyqtgraph/parametertree/tests/test_parametertypes.py b/pyqtgraph/parametertree/tests/test_parametertypes.py index a654a9ad..e84036a9 100644 --- a/pyqtgraph/parametertree/tests/test_parametertypes.py +++ b/pyqtgraph/parametertree/tests/test_parametertypes.py @@ -127,8 +127,31 @@ def check_param_types(param, types, map_func, init, objs, keys): raise Exception("Setting %s parameter value to %r should have raised an exception." % (param, v)) - - - - - \ No newline at end of file +def test_limits_enforcement(): + p = pt.Parameter.create(name='params', type='group', children=[ + dict(name='float', type='float', limits=[0, 1]), + dict(name='int', type='int', bounds=[0, 1]), + dict(name='list', type='list', values=['x', 'y']), + dict(name='dict', type='list', values={'x': 1, 'y': 2}), + ]) + t = pt.ParameterTree() + t.setParameters(p) + for k, vin, vout in [('float', -1, 0), + ('float', 2, 1), + ('int', -1, 0), + ('int', 2, 1), + ('list', 'w', 'x'), + ('dict', 'w', 1)]: + p[k] = vin + assert p[k] == vout + + +def test_data_race(): + p = pt.Parameter.create(name='int', type='int', value=0) + + p.sigValueChanged.connect(lambda: p.setValue(1)) # connect signal before adding to tree to ensure priority + t = pt.ParameterTree() + t.setParameters(p) + pi = t.nextFocusableChild(t.invisibleRootItem()) + pi.widget.setValue(2) + assert p.value() == pi.widget.value() == 1 diff --git a/pyqtgraph/widgets/SpinBox.py b/pyqtgraph/widgets/SpinBox.py index bc83bb28..2f95a6fa 100644 --- a/pyqtgraph/widgets/SpinBox.py +++ b/pyqtgraph/widgets/SpinBox.py @@ -360,10 +360,9 @@ class SpinBox(QtGui.QAbstractSpinBox): if not isinstance(value, D): value = D(asUnicode(value)) - changed = value != self.val - prev = self.val - - self.val = value + prev, self.val = self.val, value + changed = not fn.eq(value, prev) # use fn.eq to handle nan + if update and (changed or not bounded): self.updateText(prev=prev) @@ -381,7 +380,7 @@ class SpinBox(QtGui.QAbstractSpinBox): def delayedChange(self): try: - if self.val != self.lastValEmitted: + if not fn.eq(self.val, self.lastValEmitted): # use fn.eq to handle nan self.emitChanged() except RuntimeError: pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. From 8519efdd8db5a8848f3d3a2fda8bed4811a53fcb Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Sun, 20 Dec 2020 19:12:11 -0800 Subject: [PATCH 09/26] Changed my mind --- pyqtgraph/parametertree/parameterTypes.py | 75 +++++------------------ 1 file changed, 14 insertions(+), 61 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 9ebae1e3..32b54e41 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -1,5 +1,4 @@ import os -import warnings from ..Qt import QtCore, QtGui from ..python2_3 import asUnicode @@ -96,16 +95,6 @@ class WidgetParameterItem(ParameterItem): # shrink row heights a bit for more compact look sw.setHeight(int(sw.height() * 0.9)) sb.setHeight(int(sb.height() * 0.9)) - # manually override size hints for certain parameter types - t = self.param.opts['type'] - if t == 'text': - sw.setWidth(100) - sw.setHeight(100) - elif t == 'color': - sw.setWidth(100) - elif t == 'colormap': - sw.setWidth(100) - sw.setHeight(35) if self.asSubItem: self.setSizeHint(1, sb) self.subItem.setSizeHint(0, sw) @@ -162,17 +151,10 @@ class WidgetParameterItem(ParameterItem): w.value = lambda: asUnicode(w.text()) w.setValue = lambda v: w.setText(asUnicode(v)) w.sigChanging = w.textChanged - elif t == 'text': - w = QtGui.QTextEdit() - w.setMaximumHeight(100) - w.setReadOnly(opts.get('readonly', False)) - w.value = lambda: str(w.toPlainText()) - w.setValue = w.setPlainText - w.sigChanged = w.textChanged - self.hideWidget = False - self.asSubItem = True elif t == 'color': w = ColorButton() + h = w.sizeHint().height() + w.sizeHint = lambda: QtCore.QSize(100, h) w.sigChanged = w.sigColorChanged w.sigChanging = w.sigColorChanging w.value = w.color @@ -183,6 +165,7 @@ class WidgetParameterItem(ParameterItem): elif t == 'colormap': from ..widgets.GradientWidget import GradientWidget ## need this here to avoid import loop w = GradientWidget(orientation='bottom') + w.sizeHint = lambda: QtCore.QSize(300, 35) w.sigChanged = w.sigGradientChangeFinished w.sigChanging = w.sigGradientChanged w.value = w.colorMap @@ -377,7 +360,6 @@ class SimpleParameter(Parameter): 'float': float, 'bool': bool, 'str': asUnicode, - 'text': asUnicode, 'color': self._interpColor, 'colormap': self._interpColormap, }[self.opts['type']] @@ -396,7 +378,6 @@ registerParameterType('int', SimpleParameter, override=True) registerParameterType('float', SimpleParameter, override=True) registerParameterType('bool', SimpleParameter, override=True) registerParameterType('str', SimpleParameter, override=True) -registerParameterType('text', SimpleParameter, override=True) registerParameterType('color', SimpleParameter, override=True) registerParameterType('colormap', SimpleParameter, override=True) @@ -681,49 +662,21 @@ registerParameterType('action', ActionParameter, override=True) class TextParameterItem(WidgetParameterItem): - def __init__(self, param, depth): - warnings.warn( - "This functionality is now handled directly by WidgetParameterItem.", - DeprecationWarning, stacklevel=2 - ) - WidgetParameterItem.__init__(self, param, depth) - self.hideWidget = False - self.subItem = QtGui.QTreeWidgetItem() - self.addChild(self.subItem) - - def treeWidgetChanged(self): - ## TODO: fix so that superclass method can be called - ## (WidgetParameter should just natively support this style) - #WidgetParameterItem.treeWidgetChanged(self) - tw = self.treeWidget() - if tw is None: - return - - tw.setFirstItemColumnSpanned(self.subItem, True) - tw.setItemWidget(self.subItem, 0, self.textBox) - - # for now, these are copied from ParameterItem.treeWidgetChanged - self.setHidden(not self.param.opts.get('visible', True)) - self.setExpanded(self.param.opts.get('expanded', True)) - def makeWidget(self): - self.textBox = QtGui.QTextEdit() - self.textBox.setMaximumHeight(100) - self.textBox.setReadOnly(self.param.opts.get('readonly', False)) - self.textBox.value = lambda: str(self.textBox.toPlainText()) - self.textBox.setValue = self.textBox.setPlainText - self.textBox.sigChanged = self.textBox.textChanged - return self.textBox + self.hideWidget = False + self.asSubItem = True + self.textBox = w = QtGui.QTextEdit() + w.sizeHint = lambda: QtCore.QSize(300, 100) + w.setReadOnly(self.param.opts.get('readonly', False)) + w.value = lambda: str(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 - def __init__(self, *args, **kwargs): - warnings.warn( - "This functionality is now handled directly by SimpleParameter.", - DeprecationWarning, stacklevel=2 - ) - super().__init__(*args, **kwargs) -# registerParameterType('text', TextParameter, override=True) + +registerParameterType('text', TextParameter, override=True) From bea0fc1135ffa4bf613741ae607834fc7a23f09a Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Sun, 20 Dec 2020 19:29:30 -0800 Subject: [PATCH 10/26] No interaction with WidgetParameterItem.subItem --- pyqtgraph/parametertree/parameterTypes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 32b54e41..3f694bce 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -36,11 +36,11 @@ class WidgetParameterItem(ParameterItem): def __init__(self, param, depth): ParameterItem.__init__(self, param, depth) - self.asSubItem = False + 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 into column 1 with a display label and default button. + # build widget with a display label and default button w = self.makeWidget() self.widget = w self.eventProxy = EventProxy(w, self.widgetEventFilter) @@ -48,6 +48,7 @@ class WidgetParameterItem(ParameterItem): if self.asSubItem: self.subItem = QtGui.QTreeWidgetItem() self.subItem.depth = self.depth + 1 + self.subItem.setFlags(QtCore.Qt.NoItemFlags) self.addChild(self.subItem) self.defaultBtn = QtGui.QPushButton() @@ -105,7 +106,10 @@ class WidgetParameterItem(ParameterItem): def makeWidget(self): """ - Return a single widget that should be placed in the second tree column. + 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: ========== ============================================================ From d737d80c52be4577c51c501c6a7cb83b7160c53d Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Sun, 20 Dec 2020 23:07:06 -0800 Subject: [PATCH 11/26] Improved data race test --- .../parametertree/tests/test_parametertypes.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/parametertree/tests/test_parametertypes.py b/pyqtgraph/parametertree/tests/test_parametertypes.py index e84036a9..4ce19ebf 100644 --- a/pyqtgraph/parametertree/tests/test_parametertypes.py +++ b/pyqtgraph/parametertree/tests/test_parametertypes.py @@ -147,11 +147,21 @@ def test_limits_enforcement(): def test_data_race(): + # Ensure widgets don't override user setting of param values whether + # they connect the signal before or after it's added to a tree p = pt.Parameter.create(name='int', type='int', value=0) - - p.sigValueChanged.connect(lambda: p.setValue(1)) # connect signal before adding to tree to ensure priority t = pt.ParameterTree() + + def override(): + p.setValue(1) + + p.sigValueChanged.connect(override) t.setParameters(p) - pi = t.nextFocusableChild(t.invisibleRootItem()) + pi, = t.listAllItems() + assert pi.param is p + pi.widget.setValue(2) + assert p.value() == pi.widget.value() == 1 + p.sigValueChanged.disconnect(override) + p.sigValueChanged.connect(override) pi.widget.setValue(2) assert p.value() == pi.widget.value() == 1 From 4561b18377940c44c5b848574e1d60ef8c210508 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 12:30:08 -0800 Subject: [PATCH 12/26] Consistently emit values with Parameter.sigValueChanging --- pyqtgraph/parametertree/parameterTypes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 3f694bce..f4bb1b39 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -245,9 +245,7 @@ class WidgetParameterItem(ParameterItem): Called when the widget's value is changing, but not finalized. For example: editing text before pressing enter or changing focus. """ - # This is a bit sketchy: assume the last argument of each signal is - # the value.. - self.param.sigValueChanging.emit(self.param, args[-1]) + 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)""" From 944b75ffcde1dc2fb90891444301d70ec56e6dee Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 17:11:22 -0800 Subject: [PATCH 13/26] Don't miss any widgets when updating readonly --- pyqtgraph/parametertree/parameterTypes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index f4bb1b39..39cdf907 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -146,7 +146,6 @@ class WidgetParameterItem(ParameterItem): w.sigChanged = w.toggled w.value = w.isChecked w.setValue = w.setChecked - w.setEnabled(not opts.get('readonly', False)) self.hideWidget = False elif t == 'str': w = QtGui.QLineEdit() @@ -165,7 +164,6 @@ class WidgetParameterItem(ParameterItem): w.setValue = w.setColor self.hideWidget = False w.setFlat(True) - w.setEnabled(not opts.get('readonly', False)) elif t == 'colormap': from ..widgets.GradientWidget import GradientWidget ## need this here to avoid import loop w = GradientWidget(orientation='bottom') @@ -308,9 +306,11 @@ class WidgetParameterItem(ParameterItem): if 'readonly' in opts: self.updateDefaultBtn() - if isinstance(self.widget, (QtGui.QCheckBox,ColorButton)): + if hasattr(self.widget, 'setReadOnly'): + self.widget.setReadOnly(opts['readonly']) + else: self.widget.setEnabled(not opts['readonly']) - + if 'tip' in opts: self.widget.setToolTip(opts['tip']) @@ -669,7 +669,6 @@ class TextParameterItem(WidgetParameterItem): self.asSubItem = True self.textBox = w = QtGui.QTextEdit() w.sizeHint = lambda: QtCore.QSize(300, 100) - w.setReadOnly(self.param.opts.get('readonly', False)) w.value = lambda: str(w.toPlainText()) w.setValue = w.setPlainText w.sigChanged = w.textChanged From fab7b20094258cba265baa14081bdf64c0810e1d Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 17:18:34 -0800 Subject: [PATCH 14/26] ColorButton sizeHint override unnecessary --- pyqtgraph/parametertree/parameterTypes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 39cdf907..1062cc07 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -156,8 +156,6 @@ class WidgetParameterItem(ParameterItem): w.sigChanging = w.textChanged elif t == 'color': w = ColorButton() - h = w.sizeHint().height() - w.sizeHint = lambda: QtCore.QSize(100, h) w.sigChanged = w.sigColorChanged w.sigChanging = w.sigColorChanging w.value = w.color From 4e8a609375a31e211fc8f76e6d15a7399a94ac46 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 17:34:43 -0800 Subject: [PATCH 15/26] Fixed bugs with setting expanded and setExpanded --- pyqtgraph/parametertree/ParameterItem.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py index d5a58077..4cdbfaff 100644 --- a/pyqtgraph/parametertree/ParameterItem.py +++ b/pyqtgraph/parametertree/ParameterItem.py @@ -180,14 +180,8 @@ class ParameterItem(QtGui.QTreeWidgetItem): self.setHidden(not opts['visible']) if 'expanded' in opts: - if self.param.opts['syncExpanded']: - if self.isExpanded() != opts['expanded']: - self.setExpanded(opts['expanded']) - - if 'syncExpanded' in opts: - if opts['syncExpanded']: - if self.isExpanded() != self.param.opts['expanded']: - self.setExpanded(self.param.opts['expanded']) + if self.isExpanded() != opts['expanded']: + self.setExpanded(opts['expanded']) if 'title' in opts: self.titleChanged() From 4618c705e24510362b243371af68a6da9b75f4cc Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 19:01:54 -0800 Subject: [PATCH 16/26] Fixed bug where enabled opt is not respected --- pyqtgraph/parametertree/parameterTypes.py | 29 ++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 1062cc07..98c29a0d 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -213,7 +213,8 @@ class WidgetParameterItem(ParameterItem): def updateDefaultBtn(self): ## enable/disable default btn - self.defaultBtn.setEnabled(not self.param.valueIsDefault() and self.param.writable()) + 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()) @@ -301,13 +302,17 @@ class WidgetParameterItem(ParameterItem): """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(not opts['readonly']) + self.widget.setEnabled(self.param.opts['enabled'] and not opts['readonly']) if 'tip' in opts: self.widget.setToolTip(opts['tip']) @@ -415,7 +420,9 @@ class GroupParameterItem(ParameterItem): 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. @@ -474,7 +481,10 @@ class GroupParameterItem(ParameterItem): if 'addList' in opts: self.updateAddList() - + + if 'enabled' in opts and hasattr(self, 'addWidget'): + self.addWidget.setEnabled(opts['enabled']) + def updateAddList(self): self.addWidget.blockSignals(True) try: @@ -632,6 +642,7 @@ class ActionParameterItem(ParameterItem): self.layout.addStretch() self.button.clicked.connect(self.buttonClicked) self.titleChanged() + self.optsChanged(self.param, self.param.opts) def treeWidgetChanged(self): ParameterItem.treeWidgetChanged(self) @@ -645,7 +656,13 @@ class ActionParameterItem(ParameterItem): 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']) + def buttonClicked(self): self.param.activate() From b164e54e15af74045a3a51f833f74f453f3b092f Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 20:48:15 -0800 Subject: [PATCH 17/26] Get parameter's item in a nicer way --- pyqtgraph/parametertree/tests/test_parametertypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/parametertree/tests/test_parametertypes.py b/pyqtgraph/parametertree/tests/test_parametertypes.py index 4ce19ebf..5e0957c3 100644 --- a/pyqtgraph/parametertree/tests/test_parametertypes.py +++ b/pyqtgraph/parametertree/tests/test_parametertypes.py @@ -157,7 +157,7 @@ def test_data_race(): p.sigValueChanged.connect(override) t.setParameters(p) - pi, = t.listAllItems() + pi = next(iter(p.items)) assert pi.param is p pi.widget.setValue(2) assert p.value() == pi.widget.value() == 1 From acca19a1b3982b3f6be596bcc80fc92e93bb1f21 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 22:22:12 -0800 Subject: [PATCH 18/26] Clearing selection on wheelEvent is buggy, so don't --- pyqtgraph/parametertree/ParameterTree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py index a9c9c3e1..145c448c 100644 --- a/pyqtgraph/parametertree/ParameterTree.py +++ b/pyqtgraph/parametertree/ParameterTree.py @@ -159,9 +159,9 @@ class ParameterTree(TreeWidget): sel[0].selected(True) return TreeWidget.selectionChanged(self, *args) - def wheelEvent(self, ev): - self.clearSelection() - return TreeWidget.wheelEvent(self, ev) + # def wheelEvent(self, ev): + # self.clearSelection() + # return TreeWidget.wheelEvent(self, ev) def sizeHint(self): w, h = 0, 0 From 226003084581a0c4d4ce12f1d057769d4493b6ef Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 22:34:12 -0800 Subject: [PATCH 19/26] Focus change bug fix --- pyqtgraph/parametertree/parameterTypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 98c29a0d..c7bb8363 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -194,8 +194,8 @@ class WidgetParameterItem(ParameterItem): self.showEditor() def isFocusable(self): - return self.param.writable() - + 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) From 6a8f7779dccbc8b82921f97e0038317619faa07d Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 23:09:19 -0800 Subject: [PATCH 20/26] Add tool tip support to action and group parameters --- examples/parametertree.py | 6 +++--- pyqtgraph/parametertree/parameterTypes.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/parametertree.py b/examples/parametertree.py index 01fc13bf..110b164f 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -65,7 +65,7 @@ params = [ {'name': 'Basic parameter data types', 'type': 'group', 'children': [ {'name': 'Integer', 'type': 'int', 'value': 10}, {'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False}, - {'name': 'String', 'type': 'str', 'value': "hi"}, + {'name': 'String', 'type': 'str', 'value': "hi", 'tip': 'Well hello'}, {'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2}, {'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": "twosies", "three": [3,3,3]}, 'value': 2}, {'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"}, @@ -76,7 +76,7 @@ params = [ {'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6}, ]}, {'name': 'Text Parameter', 'type': 'text', 'value': 'Some text...'}, - {'name': 'Action Parameter', 'type': 'action'}, + {'name': 'Action Parameter', 'type': 'action', 'tip': 'Click me'}, ]}, {'name': 'Numerical Parameter Options', 'type': 'group', 'children': [ {'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'}, @@ -107,7 +107,7 @@ params = [ }}, ]}, ComplexParameter(name='Custom parameter group (reciprocal values)'), - ScalableGroup(name="Expandable Parameter Group", children=[ + ScalableGroup(name="Expandable Parameter Group", tip='Click to add children', children=[ {'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"}, {'name': 'ScalableParam 2', 'type': 'str', 'value': "default param 2"}, ]), diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index c7bb8363..656c69cb 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -482,8 +482,12 @@ class GroupParameterItem(ParameterItem): if 'addList' in opts: self.updateAddList() - if 'enabled' in opts and hasattr(self, 'addWidget'): - self.addWidget.setEnabled(opts['enabled']) + 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) @@ -663,6 +667,9 @@ class ActionParameterItem(ParameterItem): if 'enabled' in opts: self.button.setEnabled(opts['enabled']) + if 'tip' in opts: + self.button.setToolTip(opts['tip']) + def buttonClicked(self): self.param.activate() From 8105385230eb5a83a1a4afcc02bb75391c1a7bbb Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 23:10:44 -0800 Subject: [PATCH 21/26] Add not enabled example --- examples/parametertree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/parametertree.py b/examples/parametertree.py index 110b164f..6fd8f38d 100644 --- a/examples/parametertree.py +++ b/examples/parametertree.py @@ -93,6 +93,7 @@ params = [ ]}, {'name': 'Extra Parameter Options', 'type': 'group', 'children': [ {'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True}, + {'name': 'Disabled', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'enabled': False}, {'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True}, {'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True}, ]}, From c2f0de04712b7ad81ced0334a3515558d03776f5 Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Mon, 21 Dec 2020 23:15:57 -0800 Subject: [PATCH 22/26] Attempt at passing Qt 4 CI pipelines --- pyqtgraph/parametertree/parameterTypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 656c69cb..97c92ddd 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -199,6 +199,7 @@ class WidgetParameterItem(ParameterItem): def valueChanged(self, param, val, force=False): ## called when the parameter's value has changed ParameterItem.valueChanged(self, param, val) + val = self.param.value() # val might not have been current if force or not fn.eq(val, self.widget.value()): try: self.widget.sigChanged.disconnect(self.widgetValueChanged) From daafb1d80144e764ff8d14ba25553a7d3e3b6e7e Mon Sep 17 00:00:00 2001 From: Daniel Lidstrom Date: Wed, 23 Dec 2020 00:05:36 -0800 Subject: [PATCH 23/26] Revert previous commit --- pyqtgraph/parametertree/parameterTypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index 97c92ddd..656c69cb 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -199,7 +199,6 @@ class WidgetParameterItem(ParameterItem): def valueChanged(self, param, val, force=False): ## called when the parameter's value has changed ParameterItem.valueChanged(self, param, val) - val = self.param.value() # val might not have been current if force or not fn.eq(val, self.widget.value()): try: self.widget.sigChanged.disconnect(self.widgetValueChanged) From 0495f1dccec137f185434f1b95a655bbe6058beb Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 31 Jan 2021 20:39:07 -0800 Subject: [PATCH 24/26] Replace deprecated method with suggested one --- pyqtgraph/parametertree/parameterTypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py index d01dd272..5001ac2e 100644 --- a/pyqtgraph/parametertree/parameterTypes.py +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -288,7 +288,7 @@ class WidgetParameterItem(ParameterItem): if tree is None: return if self.asSubItem: - tree.setFirstItemColumnSpanned(self.subItem, True) + self.subItem.setFirstColumnSpanned(True) tree.setItemWidget(self.subItem, 0, self.widget) tree.setItemWidget(self, 1, self.layoutWidget) self.displayLabel.hide() From d70a009c298d7c9a8b6acbab1d0b6f06f89bb1d0 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 31 Jan 2021 21:17:44 -0800 Subject: [PATCH 25/26] Address more PyQt6 enum type shenanigans --- pyqtgraph/Qt.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py index 83654c93..52883262 100644 --- a/pyqtgraph/Qt.py +++ b/pyqtgraph/Qt.py @@ -395,7 +395,7 @@ if QT_LIB == PYSIDE6: if QT_LIB == PYQT6: # module.Class.EnumClass.Enum -> module.Class.Enum def promote_enums(module): - class_names = [x for x in dir(module) if x[0] == 'Q'] + class_names = [x for x in dir(module) if x.startswith('Q')] for class_name in class_names: klass = getattr(module, class_name) if not isinstance(klass, sip.wrappertype): @@ -418,6 +418,18 @@ if QT_LIB == PYQT6: for e in QtCore.Qt.Key: setattr(QtCore.Qt, e.name, e.value) + # TextFlags are not accepted as appropriate types + # Example: + # TypeError: QFontMetric.size( + # self, + # int, + # str, + # tabStops: int = 0, + # tabArray: Optional[List[int]] = 0 + # ): argument 1 has unexpected type 'TextFlag' + for e in QtCore.Qt.TextFlag: + setattr(QtCore.Qt, e.name, e.value) + # shim the old names for QPointF mouse coords QtGui.QSinglePointEvent.localPos = lambda o : o.position() QtGui.QSinglePointEvent.windowPos = lambda o : o.scenePosition() From e02fc10a40b6f12ab093d8220e493016388d3461 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 31 Jan 2021 21:52:51 -0800 Subject: [PATCH 26/26] handle text flags like axisitem --- pyqtgraph/Qt.py | 12 ------------ pyqtgraph/parametertree/ParameterItem.py | 11 +++++++++-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py index 52883262..5a5e5b69 100644 --- a/pyqtgraph/Qt.py +++ b/pyqtgraph/Qt.py @@ -418,18 +418,6 @@ if QT_LIB == PYQT6: for e in QtCore.Qt.Key: setattr(QtCore.Qt, e.name, e.value) - # TextFlags are not accepted as appropriate types - # Example: - # TypeError: QFontMetric.size( - # self, - # int, - # str, - # tabStops: int = 0, - # tabArray: Optional[List[int]] = 0 - # ): argument 1 has unexpected type 'TextFlag' - for e in QtCore.Qt.TextFlag: - setattr(QtCore.Qt, e.name, e.value) - # shim the old names for QPointF mouse coords QtGui.QSinglePointEvent.localPos = lambda o : o.position() QtGui.QSinglePointEvent.windowPos = lambda o : o.scenePosition() diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py index 7629a933..47b91b11 100644 --- a/pyqtgraph/parametertree/ParameterItem.py +++ b/pyqtgraph/parametertree/ParameterItem.py @@ -1,4 +1,4 @@ -from ..Qt import QtGui, QtCore +from ..Qt import QtGui, QtCore, QT_LIB from ..python2_3 import asUnicode import os, weakref, re @@ -162,7 +162,14 @@ class ParameterItem(QtGui.QTreeWidgetItem): # called when the user-visble title has changed (either opts['title'], or name if title is None) self.setText(0, self.param.title()) fm = QtGui.QFontMetrics(self.font(0)) - size = fm.size(QtCore.Qt.TextSingleLine, self.text(0)) + + if QT_LIB == 'PyQt6': + # PyQt6 doesn't allow or-ing of different enum types + # so we need to take its value property + textFlags = QtCore.Qt.TextSingleLine.value + else: + textFlags = QtCore.Qt.TextSingleLine + size = fm.size(textFlags, self.text(0)) size.setHeight(int(size.height() * 1.35)) size.setWidth(int(size.width() * 1.15)) self.setSizeHint(0, size)