Coverage for openhcs/pyqt_gui/shared/palette_manager.py: 0.0%
96 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
1"""
2QPalette Manager for OpenHCS PyQt6 GUI
4Manages QPalette integration with PyQt6ColorScheme for system-wide theming.
5Provides utilities for applying color schemes to Qt's palette system and
6managing theme switching across the entire application.
7"""
9import logging
10from typing import Optional
11from PyQt6.QtGui import QPalette, QColor
12from PyQt6.QtWidgets import QApplication
13from openhcs.pyqt_gui.shared.color_scheme import PyQt6ColorScheme
15logger = logging.getLogger(__name__)
18class PaletteManager:
19 """
20 Manages QPalette integration with PyQt6ColorScheme.
22 Provides methods to apply color schemes to Qt's palette system,
23 enabling system-wide theming and consistent color application.
24 """
26 def __init__(self, color_scheme: PyQt6ColorScheme):
27 """
28 Initialize the palette manager with a color scheme.
30 Args:
31 color_scheme: PyQt6ColorScheme instance to use for palette generation
32 """
33 self.color_scheme = color_scheme
34 self._original_palette = None
36 def update_color_scheme(self, color_scheme: PyQt6ColorScheme):
37 """
38 Update the color scheme used for palette generation.
40 Args:
41 color_scheme: New PyQt6ColorScheme instance
42 """
43 self.color_scheme = color_scheme
45 def create_palette(self) -> QPalette:
46 """
47 Create a QPalette from the current color scheme.
49 Returns:
50 QPalette: Configured palette with color scheme colors
51 """
52 palette = QPalette()
53 cs = self.color_scheme
55 # Window colors
56 palette.setColor(QPalette.ColorRole.Window, cs.to_qcolor(cs.window_bg))
57 palette.setColor(QPalette.ColorRole.WindowText, cs.to_qcolor(cs.text_primary))
59 # Base colors (input fields, etc.)
60 palette.setColor(QPalette.ColorRole.Base, cs.to_qcolor(cs.input_bg))
61 palette.setColor(QPalette.ColorRole.AlternateBase, cs.to_qcolor(cs.panel_bg))
62 palette.setColor(QPalette.ColorRole.Text, cs.to_qcolor(cs.input_text))
64 # Button colors
65 palette.setColor(QPalette.ColorRole.Button, cs.to_qcolor(cs.button_normal_bg))
66 palette.setColor(QPalette.ColorRole.ButtonText, cs.to_qcolor(cs.button_text))
68 # Selection colors
69 palette.setColor(QPalette.ColorRole.Highlight, cs.to_qcolor(cs.selection_bg))
70 palette.setColor(QPalette.ColorRole.HighlightedText, cs.to_qcolor(cs.selection_text))
72 # Disabled colors
73 palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText,
74 cs.to_qcolor(cs.text_disabled))
75 palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text,
76 cs.to_qcolor(cs.text_disabled))
77 palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText,
78 cs.to_qcolor(cs.button_disabled_text))
79 palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button,
80 cs.to_qcolor(cs.button_disabled_bg))
82 # Additional color roles
83 palette.setColor(QPalette.ColorRole.ToolTipBase, cs.to_qcolor(cs.panel_bg))
84 palette.setColor(QPalette.ColorRole.ToolTipText, cs.to_qcolor(cs.text_primary))
86 # Border/frame colors
87 palette.setColor(QPalette.ColorRole.Mid, cs.to_qcolor(cs.border_color))
88 palette.setColor(QPalette.ColorRole.Dark, cs.to_qcolor(cs.separator_color))
89 palette.setColor(QPalette.ColorRole.Light, cs.to_qcolor(cs.border_light))
91 return palette
93 def apply_palette_to_application(self, app: Optional[QApplication] = None):
94 """
95 Apply the color scheme palette to the entire application.
97 Args:
98 app: QApplication instance (uses QApplication.instance() if None)
99 """
100 if app is None:
101 app = QApplication.instance()
103 if app is None:
104 logger.warning("No QApplication instance found, cannot apply palette")
105 return
107 # Store original palette for restoration
108 if self._original_palette is None:
109 self._original_palette = app.palette()
111 # Apply new palette
112 new_palette = self.create_palette()
113 app.setPalette(new_palette)
115 logger.debug("Applied color scheme palette to application")
117 def restore_original_palette(self, app: Optional[QApplication] = None):
118 """
119 Restore the original application palette.
121 Args:
122 app: QApplication instance (uses QApplication.instance() if None)
123 """
124 if app is None:
125 app = QApplication.instance()
127 if app is None or self._original_palette is None:
128 logger.warning("Cannot restore original palette")
129 return
131 app.setPalette(self._original_palette)
132 logger.debug("Restored original application palette")
134 def get_palette_info(self) -> dict:
135 """
136 Get information about the current palette configuration.
138 Returns:
139 dict: Dictionary with palette color information
140 """
141 palette = self.create_palette()
142 cs = self.color_scheme
144 return {
145 "window_bg": cs.to_hex(cs.window_bg),
146 "window_text": cs.to_hex(cs.text_primary),
147 "base_bg": cs.to_hex(cs.input_bg),
148 "base_text": cs.to_hex(cs.input_text),
149 "button_bg": cs.to_hex(cs.button_normal_bg),
150 "button_text": cs.to_hex(cs.button_text),
151 "selection_bg": cs.to_hex(cs.selection_bg),
152 "selection_text": cs.to_hex(cs.selection_text),
153 "disabled_text": cs.to_hex(cs.text_disabled),
154 }
157class ThemeManager:
158 """
159 High-level theme management for the entire application.
161 Coordinates color scheme, style sheet generation, and palette management
162 to provide seamless theme switching capabilities.
163 """
165 def __init__(self, initial_color_scheme: Optional[PyQt6ColorScheme] = None):
166 """
167 Initialize the theme manager.
169 Args:
170 initial_color_scheme: Initial color scheme (defaults to dark theme)
171 """
172 self.color_scheme = initial_color_scheme or PyQt6ColorScheme()
173 self.palette_manager = PaletteManager(self.color_scheme)
175 # Import here to avoid circular imports
176 from openhcs.pyqt_gui.shared.style_generator import StyleSheetGenerator
177 self.style_generator = StyleSheetGenerator(self.color_scheme)
179 self._theme_change_callbacks = []
181 def switch_to_dark_theme(self):
182 """Switch to dark theme variant."""
183 self.apply_color_scheme(PyQt6ColorScheme.create_dark_theme())
185 def switch_to_light_theme(self):
186 """Switch to light theme variant."""
187 self.apply_color_scheme(PyQt6ColorScheme.create_light_theme())
189 def apply_color_scheme(self, color_scheme: PyQt6ColorScheme):
190 """
191 Apply a new color scheme to the entire application.
193 Args:
194 color_scheme: New PyQt6ColorScheme to apply
195 """
196 self.color_scheme = color_scheme
197 self.palette_manager.update_color_scheme(color_scheme)
198 self.style_generator.update_color_scheme(color_scheme)
200 # Apply to application
201 self.palette_manager.apply_palette_to_application()
203 # Notify callbacks
204 for callback in self._theme_change_callbacks:
205 try:
206 callback(color_scheme)
207 except Exception as e:
208 logger.warning(f"Theme change callback failed: {e}")
210 logger.info("Applied new color scheme to application")
212 def register_theme_change_callback(self, callback):
213 """
214 Register a callback to be called when theme changes.
216 Args:
217 callback: Function to call with new color scheme
218 """
219 self._theme_change_callbacks.append(callback)
221 def unregister_theme_change_callback(self, callback):
222 """
223 Unregister a theme change callback.
225 Args:
226 callback: Function to remove from callbacks
227 """
228 if callback in self._theme_change_callbacks:
229 self._theme_change_callbacks.remove(callback)
231 def get_current_style_sheet(self) -> str:
232 """
233 Get the current complete application style sheet.
235 Returns:
236 str: Complete QStyleSheet for current theme
237 """
238 return self.style_generator.generate_complete_application_style()
240 def load_theme_from_config(self, config_path: str) -> bool:
241 """
242 Load and apply theme from configuration file.
244 Args:
245 config_path: Path to JSON configuration file
247 Returns:
248 bool: True if successful, False otherwise
249 """
250 try:
251 color_scheme = PyQt6ColorScheme.load_color_scheme_from_config(config_path)
252 self.apply_color_scheme(color_scheme)
253 return True
254 except Exception as e:
255 logger.error(f"Failed to load theme from {config_path}: {e}")
256 return False
258 def save_current_theme(self, config_path: str) -> bool:
259 """
260 Save current theme to configuration file.
262 Args:
263 config_path: Path to save JSON configuration file
265 Returns:
266 bool: True if successful, False otherwise
267 """
268 return self.color_scheme.save_to_json(config_path)