From b350e562fa6bb6b93992f496d32e9fa1bf265915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9sir=C3=A9=20Werner=20Menrath?= Date: Sat, 2 Aug 2025 10:54:37 +0200 Subject: [PATCH] commit 37 --- .../custom_file_dialog.cpython-312.pyc | Bin 66123 -> 67430 bytes cfd_app_config.py | 1 + common_tools.py | 646 ------------------ custom_file_dialog.py | 80 ++- mainwindow.py | 3 +- 5 files changed, 47 insertions(+), 683 deletions(-) create mode 100755 cfd_app_config.py delete mode 100755 common_tools.py diff --git a/__pycache__/custom_file_dialog.cpython-312.pyc b/__pycache__/custom_file_dialog.cpython-312.pyc index 56fa39e3ccdfc5f2ca7ad176be1a680f56c8abd6..2eea4bbee2d4dcb7752358bc6ba39c5d989eee73 100644 GIT binary patch delta 8873 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 delta 8135 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 M+p+GZgmBLLzjQP2X8-^I diff --git a/cfd_app_config.py b/cfd_app_config.py new file mode 100755 index 0000000..a93a4bf --- /dev/null +++ b/cfd_app_config.py @@ -0,0 +1 @@ +#!/usr/bin/python3 diff --git a/common_tools.py b/common_tools.py deleted file mode 100755 index 2d455c9..0000000 --- a/common_tools.py +++ /dev/null @@ -1,646 +0,0 @@ -""" Classes Method and Functions for lx Apps """ - -import logging -import signal -import base64 -from subprocess import CompletedProcess, run -import re -import sys -import shutil -import tkinter as tk -from tkinter import ttk -import os -from typing import Optional, Dict, Any, NoReturn -from pathlib import Path -from tkinter import Toplevel - - -class CryptoUtil: - """ - This class is for the creation of the folders and files - required by Wire-Py, as well as for decryption - the tunnel from the user's home directory - """ - - @staticmethod - def decrypt(user) -> None: - """ - Starts SSL dencrypt - """ - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/ssl_decrypt.py", "--user", user], - capture_output=True, - text=True, - check=False, - ) - - # Output from Openssl Error - if process.stderr: - logging.error(process.stderr, exc_info=True) - - if process.returncode == 0: - logging.info("Files successfully decrypted...", exc_info=True) - else: - - logging.error( - f"Error process decrypt: Code {process.returncode}", exc_info=True - ) - - @staticmethod - def encrypt(user) -> None: - """ - Starts SSL encryption - """ - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/ssl_encrypt.py", "--user", user], - capture_output=True, - text=True, - check=False, - ) - - # Output from Openssl Error - if process.stderr: - logging.error(process.stderr, exc_info=True) - - if process.returncode == 0: - logging.info("Files successfully encrypted...", exc_info=True) - else: - logging.error( - f"Error process encrypt: Code {process.returncode}", exc_info=True - ) - - @staticmethod - def find_key(key: str = "") -> bool: - """ - Checks if the private key already exists in the system using an external script. - Returns True only if the full key is found exactly (no partial match). - """ - process: CompletedProcess[str] = run( - ["pkexec", "/usr/local/bin/match_found.py", key], - capture_output=True, - text=True, - check=False, - ) - if "True" in process.stdout: - return True - elif "False" in process.stdout: - return False - logging.error( - f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}", - exc_info=True, - ) - return False - - @staticmethod - def is_valid_base64(key: str) -> bool: - """ - Validates if the input is a valid Base64 string (WireGuard private key format). - Returns True only for non-empty strings that match the expected length. - """ - # Check for empty string - if not key or key.strip() == "": - return False - - # Regex pattern to validate Base64: [A-Za-z0-9+/]+={0,2} - base64_pattern = r"^[A-Za-z0-9+/]+={0,2}$" - if not re.match(base64_pattern, key): - return False - - try: - # Decode and check length (WireGuard private keys are 32 bytes long) - decoded = base64.b64decode(key) - if len(decoded) != 32: # 32 bytes = 256 bits - return False - except Exception as e: - logging.error(f"Error on decode Base64: {e}", exc_info=True) - return False - - return True - - -class LxTools: - """ - Class LinuxTools methods that can also be used for other apps - """ - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - @staticmethod - def center_window_cross_platform(window, width, height): - """ - Centers a window on the primary monitor in a way that works on both X11 and Wayland - - Args: - window: The tkinter window to center - width: Window width - height: Window height - """ - # Calculate the position before showing the window - - # First attempt: Try to use GDK if available (works on both X11 and Wayland) - try: - import gi - - gi.require_version("Gdk", "3.0") - from gi.repository import Gdk - - display = Gdk.Display.get_default() - monitor = display.get_primary_monitor() or display.get_monitor(0) - geometry = monitor.get_geometry() - scale_factor = monitor.get_scale_factor() - - # Calculate center position on the primary monitor - x = geometry.x + (geometry.width - width // scale_factor) // 2 - y = geometry.y + (geometry.height - height // scale_factor) // 2 - - # Set window geometry - window.geometry(f"{width}x{height}+{x}+{y}") - return - except (ImportError, AttributeError): - pass - - # Second attempt: Try xrandr for X11 - try: - import subprocess - - output = subprocess.check_output( - ["xrandr", "--query"], universal_newlines=True - ) - - # Parse the output to find the primary monitor - primary_info = None - for line in output.splitlines(): - if "primary" in line: - parts = line.split() - for part in parts: - if "x" in part and "+" in part: - primary_info = part - break - break - - if primary_info: - # Parse the geometry: WIDTH x HEIGHT+X+Y - geometry = primary_info.split("+") - dimensions = geometry[0].split("x") - primary_width = int(dimensions[0]) - primary_height = int(dimensions[1]) - primary_x = int(geometry[1]) - primary_y = int(geometry[2]) - - # Calculate center position on the primary monitor - x = primary_x + (primary_width - width) // 2 - y = primary_y + (primary_height - height) // 2 - - # Set window geometry - window.geometry(f"{width}x{height}+{x}+{y}") - return - except (ImportError, IndexError, ValueError): - pass - - # Final fallback: Use standard Tkinter method - screen_width = window.winfo_screenwidth() - screen_height = window.winfo_screenheight() - - # Try to make an educated guess for multi-monitor setups - # If screen width is much larger than height, assume multiple monitors side by side - if ( - screen_width > screen_height * 1.8 - ): # Heuristic for detecting multiple monitors - # Assume the primary monitor is on the left half - screen_width = screen_width // 2 - - x = (screen_width - width) // 2 - y = (screen_height - height) // 2 - window.geometry(f"{width}x{height}+{x}+{y}") - - @staticmethod - def clean_files(tmp_dir: Path = None, file: Path = None) -> None: - """ - Deletes temporary files and directories for cleanup when exiting the application. - - This method safely removes an optional directory defined by `AppConfig.TEMP_DIR` - and a single file to free up resources at the end of the program's execution. - All operations are performed securely, and errors such as `FileNotFoundError` - are ignored if the target files or directories do not exist. - :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted. - If `None`, the value of `AppConfig.TEMP_DIR` is used. - :param file: (Path, optional): Path to the file that should be deleted. - If `None`, no additional file will be deleted. - - Returns: - None: The method does not return any value. - """ - - if tmp_dir is not None: - shutil.rmtree(tmp_dir, ignore_errors=True) - try: - if file is not None: - Path.unlink(file) - - except FileNotFoundError: - pass - - @staticmethod - def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None: - """ - Function for cleanup after a program interruption - - :param file: Optional - File to be deleted - :param file_path: Optional - Directory to be deleted - """ - - def signal_handler(signum: int, frame: Any) -> NoReturn: - """ - Determines clear text names for signal numbers and handles signals - - Args: - signum: The signal number - frame: The current stack frame - - Returns: - NoReturn since the function either exits the program or continues execution - """ - - signals_to_names_dict: Dict[int, str] = dict( - (getattr(signal, n), n) - for n in dir(signal) - if n.startswith("SIG") and "_" not in n - ) - - signal_name: str = signals_to_names_dict.get( - signum, f"Unnamed signal: {signum}" - ) - - # End program for certain signals, report to others only reception - if signum in (signal.SIGINT, signal.SIGTERM): - exit_code: int = 1 - logging.error( - f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.", - exc_info=True, - ) - LxTools.clean_files(file_path, file) - logging.info("Breakdown by user...") - sys.exit(exit_code) - else: - logging.info(f"Signal {signum} received and ignored.") - LxTools.clean_files(file_path, file) - logging.error("Process unexpectedly ended...") - - # Register signal handlers for various signals - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) - - -# ConfigManager with caching -class ConfigManager: - """ - Universal class for managing configuration files with caching support. - - This class provides a general solution to load, save, and manage configuration - files across different projects. It uses a caching system to optimize access efficiency. - The `init()` method initializes the configuration file path, while `load()` and `save()` - synchronize data between the file and internal memory structures. - - Key Features: - - Caching to minimize I/O operations. - - Default values for missing or corrupted configuration files. - - Reusability across different projects and use cases. - - The class is designed for central application configuration management, working closely - with `ThemeManager` to dynamically manage themes or other settings. - """ - - _config = None - _config_file = None - - @classmethod - def init(cls, config_file): - """Initial the Configmanager with the given config file""" - cls._config_file = config_file - cls._config = None # Reset the cache - - @classmethod - def load(cls): - """Load the config file and return the config as dict""" - if not cls._config: - try: - lines = Path(cls._config_file).read_text( - encoding="utf-8").splitlines() - cls._config = { - "updates": lines[1].strip(), - "theme": lines[3].strip(), - "tooltips": lines[5].strip() - == "True", # is converted here to boolean!!! - "autostart": lines[7].strip() if len(lines) > 7 else "off", - "logfile": lines[9].strip(), - } - except (IndexError, FileNotFoundError): - # DeDefault values in case of error - cls._config = { - "updates": "on", - "theme": "light", - "tooltips": "True", # Default Value as string! - "autostart": "off", - "logfile": LOG_FILE_PATH, - } - return cls._config - - @classmethod - def save(cls): - """Save the config to the config file""" - if cls._config: - lines = [ - "# Configuration\n", - f"{cls._config['updates']}\n", - "# Theme\n", - f"{cls._config['theme']}\n", - "# Tooltips\n", - f"{str(cls._config['tooltips'])}\n", - "# Autostart\n", - f"{cls._config['autostart']}\n", - "# Logfile\n", - f"{cls._config['logfile']}\n", - ] - Path(cls._config_file).write_text("".join(lines), encoding="utf-8") - - @classmethod - def set(cls, key, value): - """Sets a configuration value and saves the change""" - cls.load() - cls._config[key] = value - cls.save() - - @classmethod - def get(cls, key, default=None): - """Returns a configuration value""" - config = cls.load() - return config.get(key, default) - - -class ThemeManager: - """ - Class for central theme management and UI customization. - - This static class allows dynamic adjustment of the application's appearance. - The method `change_theme()` updates the current theme and saves - the selection in the configuration file via `ConfigManager`. - It ensures a consistent visual design across the entire project. - - Key Features: - - Central control over themes. - - Automatic saving of theme settings to the configuration file. - - Tight integration with `ConfigManager` for persistent storage of preferences. - - The class is designed to apply themes consistently throughout the application, - ensuring that changes are traceable and uniform across all parts of the project. - """ - - @staticmethod - def change_theme(root, theme_in_use, theme_name=None): - """Change application theme centrally""" - root.tk.call("set_theme", theme_in_use) - if theme_in_use == theme_name: - ConfigManager.set("theme", theme_in_use) - - -class Tooltip: - def __init__(self, widget, text, wraplength=250, state_var=None): - self.widget = widget - self.text = text - self.wraplength = wraplength - self.state_var = state_var - self.tooltip_window = None - self.id = None - self.update_bindings() - if self.state_var: - self.state_var.trace_add("write", self.update_bindings) - - def update_bindings(self, *args): - self.widget.unbind("") - self.widget.unbind("") - self.widget.unbind("") - - if self.state_var is None or self.state_var.get(): - self.widget.bind("", self.enter) - self.widget.bind("", self.leave) - self.widget.bind("", self.leave) - - def enter(self, event=None): - if self.state_var is None or self.state_var.get(): - self.schedule() - - def leave(self, event=None): - self.unschedule() - self.hide_tooltip() - - def schedule(self): - self.unschedule() - self.id = self.widget.after(250, self.show_tooltip) - - def unschedule(self): - id = self.id - self.id = None - if id: - self.widget.after_cancel(id) - - def show_tooltip(self, event=None): - if self.tooltip_window or not self.text: - return - x, y, _, _ = self.widget.bbox("insert") - x += self.widget.winfo_rootx() + 25 - y += self.widget.winfo_rooty() + 20 - self.tooltip_window = tw = tk.Toplevel(self.widget) - tw.wm_overrideredirect(True) - tw.wm_geometry(f"+" + str(x) + "+" + str(y)) - label = ttk.Label(tw, text=self.text, justify=tk.LEFT, background="#FFFFE0", foreground="black", - relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength, padding=(4, 2, 4, 2)) - label.pack(ipadx=1) - - def hide_tooltip(self): - tw = self.tooltip_window - self.tooltip_window = None - if tw: - tw.destroy() - - -class LogConfig: - @staticmethod - def logger(file_path) -> None: - - file_handler = logging.FileHandler( - filename=f"{file_path}", - mode="a", - encoding="utf-8", - ) - formatter = logging.Formatter( - "%(asctime)s - %(levelname)s - %(message)s") - file_handler.setFormatter(formatter) - file_handler.setLevel(logging.DEBUG) - - logger = logging.getLogger() - logger.addHandler(file_handler) - - -class IconManager: - def __init__(self, base_path='/usr/share/icons/lx-icons/'): - self.base_path = base_path - self.icons = {} - self._define_icon_paths() - self._load_all() - - def _define_icon_paths(self): - self.icon_paths = { - # 16x16 - 'settings_16': '16/settings.png', - - # 32x32 - 'back': '32/arrow-left.png', - 'forward': '32/arrow-right.png', - 'audio_small': '32/audio.png', - 'icon_view': '32/carrel.png', - 'computer_small': '32/computer.png', - 'device_small': '32/device.png', - 'file_small': '32/document.png', - 'download_error_small': '32/download_error.png', - 'download_small': '32/download.png', - 'error_small': '32/error.png', - 'python_small': '32/file-python.png', - 'documents_small': '32/folder-water-documents.png', - 'downloads_small': '32/folder-water-download.png', - 'music_small': '32/folder-water-music.png', - 'pictures_small': '32/folder-water-pictures.png', - 'folder_small': '32/folder-water.png', - 'video_small': '32/folder-water-video.png', - 'hide': '32/hide.png', - 'home': '32/home.png', - 'info_small': '32/info.png', - 'list_view': '32/list.png', - 'log_small': '32/log.png', - 'lunix_tools_small': '32/Lunix_Tools.png', - 'key_small': '32/lxtools_key.png', - 'iso_small': '32/media-optical.png', - 'new_document_small': '32/new-document.png', - 'new_folder_small': '32/new-folder.png', - 'pdf_small': '32/pdf.png', - 'picture_small': '32/picture.png', - 'question_mark_small': '32/question_mark.png', - 'recursive_small': '32/recursive.png', - 'search_small': '32/search.png', - 'settings_small': '32/settings.png', - 'archive_small': '32/tar.png', - 'unhide': '32/unhide.png', - 'usb_small': '32/usb.png', - 'video_small_file': '32/video.png', - 'warning_small': '32/warning.png', - 'export_small': '32/wg_export.png', - 'import_small': '32/wg_import.png', - 'message_small': '32/wg_msg.png', - 'trash_small': '32/wg_trash.png', - 'vpn_small': '32/wg_vpn.png', - 'vpn_start_small': '32/wg_vpn-start.png', - 'vpn_stop_small': '32/wg_vpn-stop.png', - - # 48x48 - 'back_large': '48/arrow-left.png', - 'forward_large': '48/arrow-right.png', - 'icon_view_large': '48/carrel.png', - 'computer_large': '48/computer.png', - 'device_large': '48/device.png', - 'download_error_large': '48/download_error.png', - 'download_large': '48/download.png', - 'error_large': '48/error.png', - 'documents_large': '48/folder-water-documents.png', - 'downloads_large': '48/folder-water-download.png', - 'music_large': '48/folder-water-music.png', - 'pictures_large': '48/folder-water-pictures.png', - 'folder_large_48': '48/folder-water.png', - 'video_large_folder': '48/folder-water-video.png', - 'hide_large': '48/hide.png', - 'home_large': '48/home.png', - 'info_large': '48/info.png', - 'list_view_large': '48/list.png', - 'log_large': '48/log.png', - 'lunix_tools_large': '48/Lunix_Tools.png', - 'new_document_large': '48/new-document.png', - 'new_folder_large': '48/new-folder.png', - 'question_mark_large': '48/question_mark.png', - 'search_large_48': '48/search.png', - 'settings_large': '48/settings.png', - 'unhide_large': '48/unhide.png', - 'usb_large': '48/usb.png', - 'warning_large_48': '48/warning.png', - 'export_large': '48/wg_export.png', - 'import_large': '48/wg_import.png', - 'message_large': '48/wg_msg.png', - 'trash_large': '48/wg_trash.png', - 'vpn_large': '48/wg_vpn.png', - 'vpn_start_large': '48/wg_vpn-start.png', - 'vpn_stop_large': '48/wg_vpn-stop.png', - - # 64x64 - 'back_extralarge': '64/arrow-left.png', - 'forward_extralarge': '64/arrow-right.png', - 'audio_large': '64/audio.png', - 'icon_view_extralarge': '64/carrel.png', - 'computer_extralarge': '64/computer.png', - 'device_extralarge': '64/device.png', - 'file_large': '64/document.png', - 'download_error_extralarge': '64/download_error.png', - 'download_extralarge': '64/download.png', - 'error_extralarge': '64/error.png', - 'python_large': '64/file-python.png', - 'documents_extralarge': '64/folder-water-documents.png', - 'downloads_extralarge': '64/folder-water-download.png', - 'music_extralarge': '64/folder-water-music.png', - 'pictures_extralarge': '64/folder-water-pictures.png', - 'folder_large': '64/folder-water.png', - 'video_extralarge_folder': '64/folder-water-video.png', - 'hide_extralarge': '64/hide.png', - 'home_extralarge': '64/home.png', - 'info_extralarge': '64/info.png', - 'list_view_extralarge': '64/list.png', - 'log_extralarge': '64/log.png', - 'lunix_tools_extralarge': '64/Lunix_Tools.png', - 'iso_large': '64/media-optical.png', - 'new_document_extralarge': '64/new-document.png', - 'new_folder_extralarge': '64/new-folder.png', - 'pdf_large': '64/pdf.png', - 'picture_large': '64/picture.png', - 'question_mark_extralarge': '64/question_mark.png', - 'recursive_large': '64/recursive.png', - 'search_large': '64/search.png', - 'settings_extralarge': '64/settings.png', - 'archive_large': '64/tar.png', - 'unhide_extralarge': '64/unhide.png', - 'usb_extralarge': '64/usb.png', - 'video_large': '64/video.png', - 'warning_large': '64/warning.png', - 'export_extralarge': '64/wg_export.png', - 'import_extralarge': '64/wg_import.png', - 'message_extralarge': '64/wg_msg.png', - 'trash_extralarge': '64/wg_trash.png', - 'vpn_extralarge': '64/wg_vpn.png', - 'vpn_start_extralarge': '64/wg_vpn-start.png', - 'vpn_stop_extralarge': '64/wg_vpn-stop.png', - } - - def _load_all(self): - for key, rel_path in self.icon_paths.items(): - full_path = os.path.join(self.base_path, rel_path) - try: - self.icons[key] = tk.PhotoImage(file=full_path) - except tk.TclError as e: - print(f"Error loading icon '{key}' from '{full_path}': {e}") - size = 32 # Default size - if '16' in rel_path: - size = 16 - elif '48' in rel_path: - size = 48 - elif '64' in rel_path: - size = 64 - self.icons[key] = tk.PhotoImage(width=size, height=size) - - def get_icon(self, name): - return self.icons.get(name) \ No newline at end of file diff --git a/custom_file_dialog.py b/custom_file_dialog.py index 7f38a00..444b0ab 100644 --- a/custom_file_dialog.py +++ b/custom_file_dialog.py @@ -41,7 +41,7 @@ class CustomFileDialog(tk.Toplevel): super().__init__(parent) self.parent = parent self.title("Datei auswählen") - self.geometry("900x650") + self.geometry("1100x850") self.minsize(650, 400) self.transient(parent) self.grab_set() @@ -49,7 +49,7 @@ class CustomFileDialog(tk.Toplevel): self.selected_file = None self.current_dir = os.path.abspath( initial_dir) if initial_dir else os.path.expanduser("~") - self.filetypes = filetypes if filetypes else [("Alle Dateien", "*.*")] + self.filetypes = filetypes if filetypes else [("Alle Dateien", "*.* ")] self.current_filter_pattern = self.filetypes[0][1] self.history = [] self.history_pos = -1 @@ -64,7 +64,6 @@ class CustomFileDialog(tk.Toplevel): self.original_path_text = "" # Store original path text self.icon_manager = IconManager() - self.icons = self.icon_manager.icons self.create_styles() self.create_widgets() self.navigate_to(self.current_dir) @@ -72,22 +71,22 @@ class CustomFileDialog(tk.Toplevel): def get_file_icon(self, filename, size='large'): ext = os.path.splitext(filename)[1].lower() if ext == '.svg': - return self.icons[f'warning_{size}'] + return self.icon_manager.get_icon(f'warning_{size}') if ext == '.py': - return self.icons[f'python_{size}'] + return self.icon_manager.get_icon(f'python_{size}') if ext == '.pdf': - return self.icons[f'pdf_{size}'] + return self.icon_manager.get_icon(f'pdf_{size}') if ext in ['.tar', '.zip', '.rar', '.7z', '.gz']: - return self.icons[f'archive_{size}'] + return self.icon_manager.get_icon(f'archive_{size}') if ext in ['.mp3', '.wav', '.ogg', '.flac']: - return self.icons[f'audio_{size}'] + return self.icon_manager.get_icon(f'audio_{size}') if ext in ['.mp4', '.mkv', '.avi', '.mov']: - return self.icons[f'video_{size}'] if size == 'large' else self.icons['video_small_file'] + return self.icon_manager.get_icon(f'video_{size}') if size == 'large' else self.icon_manager.get_icon('video_small_file') if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: - return self.icons[f'picture_{size}'] + return self.icon_manager.get_icon(f'picture_{size}') if ext == '.iso': - return self.icons[f'iso_{size}'] - return self.icons[f'file_{size}'] + return self.icon_manager.get_icon(f'iso_{size}') + return self.icon_manager.get_icon(f'file_{size}') def create_styles(self): style = ttk.Style(self) @@ -188,16 +187,16 @@ class CustomFileDialog(tk.Toplevel): nav_buttons_container.grid(row=0, column=0, sticky="w") self.back_button = ttk.Button( - nav_buttons_container, image=self.icons['back'], command=self.go_back, state=tk.DISABLED, style="Header.TButton.Borderless.Round") + nav_buttons_container, image=self.icon_manager.get_icon('back'), command=self.go_back, state=tk.DISABLED, style="Header.TButton.Borderless.Round") self.back_button.pack(side="left", padx=10) Tooltip(self.back_button, "Zurück") self.forward_button = ttk.Button( - nav_buttons_container, image=self.icons['forward'], command=self.go_forward, state=tk.DISABLED, style="Header.TButton.Borderless.Round") + nav_buttons_container, image=self.icon_manager.get_icon('forward'), command=self.go_forward, state=tk.DISABLED, style="Header.TButton.Borderless.Round") self.forward_button.pack(side="left") Tooltip(self.forward_button, "Vorwärts") - self.home_button = ttk.Button(nav_buttons_container, image=self.icons['home'], command=lambda: self.navigate_to( + self.home_button = ttk.Button(nav_buttons_container, image=self.icon_manager.get_icon('home'), command=lambda: self.navigate_to( os.path.expanduser("~")), style="Header.TButton.Borderless.Round") self.home_button.pack(side="left", padx=10) Tooltip(self.home_button, "Home") @@ -217,7 +216,7 @@ class CustomFileDialog(tk.Toplevel): right_top_bar_frame, style='Accent.TFrame') search_container.pack(side="left", padx=(0, 10)) - self.search_button = ttk.Button(search_container, image=self.icons['search_small'], + self.search_button = ttk.Button(search_container, image=self.icon_manager.get_icon('search_small'), command=self.toggle_search_mode, style="Header.TButton.Borderless.Round") self.search_button.pack(side="left") Tooltip(self.search_button, "Suchen") @@ -228,7 +227,7 @@ class CustomFileDialog(tk.Toplevel): # Recursive search toggle button self.recursive_search = tk.BooleanVar(value=True) - self.recursive_button = ttk.Button(self.search_options_frame, image=self.icons['recursive_small'], + self.recursive_button = ttk.Button(self.search_options_frame, image=self.icon_manager.get_icon('recursive_small'), command=self.toggle_recursive_search, style="Header.TButton.Active.Round") self.recursive_button.pack(side="left", padx=2) @@ -238,18 +237,18 @@ class CustomFileDialog(tk.Toplevel): padding=(5, 0), style='Accent.TFrame') view_switch.pack(side="left") - self.icon_view_button = ttk.Button(view_switch, image=self.icons['icon_view'], + self.icon_view_button = ttk.Button(view_switch, image=self.icon_manager.get_icon('icon_view'), command=self.set_icon_view, style="Header.TButton.Active.Round") self.icon_view_button.pack(side="left", padx=(50, 10)) Tooltip(self.icon_view_button, "Kachelansicht") - self.list_view_button = ttk.Button(view_switch, image=self.icons['list_view'], + self.list_view_button = ttk.Button(view_switch, image=self.icon_manager.get_icon('list_view'), command=self.set_list_view, style="Header.TButton.Borderless.Round") self.list_view_button.pack(side="left") Tooltip(self.list_view_button, "Listenansicht") self.hidden_files_button = ttk.Button( - right_top_bar_frame, image=self.icons['hide'], command=self.toggle_hidden_files, style="Header.TButton.Borderless.Round") + right_top_bar_frame, image=self.icon_manager.get_icon('hide'), command=self.toggle_hidden_files, style="Header.TButton.Borderless.Round") self.hidden_files_button.pack(side="left", padx=10) Tooltip(self.hidden_files_button, "Versteckte Dateien anzeigen") @@ -278,16 +277,16 @@ class CustomFileDialog(tk.Toplevel): row=0, column=0, sticky="nsew") sidebar_buttons_config = [ {'name': 'Computer', - 'icon': self.icons['computer_small'], 'path': '/'}, - {'name': 'Downloads', 'icon': self.icons['downloads_small'], 'path': get_xdg_user_dir( + 'icon': self.icon_manager.get_icon('computer_small'), 'path': '/'}, + {'name': 'Downloads', 'icon': self.icon_manager.get_icon('downloads_small'), 'path': get_xdg_user_dir( "XDG_DOWNLOAD_DIR", "Downloads")}, - {'name': 'Dokumente', 'icon': self.icons['documents_small'], 'path': get_xdg_user_dir( + {'name': 'Dokumente', 'icon': self.icon_manager.get_icon('documents_small'), 'path': get_xdg_user_dir( "XDG_DOCUMENTS_DIR", "Documents")}, - {'name': 'Bilder', 'icon': self.icons['pictures_small'], 'path': get_xdg_user_dir( + {'name': 'Bilder', 'icon': self.icon_manager.get_icon('pictures_small'), 'path': get_xdg_user_dir( "XDG_PICTURES_DIR", "Pictures")}, - {'name': 'Musik', 'icon': self.icons['music_small'], 'path': get_xdg_user_dir( + {'name': 'Musik', 'icon': self.icon_manager.get_icon('music_small'), 'path': get_xdg_user_dir( "XDG_MUSIC_DIR", "Music")}, - {'name': 'Videos', 'icon': self.icons['video_small'], 'path': get_xdg_user_dir( + {'name': 'Videos', 'icon': self.icon_manager.get_icon('video_small'), 'path': get_xdg_user_dir( "XDG_VIDEO_DIR", "Videos")}, ] for config in sidebar_buttons_config: @@ -364,7 +363,8 @@ class CustomFileDialog(tk.Toplevel): # Populate devices for device_name, mount_point, removable in self._get_mounted_devices(): - icon = self.icons['usb_small'] if removable else self.icons['device_small'] + icon = self.icon_manager.get_icon( + 'usb_small') if removable else self.icon_manager.get_icon('device_small') button_text = f" {device_name}" if len(device_name) > 15: # Static wrapping for long names button_text = f" {device_name[:15]}\n{device_name[15:]}" @@ -408,7 +408,7 @@ class CustomFileDialog(tk.Toplevel): # Storage section at bottom - use pack instead of grid to stay at bottom storage_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame") - storage_frame.grid(row=4, column=0, sticky="sew", padx=10, pady=10) + storage_frame.grid(row=5, column=0, sticky="sew", padx=10, pady=10) self.storage_label = ttk.Label( storage_frame, text="Freier Speicher:", background=self.freespace_background) self.storage_label.pack(fill="x", padx=10) @@ -437,18 +437,20 @@ class CustomFileDialog(tk.Toplevel): self.status_bar = ttk.Label( bottom_controls_frame, text="", anchor="w", style="AccentBottom.TLabel") - self.status_bar.grid(row=0, column=1, columnspan=2, + self.status_bar.grid(row=0, column=1, columnspan=3, sticky="ew", padx=10, pady=10) ttk.Button(bottom_controls_frame, text="Öffnen", - command=self.on_open).grid(row=0, column=0, padx=10) + command=self.on_open).grid(row=0, column=0, padx=(10, 0)) + ttk.Button(bottom_controls_frame, text="Speichern", + command=self.on_save).grid(row=1, column=0, padx=(10, 0)) ttk.Button(bottom_controls_frame, text="Abbrechen", - command=self.on_cancel).grid(row=1, column=0, padx=10) + command=self.on_cancel).grid(row=1, column=1, columnspan=3, padx=(10, 0)) self.filter_combobox = ttk.Combobox( bottom_controls_frame, values=[ft[0] for ft in self.filetypes], state="readonly") self.filter_combobox.grid( - row=1, column=1, sticky="w", padx=10, pady=(5, 10)) + row=1, column=2, sticky="w", padx=(0, 10), pady=(5, 10)) self.filter_combobox.bind( "<>", self.on_filter_change) self.filter_combobox.set(self.filetypes[0][0]) @@ -456,10 +458,12 @@ class CustomFileDialog(tk.Toplevel): def toggle_hidden_files(self): self.show_hidden_files.set(not self.show_hidden_files.get()) if self.show_hidden_files.get(): - self.hidden_files_button.config(image=self.icons['unhide']) + self.hidden_files_button.config( + image=self.icon_manager.get_icon('unhide')) Tooltip(self.hidden_files_button, "Versteckte Dateien ausblenden") else: - self.hidden_files_button.config(image=self.icons['hide']) + self.hidden_files_button.config( + image=self.icon_manager.get_icon('hide')) Tooltip(self.hidden_files_button, "Versteckte Dateien anzeigen") self.populate_files() @@ -835,7 +839,7 @@ class CustomFileDialog(tk.Toplevel): container_frame, width=item_width, height=item_height, style="Item.TFrame") item_frame.grid(row=row, column=col, padx=5, ipadx=25, pady=5) item_frame.grid_propagate(False) - icon = self.icons['folder_large'] if is_dir else self.get_file_icon( + icon = self.icon_manager.get_icon('folder_large') if is_dir else self.get_file_icon( name, 'large') icon_label = ttk.Label(item_frame, image=icon, style="Icon.TLabel") icon_label.pack(pady=(10, 5)) @@ -912,7 +916,8 @@ class CustomFileDialog(tk.Toplevel): modified_time = datetime.fromtimestamp( stat.st_mtime).strftime('%d.%m.%Y %H:%M') if is_dir: - icon, file_type, size = self.icons['folder_small'], "Ordner", "" + icon, file_type, size = self.icon_manager.get_icon( + 'folder_small'), "Ordner", "" else: icon, file_type, size = self.get_file_icon( name, 'small'), "Datei", self._format_size(stat.st_size) @@ -1051,6 +1056,9 @@ class CustomFileDialog(tk.Toplevel): return self.destroy() + def on_save(self): + pass + def on_cancel(self): self.selected_file = None self.destroy() diff --git a/mainwindow.py b/mainwindow.py index 2683470..6a1f6c9 100755 --- a/mainwindow.py +++ b/mainwindow.py @@ -33,6 +33,7 @@ class GlotzMol(tk.Tk): dialog = CustomFileDialog(self, initial_dir=os.path.expanduser("~"), filetypes=[("Wireguard Files (.conf)", "*.conf"), + ("All Files", "*.*") ]) # This is the crucial part: wait for the dialog to be closed @@ -55,7 +56,7 @@ if __name__ == "__main__": style = ttk.Style(root) root.tk.call('source', f"{theme_path}/water.tcl") try: - root.tk.call('set_theme', 'dark') + root.tk.call('set_theme', 'light') except tk.TclError: pass root.mainloop()