Skip to main content
Glama
theme_system.py13.5 kB
"""Centralized Theme System for FreeCAD AI GUI Components""" from enum import Enum from PySide2 import QtCore, QtWidgets class Theme(Enum): """Available themes.""" LIGHT = "light" DARK = "dark" AUTO = "auto" # Follow system theme class ColorScheme: """Color scheme definitions.""" def __init__(self, theme: Theme = Theme.LIGHT): self.theme = theme self._load_colors() def _load_colors(self): """Load colors based on theme.""" if self.theme == Theme.LIGHT: self.colors = { # Background colors "background_primary": "#ffffff", "background_secondary": "#f8f9fa", "background_tertiary": "#e9ecef", "background_accent": "#e3f2fd", # Text colors "text_primary": "#212529", "text_secondary": "#6c757d", "text_muted": "#adb5bd", "text_inverse": "#ffffff", # Brand colors "primary": "#2196F3", "secondary": "#6c757d", "success": "#4CAF50", "warning": "#FF9800", "error": "#f44336", "info": "#17a2b8", # Chat-specific colors "user_message_bg": "#e3f2fd", "ai_message_bg": "#e8f5e9", "system_message_bg": "#fff3e0", "user_text": "#2196F3", "ai_text": "#4CAF50", "system_text": "#FF9800", # UI element colors "border": "#dee2e6", "border_focus": "#2196F3", "button_primary": "#2196F3", "button_success": "#4CAF50", "button_warning": "#FF9800", "button_danger": "#f44336", # Status colors "status_connected": "#4CAF50", "status_warning": "#FF9800", "status_error": "#f44336", "status_unknown": "#9E9E9E", } else: # DARK theme self.colors = { # Background colors "background_primary": "#1a1a1a", "background_secondary": "#2d2d2d", "background_tertiary": "#3a3a3a", "background_accent": "#1e3a5f", # Text colors "text_primary": "#ffffff", "text_secondary": "#b3b3b3", "text_muted": "#808080", "text_inverse": "#000000", # Brand colors (slightly adjusted for dark theme) "primary": "#42A5F5", "secondary": "#9e9e9e", "success": "#66BB6A", "warning": "#FFA726", "error": "#EF5350", "info": "#29B6F6", # Chat-specific colors "user_message_bg": "#1e3a5f", "ai_message_bg": "#2e5d33", "system_message_bg": "#4a3326", "user_text": "#42A5F5", "ai_text": "#66BB6A", "system_text": "#FFA726", # UI element colors "border": "#4a4a4a", "border_focus": "#42A5F5", "button_primary": "#42A5F5", "button_success": "#66BB6A", "button_warning": "#FFA726", "button_danger": "#EF5350", # Status colors "status_connected": "#66BB6A", "status_warning": "#FFA726", "status_error": "#EF5350", "status_unknown": "#9E9E9E", } def get_color(self, color_name: str) -> str: """Get color by name.""" return self.colors.get(color_name, "#000000") def get_rgba(self, color_name: str, alpha: float = 1.0) -> str: """Get color as rgba string.""" hex_color = self.get_color(color_name) if hex_color.startswith("#"): hex_color = hex_color[1:] r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) return f"rgba({r}, {g}, {b}, {alpha})" class StyleSheet: """Generate consistent stylesheets.""" def __init__(self, color_scheme: ColorScheme): self.colors = color_scheme def get_button_style(self, button_type: str = "primary") -> str: """Get button stylesheet.""" color_map = { "primary": self.colors.get_color("button_primary"), "success": self.colors.get_color("button_success"), "warning": self.colors.get_color("button_warning"), "danger": self.colors.get_color("button_danger"), } base_color = color_map.get(button_type, self.colors.get_color("primary")) hover_color = self._darken_color(base_color, 0.1) return f""" QPushButton {{ background-color: {base_color}; color: {self.colors.get_color("text_inverse")}; border: none; padding: 8px 16px; border-radius: 4px; font-weight: bold; }} QPushButton:hover {{ background-color: {hover_color}; }} QPushButton:pressed {{ background-color: {self._darken_color(base_color, 0.2)}; }} QPushButton:disabled {{ background-color: {self.colors.get_color("text_muted")}; }} """ def get_input_style(self) -> str: """Get input field stylesheet.""" return f""" QLineEdit, QTextEdit {{ background-color: {self.colors.get_color("background_primary")}; border: 2px solid {self.colors.get_color("border")}; border-radius: 4px; padding: 8px; color: {self.colors.get_color("text_primary")}; }} QLineEdit:focus, QTextEdit:focus {{ border-color: {self.colors.get_color("border_focus")}; }} """ def get_groupbox_style(self) -> str: """Get group box stylesheet.""" return f""" QGroupBox {{ background-color: {self.colors.get_color("background_secondary")}; border: 1px solid {self.colors.get_color("border")}; border-radius: 6px; margin-top: 10px; padding-top: 10px; color: {self.colors.get_color("text_primary")}; font-weight: bold; }} QGroupBox::title {{ subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; }} """ def get_conversation_style(self) -> str: """Get conversation display stylesheet.""" return f""" QTextBrowser {{ background-color: {self.colors.get_color("background_primary")}; border: 1px solid {self.colors.get_color("border")}; border-radius: 4px; padding: 10px; color: {self.colors.get_color("text_primary")}; font-family: Arial, sans-serif; }} """ def get_status_indicator_style(self, status: str) -> str: """Get status indicator stylesheet.""" status_colors = { "connected": self.colors.get_color("status_connected"), "warning": self.colors.get_color("status_warning"), "error": self.colors.get_color("status_error"), "unknown": self.colors.get_color("status_unknown"), } color = status_colors.get(status, self.colors.get_color("status_unknown")) return f""" QLabel {{ color: {color}; font-weight: bold; font-size: 16px; }} """ def get_tab_style(self) -> str: """Get tab widget stylesheet.""" return f""" QTabWidget::pane {{ border: 1px solid {self.colors.get_color("border")}; background-color: {self.colors.get_color("background_primary")}; }} QTabBar::tab {{ background-color: {self.colors.get_color("background_secondary")}; color: {self.colors.get_color("text_primary")}; padding: 8px 16px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; }} QTabBar::tab:selected {{ background-color: {self.colors.get_color("primary")}; color: {self.colors.get_color("text_inverse")}; }} QTabBar::tab:hover {{ background-color: {self.colors.get_rgba("primary", 0.2)}; }} """ def _darken_color(self, hex_color: str, factor: float) -> str: """Darken a hex color by factor (0.0 to 1.0).""" if hex_color.startswith("#"): hex_color = hex_color[1:] r = int(hex_color[0:2], 16) g = int(hex_color[2:4], 16) b = int(hex_color[4:6], 16) r = max(0, int(r * (1 - factor))) g = max(0, int(g * (1 - factor))) b = max(0, int(b * (1 - factor))) return f"#{r:02x}{g:02x}{b:02x}" class ThemeManager: """Manages themes across the application.""" def __init__(self): self.current_theme = Theme.LIGHT self.color_scheme = ColorScheme(self.current_theme) self.stylesheet = StyleSheet(self.color_scheme) self._theme_changed_callbacks = [] def set_theme(self, theme: Theme): """Set application theme.""" if theme == self.current_theme: return self.current_theme = theme self.color_scheme = ColorScheme(theme) self.stylesheet = StyleSheet(self.color_scheme) # Notify callbacks for callback in self._theme_changed_callbacks: try: callback(theme) except Exception as e: print(f"ThemeManager: Error in theme change callback: {e}") def register_theme_change_callback(self, callback): """Register callback for theme changes.""" self._theme_changed_callbacks.append(callback) def apply_theme_to_widget(self, widget: QtWidgets.QWidget): """Apply current theme to a widget.""" try: # Apply base styling widget.setStyleSheet(f""" QWidget {{ background-color: {self.color_scheme.get_color("background_primary")}; color: {self.color_scheme.get_color("text_primary")}; }} """) # Apply specific styling based on widget type self._apply_specific_styling(widget) except Exception as e: print(f"ThemeManager: Error applying theme to widget: {e}") def _apply_specific_styling(self, widget: QtWidgets.QWidget): """Apply theme-specific styling to widget types.""" # Find and style specific widget types buttons = widget.findChildren(QtWidgets.QPushButton) for button in buttons: # Determine button type from style or text if "danger" in button.objectName().lower() or "delete" in button.text().lower(): button.setStyleSheet(self.stylesheet.get_button_style("danger")) elif "success" in button.objectName().lower() or "save" in button.text().lower(): button.setStyleSheet(self.stylesheet.get_button_style("success")) elif "warning" in button.objectName().lower(): button.setStyleSheet(self.stylesheet.get_button_style("warning")) else: button.setStyleSheet(self.stylesheet.get_button_style("primary")) # Style input fields inputs = widget.findChildren(QtWidgets.QLineEdit) + widget.findChildren(QtWidgets.QTextEdit) for input_widget in inputs: input_widget.setStyleSheet(self.stylesheet.get_input_style()) # Style group boxes groupboxes = widget.findChildren(QtWidgets.QGroupBox) for groupbox in groupboxes: groupbox.setStyleSheet(self.stylesheet.get_groupbox_style()) # Style tab widgets tabwidgets = widget.findChildren(QtWidgets.QTabWidget) for tabwidget in tabwidgets: tabwidget.setStyleSheet(self.stylesheet.get_tab_style()) # Global theme manager instance _theme_manager = None def get_theme_manager() -> ThemeManager: """Get global theme manager instance.""" global _theme_manager if _theme_manager is None: _theme_manager = ThemeManager() return _theme_manager def apply_theme_to_widget(widget: QtWidgets.QWidget, theme: Theme = None): """Convenience function to apply theme to widget.""" manager = get_theme_manager() if theme and theme != manager.current_theme: manager.set_theme(theme) manager.apply_theme_to_widget(widget) def get_current_color_scheme() -> ColorScheme: """Get current color scheme.""" return get_theme_manager().color_scheme def get_current_stylesheet() -> StyleSheet: """Get current stylesheet generator.""" return get_theme_manager().stylesheet

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server