From 2a27687fb2d8900196e40b62889a48fcf24ecc89 Mon Sep 17 00:00:00 2001 From: Luke Campagnola <> Date: Thu, 7 Mar 2013 15:29:56 -0500 Subject: [PATCH] merged updates from acq4 --- doc/source/functions.rst | 2 +- pyqtgraph/__init__.py | 44 +++++++++++++++++++++ pyqtgraph/console/Console.py | 4 +- pyqtgraph/flowchart/Terminal.py | 46 ++++++++++++++++------ pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 4 +- pyqtgraph/widgets/ColorMapWidget.py | 10 ++++- pyqtgraph/widgets/DataFilterWidget.py | 10 +++-- 7 files changed, 97 insertions(+), 23 deletions(-) diff --git a/doc/source/functions.rst b/doc/source/functions.rst index 966fd926..556c5be0 100644 --- a/doc/source/functions.rst +++ b/doc/source/functions.rst @@ -97,6 +97,6 @@ Miscellaneous Functions .. autofunction:: pyqtgraph.systemInfo - +.. autofunction:: pyqtgraph.exit diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py index d3aefa83..71880fbd 100644 --- a/pyqtgraph/__init__.py +++ b/pyqtgraph/__init__.py @@ -54,6 +54,7 @@ CONFIG_OPTIONS = { 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets 'useWeave': True, ## Use weave to speed up some operations, if it is available 'weaveDebug': False, ## Print full error message if weave compile fails + 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide } @@ -190,9 +191,20 @@ from .SignalProxy import * from .colormap import * from .ptime import time +############################################################## +## PyQt and PySide both are prone to crashing on exit. +## There are two general approaches to dealing with this: +## 1. Install atexit handlers that assist in tearing down to avoid crashes. +## This helps, but is never perfect. +## 2. Terminate the process before python starts tearing down +## This is potentially dangerous +## Attempts to work around exit crashes: import atexit def cleanup(): + if not getConfigOption('exitCleanup'): + return + ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. ## Workaround for Qt exit crash: @@ -212,6 +224,38 @@ def cleanup(): atexit.register(cleanup) +## Optional function for exiting immediately (with some manual teardown) +def exit(): + """ + Causes python to exit without garbage-collecting any objects, and thus avoids + calling object destructor methods. This is a sledgehammer workaround for + a variety of bugs in PyQt and Pyside that cause crashes on exit. + + This function does the following in an attempt to 'safely' terminate + the process: + + * Invoke atexit callbacks + * Close all open file handles + * os._exit() + + Note: there is some potential for causing damage with this function if you + are using objects that _require_ their destructors to be called (for example, + to properly terminate log files, disconnect from devices, etc). Situations + like this are probably quite rare, but use at your own risk. + """ + + ## first disable our own cleanup function; won't be needing it. + setConfigOptions(exitCleanup=False) + + ## invoke atexit callbacks + atexit._run_exitfuncs() + + ## close file handles + os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. + + os._exit(os.EX_OK) + + ## Convenience functions for command-line use diff --git a/pyqtgraph/console/Console.py b/pyqtgraph/console/Console.py index 6fbe44a7..982c2424 100644 --- a/pyqtgraph/console/Console.py +++ b/pyqtgraph/console/Console.py @@ -169,7 +169,7 @@ class ConsoleWidget(QtGui.QWidget): def execMulti(self, nextLine): - self.stdout.write(nextLine+"\n") + #self.stdout.write(nextLine+"\n") if nextLine.strip() != '': self.multiline += "\n" + nextLine return @@ -372,4 +372,4 @@ class ConsoleWidget(QtGui.QWidget): return False return True - \ No newline at end of file + diff --git a/pyqtgraph/flowchart/Terminal.py b/pyqtgraph/flowchart/Terminal.py index 623d1a28..3066223d 100644 --- a/pyqtgraph/flowchart/Terminal.py +++ b/pyqtgraph/flowchart/Terminal.py @@ -521,6 +521,8 @@ class ConnectionItem(GraphicsObject): self.target = target self.length = 0 self.hovered = False + self.path = None + self.shapePath = None #self.line = QtGui.QGraphicsLineItem(self) self.source.getViewBox().addItem(self) self.updateLine() @@ -544,13 +546,18 @@ class ConnectionItem(GraphicsObject): else: return self.prepareGeometryChange() - self.resetTransform() - ang = (stop-start).angle(Point(0, 1)) - if ang is None: - ang = 0 - self.rotate(ang) - self.setPos(start) - self.length = (start-stop).length() + + self.path = QtGui.QPainterPath() + self.path.moveTo(start) + self.path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) + self.shapePath = None + #self.resetTransform() + #ang = (stop-start).angle(Point(0, 1)) + #if ang is None: + #ang = 0 + #self.rotate(ang) + #self.setPos(start) + #self.length = (start-stop).length() self.update() #self.line.setLine(start.x(), start.y(), stop.x(), stop.y()) @@ -582,12 +589,23 @@ class ConnectionItem(GraphicsObject): def boundingRect(self): - #return self.line.boundingRect() - px = self.pixelWidth() - return QtCore.QRectF(-5*px, 0, 10*px, self.length) + return self.shape().boundingRect() + ##return self.line.boundingRect() + #px = self.pixelWidth() + #return QtCore.QRectF(-5*px, 0, 10*px, self.length) + def viewRangeChanged(self): + self.shapePath = None + self.prepareGeometryChange() - #def shape(self): - #return self.line.shape() + def shape(self): + if self.shapePath is None: + if self.path is None: + return QtGui.QPainterPath() + stroker = QtGui.QPainterPathStroker() + px = self.pixelWidth() + stroker.setWidth(px*8) + self.shapePath = stroker.createStroke(self.path) + return self.shapePath def paint(self, p, *args): if self.isSelected(): @@ -598,4 +616,6 @@ class ConnectionItem(GraphicsObject): else: p.setPen(fn.mkPen(100, 100, 250, width=1)) - p.drawLine(0, 0, 0, self.length) + #p.drawLine(0, 0, 0, self.length) + + p.drawPath(self.path) diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index b562132c..21d74efd 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -1046,10 +1046,10 @@ class ViewBox(GraphicsWidget): xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() - if xr is None or xr == (None, None): + if xr is None or (xr[0] is None and xr[1] is None): useX = False xr = (0,0) - if yr is None or yr == (None, None): + if yr is None or (yr[0] is None and yr[1] is None): useY = False yr = (0,0) diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py index 619d639a..c82ecc15 100644 --- a/pyqtgraph/widgets/ColorMapWidget.py +++ b/pyqtgraph/widgets/ColorMapWidget.py @@ -169,6 +169,13 @@ class EnumColorMapItem(ptree.types.GroupParameter): self.fieldName = name vals = opts.get('values', []) childs = [{'name': v, 'type': 'color'} for v in vals] + + childs = [] + for v in vals: + ch = ptree.Parameter.create(name=str(v), type='color') + ch.maskValue = v + childs.append(ch) + ptree.types.GroupParameter.__init__(self, name=name, autoIncrementName=True, removable=True, renamable=True, children=[ @@ -191,8 +198,7 @@ class EnumColorMapItem(ptree.types.GroupParameter): colors[:] = default for v in self.param('Values'): - n = v.name() - mask = data == n + mask = data == v.maskValue c = np.array(fn.colorTuple(v.value())) / 255. colors[mask] = c #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) diff --git a/pyqtgraph/widgets/DataFilterWidget.py b/pyqtgraph/widgets/DataFilterWidget.py index a2e1a7b8..65796a15 100644 --- a/pyqtgraph/widgets/DataFilterWidget.py +++ b/pyqtgraph/widgets/DataFilterWidget.py @@ -92,14 +92,18 @@ class RangeFilterItem(ptree.types.SimpleParameter): def generateMask(self, data): vals = data[self.fieldName] - return (vals >= mn) & (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections + return (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections class EnumFilterItem(ptree.types.SimpleParameter): def __init__(self, name, opts): self.fieldName = name vals = opts.get('values', []) - childs = [{'name': v, 'type': 'bool', 'value': True} for v in vals] + childs = [] + for v in vals: + ch = ptree.Parameter.create(name=str(v), type='bool', value=True) + ch.maskValue = v + childs.append(ch) ptree.types.SimpleParameter.__init__(self, name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, children=childs) @@ -110,6 +114,6 @@ class EnumFilterItem(ptree.types.SimpleParameter): for c in self: if c.value() is True: continue - key = c.name() + key = c.maskValue mask &= vals != key return mask