From d081e5495667eb4adc7dd5e6423d8e8278efc37b Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:54:50 -0700 Subject: [PATCH 1/8] EvalNode: add method to set code --- pyqtgraph/flowchart/library/Data.py | 33 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py index 5236de8d..0ad7742b 100644 --- a/pyqtgraph/flowchart/library/Data.py +++ b/pyqtgraph/flowchart/library/Data.py @@ -189,31 +189,36 @@ class EvalNode(Node): self.ui = QtGui.QWidget() self.layout = QtGui.QGridLayout() - #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.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) - #QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), 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 setCode(self, code): + # unindent code; this allows nicer inline code specification when + # calling this method. + ind = [] + lines = code.split('\n') + for line in lines: + stripped = line.lstrip() + if len(stripped) > 0: + ind.append(len(line) - len(stripped)) + if len(ind) > 0: + ind = min(ind) + code = '\n'.join([line[ind:] for line in lines]) - #def addOutput(self): - #Node.addOutput(self, 'output', renamable=True) + self.text.clear() + self.text.insertPlainText(code) + + def code(self): + return self.text.toPlainText() def focusOutEvent(self, ev): text = str(self.text.toPlainText()) @@ -247,10 +252,10 @@ class EvalNode(Node): def restoreState(self, state): Node.restoreState(self, state) - self.text.clear() - self.text.insertPlainText(state['text']) + self.setCode(state['text']) self.restoreTerminals(state['terminals']) self.update() + class ColumnJoinNode(Node): """Concatenates record arrays and/or adds new columns""" From 868d9ebf2995007b5ef9558493bec1ee3bfdf055 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:55:17 -0700 Subject: [PATCH 2/8] Add several new data nodes --- pyqtgraph/flowchart/library/Data.py | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py index 0ad7742b..18f1c948 100644 --- a/pyqtgraph/flowchart/library/Data.py +++ b/pyqtgraph/flowchart/library/Data.py @@ -359,3 +359,117 @@ class ColumnJoinNode(Node): self.update() +class Mean(CtrlNode): + """Calculate the mean of an array across an axis. + """ + nodeName = 'Mean' + uiTemplate = [ + ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = None if s['axis'] == -1 else s['axis'] + return data.mean(axis=ax) + + +class Max(CtrlNode): + """Calculate the maximum of an array across an axis. + """ + nodeName = 'Max' + uiTemplate = [ + ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = None if s['axis'] == -1 else s['axis'] + return data.max(axis=ax) + + +class Min(CtrlNode): + """Calculate the minimum of an array across an axis. + """ + nodeName = 'Min' + uiTemplate = [ + ('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = None if s['axis'] == -1 else s['axis'] + return data.min(axis=ax) + + +class Stdev(CtrlNode): + """Calculate the standard deviation of an array across an axis. + """ + nodeName = 'Stdev' + uiTemplate = [ + ('axis', 'intSpin', {'value': -0, 'min': -1, 'max': 1000000}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = None if s['axis'] == -1 else s['axis'] + return data.std(axis=ax) + + +class Index(CtrlNode): + """Select an index from an array axis. + """ + nodeName = 'Index' + uiTemplate = [ + ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), + ('index', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = s['axis'] + ind = s['index'] + if ax == 0: + # allow support for non-ndarray sequence types + return data[ind] + else: + return data.take(ind, axis=ax) + + +class Slice(CtrlNode): + """Select a slice from an array axis. + """ + nodeName = 'Slice' + uiTemplate = [ + ('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1e6}), + ('start', 'intSpin', {'value': 0, 'min': -1e6, 'max': 1e6}), + ('stop', 'intSpin', {'value': -1, 'min': -1e6, 'max': 1e6}), + ('step', 'intSpin', {'value': 1, 'min': -1e6, 'max': 1e6}), + ] + + def processData(self, data): + s = self.stateGroup.state() + ax = s['axis'] + start = s['start'] + stop = s['stop'] + step = s['step'] + if ax == 0: + # allow support for non-ndarray sequence types + return data[start:stop:step] + else: + sl = [slice(None) for i in range(data.ndim)] + sl[ax] = slice(start, stop, step) + return data[sl] + + +class AsType(CtrlNode): + """Convert an array to a different dtype. + """ + nodeName = 'AsType' + uiTemplate = [ + ('dtype', 'combo', {'values': ['float', 'int', 'float32', 'float64', 'float128', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'], 'index': 0}), + ] + + def processData(self, data): + s = self.stateGroup.state() + return data.astype(s['dtype']) + From 2016dc0df1b2658c9da9d8606e82a4ae9b5dd724 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:56:45 -0700 Subject: [PATCH 3/8] fix nodes spinbox handling --- pyqtgraph/flowchart/library/Filters.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py index 9392b037..ada09dfb 100644 --- a/pyqtgraph/flowchart/library/Filters.py +++ b/pyqtgraph/flowchart/library/Filters.py @@ -38,7 +38,7 @@ class Bessel(CtrlNode): nodeName = 'BesselFilter' uiTemplate = [ ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), ('bidir', 'check', {'checked': True}) ] @@ -57,10 +57,10 @@ class Butterworth(CtrlNode): nodeName = 'ButterworthFilter' uiTemplate = [ ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('bidir', 'check', {'checked': True}) ] @@ -78,14 +78,14 @@ class ButterworthNotch(CtrlNode): """Butterworth notch filter""" nodeName = 'ButterworthNotchFilter' uiTemplate = [ - ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), ('bidir', 'check', {'checked': True}) ] From 19fc846b90955d1f7a9274ba0d10ef7fae1a59c3 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:58:29 -0700 Subject: [PATCH 4/8] gaussian node uses internal gaussianFilter function --- pyqtgraph/flowchart/library/Filters.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py index ada09dfb..9a7fa401 100644 --- a/pyqtgraph/flowchart/library/Filters.py +++ b/pyqtgraph/flowchart/library/Filters.py @@ -160,19 +160,13 @@ class Gaussian(CtrlNode): @metaArrayWrapper def processData(self, data): + sigma = self.ctrls['sigma'].value() try: import scipy.ndimage + return scipy.ndimage.gaussian_filter(data, sigma) except ImportError: - raise Exception("GaussianFilter node requires the package scipy.ndimage.") + return pgfn.gaussianFilter(data, sigma) - if hasattr(data, 'implements') and data.implements('MetaArray'): - info = data.infoCopy() - filt = pgfn.gaussianFilter(data.asarray(), self.ctrls['sigma'].value()) - if 'values' in info[0]: - info[0]['values'] = info[0]['values'][:filt.shape[0]] - return metaarray.MetaArray(filt, info=info) - else: - return pgfn.gaussianFilter(data, self.ctrls['sigma'].value()) class Derivative(CtrlNode): """Returns the pointwise derivative of the input""" From 237b8488371a7b68f224927c8b269ec6bbb2b41e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:59:15 -0700 Subject: [PATCH 5/8] Allow binary operator nodes to select output type --- pyqtgraph/flowchart/library/Operators.py | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py index 579d2cd2..596e8854 100644 --- a/pyqtgraph/flowchart/library/Operators.py +++ b/pyqtgraph/flowchart/library/Operators.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- from ..Node import Node +from .common import CtrlNode + class UniOpNode(Node): """Generic node for performing any operation like Out = In.fn()""" @@ -13,11 +15,22 @@ class UniOpNode(Node): def process(self, **args): return {'Out': getattr(args['In'], self.fn)()} -class BinOpNode(Node): +class BinOpNode(CtrlNode): """Generic node for performing any operation like A.fn(B)""" + + _dtypes = [ + 'float64', 'float32', 'float16', + 'int64', 'int32', 'int16', 'int8', + 'uint64', 'uint32', 'uint16', 'uint8' + ] + + uiTemplate = [ + ('outputType', 'combo', {'values': ['no change', 'input A', 'input B'] + _dtypes , 'index': 0}) + ] + def __init__(self, name, fn): self.fn = fn - Node.__init__(self, name, terminals={ + CtrlNode.__init__(self, name, terminals={ 'A': {'io': 'in'}, 'B': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'A'} @@ -36,6 +49,18 @@ class BinOpNode(Node): out = fn(args['B']) if out is NotImplemented: raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) + + # Coerce dtype if requested + typ = self.stateGroup.state()['outputType'] + if typ == 'no change': + pass + elif typ == 'input A': + out = out.astype(args['A'].dtype) + elif typ == 'input B': + out = out.astype(args['B'].dtype) + else: + out = out.astype(typ) + #print " ", fn, out return {'Out': out} From d65026f73d4d12913defcb77fb149fb21078c2b5 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 08:59:31 -0700 Subject: [PATCH 6/8] add floor division node --- pyqtgraph/flowchart/library/Operators.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py index 596e8854..d1483c16 100644 --- a/pyqtgraph/flowchart/library/Operators.py +++ b/pyqtgraph/flowchart/library/Operators.py @@ -96,4 +96,10 @@ class DivideNode(BinOpNode): # try truediv first, followed by div BinOpNode.__init__(self, name, ('__truediv__', '__div__')) +class FloorDivideNode(BinOpNode): + """Returns A // B. Does not check input types.""" + nodeName = 'FloorDivide' + def __init__(self, name): + BinOpNode.__init__(self, name, '__floordiv__') + From fedecc5808e41a8c7d10e8803e758cbe13f90572 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 09:00:50 -0700 Subject: [PATCH 7/8] minor fixes --- pyqtgraph/flowchart/Flowchart.py | 3 ++- pyqtgraph/flowchart/library/common.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py index b623f5c7..e31f3999 100644 --- a/pyqtgraph/flowchart/Flowchart.py +++ b/pyqtgraph/flowchart/Flowchart.py @@ -189,7 +189,8 @@ class Flowchart(Node): self.viewBox.addItem(item) item.moveBy(*pos) self._nodes[name] = node - self.widget().addNode(node) + if node is not self.inputNode and node is not self.outputNode: + self.widget().addNode(node) node.sigClosed.connect(self.nodeClosed) node.sigRenamed.connect(self.nodeRenamed) node.sigOutputChanged.connect(self.nodeOutputChanged) diff --git a/pyqtgraph/flowchart/library/common.py b/pyqtgraph/flowchart/library/common.py index 425fe86c..8b3376c3 100644 --- a/pyqtgraph/flowchart/library/common.py +++ b/pyqtgraph/flowchart/library/common.py @@ -30,6 +30,11 @@ def generateUi(opts): k, t, o = opt else: raise Exception("Widget specification must be (name, type) or (name, type, {opts})") + + ## clean out these options so they don't get sent to SpinBox + hidden = o.pop('hidden', False) + tip = o.pop('tip', None) + if t == 'intSpin': w = QtGui.QSpinBox() if 'max' in o: @@ -63,11 +68,12 @@ def generateUi(opts): w = ColorButton() else: raise Exception("Unknown widget type '%s'" % str(t)) - if 'tip' in o: - w.setToolTip(o['tip']) + + if tip is not None: + w.setToolTip(tip) w.setObjectName(k) l.addRow(k, w) - if o.get('hidden', False): + if hidden: w.hide() label = l.labelForField(w) label.hide() From 698f37bd10a7bd089727404f0edb0f0f9389eace Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Sep 2017 09:00:59 -0700 Subject: [PATCH 8/8] code cleanup --- pyqtgraph/flowchart/Flowchart.py | 61 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py index e31f3999..cbfd084e 100644 --- a/pyqtgraph/flowchart/Flowchart.py +++ b/pyqtgraph/flowchart/Flowchart.py @@ -166,6 +166,8 @@ class Flowchart(Node): n[oldName].rename(newName) def createNode(self, nodeType, name=None, pos=None): + """Create a new Node and add it to this flowchart. + """ if name is None: n = 0 while True: @@ -179,6 +181,10 @@ class Flowchart(Node): return node def addNode(self, node, name, pos=None): + """Add an existing Node to this flowchart. + + See also: createNode() + """ if pos is None: pos = [0, 0] if type(pos) in [QtCore.QPoint, QtCore.QPointF]: @@ -197,6 +203,8 @@ class Flowchart(Node): self.sigChartChanged.emit(self, 'add', node) def removeNode(self, node): + """Remove a Node from this flowchart. + """ node.close() def nodeClosed(self, node): @@ -234,7 +242,6 @@ class Flowchart(Node): term2 = self.internalTerminal(term2) term1.connectTo(term2) - def process(self, **args): """ Process data through the flowchart, returning the output. @@ -326,7 +333,6 @@ class Flowchart(Node): #print "DEPS:", deps ## determine correct node-processing order - #deps[self] = [] order = fn.toposort(deps) #print "ORDER1:", order @@ -350,7 +356,6 @@ class Flowchart(Node): if lastNode is None or ind > lastInd: lastNode = n lastInd = ind - #tdeps[t] = lastNode if lastInd is not None: dels.append((lastInd+1, t)) dels.sort(key=lambda a: a[0], reverse=True) @@ -405,27 +410,25 @@ class Flowchart(Node): self.inputWasSet = False else: self.sigStateChanged.emit() - - def chartGraphicsItem(self): - """Return the graphicsItem which displays the internals of this flowchart. - (graphicsItem() still returns the external-view item)""" - #return self._chartGraphicsItem + """Return the graphicsItem that displays the internal nodes and + connections of this flowchart. + + Note that the similar method `graphicsItem()` is inherited from Node + and returns the *external* graphical representation of this flowchart.""" return self.viewBox def widget(self): + """Return the control widget for this flowchart. + + This widget provides GUI access to the parameters for each node and a + graphical representation of the flowchart. + """ if self._widget is None: self._widget = FlowchartCtrlWidget(self) self.scene = self._widget.scene() self.viewBox = self._widget.viewBox() - #self._scene = QtGui.QGraphicsScene() - #self._widget.setScene(self._scene) - #self.scene.addItem(self.chartGraphicsItem()) - - #ci = self.chartGraphicsItem() - #self.viewBox.addItem(ci) - #self.viewBox.autoRange() return self._widget def listConnections(self): @@ -438,10 +441,11 @@ class Flowchart(Node): return conn def saveState(self): + """Return a serializable data structure representing the current state of this flowchart. + """ state = Node.saveState(self) state['nodes'] = [] state['connects'] = [] - #state['terminals'] = self.saveTerminals() for name, node in self._nodes.items(): cls = type(node) @@ -461,6 +465,8 @@ class Flowchart(Node): return state def restoreState(self, state, clear=False): + """Restore the state of this flowchart from a previous call to `saveState()`. + """ self.blockSignals(True) try: if clear: @@ -470,7 +476,6 @@ class Flowchart(Node): nodes.sort(key=lambda a: a['pos'][0]) for n in nodes: if n['name'] in self._nodes: - #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) self._nodes[n['name']].restoreState(n['state']) continue try: @@ -478,7 +483,6 @@ class Flowchart(Node): node.restoreState(n['state']) except: printExc("Error creating node %s: (continuing anyway)" % n['name']) - #node.graphicsItem().moveBy(*n['pos']) self.inputNode.restoreState(state.get('inputNode', {})) self.outputNode.restoreState(state.get('outputNode', {})) @@ -491,7 +495,6 @@ class Flowchart(Node): print(self._nodes[n1].terminals) print(self._nodes[n2].terminals) printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) - finally: self.blockSignals(False) @@ -499,48 +502,46 @@ class Flowchart(Node): self.sigChartLoaded.emit() self.outputChanged() self.sigStateChanged.emit() - #self.sigOutputChanged.emit() def loadFile(self, fileName=None, startDir=None): + """Load a flowchart (*.fc) file. + """ if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") - #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.loadFile) return ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. - #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") fileName = unicode(fileName) state = configfile.readConfigFile(fileName) self.restoreState(state, clear=True) self.viewBox.autoRange() - #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) self.sigFileLoaded.emit(fileName) def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): + """Save this flowchart to a .fc file + """ if fileName is None: if startDir is None: startDir = self.filePath if startDir is None: startDir = '.' self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") - #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - #self.fileDialog.setDirectory(startDir) self.fileDialog.show() self.fileDialog.fileSelected.connect(self.saveFile) return - #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") fileName = unicode(fileName) configfile.writeConfigFile(self.saveState(), fileName) self.sigFileSaved.emit(fileName) def clear(self): + """Remove all nodes from this flowchart except the original input/output nodes. + """ for n in list(self._nodes.values()): if n is self.inputNode or n is self.outputNode: continue @@ -553,18 +554,15 @@ class Flowchart(Node): self.inputNode.clearTerminals() self.outputNode.clearTerminals() -#class FlowchartGraphicsItem(QtGui.QGraphicsItem): + class FlowchartGraphicsItem(GraphicsObject): def __init__(self, chart): - #print "FlowchartGraphicsItem.__init__" - #QtGui.QGraphicsItem.__init__(self) GraphicsObject.__init__(self) self.chart = chart ## chart is an instance of Flowchart() self.updateTerminals() def updateTerminals(self): - #print "FlowchartGraphicsItem.updateTerminals" self.terminals = {} bounds = self.boundingRect() inp = self.chart.inputs() @@ -760,6 +758,7 @@ class FlowchartCtrlWidget(QtGui.QWidget): item = self.items[node] self.ui.ctrlList.setCurrentItem(item) + class FlowchartWidget(dockarea.DockArea): """Includes the actual graphical flowchart and debugging interface""" def __init__(self, chart, ctrl):