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

1""" 

2QPalette Manager for OpenHCS PyQt6 GUI 

3 

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""" 

8 

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 

14 

15logger = logging.getLogger(__name__) 

16 

17 

18class PaletteManager: 

19 """ 

20 Manages QPalette integration with PyQt6ColorScheme. 

21  

22 Provides methods to apply color schemes to Qt's palette system, 

23 enabling system-wide theming and consistent color application. 

24 """ 

25 

26 def __init__(self, color_scheme: PyQt6ColorScheme): 

27 """ 

28 Initialize the palette manager with a color scheme. 

29  

30 Args: 

31 color_scheme: PyQt6ColorScheme instance to use for palette generation 

32 """ 

33 self.color_scheme = color_scheme 

34 self._original_palette = None 

35 

36 def update_color_scheme(self, color_scheme: PyQt6ColorScheme): 

37 """ 

38 Update the color scheme used for palette generation. 

39  

40 Args: 

41 color_scheme: New PyQt6ColorScheme instance 

42 """ 

43 self.color_scheme = color_scheme 

44 

45 def create_palette(self) -> QPalette: 

46 """ 

47 Create a QPalette from the current color scheme. 

48  

49 Returns: 

50 QPalette: Configured palette with color scheme colors 

51 """ 

52 palette = QPalette() 

53 cs = self.color_scheme 

54 

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)) 

58 

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)) 

63 

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)) 

67 

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)) 

71 

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)) 

81 

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)) 

85 

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)) 

90 

91 return palette 

92 

93 def apply_palette_to_application(self, app: Optional[QApplication] = None): 

94 """ 

95 Apply the color scheme palette to the entire application. 

96  

97 Args: 

98 app: QApplication instance (uses QApplication.instance() if None) 

99 """ 

100 if app is None: 

101 app = QApplication.instance() 

102 

103 if app is None: 

104 logger.warning("No QApplication instance found, cannot apply palette") 

105 return 

106 

107 # Store original palette for restoration 

108 if self._original_palette is None: 

109 self._original_palette = app.palette() 

110 

111 # Apply new palette 

112 new_palette = self.create_palette() 

113 app.setPalette(new_palette) 

114 

115 logger.debug("Applied color scheme palette to application") 

116 

117 def restore_original_palette(self, app: Optional[QApplication] = None): 

118 """ 

119 Restore the original application palette. 

120  

121 Args: 

122 app: QApplication instance (uses QApplication.instance() if None) 

123 """ 

124 if app is None: 

125 app = QApplication.instance() 

126 

127 if app is None or self._original_palette is None: 

128 logger.warning("Cannot restore original palette") 

129 return 

130 

131 app.setPalette(self._original_palette) 

132 logger.debug("Restored original application palette") 

133 

134 def get_palette_info(self) -> dict: 

135 """ 

136 Get information about the current palette configuration. 

137  

138 Returns: 

139 dict: Dictionary with palette color information 

140 """ 

141 palette = self.create_palette() 

142 cs = self.color_scheme 

143 

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 } 

155 

156 

157class ThemeManager: 

158 """ 

159 High-level theme management for the entire application. 

160  

161 Coordinates color scheme, style sheet generation, and palette management 

162 to provide seamless theme switching capabilities. 

163 """ 

164 

165 def __init__(self, initial_color_scheme: Optional[PyQt6ColorScheme] = None): 

166 """ 

167 Initialize the theme manager. 

168  

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) 

174 

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) 

178 

179 self._theme_change_callbacks = [] 

180 

181 def switch_to_dark_theme(self): 

182 """Switch to dark theme variant.""" 

183 self.apply_color_scheme(PyQt6ColorScheme.create_dark_theme()) 

184 

185 def switch_to_light_theme(self): 

186 """Switch to light theme variant.""" 

187 self.apply_color_scheme(PyQt6ColorScheme.create_light_theme()) 

188 

189 def apply_color_scheme(self, color_scheme: PyQt6ColorScheme): 

190 """ 

191 Apply a new color scheme to the entire application. 

192  

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) 

199 

200 # Apply to application 

201 self.palette_manager.apply_palette_to_application() 

202 

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}") 

209 

210 logger.info("Applied new color scheme to application") 

211 

212 def register_theme_change_callback(self, callback): 

213 """ 

214 Register a callback to be called when theme changes. 

215  

216 Args: 

217 callback: Function to call with new color scheme 

218 """ 

219 self._theme_change_callbacks.append(callback) 

220 

221 def unregister_theme_change_callback(self, callback): 

222 """ 

223 Unregister a theme change callback. 

224  

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) 

230 

231 def get_current_style_sheet(self) -> str: 

232 """ 

233 Get the current complete application style sheet. 

234  

235 Returns: 

236 str: Complete QStyleSheet for current theme 

237 """ 

238 return self.style_generator.generate_complete_application_style() 

239 

240 def load_theme_from_config(self, config_path: str) -> bool: 

241 """ 

242 Load and apply theme from configuration file. 

243  

244 Args: 

245 config_path: Path to JSON configuration file 

246  

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 

257 

258 def save_current_theme(self, config_path: str) -> bool: 

259 """ 

260 Save current theme to configuration file. 

261  

262 Args: 

263 config_path: Path to save JSON configuration file 

264  

265 Returns: 

266 bool: True if successful, False otherwise 

267 """ 

268 return self.color_scheme.save_to_json(config_path)