""" Application settings and configuration management. This module handles application-wide settings, preferences, and configuration data. It provides a centralized location for managing user preferences, default values, and application state. Main Components: - Settings class for configuration management - Default configuration values - Settings persistence and loading - Configuration validation Usage: from cluster4npu_ui.config.settings import Settings settings = Settings() recent_files = settings.get_recent_files() settings.add_recent_file('/path/to/pipeline.mflow') """ import json import os from typing import Dict, Any, List, Optional from pathlib import Path class Settings: """ Application settings and configuration management. Handles loading, saving, and managing application settings including user preferences, recent files, and default configurations. """ def __init__(self, config_file: Optional[str] = None): """ Initialize settings manager. Args: config_file: Optional path to configuration file """ self.config_file = config_file or self._get_default_config_path() self._settings = self._load_default_settings() self.load() def _get_default_config_path(self) -> str: """Get the default configuration file path.""" home_dir = Path.home() config_dir = home_dir / '.cluster4npu' config_dir.mkdir(exist_ok=True) return str(config_dir / 'settings.json') def _load_default_settings(self) -> Dict[str, Any]: """Load default application settings.""" return { 'general': { 'auto_save': True, 'auto_save_interval': 300, # seconds 'check_for_updates': True, 'theme': 'harmonious_dark', 'language': 'en' }, 'recent_files': [], 'window': { 'main_window_geometry': None, 'main_window_state': None, 'splitter_sizes': None, 'recent_window_size': [1200, 800] }, 'pipeline': { 'default_project_location': str(Path.home() / 'Documents' / 'Cluster4NPU'), 'auto_layout': True, 'show_grid': True, 'snap_to_grid': False, 'grid_size': 20, 'auto_connect': True, 'validate_on_save': True }, 'performance': { 'max_undo_steps': 50, 'render_quality': 'high', 'enable_animations': True, 'cache_size_mb': 100 }, 'hardware': { 'auto_detect_dongles': True, 'preferred_dongle_series': '720', 'max_dongles_per_stage': 4, 'power_management': 'balanced' }, 'export': { 'default_format': 'JSON', 'include_metadata': True, 'compress_exports': False, 'export_location': str(Path.home() / 'Downloads') }, 'debugging': { 'log_level': 'INFO', 'enable_profiling': False, 'save_debug_logs': False, 'max_log_files': 10 } } def load(self) -> bool: """ Load settings from file. Returns: True if settings were loaded successfully, False otherwise """ try: if os.path.exists(self.config_file): with open(self.config_file, 'r', encoding='utf-8') as f: saved_settings = json.load(f) self._merge_settings(saved_settings) return True except Exception as e: print(f"Error loading settings: {e}") return False def save(self) -> bool: """ Save current settings to file. Returns: True if settings were saved successfully, False otherwise """ try: os.makedirs(os.path.dirname(self.config_file), exist_ok=True) with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self._settings, f, indent=2, ensure_ascii=False) return True except Exception as e: print(f"Error saving settings: {e}") return False def _merge_settings(self, saved_settings: Dict[str, Any]): """Merge saved settings with defaults.""" def merge_dict(default: dict, saved: dict) -> dict: result = default.copy() for key, value in saved.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = merge_dict(result[key], value) else: result[key] = value return result self._settings = merge_dict(self._settings, saved_settings) def get(self, key: str, default: Any = None) -> Any: """ Get a setting value using dot notation. Args: key: Setting key (e.g., 'general.auto_save') default: Default value if key not found Returns: Setting value or default """ keys = key.split('.') value = self._settings try: for k in keys: value = value[k] return value except (KeyError, TypeError): return default def set(self, key: str, value: Any): """ Set a setting value using dot notation. Args: key: Setting key (e.g., 'general.auto_save') value: Value to set """ keys = key.split('.') setting = self._settings # Navigate to the parent dictionary for k in keys[:-1]: if k not in setting: setting[k] = {} setting = setting[k] # Set the final value setting[keys[-1]] = value def get_recent_files(self) -> List[str]: """Get list of recent files.""" return self.get('recent_files', []) def add_recent_file(self, file_path: str, max_files: int = 10): """ Add a file to recent files list. Args: file_path: Path to the file max_files: Maximum number of recent files to keep """ recent_files = self.get_recent_files() # Remove if already exists if file_path in recent_files: recent_files.remove(file_path) # Add to beginning recent_files.insert(0, file_path) # Limit list size recent_files = recent_files[:max_files] self.set('recent_files', recent_files) self.save() def remove_recent_file(self, file_path: str): """Remove a file from recent files list.""" recent_files = self.get_recent_files() if file_path in recent_files: recent_files.remove(file_path) self.set('recent_files', recent_files) self.save() def clear_recent_files(self): """Clear all recent files.""" self.set('recent_files', []) self.save() def get_default_project_location(self) -> str: """Get default project location.""" return self.get('pipeline.default_project_location', str(Path.home() / 'Documents' / 'Cluster4NPU')) def set_window_geometry(self, geometry: bytes): """Save window geometry.""" # Convert bytes to base64 string for JSON serialization import base64 geometry_str = base64.b64encode(geometry).decode('utf-8') self.set('window.main_window_geometry', geometry_str) self.save() def get_window_geometry(self) -> Optional[bytes]: """Get saved window geometry.""" geometry_str = self.get('window.main_window_geometry') if geometry_str: import base64 return base64.b64decode(geometry_str.encode('utf-8')) return None def set_window_state(self, state: bytes): """Save window state.""" import base64 state_str = base64.b64encode(state).decode('utf-8') self.set('window.main_window_state', state_str) self.save() def get_window_state(self) -> Optional[bytes]: """Get saved window state.""" state_str = self.get('window.main_window_state') if state_str: import base64 return base64.b64decode(state_str.encode('utf-8')) return None def reset_to_defaults(self): """Reset all settings to default values.""" self._settings = self._load_default_settings() self.save() def export_settings(self, file_path: str) -> bool: """ Export settings to a file. Args: file_path: Path to export file Returns: True if export was successful, False otherwise """ try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(self._settings, f, indent=2, ensure_ascii=False) return True except Exception as e: print(f"Error exporting settings: {e}") return False def import_settings(self, file_path: str) -> bool: """ Import settings from a file. Args: file_path: Path to import file Returns: True if import was successful, False otherwise """ try: with open(file_path, 'r', encoding='utf-8') as f: imported_settings = json.load(f) self._merge_settings(imported_settings) self.save() return True except Exception as e: print(f"Error importing settings: {e}") return False # Global settings instance _settings_instance = None def get_settings() -> Settings: """Get the global settings instance.""" global _settings_instance if _settings_instance is None: _settings_instance = Settings() return _settings_instance