Fix: Parameter tree ignores user-set 'expanded' state (#1175)

* Fix: Parameter tree ignores user-set 'expanded' state

When setting the 'expanded' state of parameters, this change is not applied
in the graphically visible tree. This commit changes that behaviour by
adding a clause in `ParameterItem.optsChanged` to react to that.

Fixes #1130

* ParameterTree: Add option to synchronize "expanded" state

As seen in #1130, there is interest in synchronizing the "expanded" state
of `Parameter`s in `ParameterTree`s. As a default, this would lead to
users being forced to always have multiple `ParameterTree`s to be
expanded in the exact same way. Since that might not be desirable, this
commit adds an option to customize whether synchronization
of the "expanded" state should happen.

* Fix: Sync Parameter options "renamable" and "removable" with ParameterTrees

Currently, `Parameter` options `renamable` and `removable` are only considered
when building a new `ParameterTree`. This commit makes changes in those
options reflected in the corresponding `ParameterItem`s.

* ParameterTree: Reflect changes in Parameter option 'tip'

* Parameter: When setting "syncExpanded", update "expanded" state directly

Co-authored-by: 2xB <2xB@users.noreply.github.com>
This commit is contained in:
2xB 2020-05-30 22:01:39 +02:00 committed by GitHub
parent 03b8385e62
commit 7672b5b725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 32 deletions

View File

@ -137,9 +137,12 @@ class Parameter(QtCore.QObject):
(default=False)
removable If True, the user may remove this Parameter.
(default=False)
expanded If True, the Parameter will appear expanded when
displayed in a ParameterTree (its children will be
visible). (default=True)
expanded If True, the Parameter will initially be expanded in
ParameterTrees: Its children will be visible.
(default=True)
syncExpanded If True, the `expanded` state of this Parameter is
synchronized with all ParameterTrees it is displayed in.
(default=False)
title (str or None) If specified, then the parameter will be
displayed to the user using this string as its name.
However, the parameter will still be referred to
@ -161,6 +164,7 @@ class Parameter(QtCore.QObject):
'removable': False,
'strictNaming': False, # forces name to be usable as a python variable
'expanded': True,
'syncExpanded': False,
'title': None,
#'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits.
}
@ -461,7 +465,7 @@ class Parameter(QtCore.QObject):
Set any arbitrary options on this parameter.
The exact behavior of this function will depend on the parameter type, but
most parameters will accept a common set of options: value, name, limits,
default, readonly, removable, renamable, visible, enabled, and expanded.
default, readonly, removable, renamable, visible, enabled, expanded and syncExpanded.
See :func:`Parameter.__init__ <pyqtgraph.parametertree.Parameter.__init__>`
for more information on default options.

View File

@ -34,30 +34,20 @@ class ParameterItem(QtGui.QTreeWidgetItem):
param.sigOptionsChanged.connect(self.optsChanged)
param.sigParentChanged.connect(self.parentChanged)
opts = param.opts
self.updateFlags()
## flag used internally during name editing
self.ignoreNameColumnChange = False
def updateFlags(self):
## called when Parameter opts changed
opts = self.param.opts
## Generate context menu for renaming/removing parameter
self.contextMenu = QtGui.QMenu()
self.contextMenu.addSeparator()
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
if opts.get('renamable', False):
if param.opts.get('title', None) is not None:
if opts.get('title', None) is not None:
raise Exception("Cannot make parameter with both title != None and renamable == True.")
flags |= QtCore.Qt.ItemIsEditable
self.contextMenu.addAction('Rename').triggered.connect(self.editName)
if opts.get('removable', False):
self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove)
# context menu
context = opts.get('context', None)
if isinstance(context, list):
for name in context:
self.contextMenu.addAction(name).triggered.connect(
self.contextMenuTriggered(name))
elif isinstance(context, dict):
for name, title in context.items():
self.contextMenu.addAction(title).triggered.connect(
self.contextMenuTriggered(name))
## handle movable / dropEnabled options
if opts.get('movable', False):
@ -65,9 +55,6 @@ class ParameterItem(QtGui.QTreeWidgetItem):
if opts.get('dropEnabled', False):
flags |= QtCore.Qt.ItemIsDropEnabled
self.setFlags(flags)
## flag used internally during name editing
self.ignoreNameColumnChange = False
def valueChanged(self, param, val):
@ -120,7 +107,26 @@ class ParameterItem(QtGui.QTreeWidgetItem):
if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False)\
and "context" not in self.param.opts:
return
## Generate context menu for renaming/removing parameter
self.contextMenu = QtGui.QMenu() # Put in global name space to prevent garbage collection
self.contextMenu.addSeparator()
if self.param.opts.get('renamable', False):
self.contextMenu.addAction('Rename').triggered.connect(self.editName)
if self.param.opts.get('removable', False):
self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove)
# context menu
context = opts.get('context', None)
if isinstance(context, list):
for name in context:
self.contextMenu.addAction(name).triggered.connect(
self.contextMenuTriggered(name))
elif isinstance(context, dict):
for name, title in context.items():
self.contextMenu.addAction(title).triggered.connect(
self.contextMenuTriggered(name))
self.contextMenu.popup(ev.globalPos())
def columnChangedEvent(self, col):
@ -141,6 +147,10 @@ class ParameterItem(QtGui.QTreeWidgetItem):
self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back.
finally:
self.ignoreNameColumnChange = False
def expandedChangedEvent(self, expanded):
if self.param.opts['syncExpanded']:
self.param.setOpts(expanded=expanded)
def nameChanged(self, param, name):
## called when the parameter's name has changed.
@ -158,10 +168,22 @@ class ParameterItem(QtGui.QTreeWidgetItem):
def optsChanged(self, param, opts):
"""Called when any options are changed that are not
name, value, default, or limits"""
#print opts
if 'visible' in opts:
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'])
self.updateFlags()
def contextMenuTriggered(self, name):
def trigger():
self.param.contextMenu(name)

View File

@ -28,6 +28,8 @@ class ParameterTree(TreeWidget):
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
self.setHeaderHidden(not showHeader)
self.itemChanged.connect(self.itemChangedEvent)
self.itemExpanded.connect(self.itemExpandedEvent)
self.itemCollapsed.connect(self.itemCollapsedEvent)
self.lastSel = None
self.setRootIsDecorated(False)
@ -134,6 +136,14 @@ class ParameterTree(TreeWidget):
def itemChangedEvent(self, item, col):
if hasattr(item, 'columnChangedEvent'):
item.columnChangedEvent(col)
def itemExpandedEvent(self, item):
if hasattr(item, 'expandedChangedEvent'):
item.expandedChangedEvent(True)
def itemCollapsedEvent(self, item):
if hasattr(item, 'expandedChangedEvent'):
item.expandedChangedEvent(False)
def selectionChanged(self, *args):
sel = self.selectedItems()

View File

@ -44,10 +44,6 @@ class WidgetParameterItem(ParameterItem):
self.widget = w
self.eventProxy = EventProxy(w, self.widgetEventFilter)
opts = self.param.opts
if 'tip' in opts:
w.setToolTip(opts['tip'])
self.defaultBtn = QtGui.QPushButton()
self.defaultBtn.setFixedWidth(20)
self.defaultBtn.setFixedHeight(20)
@ -73,6 +69,7 @@ class WidgetParameterItem(ParameterItem):
w.sigChanging.connect(self.widgetValueChanging)
## update value shown in widget.
opts = self.param.opts
if opts.get('value', None) is not None:
self.valueChanged(self, opts['value'], force=True)
else:
@ -80,6 +77,8 @@ class WidgetParameterItem(ParameterItem):
self.widgetValueChanged()
self.updateDefaultBtn()
self.optsChanged(self.param, self.param.opts)
def makeWidget(self):
"""
@ -280,6 +279,9 @@ class WidgetParameterItem(ParameterItem):
if isinstance(self.widget, (QtGui.QCheckBox,ColorButton)):
self.widget.setEnabled(not opts['readonly'])
if 'tip' in opts:
self.widget.setToolTip(opts['tip'])
## If widget is a SpinBox, pass options straight through
if isinstance(self.widget, SpinBox):
# send only options supported by spinbox