""" Theme Manager - Verwaltet das Erscheinungsbild der Anwendung (Light & Dark Mode) Enhanced with Dark Mode support based on Corporate Design Guidelines Now using centralized theme configuration """ import os import json import logging from typing import Dict, Any, Optional from PyQt5.QtWidgets import QApplication from PyQt5.QtGui import QPalette, QColor from PyQt5.QtCore import Qt, QSettings, pyqtSignal, QObject # Import new theme system from themes.theme_config import ThemeConfig from themes.qss_generator import QSSGenerator logger = logging.getLogger("theme_manager") class ThemeManager(QObject): """ Verwaltet das Erscheinungsbild der Anwendung. Supports Light and Dark themes with smooth transitions. """ # Signal emitted when theme changes theme_changed = pyqtSignal(str) # Themennamen LIGHT_THEME = "light" DARK_THEME = "dark" def __init__(self, app: QApplication): """ Initialisiert den ThemeManager. Args: app: Die QApplication-Instanz """ super().__init__() self.app = app self.settings = QSettings("Chimaira", "SocialMediaAccountGenerator") # Load saved theme preference or default to light self.current_theme = self.settings.value("theme", self.LIGHT_THEME) # Basisverzeichnis ermitteln self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Stelle sicher, dass die Verzeichnisse existieren os.makedirs(os.path.join(self.base_dir, "resources", "themes"), exist_ok=True) os.makedirs(os.path.join(self.base_dir, "resources", "icons"), exist_ok=True) # Generate QSS from theme configuration self.qss_generator = QSSGenerator() self.theme_stylesheets = { self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME), self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME) } # Apply saved theme self.apply_theme(self.current_theme) logger.info(f"ThemeManager initialized with theme: {self.current_theme}") def regenerate_stylesheets(self): """Regenerate stylesheets from theme configuration""" self.theme_stylesheets = { self.LIGHT_THEME: self.qss_generator.generate(self.LIGHT_THEME), self.DARK_THEME: self.qss_generator.generate(self.DARK_THEME) } def get_color(self, color_key: str) -> str: """Get a color from the current theme""" return ThemeConfig.get_color(self.current_theme, color_key) def apply_theme(self, theme_name: str) -> bool: """ Wendet das angegebene Theme auf die Anwendung an. Args: theme_name: Name des Themes ("light" oder "dark") Returns: bool: True, wenn das Theme erfolgreich angewendet wurde, sonst False """ try: # Validate theme name if theme_name not in [self.LIGHT_THEME, self.DARK_THEME]: logger.warning(f"Unknown theme '{theme_name}', falling back to light theme") theme_name = self.LIGHT_THEME # Create palette based on theme palette = QPalette() # Get colors from theme configuration theme = ThemeConfig.get_theme(theme_name) if theme_name == self.DARK_THEME: # Dark Theme Palette from configuration palette.setColor(QPalette.Window, QColor(theme['bg_primary'])) palette.setColor(QPalette.WindowText, QColor(theme['text_primary'])) palette.setColor(QPalette.Base, QColor(theme['bg_tertiary'])) palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary'])) palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_tertiary'])) palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary'])) palette.setColor(QPalette.Text, QColor(theme['text_primary'])) palette.setColor(QPalette.Button, QColor(theme['bg_tertiary'])) palette.setColor(QPalette.ButtonText, QColor(theme['text_primary'])) palette.setColor(QPalette.BrightText, QColor(theme['accent'])) palette.setColor(QPalette.Link, QColor(theme['accent'])) palette.setColor(QPalette.Highlight, QColor(theme['accent'])) palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text'])) else: # Light Theme Palette from configuration palette.setColor(QPalette.Window, QColor(theme['bg_primary'])) palette.setColor(QPalette.WindowText, QColor(theme['text_primary'])) palette.setColor(QPalette.Base, QColor(theme['bg_primary'])) palette.setColor(QPalette.AlternateBase, QColor(theme['bg_secondary'])) palette.setColor(QPalette.ToolTipBase, QColor(theme['bg_primary'])) palette.setColor(QPalette.ToolTipText, QColor(theme['text_primary'])) palette.setColor(QPalette.Text, QColor(theme['text_primary'])) palette.setColor(QPalette.Button, QColor(theme['bg_secondary'])) palette.setColor(QPalette.ButtonText, QColor(theme['text_primary'])) palette.setColor(QPalette.BrightText, QColor(theme['error'])) palette.setColor(QPalette.Link, QColor(theme['accent'])) palette.setColor(QPalette.Highlight, QColor(theme['accent'])) palette.setColor(QPalette.HighlightedText, QColor(theme['btn_primary_text'])) # Apply palette to application self.app.setPalette(palette) # Apply stylesheet stylesheet = self.theme_stylesheets.get(theme_name, "") self.app.setStyleSheet(stylesheet) # Save current theme self.current_theme = theme_name self.settings.setValue("theme", theme_name) # Update logo if main window is available self._update_logo(theme_name) # Emit signal for theme change self.theme_changed.emit(theme_name) logger.info(f"Theme '{theme_name}' successfully applied") return True except Exception as e: logger.error(f"Error applying theme '{theme_name}': {e}") return False def toggle_theme(self) -> str: """ Toggles between Light and Dark theme. Returns: str: The name of the newly applied theme """ new_theme = self.DARK_THEME if self.current_theme == self.LIGHT_THEME else self.LIGHT_THEME self.apply_theme(new_theme) return new_theme def is_dark_mode(self) -> bool: """ Check if dark mode is currently active. Returns: bool: True if dark mode is active, False otherwise """ return self.current_theme == self.DARK_THEME def get_current_theme(self) -> str: """Gibt den Namen des aktuellen Themes zurück.""" return self.current_theme def get_icon_path(self, icon_name: str) -> str: """ Gibt den Pfad zum Icon zurück (theme-aware). Args: icon_name: Name des Icons (ohne Dateierweiterung) Returns: str: Pfad zum Icon """ # Social Media Icons bleiben unverändert (immer farbig) if icon_name in ["instagram", "facebook", "twitter", "tiktok", "vk", "gmail", "ok"]: return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") # Logo is theme-specific if icon_name == "intelsight-logo": theme = ThemeConfig.get_theme(self.current_theme) logo_name = theme.get('logo_path', 'intelsight-logo.svg').replace('.svg', '') return os.path.join(self.base_dir, "resources", "icons", f"{logo_name}.svg") # For other icons return os.path.join(self.base_dir, "resources", "icons", f"{icon_name}.svg") def _update_logo(self, theme_name: str): """ Updates the logo based on the current theme. Args: theme_name: Name of the theme ("light" or "dark") """ # Skip this method - logo will be updated directly in MainWindow._on_theme_toggled # This avoids circular import issues logger.debug(f"_update_logo called for theme: {theme_name} (delegating to MainWindow)") def get_color(self, color_key: str) -> str: """ Get a color from the current theme. Args: color_key: Key of the color in theme configuration Returns: Color value as hex string """ theme = ThemeConfig.get_theme(self.current_theme) return theme.get(color_key, '')