Commit 2e37d9b1 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Merge tag 'pyqtgraph-0.9.10' into core

Conflicts:
	graphicsItems/ViewBox/ViewBox.py
	parametertree/SystemSolver.py
	widgets/SpinBox.py
parents ca3fbe2f 70cfdb4b
...@@ -84,8 +84,8 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -84,8 +84,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5): def __init__(self, clickRadius=2, moveDistance=5, parent=None):
QtGui.QGraphicsScene.__init__(self) QtGui.QGraphicsScene.__init__(self, parent)
self.setClickRadius(clickRadius) self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance) self.setMoveDistance(moveDistance)
self.exportDirectory = None self.exportDirectory = None
...@@ -135,8 +135,13 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -135,8 +135,13 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
#print 'scenePress' #print 'scenePress'
QtGui.QGraphicsScene.mousePressEvent(self, ev) QtGui.QGraphicsScene.mousePressEvent(self, ev)
#print "mouseGrabberItem: ", self.mouseGrabberItem()
if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events
if self.lastHoverEvent is not None:
# If the mouse has moved since the last hover event, send a new one.
# This can happen if a context menu is open while the mouse is moving.
if ev.scenePos() != self.lastHoverEvent.scenePos():
self.sendHoverEvents(ev)
self.clickEvents.append(MouseClickEvent(ev)) self.clickEvents.append(MouseClickEvent(ev))
## set focus on the topmost focusable item under this click ## set focus on the topmost focusable item under this click
...@@ -145,10 +150,6 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -145,10 +150,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0: if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0:
i.setFocus(QtCore.Qt.MouseFocusReason) i.setFocus(QtCore.Qt.MouseFocusReason)
break break
#else:
#addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem))
#item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem())
#print "click grabbed by:", item
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
self.sigMouseMoved.emit(ev.scenePos()) self.sigMouseMoved.emit(ev.scenePos())
...@@ -189,7 +190,6 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -189,7 +190,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
#print 'sceneRelease' #print 'sceneRelease'
if self.mouseGrabberItem() is None: if self.mouseGrabberItem() is None:
#print "sending click/drag event"
if ev.button() in self.dragButtons: if ev.button() in self.dragButtons:
if self.sendDragEvent(ev, final=True): if self.sendDragEvent(ev, final=True):
#print "sent drag event" #print "sent drag event"
...@@ -231,6 +231,8 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -231,6 +231,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
prevItems = list(self.hoverItems.keys()) prevItems = list(self.hoverItems.keys())
#print "hover prev items:", prevItems
#print "hover test items:", items
for item in items: for item in items:
if hasattr(item, 'hoverEvent'): if hasattr(item, 'hoverEvent'):
event.currentItem = item event.currentItem = item
...@@ -248,6 +250,7 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -248,6 +250,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
event.enter = False event.enter = False
event.exit = True event.exit = True
#print "hover exit items:", prevItems
for item in prevItems: for item in prevItems:
event.currentItem = item event.currentItem = item
try: try:
...@@ -257,10 +260,14 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -257,10 +260,14 @@ class GraphicsScene(QtGui.QGraphicsScene):
finally: finally:
del self.hoverItems[item] del self.hoverItems[item]
if hasattr(ev, 'buttons') and int(ev.buttons()) == 0: # Update last hover event unless:
# - mouse is dragging (move+buttons); in this case we want the dragged
# item to continue receiving events until the drag is over
# - event is not a mouse event (QEvent.Leave sometimes appears here)
if (ev.type() == ev.GraphicsSceneMousePress or
(ev.type() == ev.GraphicsSceneMouseMove and int(ev.buttons()) == 0)):
self.lastHoverEvent = event ## save this so we can ask about accepted events later. self.lastHoverEvent = event ## save this so we can ask about accepted events later.
def sendDragEvent(self, ev, init=False, final=False): def sendDragEvent(self, ev, init=False, final=False):
## Send a MouseDragEvent to the current dragItem or to ## Send a MouseDragEvent to the current dragItem or to
## items near the beginning of the drag ## items near the beginning of the drag
...@@ -323,7 +330,6 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -323,7 +330,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
else: else:
acceptedItem = None acceptedItem = None
if acceptedItem is not None: if acceptedItem is not None:
ev.currentItem = acceptedItem ev.currentItem = acceptedItem
try: try:
...@@ -345,22 +351,9 @@ class GraphicsScene(QtGui.QGraphicsScene): ...@@ -345,22 +351,9 @@ class GraphicsScene(QtGui.QGraphicsScene):
if int(item.flags() & item.ItemIsFocusable) > 0: if int(item.flags() & item.ItemIsFocusable) > 0:
item.setFocus(QtCore.Qt.MouseFocusReason) item.setFocus(QtCore.Qt.MouseFocusReason)
break break
#if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
#print "GraphicsScene emitting sigSceneContextMenu"
#self.sigMouseClicked.emit(ev)
#ev.accept()
self.sigMouseClicked.emit(ev) self.sigMouseClicked.emit(ev)
return ev.isAccepted() return ev.isAccepted()
#def claimEvent(self, item, button, eventType):
#key = (button, eventType)
#if key in self.claimedEvents:
#return False
#self.claimedEvents[key] = item
#print "event", key, "claimed by", item
#return True
def items(self, *args): def items(self, *args):
#print 'args:', args #print 'args:', args
items = QtGui.QGraphicsScene.items(self, *args) items = QtGui.QGraphicsScene.items(self, *args)
......
...@@ -355,6 +355,9 @@ class HoverEvent(object): ...@@ -355,6 +355,9 @@ class HoverEvent(object):
return Point(self.currentItem.mapFromScene(self._lastScenePos)) return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self): def __repr__(self):
if self.exit:
return "<HoverEvent exit=True>"
if self.currentItem is None: if self.currentItem is None:
lp = self._lastScenePos lp = self._lastScenePos
p = self._scenePos p = self._scenePos
......
This diff is collapsed.
This diff is collapsed.
The file Image.py is a drop-in replacement for the same file in PIL 1.1.6.
It adds support for reading 16-bit TIFF files and converting then to numpy arrays.
(I submitted the changes to the PIL folks long ago, but to my knowledge the code
is not being used by them.)
To use, copy this file into
/usr/lib/python2.6/dist-packages/PIL/
or
C:\Python26\lib\site-packages\PIL\
..or wherever your system keeps its python modules.
...@@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python ...@@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
www.pyqtgraph.org www.pyqtgraph.org
""" """
__version__ = '0.9.8' __version__ = '0.9.10'
### import all the goodies and add some helper functions for easy CLI use ### import all the goodies and add some helper functions for easy CLI use
...@@ -270,7 +270,12 @@ from .Qt import isQObjectAlive ...@@ -270,7 +270,12 @@ from .Qt import isQObjectAlive
## Attempts to work around exit crashes: ## Attempts to work around exit crashes:
import atexit import atexit
_cleanupCalled = False
def cleanup(): def cleanup():
global _cleanupCalled
if _cleanupCalled:
return
if not getConfigOption('exitCleanup'): if not getConfigOption('exitCleanup'):
return return
...@@ -295,8 +300,22 @@ def cleanup(): ...@@ -295,8 +300,22 @@ def cleanup():
s.addItem(o) s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue continue
_cleanupCalled = True
atexit.register(cleanup) atexit.register(cleanup)
# Call cleanup when QApplication quits. This is necessary because sometimes
# the QApplication will quit before the atexit callbacks are invoked.
# Note: cannot connect this function until QApplication has been created, so
# instead we have GraphicsView.__init__ call this for us.
_cleanupConnected = False
def _connectCleanup():
global _cleanupConnected
if _cleanupConnected:
return
QtGui.QApplication.instance().aboutToQuit.connect(cleanup)
_cleanupConnected = True
## Optional function for exiting immediately (with some manual teardown) ## Optional function for exiting immediately (with some manual teardown)
def exit(): def exit():
......
...@@ -29,7 +29,7 @@ def test_CSVExporter(): ...@@ -29,7 +29,7 @@ def test_CSVExporter():
r = csv.reader(open('test.csv', 'r')) r = csv.reader(open('test.csv', 'r'))
lines = [line for line in r] lines = [line for line in r]
header = lines.pop(0) header = lines.pop(0)
assert header == ['myPlot_x', 'myPlot_y', 'x', 'y', 'x', 'y'] assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002']
i = 0 i = 0
for vals in lines: for vals in lines:
......
...@@ -823,16 +823,20 @@ class FlowchartWidget(dockarea.DockArea): ...@@ -823,16 +823,20 @@ class FlowchartWidget(dockarea.DockArea):
self.buildMenu() self.buildMenu()
def buildMenu(self, pos=None): def buildMenu(self, pos=None):
self.nodeMenu = QtGui.QMenu() def buildSubMenu(node, rootMenu, subMenus, pos=None):
self.subMenus = [] for section, node in node.items():
for section, nodes in self.chart.library.getNodeTree().items():
menu = QtGui.QMenu(section) menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu) rootMenu.addMenu(menu)
for name in nodes: if isinstance(node, OrderedDict):
act = menu.addAction(name) buildSubMenu(node, menu, subMenus, pos=pos)
act.nodeType = name subMenus.append(menu)
else:
act = rootMenu.addAction(section)
act.nodeType = section
act.pos = pos act.pos = pos
self.subMenus.append(menu) self.nodeMenu = QtGui.QMenu()
self.subMenus = []
buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos)
self.nodeMenu.triggered.connect(self.nodeMenuTriggered) self.nodeMenu.triggered.connect(self.nodeMenuTriggered)
return self.nodeMenu return self.nodeMenu
......
...@@ -1222,6 +1222,8 @@ def downsample(data, n, axis=0, xvals='subsample'): ...@@ -1222,6 +1222,8 @@ def downsample(data, n, axis=0, xvals='subsample'):
data = downsample(data, n[i], axis[i]) data = downsample(data, n[i], axis[i])
return data return data
if n <= 1:
return data
nPts = int(data.shape[axis] / n) nPts = int(data.shape[axis] / n)
s = list(data.shape) s = list(data.shape)
s[axis] = nPts s[axis] = nPts
......
...@@ -62,6 +62,11 @@ class AxisItem(GraphicsWidget): ...@@ -62,6 +62,11 @@ class AxisItem(GraphicsWidget):
self.textWidth = 30 ## Keeps track of maximum width / height of tick text self.textWidth = 30 ## Keeps track of maximum width / height of tick text
self.textHeight = 18 self.textHeight = 18
# If the user specifies a width / height, remember that setting
# indefinitely.
self.fixedWidth = None
self.fixedHeight = None
self.labelText = '' self.labelText = ''
self.labelUnits = '' self.labelUnits = ''
self.labelUnitPrefix='' self.labelUnitPrefix=''
...@@ -219,9 +224,9 @@ class AxisItem(GraphicsWidget): ...@@ -219,9 +224,9 @@ class AxisItem(GraphicsWidget):
#self.drawLabel = show #self.drawLabel = show
self.label.setVisible(show) self.label.setVisible(show)
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
self.setWidth() self._updateWidth()
else: else:
self.setHeight() self._updateHeight()
if self.autoSIPrefix: if self.autoSIPrefix:
self.updateAutoSIPrefix() self.updateAutoSIPrefix()
...@@ -291,26 +296,36 @@ class AxisItem(GraphicsWidget): ...@@ -291,26 +296,36 @@ class AxisItem(GraphicsWidget):
if mx > self.textWidth or mx < self.textWidth-10: if mx > self.textWidth or mx < self.textWidth-10:
self.textWidth = mx self.textWidth = mx
if self.style['autoExpandTextSpace'] is True: if self.style['autoExpandTextSpace'] is True:
self.setWidth() self._updateWidth()
#return True ## size has changed #return True ## size has changed
else: else:
mx = max(self.textHeight, x) mx = max(self.textHeight, x)
if mx > self.textHeight or mx < self.textHeight-10: if mx > self.textHeight or mx < self.textHeight-10:
self.textHeight = mx self.textHeight = mx
if self.style['autoExpandTextSpace'] is True: if self.style['autoExpandTextSpace'] is True:
self.setHeight() self._updateHeight()
#return True ## size has changed #return True ## size has changed
def _adjustSize(self): def _adjustSize(self):
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
self.setWidth() self._updateWidth()
else: else:
self.setHeight() self._updateHeight()
def setHeight(self, h=None): def setHeight(self, h=None):
"""Set the height of this axis reserved for ticks and tick labels. """Set the height of this axis reserved for ticks and tick labels.
The height of the axis label is automatically added.""" The height of the axis label is automatically added.
if h is None:
If *height* is None, then the value will be determined automatically
based on the size of the tick text."""
self.fixedHeight = h
self._updateHeight()
def _updateHeight(self):
if not self.isVisible():
h = 0
else:
if self.fixedHeight is None:
if not self.style['showValues']: if not self.style['showValues']:
h = 0 h = 0
elif self.style['autoExpandTextSpace'] is True: elif self.style['autoExpandTextSpace'] is True:
...@@ -321,14 +336,27 @@ class AxisItem(GraphicsWidget): ...@@ -321,14 +336,27 @@ class AxisItem(GraphicsWidget):
h += max(0, self.style['tickLength']) h += max(0, self.style['tickLength'])
if self.label.isVisible(): if self.label.isVisible():
h += self.label.boundingRect().height() * 0.8 h += self.label.boundingRect().height() * 0.8
else:
h = self.fixedHeight
self.setMaximumHeight(h) self.setMaximumHeight(h)
self.setMinimumHeight(h) self.setMinimumHeight(h)
self.picture = None self.picture = None
def setWidth(self, w=None): def setWidth(self, w=None):
"""Set the width of this axis reserved for ticks and tick labels. """Set the width of this axis reserved for ticks and tick labels.
The width of the axis label is automatically added.""" The width of the axis label is automatically added.
if w is None:
If *width* is None, then the value will be determined automatically
based on the size of the tick text."""
self.fixedWidth = w
self._updateWidth()
def _updateWidth(self):
if not self.isVisible():
w = 0
else:
if self.fixedWidth is None:
if not self.style['showValues']: if not self.style['showValues']:
w = 0 w = 0
elif self.style['autoExpandTextSpace'] is True: elif self.style['autoExpandTextSpace'] is True:
...@@ -339,6 +367,9 @@ class AxisItem(GraphicsWidget): ...@@ -339,6 +367,9 @@ class AxisItem(GraphicsWidget):
w += max(0, self.style['tickLength']) w += max(0, self.style['tickLength'])
if self.label.isVisible(): if self.label.isVisible():
w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
else:
w = self.fixedWidth
self.setMaximumWidth(w) self.setMaximumWidth(w)
self.setMinimumWidth(w) self.setMinimumWidth(w)
self.picture = None self.picture = None
...@@ -1009,19 +1040,18 @@ class AxisItem(GraphicsWidget): ...@@ -1009,19 +1040,18 @@ class AxisItem(GraphicsWidget):
profiler('draw text') profiler('draw text')
def show(self): def show(self):
GraphicsWidget.show(self)
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
self.setWidth() self._updateWidth()
else: else:
self.setHeight() self._updateHeight()
GraphicsWidget.show(self)
def hide(self): def hide(self):
GraphicsWidget.hide(self)
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
self.setWidth(0) self._updateWidth()
else: else:
self.setHeight(0) self._updateHeight()
GraphicsWidget.hide(self)
def wheelEvent(self, ev): def wheelEvent(self, ev):
if self.linkedView() is None: if self.linkedView() is None:
......
...@@ -160,4 +160,12 @@ class GraphicsLayout(GraphicsWidget): ...@@ -160,4 +160,12 @@ class GraphicsLayout(GraphicsWidget):
for i in list(self.items.keys()): for i in list(self.items.keys()):
self.removeItem(i) self.removeItem(i)
def setContentsMargins(self, *args):
# Wrap calls to layout. This should happen automatically, but there
# seems to be a Qt bug:
# http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout
self.layout.setContentsMargins(*args)
def setSpacing(self, *args):
self.layout.setSpacing(*args)
\ No newline at end of file
...@@ -49,7 +49,7 @@ class HistogramLUTItem(GraphicsWidget): ...@@ -49,7 +49,7 @@ class HistogramLUTItem(GraphicsWidget):
self.setLayout(self.layout) self.setLayout(self.layout)
self.layout.setContentsMargins(1,1,1,1) self.layout.setContentsMargins(1,1,1,1)
self.layout.setSpacing(0) self.layout.setSpacing(0)
self.vb = ViewBox() self.vb = ViewBox(parent=self)
self.vb.setMaximumWidth(152) self.vb.setMaximumWidth(152)
self.vb.setMinimumWidth(45) self.vb.setMinimumWidth(45)
self.vb.setMouseEnabled(x=False, y=True) self.vb.setMouseEnabled(x=False, y=True)
...@@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget): ...@@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget):
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal) self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
self.region.setZValue(1000) self.region.setZValue(1000)
self.vb.addItem(self.region) self.vb.addItem(self.region)
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10) self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
self.layout.addItem(self.axis, 0, 0) self.layout.addItem(self.axis, 0, 0)
self.layout.addItem(self.vb, 0, 1) self.layout.addItem(self.vb, 0, 1)
self.layout.addItem(self.gradient, 0, 2) self.layout.addItem(self.gradient, 0, 2)
......
...@@ -9,6 +9,8 @@ from .GraphicsObject import GraphicsObject ...@@ -9,6 +9,8 @@ from .GraphicsObject import GraphicsObject
from ..Point import Point from ..Point import Point
__all__ = ['ImageItem'] __all__ = ['ImageItem']
class ImageItem(GraphicsObject): class ImageItem(GraphicsObject):
""" """
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>` **Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
......
...@@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget): ...@@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget):
self.layout.setVerticalSpacing(0) self.layout.setVerticalSpacing(0)
if viewBox is None: if viewBox is None:
viewBox = ViewBox() viewBox = ViewBox(parent=self)
self.vb = viewBox self.vb = viewBox
self.vb.sigStateChanged.connect(self.viewStateChanged) self.vb.sigStateChanged.connect(self.viewStateChanged)
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
...@@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget): ...@@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget):
axisItems = {} axisItems = {}
self.axes = {} self.axes = {}
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
axis = axisItems.get(k, AxisItem(orientation=k)) axis = axisItems.get(k, AxisItem(orientation=k, parent=self))
axis.linkToView(self.vb) axis.linkToView(self.vb)
self.axes[k] = {'item': axis, 'pos': pos} self.axes[k] = {'item': axis, 'pos': pos}
self.layout.addItem(axis, *pos) self.layout.addItem(axis, *pos)
axis.setZValue(-1000) axis.setZValue(-1000)
axis.setFlag(axis.ItemNegativeZStacksBehindParent) axis.setFlag(axis.ItemNegativeZStacksBehindParent)
self.titleLabel = LabelItem('', size='11pt') self.titleLabel = LabelItem('', size='11pt', parent=self)
self.layout.addItem(self.titleLabel, 0, 1) self.layout.addItem(self.titleLabel, 0, 1)
self.setTitle(None) ## hide self.setTitle(None) ## hide
......
...@@ -357,10 +357,6 @@ class ScatterPlotItem(GraphicsObject): ...@@ -357,10 +357,6 @@ class ScatterPlotItem(GraphicsObject):
for i in range(len(spots)): for i in range(len(spots)):
spot = spots[i]