diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py index 71631613..1c6e76b3 100644 --- a/pyqtgraph/GraphicsScene/GraphicsScene.py +++ b/pyqtgraph/GraphicsScene/GraphicsScene.py @@ -116,7 +116,7 @@ class GraphicsScene(QtGui.QGraphicsScene): self.lastHoverEvent = None self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks - self.contextMenu = [QtGui.QAction("Export...", self)] + self.contextMenu = [QtGui.QAction(QtCore.QCoreApplication.translate("GraphicsScene", "Export..."), self)] self.contextMenu[0].triggered.connect(self.showExportDialog) self.exportDialog = None diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py index 60cb6cd1..f4af2651 100644 --- a/pyqtgraph/GraphicsScene/exportDialog.py +++ b/pyqtgraph/GraphicsScene/exportDialog.py @@ -1,4 +1,4 @@ -from ..Qt import QtCore, QtGui, QT_LIB +from ..Qt import QtCore, QtGui, QtWidgets, QT_LIB from .. import exporters as exporters from .. import functions as fn from ..graphicsItems.ViewBox import ViewBox @@ -9,6 +9,12 @@ ui_template = importlib.import_module( f'.exportDialogTemplate_{QT_LIB.lower()}', package=__package__) +class FormatExportListWidgetItem(QtWidgets.QListWidgetItem): + def __init__(self, expClass, *args, **kwargs): + QtWidgets.QListWidgetItem.__init__(self, *args, **kwargs) + self.expClass = expClass + + class ExportDialog(QtGui.QWidget): def __init__(self, scene): QtGui.QWidget.__init__(self) @@ -94,16 +100,14 @@ class ExportDialog(QtGui.QWidget): 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) + item = FormatExportListWidgetItem(exp, QtCore.QCoreApplication.translate('Exporter', exp.Name)) + self.ui.formatList.addItem(item) + if item == current: + self.ui.formatList.setCurrentRow(self.ui.formatList.count() - 1) gotCurrent = True if not gotCurrent: @@ -114,7 +118,7 @@ class ExportDialog(QtGui.QWidget): self.currentExporter = None self.ui.paramTree.clear() return - expClass = self.exporterClasses[str(item.text())] + expClass = item.expClass exp = expClass(item=self.ui.itemTree.currentItem().gitem) params = exp.parameters() diff --git a/pyqtgraph/exporters/CSVExporter.py b/pyqtgraph/exporters/CSVExporter.py index bc1de8e5..aec27a16 100644 --- a/pyqtgraph/exporters/CSVExporter.py +++ b/pyqtgraph/exporters/CSVExporter.py @@ -11,14 +11,14 @@ __all__ = ['CSVExporter'] class CSVExporter(Exporter): - Name = translate("Exporter", "CSV from plot data") + Name = "CSV from plot data" windows = [] def __init__(self, item): Exporter.__init__(self, item) self.params = Parameter(name='params', type='group', children=[ - {'name': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, - {'name': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]}, - {'name': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']} + {'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, + {'name': 'precision', 'title': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]}, + {'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']} ]) def parameters(self): @@ -83,5 +83,3 @@ class CSVExporter(Exporter): CSVExporter.register() - - diff --git a/pyqtgraph/exporters/HDF5Exporter.py b/pyqtgraph/exporters/HDF5Exporter.py index b00e0d73..83499a86 100644 --- a/pyqtgraph/exporters/HDF5Exporter.py +++ b/pyqtgraph/exporters/HDF5Exporter.py @@ -16,15 +16,16 @@ __all__ = ['HDF5Exporter'] class HDF5Exporter(Exporter): - Name = translate("Exporter", "HDF5 Export: plot (x,y)") + Name = "HDF5 Export: plot (x,y)" windows = [] allowCopy = False def __init__(self, item): Exporter.__init__(self, item) self.params = Parameter(name='params', type='group', children=[ - {'name': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export',}, - {'name': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}, + {'name': 'Name', 'title': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export', }, + {'name': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', + 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}, ]) def parameters(self): @@ -41,11 +42,11 @@ class HDF5Exporter(Exporter): if fileName is None: self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"]) return - dsname = self.params[translate("Exporter", 'Name')] - fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" + dsname = self.params['Name'] + fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" data = [] - appendAllX = self.params[translate("Exporter", 'columnMode')] == '(x,y) per plot' + appendAllX = self.params['columnMode'] == '(x,y) per plot' # Check if the arrays are ragged len_first = len(self.item.curves[0].getData()[0]) if self.item.curves[0] else None ragged = any(len(i.getData()[0]) != len_first for i in self.item.curves) diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py index de850f60..1e1ac103 100644 --- a/pyqtgraph/exporters/ImageExporter.py +++ b/pyqtgraph/exporters/ImageExporter.py @@ -8,7 +8,7 @@ translate = QtCore.QCoreApplication.translate __all__ = ['ImageExporter'] class ImageExporter(Exporter): - Name = f"{translate('Exporter', 'Image File')} (PNG, TIF, JPG, ...)" + Name = "Image File (PNG, TIF, JPG, ...)" allowCopy = True def __init__(self, item): @@ -22,26 +22,28 @@ class ImageExporter(Exporter): bg = bgbrush.color() if bgbrush.style() == QtCore.Qt.NoBrush: bg.setAlpha(0) - + self.params = Parameter(name='params', type='group', children=[ - {'name': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()), 'limits': (0, None)}, - {'name': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()), 'limits': (0, None)}, - {'name': translate("Exporter", 'antialias'), 'type': 'bool', 'value': True}, - {'name': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, - {'name': translate("Exporter", 'invertValue'), 'type': 'bool', 'value': False} + {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()), + 'limits': (0, None)}, + {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()), + 'limits': (0, None)}, + {'name': 'antialias', 'title': translate("Exporter", 'antialias'), 'type': 'bool', 'value': True}, + {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, + {'name': 'invertValue', 'title': translate("Exporter", 'invertValue'), 'type': 'bool', 'value': False} ]) - self.params.param(translate("Exporter", 'width')).sigValueChanged.connect(self.widthChanged) - self.params.param(translate("Exporter", 'height')).sigValueChanged.connect(self.heightChanged) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) def widthChanged(self): sr = self.getSourceRect() ar = float(sr.height()) / sr.width() - self.params.param(translate("Exporter", 'height')).setValue(int(self.params[translate("Exporter", 'width')] * ar), blockSignal=self.heightChanged) + self.params.param('height').setValue(int(self.params['width'] * ar), blockSignal=self.heightChanged) def heightChanged(self): sr = self.getSourceRect() ar = float(sr.width()) / sr.height() - self.params.param(translate("Exporter", 'width')).setValue(int(self.params[translate("Exporter", 'height')] * ar), blockSignal=self.widthChanged) + self.params.param('width').setValue(int(self.params['height'] * ar), blockSignal=self.widthChanged) def parameters(self): return self.params @@ -62,8 +64,8 @@ class ImageExporter(Exporter): self.fileSaveDialog(filter=filter) return - w = int(self.params[translate("Exporter", 'width')]) - h = int(self.params[translate("Exporter", 'height')]) + w = int(self.params['width']) + h = int(self.params['height']) if w == 0 or h == 0: raise Exception("Cannot export image with size=0 (requested " "export size is %dx%d)" % (w, h)) @@ -72,7 +74,7 @@ class ImageExporter(Exporter): sourceRect = self.getSourceRect() bg = np.empty((h, w, 4), dtype=np.ubyte) - color = self.params[translate("Exporter", 'background')] + color = self.params['background'] bg[:,:,0] = color.blue() bg[:,:,1] = color.green() bg[:,:,2] = color.red() @@ -91,11 +93,11 @@ class ImageExporter(Exporter): #dtr = painter.deviceTransform() try: self.setExportMode(True, { - 'antialias': self.params[translate("Exporter", 'antialias')], - 'background': self.params[translate("Exporter", 'background')], + 'antialias': self.params['antialias'], + 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale}) - painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params[translate("Exporter", 'antialias')]) + painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) finally: self.setExportMode(False) @@ -114,5 +116,4 @@ class ImageExporter(Exporter): else: return self.png.save(fileName) -ImageExporter.register() - +ImageExporter.register() diff --git a/pyqtgraph/exporters/Matplotlib.py b/pyqtgraph/exporters/Matplotlib.py index 38076dd8..b02b3161 100644 --- a/pyqtgraph/exporters/Matplotlib.py +++ b/pyqtgraph/exporters/Matplotlib.py @@ -4,8 +4,6 @@ from .Exporter import Exporter from .. import PlotItem from .. import functions as fn -translate = QtCore.QCoreApplication.translate - __all__ = ['MatplotlibExporter'] """ @@ -32,7 +30,7 @@ publication. Fonts are not vectorized (outlined), and window colors are white. """ class MatplotlibExporter(Exporter): - Name = translate('Exporter', "Matplotlib Window") + Name = "Matplotlib Window" windows = [] def __init__(self, item): diff --git a/pyqtgraph/exporters/PrintExporter.py b/pyqtgraph/exporters/PrintExporter.py index e16823e8..1b9860bd 100644 --- a/pyqtgraph/exporters/PrintExporter.py +++ b/pyqtgraph/exporters/PrintExporter.py @@ -14,8 +14,10 @@ class PrintExporter(Exporter): Exporter.__init__(self, item) tr = self.getTargetRect() self.params = Parameter(name='params', type='group', children=[ - {'name': translate("Exporter", 'width'), 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, - {'name': translate("Exporter", 'height'), 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': 0.1, + 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', + 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, ]) self.params.param('width').sigValueChanged.connect(self.widthChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged) @@ -23,13 +25,13 @@ class PrintExporter(Exporter): def widthChanged(self): sr = self.getSourceRect() ar = sr.height() / sr.width() - self.params.param(translate("Exporter", 'height')).setValue(self.params[translate("Exporter", 'width')] * ar, blockSignal=self.heightChanged) - + 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(translate("Exporter", 'width')).setValue(self.params[translate("Exporter", 'height')] * ar, blockSignal=self.widthChanged) - + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + def parameters(self): return self.params diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py index 8c59d8da..ac02990a 100644 --- a/pyqtgraph/exporters/SVGExporter.py +++ b/pyqtgraph/exporters/SVGExporter.py @@ -13,7 +13,7 @@ translate = QtCore.QCoreApplication.translate __all__ = ['SVGExporter'] class SVGExporter(Exporter): - Name = translate("Exporter", "Scalable Vector Graphics (SVG)") + Name = "Scalable Vector Graphics (SVG)" allowCopy=True def __init__(self, item): @@ -30,26 +30,28 @@ class SVGExporter(Exporter): bg.setAlpha(0) self.params = Parameter(name='params', type='group', children=[ - {'name': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, - {'name': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, - {'name': translate("Exporter", 'height'), 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, + {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, + {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(), + 'limits': (0, None)}, + {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'float', 'value': tr.height(), + 'limits': (0, None)}, #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, - {'name': translate("Exporter", 'scaling stroke'), 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, " + {'name': 'scaling stroke', 'title': translate("Exporter", 'scaling stroke'), 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, " "which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."}, ]) - self.params.param(translate("Exporter", 'width')).sigValueChanged.connect(self.widthChanged) - self.params.param(translate("Exporter", 'height')).sigValueChanged.connect(self.heightChanged) + 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(translate("Exporter", 'height')).setValue(self.params[translate("Exporter", 'width')] * ar, blockSignal=self.heightChanged) + 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(translate("Exporter", 'width')).setValue(self.params[translate("Exporter", 'height')] * ar, blockSignal=self.widthChanged) + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) def parameters(self): return self.params @@ -63,9 +65,9 @@ class SVGExporter(Exporter): ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. options = {ch.name():ch.value() for ch in self.params.children()} - options['background'] = self.params[translate("Exporter", 'background')] - options['width'] = self.params[translate("Exporter", 'width')] - options['height'] = self.params[translate("Exporter", 'height')] + options['background'] = self.params['background'] + options['width'] = self.params['width'] + options['height'] = self.params['height'] xml = generateSvg(self.item, options) if toBytes: @@ -127,9 +129,9 @@ def _generateItemSvg(item, nodes=None, root=None, options={}): ## 1) Qt SVG does not implement clipping paths. This is absurd. ## The solution is to let Qt generate SVG for each item independently, ## then glue them together manually with clipping. - ## + ## ## The format Qt generates for all items looks like this: - ## + ## ## ## ## one or more of: or or @@ -139,21 +141,21 @@ def _generateItemSvg(item, nodes=None, root=None, options={}): ## ## . . . ## - ## + ## ## 2) There seems to be wide disagreement over whether path strokes - ## should be scaled anisotropically. + ## should be scaled anisotropically. ## see: http://web.mit.edu/jonas/www/anisotropy/ ## Given that both inkscape and illustrator seem to prefer isotropic - ## scaling, we will optimize for those cases. - ## - ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but - ## inkscape only supports 1.1. - ## + ## scaling, we will optimize for those cases. + ## + ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but + ## inkscape only supports 1.1. + ## ## Both 2 and 3 can be addressed by drawing all items in world coordinates. profiler = debug.Profiler() - if nodes is None: ## nodes maps all node IDs to their XML element. + if nodes is None: ## nodes maps all node IDs to their XML element. ## this allows us to ensure all elements receive unique names. nodes = {} @@ -424,7 +426,7 @@ def itemTransform(item, root): tr.translate(pos.x(), pos.y()) tr = item.transform() * tr else: - ## find next parent that is either the root item or + ## find next parent that is either the root item or ## an item that ignores its transformation nextRoot = item while True: