Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
4 changes: 4 additions & 0 deletions doc/release_notes/release_1.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions sigimax/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
78 changes: 78 additions & 0 deletions sigimax/tests/widgets/test_plotdock_watermark.py
Original file line number Diff line number Diff line change
@@ -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()
30 changes: 19 additions & 11 deletions sigimax/widgets/plotdock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down
Loading