Bugfix/wrong translate implementation (#1520)

* Removes all translate call on the parameter name and moves them into the title instead allowing to decouple visualization from code logic

* Removes all translate calls from the Exporter class property Name and moves the translation logic when setting the QListWidgetItems for the formatList

* Adds missing call to translation function for the export action on GraphicsScene
This commit is contained in:
Sérgio Peixoto 2021-01-28 16:42:18 +00:00 committed by GitHub
parent b094945dbc
commit 3f02b30140
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 73 deletions

View File

@ -116,7 +116,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
self.lastHoverEvent = None self.lastHoverEvent = None
self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks 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.contextMenu[0].triggered.connect(self.showExportDialog)
self.exportDialog = None self.exportDialog = None

View File

@ -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 exporters as exporters
from .. import functions as fn from .. import functions as fn
from ..graphicsItems.ViewBox import ViewBox from ..graphicsItems.ViewBox import ViewBox
@ -9,6 +9,12 @@ ui_template = importlib.import_module(
f'.exportDialogTemplate_{QT_LIB.lower()}', package=__package__) 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): class ExportDialog(QtGui.QWidget):
def __init__(self, scene): def __init__(self, scene):
QtGui.QWidget.__init__(self) QtGui.QWidget.__init__(self)
@ -94,16 +100,14 @@ class ExportDialog(QtGui.QWidget):
def updateFormatList(self): def updateFormatList(self):
current = self.ui.formatList.currentItem() current = self.ui.formatList.currentItem()
if current is not None:
current = str(current.text())
self.ui.formatList.clear() self.ui.formatList.clear()
self.exporterClasses = {}
gotCurrent = False gotCurrent = False
for exp in exporters.listExporters(): for exp in exporters.listExporters():
self.ui.formatList.addItem(exp.Name) item = FormatExportListWidgetItem(exp, QtCore.QCoreApplication.translate('Exporter', exp.Name))
self.exporterClasses[exp.Name] = exp self.ui.formatList.addItem(item)
if exp.Name == current: if item == current:
self.ui.formatList.setCurrentRow(self.ui.formatList.count()-1) self.ui.formatList.setCurrentRow(self.ui.formatList.count() - 1)
gotCurrent = True gotCurrent = True
if not gotCurrent: if not gotCurrent:
@ -114,7 +118,7 @@ class ExportDialog(QtGui.QWidget):
self.currentExporter = None self.currentExporter = None
self.ui.paramTree.clear() self.ui.paramTree.clear()
return return
expClass = self.exporterClasses[str(item.text())] expClass = item.expClass
exp = expClass(item=self.ui.itemTree.currentItem().gitem) exp = expClass(item=self.ui.itemTree.currentItem().gitem)
params = exp.parameters() params = exp.parameters()

View File

@ -11,14 +11,14 @@ __all__ = ['CSVExporter']
class CSVExporter(Exporter): class CSVExporter(Exporter):
Name = translate("Exporter", "CSV from plot data") Name = "CSV from plot data"
windows = [] windows = []
def __init__(self, item): def __init__(self, item):
Exporter.__init__(self, item) Exporter.__init__(self, item)
self.params = Parameter(name='params', type='group', children=[ self.params = Parameter(name='params', type='group', children=[
{'name': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, {'name': 'separator', 'title': translate("Exporter", 'separator'), 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']},
{'name': translate("Exporter", 'precision'), 'type': 'int', 'value': 10, 'limits': [0, None]}, {'name': 'precision', 'title': 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': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}
]) ])
def parameters(self): def parameters(self):
@ -83,5 +83,3 @@ class CSVExporter(Exporter):
CSVExporter.register() CSVExporter.register()

View File

@ -16,15 +16,16 @@ __all__ = ['HDF5Exporter']
class HDF5Exporter(Exporter): class HDF5Exporter(Exporter):
Name = translate("Exporter", "HDF5 Export: plot (x,y)") Name = "HDF5 Export: plot (x,y)"
windows = [] windows = []
allowCopy = False allowCopy = False
def __init__(self, item): def __init__(self, item):
Exporter.__init__(self, item) Exporter.__init__(self, item)
self.params = Parameter(name='params', type='group', children=[ self.params = Parameter(name='params', type='group', children=[
{'name': translate("Exporter", 'Name'), 'type': 'str', 'value': 'Export',}, {'name': 'Name', 'title': 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': 'columnMode', 'title': translate("Exporter", 'columnMode'), 'type': 'list',
'values': ['(x,y) per plot', '(x,y,y,y) for all plots']},
]) ])
def parameters(self): def parameters(self):
@ -41,11 +42,11 @@ class HDF5Exporter(Exporter):
if fileName is None: if fileName is None:
self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"]) self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"])
return return
dsname = self.params[translate("Exporter", 'Name')] dsname = self.params['Name']
fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite"
data = [] 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 # Check if the arrays are ragged
len_first = len(self.item.curves[0].getData()[0]) if self.item.curves[0] else None 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) ragged = any(len(i.getData()[0]) != len_first for i in self.item.curves)

View File

@ -8,7 +8,7 @@ translate = QtCore.QCoreApplication.translate
__all__ = ['ImageExporter'] __all__ = ['ImageExporter']
class ImageExporter(Exporter): class ImageExporter(Exporter):
Name = f"{translate('Exporter', 'Image File')} (PNG, TIF, JPG, ...)" Name = "Image File (PNG, TIF, JPG, ...)"
allowCopy = True allowCopy = True
def __init__(self, item): def __init__(self, item):
@ -22,26 +22,28 @@ class ImageExporter(Exporter):
bg = bgbrush.color() bg = bgbrush.color()
if bgbrush.style() == QtCore.Qt.NoBrush: if bgbrush.style() == QtCore.Qt.NoBrush:
bg.setAlpha(0) bg.setAlpha(0)
self.params = Parameter(name='params', type='group', children=[ self.params = Parameter(name='params', type='group', children=[
{'name': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()), 'limits': (0, None)}, {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'int', 'value': int(tr.width()),
{'name': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()), 'limits': (0, None)}, 'limits': (0, None)},
{'name': translate("Exporter", 'antialias'), 'type': 'bool', 'value': True}, {'name': 'height', 'title': translate("Exporter", 'height'), 'type': 'int', 'value': int(tr.height()),
{'name': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, 'limits': (0, None)},
{'name': translate("Exporter", 'invertValue'), 'type': 'bool', 'value': False} {'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('width').sigValueChanged.connect(self.widthChanged)
self.params.param(translate("Exporter", 'height')).sigValueChanged.connect(self.heightChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged)
def widthChanged(self): def widthChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = float(sr.height()) / sr.width() 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): def heightChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = float(sr.width()) / sr.height() 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): def parameters(self):
return self.params return self.params
@ -62,8 +64,8 @@ class ImageExporter(Exporter):
self.fileSaveDialog(filter=filter) self.fileSaveDialog(filter=filter)
return return
w = int(self.params[translate("Exporter", 'width')]) w = int(self.params['width'])
h = int(self.params[translate("Exporter", 'height')]) h = int(self.params['height'])
if w == 0 or h == 0: if w == 0 or h == 0:
raise Exception("Cannot export image with size=0 (requested " raise Exception("Cannot export image with size=0 (requested "
"export size is %dx%d)" % (w, h)) "export size is %dx%d)" % (w, h))
@ -72,7 +74,7 @@ class ImageExporter(Exporter):
sourceRect = self.getSourceRect() sourceRect = self.getSourceRect()
bg = np.empty((h, w, 4), dtype=np.ubyte) bg = np.empty((h, w, 4), dtype=np.ubyte)
color = self.params[translate("Exporter", 'background')] color = self.params['background']
bg[:,:,0] = color.blue() bg[:,:,0] = color.blue()
bg[:,:,1] = color.green() bg[:,:,1] = color.green()
bg[:,:,2] = color.red() bg[:,:,2] = color.red()
@ -91,11 +93,11 @@ class ImageExporter(Exporter):
#dtr = painter.deviceTransform() #dtr = painter.deviceTransform()
try: try:
self.setExportMode(True, { self.setExportMode(True, {
'antialias': self.params[translate("Exporter", 'antialias')], 'antialias': self.params['antialias'],
'background': self.params[translate("Exporter", 'background')], 'background': self.params['background'],
'painter': painter, 'painter': painter,
'resolutionScale': resolutionScale}) '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)) self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
finally: finally:
self.setExportMode(False) self.setExportMode(False)
@ -114,5 +116,4 @@ class ImageExporter(Exporter):
else: else:
return self.png.save(fileName) return self.png.save(fileName)
ImageExporter.register() ImageExporter.register()

View File

@ -4,8 +4,6 @@ from .Exporter import Exporter
from .. import PlotItem from .. import PlotItem
from .. import functions as fn from .. import functions as fn
translate = QtCore.QCoreApplication.translate
__all__ = ['MatplotlibExporter'] __all__ = ['MatplotlibExporter']
""" """
@ -32,7 +30,7 @@ publication. Fonts are not vectorized (outlined), and window colors are white.
""" """
class MatplotlibExporter(Exporter): class MatplotlibExporter(Exporter):
Name = translate('Exporter', "Matplotlib Window") Name = "Matplotlib Window"
windows = [] windows = []
def __init__(self, item): def __init__(self, item):

View File

@ -14,8 +14,10 @@ class PrintExporter(Exporter):
Exporter.__init__(self, item) Exporter.__init__(self, item)
tr = self.getTargetRect() tr = self.getTargetRect()
self.params = Parameter(name='params', type='group', children=[ 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': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': 0.1,
{'name': translate("Exporter", 'height'), 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, '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('width').sigValueChanged.connect(self.widthChanged)
self.params.param('height').sigValueChanged.connect(self.heightChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged)
@ -23,13 +25,13 @@ class PrintExporter(Exporter):
def widthChanged(self): def widthChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = sr.height() / sr.width() 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): def heightChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = sr.width() / sr.height() 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): def parameters(self):
return self.params return self.params

View File

@ -13,7 +13,7 @@ translate = QtCore.QCoreApplication.translate
__all__ = ['SVGExporter'] __all__ = ['SVGExporter']
class SVGExporter(Exporter): class SVGExporter(Exporter):
Name = translate("Exporter", "Scalable Vector Graphics (SVG)") Name = "Scalable Vector Graphics (SVG)"
allowCopy=True allowCopy=True
def __init__(self, item): def __init__(self, item):
@ -30,26 +30,28 @@ class SVGExporter(Exporter):
bg.setAlpha(0) bg.setAlpha(0)
self.params = Parameter(name='params', type='group', children=[ self.params = Parameter(name='params', type='group', children=[
{'name': translate("Exporter", 'background'), 'type': 'color', 'value': bg}, {'name': 'background', 'title': translate("Exporter", 'background'), 'type': 'color', 'value': bg},
{'name': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, {'name': 'width', 'title': translate("Exporter", 'width'), 'type': 'float', 'value': tr.width(),
{'name': translate("Exporter", 'height'), 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, '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': 'viewbox clipping', 'type': 'bool', 'value': True},
#{'name': 'normalize coordinates', '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."}, "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('width').sigValueChanged.connect(self.widthChanged)
self.params.param(translate("Exporter", 'height')).sigValueChanged.connect(self.heightChanged) self.params.param('height').sigValueChanged.connect(self.heightChanged)
def widthChanged(self): def widthChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = sr.height() / sr.width() 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): def heightChanged(self):
sr = self.getSourceRect() sr = self.getSourceRect()
ar = sr.width() / sr.height() 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): def parameters(self):
return self.params return self.params
@ -63,9 +65,9 @@ class SVGExporter(Exporter):
## Instead, we will use Qt to generate SVG for each item independently, ## Instead, we will use Qt to generate SVG for each item independently,
## then manually reconstruct the entire document. ## then manually reconstruct the entire document.
options = {ch.name():ch.value() for ch in self.params.children()} options = {ch.name():ch.value() for ch in self.params.children()}
options['background'] = self.params[translate("Exporter", 'background')] options['background'] = self.params['background']
options['width'] = self.params[translate("Exporter", 'width')] options['width'] = self.params['width']
options['height'] = self.params[translate("Exporter", 'height')] options['height'] = self.params['height']
xml = generateSvg(self.item, options) xml = generateSvg(self.item, options)
if toBytes: 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. ## 1) Qt SVG does not implement clipping paths. This is absurd.
## The solution is to let Qt generate SVG for each item independently, ## The solution is to let Qt generate SVG for each item independently,
## then glue them together manually with clipping. ## then glue them together manually with clipping.
## ##
## The format Qt generates for all items looks like this: ## The format Qt generates for all items looks like this:
## ##
## <g> ## <g>
## <g transform="matrix(...)"> ## <g transform="matrix(...)">
## one or more of: <path/> or <polyline/> or <text/> ## one or more of: <path/> or <polyline/> or <text/>
@ -139,21 +141,21 @@ def _generateItemSvg(item, nodes=None, root=None, options={}):
## </g> ## </g>
## . . . ## . . .
## </g> ## </g>
## ##
## 2) There seems to be wide disagreement over whether path strokes ## 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/ ## see: http://web.mit.edu/jonas/www/anisotropy/
## Given that both inkscape and illustrator seem to prefer isotropic ## Given that both inkscape and illustrator seem to prefer isotropic
## scaling, we will optimize for those cases. ## scaling, we will optimize for those cases.
## ##
## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but
## inkscape only supports 1.1. ## inkscape only supports 1.1.
## ##
## Both 2 and 3 can be addressed by drawing all items in world coordinates. ## Both 2 and 3 can be addressed by drawing all items in world coordinates.
profiler = debug.Profiler() 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. ## this allows us to ensure all elements receive unique names.
nodes = {} nodes = {}
@ -424,7 +426,7 @@ def itemTransform(item, root):
tr.translate(pos.x(), pos.y()) tr.translate(pos.x(), pos.y())
tr = item.transform() * tr tr = item.transform() * tr
else: 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 ## an item that ignores its transformation
nextRoot = item nextRoot = item
while True: while True: