diff --git a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po index fd7cbbb..a12070e 100644 --- a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po +++ b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po @@ -57,6 +57,12 @@ msgstr "Écrans haute résolution" msgid "The splash screen and the watermark of empty plots are no longer blurry with a display scaling greater than 100% (e.g. 125%): when their image has a `@2x` high-resolution variant (e.g. `splash@2x.png` next to `splash.png`), it is used to display them at the resolution of the screen instead of upscaling the image. The fallback splash screen, shown when no image is available, is also drawn at the resolution of the screen." msgstr "L'écran de démarrage et le filigrane des graphiques vides ne sont plus flous avec une mise à l'échelle de l'affichage supérieure à 100 % (par exemple 125 %) : lorsque leur image dispose d'une variante haute résolution `@2x` (par exemple `splash@2x.png` à côté de `splash.png`), celle-ci est utilisée pour les afficher à la résolution de l'écran au lieu d'agrandir l'image. L'écran de démarrage de repli, affiché lorsqu'aucune image n'est disponible, est lui aussi dessiné à la résolution de l'écran." +msgid "Plot widgets" +msgstr "Widgets de tracé" + +msgid "Like the splash screen image, the watermark image of empty plots (`watermark_image_path` option) may now be given by its name: the image is then looked up in the image paths registered with guidata (see `guidata.configtools.add_image_path`), and its `@2x` high-resolution variant is used on high-DPI displays." +msgstr "Comme l'image de l'écran de démarrage, l'image du filigrane des graphiques vides (option `watermark_image_path`) peut désormais être désignée par son nom : l'image est alors recherchée dans les chemins d'images enregistrés auprès de guidata (voir `guidata.configtools.add_image_path`), et sa variante haute résolution `@2x` est utilisée sur les écrans haute résolution." + msgid "Qt helpers" msgstr "Utilitaires Qt" diff --git a/doc/release_notes/release_1.01.md b/doc/release_notes/release_1.01.md index 8668e4b..269b098 100644 --- a/doc/release_notes/release_1.01.md +++ b/doc/release_notes/release_1.01.md @@ -40,6 +40,10 @@ * The splash screen and the watermark of empty plots are no longer blurry with a display scaling greater than 100% (e.g. 125%): when their image has a `@2x` high-resolution variant (e.g. `splash@2x.png` next to `splash.png`), it is used to display them at the resolution of the screen instead of upscaling the image. The fallback splash screen, shown when no image is available, is also drawn at the resolution of the screen. +### Plot widgets ### + +* Like the splash screen image, the watermark image of empty plots (`watermark_image_path` option) may now be given by its name: the image is then looked up in the image paths registered with guidata (see `guidata.configtools.add_image_path`), and its `@2x` high-resolution variant is used on high-DPI displays. + ### Qt helpers ### * `block_signals` now restores the previous signal-blocking state of the widget and of its children instead of unblocking them unconditionally: nested blocking contexts and widgets already blocked by the caller keep their state. diff --git a/sigimax/config.py b/sigimax/config.py index 0c297f5..115093c 100644 --- a/sigimax/config.py +++ b/sigimax/config.py @@ -1420,8 +1420,9 @@ def __init__(self) -> None: category="view", default="", expected_type=str, - description="Path to the watermark image displayed on empty plots. " - "If empty, no watermark is shown.", + description="Path to the watermark image displayed on empty plots, or " + "name of an image registered with guidata (like the splash screen " + "image). If empty, no watermark is shown.", ) self.sig_format = TypedOptionField( diff --git a/sigimax/tests/widgets/test_plotdock_watermark.py b/sigimax/tests/widgets/test_plotdock_watermark.py new file mode 100644 index 0000000..f267a0b --- /dev/null +++ b/sigimax/tests/widgets/test_plotdock_watermark.py @@ -0,0 +1,78 @@ +# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. + +"""Plot watermark resource resolution tests.""" + +from __future__ import annotations + +from unittest.mock import patch + +from guidata.configtools import get_pixmap +from guidata.qthelpers import qt_app_context +from plotpy.constants import PlotType +from qtpy import QtCore as QC +from qtpy import QtGui as QG +from qtpy import QtWidgets as QW + +from sigimax.config import get_conf +from sigimax.widgets.plotdock import DockablePlotWidget + + +def _save_image(path, width: int, height: int, color: str) -> None: + """Save a plain image of the specified size and color.""" + pixmap = QG.QPixmap(width, height) + pixmap.fill(QG.QColor(color)) + assert pixmap.save(str(path)) + + +def test_watermark_resolves_image_name(tmp_path) -> None: + """An image name is resolved through guidata's registered image paths, and the + ``@2x`` variant of the image is used on high-DPI displays.""" + image_path = tmp_path / "derived-watermark.png" + loaded: list[tuple[str, QG.QPixmap]] = [] + + def load_pixmap(name: str, default: str | None) -> QG.QPixmap: + """Load the pixmap as on a high-DPI display, and record it.""" + pixmap = get_pixmap(name, default, 2.0) + loaded.append((name, pixmap)) + return pixmap + + with qt_app_context(exec_loop=False): + _save_image(image_path, 64, 32, "red") + _save_image(tmp_path / "derived-watermark@2x.png", 128, 64, "blue") + parent = QW.QWidget() + with ( + get_conf().watermark_image_path.context("derived-watermark.png"), + patch( + "sigimax.widgets.plotdock.get_image_file_path", + return_value=str(image_path), + ) as resolve, + patch("sigimax.widgets.plotdock.get_pixmap", side_effect=load_pixmap), + ): + dock = DockablePlotWidget(parent, PlotType.CURVE) + + resolve.assert_called_once_with("derived-watermark.png", default=None) + assert [name for name, _pixmap in loaded] == [str(image_path)] + assert not dock.watermark.pixmap().isNull() + # The label's pixmap is not checked: with Qt 6, ``QLabel.pixmap`` does not + # return the device pixel ratio of the pixmap passed to ``setPixmap``. + pixmap = loaded[0][1] + assert pixmap.devicePixelRatio() == 2.0 + assert pixmap.size() == QC.QSize(128, 64) + assert pixmap.toImage().pixelColor(0, 0) == QG.QColor("blue") + + +def test_missing_watermark_resource_does_not_fail() -> None: + """An unresolved watermark image must not prevent plot widget creation.""" + with qt_app_context(exec_loop=False): + parent = QW.QWidget() + with ( + get_conf().watermark_image_path.context("missing-watermark.png"), + patch( + "sigimax.widgets.plotdock.get_image_file_path", + side_effect=RuntimeError("not found"), + ), + ): + dock = DockablePlotWidget(parent, PlotType.CURVE) + + pixmap = dock.watermark.pixmap() + assert pixmap is None or pixmap.isNull() diff --git a/sigimax/widgets/plotdock.py b/sigimax/widgets/plotdock.py index d055190..5c74171 100644 --- a/sigimax/widgets/plotdock.py +++ b/sigimax/widgets/plotdock.py @@ -31,7 +31,7 @@ from typing import TYPE_CHECKING import numpy as np -from guidata.configtools import get_pixmap +from guidata.configtools import get_image_file_path, get_pixmap from guidata.qthelpers import is_dark_theme from guidata.widgets.dockable import DockableWidget from plotpy.constants import PlotType @@ -331,20 +331,28 @@ def _apply_dock_location(self) -> None: def _setup_watermark(self) -> None: """Create the watermark label from the configured image path. - If ``Conf.watermark_image_path`` is empty, no watermark is created. On - high-DPI displays, the ``@2x`` high-resolution variant of the image is used + If ``Conf.watermark_image_path`` is empty, no watermark is created. Like the + splash screen image, it may be an image name, looked up in the image paths + registered with guidata (see :func:`guidata.configtools.get_image_file_path`). + On high-DPI displays, the ``@2x`` high-resolution variant of the image is used if available (see :func:`guidata.configtools.get_pixmap`). """ path = get_conf().watermark_image_path.get() - if path: - self.watermark = QW.QLabel() - if osp.isfile(path): - pixmap = get_pixmap(osp.abspath(path), default=None) - else: - pixmap = QG.QPixmap(path) - self.watermark.setPixmap(pixmap) - else: + if not path: self.watermark = None + return + if not osp.isfile(path): + try: + path = get_image_file_path(path, default=None) + except RuntimeError: + pass + if osp.isfile(path): + pixmap = get_pixmap(osp.abspath(path), default=None) + else: + # Not a file (e.g. a Qt resource path) + pixmap = QG.QPixmap(path) + self.watermark = QW.QLabel() + self.watermark.setPixmap(pixmap) def __get_toolbar_row_col(self) -> tuple[int, int]: """Return toolbar row and column"""