From cad67a0c359d7a02783cc48e16e914fd34febcd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A9sir=C3=A9=20Werner=20Menrath?= Date: Sat, 26 Jul 2025 22:24:48 +0200 Subject: [PATCH] fix formartierung --- .../custom_file_dialog.cpython-312.pyc | Bin 44474 -> 43531 bytes custom_file_dialog.py | 455 ++++++++++++------ 2 files changed, 306 insertions(+), 149 deletions(-) diff --git a/__pycache__/custom_file_dialog.cpython-312.pyc b/__pycache__/custom_file_dialog.cpython-312.pyc index 256943d0a1cb853c25847aa44f6bc6f1b69ea373..ae4c6d23b27331cb9b7c1d7c1e8d66397f322804 100644 GIT binary patch delta 8767 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 DvxnM$ delta 9635 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 WB9j<3qBX<$rS+xXi<}iFum2BFvY=@I diff --git a/custom_file_dialog.py b/custom_file_dialog.py index b6b2530..96ffbff 100644 --- a/custom_file_dialog.py +++ b/custom_file_dialog.py @@ -8,9 +8,11 @@ from datetime import datetime SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) MAX_ITEMS_TO_DISPLAY = 1000 + def get_icon_path(icon_name): return os.path.join(SCRIPT_DIR, icon_name) + def get_xdg_user_dir(dir_key, fallback_name): home = os.path.expanduser("~") fallback_path = os.path.join(home, fallback_name) @@ -33,6 +35,7 @@ def get_xdg_user_dir(dir_key, fallback_name): pass return fallback_path + class Tooltip: def __init__(self, widget, text, wraplength=250): self.widget = widget @@ -46,11 +49,15 @@ class Tooltip: def enter(self, event=None): self.schedule() def leave(self, event=None): self.unschedule(); self.hide_tooltip() - def schedule(self): self.unschedule(); self.id = self.widget.after(500, self.show_tooltip) + + def schedule(self): self.unschedule( + ); self.id = self.widget.after(500, self.show_tooltip) + def unschedule(self): id = self.id self.id = None - if id: self.widget.after_cancel(id) + if id: + self.widget.after_cancel(id) def show_tooltip(self, event=None): x, y, _, _ = self.widget.bbox("insert") @@ -66,7 +73,9 @@ class Tooltip: def hide_tooltip(self): tw = self.tooltip_window self.tooltip_window = None - if tw: tw.destroy() + if tw: + tw.destroy() + class CustomFileDialog(tk.Toplevel): def __init__(self, parent, initial_dir=None, filetypes=None): @@ -79,7 +88,8 @@ class CustomFileDialog(tk.Toplevel): self.grab_set() self.selected_file = None - self.current_dir = os.path.abspath(initial_dir) if initial_dir else os.path.expanduser("~") + self.current_dir = os.path.abspath( + initial_dir) if initial_dir else os.path.expanduser("~") self.filetypes = filetypes if filetypes else [("Alle Dateien", "*.*")] self.current_filter_pattern = self.filetypes[0][1] self.history = [] @@ -129,14 +139,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}'] - if ext == '.py': return self.icons[f'python_{size}'] - if ext == '.pdf': return self.icons[f'pdf_{size}'] - if ext in ['.tar', '.zip', '.rar', '.7z', '.gz']: return self.icons[f'archive_{size}'] - if ext in ['.mp3', '.wav', '.ogg', '.flac']: return self.icons[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'] - if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: return self.icons[f'picture_{size}'] - if ext == '.iso': return self.icons[f'iso_{size}'] + if ext == '.svg': + return self.icons[f'warning_{size}'] + if ext == '.py': + return self.icons[f'python_{size}'] + if ext == '.pdf': + return self.icons[f'pdf_{size}'] + if ext in ['.tar', '.zip', '.rar', '.7z', '.gz']: + return self.icons[f'archive_{size}'] + if ext in ['.mp3', '.wav', '.ogg', '.flac']: + return self.icons[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'] + if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: + return self.icons[f'picture_{size}'] + if ext == '.iso': + return self.icons[f'iso_{size}'] return self.icons[f'file_{size}'] def create_styles(self): @@ -154,72 +172,127 @@ class CustomFileDialog(tk.Toplevel): self.icon_bg_color = base_bg # Main background for content style.configure("Sidebar.TFrame", background=self.sidebar_color) - style.configure("Sidebar.TButton", background=self.sidebar_color, anchor="w", padding=5) - style.map("Sidebar.TButton", background=[('active', self.selection_color)]) + style.configure("Sidebar.TButton", background=self.sidebar_color, + # Reverted border settings + anchor="center", padding=(5, 5, 5, 5)) + style.map("Sidebar.TButton", background=[ + ('active', self.selection_color)]) style.configure("Content.TFrame", background=self.icon_bg_color) style.configure("Item.TFrame", background=self.icon_bg_color) - style.map('Item.TFrame', background=[('selected', self.selection_color)]) + style.map('Item.TFrame', background=[ + ('selected', self.selection_color)]) style.configure("Item.TLabel", background=self.icon_bg_color) - style.map('Item.TLabel', background=[('selected', self.selection_color)], foreground=[('selected', "black" if not is_dark else "white")]) + style.map('Item.TLabel', background=[('selected', self.selection_color)], foreground=[ + ('selected', "black" if not is_dark else "white")]) style.configure("Icon.TLabel", background=self.icon_bg_color) - style.map('Icon.TLabel', background=[('selected', self.selection_color)]) - - style.configure("Treeview.Heading", relief="raised", borderwidth=1, font=('TkDefaultFont', 10, 'bold')) - style.configure("Treeview", rowheight=28, background=self.icon_bg_color, fieldbackground=self.icon_bg_color) - style.map("Treeview", background=[('selected', self.selection_color)], foreground=[('selected', "black" if not is_dark else "white")]) + style.map('Icon.TLabel', background=[ + ('selected', self.selection_color)]) + + style.configure("Treeview.Heading", relief="raised", + borderwidth=1, font=('TkDefaultFont', 10, 'bold')) + style.configure("Treeview", rowheight=28, + background=self.icon_bg_color, fieldbackground=self.icon_bg_color) + style.map("Treeview", background=[('selected', self.selection_color)], foreground=[ + ('selected', "black" if not is_dark else "white")]) def create_widgets(self): - main_frame = ttk.Frame(self, padding="10"); main_frame.pack(fill="both", expand=True) - paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL); + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(fill="both", expand=True) + paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL) paned_window.pack(fill="both", expand=True) - sidebar_frame = ttk.Frame(paned_window, padding=5, style="Sidebar.TFrame"); paned_window.add(sidebar_frame, weight=0) + sidebar_frame = ttk.Frame(paned_window, padding=( + 10, 10, 5, 10), style="Sidebar.TFrame") + paned_window.add(sidebar_frame, weight=0) sidebar_frame.grid_rowconfigure(1, weight=1) - sidebar_nav_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame"); sidebar_nav_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10)) - self.back_button = ttk.Button(sidebar_nav_frame, text="◀", command=self.go_back, state=tk.DISABLED, width=3); self.back_button.pack(side="left", fill="x", expand=True) - self.home_button = ttk.Button(sidebar_nav_frame, text="🏠", command=lambda: self.navigate_to(os.path.expanduser("~")), width=3); self.home_button.pack(side="left", fill="x", expand=True, padx=2) - self.forward_button = ttk.Button(sidebar_nav_frame, text="▶", command=self.go_forward, state=tk.DISABLED, width=3); self.forward_button.pack(side="left", fill="x", expand=True) + sidebar_nav_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame") + sidebar_nav_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10)) + self.back_button = ttk.Button( + sidebar_nav_frame, text="◀", command=self.go_back, state=tk.DISABLED, width=3) + self.back_button.pack(side="left", fill="x", expand=True) + self.home_button = ttk.Button(sidebar_nav_frame, text="🏠", command=lambda: self.navigate_to( + os.path.expanduser("~")), width=3) + self.home_button.pack(side="left", fill="x", expand=True, padx=2) + self.forward_button = ttk.Button( + sidebar_nav_frame, text="▶", command=self.go_forward, state=tk.DISABLED, width=3) + self.forward_button.pack(side="left", fill="x", expand=True) - sidebar_buttons_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame"); sidebar_buttons_frame.grid(row=1, column=0, sticky="nsew") + sidebar_buttons_frame = ttk.Frame( + sidebar_frame, style="Sidebar.TFrame") + sidebar_buttons_frame.grid(row=1, 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("XDG_DOWNLOAD_DIR", "Downloads")}, - {'name': 'Dokumente', 'icon': self.icons['documents_small'], 'path': get_xdg_user_dir("XDG_DOCUMENTS_DIR", "Documents")}, - {'name': 'Bilder', 'icon': self.icons['pictures_small'], 'path': get_xdg_user_dir("XDG_PICTURES_DIR", "Pictures")}, - {'name': 'Musik', 'icon': self.icons['music_small'], 'path': get_xdg_user_dir("XDG_MUSIC_DIR", "Music")}, - {'name': 'Videos', 'icon': self.icons['video_small'], 'path': get_xdg_user_dir("XDG_VIDEO_DIR", "Videos")}, + {'name': 'Computer', + 'icon': self.icons['computer_large'], 'path': '/'}, + {'name': 'Downloads', 'icon': self.icons['downloads_large'], 'path': get_xdg_user_dir( + "XDG_DOWNLOAD_DIR", "Downloads")}, + {'name': 'Dokumente', 'icon': self.icons['documents_large'], 'path': get_xdg_user_dir( + "XDG_DOCUMENTS_DIR", "Documents")}, + {'name': 'Bilder', 'icon': self.icons['pictures_large'], 'path': get_xdg_user_dir( + "XDG_PICTURES_DIR", "Pictures")}, + {'name': 'Musik', 'icon': self.icons['music_large'], 'path': get_xdg_user_dir( + "XDG_MUSIC_DIR", "Music")}, + {'name': 'Videos', 'icon': self.icons['video_large_folder'], 'path': get_xdg_user_dir( + "XDG_VIDEO_DIR", "Videos")}, ] for config in sidebar_buttons_config: - btn = ttk.Button(sidebar_buttons_frame, text=f" {config['name']}", image=config['icon'], compound="left", command=lambda p=config['path']: self.navigate_to(p), style="Sidebar.TButton"); btn.pack(fill="x", pady=1) + btn = ttk.Button(sidebar_buttons_frame, text=f" {config['name']}", image=config['icon'], + compound="top", command=lambda p=config['path']: self.navigate_to(p), style="Sidebar.TButton") + btn.pack(fill="x", pady=1) - content_frame = ttk.Frame(paned_window, padding=(5, 0, 0, 0)); paned_window.add(content_frame, weight=1) - content_frame.grid_rowconfigure(2, weight=1); content_frame.grid_columnconfigure(0, weight=1) + content_frame = ttk.Frame(paned_window, padding=(10, 10, 10, 10)) + paned_window.add(content_frame, weight=1) + content_frame.grid_rowconfigure(2, weight=1) + content_frame.grid_columnconfigure(0, weight=1) - top_bar = ttk.Frame(content_frame); top_bar.grid(row=0, column=0, sticky="ew", pady=(5, 0)); top_bar.grid_columnconfigure(0, weight=1) - self.path_entry = ttk.Entry(top_bar); self.path_entry.grid(row=0, column=0, sticky="ew"); self.path_entry.bind("", lambda e: self.navigate_to(self.path_entry.get())) - self.hidden_files_button = ttk.Checkbutton(top_bar, text="Versteckte Dateien", variable=self.show_hidden_files, command=self.populate_files); self.hidden_files_button.grid(row=0, column=1, padx=5) - view_switch = ttk.Frame(top_bar, padding=(5, 0)); view_switch.grid(row=0, column=2) - ttk.Radiobutton(view_switch, text="Kacheln", variable=self.view_mode, value="icons", command=self.populate_files).pack(side="left") - ttk.Radiobutton(view_switch, text="Liste", variable=self.view_mode, value="list", command=self.populate_files).pack(side="left") - self.filter_combobox = ttk.Combobox(top_bar, values=[ft[0] for ft in self.filetypes], state="readonly", width=20); self.filter_combobox.grid(row=0, column=3, padx=5) - self.filter_combobox.bind("<>", self.on_filter_change); self.filter_combobox.set(self.filetypes[0][0]) + top_bar = ttk.Frame(content_frame) + top_bar.grid(row=0, column=0, sticky="ew", pady=(5, 0)) + top_bar.grid_columnconfigure(0, weight=1) + self.path_entry = ttk.Entry(top_bar) + self.path_entry.grid(row=0, column=0, sticky="ew") + self.path_entry.bind( + "", lambda e: self.navigate_to(self.path_entry.get())) + self.hidden_files_button = ttk.Checkbutton( + top_bar, text="Versteckte Dateien", variable=self.show_hidden_files, command=self.populate_files) + self.hidden_files_button.grid(row=0, column=1, padx=5) + view_switch = ttk.Frame(top_bar, padding=(5, 0)) + view_switch.grid(row=0, column=2) + ttk.Radiobutton(view_switch, text="Kacheln", variable=self.view_mode, + value="icons", command=self.populate_files).pack(side="left") + ttk.Radiobutton(view_switch, text="Liste", variable=self.view_mode, + value="list", command=self.populate_files).pack(side="left") + self.filter_combobox = ttk.Combobox( + top_bar, values=[ft[0] for ft in self.filetypes], state="readonly", width=20) + self.filter_combobox.grid(row=0, column=3, padx=5) + self.filter_combobox.bind( + "<>", self.on_filter_change) + self.filter_combobox.set(self.filetypes[0][0]) - ttk.Separator(content_frame, orient='horizontal').grid(row=1, column=0, sticky="ew", pady=5) + ttk.Separator(content_frame, orient='horizontal').grid( + row=1, column=0, sticky="ew", pady=5) - self.file_list_frame = ttk.Frame(content_frame, style="Content.TFrame"); self.file_list_frame.grid(row=2, column=0, sticky="nsew"); self.bind("", self.on_window_resize) + self.file_list_frame = ttk.Frame(content_frame, style="Content.TFrame") + self.file_list_frame.grid(row=2, column=0, sticky="nsew") + self.bind("", self.on_window_resize) - bottom_frame = ttk.Frame(content_frame); bottom_frame.grid(row=3, column=0, sticky="ew", pady=(5, 0)); bottom_frame.grid_columnconfigure(0, weight=1) - self.status_bar = ttk.Label(bottom_frame, text="", anchor="w"); self.status_bar.grid(row=0, column=0, sticky="ew") - action_buttons_frame = ttk.Frame(bottom_frame); action_buttons_frame.grid(row=0, column=1) - ttk.Button(action_buttons_frame, text="Öffnen", command=self.on_open).pack(side="right") - ttk.Button(action_buttons_frame, text="Abbrechen", command=self.on_cancel).pack(side="right", padx=5) + bottom_frame = ttk.Frame(content_frame) + bottom_frame.grid(row=3, column=0, sticky="ew", pady=(5, 0)) + bottom_frame.grid_columnconfigure(0, weight=1) + self.status_bar = ttk.Label(bottom_frame, text="", anchor="w") + self.status_bar.grid(row=0, column=0, sticky="ew") + action_buttons_frame = ttk.Frame(bottom_frame) + action_buttons_frame.grid(row=0, column=1) + ttk.Button(action_buttons_frame, text="Öffnen", + command=self.on_open).pack(side="right") + ttk.Button(action_buttons_frame, text="Abbrechen", + command=self.on_cancel).pack(side="right", padx=5) def on_window_resize(self, event): new_width = self.file_list_frame.winfo_width() if self.view_mode.get() == "icons" and abs(new_width - self.last_width) > 50: - if self.resize_job: self.after_cancel(self.resize_job) + if self.resize_job: + self.after_cancel(self.resize_job) self.resize_job = self.after(200, self.populate_files) self.last_width = new_width @@ -233,13 +306,18 @@ class CustomFileDialog(tk.Toplevel): # Unbind previous global mouse wheel events self._unbind_mouse_wheel_events() - for widget in self.file_list_frame.winfo_children(): widget.destroy() - self.path_entry.delete(0, tk.END); self.path_entry.insert(0, self.current_dir) + for widget in self.file_list_frame.winfo_children(): + widget.destroy() + self.path_entry.delete(0, tk.END) + self.path_entry.insert(0, self.current_dir) self.selected_file = None current_status = self.status_bar.cget("text") - if not current_status.startswith("Zeige"): self.update_status_bar() - if self.view_mode.get() == "list": self.populate_list_view() - else: self.populate_icon_view() + if not current_status.startswith("Zeige"): + self.update_status_bar() + if self.view_mode.get() == "list": + self.populate_list_view() + else: + self.populate_icon_view() def _get_sorted_items(self): try: @@ -249,106 +327,146 @@ class CustomFileDialog(tk.Toplevel): if num_items > MAX_ITEMS_TO_DISPLAY: warning_message = f"Zeige {MAX_ITEMS_TO_DISPLAY} von {num_items} Einträgen." items = items[:MAX_ITEMS_TO_DISPLAY] - dirs = sorted([d for d in items if os.path.isdir(os.path.join(self.current_dir, d))], key=str.lower) - files = sorted([f for f in items if not os.path.isdir(os.path.join(self.current_dir, f))], key=str.lower) + dirs = sorted([d for d in items if os.path.isdir( + os.path.join(self.current_dir, d))], key=str.lower) + files = sorted([f for f in items if not os.path.isdir( + os.path.join(self.current_dir, f))], key=str.lower) return (dirs + files, None, warning_message) - except PermissionError: return ([], "Zugriff verweigert.", None) - except FileNotFoundError: return ([], "Verzeichnis nicht gefunden.", None) + except PermissionError: + return ([], "Zugriff verweigert.", None) + except FileNotFoundError: + return ([], "Verzeichnis nicht gefunden.", None) def populate_icon_view(self): - canvas = tk.Canvas(self.file_list_frame, highlightthickness=0, bg=self.icon_bg_color) - v_scrollbar = ttk.Scrollbar(self.file_list_frame, orient="vertical", command=canvas.yview) - canvas.pack(side="left", fill="both", expand=True); v_scrollbar.pack(side="right", fill="y") + canvas = tk.Canvas(self.file_list_frame, + highlightthickness=0, bg=self.icon_bg_color) + v_scrollbar = ttk.Scrollbar( + self.file_list_frame, orient="vertical", command=canvas.yview) + canvas.pack(side="left", fill="both", expand=True) + v_scrollbar.pack(side="right", fill="y") container_frame = ttk.Frame(canvas, style="Content.TFrame") canvas.create_window((0, 0), window=container_frame, anchor="nw") - container_frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) + container_frame.bind("", lambda e: canvas.configure( + scrollregion=canvas.bbox("all"))) def _on_mouse_wheel(event): delta = -1 if event.num == 4 else 1 canvas.yview_scroll(delta, "units") - - # Bind mouse wheel events specifically to the canvas + canvas.bind_all("", _on_mouse_wheel) canvas.bind_all("", _on_mouse_wheel) canvas.bind_all("", _on_mouse_wheel) items, error, warning = self._get_sorted_items() - if warning: self.status_bar.config(text=warning) - if error: ttk.Label(container_frame, text=error).pack(pady=20); return + if warning: + self.status_bar.config(text=warning) + if error: + ttk.Label(container_frame, text=error).pack(pady=20) + return item_width, item_height = 110, 100 frame_width = self.file_list_frame.winfo_width() col_count = max(1, frame_width // item_width) row, col = 0, 0 for name in items: - if not self.show_hidden_files.get() and name.startswith('.'): continue + if not self.show_hidden_files.get() and name.startswith('.'): + continue path = os.path.join(self.current_dir, name) is_dir = os.path.isdir(path) - if not is_dir and not self._matches_filetype(name): continue + if not is_dir and not self._matches_filetype(name): + continue - item_frame = ttk.Frame(container_frame, width=item_width, height=item_height, style="Item.TFrame") - item_frame.grid(row=row, column=col, padx=5, pady=5); item_frame.grid_propagate(False) - icon = self.icons['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.pack(fill="x", expand=True) + item_frame = ttk.Frame( + container_frame, width=item_width, height=item_height, style="Item.TFrame") + item_frame.grid(row=row, column=col, padx=5, pady=5) + item_frame.grid_propagate(False) + icon = self.icons['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.pack(fill="x", expand=True) Tooltip(item_frame, name) 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.on_item_double_click(p)) + widget.bind("", lambda e, p=path, + f=item_frame: self.on_item_select(p, f)) col = (col + 1) % col_count - if col == 0: row += 1 + if col == 0: + row += 1 def populate_list_view(self): - tree_frame = ttk.Frame(self.file_list_frame); tree_frame.pack(fill='both', expand=True) - columns = ("name", "size", "type", "modified"); self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings") - self.tree.heading("name", text="Name", anchor="w"); self.tree.column("name", anchor="w", width=300, stretch=True) - self.tree.heading("size", text="Größe", anchor="e"); self.tree.column("size", anchor="e", width=120, stretch=False) - self.tree.heading("type", text="Typ", anchor="w"); self.tree.column("type", anchor="w", width=120, stretch=False) - 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) - self.tree.pack(side="left", fill="both", expand=True); v_scrollbar.pack(side="right", fill="y"); h_scrollbar.pack(side="bottom", fill="x") - self.tree.bind("", self.on_list_double_click); self.tree.bind("<>", self.on_list_select) - - def _on_mouse_wheel_treeview(event): - delta = -1 if event.num == 4 else 1 - self.tree.yview_scroll(delta, "units") - - # Bind mouse wheel events specifically to the treeview - self.tree.bind_all("", _on_mouse_wheel_treeview) - self.tree.bind_all("", _on_mouse_wheel_treeview) - self.tree.bind_all("", _on_mouse_wheel_treeview) + tree_frame = ttk.Frame(self.file_list_frame) + tree_frame.pack(fill='both', expand=True) + columns = ("name", "size", "type", "modified") + self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings") + self.tree.heading("name", text="Name", anchor="w") + self.tree.column("name", anchor="w", width=300, stretch=True) + self.tree.heading("size", text="Größe", anchor="e") + self.tree.column("size", anchor="e", width=120, stretch=False) + self.tree.heading("type", text="Typ", anchor="w") + self.tree.column("type", anchor="w", width=120, stretch=False) + 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) + self.tree.pack(side="left", fill="both", expand=True) + v_scrollbar.pack(side="right", fill="y") + h_scrollbar.pack(side="bottom", fill="x") + self.tree.bind("", self.on_list_double_click) + self.tree.bind("<>", self.on_list_select) items, error, warning = self._get_sorted_items() - if warning: self.status_bar.config(text=warning) - if error: self.tree.insert("", "end", values=(error,)); return + if warning: + self.status_bar.config(text=warning) + if error: + self.tree.insert("", "end", values=(error,)) + return for name in items: - if not self.show_hidden_files.get() and name.startswith('.'): continue - path = os.path.join(self.current_dir, name); is_dir = os.path.isdir(path) - if not is_dir and not self._matches_filetype(name): continue + if not self.show_hidden_files.get() and name.startswith('.'): + continue + path = os.path.join(self.current_dir, name) + is_dir = os.path.isdir(path) + if not is_dir and not self._matches_filetype(name): + continue try: - stat = os.stat(path); modified_time = datetime.fromtimestamp(stat.st_mtime).strftime('%d.%m.%Y %H:%M') + stat = os.stat(path) + 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", "" else: - icon, file_type, size = self.get_file_icon(name, 'small'), "Datei", self._format_size(stat.st_size) - self.tree.insert("", "end", text=name, image=icon, values=(name, size, file_type, modified_time)) - except (FileNotFoundError, PermissionError): continue + icon, file_type, size = self.get_file_icon( + name, 'small'), "Datei", self._format_size(stat.st_size) + self.tree.insert("", "end", text=name, image=icon, values=( + name, size, file_type, modified_time)) + except (FileNotFoundError, PermissionError): + continue def on_item_select(self, path, item_frame): if hasattr(self, 'selected_item_frame') and self.selected_item_frame.winfo_exists(): self.selected_item_frame.state(['!selected']) for child in self.selected_item_frame.winfo_children(): - if isinstance(child, ttk.Label): child.state(['!selected']) - item_frame.state(['selected']); + if isinstance(child, ttk.Label): + child.state(['!selected']) + item_frame.state(['selected']) for child in item_frame.winfo_children(): - if isinstance(child, ttk.Label): child.state(['selected']) - self.selected_item_frame = item_frame; self.selected_file = path + if isinstance(child, ttk.Label): + child.state(['selected']) + self.selected_item_frame = item_frame + self.selected_file = path self.update_status_bar() def on_list_select(self, event): - if not self.tree.selection(): return + if not self.tree.selection(): + return item_id = self.tree.selection()[0] item_text = self.tree.item(item_id, 'values')[0] self.selected_file = os.path.join(self.current_dir, item_text) @@ -356,93 +474,132 @@ class CustomFileDialog(tk.Toplevel): def _handle_unsupported_file(self, path): if path.lower().endswith('.svg'): - self.status_bar.config(text="SVG-Dateien werden nicht unterstützt.") + self.status_bar.config( + text="SVG-Dateien werden nicht unterstützt.") return True return False def on_item_double_click(self, path): - if self._handle_unsupported_file(path): return - if os.path.isdir(path): self.navigate_to(path) - else: self.selected_file = path; self.destroy() + if self._handle_unsupported_file(path): + return + if os.path.isdir(path): + self.navigate_to(path) + else: + self.selected_file = path + self.destroy() def on_list_double_click(self, event): - if not self.tree.selection(): return + if not self.tree.selection(): + return item_id = self.tree.selection()[0] item_text = self.tree.item(item_id, 'values')[0] path = os.path.join(self.current_dir, item_text) - if self._handle_unsupported_file(path): return - if os.path.isdir(path): self.navigate_to(path) - else: self.selected_file = path; self.destroy() + if self._handle_unsupported_file(path): + return + if os.path.isdir(path): + self.navigate_to(path) + else: + self.selected_file = path + self.destroy() def on_filter_change(self, event): selected_desc = self.filter_combobox.get() for desc, pattern in self.filetypes: - if desc == selected_desc: self.current_filter_pattern = pattern; break + if desc == selected_desc: + self.current_filter_pattern = pattern + break self.populate_files() def navigate_to(self, path): try: - real_path = os.path.realpath(os.path.abspath(os.path.expanduser(path))) + real_path = os.path.realpath( + os.path.abspath(os.path.expanduser(path))) if not os.path.isdir(real_path): - self.status_bar.config(text=f"Fehler: Verzeichnis '{os.path.basename(path)}' nicht gefunden."); return + self.status_bar.config( + text=f"Fehler: Verzeichnis '{os.path.basename(path)}' nicht gefunden.") + return if not os.access(real_path, os.R_OK): - self.status_bar.config(text=f"Zugriff auf '{os.path.basename(path)}' verweigert."); return + self.status_bar.config( + text=f"Zugriff auf '{os.path.basename(path)}' verweigert.") + return self.current_dir = real_path - if self.history_pos < len(self.history) - 1: self.history = self.history[:self.history_pos + 1] + if self.history_pos < len(self.history) - 1: + self.history = self.history[:self.history_pos + 1] if not self.history or self.history[-1] != self.current_dir: - self.history.append(self.current_dir); self.history_pos = len(self.history) - 1 - self.populate_files(); self.update_nav_buttons() - except Exception as e: self.status_bar.config(text=f"Fehler: {e}") + self.history.append(self.current_dir) + self.history_pos = len(self.history) - 1 + self.populate_files() + self.update_nav_buttons() + except Exception as e: + self.status_bar.config(text=f"Fehler: {e}") def go_back(self): if self.history_pos > 0: - self.history_pos -= 1; self.current_dir = self.history[self.history_pos] - self.populate_files(); self.update_nav_buttons() + self.history_pos -= 1 + self.current_dir = self.history[self.history_pos] + self.populate_files() + self.update_nav_buttons() def go_forward(self): if self.history_pos < len(self.history) - 1: - self.history_pos += 1; self.current_dir = self.history[self.history_pos] - self.populate_files(); self.update_nav_buttons() + self.history_pos += 1 + self.current_dir = self.history[self.history_pos] + self.populate_files() + self.update_nav_buttons() def update_nav_buttons(self): - self.back_button.config(state=tk.NORMAL if self.history_pos > 0 else tk.DISABLED) - self.forward_button.config(state=tk.NORMAL if self.history_pos < len(self.history) - 1 else tk.DISABLED) + self.back_button.config( + state=tk.NORMAL if self.history_pos > 0 else tk.DISABLED) + self.forward_button.config(state=tk.NORMAL if self.history_pos < len( + self.history) - 1 else tk.DISABLED) def update_status_bar(self): try: - _, _, free = shutil.disk_usage(self.current_dir); free_str = self._format_size(free) + _, _, free = shutil.disk_usage(self.current_dir) + free_str = self._format_size(free) status_text = f"Freier Speicher: {free_str}" if self.selected_file and os.path.exists(self.selected_file) and not os.path.isdir(self.selected_file): - size = os.path.getsize(self.selected_file); size_str = self._format_size(size) + size = os.path.getsize(self.selected_file) + size_str = self._format_size(size) status_text += f" | '{os.path.basename(self.selected_file)}' Größe: {size_str}" self.status_bar.config(text=status_text) - except FileNotFoundError: self.status_bar.config(text="Verzeichnis nicht gefunden") + except FileNotFoundError: + self.status_bar.config(text="Verzeichnis nicht gefunden") def on_open(self): if self.selected_file and os.path.isfile(self.selected_file): - if self._handle_unsupported_file(self.selected_file): return + if self._handle_unsupported_file(self.selected_file): + return self.destroy() def on_cancel(self): - self.selected_file = None; self.destroy() + self.selected_file = None + self.destroy() def get_selected_file(self): return self.selected_file def _matches_filetype(self, filename): - if self.current_filter_pattern == "*.*": return True - patterns = self.current_filter_pattern.replace("*.", "").lower().split() + if self.current_filter_pattern == "*.*": + return True + patterns = self.current_filter_pattern.replace( + "*.", "").lower().split() fn_lower = filename.lower() for p in patterns: - if fn_lower.endswith(p): return True + if fn_lower.endswith(p): + return True return False def _format_size(self, size_bytes): - if size_bytes is None: return "" - if size_bytes < 1024: return f"{size_bytes} B" - if size_bytes < 1024**2: return f"{size_bytes/1024:.1f} KB" - if size_bytes < 1024**3: return f"{size_bytes/1024**2:.1f} MB" + if size_bytes is None: + return "" + if size_bytes < 1024: + return f"{size_bytes} B" + if size_bytes < 1024**2: + return f"{size_bytes/1024:.1f} KB" + if size_bytes < 1024**3: + return f"{size_bytes/1024**2:.1f} MB" return f"{size_bytes/1024**3:.1f} GB" def shorten_text(self, text, max_len): - return text if len(text) <= max_len else text[:max_len-3] + "..." \ No newline at end of file + return text if len(text) <= max_len else text[:max_len-3] + "..."