Source code for pyqt_reactive.theming.palette_manager

"""
QPalette Manager for OpenHCS PyQt6 GUI

Manages QPalette integration with ColorScheme for system-wide theming.
Provides utilities for applying color schemes to Qt's palette system and
managing theme switching across the entire application.
"""

import logging
from typing import Optional
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QApplication
from .color_scheme import ColorScheme

logger = logging.getLogger(__name__)


[docs] class PaletteManager: """ Manages QPalette integration with ColorScheme. Provides methods to apply color schemes to Qt's palette system, enabling system-wide theming and consistent color application. """
[docs] def __init__(self, color_scheme: ColorScheme): """ Initialize the palette manager with a color scheme. Args: color_scheme: ColorScheme instance to use for palette generation """ self.color_scheme = color_scheme self._original_palette = None
[docs] def update_color_scheme(self, color_scheme: ColorScheme): """ Update the color scheme used for palette generation. Args: color_scheme: New ColorScheme instance """ self.color_scheme = color_scheme
[docs] def create_palette(self) -> QPalette: """ Create a QPalette from the current color scheme. Returns: QPalette: Configured palette with color scheme colors """ palette = QPalette() cs = self.color_scheme # Window colors palette.setColor(QPalette.ColorRole.Window, cs.to_qcolor(cs.window_bg)) palette.setColor(QPalette.ColorRole.WindowText, cs.to_qcolor(cs.text_primary)) # Base colors (input fields, etc.) palette.setColor(QPalette.ColorRole.Base, cs.to_qcolor(cs.input_bg)) palette.setColor(QPalette.ColorRole.AlternateBase, cs.to_qcolor(cs.panel_bg)) palette.setColor(QPalette.ColorRole.Text, cs.to_qcolor(cs.input_text)) # Button colors palette.setColor(QPalette.ColorRole.Button, cs.to_qcolor(cs.button_normal_bg)) palette.setColor(QPalette.ColorRole.ButtonText, cs.to_qcolor(cs.button_text)) # Selection colors palette.setColor(QPalette.ColorRole.Highlight, cs.to_qcolor(cs.selection_bg)) palette.setColor(QPalette.ColorRole.HighlightedText, cs.to_qcolor(cs.selection_text)) # Disabled colors palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, cs.to_qcolor(cs.text_disabled)) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, cs.to_qcolor(cs.text_disabled)) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, cs.to_qcolor(cs.button_disabled_text)) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, cs.to_qcolor(cs.button_disabled_bg)) # Additional color roles palette.setColor(QPalette.ColorRole.ToolTipBase, cs.to_qcolor(cs.panel_bg)) palette.setColor(QPalette.ColorRole.ToolTipText, cs.to_qcolor(cs.text_primary)) # Border/frame colors palette.setColor(QPalette.ColorRole.Mid, cs.to_qcolor(cs.border_color)) palette.setColor(QPalette.ColorRole.Dark, cs.to_qcolor(cs.separator_color)) palette.setColor(QPalette.ColorRole.Light, cs.to_qcolor(cs.border_light)) return palette
[docs] def apply_palette_to_application(self, app: Optional[QApplication] = None): """ Apply the color scheme palette to the entire application. Args: app: QApplication instance (uses QApplication.instance() if None) """ if app is None: app = QApplication.instance() if app is None: logger.warning("No QApplication instance found, cannot apply palette") return # Store original palette for restoration if self._original_palette is None: self._original_palette = app.palette() # Apply new palette new_palette = self.create_palette() app.setPalette(new_palette) logger.debug("Applied color scheme palette to application")
[docs] def restore_original_palette(self, app: Optional[QApplication] = None): """ Restore the original application palette. Args: app: QApplication instance (uses QApplication.instance() if None) """ if app is None: app = QApplication.instance() if app is None or self._original_palette is None: logger.warning("Cannot restore original palette") return app.setPalette(self._original_palette) logger.debug("Restored original application palette")
[docs] def get_palette_info(self) -> dict: """ Get information about the current palette configuration. Returns: dict: Dictionary with palette color information """ palette = self.create_palette() cs = self.color_scheme return { "window_bg": cs.to_hex(cs.window_bg), "window_text": cs.to_hex(cs.text_primary), "base_bg": cs.to_hex(cs.input_bg), "base_text": cs.to_hex(cs.input_text), "button_bg": cs.to_hex(cs.button_normal_bg), "button_text": cs.to_hex(cs.button_text), "selection_bg": cs.to_hex(cs.selection_bg), "selection_text": cs.to_hex(cs.selection_text), "disabled_text": cs.to_hex(cs.text_disabled), }
[docs] class ThemeManager: """ High-level theme management for the entire application. Coordinates color scheme, style sheet generation, and palette management to provide seamless theme switching capabilities. """
[docs] def __init__(self, initial_color_scheme: Optional[ColorScheme] = None): """ Initialize the theme manager. Args: initial_color_scheme: Initial color scheme (defaults to dark theme) """ self.color_scheme = initial_color_scheme or ColorScheme() self.palette_manager = PaletteManager(self.color_scheme) # Import here to avoid circular imports from pyqt_reactive.theming.style_generator import StyleSheetGenerator self.style_generator = StyleSheetGenerator(self.color_scheme) self._theme_change_callbacks = []
[docs] def switch_to_dark_theme(self): """Switch to dark theme variant.""" self.apply_color_scheme(ColorScheme.create_dark_theme())
[docs] def switch_to_light_theme(self): """Switch to light theme variant.""" self.apply_color_scheme(ColorScheme.create_light_theme())
[docs] def apply_color_scheme(self, color_scheme: ColorScheme): """ Apply a new color scheme to the entire application. Args: color_scheme: New ColorScheme to apply """ self.color_scheme = color_scheme self.palette_manager.update_color_scheme(color_scheme) self.style_generator.update_color_scheme(color_scheme) # Apply to application self.palette_manager.apply_palette_to_application() # Notify callbacks for callback in self._theme_change_callbacks: try: callback(color_scheme) except Exception as e: logger.warning(f"Theme change callback failed: {e}") logger.info("Applied new color scheme to application")
[docs] def register_theme_change_callback(self, callback): """ Register a callback to be called when theme changes. Args: callback: Function to call with new color scheme """ self._theme_change_callbacks.append(callback)
[docs] def unregister_theme_change_callback(self, callback): """ Unregister a theme change callback. Args: callback: Function to remove from callbacks """ if callback in self._theme_change_callbacks: self._theme_change_callbacks.remove(callback)
[docs] def get_current_style_sheet(self) -> str: """ Get the current complete application style sheet. Returns: str: Complete QStyleSheet for current theme """ return self.style_generator.generate_complete_application_style()
[docs] def load_theme_from_config(self, config_path: str) -> bool: """ Load and apply theme from configuration file. Args: config_path: Path to JSON configuration file Returns: bool: True if successful, False otherwise """ try: color_scheme = ColorScheme.load_color_scheme_from_config(config_path) self.apply_color_scheme(color_scheme) return True except Exception as e: logger.error(f"Failed to load theme from {config_path}: {e}") return False
[docs] def save_current_theme(self, config_path: str) -> bool: """ Save current theme to configuration file. Args: config_path: Path to save JSON configuration file Returns: bool: True if successful, False otherwise """ return self.color_scheme.save_to_json(config_path)