# Description: Theme system for EU-Utility # Provides dark, light, and auto theme support """ EU-Utility Theme System Supports Dark, Light, and Auto (system-based) themes """ from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QColor, QPalette import json import os class ThemeManager: """Central theme management for EU-Utility.""" theme_changed = pyqtSignal(str) # Emitted when theme changes # Theme definitions THEMES = { 'dark': { 'name': 'Dark', 'bg_primary': '#1a1a2e', 'bg_secondary': '#16213e', 'bg_tertiary': '#0f3460', 'accent': '#e94560', 'text_primary': '#ffffff', 'text_secondary': '#b8b8b8', 'border': '#2d2d44', 'success': '#4caf50', 'warning': '#ff9800', 'error': '#f44336', }, 'light': { 'name': 'Light', 'bg_primary': '#f5f5f5', 'bg_secondary': '#ffffff', 'bg_tertiary': '#e0e0e0', 'accent': '#2196f3', 'text_primary': '#212121', 'text_secondary': '#757575', 'border': '#bdbdbd', 'success': '#4caf50', 'warning': '#ff9800', 'error': '#f44336', }, 'eu_classic': { 'name': 'EU Classic', 'bg_primary': '#141f23', 'bg_secondary': '#1a2a30', 'bg_tertiary': '#0f1416', 'accent': '#ff8c42', 'text_primary': '#ffffff', 'text_secondary': '#a0a0a0', 'border': '#2a3a40', 'success': '#4ecdc4', 'warning': '#ff8c42', 'error': '#e74c3c', } } _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self): if self._initialized: return self._initialized = True self.current_theme = 'eu_classic' self.auto_theme = False self._load_settings() def _load_settings(self): """Load theme settings from file.""" try: config_path = os.path.expanduser('~/.eu-utility/theme.json') if os.path.exists(config_path): with open(config_path, 'r') as f: settings = json.load(f) self.current_theme = settings.get('theme', 'eu_classic') self.auto_theme = settings.get('auto', False) except: pass def _save_settings(self): """Save theme settings to file.""" try: config_dir = os.path.expanduser('~/.eu-utility') os.makedirs(config_dir, exist_ok=True) config_path = os.path.join(config_dir, 'theme.json') with open(config_path, 'w') as f: json.dump({ 'theme': self.current_theme, 'auto': self.auto_theme }, f) except: pass def get_theme(self, name=None): """Get theme colors dictionary.""" if name is None: name = self.current_theme return self.THEMES.get(name, self.THEMES['eu_classic']) def set_theme(self, name): """Set active theme.""" if name in self.THEMES: self.current_theme = name self._save_settings() self.theme_changed.emit(name) def get_stylesheet(self, theme_name=None): """Generate QSS stylesheet for theme.""" t = self.get_theme(theme_name) return f""" QWidget {{ background-color: {t['bg_primary']}; color: {t['text_primary']}; font-family: 'Segoe UI', sans-serif; }} QPushButton {{ background-color: {t['bg_tertiary']}; color: {t['text_primary']}; border: 1px solid {t['border']}; padding: 8px 16px; border-radius: 4px; }} QPushButton:hover {{ background-color: {t['accent']}; }} QLineEdit, QTextEdit {{ background-color: {t['bg_secondary']}; color: {t['text_primary']}; border: 1px solid {t['border']}; padding: 6px; border-radius: 4px; }} QLabel {{ color: {t['text_primary']}; }} QComboBox {{ background-color: {t['bg_secondary']}; color: {t['text_primary']}; border: 1px solid {t['border']}; padding: 6px; border-radius: 4px; }} QProgressBar {{ background-color: {t['bg_secondary']}; border: 1px solid {t['border']}; border-radius: 4px; }} QProgressBar::chunk {{ background-color: {t['accent']}; border-radius: 4px; }} QScrollBar:vertical {{ background-color: {t['bg_secondary']}; width: 12px; border-radius: 6px; }} QScrollBar::handle:vertical {{ background-color: {t['border']}; border-radius: 6px; }} """ # Global instance theme_manager = ThemeManager()