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)
This commit is contained in:
parent
920fd9333e
commit
81a32b0d1e
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
from GraphicsScene import *
|
|
@ -0,0 +1,120 @@
|
|||
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)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# -*- 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
|
|
@ -0,0 +1,93 @@
|
|||
<?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">
|
||||
<widget class="QPushButton" name="exportBtn">
|
||||
<property name="text">
|
||||
<string>Export</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QPushButton" name="closeBtn">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="ParameterTree" name="paramTree">
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Export options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ParameterTree</class>
|
||||
<extends>QTreeWidget</extends>
|
||||
<header>pyqtgraph.parametertree</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,249 @@
|
|||
from pyqtgraph.Point import Point
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import weakref
|
||||
import pyqtgraph.ptime as ptime
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
39
__init__.py
39
__init__.py
|
@ -19,11 +19,44 @@ def setConfigOption(opt, value):
|
|||
def getConfigOption(opt):
|
||||
return CONFIG_OPTIONS[opt]
|
||||
|
||||
|
||||
## Rename orphaned .pyc files. This is *probably* safe :)
|
||||
|
||||
def renamePyc(startDir):
|
||||
### Used to rename orphaned .pyc files
|
||||
### When a python file changes its location in the repository, usually the .pyc file
|
||||
### is left behind, possibly causing mysterious and difficult to track bugs.
|
||||
|
||||
printed = False
|
||||
startDir = os.path.abspath(startDir)
|
||||
for path, dirs, files in os.walk(startDir):
|
||||
for f in files:
|
||||
fileName = os.path.join(path, f)
|
||||
base, ext = os.path.splitext(fileName)
|
||||
py = base + ".py"
|
||||
if ext == '.pyc' and not os.path.isfile(py):
|
||||
if not printed:
|
||||
print "NOTE: Renaming orphaned .pyc files:"
|
||||
printed = True
|
||||
n = 1
|
||||
while True:
|
||||
name2 = fileName + ".renamed%d" % n
|
||||
if not os.path.exists(name2):
|
||||
break
|
||||
n += 1
|
||||
print " " + fileName + " ==>"
|
||||
print " " + name2
|
||||
os.rename(fileName, name2)
|
||||
|
||||
import os
|
||||
path = os.path.split(__file__)[0]
|
||||
renamePyc(path)
|
||||
|
||||
|
||||
## 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 = []
|
||||
|
@ -101,4 +134,6 @@ show = image ## for backward compatibility
|
|||
def mkQApp():
|
||||
if QtGui.QApplication.instance() is None:
|
||||
global QAPP
|
||||
QAPP = QtGui.QApplication([])
|
||||
QAPP = QtGui.QApplication([])
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,11 @@ For these function arguments, the following values may be used:
|
|||
* QColor
|
||||
* QPen / QBrush where appropriate
|
||||
|
||||
Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt's QPen and QBrush classes.
|
||||
Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt's QPen and QBrush classes::
|
||||
|
||||
mkPen('y', width=3, style=QtCore.Qt.DashLine) ## Make a dashed yellow line 2px wide
|
||||
mkPen(0.5) ## solid grey line 1px wide
|
||||
mkPen(color=(200, 200, 255), style=QtCore.Qt.DotLine) ## Dotted pale-blue line
|
||||
|
||||
See the Qt documentation for 'QPen' and 'PenStyle' for more options.
|
||||
Colors can also be built using mkColor(), intColor(), hsvColor(), or Qt's QColor class
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
from pyqtgraph.widgets.FileDialog import FileDialog
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
|
||||
import os
|
||||
LastExportDirectory = None
|
||||
|
||||
|
||||
class Exporter(object):
|
||||
"""
|
||||
Abstract class used for exporting graphics to file / printer / whatever.
|
||||
"""
|
||||
|
||||
def __init__(self, item):
|
||||
"""
|
||||
Initialize with the item to be exported.
|
||||
Can be an individual graphics item or a scene.
|
||||
"""
|
||||
object.__init__(self)
|
||||
self.item = item
|
||||
|
||||
def item(self):
|
||||
return self.item
|
||||
|
||||
def parameters(self):
|
||||
"""Return the parameters used to configure this exporter."""
|
||||
raise Exception("Abstract method must be overridden in subclass.")
|
||||
|
||||
def export(self):
|
||||
""""""
|
||||
raise Exception("Abstract method must be overridden in subclass.")
|
||||
|
||||
def fileSaveDialog(self, filter=None, opts=None):
|
||||
## Show a file dialog, call self.export(fileName) when finished.
|
||||
if opts is None:
|
||||
opts = {}
|
||||
self.fileDialog = FileDialog()
|
||||
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
if filter is not None:
|
||||
if isinstance(filter, basestring):
|
||||
self.fileDialog.setNameFilter(filter)
|
||||
elif isinstance(filter, list):
|
||||
self.fileDialog.setNameFilters(filter)
|
||||
global LastExportDirectory
|
||||
exportDir = LastExportDirectory
|
||||
if exportDir is not None:
|
||||
self.fileDialog.setDirectory(exportDir)
|
||||
self.fileDialog.show()
|
||||
self.fileDialog.opts = opts
|
||||
self.fileDialog.fileSelected.connect(self.fileSaveFinished)
|
||||
return
|
||||
|
||||
def fileSaveFinished(self, fileName):
|
||||
fileName = str(fileName)
|
||||
global LastExportDirectory
|
||||
LastExportDirectory = os.path.split(fileName)[0]
|
||||
self.export(fileName=fileName, **self.fileDialog.opts)
|
||||
|
||||
|
||||
def getScene(self):
|
||||
if isinstance(self.item, pg.GraphicsScene):
|
||||
return self.item
|
||||
else:
|
||||
return self.item.scene()
|
||||
|
||||
def getSourceRect(self):
|
||||
if isinstance(self.item, pg.GraphicsScene):
|
||||
return self.item.getViewWidget().viewRect()
|
||||
else:
|
||||
return self.item.sceneBoundingRect()
|
||||
|
||||
def getTargetRect(self):
|
||||
if isinstance(self.item, pg.GraphicsScene):
|
||||
return self.item.getViewWidget().rect()
|
||||
else:
|
||||
return self.item.mapRectToDevice(self.item.boundingRect())
|
||||
|
||||
|
||||
|
||||
|
||||
#def writePs(self, fileName=None, item=None):
|
||||
#if fileName is None:
|
||||
#self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)")
|
||||
#return
|
||||
#if item is None:
|
||||
#item = self
|
||||
#printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
|
||||
#printer.setOutputFileName(fileName)
|
||||
#painter = QtGui.QPainter(printer)
|
||||
#self.render(painter)
|
||||
#painter.end()
|
||||
|
||||
#def writeToPrinter(self):
|
||||
#pass
|
|
@ -0,0 +1,62 @@
|
|||
from Exporter import Exporter
|
||||
from pyqtgraph.parametertree import Parameter
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
|
||||
__all__ = ['ImageExporter']
|
||||
|
||||
class ImageExporter(Exporter):
|
||||
Name = "Image File (PNG, TIF, JPG, ...)"
|
||||
def __init__(self, item):
|
||||
Exporter.__init__(self, item)
|
||||
tr = self.getTargetRect()
|
||||
|
||||
self.params = Parameter(name='params', type='group', children=[
|
||||
{'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)},
|
||||
{'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)},
|
||||
{'name': 'antialias', 'type': 'bool', 'value': True},
|
||||
{'name': 'background', 'type': 'color', 'value': (0,0,0,255)},
|
||||
])
|
||||
self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
||||
self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
||||
|
||||
def widthChanged(self):
|
||||
sr = self.getSourceRect()
|
||||
ar = sr.height() / sr.width()
|
||||
self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged)
|
||||
|
||||
def heightChanged(self):
|
||||
sr = self.getSourceRect()
|
||||
ar = sr.width() / sr.height()
|
||||
self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged)
|
||||
|
||||
def parameters(self):
|
||||
return self.params
|
||||
|
||||
def export(self, fileName=None):
|
||||
if fileName is None:
|
||||
filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()]
|
||||
preferred = ['*.png', '*.tif', '*.jpg']
|
||||
for p in preferred[::-1]:
|
||||
if p in filter:
|
||||
filter.remove(p)
|
||||
filter.insert(0, p)
|
||||
self.fileSaveDialog(filter=filter)
|
||||
return
|
||||
|
||||
targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
|
||||
sourceRect = self.getSourceRect()
|
||||
#self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32)
|
||||
#self.png.fill(pyqtgraph.mkColor(self.params['background']))
|
||||
bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte)
|
||||
color = self.params['background']
|
||||
bg[:,:,0] = color.blue()
|
||||
bg[:,:,1] = color.green()
|
||||
bg[:,:,2] = color.red()
|
||||
bg[:,:,3] = color.alpha()
|
||||
self.png = pg.makeQImage(bg, alpha=True)
|
||||
painter = QtGui.QPainter(self.png)
|
||||
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
|
||||
self.png.save(fileName)
|
||||
painter.end()
|
|
@ -0,0 +1,64 @@
|
|||
from Exporter import Exporter
|
||||
from pyqtgraph.parametertree import Parameter
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
|
||||
import re
|
||||
|
||||
__all__ = ['SVGExporter']
|
||||
|
||||
class SVGExporter(Exporter):
|
||||
Name = "Scalable Vector Graphics (SVG)"
|
||||
def __init__(self, item):
|
||||
Exporter.__init__(self, item)
|
||||
tr = self.getTargetRect()
|
||||
self.params = Parameter(name='params', type='group', children=[
|
||||
{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)},
|
||||
{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
|
||||
])
|
||||
self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
||||
self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
||||
|
||||
def widthChanged(self):
|
||||
sr = self.getSourceRect()
|
||||
ar = sr.height() / sr.width()
|
||||
self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged)
|
||||
|
||||
def heightChanged(self):
|
||||
sr = self.getSourceRect()
|
||||
ar = sr.width() / sr.height()
|
||||
self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged)
|
||||
|
||||
def parameters(self):
|
||||
return self.params
|
||||
|
||||
def export(self, fileName=None):
|
||||
if fileName is None:
|
||||
self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)")
|
||||
return
|
||||
self.svg = QtSvg.QSvgGenerator()
|
||||
self.svg.setFileName(fileName)
|
||||
self.svg.setSize(QtCore.QSize(100,100))
|
||||
#self.svg.setResolution(600)
|
||||
#self.svg.setViewBox()
|
||||
targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
|
||||
sourceRect = self.getSourceRect()
|
||||
painter = QtGui.QPainter(self.svg)
|
||||
self.getScene().render(painter, QtCore.QRectF(targetRect), sourceRect)
|
||||
painter.end()
|
||||
|
||||
## Workaround to set pen widths correctly
|
||||
data = open(fileName).readlines()
|
||||
for i in range(len(data)):
|
||||
line = data[i]
|
||||
m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line)
|
||||
if m is not None:
|
||||
#print "Matched group:", line
|
||||
g = m.groups()
|
||||
matrix = map(float, g[2].split(','))
|
||||
#print "matrix:", matrix
|
||||
scale = max(abs(matrix[0]), abs(matrix[3]))
|
||||
if scale == 0 or scale == 1.0:
|
||||
continue
|
||||
data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n'
|
||||
#print "old line:", line
|
||||
#print "new line:", data[i]
|
||||
open(fileName, 'w').write(''.join(data))
|
|
@ -0,0 +1,7 @@
|
|||
from SVGExporter import *
|
||||
from ImageExporter import *
|
||||
Exporters = [SVGExporter, ImageExporter]
|
||||
|
||||
def listExporters():
|
||||
return Exporters[:]
|
||||
|
|
@ -160,6 +160,7 @@ class GraphicsItemMethods(object):
|
|||
def mapToDevice(self, obj):
|
||||
"""
|
||||
Return *obj* mapped from local coordinates to device coordinates (pixels).
|
||||
If there is no device mapping available, return None.
|
||||
"""
|
||||
vt = self.deviceTransform()
|
||||
if vt is None:
|
||||
|
@ -169,6 +170,7 @@ class GraphicsItemMethods(object):
|
|||
def mapFromDevice(self, obj):
|
||||
"""
|
||||
Return *obj* mapped from device coordinates (pixels) to local coordinates.
|
||||
If there is no device mapping available, return None.
|
||||
"""
|
||||
vt = self.deviceTransform()
|
||||
if vt is None:
|
||||
|
@ -176,6 +178,27 @@ class GraphicsItemMethods(object):
|
|||
vt = vt.inverted()[0]
|
||||
return vt.map(obj)
|
||||
|
||||
def mapRectToDevice(self, rect):
|
||||
"""
|
||||
Return *rect* mapped from local coordinates to device coordinates (pixels).
|
||||
If there is no device mapping available, return None.
|
||||
"""
|
||||
vt = self.deviceTransform()
|
||||
if vt is None:
|
||||
return None
|
||||
return vt.mapRect(rect)
|
||||
|
||||
def mapRectFromDevice(self, rect):
|
||||
"""
|
||||
Return *rect* mapped from device coordinates (pixels) to local coordinates.
|
||||
If there is no device mapping available, return None.
|
||||
"""
|
||||
vt = self.deviceTransform()
|
||||
if vt is None:
|
||||
return None
|
||||
vt = vt.inverted()[0]
|
||||
return vt.mapRect(rect)
|
||||
|
||||
def mapToView(self, obj):
|
||||
vt = self.viewTransform()
|
||||
if vt is None:
|
||||
|
|
|
@ -173,19 +173,16 @@ class PlotItem(GraphicsWidget):
|
|||
self.subMenus.append(sm)
|
||||
self.ctrlMenu.addMenu(sm)
|
||||
|
||||
## exporting is handled by GraphicsScene now
|
||||
#exportOpts = collections.OrderedDict([
|
||||
#('SVG - Full Plot', self.saveSvgClicked),
|
||||
#('SVG - Curves Only', self.saveSvgCurvesClicked),
|
||||
#('Image', self.saveImgClicked),
|
||||
#('CSV', self.saveCsvClicked),
|
||||
#])
|
||||
|
||||
exportOpts = collections.OrderedDict([
|
||||
('SVG - Full Plot', self.saveSvgClicked),
|
||||
('SVG - Curves Only', self.saveSvgCurvesClicked),
|
||||
('Image', self.saveImgClicked),
|
||||
('CSV', self.saveCsvClicked),
|
||||
])
|
||||
#self.vb.menu.setExportMethods(exportOpts)
|
||||
|
||||
self.vb.menu.setExportMethods(exportOpts)
|
||||
|
||||
#self.menuAction = QtGui.QWidgetAction(self)
|
||||
#self.menuAction.setDefaultWidget(w)
|
||||
#self.ctrlMenu.addAction(self.menuAction)
|
||||
|
||||
#if HAVE_WIDGETGROUP:
|
||||
self.stateGroup = WidgetGroup()
|
||||
|
|
|
@ -46,9 +46,10 @@ class ViewBoxMenu(QtGui.QMenu):
|
|||
for sig, fn in connects:
|
||||
sig.connect(getattr(self, axis.lower()+fn))
|
||||
|
||||
self.export = QtGui.QMenu("Export")
|
||||
self.setExportMethods(view.exportMethods)
|
||||
self.addMenu(self.export)
|
||||
## exporting is handled by GraphicsScene now
|
||||
#self.export = QtGui.QMenu("Export")
|
||||
#self.setExportMethods(view.exportMethods)
|
||||
#self.addMenu(self.export)
|
||||
|
||||
self.leftMenu = QtGui.QMenu("Mouse Mode")
|
||||
group = QtGui.QActionGroup(self)
|
||||
|
@ -78,7 +79,7 @@ class ViewBoxMenu(QtGui.QMenu):
|
|||
def subMenus(self):
|
||||
if not self.valid:
|
||||
self.updateState()
|
||||
return [self.viewAll] + self.axes + [self.export, self.leftMenu]
|
||||
return [self.viewAll] + self.axes + [self.leftMenu]
|
||||
|
||||
|
||||
def setExportMethods(self, methods):
|
||||
|
|
|
@ -23,7 +23,11 @@ class ParameterTree(TreeWidget):
|
|||
self.lastSel = None
|
||||
self.setRootIsDecorated(False)
|
||||
|
||||
def setParameters(self, param, root=None, depth=0, showTop=True):
|
||||
def setParameters(self, param, showTop=True):
|
||||
self.clear()
|
||||
self.addParameters(param, showTop=showTop)
|
||||
|
||||
def addParameters(self, param, root=None, depth=0, showTop=True):
|
||||
item = param.makeTreeItem(depth=depth)
|
||||
if root is None:
|
||||
root = self.invisibleRootItem()
|
||||
|
@ -37,7 +41,11 @@ class ParameterTree(TreeWidget):
|
|||
item.treeWidgetChanged()
|
||||
|
||||
for ch in param:
|
||||
self.setParameters(ch, root=item, depth=depth+1)
|
||||
self.addParameters(ch, root=item, depth=depth+1)
|
||||
|
||||
def clear(self):
|
||||
self.invisibleRootItem().takeChildren()
|
||||
|
||||
|
||||
def focusNext(self, item, forward=True):
|
||||
## Give input focus to the next (or previous) item after 'item'
|
||||
|
|
|
@ -28,18 +28,10 @@ class ComplexParameter(Parameter):
|
|||
self.b.sigValueChanged.connect(self.bChanged)
|
||||
|
||||
def aChanged(self):
|
||||
try:
|
||||
self.b.sigValueChanged.disconnect(self.bChanged)
|
||||
self.b.setValue(1.0 / self.a.value())
|
||||
finally:
|
||||
self.b.sigValueChanged.connect(self.bChanged)
|
||||
self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged)
|
||||
|
||||
def bChanged(self):
|
||||
try:
|
||||
self.a.sigValueChanged.disconnect(self.aChanged)
|
||||
self.a.setValue(1.0 / self.b.value())
|
||||
finally:
|
||||
self.a.sigValueChanged.connect(self.aChanged)
|
||||
self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged)
|
||||
|
||||
|
||||
## test add/remove
|
||||
|
|
|
@ -3,6 +3,7 @@ from Parameter import Parameter, registerParameterType
|
|||
from ParameterItem import ParameterItem
|
||||
from pyqtgraph.widgets.SpinBox import SpinBox
|
||||
from pyqtgraph.widgets.ColorButton import ColorButton
|
||||
import pyqtgraph as pg
|
||||
import os, collections
|
||||
|
||||
class WidgetParameterItem(ParameterItem):
|
||||
|
@ -263,6 +264,14 @@ class EventProxy(QtCore.QObject):
|
|||
class SimpleParameter(Parameter):
|
||||
itemClass = WidgetParameterItem
|
||||
|
||||
def __init__(self, *args, **kargs):
|
||||
Parameter.__init__(self, *args, **kargs)
|
||||
if self.opts['type'] == 'color':
|
||||
self.value = self.colorValue
|
||||
|
||||
def colorValue(self):
|
||||
return pg.mkColor(Parameter.value(self))
|
||||
|
||||
registerParameterType('int', SimpleParameter, override=True)
|
||||
registerParameterType('float', SimpleParameter, override=True)
|
||||
registerParameterType('bool', SimpleParameter, override=True)
|
||||
|
|
|
@ -411,60 +411,60 @@ class GraphicsView(QtGui.QGraphicsView):
|
|||
return Point(p11 - p01)
|
||||
|
||||
|
||||
def writeSvg(self, fileName=None):
|
||||
if fileName is None:
|
||||
self.fileDialog = FileDialog()
|
||||
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
if GraphicsView.lastFileDir is not None:
|
||||
self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
||||
self.fileDialog.show()
|
||||
self.fileDialog.fileSelected.connect(self.writeSvg)
|
||||
return
|
||||
fileName = str(fileName)
|
||||
GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
||||
self.svg = QtSvg.QSvgGenerator()
|
||||
self.svg.setFileName(fileName)
|
||||
self.svg.setSize(self.size())
|
||||
self.svg.setResolution(600)
|
||||
painter = QtGui.QPainter(self.svg)
|
||||
self.render(painter)
|
||||
|
||||
def writeImage(self, fileName=None):
|
||||
if fileName is None:
|
||||
self.fileDialog = FileDialog()
|
||||
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) ## this is the line that makes the fileDialog not show on mac
|
||||
if GraphicsView.lastFileDir is not None:
|
||||
self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
||||
self.fileDialog.show()
|
||||
self.fileDialog.fileSelected.connect(self.writeImage)
|
||||
return
|
||||
fileName = str(fileName)
|
||||
GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
||||
self.png = QtGui.QImage(self.size(), QtGui.QImage.Format_ARGB32)
|
||||
painter = QtGui.QPainter(self.png)
|
||||
rh = self.renderHints()
|
||||
self.setRenderHints(QtGui.QPainter.Antialiasing)
|
||||
self.render(painter)
|
||||
self.setRenderHints(rh)
|
||||
self.png.save(fileName)
|
||||
|
||||
def writePs(self, fileName=None):
|
||||
if fileName is None:
|
||||
self.fileDialog = FileDialog()
|
||||
self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
self.fileDialog.show()
|
||||
self.fileDialog.fileSelected.connect(self.writePs)
|
||||
return
|
||||
#def writeSvg(self, fileName=None):
|
||||
#if fileName is None:
|
||||
# fileName = str(QtGui.QFileDialog.getSaveFileName())
|
||||
printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
|
||||
printer.setOutputFileName(fileName)
|
||||
painter = QtGui.QPainter(printer)
|
||||
self.render(painter)
|
||||
painter.end()
|
||||
#self.fileDialog = FileDialog()
|
||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
#if GraphicsView.lastFileDir is not None:
|
||||
#self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
||||
#self.fileDialog.show()
|
||||
#self.fileDialog.fileSelected.connect(self.writeSvg)
|
||||
#return
|
||||
#fileName = str(fileName)
|
||||
#GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
||||
#self.svg = QtSvg.QSvgGenerator()
|
||||
#self.svg.setFileName(fileName)
|
||||
#self.svg.setSize(self.size())
|
||||
#self.svg.setResolution(600)
|
||||
#painter = QtGui.QPainter(self.svg)
|
||||
#self.render(painter)
|
||||
|
||||
#def writeImage(self, fileName=None):
|
||||
#if fileName is None:
|
||||
#self.fileDialog = FileDialog()
|
||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) ## this is the line that makes the fileDialog not show on mac
|
||||
#if GraphicsView.lastFileDir is not None:
|
||||
#self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
||||
#self.fileDialog.show()
|
||||
#self.fileDialog.fileSelected.connect(self.writeImage)
|
||||
#return
|
||||
#fileName = str(fileName)
|
||||
#GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
||||
#self.png = QtGui.QImage(self.size(), QtGui.QImage.Format_ARGB32)
|
||||
#painter = QtGui.QPainter(self.png)
|
||||
#rh = self.renderHints()
|
||||
#self.setRenderHints(QtGui.QPainter.Antialiasing)
|
||||
#self.render(painter)
|
||||
#self.setRenderHints(rh)
|
||||
#self.png.save(fileName)
|
||||
|
||||
#def writePs(self, fileName=None):
|
||||
#if fileName is None:
|
||||
#self.fileDialog = FileDialog()
|
||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||
#self.fileDialog.show()
|
||||
#self.fileDialog.fileSelected.connect(self.writePs)
|
||||
#return
|
||||
##if fileName is None:
|
||||
## fileName = str(QtGui.QFileDialog.getSaveFileName())
|
||||
#printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
|
||||
#printer.setOutputFileName(fileName)
|
||||
#painter = QtGui.QPainter(printer)
|
||||
#self.render(painter)
|
||||
#painter.end()
|
||||
|
||||
def dragEnterEvent(self, ev):
|
||||
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
|
||||
|
|
Loading…
Reference in New Issue