Commit 81a32b0d authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Cleaned up and centralized export functionality

Moved GraphicsScene to its own directory, added exportDialog
Removed old export options from PlotItem / ViewBox (will re-enable once they are working again)
parent 920fd933
......@@ -3,7 +3,9 @@ import weakref
from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import pyqtgraph.ptime as ptime
from mouseEvents import *
import debug
import exportDialog
try:
import sip
......@@ -63,6 +65,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
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.
ExportDirectory = None
@classmethod
def registerObject(cls, obj):
"""
......@@ -78,6 +82,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
QtGui.QGraphicsScene.__init__(self)
self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance)
self.exportDirectory = None
self.clickEvents = []
self.dragButtons = []
self.mouseGrabber = None
......@@ -89,6 +95,11 @@ class GraphicsScene(QtGui.QGraphicsScene):
#self.searchRect.setPen(fn.mkPen(200,0,0))
#self.addItem(self.searchRect)
self.contextMenu = [QtGui.QAction("Export...", self)]
self.contextMenu[0].triggered.connect(self.showExportDialog)
self.exportDialog = None
def setClickRadius(self, r):
"""
......@@ -420,19 +431,22 @@ class GraphicsScene(QtGui.QGraphicsScene):
##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 getViewWidget(self):
return self.views()[0]
#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):
"""
......@@ -464,11 +478,12 @@ class GraphicsScene(QtGui.QGraphicsScene):
#items = self.itemsNearEvent(ev)
menusToAdd = []
while item.parentItem() is not None:
while item is not self:
item = item.parentItem()
#for item in items:
#if item is sender:
#continue
if item is None:
item = self
if not hasattr(item, "getContextMenus"):
continue
......@@ -484,10 +499,24 @@ class GraphicsScene(QtGui.QGraphicsScene):
menu.addSeparator()
for m in menusToAdd:
menu.addMenu(m)
if isinstance(m, QtGui.QMenu):
menu.addMenu(m)
elif isinstance(m, QtGui.QAction):
menu.addAction(m)
else:
raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m))))
return menu
def getContextMenus(self, event):
self.contextMenuItem = event.acceptedItem
return self.contextMenu
def showExportDialog(self):
if self.exportDialog is None:
self.exportDialog = exportDialog.ExportDialog(self)
self.exportDialog.show(self.contextMenuItem)
@staticmethod
def translateGraphicsItem(item):
## for fixing pyqt bugs where the wrong item is returned
......@@ -501,247 +530,4 @@ class GraphicsScene(QtGui.QGraphicsScene):
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
\ No newline at end of file
from GraphicsScene import *
import exportDialogTemplate
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.exporters as exporters
class ExportDialog(QtGui.QWidget):
def __init__(self, scene):
QtGui.QWidget.__init__(self)
self.setVisible(False)
self.setWindowTitle("Export")
self.shown = False
self.currentExporter = None
self.scene = scene
self.selectBox = QtGui.QGraphicsRectItem()
self.selectBox.setPen(pg.mkPen('y', width=3, style=QtCore.Qt.DashLine))
self.selectBox.hide()
self.scene.addItem(self.selectBox)
self.ui = exportDialogTemplate.Ui_Form()
self.ui.setupUi(self)
self.ui.closeBtn.clicked.connect(self.close)
self.ui.exportBtn.clicked.connect(self.exportClicked)
self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged)
self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged)
def show(self, item=None):
if item is not None:
while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None:
item = item.parentItem()
self.updateItemList(select=item)
self.setVisible(True)
self.activateWindow()
self.raise_()
self.selectBox.setVisible(True)
if not self.shown:
self.shown = True
vcenter = self.scene.getViewWidget().geometry().center()
self.setGeometry(vcenter.x()-self.width()/2, vcenter.y()-self.height()/2, self.width(), self.height())
def updateItemList(self, select=None):
self.ui.itemTree.clear()
si = QtGui.QTreeWidgetItem(["Entire Scene"])
si.gitem = self.scene
self.ui.itemTree.addTopLevelItem(si)
self.ui.itemTree.setCurrentItem(si)
si.setExpanded(True)
for child in self.scene.items():
if child.parentItem() is None:
self.updateItemTree(child, si, select=select)
def updateItemTree(self, item, treeItem, select=None):
si = None
if isinstance(item, pg.ViewBox):
si = QtGui.QTreeWidgetItem(['ViewBox'])
elif isinstance(item, pg.PlotItem):
si = QtGui.QTreeWidgetItem(['Plot'])
if si is not None:
si.gitem = item
treeItem.addChild(si)
treeItem = si
if si.gitem is select:
self.ui.itemTree.setCurrentItem(si)
for ch in item.childItems():
self.updateItemTree(ch, treeItem, select=select)
def exportItemChanged(self, item, prev):
if item.gitem is self.scene:
newBounds = self.scene.views()[0].viewRect()
else:
newBounds = item.gitem.sceneBoundingRect()
self.selectBox.setRect(newBounds)
self.selectBox.show()
self.updateFormatList()
def updateFormatList(self):
current = self.ui.formatList.currentItem()
if current is not None:
current = str(current.text())
self.ui.formatList.clear()
self.exporterClasses = {}
gotCurrent = False
for exp in exporters.listExporters():
self.ui.formatList.addItem(exp.Name)
self.exporterClasses[exp.Name] = exp
if exp.Name == current:
self.ui.formatList.setCurrentRow(self.ui.formatList.count()-1)
gotCurrent = True
if not gotCurrent:
self.ui.formatList.setCurrentRow(0)
def exportFormatChanged(self, item, prev):
if item is None:
self.currentExporter = None
self.ui.paramTree.clear()
return
expClass = self.exporterClasses[str(item.text())]
exp = expClass(item=self.ui.itemTree.currentItem().gitem)
params = exp.parameters()
self.ui.paramTree.setParameters(params)
self.currentExporter = exp
def exportClicked(self):
self.selectBox.hide()
self.currentExporter.export()
def close(self):
self.selectBox.setVisible(False)
self.setVisible(False)
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'exportDialogTemplate.ui'
#
# Created: Sat Mar 10 17:54:53 2012
# by: PyQt4 UI code generator 4.8.5
#
# 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(241, 367)
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label = QtGui.QLabel(Form)
self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8))
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 0, 0, 1, 3)
self.itemTree = QtGui.QTreeWidget(Form)
self.itemTree.setObjectName(_fromUtf8("itemTree"))
self.itemTree.headerItem().setText(0, _fromUtf8("1"))
self.itemTree.header().setVisible(False)
self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3)
self.label_2 = QtGui.QLabel(Form)
self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3)
self.formatList = QtGui.QListWidget(Form)
self.formatList.setObjectName(_fromUtf8("formatList"))
self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3)
self.exportBtn = QtGui.QPushButton(Form)
self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
self.exportBtn.setObjectName(_fromUtf8("exportBtn"))
self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1)
self.closeBtn = QtGui.QPushButton(Form)
self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8))
self.closeBtn.setObjectName(_fromUtf8("closeBtn"))
self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1)
self.paramTree = ParameterTree(Form)
self.paramTree.setObjectName(_fromUtf8("paramTree"))
self.paramTree.headerItem().setText(0, _fromUtf8("1"))
self.paramTree.header().setVisible(False)
self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3)
self.label_3 = QtGui.QLabel(Form)
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setObjectName(_fromUtf8("label_3"))
self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
pass
from pyqtgraph.parametertree import ParameterTree
<?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>241</width>
<height>367</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>Item to export:</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QTreeWidget" name="itemTree">
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Export format</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QListWidget" name="formatList"/>
</item>
<item row="6" column="1">