import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING from .cfd_app_config import CfdConfigManager, LocaleStrings from shared_libs.animated_icon import PIL_AVAILABLE from .cfd_sftp_manager import PARAMIKO_AVAILABLE if TYPE_CHECKING: from custom_file_dialog import CustomFileDialog try: import send2trash SEND2TRASH_AVAILABLE = True except ImportError: SEND2TRASH_AVAILABLE = False class SettingsDialog(tk.Toplevel): """A dialog window for configuring the application settings.""" def __init__(self, parent: 'CustomFileDialog', dialog_mode: str = "save") -> None: """ Initializes the SettingsDialog. Args: parent: The parent widget. dialog_mode (str, optional): The mode of the main dialog ("open" or "save"), which affects available settings. Defaults to "save". """ super().__init__(parent) self.transient(parent) self.grab_set() self.title(LocaleStrings.SET["title"]) self.settings = CfdConfigManager.load() self.dialog_mode = dialog_mode # Variables self.search_icon_pos = tk.StringVar( value=self.settings.get("search_icon_pos", "right")) self.button_box_pos = tk.StringVar( value=self.settings.get("button_box_pos", "left")) self.window_size_preset = tk.StringVar( value=self.settings.get("window_size_preset", "1050x850")) self.default_view_mode = tk.StringVar( value=self.settings.get("default_view_mode", "icons")) self.search_hidden_files = tk.BooleanVar( value=self.settings.get("search_hidden_files", False)) self.recursive_search = tk.BooleanVar( value=self.settings.get("recursive_search", True)) self.use_trash = tk.BooleanVar( value=self.settings.get("use_trash", False)) self.confirm_delete = tk.BooleanVar( value=self.settings.get("confirm_delete", False)) self.use_pillow_animation = tk.BooleanVar( value=self.settings.get("use_pillow_animation", False)) self.animation_type = tk.StringVar( value=self.settings.get("animation_type", "double_arc")) self.keep_bookmarks_on_reset = tk.BooleanVar( value=self.settings.get("keep_bookmarks_on_reset", True)) # --- UI Elements --- main_frame = ttk.Frame(self, padding=10) main_frame.pack(fill="both", expand=True) # Button Box Position button_box_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["button_box_pos_label"], padding=10) button_box_frame.pack(fill="x", pady=5) ttk.Radiobutton(button_box_frame, text=LocaleStrings.SET["left_radio"], variable=self.button_box_pos, value="left").pack(side="left", padx=5) ttk.Radiobutton(button_box_frame, text=LocaleStrings.SET["right_radio"], variable=self.button_box_pos, value="right").pack(side="left", padx=5) # Window Size size_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["window_size_label"], padding=10) size_frame.pack(fill="x", pady=5) sizes = ["1050x850", "850x650", "650x450"] size_combo = ttk.Combobox( size_frame, textvariable=self.window_size_preset, values=sizes, state="readonly") size_combo.pack(fill="x") # Default View Mode view_mode_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["default_view_mode_label"], padding=10) view_mode_frame.pack(fill="x", pady=5) ttk.Radiobutton(view_mode_frame, text=LocaleStrings.SET["icons_radio"], variable=self.default_view_mode, value="icons").pack(side="left", padx=5) ttk.Radiobutton(view_mode_frame, text=LocaleStrings.SET["list_radio"], variable=self.default_view_mode, value="list").pack(side="left", padx=5) # Search Hidden Files search_hidden_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["search_settings"], padding=10) search_hidden_frame.pack(fill="x", pady=5) ttk.Checkbutton(search_hidden_frame, text=LocaleStrings.SET["search_hidden_check"], variable=self.search_hidden_files).pack(anchor="w") ttk.Checkbutton(search_hidden_frame, text=LocaleStrings.SET["recursive_search_check"], variable=self.recursive_search).pack(anchor="w") # Deletion Settings delete_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["deletion_settings"], padding=10) delete_frame.pack(fill="x", pady=5) self.use_trash_checkbutton = ttk.Checkbutton(delete_frame, text=f"{LocaleStrings.SET['use_trash_check']} ({LocaleStrings.SET['recommended']})", variable=self.use_trash) self.use_trash_checkbutton.pack(anchor="w") if not SEND2TRASH_AVAILABLE: self.use_trash_checkbutton.config(state=tk.DISABLED) ttk.Label(delete_frame, text=f"({LocaleStrings.SET['send2trash_not_found']})", font=("TkDefaultFont", 9, "italic")).pack(anchor="w", padx=(20, 0)) self.confirm_delete_checkbutton = ttk.Checkbutton(delete_frame, text=LocaleStrings.SET["confirm_delete_check"], variable=self.confirm_delete) self.confirm_delete_checkbutton.pack(anchor="w") # Pillow Animation pillow_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["animation_settings"], padding=10) pillow_frame.pack(fill="x", pady=5) self.use_pillow_animation_checkbutton = ttk.Checkbutton(pillow_frame, text=f"{LocaleStrings.SET['use_pillow_check']} ({LocaleStrings.SET['pillow']})", variable=self.use_pillow_animation) self.use_pillow_animation_checkbutton.pack(anchor="w") if not PIL_AVAILABLE: self.use_pillow_animation_checkbutton.config(state=tk.DISABLED) ttk.Label(pillow_frame, text=f"({LocaleStrings.SET['pillow_not_found']})", font=("TkDefaultFont", 9, "italic")).pack(anchor="w", padx=(20, 0)) # Animation Type anim_type_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["animation_type"], padding=10) anim_type_frame.pack(fill="x", pady=5) ttk.Radiobutton(anim_type_frame, text=LocaleStrings.SET["counter_arc"], variable=self.animation_type, value="counter_arc").pack(side="left", padx=5) ttk.Radiobutton(anim_type_frame, text=LocaleStrings.SET["double_arc"], variable=self.animation_type, value="double_arc").pack(side="left", padx=5) ttk.Radiobutton(anim_type_frame, text=LocaleStrings.SET["line"], variable=self.animation_type, value="line").pack(side="left", padx=5) ttk.Radiobutton(anim_type_frame, text=LocaleStrings.SET["blink"], variable=self.animation_type, value="blink").pack(side="left", padx=5) # SFTP Settings sftp_frame = ttk.LabelFrame( main_frame, text=LocaleStrings.SET["sftp_settings"], padding=10) sftp_frame.pack(fill="x", pady=5) if not PARAMIKO_AVAILABLE: ttk.Label(sftp_frame, text=LocaleStrings.SET["paramiko_not_found"], font=("TkDefaultFont", 9, "italic")).pack(anchor="w") ttk.Label(sftp_frame, text=LocaleStrings.SET["sftp_disabled"], font=("TkDefaultFont", 9, "italic")).pack(anchor="w") else: ttk.Label(sftp_frame, text=LocaleStrings.SET["paramiko_found"], font=("TkDefaultFont", 9)).pack(anchor="w") # Keyring status try: import keyring keyring_available = True except ImportError: keyring_available = False if keyring_available: ttk.Label(sftp_frame, text="Keyring library found. Passwords will be stored securely.", font=("TkDefaultFont", 9)).pack(anchor="w", pady=(5,0)) else: ttk.Label(sftp_frame, text="Keyring library not found. Passwords cannot be saved.", font=("TkDefaultFont", 9, "italic")).pack(anchor="w", pady=(5,0)) self.keep_bookmarks_checkbutton = ttk.Checkbutton(sftp_frame, text=LocaleStrings.SET["keep_sftp_bookmarks"], variable=self.keep_bookmarks_on_reset) self.keep_bookmarks_checkbutton.pack(anchor="w", pady=(5,0)) # Disable deletion options in "open" mode if not self.dialog_mode == "save": self.use_trash_checkbutton.config(state=tk.DISABLED) self.confirm_delete_checkbutton.config(state=tk.DISABLED) info_label = ttk.Label(delete_frame, text=f"({LocaleStrings.SET['deletion_options_info']})", font=("TkDefaultFont", 9, "italic")) info_label.pack(anchor="w", padx=(20, 0)) # --- Action Buttons --- button_frame = ttk.Frame(main_frame) button_frame.pack(fill="x", pady=(10, 0)) ttk.Button(button_frame, text=LocaleStrings.SET["reset_to_default"], command=self.reset_to_defaults).pack(side="left", padx=5) ttk.Button(button_frame, text=LocaleStrings.SET["save_button"], command=self.save_settings).pack(side="right", padx=5) ttk.Button(button_frame, text=LocaleStrings.SET["cancel_button"], command=self.destroy).pack(side="right") def save_settings(self) -> None: """ Saves the current settings to the configuration file and closes the dialog. Triggers a UI rebuild in the parent dialog to apply the changes. """ new_settings = { "button_box_pos": self.button_box_pos.get(), "window_size_preset": self.window_size_preset.get(), "default_view_mode": self.default_view_mode.get(), "search_hidden_files": self.search_hidden_files.get(), "recursive_search": self.recursive_search.get(), "use_trash": self.use_trash.get(), "confirm_delete": self.confirm_delete.get(), "use_pillow_animation": self.use_pillow_animation.get(), "animation_type": self.animation_type.get(), "keep_bookmarks_on_reset": self.keep_bookmarks_on_reset.get() } CfdConfigManager.save(new_settings) self.master.reload_config_and_rebuild_ui() self.destroy() def reset_to_defaults(self) -> None: """Resets all settings in the dialog to their default values.""" defaults = CfdConfigManager._default_settings self.button_box_pos.set(defaults["button_box_pos"]) self.window_size_preset.set(defaults["window_size_preset"]) self.default_view_mode.set(defaults["default_view_mode"]) self.search_hidden_files.set(defaults["search_hidden_files"]) self.recursive_search.set(defaults["recursive_search"]) self.use_trash.set(defaults["use_trash"]) self.confirm_delete.set(defaults["confirm_delete"]) self.use_pillow_animation.set(defaults.get( "use_pillow_animation", True) and PIL_AVAILABLE) self.animation_type.set(defaults.get("animation_type", "counter_arc")) self.keep_bookmarks_on_reset.set(defaults.get("keep_bookmarks_on_reset", True))