Merge tag 'pyqtgraph-0.9.10' into core
Conflicts: graphicsItems/ViewBox/ViewBox.py parametertree/SystemSolver.py widgets/SpinBox.py
This commit is contained in:
commit
2e37d9b12c
|
@ -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
|
||||
|
|
2099
PIL_Fix/Image.py-1.6
2099
PIL_Fix/Image.py-1.6
File diff suppressed because it is too large
Load Diff
2129
PIL_Fix/Image.py-1.7
2129
PIL_Fix/Image.py-1.7
File diff suppressed because it is too large
Load Diff
|
@ -1,11 +0,0 @@
|
|||
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.
|
21
__init__.py
21
__init__.py
|
@ -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):
|
||||
|
||||
if self.orientation in ['left', 'right']:
|
||||
self.setWidth()
|
||||
else:
|
||||
self.setHeight()
|
||||
GraphicsWidget.show(self)
|
||||
if self.orientation in ['left', 'right']:
|
||||
self._updateWidth()
|
||||
else:
|
||||
self._updateHeight()
|
||||
|
||||
def hide(self):
|
||||
if self.orientation in ['left', 'right']:
|
||||
self.setWidth(0)
|
||||
else:
|
||||
self.setHeight(0)
|
||||
GraphicsWidget.hide(self)
|
||||
if self.orientation in ['left', 'right']:
|
||||
self._updateWidth()
|
||||
else:
|
||||
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)
|
||||
|
|
@ -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):
|
|||
if k in args:
|
||||
if not np.all(np.isfinite(args[k])):
|
||||
r = args.pop(k)
|
||||
print "Warning: %s is invalid: %s" % (k, str(r))
|
||||
#print("Warning: %s is invalid: %s" % (k, str(r))
|
||||
|
||||
self.setRange(**args)
|
||||
finally:
|
||||
|
@ -1135,6 +1135,8 @@ class ViewBox(GraphicsWidget):
|
|||
Return the transform that maps from child(item in the childGroup) coordinates to local coordinates.
|
||||
(This maps from inside the viewbox to outside)
|
||||
"""
|
||||
if self._matrixNeedsUpdate:
|
||||
self.updateMatrix()
|
||||
m = self.childGroup.transform()
|
||||
#m1 = QtGui.QTransform()
|
||||
#m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y())
|
||||
|
@ -1694,6 +1696,8 @@ class ViewBox(GraphicsWidget):
|
|||
def forgetView(vid, name):
|
||||
if ViewBox is None: ## can happen as python is shutting down
|
||||
return
|
||||
if QtGui.QApplication.instance() is None:
|
||||
return
|
||||
## Called with ID and name of view (the view itself is no longer available)
|
||||
for v in list(ViewBox.AllViews.keys()):
|
||||
if id(v) == vid:
|
||||
|
@ -1716,6 +1720,8 @@ class ViewBox(GraphicsWidget):
|
|||
pass
|
||||
except TypeError: ## view has already been deleted (?)
|
||||
pass
|
||||
except AttributeError: # PySide has deleted signal
|
||||
pass
|
||||
|
||||
def locate(self, item, timeout=3.0, children=False):
|
||||
"""
|
||||
|
|
|
@ -20,11 +20,11 @@ def test_ViewBox():
|
|||
win.show()
|
||||
vb = win.addViewBox()
|
||||
|
||||
# set range before viewbox is shown
|
||||
vb.setRange(xRange=[0, 10], yRange=[0, 10], padding=0)
|
||||
|
||||
# required to make mapFromView work properly.
|
||||
qtest.qWaitForWindowShown(win)
|
||||
vb.update()
|
||||
|
||||
g = pg.GridItem()
|
||||
vb.addItem(g)
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
app = pg.mkQApp()
|
||||
plot = pg.plot()
|
||||
app.processEvents()
|
||||
|
||||
# set view range equal to its bounding rect.
|
||||
# This causes plots to look the same regardless of pxMode.
|
||||
plot.setRange(rect=plot.boundingRect())
|
||||
|
||||
|
||||
def test_modes():
|
||||
for i, pxMode in enumerate([True, False]):
|
||||
for j, useCache in enumerate([True, False]):
|
||||
s = pg.ScatterPlotItem()
|
||||
s.opts['useCache'] = useCache
|
||||
plot.addItem(s)
|
||||
s.setData(x=np.array([10,40,20,30])+i*100, y=np.array([40,60,10,30])+j*100, pxMode=pxMode)
|
||||
s.addPoints(x=np.array([60, 70])+i*100, y=np.array([60, 70])+j*100, size=[20, 30])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_modes()
|
|
@ -1,95 +0,0 @@
|
|||
"""
|
||||
ViewBox test cases:
|
||||
|
||||
* call setRange then resize; requested range must be fully visible
|
||||
* lockAspect works correctly for arbitrary aspect ratio
|
||||
* autoRange works correctly with aspect locked
|
||||
* call setRange with aspect locked, then resize
|
||||
* AutoRange with all the bells and whistles
|
||||
* item moves / changes transformation / changes bounds
|
||||
* pan only
|
||||
* fractional range
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import pyqtgraph as pg
|
||||
app = pg.mkQApp()
|
||||
|
||||
imgData = pg.np.zeros((10, 10))
|
||||
imgData[0] = 3
|
||||
imgData[-1] = 3
|
||||
imgData[:,0] = 3
|
||||
imgData[:,-1] = 3
|
||||
|
||||
def testLinkWithAspectLock():
|
||||
global win, vb
|
||||
win = pg.GraphicsWindow()
|
||||
vb = win.addViewBox(name="image view")
|
||||
vb.setAspectLocked()
|
||||
vb.enableAutoRange(x=False, y=False)
|
||||
p1 = win.addPlot(name="plot 1")
|
||||
p2 = win.addPlot(name="plot 2", row=1, col=0)
|
||||
win.ci.layout.setRowFixedHeight(1, 150)
|
||||
win.ci.layout.setColumnFixedWidth(1, 150)
|
||||
|
||||
def viewsMatch():
|
||||
r0 = pg.np.array(vb.viewRange())
|
||||
r1 = pg.np.array(p1.vb.viewRange()[1])
|
||||
r2 = pg.np.array(p2.vb.viewRange()[1])
|
||||
match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all()
|
||||
return match
|
||||
|
||||
p1.setYLink(vb)
|
||||
p2.setXLink(vb)
|
||||
print "link views match:", viewsMatch()
|
||||
win.show()
|
||||
print "show views match:", viewsMatch()
|
||||
img = pg.ImageItem(imgData)
|
||||
vb.addItem(img)
|
||||
vb.autoRange()
|
||||
p1.plot(x=imgData.sum(axis=0), y=range(10))
|
||||
p2.plot(x=range(10), y=imgData.sum(axis=1))
|
||||
print "add items views match:", viewsMatch()
|
||||
#p1.setAspectLocked()
|
||||
#grid = pg.GridItem()
|
||||
#vb.addItem(grid)
|
||||
pg.QtGui.QApplication.processEvents()
|
||||
pg.QtGui.QApplication.processEvents()
|
||||
#win.resize(801, 600)
|
||||
|
||||
def testAspectLock():
|
||||
global win, vb
|
||||
win = pg.GraphicsWindow()
|
||||
vb = win.addViewBox(name="image view")
|
||||
vb.setAspectLocked()
|
||||
img = pg.ImageItem(imgData)
|
||||
vb.addItem(img)
|
||||
|
||||
|
||||
#app.processEvents()
|
||||
#print "init views match:", viewsMatch()
|
||||
#p2.setYRange(-300, 300)
|
||||
#print "setRange views match:", viewsMatch()
|
||||
#app.processEvents()
|
||||
#print "setRange views match (after update):", viewsMatch()
|
||||
|
||||
#print "--lock aspect--"
|
||||
#p1.setAspectLocked(True)
|
||||
#print "lockAspect views match:", viewsMatch()
|
||||
#p2.setYRange(-200, 200)
|
||||
#print "setRange views match:", viewsMatch()
|
||||
#app.processEvents()
|
||||
#print "setRange views match (after update):", viewsMatch()
|
||||
|
||||
#win.resize(100, 600)
|
||||
#app.processEvents()
|
||||
#vb.setRange(xRange=[-10, 10], padding=0)
|
||||
#app.processEvents()
|
||||
#win.resize(600, 100)
|
||||
#app.processEvents()
|
||||
#print vb.viewRange()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
testLinkWithAspectLock()
|
|
@ -0,0 +1,86 @@
|
|||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
app = pg.mkQApp()
|
||||
plot = pg.plot()
|
||||
app.processEvents()
|
||||
|
||||
# set view range equal to its bounding rect.
|
||||
# This causes plots to look the same regardless of pxMode.
|
||||
plot.setRange(rect=plot.boundingRect())
|
||||
|
||||
|
||||
def test_scatterplotitem():
|
||||
for i, pxMode in enumerate([True, False]):
|
||||
for j, useCache in enumerate([True, False]):
|
||||
s = pg.ScatterPlotItem()
|
||||
s.opts['useCache'] = useCache
|
||||
plot.addItem(s)
|
||||
s.setData(x=np.array([10,40,20,30])+i*100, y=np.array([40,60,10,30])+j*100, pxMode=pxMode)
|
||||
s.addPoints(x=np.array([60, 70])+i*100, y=np.array([60, 70])+j*100, size=[20, 30])
|
||||
|
||||
# Test uniform spot updates
|
||||
s.setSize(10)
|
||||
s.setBrush('r')
|
||||
s.setPen('g')
|
||||
s.setSymbol('+')
|
||||
app.processEvents()
|
||||
|
||||
# Test list spot updates
|
||||
s.setSize([10] * 6)
|
||||
s.setBrush([pg.mkBrush('r')] * 6)
|
||||
s.setPen([pg.mkPen('g')] * 6)
|
||||
s.setSymbol(['+'] * 6)
|
||||
s.setPointData([s] * 6)
|
||||
app.processEvents()
|
||||
|
||||
# Test array spot updates
|
||||
s.setSize(np.array([10] * 6))
|
||||
s.setBrush(np.array([pg.mkBrush('r')] * 6))
|
||||
s.setPen(np.array([pg.mkPen('g')] * 6))
|
||||
s.setSymbol(np.array(['+'] * 6))
|
||||
s.setPointData(np.array([s] * 6))
|
||||
app.processEvents()
|
||||
|
||||
# Test per-spot updates
|
||||
spot = s.points()[0]
|
||||
spot.setSize(20)
|
||||
spot.setBrush('b')
|
||||
spot.setPen('g')
|
||||
spot.setSymbol('o')
|
||||
spot.setData(None)
|
||||
app.processEvents()
|
||||
|
||||
plot.clear()
|
||||
|
||||
|
||||
def test_init_spots():
|
||||
spots = [
|
||||
{'x': 0, 'y': 1},
|
||||
{'pos': (1, 2), 'pen': None, 'brush': None, 'data': 'zzz'},
|
||||
]
|
||||
s = pg.ScatterPlotItem(spots=spots)
|
||||
|
||||
# Check we can display without errors
|
||||
plot.addItem(s)
|
||||
app.processEvents()
|
||||
plot.clear()
|
||||
|
||||
# check data is correct
|
||||
spots = s.points()
|
||||
|
||||
defPen = pg.mkPen(pg.getConfigOption('foreground'))
|
||||
|
||||
assert spots[0].pos().x() == 0
|
||||
assert spots[0].pos().y() == 1
|
||||
assert spots[0].pen() == defPen
|
||||
assert spots[0].data() is None
|
||||
|
||||
assert spots[1].pos().x() == 1
|
||||
assert spots[1].pos().y() == 2
|
||||
assert spots[1].pen() == pg.mkPen(None)
|
||||
assert spots[1].brush() == pg.mkBrush(None)
|
||||
assert spots[1].data() == 'zzz'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_scatterplotitem()
|
|
@ -28,8 +28,13 @@ GLOptions = {
|
|||
|
||||
|
||||
class GLGraphicsItem(QtCore.QObject):
|
||||
_nextId = 0
|
||||
|
||||
def __init__(self, parentItem=None):
|
||||
QtCore.QObject.__init__(self)
|
||||
self._id = GLGraphicsItem._nextId
|
||||
GLGraphicsItem._nextId += 1
|
||||
|
||||
self.__parent = None
|
||||
self.__view = None
|
||||
self.__children = set()
|
||||
|
|
|
@ -7,6 +7,8 @@ from .. import functions as fn
|
|||
|
||||
##Vector = QtGui.QVector3D
|
||||
|
||||
ShareWidget = None
|
||||
|
||||
class GLViewWidget(QtOpenGL.QGLWidget):
|
||||
"""
|
||||
Basic widget for displaying 3D data
|
||||
|
@ -16,14 +18,14 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||
|
||||
"""
|
||||
|
||||
ShareWidget = None
|
||||
|
||||
def __init__(self, parent=None):
|
||||
if GLViewWidget.ShareWidget is None:
|
||||
global ShareWidget
|
||||
|
||||
if ShareWidget is None:
|
||||
## create a dummy widget to allow sharing objects (textures, shaders, etc) between views
|
||||
GLViewWidget.ShareWidget = QtOpenGL.QGLWidget()
|
||||
ShareWidget = QtOpenGL.QGLWidget()
|
||||
|
||||
QtOpenGL.QGLWidget.__init__(self, parent, GLViewWidget.ShareWidget)
|
||||
QtOpenGL.QGLWidget.__init__(self, parent, ShareWidget)
|
||||
|
||||
self.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
|
||||
|
@ -157,7 +159,6 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||
|
||||
items = [(h.near, h.names[0]) for h in hits]
|
||||
items.sort(key=lambda i: i[0])
|
||||
|
||||
return [self._itemNames[i[1]] for i in items]
|
||||
|
||||
def paintGL(self, region=None, viewport=None, useItemNames=False):
|
||||
|
@ -191,8 +192,8 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
|||
try:
|
||||
glPushAttrib(GL_ALL_ATTRIB_BITS)
|
||||
if useItemNames:
|
||||
glLoadName(id(i))
|
||||
self._itemNames[id(i)] = i
|
||||
glLoadName(i._id)
|
||||
self._itemNames[i._id] = i
|
||||
i.paint()
|
||||
except:
|
||||
from .. import debug
|
||||
|
|
|
@ -66,7 +66,8 @@ class GLScatterPlotItem(GLGraphicsItem):
|
|||
#print pData.shape, pData.min(), pData.max()
|
||||
pData = pData.astype(np.ubyte)
|
||||
|
||||
self.pointTexture = glGenTextures(1)
|
||||
if getattr(self, "pointTexture", None) is None:
|
||||
self.pointTexture = glGenTextures(1)
|
||||
glActiveTexture(GL_TEXTURE0)
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glBindTexture(GL_TEXTURE_2D, self.pointTexture)
|
||||
|
|
|
@ -377,5 +377,5 @@ if __name__ == '__main__':
|
|||
camera.flash = 0
|
||||
|
||||
camera.solve()
|
||||
print camera.saveState()
|
||||
|
||||
print(camera.saveState())
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import os, sys, subprocess, tempfile
|
||||
import pyqtgraph as pg
|
||||
|
||||
|
||||
code = """
|
||||
import sys
|
||||
sys.path.insert(0, '{path}')
|
||||
import pyqtgraph as pg
|
||||
app = pg.mkQApp()
|
||||
w = pg.{classname}({args})
|
||||
"""
|
||||
|
||||
|
||||
def test_exit_crash():
|
||||
# For each Widget subclass, run a simple python script that creates an
|
||||
# instance and then shuts down. The intent is to check for segmentation
|
||||
# faults when each script exits.
|
||||
tmp = tempfile.mktemp(".py")
|
||||
path = os.path.dirname(pg.__file__)
|
||||
|
||||
initArgs = {
|
||||
'CheckTable': "[]",
|
||||
'ProgressDialog': '"msg"',
|
||||
'VerticalLabel': '"msg"',
|
||||
}
|
||||
|
||||
for name in dir(pg):
|
||||
obj = getattr(pg, name)
|
||||
if not isinstance(obj, type) or not issubclass(obj, pg.QtGui.QWidget):
|
||||
continue
|
||||
|
||||
print name
|
||||
argstr = initArgs.get(name, "")
|
||||
open(tmp, 'w').write(code.format(path=path, classname=name, args=argstr))
|
||||
proc = subprocess.Popen([sys.executable, tmp])
|
||||
assert proc.wait() == 0
|
||||
|
||||
os.remove(tmp)
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Importing this module installs support for 16-bit images in PIL.
|
||||
This works by patching objects in the PIL namespace; no files are
|
||||
modified.
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
if Image.VERSION == '1.1.7':
|
||||
Image._MODE_CONV["I;16"] = ('%su2' % Image._ENDIAN, None)
|
||||
Image._fromarray_typemap[((1, 1), "<u2")] = ("I", "I;16")
|
||||
if Image.VERSION == '1.1.6':
|
||||
Image._MODE_CONV["I;16"] = ('%su2' % Image._ENDIAN, None)
|
||||
## just a copy of fromarray() from Image.py with I;16 added in
|
||||
def fromarray(obj, mode=None):
|
||||
arr = obj.__array_interface__
|
||||
shape = arr['shape']
|
||||
ndim = len(shape)
|
||||
try:
|
||||
strides = arr['strides']
|
||||
except KeyError:
|
||||
strides = None
|
||||
if mode is None:
|
||||
typestr = arr['typestr']
|
||||
if not (typestr[0] == '|' or typestr[0] == Image._ENDIAN or
|
||||
typestr[1:] not in ['u1', 'b1', 'i4', 'f4']):
|
||||
raise TypeError("cannot handle data-type")
|
||||
if typestr[0] == Image._ENDIAN:
|
||||
typestr = typestr[1:3]
|
||||
else:
|
||||
typestr = typestr[:2]
|
||||
if typestr == 'i4':
|
||||
mode = 'I'
|
||||
if typestr == 'u2':
|
||||
mode = 'I;16'
|
||||
elif typestr == 'f4':
|
||||
mode = 'F'
|
||||
elif typestr == 'b1':
|
||||
mode = '1'
|
||||
elif ndim == 2:
|
||||
mode = 'L'
|
||||
elif ndim == 3:
|
||||
mode = 'RGB'
|
||||
elif ndim == 4:
|
||||
mode = 'RGBA'
|
||||
else:
|
||||
raise TypeError("Do not understand data.")
|
||||
ndmax = 4
|
||||
bad_dims=0
|
||||
if mode in ['1','L','I','P','F']:
|
||||
ndmax = 2
|
||||
elif mode == 'RGB':
|
||||
ndmax = 3
|
||||
if ndim > ndmax:
|
||||
raise ValueError("Too many dimensions.")
|
||||
|
||||
size = shape[:2][::-1]
|
||||
if strides is not None:
|
||||
obj = obj.tostring()
|
||||
|
||||
return frombuffer(mode, size, obj, "raw", mode, 0, 1)
|
||||
|
||||
Image.fromarray=fromarray
|
|
@ -71,6 +71,13 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||
|
||||
QtGui.QGraphicsView.__init__(self, parent)
|
||||
|
||||
# This connects a cleanup function to QApplication.aboutToQuit. It is
|
||||
# called from here because we have no good way to react when the
|
||||
# QApplication is created by the user.
|
||||
# See pyqtgraph.__init__.py
|
||||
from .. import _connectCleanup
|
||||
_connectCleanup()
|
||||
|
||||
if useOpenGL is None:
|
||||
useOpenGL = getConfigOption('useOpenGL')
|
||||
|
||||
|
@ -102,7 +109,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||
self.currentItem = None
|
||||
self.clearMouse()
|
||||
self.updateMatrix()
|
||||
self.sceneObj = GraphicsScene()
|
||||
# GraphicsScene must have parent or expect crashes!
|
||||
self.sceneObj = GraphicsScene(parent=self)
|
||||
self.setScene(self.sceneObj)
|
||||
|
||||
## Workaround for PySide crash
|
||||
|
@ -143,7 +151,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||
|
||||
def paintEvent(self, ev):
|
||||
self.scene().prepareForPaint()
|
||||
#print "GV: paint", ev.rect()
|
||||
return QtGui.QGraphicsView.paintEvent(self, ev)
|
||||
|
||||
def render(self, *args, **kwds):
|
||||
|
|
|
@ -239,12 +239,15 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
|||
Select the numerical portion of the text to allow quick editing by the user.
|
||||
"""
|
||||
le = self.lineEdit()
|
||||
text = le.text()
|
||||
try:
|
||||
index = text.index(' ')
|
||||
except ValueError:
|
||||
return
|
||||
le.setSelection(0, index)
|
||||
text = asUnicode(le.text())
|
||||
if self.opts['suffix'] == '':
|
||||
le.setSelection(0, len(text))
|
||||
else:
|
||||
try:
|
||||
index = text.index(' ')
|
||||
except ValueError:
|
||||
return
|
||||
le.setSelection(0, index)
|
||||
|
||||
def value(self):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue