Merge pull request #1469 from lidstrom83/ptree
Various improvements to parametertree
This commit is contained in:
commit
9da7b8f7c1
@ -64,8 +64,8 @@ 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': 'String', 'type': 'str', 'value': "hi"},
|
||||
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1, 'finite': False},
|
||||
{'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'},
|
||||
@ -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},
|
||||
]},
|
||||
@ -107,7 +108,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"},
|
||||
]),
|
||||
@ -171,7 +172,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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
from os import path, listdir
|
||||
import collections
|
||||
|
||||
@ -178,7 +178,6 @@ def _get_from_colorcet(name):
|
||||
return cm
|
||||
|
||||
|
||||
|
||||
class ColorMap(object):
|
||||
"""
|
||||
A ColorMap defines a relationship between a scalar value and a range of colors.
|
||||
@ -443,3 +442,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)
|
||||
|
@ -425,18 +425,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))
|
||||
|
@ -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
|
||||
@ -288,15 +289,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
|
||||
|
@ -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
|
||||
|
||||
@ -161,6 +161,18 @@ 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))
|
||||
|
||||
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)
|
||||
|
||||
def limitsChanged(self, param, limits):
|
||||
"""Called when the parameter's limits have changed"""
|
||||
@ -177,14 +189,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()
|
||||
|
@ -159,6 +159,36 @@ class ParameterTree(TreeWidget):
|
||||
sel[0].selected(True)
|
||||
return super().selectionChanged(*args)
|
||||
|
||||
def wheelEvent(self, ev):
|
||||
self.clearSelection()
|
||||
return super().wheelEvent(ev)
|
||||
# commented out due to being unreliable
|
||||
# def wheelEvent(self, ev):
|
||||
# self.clearSelection()
|
||||
# return super().wheelEvent(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)
|
||||
|
@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from ..Qt import QtCore, QtGui
|
||||
from ..python2_3 import asUnicode
|
||||
from .Parameter import Parameter, registerParameterType
|
||||
@ -8,7 +8,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
|
||||
|
||||
|
||||
@ -35,15 +34,22 @@ class WidgetParameterItem(ParameterItem):
|
||||
"""
|
||||
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 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)
|
||||
|
||||
|
||||
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()
|
||||
self.defaultBtn.setAutoDefault(False)
|
||||
self.defaultBtn.setFixedWidth(20)
|
||||
@ -57,8 +63,10 @@ class WidgetParameterItem(ParameterItem):
|
||||
layout = QtGui.QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(2)
|
||||
layout.addWidget(w)
|
||||
layout.addWidget(self.displayLabel)
|
||||
if not self.asSubItem:
|
||||
layout.addWidget(w, 1)
|
||||
layout.addWidget(self.displayLabel, 1)
|
||||
layout.addStretch(0)
|
||||
layout.addWidget(self.defaultBtn)
|
||||
self.layoutWidget = QtGui.QWidget()
|
||||
self.layoutWidget.setLayout(layout)
|
||||
@ -81,9 +89,26 @@ 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(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 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:
|
||||
|
||||
========== ============================================================
|
||||
@ -120,7 +145,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()
|
||||
@ -137,15 +161,16 @@ 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')
|
||||
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
|
||||
else:
|
||||
raise Exception("Unknown type '%s'" % asUnicode(t))
|
||||
return w
|
||||
@ -168,23 +193,27 @@ 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)
|
||||
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):
|
||||
## 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())
|
||||
@ -212,9 +241,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)"""
|
||||
@ -260,9 +287,12 @@ class WidgetParameterItem(ParameterItem):
|
||||
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)
|
||||
self.selected(False)
|
||||
|
||||
def defaultClicked(self):
|
||||
self.param.setToDefault()
|
||||
@ -271,12 +301,18 @@ 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 isinstance(self.widget, (QtGui.QCheckBox,ColorButton)):
|
||||
self.widget.setEnabled(not opts['readonly'])
|
||||
|
||||
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'])
|
||||
|
||||
@ -331,11 +367,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))
|
||||
@ -403,8 +434,12 @@ 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())
|
||||
|
||||
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.
|
||||
@ -416,7 +451,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)))
|
||||
@ -425,8 +459,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.
|
||||
@ -464,7 +498,14 @@ class GroupParameterItem(ParameterItem):
|
||||
|
||||
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:
|
||||
@ -633,12 +674,14 @@ 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()
|
||||
self.optsChanged(self.param, self.param.opts)
|
||||
|
||||
def treeWidgetChanged(self):
|
||||
ParameterItem.treeWidgetChanged(self)
|
||||
tree = self.treeWidget()
|
||||
@ -650,8 +693,17 @@ class ActionParameterItem(ParameterItem):
|
||||
|
||||
def titleChanged(self):
|
||||
self.button.setText(self.param.title())
|
||||
ParameterItem.titleChanged(self)
|
||||
|
||||
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()
|
||||
|
||||
@ -672,39 +724,21 @@ registerParameterType('action', ActionParameter, override=True)
|
||||
|
||||
class TextParameterItem(WidgetParameterItem):
|
||||
"""ParameterItem displaying a QTextEdit widget."""
|
||||
def __init__(self, param, depth):
|
||||
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
|
||||
|
||||
self.subItem.setFirstColumnSpanned(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.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
|
||||
|
||||
|
||||
|
||||
|
||||
registerParameterType('text', TextParameter, override=True)
|
||||
|
@ -127,8 +127,41 @@ 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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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():
|
||||
# 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)
|
||||
t = pt.ParameterTree()
|
||||
|
||||
def override():
|
||||
p.setValue(1)
|
||||
|
||||
p.sigValueChanged.connect(override)
|
||||
t.setParameters(p)
|
||||
pi = next(iter(p.items))
|
||||
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
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user