pyqtgraph/pyqtgraph/canvas/Canvas.py

605 lines
20 KiB
Python

# -*- 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
from ..Qt import QtGui, QtCore, USE_PYSIDE
from ..graphicsItems.ROI import ROI
from ..graphicsItems.ViewBox import ViewBox
from ..graphicsItems.GridItem import GridItem
if USE_PYSIDE:
from .CanvasTemplate_pyside import *
else:
from .CanvasTemplate_pyqt import *
import numpy as np
from .. import debug
import weakref
from .CanvasManager import CanvasManager
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.reflectSelectionBtn.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.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked)
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)
self.menu = QtGui.QMenu()
#self.menu.setTitle("Image")
remAct = QtGui.QAction("Remove item", self.menu)
remAct.triggered.connect(self.removeClicked)
self.menu.addAction(remAct)
self.menu.remAct = remAct
self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent
#def storeSvg(self):
#from pyqtgraph.GraphicsScene.exportDialog import ExportDialog
#ex = ExportDialog(self.ui.view)
#ex.show()
#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.reflectSelectionBtn.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.reflectSelectionBtn.show()
self.ui.resetTransformsBtn.show()
#self.multiSelectBoxBase = self.multiSelectBox.getState().copy()
def mirrorSelectionClicked(self):
for ci in self.selectedItems():
ci.mirrorY()
self.showMultiSelectBox()
def reflectSelectionClicked(self):
for ci in self.selectedItems():
ci.mirrorXY()
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)
item._canvasItem = citem
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 range(parent.childCount())]
z = citem.zValue()
if z is None:
zvals = [i.zValue() for i in siblings]
if parent == self.itemList.invisibleRootItem():
if len(zvals) == 0:
z = 0
else:
z = max(zvals)+10
else:
if len(zvals) == 0:
z = parent.canvasItem().zValue()
else:
z = max(zvals)+1
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 = weakref.ref(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 range(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, QtGui.QTreeWidgetItem):
item = item.canvasItem()
if isinstance(item, CanvasItem):
item.setCanvas(None)
listItem = item.listItem
listItem.canvasItem = None
item.listItem = None
self.itemList.removeTopLevelItem(listItem)
self.items.remove(item)
ctrl = item.ctrlWidget()
ctrl.hide()
self.ui.ctrlLayout.removeWidget(ctrl)
else:
if hasattr(item, '_canvasItem'):
self.removeItem(item._canvasItem)
else:
self.view.removeItem(item)
## disconnect signals, remove from list, etc..
def clear(self):
while len(self.items) > 0:
self.removeItem(self.items[0])
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)
def itemListContextMenuEvent(self, ev):
self.menuItem = self.itemList.itemAt(ev.pos())
self.menu.popup(ev.globalPos())
def removeClicked(self):
#self.removeItem(self.menuItem)
for item in self.selectedItems():
self.removeItem(item)
self.menuItem = None
import gc
gc.collect()
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)