Version 1.7.2: Icon-System – Theme-Reaktivität generalisiert, Render vereinfacht
- icons.py: IconRefreshMixin ergänzt – reagiert auf PaletteChange/StyleChange und färbt Icons nur bei tatsächlichem Textfarb-Wechsel neu ein (farb-gegated) - icons.py: Render auf einen einzelnen 64px-Pixmap vereinfacht (Qt skaliert), statt drei fixe Größen (16/24/32) - MainWindow: nutzt IconRefreshMixin; hasattr-Guards in _setup_icons entfernt (läuft jetzt nur im fertig initialisierten Zustand); change_theme entschlackt – Icon-/Baum-Aktualisierung erfolgt über changeEvent statt unbedingtem Aufruf - XslDependencyDialog: nutzt IconRefreshMixin, färbt Button- und Baum-Icons bei Theme-Wechsel neu ein (bislang blieben offene non-modale Dialoge stale) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+57
-9
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from PySide6.QtCore import QByteArray, QFile, Qt
|
||||
from PySide6.QtCore import QByteArray, QEvent, QFile, Qt
|
||||
from PySide6.QtGui import QIcon, QPainter, QPixmap, QPalette
|
||||
from PySide6.QtSvg import QSvgRenderer
|
||||
from PySide6.QtWidgets import QApplication
|
||||
@@ -7,6 +7,10 @@ import res.resources_rc # noqa: F401 # registriert die Qt-Ressourcen (Icons) b
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Basisgröße für das einmalige Rendern; Qt skaliert daraus die benötigten Icon-Größen
|
||||
# (16/24/32 px in Menüs, Bäumen, Buttons). 64 px bietet genug Reserve auch für HiDPI.
|
||||
_RENDER_SIZE = 64
|
||||
|
||||
# Cache: (Icon-Name, Theme-Textfarbe) → fertig gerendertes QIcon.
|
||||
# Der Farb-Anteil im Schlüssel sorgt dafür, dass ein Theme-Wechsel automatisch
|
||||
# neue Einträge erzeugt, ohne dass der Cache explizit geleert werden muss.
|
||||
@@ -44,14 +48,58 @@ def icon(name: str) -> QIcon:
|
||||
f.close()
|
||||
|
||||
renderer = QSvgRenderer(svg_data)
|
||||
result = QIcon()
|
||||
for size in (16, 24, 32):
|
||||
pixmap = QPixmap(size, size)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
renderer.render(painter)
|
||||
painter.end()
|
||||
result.addPixmap(pixmap)
|
||||
pixmap = QPixmap(_RENDER_SIZE, _RENDER_SIZE)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
renderer.render(painter)
|
||||
painter.end()
|
||||
|
||||
result = QIcon(pixmap)
|
||||
_ICON_CACHE[cache_key] = result
|
||||
return result
|
||||
|
||||
|
||||
class IconRefreshMixin:
|
||||
"""
|
||||
Mischklasse für Top-Level-Widgets, deren Icons sich beim Theme-Wechsel automatisch neu einfärben.
|
||||
|
||||
Voraussetzungen für die nutzende Klasse:
|
||||
- Sie stellt eine Methode ``_setup_icons()`` bereit, die alle ihre Icons neu setzt.
|
||||
- Nach abgeschlossener Initialisierung setzt sie ``self._icons_ready = True``
|
||||
(idealerweise zusammen mit ``self._last_icon_color``, siehe ``mark_icons_ready()``).
|
||||
|
||||
Bei einem Style-/Palette-Wechsel wird – nur wenn sich die Theme-Textfarbe tatsächlich
|
||||
geändert hat – ``_on_icon_theme_changed()`` aufgerufen (Standard: ``_setup_icons()``).
|
||||
Unterklassen können ``_on_icon_theme_changed()`` überschreiben, um zusätzlich z.B.
|
||||
Baum-Icons neu zu zeichnen.
|
||||
|
||||
Hinweis: ``IconRefreshMixin`` muss in der Klassenbasis VOR der Qt-Basisklasse
|
||||
(``QMainWindow``/``QDialog``) stehen, damit das überschriebene ``changeEvent`` greift.
|
||||
"""
|
||||
|
||||
_icons_ready: bool = False
|
||||
_last_icon_color: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def _current_text_color() -> str | None:
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
return None
|
||||
return app.palette().color(QPalette.ColorRole.WindowText).name()
|
||||
|
||||
def mark_icons_ready(self):
|
||||
"""Schaltet die Theme-Reaktivität scharf (nach dem ersten ``_setup_icons()`` aufrufen)."""
|
||||
self._last_icon_color = self._current_text_color()
|
||||
self._icons_ready = True
|
||||
|
||||
def changeEvent(self, event):
|
||||
if event.type() in (QEvent.Type.PaletteChange, QEvent.Type.StyleChange) and self._icons_ready:
|
||||
color = self._current_text_color()
|
||||
if color is not None and color != self._last_icon_color:
|
||||
self._last_icon_color = color
|
||||
self._on_icon_theme_changed()
|
||||
super().changeEvent(event)
|
||||
|
||||
def _on_icon_theme_changed(self):
|
||||
"""Reaktion auf eine geänderte Theme-Textfarbe. Standard: alle Icons neu setzen."""
|
||||
self._setup_icons()
|
||||
|
||||
+16
-9
@@ -25,7 +25,7 @@ from ui.mixins import (
|
||||
TransformationMixin,
|
||||
)
|
||||
from conf import app_settings, Project, ProjectData, TreeNode, XslFile
|
||||
from icons import icon
|
||||
from icons import icon, IconRefreshMixin
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MainWindow(
|
||||
IconRefreshMixin,
|
||||
QMainWindow,
|
||||
TreeManagerMixin,
|
||||
PdfViewerMixin,
|
||||
@@ -141,8 +142,11 @@ class MainWindow(
|
||||
# Zoom per Mausrad (STRG+Mausrad) für PDF-Viewer aktivieren
|
||||
self._setup_scroll_area_zoom()
|
||||
|
||||
# Icons setzen (überschreibt fromTheme()-Icons aus _ui.py)
|
||||
# Icons setzen (überschreibt fromTheme()-Icons aus _ui.py). Läuft nach
|
||||
# _connect_signals(), daher existieren auch die programmatisch erzeugten Aktionen.
|
||||
self._setup_icons()
|
||||
# Ab hier reagiert das Fenster auf Theme-Wechsel (siehe IconRefreshMixin)
|
||||
self.mark_icons_ready()
|
||||
|
||||
# Gespeicherte UI-Zustände wiederherstellen
|
||||
self._restore_ui_state()
|
||||
@@ -157,10 +161,14 @@ class MainWindow(
|
||||
self.ui.view_ref_pdf.setIcon(icon("file"))
|
||||
self.ui.view_new_pdf.setIcon(icon("file"))
|
||||
self.ui.accept_changes.setIcon(icon("check-circle"))
|
||||
if hasattr(self, "action_xsl_dependencies"):
|
||||
self.action_xsl_dependencies.setIcon(icon("git-branch"))
|
||||
if hasattr(self, "action_info"):
|
||||
self.action_info.setIcon(icon("info"))
|
||||
self.action_xsl_dependencies.setIcon(icon("git-branch"))
|
||||
self.action_info.setIcon(icon("info"))
|
||||
|
||||
def _on_icon_theme_changed(self):
|
||||
"""Theme-Wechsel: Icons neu einfärben und – falls ein Projekt offen ist – die Baum-Icons."""
|
||||
self._setup_icons()
|
||||
if getattr(self, "pdf_project", None):
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
def _restore_ui_state(self):
|
||||
"""Stellt die gespeicherten UI-Zustände wieder her (Fenstergeometrie, Splitter, TreeWidget-Spalten)."""
|
||||
@@ -358,9 +366,8 @@ class MainWindow(
|
||||
logger.info(f"Theme erfolgreich gewechselt zu: {theme_name}")
|
||||
app_settings.theme = theme_name
|
||||
app_settings.save()
|
||||
self._setup_icons()
|
||||
if hasattr(self, "pdf_project") and self.pdf_project:
|
||||
self._load_nodes_to_tree()
|
||||
# Icons/Baum werden über changeEvent (PaletteChange/StyleChange) neu eingefärbt,
|
||||
# siehe IconRefreshMixin._on_icon_theme_changed()
|
||||
else:
|
||||
logger.error(f"Fehler: Theme '{theme_name}' konnte nicht erstellt werden")
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QUrl, Qt
|
||||
from icons import icon
|
||||
from icons import icon, IconRefreshMixin
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QDialog,
|
||||
@@ -120,7 +120,7 @@ except ImportError:
|
||||
logger.warning("PySide6-WebEngine nicht verfügbar — Netzwerkgraph-Tab deaktiviert")
|
||||
|
||||
|
||||
class XslDependencyDialog(QDialog):
|
||||
class XslDependencyDialog(IconRefreshMixin, QDialog):
|
||||
"""Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen."""
|
||||
|
||||
def __init__(
|
||||
@@ -185,6 +185,25 @@ class XslDependencyDialog(QDialog):
|
||||
self._build_layout_controls()
|
||||
self._restore_graph_layout_settings()
|
||||
|
||||
# Ab hier auf Theme-Wechsel reagieren (siehe IconRefreshMixin)
|
||||
self.mark_icons_ready()
|
||||
|
||||
def _setup_icons(self):
|
||||
"""Färbt alle Icons gemäß aktuellem Theme neu ein (Button und Baum-Icons)."""
|
||||
self.ui.settingsButton.setIcon(icon("sliders"))
|
||||
|
||||
# Datei-Baum neu einfärben, dabei die aktuelle Auswahl erhalten
|
||||
current = self.ui.fileTree.currentItem()
|
||||
current_path = current.data(0, Qt.ItemDataRole.UserRole) if current else None
|
||||
self._populate_file_tree()
|
||||
if current_path is not None:
|
||||
for i in range(self.ui.fileTree.topLevelItemCount()):
|
||||
item = self.ui.fileTree.topLevelItem(i)
|
||||
if item.data(0, Qt.ItemDataRole.UserRole) == current_path:
|
||||
# Setzt die Auswahl → _on_file_selected zeichnet den Abhängigkeitsbaum neu ein
|
||||
self.ui.fileTree.setCurrentItem(item)
|
||||
break
|
||||
|
||||
def reject(self):
|
||||
"""Speichert Layout-Einstellungen und räumt temporäre Dateien und QWebEngineView auf."""
|
||||
self._save_graph_layout_settings()
|
||||
|
||||
Reference in New Issue
Block a user