""" Settings Dialog for the application Allows users to configure various settings including view modes """ import customtkinter as ctk from gui.styles import COLORS, FONTS from utils.logger import logger import json from pathlib import Path class SettingsDialog(ctk.CTkToplevel): def __init__(self, parent, sidebar_view): super().__init__(parent) self.sidebar_view = sidebar_view self.settings_file = Path.home() / ".claude_project_manager" / "ui_settings.json" # Window setup self.title("Einstellungen") self.minsize(500, 450) self.resizable(True, True) # Make modal self.transient(parent) self.grab_set() # Load current settings self.settings = self.load_settings() # Setup UI self.setup_ui() # Update window size to fit content self.update_idletasks() # Center window self.center_window() # Focus self.focus() def setup_ui(self): """Setup the settings UI""" # Main container main_frame = ctk.CTkFrame(self, fg_color=COLORS['bg_primary']) main_frame.pack(fill="both", expand=True, padx=20, pady=20) # Title title_label = ctk.CTkLabel( main_frame, text="⚙️ Einstellungen", font=FONTS['subtitle'], text_color=COLORS['text_primary'] ) title_label.pack(pady=(0, 20)) # Activity Server Section activity_section = ctk.CTkFrame(main_frame, fg_color=COLORS['bg_secondary']) activity_section.pack(fill="x", pady=(0, 15)) activity_header = ctk.CTkLabel( activity_section, text="👥 Team-Aktivität Server", font=FONTS['body'], text_color=COLORS['text_primary'] ) activity_header.pack(anchor="w", padx=15, pady=(10, 5)) # Server URL url_label = ctk.CTkLabel( activity_section, text="Server URL:", font=FONTS['small'], text_color=COLORS['text_secondary'] ) url_label.pack(anchor="w", padx=30, pady=(5, 0)) self.server_url_var = ctk.StringVar(value=self.settings.get("activity_server_url", "http://91.99.192.14:3001")) self.server_url_entry = ctk.CTkEntry( activity_section, textvariable=self.server_url_var, width=300, fg_color=COLORS['bg_primary'] ) self.server_url_entry.pack(padx=30, pady=(0, 5)) # API Key api_label = ctk.CTkLabel( activity_section, text="API Key:", font=FONTS['small'], text_color=COLORS['text_secondary'] ) api_label.pack(anchor="w", padx=30, pady=(5, 0)) self.api_key_var = ctk.StringVar(value=self.settings.get("activity_api_key", "")) self.api_key_entry = ctk.CTkEntry( activity_section, textvariable=self.api_key_var, width=300, fg_color=COLORS['bg_primary'], show="*" ) self.api_key_entry.pack(padx=30, pady=(0, 5)) # User Name user_label = ctk.CTkLabel( activity_section, text="Benutzername:", font=FONTS['small'], text_color=COLORS['text_secondary'] ) user_label.pack(anchor="w", padx=30, pady=(5, 0)) self.user_name_var = ctk.StringVar(value=self.settings.get("activity_user_name", "")) self.user_name_entry = ctk.CTkEntry( activity_section, textvariable=self.user_name_var, width=300, fg_color=COLORS['bg_primary'] ) self.user_name_entry.pack(padx=30, pady=(0, 5)) # Test connection button test_btn = ctk.CTkButton( activity_section, text="Verbindung testen", command=self.test_activity_connection, fg_color=COLORS['bg_tile'], hover_color=COLORS['bg_tile_hover'], text_color=COLORS['text_primary'], width=150 ) test_btn.pack(pady=(5, 10)) # Status label self.connection_status_label = ctk.CTkLabel( activity_section, text="", font=FONTS['small'], text_color=COLORS['text_secondary'], height=20 ) self.connection_status_label.pack(pady=(0, 10)) # Buttons button_frame = ctk.CTkFrame(main_frame, fg_color="transparent") button_frame.pack(fill="x", pady=(20, 0)) # Cancel button (left side) cancel_btn = ctk.CTkButton( button_frame, text="Abbrechen", command=self.destroy, fg_color=COLORS['bg_tile'], hover_color=COLORS['bg_tile_hover'], text_color=COLORS['text_primary'], width=100 ) cancel_btn.pack(side="left", padx=(0, 5)) # Apply button (right side) apply_btn = ctk.CTkButton( button_frame, text="Anwenden", command=self.apply_settings, fg_color=COLORS['accent_primary'], hover_color=COLORS['accent_hover'], width=100 ) apply_btn.pack(side="right", padx=(5, 0)) # Save button (right side, before Apply) save_btn = ctk.CTkButton( button_frame, text="Speichern", command=self.save_settings_only, fg_color=COLORS['accent_secondary'], hover_color=COLORS['accent_hover'], text_color=COLORS['text_primary'], width=100 ) save_btn.pack(side="right", padx=(5, 0)) def load_settings(self): """Load settings from file""" try: if self.settings_file.exists(): with open(self.settings_file, 'r') as f: return json.load(f) except Exception as e: logger.error(f"Failed to load UI settings: {e}") return {} # No settings currently def save_settings(self): """Save settings to file""" try: self.settings_file.parent.mkdir(parents=True, exist_ok=True) with open(self.settings_file, 'w') as f: json.dump(self.settings, f, indent=2) logger.info(f"Saved UI settings: {self.settings}") except Exception as e: logger.error(f"Failed to save UI settings: {e}") def save_settings_only(self): """Save settings without applying to service or closing dialog""" # Get values server_url = self.server_url_var.get().strip() api_key = self.api_key_var.get().strip() user_name = self.user_name_var.get().strip() # Update settings self.settings["activity_server_url"] = server_url self.settings["activity_api_key"] = api_key self.settings["activity_user_name"] = user_name self.save_settings() # Show confirmation self.connection_status_label.configure( text="✅ Einstellungen gespeichert!", text_color=COLORS['accent_success'] ) logger.info("Settings saved successfully") # Clear status after 2 seconds self.after(2000, lambda: self.connection_status_label.configure(text="")) def apply_settings(self): """Apply the selected settings""" import uuid from services.activity_sync import activity_service # Get values server_url = self.server_url_var.get().strip() api_key = self.api_key_var.get().strip() user_name = self.user_name_var.get().strip() # Update settings self.settings["activity_server_url"] = server_url self.settings["activity_api_key"] = api_key self.settings["activity_user_name"] = user_name self.save_settings() # Update activity service activity_service.server_url = server_url activity_service.api_key = api_key activity_service.user_name = user_name activity_service.user_id = self.settings.get("activity_user_id", str(uuid.uuid4())) # Save user ID if newly generated if "activity_user_id" not in self.settings: self.settings["activity_user_id"] = activity_service.user_id self.save_settings() # Save activity settings activity_service.save_settings() # Reconnect if settings changed if activity_service.connected: activity_service.disconnect() if activity_service.is_configured(): activity_service.connect() logger.info("Activity server settings applied") # Close dialog self.destroy() def test_activity_connection(self): """Test connection to activity server""" import requests from tkinter import messagebox server_url = self.server_url_var.get().strip() api_key = self.api_key_var.get().strip() if not server_url: self.connection_status_label.configure( text="⚠️ Bitte Server URL eingeben", text_color=COLORS['accent_warning'] ) return self.connection_status_label.configure( text="🔄 Teste Verbindung...", text_color=COLORS['text_secondary'] ) self.update() try: # Try to connect to the server response = requests.get( f"{server_url}/health", timeout=5, headers={"Authorization": f"Bearer {api_key}"} if api_key else {} ) if response.status_code == 200: self.connection_status_label.configure( text="✅ Verbindung erfolgreich!", text_color=COLORS['accent_success'] ) logger.info(f"Activity server connection successful: {server_url}") else: self.connection_status_label.configure( text=f"❌ Server antwortet mit Status {response.status_code}", text_color=COLORS['accent_error'] ) logger.warning(f"Activity server returned status {response.status_code}") except requests.exceptions.ConnectionError: self.connection_status_label.configure( text="❌ Server nicht erreichbar", text_color=COLORS['accent_error'] ) logger.error(f"Activity server not reachable: {server_url}") except requests.exceptions.Timeout: self.connection_status_label.configure( text="❌ Verbindung Timeout", text_color=COLORS['accent_error'] ) logger.error(f"Activity server connection timeout: {server_url}") except Exception as e: self.connection_status_label.configure( text=f"❌ Fehler: {str(e)}", text_color=COLORS['accent_error'] ) logger.error(f"Activity server connection error: {e}") def center_window(self): """Center the dialog on parent window""" self.update_idletasks() # Get parent window position parent = self.master parent_x = parent.winfo_x() parent_y = parent.winfo_y() parent_width = parent.winfo_width() parent_height = parent.winfo_height() # Get dialog size dialog_width = self.winfo_width() dialog_height = self.winfo_height() # Calculate position x = parent_x + (parent_width - dialog_width) // 2 y = parent_y + (parent_height - dialog_height) // 2 self.geometry(f"+{x}+{y}")