flowchart updates. Added nodes, expanded context menus.

This commit is contained in:
Luke Campagnola 2012-04-23 10:13:21 -04:00
parent 62cdaf0b46
commit 697130789c
5 changed files with 131 additions and 45 deletions

View File

@ -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])

View File

@ -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())

View File

@ -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)

View File

@ -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())

View File

@ -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):