PerKeyEditor: rebuild tool icons on GTK theme change

Tool icon pixbufs are baked at construction time using the button's
current style context foreground color. Without this, switching the
desktop theme (e.g. light <-> dark) at runtime leaves the editor's
icons stuck in the previous color while the rest of the UI updates.

Subscribe to Gtk.Settings notify::gtk-theme-name and
notify::gtk-application-prefer-dark-theme; on either, replace each
themed button's child Gtk.Image with a freshly-recolored one.
Disconnect both signals from PerKeyEditor.shutdown so the editor
doesn't leak handlers across openings.
This commit is contained in:
Ken Sanislo 2026-05-11 00:28:26 -07:00 committed by Peter F. Patel-Schneider
parent cf9a88bcaf
commit 1210d32b93
1 changed files with 30 additions and 0 deletions

View File

@ -130,6 +130,9 @@ class PerKeyEditor(Gtk.Box):
# toolbar row # toolbar row
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
self._tool_buttons: dict[str, Gtk.RadioButton] = {} self._tool_buttons: dict[str, Gtk.RadioButton] = {}
# Track which buttons display a themed icon, so we can re-render them
# when the active GTK theme switches (light <-> dark, theme name).
self._themed_icon_buttons: dict[Gtk.RadioButton, str] = {}
self._gradient_swatch: GradientSwatch | None = None self._gradient_swatch: GradientSwatch | None = None
first: Gtk.RadioButton | None = None first: Gtk.RadioButton | None = None
supported = layout.supported_tools if layout else ("single", "rect", "bucket", "gradient") supported = layout.supported_tools if layout else ("single", "rect", "bucket", "gradient")
@ -150,6 +153,7 @@ class PerKeyEditor(Gtk.Box):
btn.add(image) btn.add(image)
btn.set_tooltip_text(tip or label) btn.set_tooltip_text(tip or label)
btn.get_accessible().set_name(label) btn.get_accessible().set_name(label)
self._themed_icon_buttons[btn] = icon_name
else: else:
btn.set_label(label) btn.set_label(label)
btn.set_tooltip_text(tip) btn.set_tooltip_text(tip)
@ -159,6 +163,14 @@ class PerKeyEditor(Gtk.Box):
toolbar.pack_start(btn, False, False, 0) toolbar.pack_start(btn, False, False, 0)
self._tool_buttons[name] = btn self._tool_buttons[name] = btn
# Re-render themed icons when the GTK theme changes at runtime.
self._theme_signal_handlers: list[tuple[object, int]] = []
if self._themed_icon_buttons:
settings = Gtk.Settings.get_default()
for prop in ("notify::gtk-theme-name", "notify::gtk-application-prefer-dark-theme"):
hid = settings.connect(prop, self._on_gtk_theme_changed)
self._theme_signal_handlers.append((settings, hid))
initial_active, initial_previous = 0xFF0000, 0xFF0000 initial_active, initial_previous = 0xFF0000, 0xFF0000
try: try:
persisted = sink.palette_state() persisted = sink.palette_state()
@ -209,6 +221,24 @@ class PerKeyEditor(Gtk.Box):
except Exception as e: except Exception as e:
logger.debug("perkey sink unsubscribe failed: %s", e) logger.debug("perkey sink unsubscribe failed: %s", e)
self._unsubscribe = None self._unsubscribe = None
for obj, hid in self._theme_signal_handlers:
try:
obj.disconnect(hid)
except Exception as e:
logger.debug("theme signal disconnect failed: %s", e)
self._theme_signal_handlers = []
def _on_gtk_theme_changed(self, _settings, _pspec) -> None:
"""Rebuild themed tool icons so they match the new theme's foreground."""
for btn, icon_name in self._themed_icon_buttons.items():
old = btn.get_child()
new_image = _tool_icon_image(icon_name, btn)
if new_image is None:
continue
if old is not None:
btn.remove(old)
btn.add(new_image)
new_image.show()
def canvas_size(self) -> tuple[int, int]: def canvas_size(self) -> tuple[int, int]:
"""Return the canvas's pixel size_request — what the dialog should """Return the canvas's pixel size_request — what the dialog should