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 @@ -51,6 +51,12 @@ msgstr "Avec Qt 5, les tailles des panneaux ancrables enregistrées à la fermet
msgid "Closing the main window (unless it is only hidden, see `hide_on_close`) now releases it as the instance returned by `get_instance`, which creates a new window afterwards: the closed window, with its widgets and data, is no longer kept in memory until another main window is created."
msgstr "La fermeture de la fenêtre principale (sauf si elle est seulement masquée, voir `hide_on_close`) la libère désormais en tant qu'instance renvoyée par `get_instance`, qui crée ensuite une nouvelle fenêtre : la fenêtre fermée, avec ses widgets et ses données, n'est plus conservée en mémoire jusqu'à la création d'une autre fenêtre principale."

msgid "High-DPI displays"
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 "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 @@ -36,6 +36,10 @@
* With Qt 5, the dock widget sizes saved on exit are now restored at startup: the window is sized and laid out before its state is restored, so tabified docks no longer shrink to the default window size and leave the extra width to the central widget.
* Closing the main window (unless it is only hidden, see `hide_on_close`) now releases it as the instance returned by `get_instance`, which creates a new window afterwards: the closed window, with its widgets and data, is no longer kept in memory until another main window is created.

### High-DPI displays ###

* 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.

### 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
32 changes: 30 additions & 2 deletions sigimax/tests/widgets/test_splashscreen_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from unittest.mock import patch

from guidata.configtools import get_pixmap
from qtpy import QtCore as QC
from qtpy import QtGui as QG

from sigimax.widgets.splashscreen import SigimaXSplashScreen, SplashScreenConfig
Expand All @@ -32,6 +34,32 @@ def test_splash_resolves_image_basename(tmp_path) -> None:
splash.close()


def test_splash_uses_hidpi_variant(tmp_path) -> None:
"""The ``@2x`` variant of the image is used on high-DPI displays."""
image_path = tmp_path / "splash.png"
for path, size, color in (
(image_path, (64, 32), "red"),
(tmp_path / "splash@2x.png", (128, 64), "blue"),
):
pixmap = QG.QPixmap(*size)
pixmap.fill(QG.QColor(color))
assert pixmap.save(str(path))

config = SplashScreenConfig(image_path=str(image_path), show_progress=False)
with patch(
"sigimax.widgets.splashscreen.get_pixmap",
side_effect=lambda name, default: get_pixmap(name, default, 2.0),
):
splash = SigimaXSplashScreen(config)

pixmap = splash.pixmap()
assert pixmap.devicePixelRatio() == 2.0
assert pixmap.size() == QC.QSize(128, 64)
assert pixmap.toImage().pixelColor(0, 0) == QG.QColor("blue")
assert splash.size() == QC.QSize(64, 32)
splash.close()


def test_missing_splash_resource_uses_fallback() -> None:
"""An unresolved configured image must not prevent application startup."""
config = SplashScreenConfig(
Expand All @@ -45,8 +73,8 @@ def test_missing_splash_resource_uses_fallback() -> None:
splash = SigimaXSplashScreen(config)

assert not splash.pixmap().isNull()
assert splash.pixmap().size().width() == 480
assert splash.pixmap().size().height() == 280
pixmap = splash.pixmap()
assert pixmap.size() / pixmap.devicePixelRatio() == QC.QSize(480, 280)
splash.close()


Expand Down
11 changes: 9 additions & 2 deletions sigimax/widgets/plotdock.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
"SigimaXPlotWidget",
]

import os.path as osp
import warnings
from typing import TYPE_CHECKING

import numpy as np
from guidata.configtools import get_pixmap
from guidata.qthelpers import is_dark_theme
from guidata.widgets.dockable import DockableWidget
from plotpy.constants import PlotType
Expand Down Expand Up @@ -329,12 +331,17 @@ 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.
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 available (see :func:`guidata.configtools.get_pixmap`).
"""
path = get_conf().watermark_image_path.get()
if path:
self.watermark = QW.QLabel()
pixmap = QG.QPixmap(path)
if osp.isfile(path):
pixmap = get_pixmap(osp.abspath(path), default=None)
else:
pixmap = QG.QPixmap(path)
self.watermark.setPixmap(pixmap)
else:
self.watermark = None
Expand Down
34 changes: 23 additions & 11 deletions sigimax/widgets/splashscreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@
from __future__ import annotations

import dataclasses
import os.path as osp
from typing import TYPE_CHECKING

from guidata.configtools import get_image_file_path
from guidata.configtools import get_image_file_path, get_pixmap
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
Expand All @@ -67,8 +68,11 @@ class SplashScreenConfig:

Args:
image_path: Absolute or relative path to the splash image (PNG, SVG,
or any format supported by :class:`QPixmap`). If empty or ``None``,
the splash screen is disabled.
or any format supported by :class:`QPixmap`), or name of an image
registered in guidata's image paths. A ``@2x`` high-resolution variant
of the image (e.g. ``splash@2x.png`` next to ``splash.png``) is used on
high-DPI displays, if available. If empty or ``None``, the splash screen
is disabled.
app_name: Application name overlaid on the splash image.
app_version: Application version overlaid on the splash image.
tagline: Optional subtitle displayed below the version.
Expand Down Expand Up @@ -177,18 +181,24 @@ def from_conf(cls) -> SigimaXSplashScreen | None:
def _load_pixmap(self) -> QG.QPixmap:
"""Load the splash image as a :class:`QPixmap`.

The image is loaded at the resolution of the display: on high-DPI displays,
its ``@2x`` high-resolution variant (e.g. ``splash@2x.png`` next to
``splash.png``) is used if available (see
:func:`guidata.configtools.get_pixmap`).

Returns:
The loaded pixmap. If the image cannot be loaded, a minimal
fallback pixmap is generated.
"""
path = self._config.image_path or ""
pixmap = QG.QPixmap(path)
if pixmap.isNull() and path:
if path and not osp.isfile(path):
try:
resolved_path = get_image_file_path(path, default=None)
path = get_image_file_path(path, default=None)
except RuntimeError:
resolved_path = ""
pixmap = QG.QPixmap(resolved_path)
path = ""
pixmap = QG.QPixmap()
if path:
pixmap = get_pixmap(osp.abspath(path), default=None)
if pixmap.isNull():
pixmap = self._create_fallback_pixmap()
return pixmap
Expand All @@ -197,11 +207,13 @@ def _create_fallback_pixmap(self) -> QG.QPixmap:
"""Create a minimal fallback pixmap when no image is available.

Returns:
A 480x280 pixmap with the application name drawn on a dark
background.
A 480x280 pixmap (logical size, drawn at the resolution of the
display) with the application name drawn on a dark background.
"""
width, height = 480, 280
pixmap = QG.QPixmap(width, height)
ratio = QG.QGuiApplication.instance().devicePixelRatio()
pixmap = QG.QPixmap(round(width * ratio), round(height * ratio))
pixmap.setDevicePixelRatio(ratio)
pixmap.fill(QG.QColor(40, 40, 40))

painter = QG.QPainter(pixmap)
Expand Down
Loading