Imported major changes from acq4 project.
This commit is contained in:
parent
4d846e2aad
commit
d4e8e2b883
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
Binary file not shown.
|
@ -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>
|
1284
PlotItem.py
1284
PlotItem.py
File diff suppressed because it is too large
Load Diff
17
Point.py
17
Point.py
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
Binary file not shown.
|
@ -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):
|
||||
|
|
Binary file not shown.
|
@ -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))
|
||||
|
||||
|
||||
|
Binary file not shown.
81
__init__.py
81
__init__.py
|
@ -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:
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
|
@ -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>
|
|
@ -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))
|
||||
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from Canvas import *
|
||||
from CanvasItem import *
|
2
debug.py
2
debug.py
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
from DockArea import DockArea
|
||||
from Dock import Dock
|
|
@ -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()
|
||||
|
|
@ -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.
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.
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.
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.
|
@ -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 |
|
@ -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 — 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> »</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> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2011, Luke Campagnola.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -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 — 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> »</li>
|
||||
<li><a href="index.html" accesskey="U">Module code</a> »</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">## 'Qt' 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">'leftButtonPan'</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'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">'.py'</span> <span class="ow">and</span> <span class="n">f</span> <span class="o">!=</span> <span class="s">'__init__.py'</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">"."</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">'*'</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">'__all__'</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">'_'</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">'graphicsItems'</span><span class="p">)</span>
|
||||
<span class="n">importAll</span><span class="p">(</span><span class="s">'widgets'</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">"""</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() <pyqtgraph.PlotItem.plot>`)</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="n">mkQApp</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="s">'title'</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">'title'</span><span class="p">])</span>
|
||||
<span class="k">del</span> <span class="n">kargs</span><span class="p">[</span><span class="s">'title'</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">></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">"""</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() <pyqtgraph.ImageView.setImage>`)</span>
|
||||
<span class="sd"> """</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> »</li>
|
||||
<li><a href="index.html" >Module code</a> »</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© Copyright 2011, Luke Campagnola.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.0.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
API Reference
|
||||
=============
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
functions
|
||||
graphicsItems/index
|
||||
widgets/index
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in New Issue