Merge branch 'flowchart_plotnode_menu' into develop
This commit is contained in:
commit
e62c833a0c
|
@ -58,11 +58,15 @@ fc.setInput(dataIn=data)
|
||||||
|
|
||||||
## populate the flowchart with a basic set of processing nodes.
|
## populate the flowchart with a basic set of processing nodes.
|
||||||
## (usually we let the user do this)
|
## (usually we let the user do this)
|
||||||
|
plotList = {'Top Plot': pw1, 'Bottom Plot': pw2}
|
||||||
|
|
||||||
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
|
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
|
||||||
|
pw1Node.setPlotList(plotList)
|
||||||
pw1Node.setPlot(pw1)
|
pw1Node.setPlot(pw1)
|
||||||
|
|
||||||
pw2Node = fc.createNode('PlotWidget', pos=(150, -150))
|
pw2Node = fc.createNode('PlotWidget', pos=(150, -150))
|
||||||
pw2Node.setPlot(pw2)
|
pw2Node.setPlot(pw2)
|
||||||
|
pw2Node.setPlotList(plotList)
|
||||||
|
|
||||||
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
|
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
|
||||||
fNode.ctrls['sigma'].setValue(5)
|
fNode.ctrls['sigma'].setValue(5)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import weakref
|
||||||
from ...Qt import QtCore, QtGui
|
from ...Qt import QtCore, QtGui
|
||||||
from ...graphicsItems.ScatterPlotItem import ScatterPlotItem
|
from ...graphicsItems.ScatterPlotItem import ScatterPlotItem
|
||||||
from ...graphicsItems.PlotCurveItem import PlotCurveItem
|
from ...graphicsItems.PlotCurveItem import PlotCurveItem
|
||||||
from ... import PlotDataItem
|
from ... import PlotDataItem, ComboBox
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -16,7 +16,9 @@ class PlotWidgetNode(Node):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}})
|
Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}})
|
||||||
self.plot = None
|
self.plot = None # currently selected plot
|
||||||
|
self.plots = {} # list of available plots user may select from
|
||||||
|
self.ui = None
|
||||||
self.items = {}
|
self.items = {}
|
||||||
|
|
||||||
def disconnected(self, localTerm, remoteTerm):
|
def disconnected(self, localTerm, remoteTerm):
|
||||||
|
@ -26,16 +28,27 @@ class PlotWidgetNode(Node):
|
||||||
|
|
||||||
def setPlot(self, plot):
|
def setPlot(self, plot):
|
||||||
#print "======set plot"
|
#print "======set plot"
|
||||||
|
if plot == self.plot:
|
||||||
|
return
|
||||||
|
|
||||||
|
# clear data from previous plot
|
||||||
|
if self.plot is not None:
|
||||||
|
for vid in list(self.items.keys()):
|
||||||
|
self.plot.removeItem(self.items[vid])
|
||||||
|
del self.items[vid]
|
||||||
|
|
||||||
self.plot = plot
|
self.plot = plot
|
||||||
|
self.updateUi()
|
||||||
|
self.update()
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
|
||||||
def getPlot(self):
|
def getPlot(self):
|
||||||
return self.plot
|
return self.plot
|
||||||
|
|
||||||
def process(self, In, display=True):
|
def process(self, In, display=True):
|
||||||
if display:
|
if display and self.plot is not None:
|
||||||
#self.plot.clearPlots()
|
|
||||||
items = set()
|
items = set()
|
||||||
|
# Add all new input items to selected plot
|
||||||
for name, vals in In.items():
|
for name, vals in In.items():
|
||||||
if vals is None:
|
if vals is None:
|
||||||
continue
|
continue
|
||||||
|
@ -45,14 +58,13 @@ class PlotWidgetNode(Node):
|
||||||
for val in vals:
|
for val in vals:
|
||||||
vid = id(val)
|
vid = id(val)
|
||||||
if vid in self.items and self.items[vid].scene() is self.plot.scene():
|
if vid in self.items and self.items[vid].scene() is self.plot.scene():
|
||||||
|
# Item is already added to the correct scene
|
||||||
|
# possible bug: what if two plots occupy the same scene? (should
|
||||||
|
# rarely be a problem because items are removed from a plot before
|
||||||
|
# switching).
|
||||||
items.add(vid)
|
items.add(vid)
|
||||||
else:
|
else:
|
||||||
#if isinstance(val, PlotCurveItem):
|
# Add the item to the plot, or generate a new item if needed.
|
||||||
#self.plot.addItem(val)
|
|
||||||
#item = val
|
|
||||||
#if isinstance(val, ScatterPlotItem):
|
|
||||||
#self.plot.addItem(val)
|
|
||||||
#item = val
|
|
||||||
if isinstance(val, QtGui.QGraphicsItem):
|
if isinstance(val, QtGui.QGraphicsItem):
|
||||||
self.plot.addItem(val)
|
self.plot.addItem(val)
|
||||||
item = val
|
item = val
|
||||||
|
@ -60,22 +72,48 @@ class PlotWidgetNode(Node):
|
||||||
item = self.plot.plot(val)
|
item = self.plot.plot(val)
|
||||||
self.items[vid] = item
|
self.items[vid] = item
|
||||||
items.add(vid)
|
items.add(vid)
|
||||||
|
|
||||||
|
# Any left-over items that did not appear in the input must be removed
|
||||||
for vid in list(self.items.keys()):
|
for vid in list(self.items.keys()):
|
||||||
if vid not in items:
|
if vid not in items:
|
||||||
#print "remove", self.items[vid]
|
|
||||||
self.plot.removeItem(self.items[vid])
|
self.plot.removeItem(self.items[vid])
|
||||||
del self.items[vid]
|
del self.items[vid]
|
||||||
|
|
||||||
def processBypassed(self, args):
|
def processBypassed(self, args):
|
||||||
|
if self.plot is None:
|
||||||
|
return
|
||||||
for item in list(self.items.values()):
|
for item in list(self.items.values()):
|
||||||
self.plot.removeItem(item)
|
self.plot.removeItem(item)
|
||||||
self.items = {}
|
self.items = {}
|
||||||
|
|
||||||
#def setInput(self, **args):
|
def ctrlWidget(self):
|
||||||
#for k in args:
|
if self.ui is None:
|
||||||
#self.plot.plot(args[k])
|
self.ui = ComboBox()
|
||||||
|
self.ui.currentIndexChanged.connect(self.plotSelected)
|
||||||
|
self.updateUi()
|
||||||
|
return self.ui
|
||||||
|
|
||||||
|
def plotSelected(self, index):
|
||||||
|
self.setPlot(self.ui.value())
|
||||||
|
|
||||||
|
def setPlotList(self, plots):
|
||||||
|
"""
|
||||||
|
Specify the set of plots (PlotWidget or PlotItem) that the user may
|
||||||
|
select from.
|
||||||
|
|
||||||
|
*plots* must be a dictionary of {name: plot} pairs.
|
||||||
|
"""
|
||||||
|
self.plots = plots
|
||||||
|
self.updateUi()
|
||||||
|
|
||||||
|
def updateUi(self):
|
||||||
|
# sets list and automatically preserves previous selection
|
||||||
|
self.ui.setItems(self.plots)
|
||||||
|
try:
|
||||||
|
self.ui.setValue(self.plot)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CanvasNode(Node):
|
class CanvasNode(Node):
|
||||||
"""Connection to a Canvas widget."""
|
"""Connection to a Canvas widget."""
|
||||||
|
|
|
@ -95,7 +95,6 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
|
|
||||||
lastFileDir = None
|
lastFileDir = None
|
||||||
managers = {}
|
|
||||||
|
|
||||||
def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
|
def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
|
||||||
"""
|
"""
|
||||||
|
@ -369,28 +368,6 @@ class PlotItem(GraphicsWidget):
|
||||||
self.scene().removeItem(self.vb)
|
self.scene().removeItem(self.vb)
|
||||||
self.vb = None
|
self.vb = None
|
||||||
|
|
||||||
## causes invalid index errors:
|
|
||||||
#for i in range(self.layout.count()):
|
|
||||||
#self.layout.removeAt(i)
|
|
||||||
|
|
||||||
#for p in self.proxies:
|
|
||||||
#try:
|
|
||||||
#p.setWidget(None)
|
|
||||||
#except RuntimeError:
|
|
||||||
#break
|
|
||||||
#self.scene().removeItem(p)
|
|
||||||
#self.proxies = []
|
|
||||||
|
|
||||||
#self.menuAction.releaseWidget(self.menuAction.defaultWidget())
|
|
||||||
#self.menuAction.setParent(None)
|
|
||||||
#self.menuAction = None
|
|
||||||
|
|
||||||
#if self.manager is not None:
|
|
||||||
#self.manager.sigWidgetListChanged.disconnect(self.updatePlotList)
|
|
||||||
#self.manager.removeWidget(self.name)
|
|
||||||
#else:
|
|
||||||
#print "no manager"
|
|
||||||
|
|
||||||
def registerPlot(self, name): ## for backward compatibility
|
def registerPlot(self, name): ## for backward compatibility
|
||||||
self.vb.register(name)
|
self.vb.register(name)
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,212 @@
|
||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
from ..SignalProxy import SignalProxy
|
from ..SignalProxy import SignalProxy
|
||||||
|
from ..pgcollections import OrderedDict
|
||||||
|
from ..python2_3 import asUnicode
|
||||||
|
|
||||||
class ComboBox(QtGui.QComboBox):
|
class ComboBox(QtGui.QComboBox):
|
||||||
"""Extends QComboBox to add extra functionality.
|
"""Extends QComboBox to add extra functionality.
|
||||||
- updateList() - updates the items in the comboBox while blocking signals, remembers and resets to the previous values if it's still in the list
|
|
||||||
|
* Handles dict mappings -- user selects a text key, and the ComboBox indicates
|
||||||
|
the selected value.
|
||||||
|
* Requires item strings to be unique
|
||||||
|
* Remembers selected value if list is cleared and subsequently repopulated
|
||||||
|
* setItems() replaces the items in the ComboBox and blocks signals if the
|
||||||
|
value ultimately does not change.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, parent=None, items=None, default=None):
|
def __init__(self, parent=None, items=None, default=None):
|
||||||
QtGui.QComboBox.__init__(self, parent)
|
QtGui.QComboBox.__init__(self, parent)
|
||||||
|
self.currentIndexChanged.connect(self.indexChanged)
|
||||||
|
self._ignoreIndexChange = False
|
||||||
|
|
||||||
#self.value = default
|
self._chosenText = None
|
||||||
|
self._items = OrderedDict()
|
||||||
|
|
||||||
if items is not None:
|
if items is not None:
|
||||||
self.addItems(items)
|
self.setItems(items)
|
||||||
if default is not None:
|
if default is not None:
|
||||||
self.setValue(default)
|
self.setValue(default)
|
||||||
|
|
||||||
def setValue(self, value):
|
def setValue(self, value):
|
||||||
ind = self.findText(value)
|
"""Set the selected item to the first one having the given value."""
|
||||||
|
text = None
|
||||||
|
for k,v in self._items.items():
|
||||||
|
if v == value:
|
||||||
|
text = k
|
||||||
|
break
|
||||||
|
if text is None:
|
||||||
|
raise ValueError(value)
|
||||||
|
|
||||||
|
self.setText(text)
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
"""Set the selected item to the first one having the given text."""
|
||||||
|
ind = self.findText(text)
|
||||||
if ind == -1:
|
if ind == -1:
|
||||||
return
|
raise ValueError(text)
|
||||||
#self.value = value
|
#self.value = value
|
||||||
self.setCurrentIndex(ind)
|
self.setCurrentIndex(ind)
|
||||||
|
|
||||||
def updateList(self, items):
|
def value(self):
|
||||||
prevVal = str(self.currentText())
|
"""
|
||||||
try:
|
If items were given as a list of strings, then return the currently
|
||||||
|
selected text. If items were given as a dict, then return the value
|
||||||
|
corresponding to the currently selected key. If the combo list is empty,
|
||||||
|
return None.
|
||||||
|
"""
|
||||||
|
if self.count() == 0:
|
||||||
|
return None
|
||||||
|
text = asUnicode(self.currentText())
|
||||||
|
return self._items[text]
|
||||||
|
|
||||||
|
def ignoreIndexChange(func):
|
||||||
|
# Decorator that prevents updates to self._chosenText
|
||||||
|
def fn(self, *args, **kwds):
|
||||||
|
prev = self._ignoreIndexChange
|
||||||
|
self._ignoreIndexChange = True
|
||||||
|
try:
|
||||||
|
ret = func(self, *args, **kwds)
|
||||||
|
finally:
|
||||||
|
self._ignoreIndexChange = prev
|
||||||
|
return ret
|
||||||
|
return fn
|
||||||
|
|
||||||
|
def blockIfUnchanged(func):
|
||||||
|
# decorator that blocks signal emission during complex operations
|
||||||
|
# and emits currentIndexChanged only if the value has actually
|
||||||
|
# changed at the end.
|
||||||
|
def fn(self, *args, **kwds):
|
||||||
|
prevVal = self.value()
|
||||||
|
blocked = self.signalsBlocked()
|
||||||
self.blockSignals(True)
|
self.blockSignals(True)
|
||||||
|
try:
|
||||||
|
ret = func(self, *args, **kwds)
|
||||||
|
finally:
|
||||||
|
self.blockSignals(blocked)
|
||||||
|
|
||||||
|
# only emit if the value has changed
|
||||||
|
if self.value() != prevVal:
|
||||||
|
self.currentIndexChanged.emit(self.currentIndex())
|
||||||
|
|
||||||
|
return ret
|
||||||
|
return fn
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
@blockIfUnchanged
|
||||||
|
def setItems(self, items):
|
||||||
|
"""
|
||||||
|
*items* may be a list or a dict.
|
||||||
|
If a dict is given, then the keys are used to populate the combo box
|
||||||
|
and the values will be used for both value() and setValue().
|
||||||
|
"""
|
||||||
|
prevVal = self.value()
|
||||||
|
|
||||||
|
self.blockSignals(True)
|
||||||
|
try:
|
||||||
self.clear()
|
self.clear()
|
||||||
self.addItems(items)
|
self.addItems(items)
|
||||||
self.setValue(prevVal)
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.blockSignals(False)
|
self.blockSignals(False)
|
||||||
|
|
||||||
if str(self.currentText()) != prevVal:
|
# only emit if we were not able to re-set the original value
|
||||||
|
if self.value() != prevVal:
|
||||||
self.currentIndexChanged.emit(self.currentIndex())
|
self.currentIndexChanged.emit(self.currentIndex())
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.items.copy()
|
||||||
|
|
||||||
|
def updateList(self, items):
|
||||||
|
# for backward compatibility
|
||||||
|
return self.setItems(items)
|
||||||
|
|
||||||
|
def indexChanged(self, index):
|
||||||
|
# current index has changed; need to remember new 'chosen text'
|
||||||
|
if self._ignoreIndexChange:
|
||||||
|
return
|
||||||
|
self._chosenText = asUnicode(self.currentText())
|
||||||
|
|
||||||
|
def setCurrentIndex(self, index):
|
||||||
|
QtGui.QComboBox.setCurrentIndex(self, index)
|
||||||
|
|
||||||
|
def itemsChanged(self):
|
||||||
|
# try to set the value to the last one selected, if it is available.
|
||||||
|
if self._chosenText is not None:
|
||||||
|
try:
|
||||||
|
self.setText(self._chosenText)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
def insertItem(self, *args):
|
||||||
|
raise NotImplementedError()
|
||||||
|
#QtGui.QComboBox.insertItem(self, *args)
|
||||||
|
#self.itemsChanged()
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
def insertItems(self, *args):
|
||||||
|
raise NotImplementedError()
|
||||||
|
#QtGui.QComboBox.insertItems(self, *args)
|
||||||
|
#self.itemsChanged()
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
def addItem(self, *args, **kwds):
|
||||||
|
# Need to handle two different function signatures for QComboBox.addItem
|
||||||
|
try:
|
||||||
|
if isinstance(args[0], basestring):
|
||||||
|
text = args[0]
|
||||||
|
if len(args) == 2:
|
||||||
|
value = args[1]
|
||||||
|
else:
|
||||||
|
value = kwds.get('value', text)
|
||||||
|
else:
|
||||||
|
text = args[1]
|
||||||
|
if len(args) == 3:
|
||||||
|
value = args[2]
|
||||||
|
else:
|
||||||
|
value = kwds.get('value', text)
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
raise TypeError("First or second argument of addItem must be a string.")
|
||||||
|
|
||||||
|
if text in self._items:
|
||||||
|
raise Exception('ComboBox already has item named "%s".' % text)
|
||||||
|
|
||||||
|
self._items[text] = value
|
||||||
|
QtGui.QComboBox.addItem(self, *args)
|
||||||
|
self.itemsChanged()
|
||||||
|
|
||||||
|
def setItemValue(self, name, value):
|
||||||
|
if name not in self._items:
|
||||||
|
self.addItem(name, value)
|
||||||
|
else:
|
||||||
|
self._items[name] = value
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
@blockIfUnchanged
|
||||||
|
def addItems(self, items):
|
||||||
|
if isinstance(items, list):
|
||||||
|
texts = items
|
||||||
|
items = dict([(x, x) for x in items])
|
||||||
|
elif isinstance(items, dict):
|
||||||
|
texts = list(items.keys())
|
||||||
|
else:
|
||||||
|
raise TypeError("items argument must be list or dict (got %s)." % type(items))
|
||||||
|
|
||||||
|
for t in texts:
|
||||||
|
if t in self._items:
|
||||||
|
raise Exception('ComboBox already has item named "%s".' % t)
|
||||||
|
|
||||||
|
|
||||||
|
for k,v in items.items():
|
||||||
|
self._items[k] = v
|
||||||
|
QtGui.QComboBox.addItems(self, list(texts))
|
||||||
|
|
||||||
|
self.itemsChanged()
|
||||||
|
|
||||||
|
@ignoreIndexChange
|
||||||
|
def clear(self):
|
||||||
|
self._items = OrderedDict()
|
||||||
|
QtGui.QComboBox.clear(self)
|
||||||
|
self.itemsChanged()
|
||||||
|
|
||||||
|
|
44
pyqtgraph/widgets/tests/test_combobox.py
Normal file
44
pyqtgraph/widgets/tests/test_combobox.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import pyqtgraph as pg
|
||||||
|
pg.mkQApp()
|
||||||
|
|
||||||
|
def test_combobox():
|
||||||
|
cb = pg.ComboBox()
|
||||||
|
items = {'a': 1, 'b': 2, 'c': 3}
|
||||||
|
cb.setItems(items)
|
||||||
|
cb.setValue(2)
|
||||||
|
assert str(cb.currentText()) == 'b'
|
||||||
|
assert cb.value() == 2
|
||||||
|
|
||||||
|
# Clear item list; value should be None
|
||||||
|
cb.clear()
|
||||||
|
assert cb.value() == None
|
||||||
|
|
||||||
|
# Reset item list; value should be set automatically
|
||||||
|
cb.setItems(items)
|
||||||
|
assert cb.value() == 2
|
||||||
|
|
||||||
|
# Clear item list; repopulate with same names and new values
|
||||||
|
items = {'a': 4, 'b': 5, 'c': 6}
|
||||||
|
cb.clear()
|
||||||
|
cb.setItems(items)
|
||||||
|
assert cb.value() == 5
|
||||||
|
|
||||||
|
# Set list instead of dict
|
||||||
|
cb.setItems(list(items.keys()))
|
||||||
|
assert str(cb.currentText()) == 'b'
|
||||||
|
|
||||||
|
cb.setValue('c')
|
||||||
|
assert cb.value() == str(cb.currentText())
|
||||||
|
assert cb.value() == 'c'
|
||||||
|
|
||||||
|
cb.setItemValue('c', 7)
|
||||||
|
assert cb.value() == 7
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cb = pg.ComboBox()
|
||||||
|
cb.show()
|
||||||
|
cb.setItems({'': None, 'a': 1, 'b': 2, 'c': 3})
|
||||||
|
def fn(ind):
|
||||||
|
print("New value: %s" % cb.value())
|
||||||
|
cb.currentIndexChanged.connect(fn)
|
Loading…
Reference in New Issue
Block a user