209 lines
8.8 KiB
Python
209 lines
8.8 KiB
Python
from PyQt6.QtCore import QSettings
|
|
from app.core.observer_manager import ObserverManager, NotificationType
|
|
from app.core.theme_manager import ThemeManager
|
|
from os import path
|
|
import app.utils.paths as paths
|
|
import json
|
|
import logging
|
|
from typing import Dict, Any, Union
|
|
|
|
# Configure logging
|
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
|
|
class SettingsManager:
|
|
"""
|
|
Gestion des paramètres utilisateurs avec sauvegarde persistante via QSettings.
|
|
Notifie les changements via ObserverManager.
|
|
"""
|
|
def __init__(self, observer_manager: ObserverManager, theme_manager: ThemeManager) -> None:
|
|
self.observer_manager: ObserverManager = observer_manager
|
|
self.theme_manager: ThemeManager = theme_manager
|
|
|
|
# Hardcoded fallback settings in case files are missing
|
|
self.fallback_settings: Dict[str, Any] = {
|
|
"theme": "dark",
|
|
"lang": "fr",
|
|
"window_size": {"width": 1000, "height": 600},
|
|
"maximized": False
|
|
}
|
|
|
|
self.fallback_config: Dict[str, Any] = {
|
|
"app_name": "Application",
|
|
"app_os": "Windows",
|
|
"app_version": "1.0.0",
|
|
"architecture": "x64",
|
|
"icon_path": "data/assets/icon.ico",
|
|
"main_script": "main.py",
|
|
"git_repo": ""
|
|
}
|
|
|
|
# Load default settings
|
|
self.default_settings: Dict[str, Any] = self._load_default_settings()
|
|
# Load config
|
|
self.config: Dict[str, Any] = self._load_config()
|
|
|
|
# Initialize QSettings with error handling
|
|
self.settings: QSettings = self._initialize_qsettings()
|
|
|
|
# Set initial theme
|
|
try:
|
|
self.theme_manager.set_theme(self.get_theme())
|
|
except Exception as e:
|
|
logger.error(f"Error setting initial theme: {e}")
|
|
self.theme_manager.set_theme(self.fallback_settings["theme"])
|
|
|
|
def _load_default_settings(self) -> Dict[str, Any]:
|
|
"""Load default settings with error handling"""
|
|
try:
|
|
defaults_path = path.join(paths.get_data_dir(), "others", "defaults_settings.json")
|
|
if path.exists(defaults_path):
|
|
with open(defaults_path, 'r', encoding='utf-8') as f:
|
|
settings = json.load(f)
|
|
# Validate required keys
|
|
for key in self.fallback_settings.keys():
|
|
if key not in settings:
|
|
logger.warning(f"Missing key '{key}' in defaults_settings.json, using fallback")
|
|
settings[key] = self.fallback_settings[key]
|
|
return settings
|
|
else:
|
|
logger.warning(f"defaults_settings.json not found, using fallback settings")
|
|
return self.fallback_settings.copy()
|
|
except (json.JSONDecodeError, FileNotFoundError, KeyError, UnicodeDecodeError) as e:
|
|
logger.error(f"Error loading default settings: {e}")
|
|
return self.fallback_settings.copy()
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error loading default settings: {e}")
|
|
return self.fallback_settings.copy()
|
|
|
|
def _load_config(self) -> Dict[str, Any]:
|
|
"""Load config.json with error handling"""
|
|
try:
|
|
config_path = paths.resource_path("config.json")
|
|
if path.exists(config_path):
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
# Validate required keys
|
|
for key in self.fallback_config.keys():
|
|
if key not in config:
|
|
logger.warning(f"Missing key '{key}' in config.json, using fallback")
|
|
config[key] = self.fallback_config[key]
|
|
return config
|
|
else:
|
|
logger.warning("config.json not found, using fallback config")
|
|
return self.fallback_config.copy()
|
|
except (json.JSONDecodeError, FileNotFoundError, KeyError, UnicodeDecodeError) as e:
|
|
logger.error(f"Error loading config: {e}")
|
|
return self.fallback_config.copy()
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error loading config: {e}")
|
|
return self.fallback_config.copy()
|
|
|
|
def _initialize_qsettings(self) -> QSettings:
|
|
"""Initialize QSettings with error handling"""
|
|
try:
|
|
app_name = self.get_config("app_name")
|
|
user_data_dir = paths.get_user_data_dir(app_name)
|
|
settings_path = path.join(user_data_dir, f"{app_name}.ini")
|
|
return QSettings(settings_path, QSettings.Format.IniFormat)
|
|
except Exception as e:
|
|
logger.error(f"Error initializing QSettings: {e}")
|
|
# Fallback to memory-only settings
|
|
return QSettings()
|
|
|
|
# Config
|
|
def get_config(self, key: str) -> Any:
|
|
"""Get configuration value by key"""
|
|
try:
|
|
return self.config.get(key, self.fallback_config.get(key))
|
|
except Exception as e:
|
|
logger.error(f"Error getting config key '{key}': {e}")
|
|
return self.fallback_config.get(key)
|
|
|
|
# Theme
|
|
def get_theme(self) -> str:
|
|
"""Get the current theme"""
|
|
try:
|
|
return self.settings.value("theme", self.default_settings.get("theme"))
|
|
except Exception as e:
|
|
logger.error(f"Error getting theme: {e}")
|
|
return self.fallback_settings["theme"]
|
|
|
|
def set_theme(self, mode: str) -> None:
|
|
"""Set the application theme"""
|
|
try:
|
|
if mode != self.get_theme():
|
|
self.settings.setValue("theme", mode)
|
|
self.theme_manager.set_theme(mode)
|
|
self.observer_manager.notify(NotificationType.THEME)
|
|
except Exception as e:
|
|
logger.error(f"Error setting theme: {e}")
|
|
|
|
# Language
|
|
def get_language(self) -> str:
|
|
"""Get the current language"""
|
|
try:
|
|
return self.settings.value("lang", self.default_settings.get("lang"))
|
|
except Exception as e:
|
|
logger.error(f"Error getting language: {e}")
|
|
return self.fallback_settings["lang"]
|
|
|
|
def set_language(self, lang_code: str) -> None:
|
|
"""Set the application language"""
|
|
try:
|
|
if lang_code != self.get_language():
|
|
self.settings.setValue("lang", lang_code)
|
|
self.observer_manager.notify(NotificationType.LANGUAGE)
|
|
except Exception as e:
|
|
logger.error(f"Error setting language: {e}")
|
|
|
|
# Window size and maximized
|
|
def get_window_size(self) -> Dict[str, int]:
|
|
"""Get the window size"""
|
|
try:
|
|
size = self.settings.value("window_size", self.default_settings.get("window_size"))
|
|
# Validate window size values
|
|
if isinstance(size, dict) and "width" in size and "height" in size:
|
|
width = int(size["width"]) if size["width"] > 0 else self.fallback_settings["window_size"]["width"]
|
|
height = int(size["height"]) if size["height"] > 0 else self.fallback_settings["window_size"]["height"]
|
|
return {"width": width, "height": height}
|
|
else:
|
|
return self.fallback_settings["window_size"].copy()
|
|
except Exception as e:
|
|
logger.error(f"Error getting window size: {e}")
|
|
return self.fallback_settings["window_size"].copy()
|
|
|
|
def set_window_size(self, width: int, height: int) -> None:
|
|
"""Set the window size"""
|
|
try:
|
|
# Validate input values
|
|
if width <= 0 or height <= 0:
|
|
logger.warning(f"Invalid window size: {width}x{height}")
|
|
return
|
|
|
|
current_size = self.get_window_size()
|
|
if current_size["width"] != width or current_size["height"] != height:
|
|
new_size = {"width": width, "height": height}
|
|
self.settings.setValue("window_size", new_size)
|
|
except Exception as e:
|
|
logger.error(f"Error setting window size: {e}")
|
|
|
|
def get_maximized(self) -> bool:
|
|
"""Check if the window is maximized"""
|
|
try:
|
|
value = self.settings.value("maximized", self.default_settings.get("maximized"))
|
|
if isinstance(value, str):
|
|
return value.lower() == "true"
|
|
elif isinstance(value, bool):
|
|
return value
|
|
else:
|
|
return self.fallback_settings["maximized"]
|
|
except Exception as e:
|
|
logger.error(f"Error getting maximized state: {e}")
|
|
return self.fallback_settings["maximized"]
|
|
|
|
def set_maximized(self, maximized: bool) -> None:
|
|
"""Set the window maximized state"""
|
|
try:
|
|
self.settings.setValue("maximized", maximized)
|
|
except Exception as e:
|
|
logger.error(f"Error setting maximized state: {e}") |