From 2880e0d7a16f12ea42444aea8a698b9b4c534175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9sir=C3=A9=20Werner=20Menrath?= Date: Sun, 3 Aug 2025 14:46:55 +0200 Subject: [PATCH] commit 47 --- __pycache__/cfd_app_config.cpython-312.pyc | Bin 5964 -> 4105 bytes .../custom_file_dialog.cpython-312.pyc | Bin 72118 -> 73910 bytes cfd_app_config.py | 51 +----- custom_file_dialog.py | 152 ++++++++++-------- mainwindow.py | 4 +- 5 files changed, 93 insertions(+), 114 deletions(-) diff --git a/__pycache__/cfd_app_config.cpython-312.pyc b/__pycache__/cfd_app_config.cpython-312.pyc index 5d59b468563e292827a07afcdfae710ae7084617..8103eb7c83fbfd5568599c8dbfe7c450f781a5ba 100644 GIT binary patch delta 1224 zcmZ8gO>7%Q6rS0g9q-y+|HZYPrfSxzsIpW|OG2v(LfVj|Rn=lujeB4@Sf(>hw)O5> zvumZvfdnZ%aDp(0^iT<|5H2X(D#U>!m0E-lXi>$53mlLQNR_yhnN2AbBh9z-eeb<_ z^Jd@NpM1~CePfzwAjau8-(SgPwsWIBT1?}t&~S?Ap@wHDlVH(35vM%ge%SK84^gXiE$IK$7v zk2V~WW@wJ6Gr~?yg4Q$QRQW{H5-m?~q7@P?B{iZGgQQ7@WbY!UNK15tj&zh!xkHYa ziARp)Nr4neiHzLUoO0rG;F(W3*||b$KMQe?ZZ zcS-$1*-K*r`KPId-i+0t)3FhMlA4}~3k!?q=dN9Dww4x~&5Ml-OSa-3L;$!~RZ+d- z{w}WFJ*x00dZm_Gb$t@7wnBHE#`&kjbAA4z@e2RY`0BW=#Hn7JQ9lY}o!)Z8s1@97 z*ea9#5aUD%W6irsTxN4W-G^uPeo6lZ;W&SgU4XCivN?AwD}31@8GvpCuFnc`skssL z!Z^Lk#G)0^Tam4>!+g^$<8f)P@-NLQ|J*!+X9W%4;%D;*`CsN@u`Z}M7G^Z+GrwVv z@^|x_8_bYF`Btm#`$3d+LD*^~$7D%#ax_AsIR^JGhV|A%{HAdp^UuoDndr8>Xl!R#s3lii-Fqx3OCSg&) zQFcx)|EpR)GA7}&nBk;=+hBnBSmoGM;v`MB$zB#8*(A|Q*L|Cb9s}%@lyJ1xbN%K0 zR2tu@9L880b$+ih)=M@&C;794f9o?|K>A%e%Pt866f=|_0~JCz!~mXn1ZqD4<7aT_ r5tw|8UxiacAYkwg1X_Lb;t$CB7FiGLr@lj{H{oM!K>P;~kYxA=S2-C- delta 2918 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 B%Ul2e delta 6442 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 diff --git a/cfd_app_config.py b/cfd_app_config.py index 481c236..fd264e4 100755 --- a/cfd_app_config.py +++ b/cfd_app_config.py @@ -1,9 +1,8 @@ #!/usr/bin/python3 -"""App configuration for Wire-Py""" -import logging + +"""App configuration for Custom File Dialog""" from pathlib import Path import os -from subprocess import CompletedProcess, run from typing import Dict, Any from shared_libs.common_tools import Translate @@ -19,7 +18,7 @@ class AppConfig: Key Responsibilities: - Centralizes all configuration values (paths, UI preferences, localization). - - Ensures required directories and files exist on startup. + - Ensures required directories and files exist. - Handles translation setup via `gettext` for multilingual support. - Manages default settings file generation. - Configures autostart services using systemd for user-specific launch behavior. @@ -32,11 +31,6 @@ class AppConfig: SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) MAX_ITEMS_TO_DISPLAY = 1000 - # Logging - LOG_DIR = Path.home() / ".local/share/lxlogs" - Path(LOG_DIR).mkdir(parents=True, exist_ok=True) - LOG_FILE_PATH = LOG_DIR / "cfiledialog.log" - # Base paths BASE_DIR: Path = Path.home() CONFIG_DIR: Path = BASE_DIR / ".config/cfiledialog" @@ -48,29 +42,17 @@ class AppConfig: "# Theme": "dark", "# Tooltips": True, "# Autostart": "off", - "# Logfile": LOG_FILE_PATH, } - # Updates - # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year - VERSION: str = "v. 1.07.2725" - UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/example/releases" - DOWNLOAD_URL: str = "https://git.ilunix.de/punix/example/archive" - # UI configuration UI_CONFIG: Dict[str, Any] = { - "window_title": "File Dialog", "window_size": (1050, 850), + "window_min_size": (750, 550), "font_family": "Ubuntu", "font_size": 11, "resizable_window": (True, True), } - # System-dependent paths - SYSTEM_PATHS: Dict[str, Path] = { - "tcl_path": "/usr/share/TK-Themes", - } - @classmethod def ensure_directories(cls) -> None: """Ensures that all required directories exist""" @@ -86,37 +68,12 @@ class AppConfig: ) cls.SETTINGS_FILE.write_text(content) - @classmethod - def ensure_log(cls) -> None: - """Ensures that the log file exists""" - if not cls.LOG_FILE_PATH.exists(): - cls.LOG_FILE_PATH.touch() - # here is initializing the class for translation strings _ = Translate.setup_translations("custom_file_fialog") class Msg: - """ - A utility class that provides centralized access to translated message strings. - - This class contains a dictionary of message strings used throughout the custom file dialog application. - All strings are prepared for translation using gettext. The short key names make the code - more concise while maintaining readability. - - Attributes: - STR (dict): A dictionary mapping short keys to translated message strings. - Keys are abbreviated for brevity but remain descriptive. - - Usage: - Import this class and access messages using the dictionary: - `Msg.STR["sel_tl"]` returns the translated "Select tunnel" message. - - Note: - Ensure that gettext translation is properly initialized before - accessing these strings to ensure correct localization. - """ STR: Dict[str, str] = { # Strings for messages diff --git a/custom_file_dialog.py b/custom_file_dialog.py index 75389e7..16a42c5 100644 --- a/custom_file_dialog.py +++ b/custom_file_dialog.py @@ -6,18 +6,13 @@ from datetime import datetime import subprocess import json from shared_libs.message import MessageDialog -from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools, ThemeManager +from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools from cfd_app_config import AppConfig from cfd_ui_setup import StyleManager, WidgetManager, get_xdg_user_dir -# Helper to make icon paths robust, so the script can be run from anywhere -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -MAX_ITEMS_TO_DISPLAY = 1000 - - class CustomFileDialog(tk.Toplevel): - def __init__(self, parent, initial_dir=None, filetypes=None, dialog_mode="open"): + def __init__(self, parent, initial_dir=None, filetypes=None, dialog_mode="open", title="File Dialog"): super().__init__(parent) self.my_tool_tip = None @@ -27,15 +22,10 @@ class CustomFileDialog(tk.Toplevel): self.y_height = AppConfig.UI_CONFIG["window_size"][1] # Set the window size self.geometry(f"{self.x_width}x{self.y_height}") - self.minsize(AppConfig.UI_CONFIG["window_size"][0], - AppConfig.UI_CONFIG["window_size"][1], + self.minsize(AppConfig.UI_CONFIG["window_min_size"][0], + AppConfig.UI_CONFIG["window_min_size"][1], ) - self.title(AppConfig.UI_CONFIG["window_title"]) - # self.tk.call( - # "source", f"{AppConfig.SYSTEM_PATHS['tcl_path']}/water.tcl") - # ConfigManager.init(AppConfig.SETTINGS_FILE) - # theme = ConfigManager.get("theme") - # ThemeManager.change_theme(self, theme) + self.title(title) self.image = IconManager() LxTools.center_window_cross_platform(self, self.x_width, self.y_height) self.parent = parent @@ -67,7 +57,7 @@ class CustomFileDialog(tk.Toplevel): def get_file_icon(self, filename, size='large'): ext = os.path.splitext(filename)[1].lower() - + if ext == '.py': return self.icon_manager.get_icon(f'python_{size}') if ext == '.pdf': @@ -381,14 +371,16 @@ class CustomFileDialog(tk.Toplevel): filename = item['text'].strip() directory = item['values'][0] full_path = os.path.join(directory, filename) - + # Update status bar try: stat = os.stat(full_path) size_str = self._format_size(stat.st_size) - self.widget_manager.status_bar.config(text=f"'{filename}' Größe: {size_str}") + self.widget_manager.status_bar.config( + text=f"'{filename}' Größe: {size_str}") except (FileNotFoundError, PermissionError): - self.widget_manager.status_bar.config(text=f"'{filename}' nicht zugänglich") + self.widget_manager.status_bar.config( + text=f"'{filename}' nicht zugänglich") # If in save mode, update filename entry if self.dialog_mode == "save": @@ -437,9 +429,9 @@ class CustomFileDialog(tk.Toplevel): directory = item['values'][0] # Exit search mode and navigate to the directory - self.toggle_search_mode() # To restore normal view + self.toggle_search_mode() # To restore normal view self.navigate_to(directory) - + # Select the file in the list self.after(100, lambda: self._select_file_in_view(filename)) @@ -451,7 +443,7 @@ class CustomFileDialog(tk.Toplevel): self.tree.focus(item_id) self.tree.see(item_id) break - else: # icon view + else: # icon view # This is more complex as items are in a grid. A simple selection is not straightforward. # For now, we just navigate to the folder. pass @@ -499,13 +491,13 @@ class CustomFileDialog(tk.Toplevel): try: if not os.path.isdir(folder_path) or not os.access(folder_path, os.R_OK): return None - + items = os.listdir(folder_path) - + # Filter based on hidden files setting if not self.show_hidden_files.get(): items = [item for item in items if not item.startswith('.')] - + return len(items) except (PermissionError, FileNotFoundError): return None @@ -558,9 +550,12 @@ class CustomFileDialog(tk.Toplevel): scrollregion=canvas.bbox("all"))) def _on_mouse_wheel(event): - if event.num == 4: delta = -1 - elif event.num == 5: delta = 1 - else: delta = -1 * int(event.delta / 120) + if event.num == 4: + delta = -1 + elif event.num == 5: + delta = 1 + else: + delta = -1 * int(event.delta / 120) canvas.yview_scroll(delta, "units") # Check if scrolled to the bottom and if there are more items to load if self.currently_loaded_count < len(self.all_items) and canvas.yview()[1] > 0.9: @@ -577,18 +572,21 @@ class CustomFileDialog(tk.Toplevel): ttk.Label(container_frame, text=error).pack(pady=20) return - self._load_more_items_icon_view(container_frame, item_to_rename, item_to_select) + self._load_more_items_icon_view( + container_frame, item_to_rename, item_to_select) def _load_more_items_icon_view(self, container, item_to_rename=None, item_to_select=None): start_index = self.currently_loaded_count - end_index = min(len(self.all_items), start_index + self.items_to_load_per_batch) + end_index = min(len(self.all_items), start_index + + self.items_to_load_per_batch) - if start_index >= end_index: return # All items loaded + if start_index >= end_index: + return # All items loaded item_width, item_height = 125, 100 frame_width = self.widget_manager.file_list_frame.winfo_width() col_count = max(1, frame_width // item_width - 1) - + row = start_index // col_count col = start_index % col_count @@ -609,12 +607,15 @@ class CustomFileDialog(tk.Toplevel): if name == item_to_rename: self.start_rename(item_frame, path) else: - 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 = 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)) - name_label = ttk.Label(item_frame, text=self.shorten_text(name, 14), anchor="center", style="Item.TLabel") + name_label = ttk.Label(item_frame, text=self.shorten_text( + name, 14), anchor="center", style="Item.TLabel") name_label.pack(fill="x", expand=True) - + tooltip_text = name if is_dir and len(self.all_items) < 500: content_count = self._get_folder_content_count(path) @@ -623,17 +624,22 @@ class CustomFileDialog(tk.Toplevel): Tooltip(item_frame, tooltip_text) for widget in [item_frame, icon_label, name_label]: - widget.bind("", lambda e, p=path: self.on_item_double_click(p)) - widget.bind("", lambda e, p=path, f=item_frame: self.on_item_select(p, f)) - widget.bind("", lambda e, p=path: self._show_context_menu(e, p)) - widget.bind("", lambda e, p=path, f=item_frame: self.on_rename_request(e, p, f)) + widget.bind("", lambda e, + p=path: self.on_item_double_click(p)) + widget.bind("", lambda e, p=path, + f=item_frame: self.on_item_select(p, f)) + widget.bind("", lambda e, + p=path: self._show_context_menu(e, p)) + widget.bind("", lambda e, p=path, + f=item_frame: self.on_rename_request(e, p, f)) if name == item_to_select: self.on_item_select(path, item_frame) col = (col + 1) % col_count - if col == 0: row += 1 - + if col == 0: + row += 1 + self.currently_loaded_count = end_index def populate_list_view(self, item_to_rename=None, item_to_select=None): @@ -646,7 +652,8 @@ class CustomFileDialog(tk.Toplevel): tree_frame.grid_columnconfigure(0, weight=1) columns = ("size", "type", "modified") - self.tree = ttk.Treeview(tree_frame, columns=columns, show="tree headings") + self.tree = ttk.Treeview( + tree_frame, columns=columns, show="tree headings") self.tree.heading("#0", text="Name", anchor="w") self.tree.column("#0", anchor="w", width=250, stretch=True) @@ -657,9 +664,12 @@ class CustomFileDialog(tk.Toplevel): self.tree.heading("modified", text="Geändert am", anchor="w") self.tree.column("modified", anchor="w", width=160, stretch=False) - v_scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) - h_scrollbar = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) - self.tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set) + v_scrollbar = ttk.Scrollbar( + tree_frame, orient="vertical", command=self.tree.yview) + h_scrollbar = ttk.Scrollbar( + tree_frame, orient="horizontal", command=self.tree.xview) + self.tree.configure(yscrollcommand=v_scrollbar.set, + xscrollcommand=h_scrollbar.set) self.tree.grid(row=0, column=0, sticky='nsew') v_scrollbar.grid(row=0, column=1, sticky='ns') @@ -687,10 +697,11 @@ class CustomFileDialog(tk.Toplevel): def _load_more_items_list_view(self, item_to_rename=None, item_to_select=None): start_index = self.currently_loaded_count - end_index = min(len(self.all_items), start_index + self.items_to_load_per_batch) + end_index = min(len(self.all_items), start_index + + self.items_to_load_per_batch) if start_index >= end_index: - return # All items loaded + return # All items loaded for i in range(start_index, end_index): name = self.all_items[i] @@ -702,12 +713,16 @@ class CustomFileDialog(tk.Toplevel): continue try: stat = os.stat(path) - modified_time = datetime.fromtimestamp(stat.st_mtime).strftime('%d.%m.%Y %H:%M') + modified_time = datetime.fromtimestamp( + stat.st_mtime).strftime('%d.%m.%Y %H:%M') if is_dir: - icon, file_type, size = self.icon_manager.get_icon('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) - item_id = self.tree.insert("", "end", text=f" {name}", image=icon, values=(size, file_type, modified_time)) + icon, file_type, size = self.get_file_icon( + name, 'small'), "Datei", self._format_size(stat.st_size) + item_id = self.tree.insert("", "end", text=f" {name}", image=icon, values=( + size, file_type, modified_time)) if name == item_to_rename: self.tree.selection_set(item_id) self.tree.focus(item_id) @@ -719,7 +734,7 @@ class CustomFileDialog(tk.Toplevel): self.tree.see(item_id) except (FileNotFoundError, PermissionError): continue - + self.currently_loaded_count = end_index def on_item_select(self, path, item_frame): @@ -735,10 +750,12 @@ class CustomFileDialog(tk.Toplevel): self.selected_item_frame = item_frame self.selected_file = path self.update_status_bar() - self.bind("", lambda e, p=path, f=item_frame: self.on_rename_request(e, p, f)) + self.bind("", lambda e, p=path, + f=item_frame: self.on_rename_request(e, p, f)) if self.dialog_mode == "save" and not os.path.isdir(path): self.widget_manager.filename_entry.delete(0, tk.END) - self.widget_manager.filename_entry.insert(0, os.path.basename(path)) + self.widget_manager.filename_entry.insert( + 0, os.path.basename(path)) def on_list_select(self, event): if not self.tree.selection(): @@ -773,8 +790,6 @@ class CustomFileDialog(tk.Toplevel): if item_path and item_frame: self.start_rename(item_frame, item_path) - - def on_item_double_click(self, path): if os.path.isdir(path): self.navigate_to(path) @@ -783,7 +798,8 @@ class CustomFileDialog(tk.Toplevel): self.destroy() elif self.dialog_mode == "save": self.widget_manager.filename_entry.delete(0, tk.END) - self.widget_manager.filename_entry.insert(0, os.path.basename(path)) + self.widget_manager.filename_entry.insert( + 0, os.path.basename(path)) self.on_save() def on_list_double_click(self, event): @@ -930,7 +946,8 @@ class CustomFileDialog(tk.Toplevel): def _copy_to_clipboard(self, data): self.clipboard_clear() self.clipboard_append(data) - self.widget_manager.status_bar.config(text=f"'{self.shorten_text(data, 50)}' in Zwischenablage kopiert.") + self.widget_manager.status_bar.config( + text=f"'{self.shorten_text(data, 50)}' in Zwischenablage kopiert.") def _show_context_menu(self, event, item_path): if not item_path: @@ -940,14 +957,18 @@ class CustomFileDialog(tk.Toplevel): if hasattr(self, 'context_menu') and self.context_menu.winfo_exists(): self.context_menu.destroy() - self.context_menu = tk.Menu(self, tearoff=0, background=self.style_manager.header, foreground=self.style_manager.color_foreground, activebackground=self.style_manager.selection_color, activeforeground=self.style_manager.color_foreground, relief='flat', borderwidth=0) - - self.context_menu.add_command(label="Dateiname in Zwischenablage", command=lambda: self._copy_to_clipboard(os.path.basename(item_path))) - self.context_menu.add_command(label="Pfad in Zwischenablage", command=lambda: self._copy_to_clipboard(item_path)) + self.context_menu = tk.Menu(self, tearoff=0, background=self.style_manager.header, foreground=self.style_manager.color_foreground, + activebackground=self.style_manager.selection_color, activeforeground=self.style_manager.color_foreground, relief='flat', borderwidth=0) + + self.context_menu.add_command(label="Dateiname in Zwischenablage", + command=lambda: self._copy_to_clipboard(os.path.basename(item_path))) + self.context_menu.add_command( + label="Pfad in Zwischenablage", command=lambda: self._copy_to_clipboard(item_path)) if self.search_mode: self.context_menu.add_separator() - self.context_menu.add_command(label="Speicherort öffnen", command=lambda: self._open_file_location_from_context(item_path)) + self.context_menu.add_command( + label="Speicherort öffnen", command=lambda: self._open_file_location_from_context(item_path)) self.context_menu.tk_popup(event.x_root, event.y_root) return "break" @@ -985,7 +1006,8 @@ class CustomFileDialog(tk.Toplevel): if os.path.exists(new_path): self.widget_manager.status_bar.config( text=f"'{new_name}' existiert bereits.") - self.populate_files(item_to_select=os.path.basename(item_path)) + self.populate_files( + item_to_select=os.path.basename(item_path)) return try: os.rename(item_path, new_path) diff --git a/mainwindow.py b/mainwindow.py index c7d103a..c063afb 100755 --- a/mainwindow.py +++ b/mainwindow.py @@ -33,7 +33,7 @@ class GlotzMol(tk.Tk): dialog = CustomFileDialog(self, initial_dir=os.path.expanduser("~"), filetypes=[("All Files", "*.*") - ]) + ], dialog_mode="save", title="Save File") # This is the crucial part: wait for the dialog to be closed self.wait_window(dialog) @@ -55,7 +55,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()