Imported major changes from acq4 project.

This commit is contained in:
Luke Campagnola 2012-03-01 21:55:32 -05:00
parent 4d846e2aad
commit d4e8e2b883
415 changed files with 45250 additions and 6650 deletions

View File

@ -1,452 +0,0 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtGui, QtCore
import weakref
class TickSlider(QtGui.QGraphicsView):
def __init__(self, parent=None, orientation='bottom', allowAdd=True, **kargs):
QtGui.QGraphicsView.__init__(self, parent)
#self.orientation = orientation
self.allowAdd = allowAdd
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
self.length = 100
self.tickSize = 15
self.orientations = {
'left': (270, 1, -1),
'right': (270, 1, 1),
'top': (0, 1, -1),
'bottom': (0, 1, 1)
}
self.scene = QtGui.QGraphicsScene()
self.setScene(self.scene)
self.ticks = {}
self.maxDim = 20
self.setOrientation(orientation)
self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
self.setBackgroundRole(QtGui.QPalette.NoRole)
self.setMouseTracking(True)
def keyPressEvent(self, ev):
ev.ignore()
def setMaxDim(self, mx=None):
if mx is None:
mx = self.maxDim
else:
self.maxDim = mx
if self.orientation in ['bottom', 'top']:
self.setFixedHeight(mx)
self.setMaximumWidth(16777215)
else:
self.setFixedWidth(mx)
self.setMaximumHeight(16777215)
def setOrientation(self, ort):
self.orientation = ort
self.resetTransform()
self.rotate(self.orientations[ort][0])
self.scale(*self.orientations[ort][1:])
self.setMaxDim()
def addTick(self, x, color=None, movable=True):
if color is None:
color = QtGui.QColor(255,255,255)
tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize)
self.ticks[tick] = x
self.scene.addItem(tick)
return tick
def removeTick(self, tick):
del self.ticks[tick]
self.scene.removeItem(tick)
def tickMoved(self, tick, pos):
#print "tick changed"
## Correct position of tick if it has left bounds.
newX = min(max(0, pos.x()), self.length)
pos.setX(newX)
tick.setPos(pos)
self.ticks[tick] = float(newX) / self.length
def tickClicked(self, tick, ev):
if ev.button() == QtCore.Qt.RightButton:
self.removeTick(tick)
def widgetLength(self):
if self.orientation in ['bottom', 'top']:
return self.width()
else:
return self.height()
def resizeEvent(self, ev):
wlen = max(40, self.widgetLength())
self.setLength(wlen-self.tickSize)
bounds = self.scene.itemsBoundingRect()
bounds.setLeft(min(-self.tickSize*0.5, bounds.left()))
bounds.setRight(max(self.length + self.tickSize, bounds.right()))
#bounds.setTop(min(bounds.top(), self.tickSize))
#bounds.setBottom(max(0, bounds.bottom()))
self.setSceneRect(bounds)
self.fitInView(bounds, QtCore.Qt.KeepAspectRatio)
def setLength(self, newLen):
for t, x in self.ticks.items():
t.setPos(x * newLen, t.pos().y())
self.length = float(newLen)
def mousePressEvent(self, ev):
QtGui.QGraphicsView.mousePressEvent(self, ev)
self.ignoreRelease = False
if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks
self.ignoreRelease = True
def mouseReleaseEvent(self, ev):
QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
if self.ignoreRelease:
return
pos = self.mapToScene(ev.pos())
if pos.x() < 0 or pos.x() > self.length:
return
if pos.y() < 0 or pos.y() > self.tickSize:
return
if ev.button() == QtCore.Qt.LeftButton and self.allowAdd:
pos.setX(min(max(pos.x(), 0), self.length))
self.addTick(pos.x()/self.length)
elif ev.button() == QtCore.Qt.RightButton:
self.showMenu(ev)
def showMenu(self, ev):
pass
def setTickColor(self, tick, color):
tick = self.getTick(tick)
tick.color = color
tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color)))
def setTickValue(self, tick, val):
tick = self.getTick(tick)
val = min(max(0.0, val), 1.0)
x = val * self.length
pos = tick.pos()
pos.setX(x)
tick.setPos(pos)
self.ticks[tick] = val
def tickValue(self, tick):
tick = self.getTick(tick)
return self.ticks[tick]
def getTick(self, tick):
if type(tick) is int:
tick = self.listTicks()[tick][0]
return tick
def mouseMoveEvent(self, ev):
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
#print ev.pos(), ev.buttons()
def listTicks(self):
ticks = self.ticks.items()
ticks.sort(lambda a,b: cmp(a[1], b[1]))
return ticks
class GradientWidget(TickSlider):
sigGradientChanged = QtCore.Signal(object)
def __init__(self, *args, **kargs):
TickSlider.__init__(self, *args, **kargs)
self.currentTick = None
self.currentTickColor = None
self.rectSize = 15
self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize))
self.colorMode = 'rgb'
self.colorDialog = QtGui.QColorDialog()
self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True)
self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True)
#QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged)
self.colorDialog.currentColorChanged.connect(self.currentColorChanged)
#QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected)
self.colorDialog.rejected.connect(self.currentColorRejected)
#self.gradient = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(100,0))
self.scene.addItem(self.gradRect)
self.addTick(0, QtGui.QColor(0,0,0), True)
self.addTick(1, QtGui.QColor(255,0,0), True)
self.setMaxDim(self.rectSize + self.tickSize)
self.updateGradient()
#self.btn = QtGui.QPushButton('RGB')
#self.btnProxy = self.scene.addWidget(self.btn)
#self.btnProxy.setFlag(self.btnProxy.ItemIgnoresTransformations)
#self.btnProxy.scale(0.7, 0.7)
#self.btnProxy.translate(-self.btnProxy.sceneBoundingRect().width()+self.tickSize/2., 0)
#if self.orientation == 'bottom':
#self.btnProxy.translate(0, -self.rectSize)
def setColorMode(self, cm):
if cm not in ['rgb', 'hsv']:
raise Exception("Unknown color mode %s" % str(cm))
self.colorMode = cm
self.updateGradient()
def updateGradient(self):
self.gradient = self.getGradient()
self.gradRect.setBrush(QtGui.QBrush(self.gradient))
#self.emit(QtCore.SIGNAL('gradientChanged'), self)
self.sigGradientChanged.emit(self)
def setLength(self, newLen):
TickSlider.setLength(self, newLen)
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
self.updateGradient()
def currentColorChanged(self, color):
if color.isValid() and self.currentTick is not None:
self.setTickColor(self.currentTick, color)
self.updateGradient()
def currentColorRejected(self):
self.setTickColor(self.currentTick, self.currentTickColor)
self.updateGradient()
def tickClicked(self, tick, ev):
if ev.button() == QtCore.Qt.LeftButton:
if not tick.colorChangeAllowed:
return
self.currentTick = tick
self.currentTickColor = tick.color
self.colorDialog.setCurrentColor(tick.color)
self.colorDialog.open()
#color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
#if color.isValid():
#self.setTickColor(tick, color)
#self.updateGradient()
elif ev.button() == QtCore.Qt.RightButton:
if not tick.removeAllowed:
return
if len(self.ticks) > 2:
self.removeTick(tick)
self.updateGradient()
def tickMoved(self, tick, pos):
TickSlider.tickMoved(self, tick, pos)
self.updateGradient()
def getGradient(self):
g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0))
if self.colorMode == 'rgb':
ticks = self.listTicks()
g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks])
elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop
ticks = self.listTicks()
stops = []
stops.append((ticks[0][1], ticks[0][0].color))
for i in range(1,len(ticks)):
x1 = ticks[i-1][1]
x2 = ticks[i][1]
dx = (x2-x1) / 10.
for j in range(1,10):
x = x1 + dx*j
stops.append((x, self.getColor(x)))
stops.append((x2, self.getColor(x2)))
g.setStops(stops)
return g
def getColor(self, x):
ticks = self.listTicks()
if x <= ticks[0][1]:
return QtGui.QColor(ticks[0][0].color) # always copy colors before handing them out
if x >= ticks[-1][1]:
return QtGui.QColor(ticks[-1][0].color)
x2 = ticks[0][1]
for i in range(1,len(ticks)):
x1 = x2
x2 = ticks[i][1]
if x1 <= x and x2 >= x:
break
dx = (x2-x1)
if dx == 0:
f = 0.
else:
f = (x-x1) / dx
c1 = ticks[i-1][0].color
c2 = ticks[i][0].color
if self.colorMode == 'rgb':
r = c1.red() * (1.-f) + c2.red() * f
g = c1.green() * (1.-f) + c2.green() * f
b = c1.blue() * (1.-f) + c2.blue() * f
a = c1.alpha() * (1.-f) + c2.alpha() * f
return QtGui.QColor(r, g, b,a)
elif self.colorMode == 'hsv':
h1,s1,v1,_ = c1.getHsv()
h2,s2,v2,_ = c2.getHsv()
h = h1 * (1.-f) + h2 * f
s = s1 * (1.-f) + s2 * f
v = v1 * (1.-f) + v2 * f
c = QtGui.QColor()
c.setHsv(h,s,v)
return c
def mouseReleaseEvent(self, ev):
TickSlider.mouseReleaseEvent(self, ev)
self.updateGradient()
def addTick(self, x, color=None, movable=True):
if color is None:
color = self.getColor(x)
t = TickSlider.addTick(self, x, color=color, movable=movable)
t.colorChangeAllowed = True
t.removeAllowed = True
return t
def saveState(self):
ticks = []
for t in self.ticks:
c = t.color
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
state = {'mode': self.colorMode, 'ticks': ticks}
return state
def restoreState(self, state):
self.setColorMode(state['mode'])
for t in self.ticks.keys():
self.removeTick(t)
for t in state['ticks']:
c = QtGui.QColor(*t[1])
self.addTick(t[0], c)
self.updateGradient()
class BlackWhiteSlider(GradientWidget):
def __init__(self, parent):
GradientWidget.__init__(self, parent)
self.getTick(0).colorChangeAllowed = False
self.getTick(1).colorChangeAllowed = False
self.allowAdd = False
self.setTickColor(self.getTick(1), QtGui.QColor(255,255,255))
self.setOrientation('right')
def getLevels(self):
return (self.tickValue(0), self.tickValue(1))
def setLevels(self, black, white):
self.setTickValue(0, black)
self.setTickValue(1, white)
class GammaWidget(TickSlider):
pass
class Tick(QtGui.QGraphicsPolygonItem):
def __init__(self, view, pos, color, movable=True, scale=10):
#QObjectWorkaround.__init__(self)
self.movable = movable
self.view = weakref.ref(view)
self.scale = scale
self.color = color
#self.endTick = endTick
self.pg = QtGui.QPolygonF([QtCore.QPointF(0,0), QtCore.QPointF(-scale/3**0.5,scale), QtCore.QPointF(scale/3**0.5,scale)])
QtGui.QGraphicsPolygonItem.__init__(self, self.pg)
self.setPos(pos[0], pos[1])
self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemIsSelectable)
self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
if self.movable:
self.setZValue(1)
else:
self.setZValue(0)
#def x(self):
#return self.pos().x()/100.
def mouseMoveEvent(self, ev):
#print self, "move", ev.scenePos()
if not self.movable:
return
if not ev.buttons() & QtCore.Qt.LeftButton:
return
newPos = ev.scenePos() + self.mouseOffset
newPos.setY(self.pos().y())
#newPos.setX(min(max(newPos.x(), 0), 100))
self.setPos(newPos)
self.view().tickMoved(self, newPos)
self.movedSincePress = True
#self.emit(QtCore.SIGNAL('tickChanged'), self)
ev.accept()
def mousePressEvent(self, ev):
self.movedSincePress = False
if ev.button() == QtCore.Qt.LeftButton:
ev.accept()
self.mouseOffset = self.pos() - ev.scenePos()
self.pressPos = ev.scenePos()
elif ev.button() == QtCore.Qt.RightButton:
ev.accept()
#if self.endTick:
#return
#self.view.tickChanged(self, delete=True)
def mouseReleaseEvent(self, ev):
#print self, "release", ev.scenePos()
if not self.movedSincePress:
self.view().tickClicked(self, ev)
#if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
#color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
#if color.isValid():
#self.color = color
#self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
##self.emit(QtCore.SIGNAL('tickChanged'), self)
#self.view.tickChanged(self)
if __name__ == '__main__':
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
w.show()
w.resize(400,400)
cw = QtGui.QWidget()
w.setCentralWidget(cw)
l = QtGui.QGridLayout()
l.setSpacing(0)
cw.setLayout(l)
w1 = GradientWidget(orientation='top')
w2 = GradientWidget(orientation='right', allowAdd=False)
w2.setTickColor(1, QtGui.QColor(255,255,255))
w3 = GradientWidget(orientation='bottom')
w4 = TickSlider(orientation='left')
l.addWidget(w1, 0, 1)
l.addWidget(w2, 1, 2)
l.addWidget(w3, 2, 1)
l.addWidget(w4, 1, 0)

747
GraphicsScene.py Normal file
View File

@ -0,0 +1,747 @@
from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg
import weakref
from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import pyqtgraph.ptime as ptime
import debug
try:
import sip
HAVE_SIP = True
except:
HAVE_SIP = False
__all__ = ['GraphicsScene']
class GraphicsScene(QtGui.QGraphicsScene):
"""
Extension of QGraphicsScene that implements a complete, parallel mouse event system.
(It would have been preferred to just alter the way QGraphicsScene creates and delivers
events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent
is private)
- Generates MouseClicked events in addition to the usual press/move/release events.
(This works around a problem where it is impossible to have one item respond to a
drag if another is watching for a click.)
- Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects
- Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu.
- Allows items to decide _before_ a mouse click which item will be the recipient of mouse events.
This lets us indicate unambiguously to the user which item they are about to click/drag on
- Eats mouseMove events that occur too soon after a mouse press.
- Reimplements items() and itemAt() to circumvent PyQt bug
Mouse interaction is as follows:
1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents
as well as custom HoverEvents.
2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button),
acceptDrags(button) or both. If this method call returns True, this informs the item that _if_
the user clicks/drags the specified mouse button, the item is guaranteed to be the
recipient of click/drag events (the item may wish to change its appearance to indicate this).
If the call to acceptClicks/Drags returns False, then the item is guaranteed to NOT receive
the requested event (because another item has already accepted it).
3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then
No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows
items to function properly if they are expecting the usual press/move/release sequence of events.
(It is recommended that items do NOT accept press events, and instead use click/drag events)
Note: The default implementation of QGraphicsItem.mousePressEvent will ACCEPT the event if the
item is has its Selectable or Movable flags enabled. You may need to override this behavior.
3) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events.
If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released,
then a mouseDragEvent is generated.
If no drag events are generated before the button is released, then a mouseClickEvent is generated.
4) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent
in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event.
ClickEvents may be delivered in this way even if no
item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial
move in a drag.
"""
_addressCache = weakref.WeakValueDictionary()
sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over
sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move
sigMouseClicked = QtCore.Signal(object) ## emitted when MouseClickEvent is not accepted by any items under the click.
@classmethod
def registerObject(cls, obj):
"""
Workaround for PyQt bug in qgraphicsscene.items()
All subclasses of QGraphicsObject must register themselves with this function.
(otherwise, mouse interaction with those objects will likely fail)
"""
if HAVE_SIP and isinstance(obj, sip.wrapper):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5):
QtGui.QGraphicsScene.__init__(self)
self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance)
self.clickEvents = []
self.dragButtons = []
self.mouseGrabber = None
self.dragItem = None
self.lastDrag = None
self.hoverItems = weakref.WeakKeyDictionary()
self.lastHoverEvent = None
#self.searchRect = QtGui.QGraphicsRectItem()
#self.searchRect.setPen(fn.mkPen(200,0,0))
#self.addItem(self.searchRect)
def setClickRadius(self, r):
"""
Set the distance away from mouse clicks to search for interacting items.
When clicking, the scene searches first for items that directly intersect the click position
followed by any other items that are within a rectangle that extends r pixels away from the
click position.
"""
self._clickRadius = r
def setMoveDistance(self, d):
"""
Set the distance the mouse must move after a press before mouseMoveEvents will be delivered.
This ensures that clicks with a small amount of movement are recognized as clicks instead of
drags.
"""
self._moveDistance = d
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
self.clickEvents.append(MouseClickEvent(ev))
#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())
## First allow QGraphicsScene to deliver hoverEnter/Move/ExitEvents
QtGui.QGraphicsScene.mouseMoveEvent(self, ev)
## Next deliver our own HoverEvents
self.sendHoverEvents(ev)
if int(ev.buttons()) != 0: ## button is pressed; send mouseMoveEvents and mouseDragEvents
QtGui.QGraphicsScene.mouseMoveEvent(self, ev)
if self.mouseGrabberItem() is None:
now = ptime.time()
init = False
## keep track of which buttons are involved in dragging
for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]:
if int(ev.buttons() & btn) == 0:
continue
if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0]
dist = Point(ev.screenPos() - cev.screenPos())
if dist.length() < self._moveDistance and now - cev.time() < 0.5:
continue
init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True
self.dragButtons.append(int(btn))
## If we have dragged buttons, deliver a drag event
if len(self.dragButtons) > 0:
if self.sendDragEvent(ev, init=init):
ev.accept()
def leaveEvent(self, ev): ## inform items that mouse is gone
if len(self.dragButtons) == 0:
self.sendHoverEvents(ev, exitOnly=True)
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"
ev.accept()
self.dragButtons.remove(ev.button())
else:
cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())]
if self.sendClickEvent(cev[0]):
#print "sent click event"
ev.accept()
self.clickEvents.remove(cev[0])
if int(ev.buttons()) == 0:
self.dragItem = None
self.dragButtons = []
self.clickEvents = []
self.lastDrag = None
QtGui.QGraphicsScene.mouseReleaseEvent(self, ev)
self.sendHoverEvents(ev) ## let items prepare for next click/drag
def mouseDoubleClickEvent(self, ev):
QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev)
if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events
self.clickEvents.append(MouseClickEvent(ev, double=True))
def sendHoverEvents(self, ev, exitOnly=False):
## if exitOnly, then just inform all previously hovered items that the mouse has left.
if exitOnly:
acceptable=False
items = []
event = HoverEvent(None, acceptable)
else:
acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event.
event = HoverEvent(ev, acceptable)
items = self.itemsNearEvent(event)
self.sigMouseHover.emit(items)
prevItems = self.hoverItems.keys()
for item in items:
if hasattr(item, 'hoverEvent'):
event.currentItem = item
if item not in self.hoverItems:
self.hoverItems[item] = None
event.enter = True
else:
prevItems.remove(item)
event.enter = False
try:
item.hoverEvent(event)
except:
debug.printExc("Error sending hover event:")
event.enter = False
event.exit = True
for item in prevItems:
event.currentItem = item
try:
item.hoverEvent(event)
except:
debug.printExc("Error sending hover exit event:")
finally:
del self.hoverItems[item]
if hasattr(ev, 'buttons') 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
## items near the beginning of the drag
event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final)
#print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem
if init and self.dragItem is None:
if self.lastHoverEvent is not None:
acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None)
else:
acceptedItem = None
if acceptedItem is not None:
#print "Drag -> pre-selected item:", acceptedItem
self.dragItem = acceptedItem
event.currentItem = self.dragItem
try:
self.dragItem.mouseDragEvent(event)
except:
debug.printExc("Error sending drag event:")
else:
#print "drag -> new item"
for item in self.itemsNearEvent(event):
#print "check item:", item
if hasattr(item, 'mouseDragEvent'):
event.currentItem = item
try:
item.mouseDragEvent(event)
except:
debug.printExc("Error sending drag event:")
if event.isAccepted():
#print " --> accepted"
self.dragItem = item
break
elif self.dragItem is not None:
event.currentItem = self.dragItem
try:
self.dragItem.mouseDragEvent(event)
except:
debug.printExc("Error sending hover exit event:")
self.lastDrag = event
return event.isAccepted()
def sendClickEvent(self, ev):
## if we are in mid-drag, click events may only go to the dragged item.
if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'):
ev.currentItem = self.dragItem
self.dragItem.mouseClickEvent(ev)
## otherwise, search near the cursor
else:
if self.lastHoverEvent is not None:
acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
else:
acceptedItem = None
if acceptedItem is not None:
ev.currentItem = acceptedItem
try:
acceptedItem.mouseClickEvent(ev)
except:
debug.printExc("Error sending click event:")
else:
for item in self.itemsNearEvent(ev):
if hasattr(item, 'mouseClickEvent'):
ev.currentItem = item
try:
item.mouseClickEvent(ev)
except:
debug.printExc("Error sending click event:")
if ev.isAccepted():
break
if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
#print "GraphicsScene emitting sigSceneContextMenu"
self.sigMouseClicked.emit(ev)
ev.accept()
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)
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
## then the object returned will be different than the actual item that was originally added to the scene
items2 = map(self.translateGraphicsItem, items)
#if HAVE_SIP and isinstance(self, sip.wrapper):
#items2 = []
#for i in items:
#addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem))
#i2 = GraphicsScene._addressCache.get(addr, i)
##print i, "==>", i2
#items2.append(i2)
#print 'items:', items
return items2
def selectedItems(self, *args):
items = QtGui.QGraphicsScene.selectedItems(self, *args)
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
## then the object returned will be different than the actual item that was originally added to the scene
#if HAVE_SIP and isinstance(self, sip.wrapper):
#items2 = []
#for i in items:
#addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem))
#i2 = GraphicsScene._addressCache.get(addr, i)
##print i, "==>", i2
#items2.append(i2)
items2 = map(self.translateGraphicsItem, items)
#print 'items:', items
return items2
def itemAt(self, *args):
item = QtGui.QGraphicsScene.itemAt(self, *args)
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
## then the object returned will be different than the actual item that was originally added to the scene
#if HAVE_SIP and isinstance(self, sip.wrapper):
#addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem))
#item = GraphicsScene._addressCache.get(addr, item)
#return item
return self.translateGraphicsItem(item)
def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder):
"""
Return an iterator that iterates first through the items that directly intersect point (in Z order)
followed by any other items that are within the scene's click radius.
"""
#tr = self.getViewWidget(event.widget()).transform()
view = self.views()[0]
tr = view.viewportTransform()
r = self._clickRadius
rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect()
seen = set()
if hasattr(event, 'buttonDownScenePos'):
point = event.buttonDownScenePos()
else:
point = event.scenePos()
w = rect.width()
h = rect.height()
rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h)
#self.searchRect.setRect(rgn)
items = self.items(point, selMode, sortOrder, tr)
## remove items whose shape does not contain point (scene.items() apparently sucks at this)
items2 = []
for item in items:
shape = item.shape()
if shape is None:
continue
if item.mapToScene(shape).contains(point):
items2.append(item)
## Sort by descending Z-order (don't trust scene.itms() to do this either)
## use 'absolute' z value, which is the sum of all item/parent ZValues
def absZValue(item):
if item is None:
return 0
return item.zValue() + absZValue(item.parentItem())
items2.sort(lambda a,b: cmp(absZValue(b), absZValue(a)))
return items2
#for item in items:
##seen.add(item)
#shape = item.mapToScene(item.shape())
#if not shape.contains(point):
#continue
#yield item
#for item in self.items(rgn, selMode, sortOrder, tr):
##if item not in seen:
#yield item
def getViewWidget(self, widget):
## same pyqt bug -- mouseEvent.widget() doesn't give us the original python object.
## [[doesn't seem to work correctly]]
if HAVE_SIP and isinstance(self, sip.wrapper):
addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget))
#print "convert", widget, addr
for v in self.views():
addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget))
#print " check:", v, addr2
if addr2 == addr:
return v
else:
return widget
def addParentContextMenus(self, item, menu, event):
"""
Can be called by any item in the scene to expand its context menu to include parent context menus.
Parents may implement getContextMenus to add new menus / actions to the existing menu.
getContextMenus must accept 1 argument (the event that generated the original menu) and
return a single QMenu or a list of QMenus.
The final menu will look like:
Original Item 1
Original Item 2
...
Original Item N
------------------
Parent Item 1
Parent Item 2
...
Grandparent Item 1
...
Arguments:
item - The item that initially created the context menu
(This is probably the item making the call to this function)
menu - The context menu being shown by the item
event - The original event that triggered the menu to appear.
"""
#items = self.itemsNearEvent(ev)
menusToAdd = []
while item.parentItem() is not None:
item = item.parentItem()
#for item in items:
#if item is sender:
#continue
if not hasattr(item, "getContextMenus"):
continue
subMenus = item.getContextMenus(event)
if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
subMenus = [subMenus]
for sm in subMenus:
menusToAdd.append(sm)
if len(menusToAdd) > 0:
menu.addSeparator()
for m in menusToAdd:
menu.addMenu(m)
return menu
@staticmethod
def translateGraphicsItem(item):
## for fixing pyqt bugs where the wrong item is returned
if HAVE_SIP and isinstance(item, sip.wrapper):
addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem))
item = GraphicsScene._addressCache.get(addr, item)
return item
@staticmethod
def translateGraphicsItems(items):
return map(GraphicsScene.translateGraphicsItem, items)
class MouseDragEvent:
def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False):
self.start = start
self.finish = finish
self.accepted = False
self.currentItem = None
self._buttonDownScenePos = {}
self._buttonDownScreenPos = {}
for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]:
self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn)
self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn)
self._scenePos = moveEvent.scenePos()
self._screenPos = moveEvent.screenPos()
if lastEvent is None:
self._lastScenePos = pressEvent.scenePos()
self._lastScreenPos = pressEvent.screenPos()
else:
self._lastScenePos = lastEvent.scenePos()
self._lastScreenPos = lastEvent.screenPos()
self._buttons = moveEvent.buttons()
self._button = pressEvent.button()
self._modifiers = moveEvent.modifiers()
def accept(self):
self.accepted = True
self.acceptedItem = self.currentItem
def ignore(self):
self.accepted = False
def isAccepted(self):
return self.accepted
def scenePos(self):
return Point(self._scenePos)
def screenPos(self):
return Point(self._screenPos)
def buttonDownScenePos(self, btn=None):
if btn is None:
btn = self.button()
return Point(self._buttonDownScenePos[int(btn)])
def buttonDownScreenPos(self, btn=None):
if btn is None:
btn = self.button()
return Point(self._buttonDownScreenPos[int(btn)])
def lastScenePos(self):
return Point(self._lastScenePos)
def lastScreenPos(self):
return Point(self._lastScreenPos)
def buttons(self):
return self._buttons
def button(self):
"""Return the button that initiated the drag (may be different from the buttons currently pressed)"""
return self._button
def pos(self):
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def buttonDownPos(self, btn=None):
if btn is None:
btn = self.button()
return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)]))
def isStart(self):
return self.start
def isFinish(self):
return self.finish
def __repr__(self):
lp = self.lastPos()
p = self.pos()
return "<MouseDragEvent (%g,%g)->(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish()))
def modifiers(self):
return self._modifiers
class MouseClickEvent:
def __init__(self, pressEvent, double=False):
self.accepted = False
self.currentItem = None
self._double = double
self._scenePos = pressEvent.scenePos()
self._screenPos = pressEvent.screenPos()
self._button = pressEvent.button()
self._buttons = pressEvent.buttons()
self._modifiers = pressEvent.modifiers()
self._time = ptime.time()
def accept(self):
self.accepted = True
self.acceptedItem = self.currentItem
def ignore(self):
self.accepted = False
def isAccepted(self):
return self.accepted
def scenePos(self):
return Point(self._scenePos)
def screenPos(self):
return Point(self._screenPos)
def buttons(self):
return self._buttons
def button(self):
return self._button
def double(self):
return self._double
def pos(self):
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def modifiers(self):
return self._modifiers
def __repr__(self):
p = self.pos()
return "<MouseClickEvent (%g,%g) button=%d>" % (p.x(), p.y(), int(self.button()))
def time(self):
return self._time
class HoverEvent:
"""
This event class both informs items that the mouse cursor is nearby and allows items to
communicate with one another about whether each item will accept _potential_ mouse events.
It is common for multiple overlapping items to receive hover events and respond by changing
their appearance. This can be misleading to the user since, in general, only one item will
respond to mouse events. To avoid this, items make calls to event.acceptClicks(button)
and/or acceptDrags(button).
Each item may make multiple calls to acceptClicks/Drags, each time for a different button.
If the method returns True, then the item is guaranteed to be
the recipient of the claimed event IF the user presses the specified mouse button before
moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified
event (because another has already claimed it) and the item should change its appearance
accordingly.
event.isEnter() returns True if the mouse has just entered the item's shape;
event.isExit() returns True if the mouse has just left.
"""
def __init__(self, moveEvent, acceptable):
self.enter = False
self.acceptable = acceptable
self.exit = False
self.__clickItems = weakref.WeakValueDictionary()
self.__dragItems = weakref.WeakValueDictionary()
self.currentItem = None
if moveEvent is not None:
self._scenePos = moveEvent.scenePos()
self._screenPos = moveEvent.screenPos()
self._lastScenePos = moveEvent.lastScenePos()
self._lastScreenPos = moveEvent.lastScreenPos()
self._buttons = moveEvent.buttons()
self._modifiers = moveEvent.modifiers()
else:
self.exit = True
def isEnter(self):
return self.enter
def isExit(self):
return self.exit
def acceptClicks(self, button):
""""""
if not self.acceptable:
return False
if button not in self.__clickItems:
self.__clickItems[button] = self.currentItem
return True
return False
def acceptDrags(self, button):
if not self.acceptable:
return False
if button not in self.__dragItems:
self.__dragItems[button] = self.currentItem
return True
return False
def scenePos(self):
return Point(self._scenePos)
def screenPos(self):
return Point(self._screenPos)
def lastScenePos(self):
return Point(self._lastScenePos)
def lastScreenPos(self):
return Point(self._lastScreenPos)
def buttons(self):
return self._buttons
def pos(self):
return Point(self.currentItem.mapFromScene(self._scenePos))
def lastPos(self):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self):
lp = self.lastPos()
p = self.pos()
return "<HoverEvent (%g,%g)->(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit()))
def modifiers(self):
return self._modifiers
def clickItems(self):
return self.__clickItems
def dragItems(self):
return self.__dragItems

BIN
GraphicsScene.pyc Normal file

Binary file not shown.

View File

@ -1,283 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>726</width>
<height>588</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="0" rowspan="3">
<widget class="GraphicsView" name="graphicsView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>10</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QPushButton" name="roiBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>R</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="GradientWidget" name="gradientWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>100</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QPushButton" name="normBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>N</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="4">
<widget class="QGroupBox" name="normGroup">
<property name="title">
<string>Normalization</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="2">
<widget class="QRadioButton" name="normSubtractRadio">
<property name="text">
<string>Subtract</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="normDivideRadio">
<property name="text">
<string>Divide</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Operation:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Mean:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Blur:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="normROICheck">
<property name="text">
<string>ROI</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QDoubleSpinBox" name="normXBlurSpin"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_8">
<property name="text">
<string>X</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Y</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QDoubleSpinBox" name="normYBlurSpin"/>
</item>
<item row="2" column="5">
<widget class="QLabel" name="label_10">
<property name="text">
<string>T</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QRadioButton" name="normOffRadio">
<property name="text">
<string>Off</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="normTimeRangeCheck">
<property name="text">
<string>Time range</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="normFrameCheck">
<property name="text">
<string>Frame</string>
</property>
</widget>
</item>
<item row="2" column="6">
<widget class="QDoubleSpinBox" name="normTBlurSpin"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="PlotWidget" name="roiPlot" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GradientWidget</class>
<extends>QWidget</extends>
<header>pyqtgraph.GradientWidget</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GraphicsView</class>
<extends>QWidget</extends>
<header>GraphicsView</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header>PlotWidget</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
"""
from PyQt4 import QtCore
from Qt import QtCore
import numpy as np
def clip(x, mn, mx):
@ -23,12 +23,12 @@ class Point(QtCore.QPointF):
if isinstance(args[0], QtCore.QSizeF):
QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height()))
return
elif isinstance(args[0], float) or isinstance(args[0], int):
QtCore.QPointF.__init__(self, float(args[0]), float(args[0]))
return
elif hasattr(args[0], '__getitem__'):
QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1]))
return
elif type(args[0]) in [float, int]:
QtCore.QPointF.__init__(self, float(args[0]), float(args[0]))
return
elif len(args) == 2:
QtCore.QPointF.__init__(self, args[0], args[1])
return
@ -101,6 +101,10 @@ class Point(QtCore.QPointF):
"""Returns the vector length of this Point."""
return (self[0]**2 + self[1]**2) ** 0.5
def norm(self):
"""Returns a vector in the same direction with unit length."""
return self / self.length()
def angle(self, a):
"""Returns the angle in degrees between this vector and the vector a."""
n1 = self.length()
@ -139,4 +143,7 @@ class Point(QtCore.QPointF):
return max(self[0], self[1])
def copy(self):
return Point(self)
return Point(self)
def toQPoint(self):
return QtCore.QPoint(*self)

BIN
Point.pyc Normal file

Binary file not shown.

5
Qt.py Normal file
View File

@ -0,0 +1,5 @@
## Do all Qt imports from here to allow easier PyQt / PySide compatibility
from PyQt4 import QtGui, QtCore, QtOpenGL, QtSvg
if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal

BIN
Qt.pyc Normal file

Binary file not shown.

View File

@ -1,31 +1,38 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtCore
from Qt import QtCore
from ptime import time
__all__ = ['SignalProxy']
class SignalProxy(QtCore.QObject):
"""Object which collects rapid-fire signals and condenses them
into a single signal. Used, for example, to prevent a SpinBox
from generating multiple signals when the mouse wheel is rolled
over it."""
over it.
def __init__(self, source, signal, delay=0.3):
Emits sigDelayed after input signals have stopped for a certain period of time.
"""
sigDelayed = QtCore.Signal(object)
def __init__(self, signal, delay=0.3, slot=None):
"""Initialization arguments:
source - Any QObject that will emit signal, or None if signal is new style
signal - Output of QtCore.SIGNAL(...), or obj.signal for new style
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)"""
signal - a bound Signal or pyqtSignal instance
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)
slot - Optional function to connect sigDelayed to.
"""
QtCore.QObject.__init__(self)
if source is None:
signal.connect(self.signalReceived)
self.signal = QtCore.SIGNAL('signal')
else:
source.connect(source, signal, self.signalReceived)
self.signal = signal
signal.connect(self.signalReceived)
self.signal = signal
self.delay = delay
self.args = None
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.flush)
self.block = False
self.slot = slot
if slot is not None:
self.sigDelayed.connect(slot)
def setDelay(self, delay):
self.delay = delay
@ -42,28 +49,39 @@ class SignalProxy(QtCore.QObject):
"""If there is a signal queued up, send it now."""
if self.args is None or self.block:
return False
self.emit(self.signal, *self.args)
#self.emit(self.signal, *self.args)
self.sigDelayed.emit(self.args)
self.args = None
self.timer.stop()
return True
def disconnect(self):
self.block = True
try:
self.signal.disconnect(self.signalReceived)
except:
pass
try:
self.sigDelayed.disconnect(self.slot)
except:
pass
def proxyConnect(source, signal, slot, delay=0.3):
"""Connect a signal to a slot with delay. Returns the SignalProxy
object that was created. Be sure to store this object so it is not
garbage-collected immediately."""
sp = SignalProxy(source, signal, delay)
if source is None:
sp.connect(sp, QtCore.SIGNAL('signal'), slot)
else:
sp.connect(sp, signal, slot)
return sp
#def proxyConnect(source, signal, slot, delay=0.3):
#"""Connect a signal to a slot with delay. Returns the SignalProxy
#object that was created. Be sure to store this object so it is not
#garbage-collected immediately."""
#sp = SignalProxy(source, signal, delay)
#if source is None:
#sp.connect(sp, QtCore.SIGNAL('signal'), slot)
#else:
#sp.connect(sp, signal, slot)
#return sp
if __name__ == '__main__':
from PyQt4 import QtGui
from Qt import QtGui
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
spin = QtGui.QSpinBox()
@ -71,8 +89,12 @@ if __name__ == '__main__':
win.show()
def fn(*args):
print "Got signal:", args
print "Raw signal:", args
def fn2(*args):
print "Delayed signal:", args
proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn)
spin.valueChanged.connect(fn)
#proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn)
proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2)

BIN
SignalProxy.pyc Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
from Qt import QtCore, QtGui
from Point import Point
import numpy as np
@ -108,8 +108,8 @@ class Transform(QtGui.QTransform):
def saveState(self):
p = self._state['pos']
s = self._state['scale']
if s[0] == 0:
raise Exception('Invalid scale')
#if s[0] == 0:
#raise Exception('Invalid scale: %s' % str(s))
return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']}
def restoreState(self, state):

BIN
Transform.pyc Normal file

Binary file not shown.

282
WidgetGroup.py Normal file
View File

@ -0,0 +1,282 @@
# -*- coding: utf-8 -*-
"""
WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets
Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
This class addresses the problem of having to save and restore the state
of a large group of widgets.
"""
from Qt import QtCore, QtGui
import weakref, inspect
__all__ = ['WidgetGroup']
def splitterState(w):
s = str(w.saveState().toPercentEncoding())
return s
def restoreSplitter(w, s):
if type(s) is list:
w.setSizes(s)
elif type(s) is str:
w.restoreState(QtCore.QByteArray.fromPercentEncoding(s))
else:
print "Can't configure QSplitter using object of type", type(s)
if w.count() > 0: ## make sure at least one item is not collapsed
for i in w.sizes():
if i > 0:
return
w.setSizes([50] * w.count())
def comboState(w):
ind = w.currentIndex()
data = w.itemData(ind)
#if not data.isValid():
if data is not None:
try:
if not data.isValid():
data = None
else:
data = data.toInt()[0]
except AttributeError:
pass
if data is None:
return unicode(w.itemText(ind))
else:
return data
def setComboState(w, v):
if type(v) is int:
#ind = w.findData(QtCore.QVariant(v))
ind = w.findData(v)
if ind > -1:
w.setCurrentIndex(ind)
return
w.setCurrentIndex(w.findText(str(v)))
class WidgetGroup(QtCore.QObject):
"""This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously."""
## List of widget types which can be handled by WidgetGroup.
## The value for each type is a tuple (change signal function, get function, set function, [auto-add children])
## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just
## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here)
## If the change signal is None, the value of the widget is not cached.
## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method
## which returns the tuple.
classes = {
QtGui.QSpinBox:
(lambda w: w.valueChanged,
QtGui.QSpinBox.value,
QtGui.QSpinBox.setValue),
QtGui.QDoubleSpinBox:
(lambda w: w.valueChanged,
QtGui.QDoubleSpinBox.value,
QtGui.QDoubleSpinBox.setValue),
QtGui.QSplitter:
(None,
splitterState,
restoreSplitter,
True),
QtGui.QCheckBox:
(lambda w: w.stateChanged,
QtGui.QCheckBox.isChecked,
QtGui.QCheckBox.setChecked),
QtGui.QComboBox:
(lambda w: w.currentIndexChanged,
comboState,
setComboState),
QtGui.QGroupBox:
(lambda w: w.toggled,
QtGui.QGroupBox.isChecked,
QtGui.QGroupBox.setChecked,
True),
QtGui.QLineEdit:
(lambda w: w.editingFinished,
lambda w: str(w.text()),
QtGui.QLineEdit.setText),
QtGui.QRadioButton:
(lambda w: w.toggled,
QtGui.QRadioButton.isChecked,
QtGui.QRadioButton.setChecked),
QtGui.QSlider:
(lambda w: w.valueChanged,
QtGui.QSlider.value,
QtGui.QSlider.setValue),
}
sigChanged = QtCore.Signal(str, object)
def __init__(self, widgetList=None):
"""Initialize WidgetGroup, adding specified widgets into this group.
widgetList can be:
- a list of widget specifications (widget, [name], [scale])
- a dict of name: widget pairs
- any QObject, and all compatible child widgets will be added recursively.
The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded
in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user)
"""
QtCore.QObject.__init__(self)
self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here
self.scales = weakref.WeakKeyDictionary()
self.cache = {} ## name:value pairs
self.uncachedWidgets = weakref.WeakKeyDictionary()
if isinstance(widgetList, QtCore.QObject):
self.autoAdd(widgetList)
elif isinstance(widgetList, list):
for w in widgetList:
self.addWidget(*w)
elif isinstance(widgetList, dict):
for name, w in widgetList.iteritems():
self.addWidget(w, name)
elif widgetList is None:
return
else:
raise Exception("Wrong argument type %s" % type(widgetList))
def addWidget(self, w, name=None, scale=None):
if not self.acceptsType(w):
raise Exception("Widget type %s not supported by WidgetGroup" % type(w))
if name is None:
name = str(w.objectName())
if name == '':
raise Exception("Cannot add widget '%s' without a name." % str(w))
self.widgetList[w] = name
self.scales[w] = scale
self.readWidget(w)
if type(w) in WidgetGroup.classes:
signal = WidgetGroup.classes[type(w)][0]
else:
signal = w.widgetGroupInterface()[0]
if signal is not None:
if inspect.isfunction(signal) or inspect.ismethod(signal):
signal = signal(w)
signal.connect(self.mkChangeCallback(w))
else:
self.uncachedWidgets[w] = None
def findWidget(self, name):
for w in self.widgetList:
if self.widgetList[w] == name:
return w
return None
def interface(self, obj):
t = type(obj)
if t in WidgetGroup.classes:
return WidgetGroup.classes[t]
else:
return obj.widgetGroupInterface()
def checkForChildren(self, obj):
"""Return true if we should automatically search the children of this object for more."""
iface = self.interface(obj)
return (len(iface) > 3 and iface[3])
def autoAdd(self, obj):
## Find all children of this object and add them if possible.
accepted = self.acceptsType(obj)
if accepted:
#print "%s auto add %s" % (self.objectName(), obj.objectName())
self.addWidget(obj)
if not accepted or self.checkForChildren(obj):
for c in obj.children():
self.autoAdd(c)
def acceptsType(self, obj):
for c in WidgetGroup.classes:
if isinstance(obj, c):
return True
if hasattr(obj, 'widgetGroupInterface'):
return True
return False
#return (type(obj) in WidgetGroup.classes)
def setScale(self, widget, scale):
val = self.readWidget(widget)
self.scales[widget] = scale
self.setWidget(widget, val)
#print "scaling %f to %f" % (val, self.readWidget(widget))
def mkChangeCallback(self, w):
return lambda *args: self.widgetChanged(w, *args)
def widgetChanged(self, w, *args):
#print "widget changed"
n = self.widgetList[w]
v1 = self.cache[n]
v2 = self.readWidget(w)
if v1 != v2:
#print "widget", n, " = ", v2
self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2)
self.sigChanged.emit(self.widgetList[w], v2)
def state(self):
for w in self.uncachedWidgets:
self.readWidget(w)
#cc = self.cache.copy()
#if 'averageGroup' in cc:
#val = cc['averageGroup']
#w = self.findWidget('averageGroup')
#self.readWidget(w)
#if val != self.cache['averageGroup']:
#print " AverageGroup did not match cached value!"
#else:
#print " AverageGroup OK"
return self.cache.copy()
def setState(self, s):
#print "SET STATE", self, s
for w in self.widgetList:
n = self.widgetList[w]
#print " restore %s?" % n
if n not in s:
continue
#print " restore state", w, n, s[n]
self.setWidget(w, s[n])
def readWidget(self, w):
if type(w) in WidgetGroup.classes:
getFunc = WidgetGroup.classes[type(w)][1]
else:
getFunc = w.widgetGroupInterface()[1]
if getFunc is None:
return None
val = getFunc(w)
if self.scales[w] is not None:
val /= self.scales[w]
#if isinstance(val, QtCore.QString):
#val = str(val)
n = self.widgetList[w]
self.cache[n] = val
return val
def setWidget(self, w, v):
v1 = v
if self.scales[w] is not None:
v *= self.scales[w]
if type(w) in WidgetGroup.classes:
setFunc = WidgetGroup.classes[type(w)][2]
else:
setFunc = w.widgetGroupInterface()[2]
setFunc(w, v)
#name = self.widgetList[w]
#if name in self.cache and (self.cache[name] != v1):
#print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1))

BIN
WidgetGroup.pyc Normal file

Binary file not shown.

View File

@ -1,37 +1,102 @@
# -*- coding: utf-8 -*-
### import all the goodies and add some helper functions for easy CLI use
from functions import *
from graphicsItems import *
from graphicsWindows import *
#import PlotWidget
#import ImageView
from PyQt4 import QtGui
## 'Qt' is a local module; it is intended mainly to cover up the differences
## between PyQt4 and PySide.
from Qt import QtGui
## not really safe.
#if QtGui.QApplication.instance() is None:
#app = QtGui.QApplication([])
CONFIG_OPTIONS = {
'leftButtonPan': True ## if false, left button drags a rubber band for zooming in viewbox
}
def setConfigOption(opt, value):
CONFIG_OPTIONS[opt] = value
def getConfigOption(opt):
return CONFIG_OPTIONS[opt]
## Import almost everything to make it available from a single namespace
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
## these must be imported separately.
import os
def importAll(path):
d = os.path.join(os.path.split(__file__)[0], path)
files = []
for f in os.listdir(d):
if os.path.isdir(os.path.join(d, f)):
files.append(f)
elif f[-3:] == '.py' and f != '__init__.py':
files.append(f[:-3])
for modName in files:
mod = __import__(path+"."+modName, globals(), locals(), fromlist=['*'])
if hasattr(mod, '__all__'):
names = mod.__all__
else:
names = [n for n in dir(mod) if n[0] != '_']
for k in names:
if hasattr(mod, k):
globals()[k] = getattr(mod, k)
importAll('graphicsItems')
importAll('widgets')
from imageview import *
from WidgetGroup import *
from Point import Point
from Transform import Transform
from functions import *
from graphicsWindows import *
from SignalProxy import *
## Convenience functions for command-line use
plots = []
images = []
QAPP = None
def plot(*args, **kargs):
"""
| Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it.
| Accepts a *title* argument to set the title of the window.
| All other arguments are used to plot data. (see :func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`)
"""
mkQApp()
if 'title' in kargs:
w = PlotWindow(title=kargs['title'])
del kargs['title']
else:
w = PlotWindow()
w.plot(*args, **kargs)
if len(args)+len(kargs) > 0:
w.plot(*args, **kargs)
plots.append(w)
w.show()
return w
def show(*args, **kargs):
def image(*args, **kargs):
"""
| Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside.
| Will show 2D or 3D image data.
| Accepts a *title* argument to set the title of the window.
| All other arguments are used to show data. (see :func:`ImageView.setImage() <pyqtgraph.ImageView.setImage>`)
"""
mkQApp()
w = ImageWindow(*args, **kargs)
images.append(w)
w.show()
return w
show = image ## for backward compatibility
def mkQApp():
if QtGui.QApplication.instance() is None:

BIN
__init__.pyc Normal file

Binary file not shown.

554
canvas/Canvas.py Normal file
View File

@ -0,0 +1,554 @@
# -*- coding: utf-8 -*-
if __name__ == '__main__':
import sys, os
md = os.path.dirname(os.path.abspath(__file__))
sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path
#print md
from CanvasTemplate import *
#from pyqtgraph.GraphicsView import GraphicsView
#import pyqtgraph.graphicsItems as graphicsItems
#from pyqtgraph.PlotWidget import PlotWidget
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.graphicsItems.ROI import ROI
from pyqtgraph.graphicsItems.ViewBox import ViewBox
from pyqtgraph.graphicsItems.GridItem import GridItem
#import DataManager
import numpy as np
import debug
#import pyqtgraph as pg
import weakref
from CanvasManager import CanvasManager
#import items
from CanvasItem import CanvasItem, GroupCanvasItem
class Canvas(QtGui.QWidget):
sigSelectionChanged = QtCore.Signal(object, object)
sigItemTransformChanged = QtCore.Signal(object, object)
sigItemTransformChangeFinished = QtCore.Signal(object, object)
def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
#self.view = self.ui.view
self.view = ViewBox()
self.ui.view.setCentralItem(self.view)
self.itemList = self.ui.itemList
self.itemList.setSelectionMode(self.itemList.ExtendedSelection)
self.allowTransforms = allowTransforms
self.multiSelectBox = SelectBox()
self.view.addItem(self.multiSelectBox)
self.multiSelectBox.hide()
self.multiSelectBox.setZValue(1e6)
self.ui.mirrorSelectionBtn.hide()
self.ui.resetTransformsBtn.hide()
self.redirect = None ## which canvas to redirect items to
self.items = []
#self.view.enableMouse()
self.view.setAspectLocked(True)
self.view.invertY()
grid = GridItem()
self.grid = CanvasItem(grid, name='Grid', movable=False)
self.addItem(self.grid)
self.hideBtn = QtGui.QPushButton('>', self)
self.hideBtn.setFixedWidth(20)
self.hideBtn.setFixedHeight(20)
self.ctrlSize = 200
self.sizeApplied = False
self.hideBtn.clicked.connect(self.hideBtnClicked)
self.ui.splitter.splitterMoved.connect(self.splitterMoved)
self.ui.itemList.itemChanged.connect(self.treeItemChanged)
self.ui.itemList.sigItemMoved.connect(self.treeItemMoved)
self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected)
self.ui.autoRangeBtn.clicked.connect(self.autoRange)
self.ui.storeSvgBtn.clicked.connect(self.storeSvg)
self.ui.storePngBtn.clicked.connect(self.storePng)
self.ui.redirectCheck.toggled.connect(self.updateRedirect)
self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect)
self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged)
self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished)
self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked)
self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked)
self.resizeEvent()
if hideCtrl:
self.hideBtnClicked()
if name is not None:
self.registeredName = CanvasManager.instance().registerCanvas(self, name)
self.ui.redirectCombo.setHostName(self.registeredName)
def storeSvg(self):
self.ui.view.writeSvg()
def storePng(self):
self.ui.view.writeImage()
def splitterMoved(self):
self.resizeEvent()
def hideBtnClicked(self):
ctrlSize = self.ui.splitter.sizes()[1]
if ctrlSize == 0:
cs = self.ctrlSize
w = self.ui.splitter.size().width()
if cs > w:
cs = w - 20
self.ui.splitter.setSizes([w-cs, cs])
self.hideBtn.setText('>')
else:
self.ctrlSize = ctrlSize
self.ui.splitter.setSizes([100, 0])
self.hideBtn.setText('<')
self.resizeEvent()
def autoRange(self):
self.view.autoRange()
def resizeEvent(self, ev=None):
if ev is not None:
QtGui.QWidget.resizeEvent(self, ev)
self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0)
if not self.sizeApplied:
self.sizeApplied = True
s = min(self.width(), max(100, min(200, self.width()*0.25)))
s2 = self.width()-s
self.ui.splitter.setSizes([s2, s])
def updateRedirect(self, *args):
### Decide whether/where to redirect items and make it so
cname = str(self.ui.redirectCombo.currentText())
man = CanvasManager.instance()
if self.ui.redirectCheck.isChecked() and cname != '':
redirect = man.getCanvas(cname)
else:
redirect = None
if self.redirect is redirect:
return
self.redirect = redirect
if redirect is None:
self.reclaimItems()
else:
self.redirectItems(redirect)
def redirectItems(self, canvas):
for i in self.items:
if i is self.grid:
continue
li = i.listItem
parent = li.parent()
if parent is None:
tree = li.treeWidget()
if tree is None:
print "Skipping item", i, i.name
continue
tree.removeTopLevelItem(li)
else:
parent.removeChild(li)
canvas.addItem(i)
def reclaimItems(self):
items = self.items
#self.items = {'Grid': items['Grid']}
#del items['Grid']
self.items = [self.grid]
items.remove(self.grid)
for i in items:
i.canvas.removeItem(i)
self.addItem(i)
def treeItemChanged(self, item, col):
#gi = self.items.get(item.name, None)
#if gi is None:
#return
try:
citem = item.canvasItem
except AttributeError:
return
if item.checkState(0) == QtCore.Qt.Checked:
for i in range(item.childCount()):
item.child(i).setCheckState(0, QtCore.Qt.Checked)
citem.show()
else:
for i in range(item.childCount()):
item.child(i).setCheckState(0, QtCore.Qt.Unchecked)
citem.hide()
def treeItemSelected(self):
sel = self.selectedItems()
#sel = []
#for listItem in self.itemList.selectedItems():
#if hasattr(listItem, 'canvasItem') and listItem.canvasItem is not None:
#sel.append(listItem.canvasItem)
#sel = [self.items[item.name] for item in sel]
if len(sel) == 0:
#self.selectWidget.hide()
return
multi = len(sel) > 1
for i in self.items:
#i.ctrlWidget().hide()
## updated the selected state of every item
i.selectionChanged(i in sel, multi)
if len(sel)==1:
#item = sel[0]
#item.ctrlWidget().show()
self.multiSelectBox.hide()
self.ui.mirrorSelectionBtn.hide()
self.ui.resetTransformsBtn.hide()
elif len(sel) > 1:
self.showMultiSelectBox()
#if item.isMovable():
#self.selectBox.setPos(item.item.pos())
#self.selectBox.setSize(item.item.sceneBoundingRect().size())
#self.selectBox.show()
#else:
#self.selectBox.hide()
#self.emit(QtCore.SIGNAL('itemSelected'), self, item)
self.sigSelectionChanged.emit(self, sel)
def selectedItems(self):
"""
Return list of all selected canvasItems
"""
return [item.canvasItem for item in self.itemList.selectedItems() if item.canvasItem is not None]
#def selectedItem(self):
#sel = self.itemList.selectedItems()
#if sel is None or len(sel) < 1:
#return
#return self.items.get(sel[0].name, None)
def selectItem(self, item):
li = item.listItem
#li = self.getListItem(item.name())
#print "select", li
self.itemList.setCurrentItem(li)
def showMultiSelectBox(self):
## Get list of selected canvas items
items = self.selectedItems()
rect = self.view.itemBoundingRect(items[0].graphicsItem())
for i in items:
if not i.isMovable(): ## all items in selection must be movable
return
br = self.view.itemBoundingRect(i.graphicsItem())
rect = rect|br
self.multiSelectBox.blockSignals(True)
self.multiSelectBox.setPos([rect.x(), rect.y()])
self.multiSelectBox.setSize(rect.size())
self.multiSelectBox.setAngle(0)
self.multiSelectBox.blockSignals(False)
self.multiSelectBox.show()
self.ui.mirrorSelectionBtn.show()
self.ui.resetTransformsBtn.show()
#self.multiSelectBoxBase = self.multiSelectBox.getState().copy()
def mirrorSelectionClicked(self):
for ci in self.selectedItems():
ci.mirrorY()
self.showMultiSelectBox()
def resetTransformsClicked(self):
for i in self.selectedItems():
i.resetTransformClicked()
self.showMultiSelectBox()
def multiSelectBoxChanged(self):
self.multiSelectBoxMoved()
def multiSelectBoxChangeFinished(self):
for ci in self.selectedItems():
ci.applyTemporaryTransform()
ci.sigTransformChangeFinished.emit(ci)
def multiSelectBoxMoved(self):
transform = self.multiSelectBox.getGlobalTransform()
for ci in self.selectedItems():
ci.setTemporaryTransform(transform)
ci.sigTransformChanged.emit(ci)
def addGraphicsItem(self, item, **opts):
"""Add a new GraphicsItem to the scene at pos.
Common options are name, pos, scale, and z
"""
citem = CanvasItem(item, **opts)
self.addItem(citem)
return citem
def addGroup(self, name, **kargs):
group = GroupCanvasItem(name=name)
self.addItem(group, **kargs)
return group
def addItem(self, citem):
"""
Add an item to the canvas.
"""
## Check for redirections
if self.redirect is not None:
name = self.redirect.addItem(citem)
self.items.append(citem)
return name
if not self.allowTransforms:
citem.setMovable(False)
citem.sigTransformChanged.connect(self.itemTransformChanged)
citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished)
citem.sigVisibilityChanged.connect(self.itemVisibilityChanged)
## Determine name to use in the item list
name = citem.opts['name']
if name is None:
name = 'item'
newname = name
## If name already exists, append a number to the end
## NAH. Let items have the same name if they really want.
#c=0
#while newname in self.items:
#c += 1
#newname = name + '_%03d' %c
#name = newname
## find parent and add item to tree
#currentNode = self.itemList.invisibleRootItem()
insertLocation = 0
#print "Inserting node:", name
## determine parent list item where this item should be inserted
parent = citem.parentItem()
if parent in (None, self.view.childGroup):
parent = self.itemList.invisibleRootItem()
else:
parent = parent.listItem
## set Z value above all other siblings if none was specified
siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())]
z = citem.zValue()
if z is None:
zvals = [i.zValue() for i in siblings]
if len(zvals) == 0:
z = 0
else:
z = max(zvals)+10
citem.setZValue(z)
## determine location to insert item relative to its siblings
for i in range(parent.childCount()):
ch = parent.child(i)
zval = ch.canvasItem.graphicsItem().zValue() ## should we use CanvasItem.zValue here?
if zval < z:
insertLocation = i
break
else:
insertLocation = i+1
node = QtGui.QTreeWidgetItem([name])
flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled
if not isinstance(citem, GroupCanvasItem):
flags = flags & ~QtCore.Qt.ItemIsDropEnabled
node.setFlags(flags)
if citem.opts['visible']:
node.setCheckState(0, QtCore.Qt.Checked)
else:
node.setCheckState(0, QtCore.Qt.Unchecked)
node.name = name
#if citem.opts['parent'] != None:
## insertLocation is incorrect in this case
parent.insertChild(insertLocation, node)
#else:
#root.insertChild(insertLocation, node)
citem.name = name
citem.listItem = node
node.canvasItem = citem
self.items.append(citem)
ctrl = citem.ctrlWidget()
ctrl.hide()
self.ui.ctrlLayout.addWidget(ctrl)
## inform the canvasItem that its parent canvas has changed
citem.setCanvas(self)
## Autoscale to fit the first item added (not including the grid).
if len(self.items) == 2:
self.autoRange()
#for n in name:
#nextnode = None
#for x in range(currentNode.childCount()):
#ch = currentNode.child(x)
#if hasattr(ch, 'name'): ## check Z-value of current item to determine insert location
#zval = ch.canvasItem.zValue()
#if zval > z:
###print " ->", x
#insertLocation = x+1
#if n == ch.text(0):
#nextnode = ch
#break
#if nextnode is None: ## If name doesn't exist, create it
#nextnode = QtGui.QTreeWidgetItem([n])
#nextnode.setFlags((nextnode.flags() | QtCore.Qt.ItemIsUserCheckable) & ~QtCore.Qt.ItemIsDropEnabled)
#nextnode.setCheckState(0, QtCore.Qt.Checked)
### Add node to correct position in list by Z-value
###print " ==>", insertLocation
#currentNode.insertChild(insertLocation, nextnode)
#if n == name[-1]: ## This is the leaf; add some extra properties.
#nextnode.name = name
#if n == name[0]: ## This is the root; make the item movable
#nextnode.setFlags(nextnode.flags() | QtCore.Qt.ItemIsDragEnabled)
#else:
#nextnode.setFlags(nextnode.flags() & ~QtCore.Qt.ItemIsDragEnabled)
#currentNode = nextnode
return citem
def treeItemMoved(self, item, parent, index):
##Item moved in tree; update Z values
if parent is self.itemList.invisibleRootItem():
item.canvasItem.setParentItem(self.view.childGroup)
else:
item.canvasItem.setParentItem(parent.canvasItem)
siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())]
zvals = [i.zValue() for i in siblings]
zvals.sort(reverse=True)
for i in range(len(siblings)):
item = siblings[i]
item.setZValue(zvals[i])
#item = self.itemList.topLevelItem(i)
##ci = self.items[item.name]
#ci = item.canvasItem
#if ci is None:
#continue
#if ci.zValue() != zvals[i]:
#ci.setZValue(zvals[i])
#if self.itemList.topLevelItemCount() < 2:
#return
#name = item.name
#gi = self.items[name]
#if index == 0:
#next = self.itemList.topLevelItem(1)
#z = self.items[next.name].zValue()+1
#else:
#prev = self.itemList.topLevelItem(index-1)
#z = self.items[prev.name].zValue()-1
#gi.setZValue(z)
def itemVisibilityChanged(self, item):
listItem = item.listItem
checked = listItem.checkState(0) == QtCore.Qt.Checked
vis = item.isVisible()
if vis != checked:
if vis:
listItem.setCheckState(0, QtCore.Qt.Checked)
else:
listItem.setCheckState(0, QtCore.Qt.Unchecked)
def removeItem(self, item):
if isinstance(item, CanvasItem):
item.setCanvas(None)
#self.view.scene().removeItem(item.item)
self.itemList.removeTopLevelItem(item.listItem)
#del self.items[item.name]
self.items.remove(item)
else:
self.view.removeItem(item)
## disconnect signals, remove from list, etc..
def addToScene(self, item):
self.view.addItem(item)
def removeFromScene(self, item):
self.view.removeItem(item)
def listItems(self):
"""Return a dictionary of name:item pairs"""
return self.items
def getListItem(self, name):
return self.items[name]
#def scene(self):
#return self.view.scene()
def itemTransformChanged(self, item):
#self.emit(QtCore.SIGNAL('itemTransformChanged'), self, item)
self.sigItemTransformChanged.emit(self, item)
def itemTransformChangeFinished(self, item):
#self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item)
self.sigItemTransformChangeFinished.emit(self, item)
class SelectBox(ROI):
def __init__(self, scalable=False):
#QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
ROI.__init__(self, [0,0], [1,1])
center = [0.5, 0.5]
if scalable:
self.addScaleHandle([1, 1], center, lockAspect=True)
self.addScaleHandle([0, 0], center, lockAspect=True)
self.addRotateHandle([0, 1], center)
self.addRotateHandle([1, 0], center)

490
canvas/CanvasItem.py Normal file
View File

@ -0,0 +1,490 @@
# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
from pyqtgraph.graphicsItems.ROI import ROI
import pyqtgraph as pg
import TransformGuiTemplate
import debug
class SelectBox(ROI):
def __init__(self, scalable=False):
#QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
ROI.__init__(self, [0,0], [1,1], invertible=True)
center = [0.5, 0.5]
if scalable:
self.addScaleHandle([1, 1], center, lockAspect=True)
self.addScaleHandle([0, 0], center, lockAspect=True)
self.addRotateHandle([0, 1], center)
self.addRotateHandle([1, 0], center)
class CanvasItem(QtCore.QObject):
sigResetUserTransform = QtCore.Signal(object)
sigTransformChangeFinished = QtCore.Signal(object)
sigTransformChanged = QtCore.Signal(object)
"""CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and
provides a control widget"""
sigVisibilityChanged = QtCore.Signal(object)
transformCopyBuffer = None
def __init__(self, item, **opts):
defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0,
defOpts.update(opts)
self.opts = defOpts
self.selectedAlone = False ## whether this item is the only one selected
QtCore.QObject.__init__(self)
self.canvas = None
self._graphicsItem = item
parent = self.opts['parent']
if parent is not None:
self._graphicsItem.setParentItem(parent.graphicsItem())
self._parentItem = parent
else:
self._parentItem = None
z = self.opts['z']
if z is not None:
item.setZValue(z)
self.ctrl = QtGui.QWidget()
self.layout = QtGui.QGridLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0,0,0,0)
self.ctrl.setLayout(self.layout)
self.alphaLabel = QtGui.QLabel("Alpha")
self.alphaSlider = QtGui.QSlider()
self.alphaSlider.setMaximum(1023)
self.alphaSlider.setOrientation(QtCore.Qt.Horizontal)
self.alphaSlider.setValue(1023)
self.layout.addWidget(self.alphaLabel, 0, 0)
self.layout.addWidget(self.alphaSlider, 0, 1)
self.resetTransformBtn = QtGui.QPushButton('Reset Transform')
self.copyBtn = QtGui.QPushButton('Copy')
self.pasteBtn = QtGui.QPushButton('Paste')
self.transformWidget = QtGui.QWidget()
self.transformGui = TransformGuiTemplate.Ui_Form()
self.transformGui.setupUi(self.transformWidget)
self.layout.addWidget(self.transformWidget, 3, 0, 1, 2)
self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY)
self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2)
self.layout.addWidget(self.copyBtn, 2, 0, 1, 1)
self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1)
self.alphaSlider.valueChanged.connect(self.alphaChanged)
self.alphaSlider.sliderPressed.connect(self.alphaPressed)
self.alphaSlider.sliderReleased.connect(self.alphaReleased)
#self.canvas.sigSelectionChanged.connect(self.selectionChanged)
self.resetTransformBtn.clicked.connect(self.resetTransformClicked)
self.copyBtn.clicked.connect(self.copyClicked)
self.pasteBtn.clicked.connect(self.pasteClicked)
self.setMovable(self.opts['movable']) ## update gui to reflect this option
if 'transform' in self.opts:
self.baseTransform = self.opts['transform']
else:
self.baseTransform = pg.Transform()
if 'pos' in self.opts and self.opts['pos'] is not None:
self.baseTransform.translate(self.opts['pos'])
if 'angle' in self.opts and self.opts['angle'] is not None:
self.baseTransform.rotate(self.opts['angle'])
if 'scale' in self.opts and self.opts['scale'] is not None:
self.baseTransform.scale(self.opts['scale'])
## create selection box (only visible when selected)
tr = self.baseTransform.saveState()
if 'scalable' not in opts and tr['scale'] == (1,1):
self.opts['scalable'] = True
## every CanvasItem implements its own individual selection box
## so that subclasses are free to make their own.
self.selectBox = SelectBox(scalable=self.opts['scalable'])
#self.canvas.scene().addItem(self.selectBox)
self.selectBox.hide()
self.selectBox.setZValue(1e6)
self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved
self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished)
## set up the transformations that will be applied to the item
## (It is not safe to use item.setTransform, since the item might count on that not changing)
self.itemRotation = QtGui.QGraphicsRotation()
self.itemScale = QtGui.QGraphicsScale()
self._graphicsItem.setTransformations([self.itemRotation, self.itemScale])
self.tempTransform = pg.Transform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done.
self.userTransform = pg.Transform() ## stores the total transform of the object
self.resetUserTransform()
## now happens inside resetUserTransform -> selectBoxToItem
# self.selectBoxBase = self.selectBox.getState().copy()
#print "Created canvas item", self
#print " base:", self.baseTransform
#print " user:", self.userTransform
#print " temp:", self.tempTransform
#print " bounds:", self.item.sceneBoundingRect()
def setMovable(self, m):
self.opts['movable'] = m
if m:
self.resetTransformBtn.show()
self.copyBtn.show()
self.pasteBtn.show()
else:
self.resetTransformBtn.hide()
self.copyBtn.hide()
self.pasteBtn.hide()
def setCanvas(self, canvas):
## Called by canvas whenever the item is added.
## It is our responsibility to add all graphicsItems to the canvas's scene
## The canvas will automatically add our graphicsitem,
## so we just need to take care of the selectbox.
if canvas is self.canvas:
return
if canvas is None:
self.canvas.removeFromScene(self._graphicsItem)
self.canvas.removeFromScene(self.selectBox)
else:
canvas.addToScene(self._graphicsItem)
canvas.addToScene(self.selectBox)
self.canvas = canvas
def graphicsItem(self):
"""Return the graphicsItem for this canvasItem."""
return self._graphicsItem
def parentItem(self):
return self._parentItem
def setParentItem(self, parent):
self._parentItem = parent
if parent is not None:
if isinstance(parent, CanvasItem):
parent = parent.graphicsItem()
self.graphicsItem().setParentItem(parent)
#def name(self):
#return self.opts['name']
def copyClicked(self):
CanvasItem.transformCopyBuffer = self.saveTransform()
def pasteClicked(self):
t = CanvasItem.transformCopyBuffer
if t is None:
return
else:
self.restoreTransform(t)
def mirrorY(self):
if not self.isMovable():
return
#flip = self.transformGui.mirrorImageCheck.isChecked()
#tr = self.userTransform.saveState()
inv = pg.Transform()
inv.scale(-1, 1)
self.userTransform = self.userTransform * inv
self.updateTransform()
self.selectBoxFromUser()
#if flip:
#if tr['scale'][0] < 0 xor tr['scale'][1] < 0:
#return
#else:
#self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]])
#self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]])
#self.userTransform.setRotate(-tr['angle'])
#self.updateTransform()
#self.selectBoxFromUser()
#return
#elif not flip:
#if tr['scale'][0] > 0 and tr['scale'][1] > 0:
#return
#else:
#self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]])
#self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]])
#self.userTransform.setRotate(-tr['angle'])
#self.updateTransform()
#self.selectBoxFromUser()
#return
def hasUserTransform(self):
#print self.userRotate, self.userTranslate
return not self.userTransform.isIdentity()
def ctrlWidget(self):
return self.ctrl
def alphaChanged(self, val):
alpha = val / 1023.
self._graphicsItem.setOpacity(alpha)
def isMovable(self):
return self.opts['movable']
def selectBoxMoved(self):
"""The selection box has moved; get its transformation information and pass to the graphics item"""
self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase)
self.updateTransform()
def scale(self, x, y):
self.userTransform.scale(x, y)
self.selectBoxFromUser()
self.updateTransform()
def rotate(self, ang):
self.userTransform.rotate(ang)
self.selectBoxFromUser()
self.updateTransform()
def translate(self, x, y):
self.userTransform.translate(x, y)
self.selectBoxFromUser()
self.updateTransform()
def setTranslate(self, x, y):
self.userTransform.setTranslate(x, y)
self.selectBoxFromUser()
self.updateTransform()
def setRotate(self, angle):
self.userTransform.setRotate(angle)
self.selectBoxFromUser()
self.updateTransform()
def setScale(self, x, y):
self.userTransform.setScale(x, y)
self.selectBoxFromUser()
self.updateTransform()
def setTemporaryTransform(self, transform):
self.tempTransform = transform
self.updateTransform()
def applyTemporaryTransform(self):
"""Collapses tempTransform into UserTransform, resets tempTransform"""
self.userTransform = self.userTransform * self.tempTransform ## order is important!
self.resetTemporaryTransform()
self.selectBoxFromUser() ## update the selection box to match the new userTransform
#st = self.userTransform.saveState()
#self.userTransform = self.userTransform * self.tempTransform ## order is important!
#### matrix multiplication affects the scale factors, need to reset
#if st['scale'][0] < 0 or st['scale'][1] < 0:
#nst = self.userTransform.saveState()
#self.userTransform.setScale([-nst['scale'][0], -nst['scale'][1]])
#self.resetTemporaryTransform()
#self.selectBoxFromUser()
#self.selectBoxChangeFinished()
def resetTemporaryTransform(self):
self.tempTransform = pg.Transform() ## don't use Transform.reset()--this transform might be used elsewhere.
self.updateTransform()
def transform(self):
return self._graphicsItem.transform()
def updateTransform(self):
"""Regenerate the item position from the base, user, and temp transforms"""
transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important
s = transform.saveState()
self._graphicsItem.setPos(*s['pos'])
self.itemRotation.setAngle(s['angle'])
self.itemScale.setXScale(s['scale'][0])
self.itemScale.setYScale(s['scale'][1])
self.displayTransform(transform)
def displayTransform(self, transform):
"""Updates transform numbers in the ctrl widget."""
tr = transform.saveState()
self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1]))
self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle'])
self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1]))
#self.transformGui.mirrorImageCheck.setChecked(False)
#if tr['scale'][0] < 0:
# self.transformGui.mirrorImageCheck.setChecked(True)
def resetUserTransform(self):
#self.userRotate = 0
#self.userTranslate = pg.Point(0,0)
self.userTransform.reset()
self.updateTransform()
self.selectBox.blockSignals(True)
self.selectBoxToItem()
self.selectBox.blockSignals(False)
self.sigTransformChanged.emit(self)
self.sigTransformChangeFinished.emit(self)
def resetTransformClicked(self):
self.resetUserTransform()
self.sigResetUserTransform.emit(self)
def restoreTransform(self, tr):
try:
#self.userTranslate = pg.Point(tr['trans'])
#self.userRotate = tr['rot']
self.userTransform = pg.Transform(tr)
self.updateTransform()
self.selectBoxFromUser() ## move select box to match
self.sigTransformChanged.emit(self)
self.sigTransformChangeFinished.emit(self)
except:
#self.userTranslate = pg.Point([0,0])
#self.userRotate = 0
self.userTransform = pg.Transform()
debug.printExc("Failed to load transform:")
#print "set transform", self, self.userTranslate
def saveTransform(self):
"""Return a dict containing the current user transform"""
#print "save transform", self, self.userTranslate
#return {'trans': list(self.userTranslate), 'rot': self.userRotate}
return self.userTransform.saveState()
def selectBoxFromUser(self):
"""Move the selection box to match the current userTransform"""
## user transform
#trans = QtGui.QTransform()
#trans.translate(*self.userTranslate)
#trans.rotate(-self.userRotate)
#x2, y2 = trans.map(*self.selectBoxBase['pos'])
self.selectBox.blockSignals(True)
self.selectBox.setState(self.selectBoxBase)
self.selectBox.applyGlobalTransform(self.userTransform)
#self.selectBox.setAngle(self.userRotate)
#self.selectBox.setPos([x2, y2])
self.selectBox.blockSignals(False)
def selectBoxToItem(self):
"""Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)"""
self.itemRect = self._graphicsItem.boundingRect()
rect = self._graphicsItem.mapRectToParent(self.itemRect)
self.selectBox.blockSignals(True)
self.selectBox.setPos([rect.x(), rect.y()])
self.selectBox.setSize(rect.size())
self.selectBox.setAngle(0)
self.selectBoxBase = self.selectBox.getState().copy()
self.selectBox.blockSignals(False)
def zValue(self):
return self.opts['z']
def setZValue(self, z):
self.opts['z'] = z
if z is not None:
self._graphicsItem.setZValue(z)
#def selectionChanged(self, canvas, items):
#self.selected = len(items) == 1 and (items[0] is self)
#self.showSelectBox()
def selectionChanged(self, sel, multi):
"""
Inform the item that its selection state has changed.
Arguments:
sel: bool, whether the item is currently selected
multi: bool, whether there are multiple items currently selected
"""
self.selectedAlone = sel and not multi
self.showSelectBox()
if self.selectedAlone:
self.ctrlWidget().show()
else:
self.ctrlWidget().hide()
def showSelectBox(self):
"""Display the selection box around this item if it is selected and movable"""
if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1:
self.selectBox.show()
else:
self.selectBox.hide()
def hideSelectBox(self):
self.selectBox.hide()
def selectBoxChanged(self):
self.selectBoxMoved()
#self.updateTransform(self.selectBox)
#self.emit(QtCore.SIGNAL('transformChanged'), self)
self.sigTransformChanged.emit(self)
def selectBoxChangeFinished(self):
#self.emit(QtCore.SIGNAL('transformChangeFinished'), self)
self.sigTransformChangeFinished.emit(self)
def alphaPressed(self):
"""Hide selection box while slider is moving"""
self.hideSelectBox()
def alphaReleased(self):
self.showSelectBox()
def show(self):
if self.opts['visible']:
return
self.opts['visible'] = True
self._graphicsItem.show()
self.showSelectBox()
self.sigVisibilityChanged.emit(self)
def hide(self):
if not self.opts['visible']:
return
self.opts['visible'] = False
self._graphicsItem.hide()
self.hideSelectBox()
self.sigVisibilityChanged.emit(self)
def setVisible(self, vis):
if vis:
self.show()
else:
self.hide()
def isVisible(self):
return self.opts['visible']
class GroupCanvasItem(CanvasItem):
"""
Canvas item used for grouping others
"""
def __init__(self, **opts):
defOpts = {'movable': False, 'scalable': False}
defOpts.update(opts)
item = pg.ItemGroup()
CanvasItem.__init__(self, item, **defOpts)

76
canvas/CanvasManager.py Normal file
View File

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtCore, QtGui
if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal
import weakref
class CanvasManager(QtCore.QObject):
SINGLETON = None
sigCanvasListChanged = QtCore.Signal()
def __init__(self):
if CanvasManager.SINGLETON is not None:
raise Exception("Can only create one canvas manager.")
CanvasManager.SINGLETON = self
QtCore.QObject.__init__(self)
self.canvases = weakref.WeakValueDictionary()
@classmethod
def instance(cls):
return CanvasManager.SINGLETON
def registerCanvas(self, canvas, name):
n2 = name
i = 0
while n2 in self.canvases:
n2 = "%s_%03d" % (name, i)
i += 1
self.canvases[n2] = canvas
self.sigCanvasListChanged.emit()
return n2
def unregisterCanvas(self, name):
c = self.canvases[name]
del self.canvases[name]
self.sigCanvasListChanged.emit()
def listCanvases(self):
return self.canvases.keys()
def getCanvas(self, name):
return self.canvases[name]
manager = CanvasManager()
class CanvasCombo(QtGui.QComboBox):
def __init__(self, parent=None):
QtGui.QComboBox.__init__(self, parent)
man = CanvasManager.instance()
man.sigCanvasListChanged.connect(self.updateCanvasList)
self.hostName = None
self.updateCanvasList()
def updateCanvasList(self):
canvases = CanvasManager.instance().listCanvases()
canvases.insert(0, "")
if self.hostName in canvases:
canvases.remove(self.hostName)
sel = self.currentText()
if sel in canvases:
self.blockSignals(True) ## change does not affect current selection; block signals during update
self.clear()
for i in canvases:
self.addItem(i)
if i == sel:
self.setCurrentIndex(self.count())
self.blockSignals(False)
def setHostName(self, name):
self.hostName = name
self.updateCanvasList()

96
canvas/CanvasTemplate.py Normal file
View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'CanvasTemplate.ui'
#
# Created: Sun Dec 18 20:04:41 2011
# by: PyQt4 UI code generator 4.8.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form"))
Form.resize(466, 422)
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setMargin(0)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.splitter = QtGui.QSplitter(Form)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName(_fromUtf8("splitter"))
self.view = GraphicsView(self.splitter)
self.view.setObjectName(_fromUtf8("view"))
self.layoutWidget = QtGui.QWidget(self.splitter)
self.layoutWidget.setObjectName(_fromUtf8("layoutWidget"))
self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget)
self.gridLayout_2.setMargin(0)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth())
self.autoRangeBtn.setSizePolicy(sizePolicy)
self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn"))
self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2)
self.itemList = TreeWidget(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(100)
sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth())
self.itemList.setSizePolicy(sizePolicy)
self.itemList.setHeaderHidden(True)
self.itemList.setObjectName(_fromUtf8("itemList"))
self.itemList.headerItem().setText(0, _fromUtf8("1"))
self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2)
self.ctrlLayout = QtGui.QGridLayout()
self.ctrlLayout.setSpacing(0)
self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout"))
self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2)
self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget)
self.storeSvgBtn.setObjectName(_fromUtf8("storeSvgBtn"))
self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1)
self.storePngBtn = QtGui.QPushButton(self.layoutWidget)
self.storePngBtn.setObjectName(_fromUtf8("storePngBtn"))
self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.redirectCheck = QtGui.QCheckBox(self.layoutWidget)
self.redirectCheck.setObjectName(_fromUtf8("redirectCheck"))
self.horizontalLayout.addWidget(self.redirectCheck)
self.redirectCombo = CanvasCombo(self.layoutWidget)
self.redirectCombo.setObjectName(_fromUtf8("redirectCombo"))
self.horizontalLayout.addWidget(self.redirectCombo)
self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2)
self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget)
self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn"))
self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 8, 0, 1, 1)
self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget)
self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn"))
self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 1, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8))
self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8))
self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8))
self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8))
self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.widgets.GraphicsView import GraphicsView
from CanvasManager import CanvasCombo
from pyqtgraph.widgets.TreeWidget import TreeWidget

142
canvas/CanvasTemplate.ui Normal file
View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>466</width>
<height>422</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="GraphicsView" name="view"/>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="autoRangeBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Auto Range</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="TreeWidget" name="itemList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>100</verstretch>
</sizepolicy>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item row="10" column="0" colspan="2">
<layout class="QGridLayout" name="ctrlLayout">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="storeSvgBtn">
<property name="text">
<string>Store SVG</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="storePngBtn">
<property name="text">
<string>Store PNG</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="redirectCheck">
<property name="toolTip">
<string>Check to display all local items in a remote canvas.</string>
</property>
<property name="text">
<string>Redirect</string>
</property>
</widget>
</item>
<item>
<widget class="CanvasCombo" name="redirectCombo"/>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="mirrorSelectionBtn">
<property name="text">
<string>Mirror Selection</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="resetTransformsBtn">
<property name="text">
<string>Reset Transforms</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TreeWidget</class>
<extends>QTreeWidget</extends>
<header>pyqtgraph.widgets.TreeWidget</header>
</customwidget>
<customwidget>
<class>GraphicsView</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph.widgets.GraphicsView</header>
</customwidget>
<customwidget>
<class>CanvasCombo</class>
<extends>QComboBox</extends>
<header>CanvasManager</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'TransformGuiTemplate.ui'
#
# Created: Sun Dec 18 20:04:40 2011
# by: PyQt4 UI code generator 4.8.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form"))
Form.resize(169, 82)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth())
Form.setSizePolicy(sizePolicy)
self.verticalLayout = QtGui.QVBoxLayout(Form)
self.verticalLayout.setSpacing(1)
self.verticalLayout.setMargin(0)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.translateLabel = QtGui.QLabel(Form)
self.translateLabel.setObjectName(_fromUtf8("translateLabel"))
self.verticalLayout.addWidget(self.translateLabel)
self.rotateLabel = QtGui.QLabel(Form)
self.rotateLabel.setObjectName(_fromUtf8("rotateLabel"))
self.verticalLayout.addWidget(self.rotateLabel)
self.scaleLabel = QtGui.QLabel(Form)
self.scaleLabel.setObjectName(_fromUtf8("scaleLabel"))
self.verticalLayout.addWidget(self.scaleLabel)
self.mirrorImageBtn = QtGui.QPushButton(Form)
self.mirrorImageBtn.setToolTip(_fromUtf8(""))
self.mirrorImageBtn.setObjectName(_fromUtf8("mirrorImageBtn"))
self.verticalLayout.addWidget(self.mirrorImageBtn)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8))
self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8))
self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8))
self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>169</width>
<height>82</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>1</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="translateLabel">
<property name="text">
<string>Translate:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rotateLabel">
<property name="text">
<string>Rotate:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scaleLabel">
<property name="text">
<string>Scale:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mirrorImageBtn">
<property name="toolTip">
<string extracomment="Mirror the item across the global Y axis"/>
</property>
<property name="text">
<string>Mirror</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

3
canvas/__init__.py Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from Canvas import *
from CanvasItem import *

View File

@ -8,7 +8,7 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile
import ptime
from numpy import ndarray
from PyQt4 import QtCore, QtGui
from Qt import QtCore, QtGui
__ftraceDepth = 0
def ftrace(func):

BIN
debug.pyc Normal file

Binary file not shown.

267
dockarea/Container.py Normal file
View File

@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtCore, QtGui
import weakref
class Container(object):
#sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject.
def __init__(self, area):
object.__init__(self)
self.area = area
self._container = None
self._stretch = (10, 10)
self.stretches = weakref.WeakKeyDictionary()
def container(self):
return self._container
def containerChanged(self, c):
self._container = c
def type(self):
return None
def insert(self, new, pos=None, neighbor=None):
if not isinstance(new, list):
new = [new]
if neighbor is None:
if pos == 'before':
index = 0
else:
index = self.count()
else:
index = self.indexOf(neighbor)
if index == -1:
index = 0
if pos == 'after':
index += 1
for n in new:
#print "change container", n, " -> ", self
n.containerChanged(self)
#print "insert", n, " -> ", self, index
self._insertItem(n, index)
index += 1
n.sigStretchChanged.connect(self.childStretchChanged)
#print "child added", self
self.updateStretch()
def apoptose(self, propagate=True):
##if there is only one (or zero) item in this container, disappear.
cont = self._container
c = self.count()
if c > 1:
return
if self.count() == 1: ## if there is one item, give it to the parent container (unless this is the top)
if self is self.area.topContainer:
return
self.container().insert(self.widget(0), 'before', self)
#print "apoptose:", self
self.close()
if propagate and cont is not None:
cont.apoptose()
def close(self):
self.area = None
self._container = None
self.setParent(None)
def childEvent(self, ev):
ch = ev.child()
if ev.removed() and hasattr(ch, 'sigStretchChanged'):
#print "Child", ev.child(), "removed, updating", self
try:
ch.sigStretchChanged.disconnect(self.childStretchChanged)
except:
pass
self.updateStretch()
def childStretchChanged(self):
#print "child", QtCore.QObject.sender(self), "changed shape, updating", self
self.updateStretch()
def setStretch(self, x=None, y=None):
#print "setStretch", self, x, y
self._stretch = (x, y)
self.sigStretchChanged.emit()
def updateStretch(self):
###Set the stretch values for this container to reflect its contents
pass
def stretch(self):
"""Return the stretch factors for this container"""
return self._stretch
class SplitContainer(Container, QtGui.QSplitter):
"""Horizontal or vertical splitter with some changes:
- save/restore works correctly
"""
sigStretchChanged = QtCore.Signal()
def __init__(self, area, orientation):
QtGui.QSplitter.__init__(self)
self.setOrientation(orientation)
Container.__init__(self, area)
#self.splitterMoved.connect(self.restretchChildren)
def _insertItem(self, item, index):
self.insertWidget(index, item)
item.show() ## need to show since it may have been previously hidden by tab
def saveState(self):
sizes = self.sizes()
if all([x == 0 for x in sizes]):
sizes = [10] * len(sizes)
return {'sizes': sizes}
def restoreState(self, state):
sizes = state['sizes']
self.setSizes(sizes)
for i in range(len(sizes)):
self.setStretchFactor(i, sizes[i])
def childEvent(self, ev):
QtGui.QSplitter.childEvent(self, ev)
Container.childEvent(self, ev)
#def restretchChildren(self):
#sizes = self.sizes()
#tot = sum(sizes)
class HContainer(SplitContainer):
def __init__(self, area):
SplitContainer.__init__(self, area, QtCore.Qt.Horizontal)
def type(self):
return 'horizontal'
def updateStretch(self):
##Set the stretch values for this container to reflect its contents
#print "updateStretch", self
x = 0
y = 0
sizes = []
for i in range(self.count()):
wx, wy = self.widget(i).stretch()
x += wx
y = max(y, wy)
sizes.append(wx)
#print " child", self.widget(i), wx, wy
self.setStretch(x, y)
#print sizes
tot = float(sum(sizes))
if tot == 0:
scale = 1.0
else:
scale = self.width() / tot
self.setSizes([int(s*scale) for s in sizes])
class VContainer(SplitContainer):
def __init__(self, area):
SplitContainer.__init__(self, area, QtCore.Qt.Vertical)
def type(self):
return 'vertical'
def updateStretch(self):
##Set the stretch values for this container to reflect its contents
#print "updateStretch", self
x = 0
y = 0
sizes = []
for i in range(self.count()):
wx, wy = self.widget(i).stretch()
y += wy
x = max(x, wx)
sizes.append(wy)
#print " child", self.widget(i), wx, wy
self.setStretch(x, y)
#print sizes
tot = float(sum(sizes))
if tot == 0:
scale = 1.0
else:
scale = self.height() / tot
self.setSizes([int(s*scale) for s in sizes])
class TContainer(Container, QtGui.QWidget):
sigStretchChanged = QtCore.Signal()
def __init__(self, area):
QtGui.QWidget.__init__(self)
Container.__init__(self, area)
self.layout = QtGui.QGridLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0,0,0,0)
self.setLayout(self.layout)
self.hTabLayout = QtGui.QHBoxLayout()
self.hTabBox = QtGui.QWidget()
self.hTabBox.setLayout(self.hTabLayout)
self.hTabLayout.setSpacing(2)
self.hTabLayout.setContentsMargins(0,0,0,0)
self.layout.addWidget(self.hTabBox, 0, 1)
self.stack = QtGui.QStackedWidget()
self.layout.addWidget(self.stack, 1, 1)
self.stack.childEvent = self.stackChildEvent
self.setLayout(self.layout)
for n in ['count', 'widget', 'indexOf']:
setattr(self, n, getattr(self.stack, n))
def _insertItem(self, item, index):
if not isinstance(item, Dock.Dock):
raise Exception("Tab containers may hold only docks, not other containers.")
self.stack.insertWidget(index, item)
self.hTabLayout.insertWidget(index, item.label)
#QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked)
item.label.sigClicked.connect(self.tabClicked)
self.tabClicked(item.label)
def tabClicked(self, tab, ev=None):
if ev is None or ev.button() == QtCore.Qt.LeftButton:
for i in range(self.count()):
w = self.widget(i)
if w is tab.dock:
w.label.setDim(False)
self.stack.setCurrentIndex(i)
else:
w.label.setDim(True)
def type(self):
return 'tab'
def saveState(self):
return {'index': self.stack.currentIndex()}
def restoreState(self, state):
self.stack.setCurrentIndex(state['index'])
def updateStretch(self):
##Set the stretch values for this container to reflect its contents
x = 0
y = 0
for i in range(self.count()):
wx, wy = self.widget(i).stretch()
x = max(x, wx)
y = max(y, wy)
self.setStretch(x, y)
def stackChildEvent(self, ev):
QtGui.QStackedWidget.childEvent(self.stack, ev)
Container.childEvent(self, ev)
import Dock

350
dockarea/Dock.py Normal file
View File

@ -0,0 +1,350 @@
from pyqtgraph.Qt import QtCore, QtGui
from DockDrop import *
from pyqtgraph.widgets.VerticalLabel import VerticalLabel
class Dock(QtGui.QWidget, DockDrop):
sigStretchChanged = QtCore.Signal()
def __init__(self, name, area=None, size=(10, 10)):
QtGui.QWidget.__init__(self)
DockDrop.__init__(self)
self.area = area
self.label = DockLabel(name, self)
self.labelHidden = False
self.moveLabel = True ## If false, the dock is no longer allowed to move the label.
self.autoOrient = True
self.orientation = 'horizontal'
#self.label.setAlignment(QtCore.Qt.AlignHCenter)
self.topLayout = QtGui.QGridLayout()
self.topLayout.setContentsMargins(0, 0, 0, 0)
self.topLayout.setSpacing(0)
self.setLayout(self.topLayout)
self.topLayout.addWidget(self.label, 0, 1)
self.widgetArea = QtGui.QWidget()
self.topLayout.addWidget(self.widgetArea, 1, 1)
self.layout = QtGui.QGridLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.widgetArea.setLayout(self.layout)
self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.widgets = []
self.currentRow = 0
#self.titlePos = 'top'
self.raiseOverlay()
self.hStyle = """
Dock > QWidget {
border: 1px solid #000;
border-radius: 5px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-top-width: 0px;
}"""
self.vStyle = """
Dock > QWidget {
border: 1px solid #000;
border-radius: 5px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-left-width: 0px;
}"""
self.nStyle = """
Dock > QWidget {
border: 1px solid #000;
border-radius: 5px;
}"""
self.dragStyle = """
Dock > QWidget {
border: 4px solid #00F;
border-radius: 5px;
}"""
self.setAutoFillBackground(False)
self.widgetArea.setStyleSheet(self.hStyle)
self.setStretch(*size)
def setStretch(self, x=None, y=None):
#print "setStretch", self, x, y
#self._stretch = (x, y)
if x is None:
x = 0
if y is None:
y = 0
#policy = self.sizePolicy()
#policy.setHorizontalStretch(x)
#policy.setVerticalStretch(y)
#self.setSizePolicy(policy)
self._stretch = (x, y)
self.sigStretchChanged.emit()
#print "setStretch", self, x, y, self.stretch()
def stretch(self):
#policy = self.sizePolicy()
#return policy.horizontalStretch(), policy.verticalStretch()
return self._stretch
#def stretch(self):
#return self._stretch
def hideTitleBar(self):
self.label.hide()
self.labelHidden = True
if 'center' in self.allowedAreas:
self.allowedAreas.remove('center')
self.updateStyle()
def showTitleBar(self):
self.label.show()
self.labelHidden = False
self.allowedAreas.add('center')
self.updateStyle()
def setOrientation(self, o='auto', force=False):
#print self.name(), "setOrientation", o, force
if o == 'auto':
if self.container().type() == 'tab':
o = 'horizontal'
elif self.width() > self.height()*1.5:
o = 'vertical'
else:
o = 'horizontal'
if force or self.orientation != o:
self.orientation = o
self.label.setOrientation(o)
self.updateStyle()
def updateStyle(self):
#print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible()
if self.labelHidden:
self.widgetArea.setStyleSheet(self.nStyle)
elif self.orientation == 'vertical':
self.label.setOrientation('vertical')
if self.moveLabel:
#print self.name(), "reclaim label"
self.topLayout.addWidget(self.label, 1, 0)
self.widgetArea.setStyleSheet(self.vStyle)
else:
self.label.setOrientation('horizontal')
if self.moveLabel:
#print self.name(), "reclaim label"
self.topLayout.addWidget(self.label, 0, 1)
self.widgetArea.setStyleSheet(self.hStyle)
def resizeEvent(self, ev):
self.setOrientation()
self.resizeOverlay(self.size())
def name(self):
return str(self.label.text())
def container(self):
return self._container
def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1):
if row is None:
row = self.currentRow
self.currentRow = max(row+1, self.currentRow)
self.widgets.append(widget)
self.layout.addWidget(widget, row, col, rowspan, colspan)
self.raiseOverlay()
def startDrag(self):
self.drag = QtGui.QDrag(self)
mime = QtCore.QMimeData()
#mime.setPlainText("asd")
self.drag.setMimeData(mime)
self.widgetArea.setStyleSheet(self.dragStyle)
self.update()
action = self.drag.exec_()
self.updateStyle()
def float(self):
self.area.floatDock(self)
def containerChanged(self, c):
#print self.name(), "container changed"
self._container = c
if c.type() != 'tab':
self.moveLabel = True
self.label.setDim(False)
else:
self.moveLabel = False
self.setOrientation(force=True)
def __repr__(self):
return "<Dock %s %s>" % (self.name(), self.stretch())
class DockLabel(VerticalLabel):
sigClicked = QtCore.Signal(object, object)
def __init__(self, text, dock):
self.dim = False
self.fixedWidth = False
VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False)
self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter)
self.dock = dock
self.updateStyle()
self.setAutoFillBackground(False)
#def minimumSizeHint(self):
##sh = QtGui.QWidget.minimumSizeHint(self)
#return QtCore.QSize(20, 20)
def updateStyle(self):
r = '3px'
if self.dim:
fg = '#aaa'
bg = '#44a'
border = '#339'
else:
fg = '#fff'
bg = '#66c'
border = '#55B'
if self.orientation == 'vertical':
self.vStyle = """DockLabel {
background-color : %s;
color : %s;
border-top-right-radius: 0px;
border-top-left-radius: %s;
border-bottom-right-radius: 0px;
border-bottom-left-radius: %s;
border-width: 0px;
border-right: 2px solid %s;
padding-top: 3px;
padding-bottom: 3px;
}""" % (bg, fg, r, r, border)
self.setStyleSheet(self.vStyle)
else:
self.hStyle = """DockLabel {
background-color : %s;
color : %s;
border-top-right-radius: %s;
border-top-left-radius: %s;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
border-width: 0px;
border-bottom: 2px solid %s;
padding-left: 3px;
padding-right: 3px;
}""" % (bg, fg, r, r, border)
self.setStyleSheet(self.hStyle)
def setDim(self, d):
if self.dim != d:
self.dim = d
self.updateStyle()
def setOrientation(self, o):
VerticalLabel.setOrientation(self, o)
self.updateStyle()
def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
self.pressPos = ev.pos()
self.startedDrag = False
ev.accept()
def mouseMoveEvent(self, ev):
if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance():
self.dock.startDrag()
ev.accept()
#print ev.pos()
def mouseReleaseEvent(self, ev):
if not self.startedDrag:
#self.emit(QtCore.SIGNAL('clicked'), self, ev)
self.sigClicked.emit(self, ev)
ev.accept()
def mouseDoubleClickEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
self.dock.float()
#def paintEvent(self, ev):
#p = QtGui.QPainter(self)
##p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200)))
#p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100)))
#p.drawRect(self.rect().adjusted(0, 0, -1, -1))
#VerticalLabel.paintEvent(self, ev)
#class DockLabel(QtGui.QWidget):
#def __init__(self, text, dock):
#QtGui.QWidget.__init__(self)
#self._text = text
#self.dock = dock
#self.orientation = None
#self.setOrientation('horizontal')
#def text(self):
#return self._text
#def mousePressEvent(self, ev):
#if ev.button() == QtCore.Qt.LeftButton:
#self.pressPos = ev.pos()
#self.startedDrag = False
#ev.accept()
#def mouseMoveEvent(self, ev):
#if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance():
#self.dock.startDrag()
#ev.accept()
##print ev.pos()
#def mouseReleaseEvent(self, ev):
#ev.accept()
#def mouseDoubleClickEvent(self, ev):
#if ev.button() == QtCore.Qt.LeftButton:
#self.dock.float()
#def setOrientation(self, o):
#if self.orientation == o:
#return
#self.orientation = o
#self.update()
#self.updateGeometry()
#def paintEvent(self, ev):
#p = QtGui.QPainter(self)
#p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200)))
#p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100)))
#p.drawRect(self.rect().adjusted(0, 0, -1, -1))
#p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255)))
#if self.orientation == 'vertical':
#p.rotate(-90)
#rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width())
#else:
#rgn = self.rect()
#align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter
#self.hint = p.drawText(rgn, align, self.text())
#p.end()
#if self.orientation == 'vertical':
#self.setMaximumWidth(self.hint.height())
#self.setMaximumHeight(16777215)
#else:
#self.setMaximumHeight(self.hint.height())
#self.setMaximumWidth(16777215)
#def sizeHint(self):
#if self.orientation == 'vertical':
#if hasattr(self, 'hint'):
#return QtCore.QSize(self.hint.height(), self.hint.width())
#else:
#return QtCore.QSize(19, 50)
#else:
#if hasattr(self, 'hint'):
#return QtCore.QSize(self.hint.width(), self.hint.height())
#else:
#return QtCore.QSize(50, 19)

267
dockarea/DockArea.py Normal file
View File

@ -0,0 +1,267 @@
# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtCore, QtGui
from Container import *
from DockDrop import *
import pyqtgraph.debug as debug
import weakref
## TODO:
# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?)
# - drop between tabs
# - nest splitters inside tab boxes, etc.
class DockArea(Container, QtGui.QWidget, DockDrop):
def __init__(self, temporary=False, home=None):
Container.__init__(self, self)
QtGui.QWidget.__init__(self)
DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom'])
self.layout = QtGui.QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
self.docks = weakref.WeakValueDictionary()
self.topContainer = None
self.raiseOverlay()
self.temporary = temporary
self.tempAreas = []
self.home = home
def type(self):
return "top"
def addDock(self, dock, position='bottom', relativeTo=None):
"""Adds a dock to this area.
position may be: bottom, top, left, right, over, under
If relativeTo specifies an existing dock, the new dock is added adjacent to it"""
## Determine the container to insert this dock into.
## If there is no neighbor, then the container is the top.
if relativeTo is None or relativeTo is self:
if self.topContainer is None:
container = self
neighbor = None
else:
container = self.topContainer
neighbor = None
else:
if isinstance(relativeTo, basestring):
relativeTo = self.docks[relativeTo]
container = self.getContainer(relativeTo)
neighbor = relativeTo
## what container type do we need?
neededContainer = {
'bottom': 'vertical',
'top': 'vertical',
'left': 'horizontal',
'right': 'horizontal',
'above': 'tab',
'below': 'tab'
}[position]
## Can't insert new containers into a tab container; insert outside instead.
if neededContainer != container.type() and container.type() == 'tab':
neighbor = container
container = container.container()
## Decide if the container we have is suitable.
## If not, insert a new container inside.
if neededContainer != container.type():
if neighbor is None:
container = self.addContainer(neededContainer, self.topContainer)
else:
container = self.addContainer(neededContainer, neighbor)
## Insert the new dock before/after its neighbor
insertPos = {
'bottom': 'after',
'top': 'before',
'left': 'before',
'right': 'after',
'above': 'before',
'below': 'after'
}[position]
#print "request insert", dock, insertPos, neighbor
container.insert(dock, insertPos, neighbor)
dock.area = self
self.docks[dock.name()] = dock
def getContainer(self, obj):
if obj is None:
return self
return obj.container()
def makeContainer(self, typ):
if typ == 'vertical':
new = VContainer(self)
elif typ == 'horizontal':
new = HContainer(self)
elif typ == 'tab':
new = TContainer(self)
return new
def addContainer(self, typ, obj):
"""Add a new container around obj"""
new = self.makeContainer(typ)
container = self.getContainer(obj)
container.insert(new, 'before', obj)
#print "Add container:", new, " -> ", container
if obj is not None:
new.insert(obj)
self.raiseOverlay()
return new
def insert(self, new, pos=None, neighbor=None):
if self.topContainer is not None:
self.topContainer.containerChanged(None)
self.layout.addWidget(new)
self.topContainer = new
#print self, "set top:", new
new._container = self
self.raiseOverlay()
#print "Insert top:", new
def count(self):
if self.topContainer is None:
return 0
return 1
def moveDock(self, dock, position, neighbor):
old = dock.container()
## Moving to the edge of a tabbed dock causes a drop outside the tab box
if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab':
neighbor = neighbor.container()
self.addDock(dock, position, neighbor)
old.apoptose()
#def paintEvent(self, ev):
#self.drawDockOverlay()
def resizeEvent(self, ev):
self.resizeOverlay(self.size())
def addTempArea(self):
if self.home is None:
area = DockArea(temporary=True, home=self)
self.tempAreas.append(area)
win = QtGui.QMainWindow()
win.setCentralWidget(area)
area.win = win
win.show()
else:
area = self.home.addTempArea()
#print "added temp area", area, area.window()
return area
def floatDock(self, dock):
area = self.addTempArea()
area.win.resize(dock.size())
area.moveDock(dock, 'top', None)
def removeTempArea(self, area):
self.tempAreas.remove(area)
#print "close window", area.window()
area.window().close()
def saveState(self):
state = {'main': self.childState(self.topContainer), 'float': []}
for a in self.tempAreas:
geo = a.win.geometry()
geo = (geo.x(), geo.y(), geo.width(), geo.height())
state['float'].append((a.saveState(), geo))
return state
def childState(self, obj):
if isinstance(obj, Dock):
return ('dock', obj.name(), {})
else:
childs = []
for i in range(obj.count()):
childs.append(self.childState(obj.widget(i)))
return (obj.type(), childs, obj.saveState())
def restoreState(self, state):
## 1) make dict of all docks and list of existing containers
containers, docks = self.findAll()
oldTemps = self.tempAreas[:]
#print "found docks:", docks
## 2) create container structure, move docks into new containers
self.buildFromState(state['main'], docks, self)
## 3) create floating areas, populate
for s in state['float']:
a = self.addTempArea()
a.buildFromState(s[0]['main'], docks, a)
a.win.setGeometry(*s[1])
## 4) Add any remaining docks to the bottom
for d in docks.itervalues():
self.moveDock(d, 'below', None)
#print "\nKill old containers:"
## 5) kill old containers
for c in containers:
c.close()
for a in oldTemps:
a.apoptose()
def buildFromState(self, state, docks, root, depth=0):
typ, contents, state = state
pfx = " " * depth
if typ == 'dock':
obj = docks[contents]
del docks[contents]
else:
obj = self.makeContainer(typ)
root.insert(obj, 'after')
#print pfx+"Add:", obj, " -> ", root
if typ != 'dock':
for o in contents:
self.buildFromState(o, docks, obj, depth+1)
obj.apoptose(propagate=False)
obj.restoreState(state) ## this has to be done later?
def findAll(self, obj=None, c=None, d=None):
if obj is None:
obj = self.topContainer
## check all temp areas first
if c is None:
c = []
d = {}
for a in self.tempAreas:
c1, d1 = a.findAll()
c.extend(c1)
d.update(d1)
if isinstance(obj, Dock):
d[obj.name()] = obj
else:
c.append(obj)
for i in range(obj.count()):
o2 = obj.widget(i)
c2, d2 = self.findAll(o2)
c.extend(c2)
d.update(d2)
return (c, d)
def apoptose(self):
#print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count()
if self.temporary and self.topContainer.count() == 0:
self.topContainer = None
self.home.removeTempArea(self)
#self.close()

129
dockarea/DockDrop.py Normal file
View File

@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtCore, QtGui
class DockDrop(object):
"""Provides dock-dropping methods"""
def __init__(self, allowedAreas=None):
object.__init__(self)
if allowedAreas is None:
allowedAreas = ['center', 'right', 'left', 'top', 'bottom']
self.allowedAreas = set(allowedAreas)
self.setAcceptDrops(True)
self.dropArea = None
self.overlay = DropAreaOverlay(self)
self.overlay.raise_()
def resizeOverlay(self, size):
self.overlay.resize(size)
def raiseOverlay(self):
self.overlay.raise_()
def dragEnterEvent(self, ev):
if isinstance(ev.source(), Dock.Dock):
#print "drag enter accept"
ev.accept()
else:
#print "drag enter ignore"
ev.ignore()
def dragMoveEvent(self, ev):
#print "drag move"
ld = ev.pos().x()
rd = self.width() - ld
td = ev.pos().y()
bd = self.height() - td
mn = min(ld, rd, td, bd)
if mn > 30:
self.dropArea = "center"
elif (ld == mn or td == mn) and mn > self.height()/3.:
self.dropArea = "center"
elif (rd == mn or ld == mn) and mn > self.width()/3.:
self.dropArea = "center"
elif rd == mn:
self.dropArea = "right"
elif ld == mn:
self.dropArea = "left"
elif td == mn:
self.dropArea = "top"
elif bd == mn:
self.dropArea = "bottom"
if ev.source() is self and self.dropArea == 'center':
#print " no self-center"
self.dropArea = None
ev.ignore()
elif self.dropArea not in self.allowedAreas:
#print " not allowed"
self.dropArea = None
ev.ignore()
else:
#print " ok"
ev.accept()
self.overlay.setDropArea(self.dropArea)
def dragLeaveEvent(self, ev):
self.dropArea = None
self.overlay.setDropArea(self.dropArea)
def dropEvent(self, ev):
area = self.dropArea
if area is None:
return
if area == 'center':
area = 'above'
self.area.moveDock(ev.source(), area, self)
self.dropArea = None
self.overlay.setDropArea(self.dropArea)
class DropAreaOverlay(QtGui.QWidget):
"""Overlay widget that draws drop areas during a drag-drop operation"""
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.dropArea = None
self.hide()
self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
def setDropArea(self, area):
self.dropArea = area
if area is None:
self.hide()
else:
## Resize overlay to just the region where drop area should be displayed.
## This works around a Qt bug--can't display transparent widgets over QGLWidget
prgn = self.parent().rect()
rgn = QtCore.QRect(prgn)
w = min(30, prgn.width()/3.)
h = min(30, prgn.height()/3.)
if self.dropArea == 'left':
rgn.setWidth(w)
elif self.dropArea == 'right':
rgn.setLeft(rgn.left() + prgn.width() - w)
elif self.dropArea == 'top':
rgn.setHeight(h)
elif self.dropArea == 'bottom':
rgn.setTop(rgn.top() + prgn.height() - h)
elif self.dropArea == 'center':
rgn.adjust(w, h, -w, -h)
self.setGeometry(rgn)
self.show()
self.update()
def paintEvent(self, ev):
if self.dropArea is None:
return
p = QtGui.QPainter(self)
rgn = self.rect()
p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50)))
p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3))
p.drawRect(rgn)
import Dock

2
dockarea/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from DockArea import DockArea
from Dock import Dock

83
dockarea/__main__.py Normal file
View File

@ -0,0 +1,83 @@
import sys
## Make sure pyqtgraph is importable
p = os.path.dirname(os.path.abspath(__file__))
p = os.path.join(p, '..', '..')
sys.path.insert(0, p)
from pyqtgraph.Qt import QtCore, QtGui
from DockArea import *
from Dock import *
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
area = DockArea()
win.setCentralWidget(area)
win.resize(800,800)
from Dock import Dock
d1 = Dock("Dock1", size=(200,200))
d2 = Dock("Dock2", size=(100,100))
d3 = Dock("Dock3", size=(1,1))
d4 = Dock("Dock4", size=(50,50))
d5 = Dock("Dock5", size=(100,100))
d6 = Dock("Dock6", size=(300,300))
area.addDock(d1, 'left')
area.addDock(d2, 'right')
area.addDock(d3, 'bottom')
area.addDock(d4, 'right')
area.addDock(d5, 'left', d1)
area.addDock(d6, 'top', d4)
area.moveDock(d6, 'above', d4)
d3.hideTitleBar()
print "===build complete===="
for d in [d1, d2, d3, d4, d5]:
w = QtGui.QWidget()
l = QtGui.QVBoxLayout()
w.setLayout(l)
btns = []
for i in range(4):
btns.append(QtGui.QPushButton("%s Button %d"%(d.name(), i)))
l.addWidget(btns[-1])
d.w = (w, l, btns)
d.addWidget(w)
import pyqtgraph as pg
p = pg.PlotWidget()
d6.addWidget(p)
print "===widgets added==="
#s = area.saveState()
#print "\n\n-------restore----------\n\n"
#area.restoreState(s)
s = None
def save():
global s
s = area.saveState()
def load():
global s
area.restoreState(s)
#d6.container().setCurrentIndex(0)
#d2.label.setTabPos(40)
#win2 = QtGui.QMainWindow()
#area2 = DockArea()
#win2.setCentralWidget(area2)
#win2.resize(800,800)
win.show()
#win2.show()

130
documentation/Makefile Normal file
View File

@ -0,0 +1,130 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyqtgraph.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyqtgraph.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/pyqtgraph"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyqtgraph"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: ba0221080f2c4e6cd48402ae7f991103
tags: fbb0d17656682115ca4d033fb2f83ba1

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,89 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Overview: module code &mdash; pyqtgraph v1.8 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '1.8',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">pyqtgraph v1.8 documentation</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1>All modules for which code is available</h1>
<ul><li><a href="pyqtgraph.html">pyqtgraph</a></li>
</ul>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" size="18" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">pyqtgraph v1.8 documentation</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2011, Luke Campagnola.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1.
</div>
</body>
</html>

View File

@ -0,0 +1,192 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>pyqtgraph &mdash; pyqtgraph v1.8 documentation</title>
<link rel="stylesheet" href="../_static/default.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '1.8',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<link rel="top" title="pyqtgraph v1.8 documentation" href="../index.html" />
<link rel="up" title="Module code" href="index.html" />
</head>
<body>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">pyqtgraph v1.8 documentation</a> &raquo;</li>
<li><a href="index.html" accesskey="U">Module code</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<h1>Source code for pyqtgraph</h1><div class="highlight"><pre>
<span class="c"># -*- coding: utf-8 -*-</span>
<span class="c">### import all the goodies and add some helper functions for easy CLI use</span>
<span class="c">## &#39;Qt&#39; is a local module; it is intended mainly to cover up the differences</span>
<span class="c">## between PyQt4 and PySide.</span>
<span class="kn">from</span> <span class="nn">Qt</span> <span class="kn">import</span> <span class="n">QtGui</span>
<span class="n">CONFIG_OPTIONS</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">&#39;leftButtonPan&#39;</span><span class="p">:</span> <span class="bp">True</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">setConfigOption</span><span class="p">(</span><span class="n">opt</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="n">CONFIG_OPTIONS</span><span class="p">[</span><span class="n">opt</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">def</span> <span class="nf">getConfigOption</span><span class="p">(</span><span class="n">opt</span><span class="p">):</span>
<span class="k">return</span> <span class="n">CONFIG_OPTIONS</span><span class="p">[</span><span class="n">opt</span><span class="p">]</span>
<span class="c">## Import almost everything to make it available from a single namespace</span>
<span class="c">## don&#39;t import the more complex systems--canvas, parametertree, flowchart, dockarea</span>
<span class="c">## these must be imported separately.</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="k">def</span> <span class="nf">importAll</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">__file__</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="n">path</span><span class="p">)</span>
<span class="n">files</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">f</span><span class="p">)):</span>
<span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">f</span><span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">:]</span> <span class="o">==</span> <span class="s">&#39;.py&#39;</span> <span class="ow">and</span> <span class="n">f</span> <span class="o">!=</span> <span class="s">&#39;__init__.py&#39;</span><span class="p">:</span>
<span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">f</span><span class="p">[:</span><span class="o">-</span><span class="mi">3</span><span class="p">])</span>
<span class="k">for</span> <span class="n">modName</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">mod</span> <span class="o">=</span> <span class="nb">__import__</span><span class="p">(</span><span class="n">path</span><span class="o">+</span><span class="s">&quot;.&quot;</span><span class="o">+</span><span class="n">modName</span><span class="p">,</span> <span class="nb">globals</span><span class="p">(),</span> <span class="nb">locals</span><span class="p">(),</span> <span class="n">fromlist</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;*&#39;</span><span class="p">])</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="s">&#39;__all__&#39;</span><span class="p">):</span>
<span class="n">names</span> <span class="o">=</span> <span class="n">mod</span><span class="o">.</span><span class="n">__all__</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="n">mod</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="s">&#39;_&#39;</span><span class="p">]</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">names</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">k</span><span class="p">):</span>
<span class="nb">globals</span><span class="p">()[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
<span class="n">importAll</span><span class="p">(</span><span class="s">&#39;graphicsItems&#39;</span><span class="p">)</span>
<span class="n">importAll</span><span class="p">(</span><span class="s">&#39;widgets&#39;</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">imageview</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">WidgetGroup</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">Point</span> <span class="kn">import</span> <span class="n">Point</span>
<span class="kn">from</span> <span class="nn">Transform</span> <span class="kn">import</span> <span class="n">Transform</span>
<span class="kn">from</span> <span class="nn">functions</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">graphicsWindows</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">from</span> <span class="nn">SignalProxy</span> <span class="kn">import</span> <span class="o">*</span>
<span class="c">## Convenience functions for command-line use</span>
<span class="n">plots</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">images</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">QAPP</span> <span class="o">=</span> <span class="bp">None</span>
<div class="viewcode-block" id="plot"><a class="viewcode-back" href="../functions.html#pyqtgraph.plot">[docs]</a><span class="k">def</span> <span class="nf">plot</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> | Create and return a PlotWindow (this is just a window with PlotWidget inside), plot data in it.</span>
<span class="sd"> | Accepts a *title* argument to set the title of the window.</span>
<span class="sd"> | All other arguments are used to plot data. (see :func:`PlotItem.plot() &lt;pyqtgraph.PlotItem.plot&gt;`)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">mkQApp</span><span class="p">()</span>
<span class="k">if</span> <span class="s">&#39;title&#39;</span> <span class="ow">in</span> <span class="n">kargs</span><span class="p">:</span>
<span class="n">w</span> <span class="o">=</span> <span class="n">PlotWindow</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="n">kargs</span><span class="p">[</span><span class="s">&#39;title&#39;</span><span class="p">])</span>
<span class="k">del</span> <span class="n">kargs</span><span class="p">[</span><span class="s">&#39;title&#39;</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">w</span> <span class="o">=</span> <span class="n">PlotWindow</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span><span class="o">+</span><span class="nb">len</span><span class="p">(</span><span class="n">kargs</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">w</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">)</span>
<span class="n">plots</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">w</span><span class="p">)</span>
<span class="n">w</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="k">return</span> <span class="n">w</span>
</div>
<div class="viewcode-block" id="image"><a class="viewcode-back" href="../functions.html#pyqtgraph.image">[docs]</a><span class="k">def</span> <span class="nf">image</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> | Create and return an ImageWindow (this is just a window with ImageView widget inside), show image data inside.</span>
<span class="sd"> | Will show 2D or 3D image data.</span>
<span class="sd"> | Accepts a *title* argument to set the title of the window.</span>
<span class="sd"> | All other arguments are used to show data. (see :func:`ImageView.setImage() &lt;pyqtgraph.ImageView.setImage&gt;`)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="n">mkQApp</span><span class="p">()</span>
<span class="n">w</span> <span class="o">=</span> <span class="n">ImageWindow</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kargs</span><span class="p">)</span>
<span class="n">images</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">w</span><span class="p">)</span>
<span class="n">w</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="k">return</span> <span class="n">w</span></div>
<span class="n">show</span> <span class="o">=</span> <span class="n">image</span> <span class="c">## for backward compatibility</span>
<span class="k">def</span> <span class="nf">mkQApp</span><span class="p">():</span>
<span class="k">if</span> <span class="n">QtGui</span><span class="o">.</span><span class="n">QApplication</span><span class="o">.</span><span class="n">instance</span><span class="p">()</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">global</span> <span class="n">QAPP</span>
<span class="n">QAPP</span> <span class="o">=</span> <span class="n">QtGui</span><span class="o">.</span><span class="n">QApplication</span><span class="p">([])</span>
</pre></div>
</div>
</div>
</div>
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
<div id="searchbox" style="display: none">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" size="18" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li><a href="../index.html">pyqtgraph v1.8 documentation</a> &raquo;</li>
<li><a href="index.html" >Module code</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; Copyright 2011, Luke Campagnola.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1.
</div>
</body>
</html>

View File

@ -0,0 +1,11 @@
API Reference
=============
Contents:
.. toctree::
:maxdepth: 2
functions
graphicsItems/index
widgets/index

View File

@ -0,0 +1,53 @@
Pyqtgraph's Helper Functions
============================
Simple Data Display Functions
-----------------------------
.. autofunction:: pyqtgraph.plot
.. autofunction:: pyqtgraph.image
Color, Pen, and Brush Functions
-------------------------------
Qt uses the classes QColor, QPen, and QBrush to determine how to draw lines and fill shapes. These classes are highly capable but somewhat awkward to use. Pyqtgraph offers the functions :func:`~pyqtgraph.mkColor`, :func:`~pyqtgraph.mkPen`, and :func:`~pyqtgraph.mkBrush` to simplify the process of creating these classes. In most cases, however, it will be unnecessary to call these functions directly--any function or method that accepts *pen* or *brush* arguments will make use of these functions for you. For example, the following three lines all have the same effect::
pg.plot(xdata, ydata, pen='r')
pg.plot(xdata, ydata, pen=pg.mkPen('r'))
pg.plot(xdata, ydata, pen=QPen(QColor(255, 0, 0)))
.. autofunction:: pyqtgraph.mkColor
.. autofunction:: pyqtgraph.mkPen
.. autofunction:: pyqtgraph.mkBrush
.. autofunction:: pyqtgraph.hsvColor
.. autofunction:: pyqtgraph.intColor
.. autofunction:: pyqtgraph.colorTuple
.. autofunction:: pyqtgraph.colorStr
Data Slicing
------------
.. autofunction:: pyqtgraph.affineSlice
SI Unit Conversion Functions
----------------------------
.. autofunction:: pyqtgraph.siFormat
.. autofunction:: pyqtgraph.siScale
.. autofunction:: pyqtgraph.siEval

View File

@ -0,0 +1,8 @@
ArrowItem
=========
.. autoclass:: pyqtgraph.ArrowItem
:members:
.. automethod:: pyqtgraph.ArrowItem.__init__

Some files were not shown because too many files have changed in this diff Show More