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):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5):
QtGui.QGraphicsScene.__init__(self)
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
QtGui.QGraphicsScene.__init__(self, parent)
self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance)
self.exportDirectory = None
......@@ -135,8 +135,13 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mousePressEvent(self, ev):
#print 'scenePress'
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.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))
## set focus on the topmost focusable item under this click
......@@ -145,10 +150,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0:
i.setFocus(QtCore.Qt.MouseFocusReason)
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):
self.sigMouseMoved.emit(ev.scenePos())
......@@ -189,7 +190,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
def mouseReleaseEvent(self, ev):
#print 'sceneRelease'
if self.mouseGrabberItem() is None:
#print "sending click/drag event"
if ev.button() in self.dragButtons:
if self.sendDragEvent(ev, final=True):
#print "sent drag event"
......@@ -231,6 +231,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
prevItems = list(self.hoverItems.keys())
#print "hover prev items:", prevItems
#print "hover test items:", items
for item in items:
if hasattr(item, 'hoverEvent'):
event.currentItem = item
......@@ -248,6 +250,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
event.enter = False
event.exit = True
#print "hover exit items:", prevItems
for item in prevItems:
event.currentItem = item
try:
......@@ -257,9 +260,13 @@ class GraphicsScene(QtGui.QGraphicsScene):
finally:
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.
def sendDragEvent(self, ev, init=False, final=False):
## Send a MouseDragEvent to the current dragItem or to
......@@ -323,7 +330,6 @@ class GraphicsScene(QtGui.QGraphicsScene):
acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
else:
acceptedItem = None
if acceptedItem is not None:
ev.currentItem = acceptedItem
try:
......@@ -345,22 +351,9 @@ class GraphicsScene(QtGui.QGraphicsScene):
if int(item.flags() & item.ItemIsFocusable) > 0:
item.setFocus(QtCore.Qt.MouseFocusReason)
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)
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):
#print 'args:', args
items = QtGui.QGraphicsScene.items(self, *args)
......
......@@ -355,6 +355,9 @@ class HoverEvent(object):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self):
if self.exit:
return "<HoverEvent exit=True>"
if self.currentItem is None:
lp = self._lastScenePos
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
www.pyqtgraph.org
"""
__version__ = '0.9.8'
__version__ = '0.9.10'
### import all the goodies and add some helper functions for easy CLI use
......@@ -270,7 +270,12 @@ from .Qt import isQObjectAlive
## Attempts to work around exit crashes:
import atexit
_cleanupCalled = False
def cleanup():
global _cleanupCalled
if _cleanupCalled:
return
if not getConfigOption('exitCleanup'):
return
......@@ -295,8 +300,22 @@ def cleanup():
s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue
_cleanupCalled = True
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)
def exit():
......
......@@ -29,7 +29,7 @@ def test_CSVExporter():
r = csv.reader(open('test.csv', 'r'))
lines = [line for line in r]
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
for vals in lines:
......
......@@ -823,16 +823,20 @@ class FlowchartWidget(dockarea.DockArea):
self.buildMenu()
def buildMenu(self, pos=None):
def buildSubMenu(node, rootMenu, subMenus, pos=None):
for section, node in node.items():
menu = QtGui.QMenu(section)
rootMenu.addMenu(menu)
if isinstance(node, OrderedDict):
buildSubMenu(node, menu, subMenus, pos=pos)
subMenus.append(menu)
else:
act = rootMenu.addAction(section)
act.nodeType = section
act.pos = pos
self.nodeMenu = QtGui.QMenu()
self.subMenus = []
for section, nodes in self.chart.library.getNodeTree().items():
menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu)
for name in nodes:
act = menu.addAction(name)
act.nodeType = name
act.pos = pos
self.subMenus.append(menu)
self.subMenus = []
buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos)
self.nodeMenu.triggered.connect(self.nodeMenuTriggered)
return self.nodeMenu
......
......@@ -1222,6 +1222,8 @@ def downsample(data, n, axis=0, xvals='subsample'):
data = downsample(data, n[i], axis[i])
return data
if n <= 1:
return data
nPts = int(data.shape[axis] / n)
s = list(data.shape)
s[axis] = nPts
......
......@@ -62,6 +62,11 @@ class AxisItem(GraphicsWidget):
self.textWidth = 30 ## Keeps track of maximum width / height of tick text
self.textHeight = 18
# If the user specifies a width / height, remember that setting
# indefinitely.
self.fixedWidth = None
self.fixedHeight = None
self.labelText = ''
self.labelUnits = ''
self.labelUnitPrefix=''
......@@ -219,9 +224,9 @@ class AxisItem(GraphicsWidget):
#self.drawLabel = show
self.label.setVisible(show)
if self.orientation in ['left', 'right']:
self.setWidth()
self._updateWidth()
else:
self.setHeight()
self._updateHeight()
if self.autoSIPrefix:
self.updateAutoSIPrefix()
......@@ -291,54 +296,80 @@ class AxisItem(GraphicsWidget):
if mx > self.textWidth or mx < self.textWidth-10:
self.textWidth = mx
if self.style['autoExpandTextSpace'] is True:
self.setWidth()
self._updateWidth()
#return True ## size has changed
else:
mx = max(self.textHeight, x)
if mx > self.textHeight or mx < self.textHeight-10:
self.textHeight = mx
if self.style['autoExpandTextSpace'] is True:
self.setHeight()
self._updateHeight()
#return True ## size has changed
def _adjustSize(self):
if self.orientation in ['left', 'right']:
self.setWidth()
self._updateWidth()
else:
self.setHeight()
self._updateHeight()
def setHeight(self, h=None):
"""Set the height of this axis reserved for ticks and tick labels.
The height of the axis label is automatically added."""
if h is None:
if not self.style['showValues']:
h = 0
elif self.style['autoExpandTextSpace'] is True:
h = self.textHeight
The height of the axis label is automatically added.
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']:
h = 0
elif self.style['autoExpandTextSpace'] is True:
h = self.textHeight
else:
h = self.style['tickTextHeight']
h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0
h += max(0, self.style['tickLength'])
if self.label.isVisible():
h += self.label.boundingRect().height() * 0.8
else:
h = self.style['tickTextHeight']
h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0
h += max(0, self.style['tickLength'])
if self.label.isVisible():
h += self.label.boundingRect().height() * 0.8
h = self.fixedHeight
self.setMaximumHeight(h)
self.setMinimumHeight(h)
self.picture = None
def setWidth(self, w=None):
"""Set the width of this axis reserved for ticks and tick labels.
The width of the axis label is automatically added."""
if w is None:
if not self.style['showValues']:
w = 0
elif self.style['autoExpandTextSpace'] is True:
w = self.textWidth
The width of the axis label is automatically added.
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']:
w = 0
elif self.style['autoExpandTextSpace'] is True:
w = self.textWidth
else:
w = self.style['tickTextWidth']
w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0
w += max(0, self.style['tickLength'])
if self.label.isVisible():
w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
else:
w = self.style['tickTextWidth']
w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0
w += max(0, self.style['tickLength'])
if self.label.isVisible():
w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
w = self.fixedWidth
self.setMaximumWidth(w)
self.setMinimumWidth(w)
self.picture = None
......@@ -1009,19 +1040,18 @@ class AxisItem(GraphicsWidget):
profiler('draw text')
def show(self):
GraphicsWidget.show(self)
if self.orientation in ['left', 'right']:
self.setWidth()
self._updateWidth()
else:
self.setHeight()
GraphicsWidget.show(self)
self._updateHeight()
def hide(self):
GraphicsWidget.hide(self)
if self.orientation in ['left', 'right']:
self.setWidth(0)
self._updateWidth()
else:
self.setHeight(0)
GraphicsWidget.hide(self)
self._updateHeight()
def wheelEvent(self, ev):
if self.linkedView() is None:
......
......@@ -160,4 +160,12 @@ class GraphicsLayout(GraphicsWidget):
for i in list(self.items.keys()):
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):
self.setLayout(self.layout)
self.layout.setContentsMargins(1,1,1,1)
self.layout.setSpacing(0)
self.vb = ViewBox()
self.vb = ViewBox(parent=self)
self.vb.setMaximumWidth(152)
self.vb.setMinimumWidth(45)
self.vb.setMouseEnabled(x=False, y=True)
......@@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget):
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
self.region.setZValue(1000)
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.vb, 0, 1)
self.layout.addItem(self.gradient, 0, 2)
......
......@@ -9,6 +9,8 @@ from .GraphicsObject import GraphicsObject
from ..Point import Point
__all__ = ['ImageItem']
class ImageItem(GraphicsObject):
"""
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
......
......@@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget):
self.layout.setVerticalSpacing(0)
if viewBox is None:
viewBox = ViewBox()
viewBox = ViewBox(parent=self)
self.vb = viewBox
self.vb.sigStateChanged.connect(self.viewStateChanged)
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
......@@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget):
axisItems = {}
self.axes = {}
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)
self.axes[k] = {'item': axis, 'pos': pos}
self.layout.addItem(axis, *pos)
axis.setZValue(-1000)
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
self.titleLabel = LabelItem('', size='11pt')
self.titleLabel = LabelItem('', size='11pt', parent=self)
self.layout.addItem(self.titleLabel, 0, 1)
self.setTitle(None) ## hide
......
......@@ -241,8 +241,8 @@ class ScatterPlotItem(GraphicsObject):
'useCache': True, ## If useCache is False, symbols are re-drawn on every paint.
'antialias': getConfigOption('antialias'),
'name': None,
}
}
self.setPen(fn.mkPen(getConfigOption('foreground')), update=False)
self.setBrush(fn.mkBrush(100,100,150), update=False)
self.setSymbol('o', update=False)
......@@ -351,16 +351,12 @@ class ScatterPlotItem(GraphicsObject):
newData = self.data[len(oldData):]
newData['size'] = -1 ## indicates to use default size
if 'spots' in kargs:
spots = kargs['spots']
for i in range(len(spots)):
spot = spots[i]
for k in spot:
#if k == 'pen':
#newData[k] = fn.mkPen(spot[k])
#elif k == 'brush':
#newData[k] = fn.mkBrush(spot[k])
if k == 'pos':
pos = spot[k]
if isinstance(pos, QtCore.QPointF):
......@@ -369,10 +365,12 @@ class ScatterPlotItem(GraphicsObject):
x,y = pos[0], pos[1]
newData[i]['x'] = x
newData[i]['y'] = y
elif k in ['x', 'y', 'size', 'symbol', 'pen', 'brush', 'data']:
elif k == 'pen':
newData[i][k] = fn.mkPen(spot[k])
elif k == 'brush':
newData[i][k] = fn.mkBrush(spot[k])
elif k in ['x', 'y', 'size', 'symbol', 'brush', 'data']:
newData[i][k] = spot[k]
#elif k == 'data':
#self.pointData[i] = spot[k]
else:
raise Exception("Unknown spot parameter: %s" % k)
elif 'y' in kargs:
......@@ -389,10 +387,10 @@ class ScatterPlotItem(GraphicsObject):
if k in kargs:
setMethod = getattr(self, 'set' + k[0].upper() + k[1:])
setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None))
if 'data' in kargs:
self.setPointData(kargs['data'], dataSet=newData)
self.prepareGeometryChange()
self.informViewBoundsChanged()
self.bounds = [None, None]
......@@ -428,10 +426,10 @@ class ScatterPlotItem(GraphicsObject):
all spots which do not have a pen explicitly set."""
update = kargs.pop('update', True)
dataSet = kargs.pop('dataSet', self.data)
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
pens = args[0]
if kargs['mask'] is not None:
if 'mask' in kargs and kargs['mask'] is not None:
pens = pens[kargs['mask']]
if len(pens) != len(dataSet):
raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet)))
......@@ -453,7 +451,7 @@ class ScatterPlotItem(GraphicsObject):
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
brushes = args[0]
if kargs['mask'] is not None:
if 'mask' in kargs and kargs['mask'] is not None:
brushes = brushes[kargs['mask']]
if len(brushes) != len(dataSet):
raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet)))
......
......@@ -44,6 +44,11 @@ class TextItem(UIGraphicsItem):
self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport
def setText(self, text, color=(200,200,200)):
"""
Set the text and color of this item.
This method sets the plain text of the item; see also setHtml().
"""
color = fn.mkColor(color)
self.textItem.setDefaultTextColor(color)
self.textItem.setPlainText(text)
......@@ -57,18 +62,41 @@ class TextItem(UIGraphicsItem):
#self.translate(0, 20)
def setPlainText(self, *args):
"""
Set the plain text to be rendered by this item.
See QtGui.QGraphicsTextItem.setPlainText().
"""
self.textItem.setPlainText(*args)
self.updateText()
def setHtml(self, *args):
"""
Set the HTML code to be rendered by this item.
See QtGui.QGraphicsTextItem.setHtml().
"""
self.textItem.setHtml(*args)
self.updateText()
def setTextWidth(self, *args):
"""
Set the width of the text.
If the text requires more space than the width limit, then it will be
wrapped into multiple lines.
See QtGui.QGraphicsTextItem.setTextWidth().
"""
self.textItem.setTextWidth(*args)
self.updateText()
def setFont(self, *args):
"""
Set the font for this text.
See QtGui.QGraphicsTextItem.setFont().
"""
self.textItem.setFont(*args)
self.updateText()
......
......@@ -427,11 +427,11 @@ class ViewBox(GraphicsWidget):
self.linkedYChanged()
self.updateAutoRange()
self.updateViewRange()
self._matrixNeedsUpdate = True
self.sigStateChanged.emit(self)
self.background.setRect(self.rect())
self.sigResized.emit(self)
def viewRange(self):
"""Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]"""
return [x[:] for x in self.state['viewRange']] ## return copy
......@@ -909,7 +909,7 @@ class ViewBox(GraphicsWidget):