flowchart updates. Added nodes, expanded context menus.
This commit is contained in:
parent
62cdaf0b46
commit
697130789c
@ -84,14 +84,18 @@ class Flowchart(Node):
|
||||
|
||||
self.widget()
|
||||
|
||||
self.inputNode = Node('Input', allowRemove=False)
|
||||
self.outputNode = Node('Output', allowRemove=False)
|
||||
self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True)
|
||||
self.outputNode = Node('Output', allowRemove=False, allowAddInput=True)
|
||||
self.addNode(self.inputNode, 'Input', [-150, 0])
|
||||
self.addNode(self.outputNode, 'Output', [300, 0])
|
||||
|
||||
self.outputNode.sigOutputChanged.connect(self.outputChanged)
|
||||
self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed)
|
||||
self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed)
|
||||
self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved)
|
||||
self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved)
|
||||
self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
|
||||
self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
|
||||
|
||||
self.viewBox.autoRange(padding = 0.04)
|
||||
|
||||
@ -121,11 +125,20 @@ class Flowchart(Node):
|
||||
if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node
|
||||
opts['io'] = 'out'
|
||||
opts['multi'] = False
|
||||
term2 = self.inputNode.addTerminal(name, **opts)
|
||||
self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded)
|
||||
try:
|
||||
term2 = self.inputNode.addTerminal(name, **opts)
|
||||
finally:
|
||||
self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
|
||||
|
||||
else:
|
||||
opts['io'] = 'in'
|
||||
#opts['multi'] = False
|
||||
term2 = self.outputNode.addTerminal(name, **opts)
|
||||
self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded)
|
||||
try:
|
||||
term2 = self.outputNode.addTerminal(name, **opts)
|
||||
finally:
|
||||
self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded)
|
||||
return term
|
||||
|
||||
def removeTerminal(self, name):
|
||||
@ -138,6 +151,16 @@ class Flowchart(Node):
|
||||
def internalTerminalRenamed(self, term, oldName):
|
||||
self[oldName].rename(term.name())
|
||||
|
||||
def internalTerminalAdded(self, node, term):
|
||||
if term._io == 'in':
|
||||
io = 'out'
|
||||
else:
|
||||
io = 'in'
|
||||
Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable())
|
||||
|
||||
def internalTerminalRemoved(self, node, term):
|
||||
Node.removeTerminal(self, term.name())
|
||||
|
||||
def terminalRenamed(self, term, oldName):
|
||||
newName = term.name()
|
||||
#print "flowchart rename", newName, oldName
|
||||
@ -475,7 +498,7 @@ class Flowchart(Node):
|
||||
self.inputNode.restoreState(state.get('inputNode', {}))
|
||||
self.outputNode.restoreState(state.get('outputNode', {}))
|
||||
|
||||
#self.restoreTerminals(state['terminals'])
|
||||
self.restoreTerminals(state['terminals'])
|
||||
for n1, t1, n2, t2 in state['connects']:
|
||||
try:
|
||||
self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2])
|
||||
|
@ -20,7 +20,9 @@ class Node(QtCore.QObject):
|
||||
sigOutputChanged = QtCore.Signal(object) # self
|
||||
sigClosed = QtCore.Signal(object)
|
||||
sigRenamed = QtCore.Signal(object, object)
|
||||
sigTerminalRenamed = QtCore.Signal(object, object)
|
||||
sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName
|
||||
sigTerminalAdded = QtCore.Signal(object, object) # self, term
|
||||
sigTerminalRemoved = QtCore.Signal(object, object) # self, term
|
||||
|
||||
|
||||
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
|
||||
@ -77,6 +79,8 @@ class Node(QtCore.QObject):
|
||||
if name in self._outputs:
|
||||
del self._outputs[name]
|
||||
self.graphicsItem().updateTerminals()
|
||||
self.sigTerminalRemoved.emit(self, term)
|
||||
|
||||
|
||||
def terminalRenamed(self, term, oldName):
|
||||
"""Called after a terminal has been renamed"""
|
||||
@ -107,6 +111,7 @@ class Node(QtCore.QObject):
|
||||
elif term.isOutput():
|
||||
self._outputs[name] = term
|
||||
self.graphicsItem().updateTerminals()
|
||||
self.sigTerminalAdded.emit(self, term)
|
||||
return term
|
||||
|
||||
|
||||
@ -527,16 +532,22 @@ class NodeGraphicsItem(GraphicsObject):
|
||||
def buildMenu(self):
|
||||
self.menu = QtGui.QMenu()
|
||||
self.menu.setTitle("Node")
|
||||
a = self.menu.addAction("Add input", self.node.addInput)
|
||||
a = self.menu.addAction("Add input", self.addInputFromMenu)
|
||||
if not self.node._allowAddInput:
|
||||
a.setEnabled(False)
|
||||
a = self.menu.addAction("Add output", self.node.addOutput)
|
||||
a = self.menu.addAction("Add output", self.addOutputFromMenu)
|
||||
if not self.node._allowAddOutput:
|
||||
a.setEnabled(False)
|
||||
a = self.menu.addAction("Remove node", self.node.close)
|
||||
if not self.node._allowRemove:
|
||||
a.setEnabled(False)
|
||||
|
||||
def addInputFromMenu(self): ## called when add input is clicked in context menu
|
||||
self.node.addInput(renamable=True, removable=True, multiable=True)
|
||||
|
||||
def addOutputFromMenu(self): ## called when add output is clicked in context menu
|
||||
self.node.addOutput(renamable=True, removable=True, multiable=False)
|
||||
|
||||
#def menuTriggered(self, action):
|
||||
##print "node.menuTriggered called. action:", action
|
||||
#act = str(action.text())
|
||||
|
@ -8,15 +8,23 @@ from pyqtgraph.Point import Point
|
||||
from eq import *
|
||||
|
||||
class Terminal:
|
||||
def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, bypass=None):
|
||||
"""Construct a new terminal. Optiona are:
|
||||
node - the node to which this terminal belongs
|
||||
name - string, the name of the terminal
|
||||
io - 'in' or 'out'
|
||||
optional - bool, whether the node may process without connection to this terminal
|
||||
multi - bool, for inputs: whether this terminal may make multiple connections
|
||||
for outputs: whether this terminal creates a different value for each connection
|
||||
pos - [x, y], the position of the terminal within its node's boundaries
|
||||
def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None):
|
||||
"""
|
||||
Construct a new terminal.
|
||||
|
||||
============== =================================================================================
|
||||
**Arguments:**
|
||||
node the node to which this terminal belongs
|
||||
name string, the name of the terminal
|
||||
io 'in' or 'out'
|
||||
optional bool, whether the node may process without connection to this terminal
|
||||
multi bool, for inputs: whether this terminal may make multiple connections
|
||||
for outputs: whether this terminal creates a different value for each connection
|
||||
pos [x, y], the position of the terminal within its node's boundaries
|
||||
renamable (bool) Whether the terminal can be renamed by the user
|
||||
removable (bool) Whether the terminal can be removed by the user
|
||||
multiable (bool) Whether the user may toggle the *multi* option for this terminal
|
||||
============== =================================================================================
|
||||
"""
|
||||
self._io = io
|
||||
#self._isOutput = opts[0] in ['out', 'io']
|
||||
@ -27,6 +35,8 @@ class Terminal:
|
||||
self._node = weakref.ref(node)
|
||||
self._name = name
|
||||
self._renamable = renamable
|
||||
self._removable = removable
|
||||
self._multiable = multiable
|
||||
self._connections = {}
|
||||
self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem())
|
||||
self._bypass = bypass
|
||||
@ -121,6 +131,10 @@ class Terminal:
|
||||
|
||||
def isMultiValue(self):
|
||||
return self._multi
|
||||
|
||||
def setMultiValue(self, b):
|
||||
"""Set whether this is a multi-value terminal."""
|
||||
self._multi = b
|
||||
|
||||
def isOutput(self):
|
||||
return self._io == 'out'
|
||||
@ -128,6 +142,12 @@ class Terminal:
|
||||
def isRenamable(self):
|
||||
return self._renamable
|
||||
|
||||
def isRemovable(self):
|
||||
return self._removable
|
||||
|
||||
def isMultiable(self):
|
||||
return self._multiable
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@ -278,7 +298,7 @@ class Terminal:
|
||||
item.scene().removeItem(item)
|
||||
|
||||
def saveState(self):
|
||||
return {'io': self._io, 'multi': self._multi, 'optional': self._optional}
|
||||
return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable}
|
||||
|
||||
|
||||
#class TerminalGraphicsItem(QtGui.QGraphicsItem):
|
||||
@ -357,40 +377,46 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
#ev.accept()
|
||||
ev.ignore()
|
||||
ev.ignore() ## necessary to allow click/drag events to process correctly
|
||||
|
||||
def mouseClickEvent(self, ev):
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
ev.accept()
|
||||
self.label.setFocus(QtCore.Qt.MouseFocusReason)
|
||||
if ev.button() == QtCore.Qt.RightButton:
|
||||
if self.raiseContextMenu(ev):
|
||||
ev.accept()
|
||||
elif ev.button() == QtCore.Qt.RightButton:
|
||||
ev.accept()
|
||||
self.raiseContextMenu(ev)
|
||||
|
||||
def raiseContextMenu(self, ev):
|
||||
## only raise menu if this terminal is removable
|
||||
menu = self.getMenu()
|
||||
if menu is None:
|
||||
return False
|
||||
menu = self.scene().addParentContextMenus(self, menu, ev)
|
||||
pos = ev.screenPos()
|
||||
menu.popup(QtCore.QPoint(pos.x(), pos.y()))
|
||||
return True
|
||||
|
||||
def getMenu(self):
|
||||
if self.menu is None:
|
||||
if self.removable():
|
||||
self.menu = QtGui.QMenu()
|
||||
self.menu.setTitle("Terminal")
|
||||
self.menu.addAction("Remove terminal", self.removeSelf)
|
||||
else:
|
||||
return None
|
||||
self.menu = QtGui.QMenu()
|
||||
self.menu.setTitle("Terminal")
|
||||
remAct = QtGui.QAction("Remove terminal", self.menu)
|
||||
remAct.triggered.connect(self.removeSelf)
|
||||
self.menu.addAction(remAct)
|
||||
self.menu.remAct = remAct
|
||||
if not self.term.isRemovable():
|
||||
remAct.setEnabled(False)
|
||||
multiAct = QtGui.QAction("Multi-value", self.menu)
|
||||
multiAct.setCheckable(True)
|
||||
multiAct.setChecked(self.term.isMultiValue())
|
||||
multiAct.triggered.connect(self.toggleMulti)
|
||||
self.menu.addAction(multiAct)
|
||||
self.menu.multiAct = multiAct
|
||||
if self.term.isMultiable():
|
||||
multiAct.setEnabled = False
|
||||
return self.menu
|
||||
|
||||
def removable(self):
|
||||
return (
|
||||
(self.term.isOutput() and self.term.node()._allowAddOutput) or
|
||||
(self.term.isInput() and self.term.node()._allowAddInput))
|
||||
def toggleMulti(self):
|
||||
multi = self.menu.multiAct.isChecked()
|
||||
self.term.setMultiValue(multi)
|
||||
|
||||
## probably never need this
|
||||
#def getContextMenus(self, ev):
|
||||
@ -441,6 +467,7 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
def hoverEvent(self, ev):
|
||||
if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||
ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it.
|
||||
ev.acceptClicks(QtCore.Qt.RightButton)
|
||||
self.box.setBrush(fn.mkBrush('w'))
|
||||
else:
|
||||
self.box.setBrush(self.brush)
|
||||
|
@ -195,31 +195,31 @@ class EvalNode(Node):
|
||||
|
||||
self.ui = QtGui.QWidget()
|
||||
self.layout = QtGui.QGridLayout()
|
||||
self.addInBtn = QtGui.QPushButton('+Input')
|
||||
self.addOutBtn = QtGui.QPushButton('+Output')
|
||||
#self.addInBtn = QtGui.QPushButton('+Input')
|
||||
#self.addOutBtn = QtGui.QPushButton('+Output')
|
||||
self.text = QtGui.QTextEdit()
|
||||
self.text.setTabStopWidth(30)
|
||||
self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal")
|
||||
self.layout.addWidget(self.addInBtn, 0, 0)
|
||||
self.layout.addWidget(self.addOutBtn, 0, 1)
|
||||
#self.layout.addWidget(self.addInBtn, 0, 0)
|
||||
#self.layout.addWidget(self.addOutBtn, 0, 1)
|
||||
self.layout.addWidget(self.text, 1, 0, 1, 2)
|
||||
self.ui.setLayout(self.layout)
|
||||
|
||||
#QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput)
|
||||
self.addInBtn.clicked.connect(self.addInput)
|
||||
#self.addInBtn.clicked.connect(self.addInput)
|
||||
#QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput)
|
||||
self.addOutBtn.clicked.connect(self.addOutput)
|
||||
#self.addOutBtn.clicked.connect(self.addOutput)
|
||||
self.text.focusOutEvent = self.focusOutEvent
|
||||
self.lastText = None
|
||||
|
||||
def ctrlWidget(self):
|
||||
return self.ui
|
||||
|
||||
def addInput(self):
|
||||
Node.addInput(self, 'input', renamable=True)
|
||||
#def addInput(self):
|
||||
#Node.addInput(self, 'input', renamable=True)
|
||||
|
||||
def addOutput(self):
|
||||
Node.addOutput(self, 'output', renamable=True)
|
||||
#def addOutput(self):
|
||||
#Node.addOutput(self, 'output', renamable=True)
|
||||
|
||||
def focusOutEvent(self, ev):
|
||||
text = str(self.text.toPlainText())
|
||||
|
@ -5,6 +5,7 @@ import weakref
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem
|
||||
from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem
|
||||
from pyqtgraph import PlotDataItem
|
||||
|
||||
from common import *
|
||||
import numpy as np
|
||||
@ -117,6 +118,30 @@ class CanvasNode(Node):
|
||||
del self.items[vid]
|
||||
|
||||
|
||||
class PlotCurve(CtrlNode):
|
||||
"""Generates a plot curve from x/y data"""
|
||||
nodeName = 'PlotCurve'
|
||||
uiTemplate = [
|
||||
('color', 'color'),
|
||||
]
|
||||
|
||||
def __init__(self, name):
|
||||
CtrlNode.__init__(self, name, terminals={
|
||||
'x': {'io': 'in'},
|
||||
'y': {'io': 'in'},
|
||||
'plot': {'io': 'out'}
|
||||
})
|
||||
self.item = PlotDataItem()
|
||||
|
||||
def process(self, x, y, display=True):
|
||||
#print "scatterplot process"
|
||||
if not display:
|
||||
return {'plot': None}
|
||||
|
||||
self.item.setData(x, y, pen=self.ctrls['color'].color())
|
||||
return {'plot': self.item}
|
||||
|
||||
|
||||
|
||||
|
||||
class ScatterPlot(CtrlNode):
|
||||
|
Loading…
x
Reference in New Issue
Block a user