diff --git a/__pycache__/custom_file_dialog.cpython-312.pyc b/__pycache__/custom_file_dialog.cpython-312.pyc index 1491f4a..50f3c02 100644 Binary files a/__pycache__/custom_file_dialog.cpython-312.pyc and b/__pycache__/custom_file_dialog.cpython-312.pyc differ diff --git a/custom_file_dialog.py b/custom_file_dialog.py index a74aeaf..5cbabfe 100644 --- a/custom_file_dialog.py +++ b/custom_file_dialog.py @@ -6,15 +6,12 @@ from datetime import datetime # 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 def get_icon_path(icon_name): return os.path.join(SCRIPT_DIR, icon_name) def get_xdg_user_dir(dir_key, fallback_name): - """ - Ermittelt den Pfad eines Benutzerverzeichnisses aus der XDG-Konfigurationsdatei. - Kann mit absoluten Pfaden und Pfaden relativ zum Home-Verzeichnis umgehen. - """ home = os.path.expanduser("~") fallback_path = os.path.join(home, fallback_name) config_path = os.path.join(home, ".config", "user-dirs.dirs") @@ -29,13 +26,11 @@ def get_xdg_user_dir(dir_key, fallback_name): if line.startswith(f"{dir_key}="): path = line.split('=', 1)[1].strip().strip('"') path = path.replace('$HOME', home) - # Handle paths relative to home dir, e.g., "Music" vs "/home/user/Music" if not os.path.isabs(path): path = os.path.join(home, path) return path except Exception: - pass # Fallback on any error - + pass return fallback_path class Tooltip: @@ -51,11 +46,7 @@ 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 @@ -144,8 +135,7 @@ class CustomFileDialog(tk.Toplevel): def create_styles(self): style = ttk.Style(self) - self.selection_color = "#D5E5F5" - self.selection_fg_color = "black" + self.selection_color = "#D5E5F5"; self.selection_fg_color = "black" self.icon_bg_color = style.lookup('TFrame', 'background') style.configure("Item.TFrame", background=self.icon_bg_color) style.map('Item.TFrame', background=[('selected', self.selection_color)]) @@ -160,26 +150,18 @@ class CustomFileDialog(tk.Toplevel): ttk.Style().configure("Sidebar.TButton", anchor="w", padding=5) 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) - paned_window.pack(fill="both", expand=True) + 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) - paned_window.add(sidebar_frame, weight=0) + sidebar_frame = ttk.Frame(paned_window, padding=5); paned_window.add(sidebar_frame, weight=0) sidebar_frame.grid_rowconfigure(1, weight=1) - sidebar_nav_frame = ttk.Frame(sidebar_frame) - 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); 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) - sidebar_buttons_frame.grid(row=1, column=0, sticky="nsew") + sidebar_buttons_frame = ttk.Frame(sidebar_frame); 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")}, @@ -189,46 +171,25 @@ class CustomFileDialog(tk.Toplevel): {'name': 'Videos', 'icon': self.icons['video_small'], '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="left", 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(1, weight=1) - content_frame.grid_columnconfigure(0, weight=1) + content_frame = ttk.Frame(paned_window, padding=(5, 0, 0, 0)); paned_window.add(content_frame, weight=1) + content_frame.grid_rowconfigure(1, 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, 5)) - 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) + top_bar = ttk.Frame(content_frame); top_bar.grid(row=0, column=0, sticky="ew", pady=(5, 5)); 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]) + 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]) - self.file_list_frame = ttk.Frame(content_frame, style="Content.TFrame") - self.file_list_frame.grid(row=1, 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=1, column=0, sticky="nsew"); self.bind("", self.on_window_resize) - bottom_frame = ttk.Frame(content_frame) - bottom_frame.grid(row=2, 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) + bottom_frame = ttk.Frame(content_frame); bottom_frame.grid(row=2, 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) @@ -241,37 +202,48 @@ class CustomFileDialog(tk.Toplevel): def populate_files(self): 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.path_entry.delete(0, tk.END); self.path_entry.insert(0, self.current_dir) self.selected_file = None - self.update_status_bar() + # Clear status bar before populating, but preserve important warnings + 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() def _get_sorted_items(self): try: items = os.listdir(self.current_dir) + num_items = len(items) + warning_message = None + 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) - return (dirs + files, None) - except PermissionError: - return ([], "Zugriff verweigert.") - except FileNotFoundError: - return ([], "Verzeichnis nicht gefunden.") + return (dirs + files, None, warning_message) + 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.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"))) - items, error = self._get_sorted_items() + def _on_mouse_wheel(event): + if event.num == 4 or event.delta > 0: canvas.yview_scroll(-1, "units") + elif event.num == 5 or event.delta < 0: canvas.yview_scroll(1, "units") + + for widget in [canvas, container_frame]: + widget.bind("", _on_mouse_wheel); widget.bind("", _on_mouse_wheel); widget.bind("", _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 - item_width, item_height = 120, 100 + item_width, item_height = 115, 100 frame_width = self.file_list_frame.winfo_width() col_count = max(1, frame_width // item_width) row, col = 0, 0 @@ -282,47 +254,40 @@ class CustomFileDialog(tk.Toplevel): 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) + 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, 15), anchor="center", style="Item.TLabel") - name_label.pack(fill="x", expand=True) + 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, 15), 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("", _on_mouse_wheel); widget.bind("", _on_mouse_wheel); widget.bind("", _on_mouse_wheel) col = (col + 1) % col_count 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") + 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) + 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.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 = self._get_sorted_items() - if error: self.tree.insert("", "end", text=error); return + items, error, warning = self._get_sorted_items() + 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) + 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: @@ -335,11 +300,10 @@ class CustomFileDialog(tk.Toplevel): 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']) + 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 + self.selected_item_frame = item_frame; self.selected_file = path self.update_status_bar() def on_list_select(self, event): @@ -357,11 +321,8 @@ class CustomFileDialog(tk.Toplevel): 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 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 @@ -369,54 +330,38 @@ class CustomFileDialog(tk.Toplevel): 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 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))) 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) @@ -424,16 +369,13 @@ class CustomFileDialog(tk.Toplevel): 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): @@ -441,21 +383,17 @@ class CustomFileDialog(tk.Toplevel): 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.split() - for pattern in patterns: - # Handles patterns like "*.txt" - p = pattern.lower().replace("*.", "") - if filename.lower().endswith(p): - return True + 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 return False def _format_size(self, size_bytes): @@ -466,4 +404,4 @@ class CustomFileDialog(tk.Toplevel): 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] + "..." diff --git a/mainwindow.py b/mainwindow.py index 6f115d3..3d809be 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -29,16 +29,14 @@ class GlotzMol(tk.Tk): container.columnconfigure(0, weight=1) def open_custom_dialog(self): - # Initial directory can be anywhere, let's test with /backup - initial_directory = "/backup" if os.path.exists( - "/backup") else os.path.expanduser("~") dialog = CustomFileDialog(self, - initial_dir=initial_directory, - filetypes=[("Audio-Dateien", "*.mp3 *.wav"), + initial_dir=os.path.expanduser("~"), + filetypes=[("Alle Dateien", "*.*"), + ("Audio-Dateien", "*.mp3 *.wav"), ("Video-Dateien", "*.mkv *.mp4"), ("ISO-Images", "*.iso"), - ("Alle Dateien", "*.*")]) + ]) # This is the crucial part: wait for the dialog to be closed self.wait_window(dialog)