diff --git a/__pycache__/custom_file_dialog.cpython-312.pyc b/__pycache__/custom_file_dialog.cpython-312.pyc index 60448b1..1491f4a 100644 Binary files a/__pycache__/custom_file_dialog.cpython-312.pyc and b/__pycache__/custom_file_dialog.cpython-312.pyc differ diff --git a/computer-32.png b/computer-32.png new file mode 100644 index 0000000..4fa0929 Binary files /dev/null and b/computer-32.png differ diff --git a/computer-64.png b/computer-64.png new file mode 100644 index 0000000..f15e0e6 Binary files /dev/null and b/computer-64.png differ diff --git a/custom_file_dialog.py b/custom_file_dialog.py index b609065..a74aeaf 100644 --- a/custom_file_dialog.py +++ b/custom_file_dialog.py @@ -4,6 +4,39 @@ import tkinter as tk from tkinter import ttk 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__)) + +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") + + if not os.path.exists(config_path): + return fallback_path + + try: + with open(config_path, 'r') as f: + for line in f: + line = line.strip() + 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 + + return fallback_path class Tooltip: def __init__(self, widget, text, wraplength=250): @@ -19,14 +52,14 @@ 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") @@ -35,23 +68,14 @@ class Tooltip: self.tooltip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") - style = ttk.Style() - try: - bg = style.lookup("Tooltip", "background", default="#FFFFE0") - fg = style.lookup("Tooltip", "foreground", default="black") - except tk.TclError: - bg = "#FFFFE0" - fg = "black" - label = ttk.Label(tw, text=self.text, justify=tk.LEFT, background=bg, foreground=fg, + 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() - + if tw: tw.destroy() class CustomFileDialog(tk.Toplevel): def __init__(self, parent, initial_dir=None, filetypes=None): @@ -64,12 +88,11 @@ 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 = [self.current_dir] - self.history_pos = 0 + self.history = [] + self.history_pos = -1 self.view_mode = tk.StringVar(value="icons") self.show_hidden_files = tk.BooleanVar(value=False) self.resize_job = None @@ -78,124 +101,63 @@ class CustomFileDialog(tk.Toplevel): self.load_icons() self.create_styles() self.create_widgets() - self.update_status_bar() - self.after(50, self.populate_files) + self.navigate_to(self.current_dir) def load_icons(self): self.icons = {} - try: - # Sidebar Icons - self.icons['downloads_small'] = tk.PhotoImage( - file="./folder-water-download-32.png") - self.icons['documents_small'] = tk.PhotoImage( - file="./folder-water-documents-32.png") - self.icons['pictures_small'] = tk.PhotoImage( - file="./folder-water-pictures-32.png") - self.icons['music_small'] = tk.PhotoImage( - file="./folder-water-music-32.png") - self.icons['video_small'] = tk.PhotoImage( - file="./folder-water-video-32.png") - - # File Icons (Large) - self.icons['folder_large'] = tk.PhotoImage( - file="./folder-water-64.png") - self.icons['file_large'] = tk.PhotoImage(file="./document-64.png") - self.icons['python_large'] = tk.PhotoImage( - file="./file-python-64.png") - self.icons['pdf_large'] = tk.PhotoImage(file="./pdf-64.png") - self.icons['archive_large'] = tk.PhotoImage(file="./tar-64.png") - self.icons['audio_large'] = tk.PhotoImage(file="./audio-64.png") - self.icons['video_large'] = tk.PhotoImage(file="./video-64.png") - self.icons['picture_large'] = tk.PhotoImage( - file="./picture-64.png") - self.icons['iso_large'] = tk.PhotoImage( - file="./media-optical-64.png") - - # File Icons (Small) - self.icons['folder_small'] = tk.PhotoImage( - file="./folder-water-32.png") - self.icons['file_small'] = tk.PhotoImage(file="./document-32.png") - self.icons['python_small'] = tk.PhotoImage( - file="./file-python-32.png") - self.icons['pdf_small'] = tk.PhotoImage(file="./pdf-32.png") - self.icons['archive_small'] = tk.PhotoImage(file="./tar-32.png") - self.icons['audio_small'] = tk.PhotoImage(file="./audio-32.png") - self.icons['video_small_file'] = tk.PhotoImage( - file="./video-32.png") - self.icons['picture_small'] = tk.PhotoImage( - file="./picture-32.png") - self.icons['iso_small'] = tk.PhotoImage( - file="./media-optical-32.png") - - except tk.TclError: - # Fallback if icons are missing - for key in ['downloads_small', 'documents_small', 'pictures_small', 'music_small', 'video_small', - 'folder_large', 'file_large', 'python_large', 'pdf_large', 'archive_large', 'audio_large', 'video_large', 'picture_large', 'iso_large', - 'folder_small', 'file_small', 'python_small', 'pdf_small', 'archive_small', 'audio_small', 'video_small_file', 'picture_small', 'iso_small']: - self.icons[key] = tk.PhotoImage( - width=32, height=32) if 'small' in key else tk.PhotoImage(width=64, height=64) + icon_files = { + 'computer_small': 'computer-32.png', + 'downloads_small': 'folder-water-download-32.png', + 'documents_small': 'folder-water-documents-32.png', + 'pictures_small': 'folder-water-pictures-32.png', + 'music_small': 'folder-water-music-32.png', + 'video_small': 'folder-water-video-32.png', + 'warning_small': 'warning.png', 'warning_large': 'warning.png', + 'folder_large': 'folder-water-64.png', 'file_large': 'document-64.png', + 'python_large': 'file-python-64.png', 'pdf_large': 'pdf-64.png', + 'archive_large': 'tar-64.png', 'audio_large': 'audio-64.png', + 'video_large': 'video-64.png', 'picture_large': 'picture-64.png', + 'iso_large': 'media-optical-64.png', 'folder_small': 'folder-water-32.png', + 'file_small': 'document-32.png', 'python_small': 'file-python-32.png', + 'pdf_small': 'pdf-32.png', 'archive_small': 'tar-32.png', + 'audio_small': 'audio-32.png', 'video_small_file': 'video-32.png', + 'picture_small': 'picture-32.png', 'iso_small': 'media-optical-32.png' + } + for key, filename in icon_files.items(): + try: + self.icons[key] = tk.PhotoImage(file=get_icon_path(filename)) + except tk.TclError: + size = 32 if 'small' in key else 64 + self.icons[key] = tk.PhotoImage(width=size, height=size) def get_file_icon(self, filename, size='large'): ext = os.path.splitext(filename)[1].lower() - if ext == '.py': - return self.icons[f'python_{size}'] - elif ext == '.pdf': - return self.icons[f'pdf_{size}'] - elif ext in ['.tar', '.zip', '.rar', '.7z']: - return self.icons[f'archive_{size}'] - elif ext in ['.mp3', '.wav', '.ogg', '.flac']: - return self.icons[f'audio_{size}'] - elif ext in ['.mp4', '.mkv', '.avi', '.mov']: - return self.icons[f'video_{size}'] if size == 'large' else self.icons['video_small_file'] - elif ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']: - return self.icons[f'picture_{size}'] - elif ext == '.iso': - return self.icons[f'iso_{size}'] - else: - return self.icons[f'file_{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): style = ttk.Style(self) - - # Check for dark theme - try: - theme_bg = style.lookup('TFrame', 'background') - is_dark = sum(self.winfo_rgb(theme_bg)) / 3 < 32768 - except tk.TclError: - is_dark = False - - # --- Theme-Aware Colors --- - if is_dark: - self.selection_color = "#494949" - self.selection_fg_color = "white" - self.icon_bg_color = style.lookup('TFrame', 'background') - else: - self.selection_color = "#D5E5F5" - self.selection_fg_color = "black" - self.icon_bg_color = style.lookup('TFrame', 'background') - - # --- Style Definitions --- + 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)]) - + 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', self.selection_fg_color)]) - - # Style for the icon label itself + style.map('Item.TLabel', background=[('selected', self.selection_color)], foreground=[('selected', self.selection_fg_color)]) 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.Heading", relief="raised", borderwidth=1, font=('TkDefaultFont', 10, 'bold')) style.configure("Treeview", rowheight=28) style.configure("Content.TFrame", background=self.icon_bg_color) - - style.map("Treeview", - background=[('selected', self.selection_color)], - foreground=[('selected', self.selection_fg_color)]) + style.map("Treeview", background=[('selected', self.selection_color)], foreground=[('selected', self.selection_fg_color)]) + ttk.Style().configure("Sidebar.TButton", anchor="w", padding=5) def create_widgets(self): main_frame = ttk.Frame(self, padding="10") @@ -207,38 +169,29 @@ class CustomFileDialog(tk.Toplevel): paned_window.add(sidebar_frame, weight=0) sidebar_frame.grid_rowconfigure(1, weight=1) - # --- Navigation buttons in Sidebar --- 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 = 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 = 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 = 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_config = [ - {'name': 'Downloads', 'icon': self.icons['downloads_small'], 'path': os.path.join( - os.path.expanduser("~"), "Downloads")}, - {'name': 'Dokumente', 'icon': self.icons['documents_small'], 'path': os.path.join( - os.path.expanduser("~"), "Documents")}, - {'name': 'Bilder', 'icon': self.icons['pictures_small'], 'path': os.path.join( - os.path.expanduser("~"), "Pictures")}, - {'name': 'Musik', 'icon': self.icons['music_small'], 'path': os.path.join( - os.path.expanduser("~"), "Music")}, - {'name': 'Videos', 'icon': self.icons['video_small'], 'path': os.path.join( - os.path.expanduser("~"), "Videos")}, + {'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")}, ] 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) - ttk.Style().configure("Sidebar.TButton", anchor="w", padding=5) content_frame = ttk.Frame(paned_window, padding=(5, 0, 0, 0)) paned_window.add(content_frame, weight=1) @@ -250,22 +203,19 @@ class CustomFileDialog(tk.Toplevel): 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 = 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=15) + 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.bind("<>", self.on_filter_change) self.filter_combobox.set(self.filetypes[0][0]) self.file_list_frame = ttk.Frame(content_frame, style="Content.TFrame") @@ -279,169 +229,134 @@ class CustomFileDialog(tk.Toplevel): 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) + 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 def populate_files(self): - for widget in self.file_list_frame.winfo_children(): - widget.destroy() + 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 self.update_status_bar() - if self.view_mode.get() == "list": - self.populate_list_view() - else: - self.populate_icon_view() + 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) + 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.") 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 = 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") - self.container_frame = container_frame 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"))) - try: - items = os.listdir(self.current_dir) - except PermissionError: - ttk.Label(container_frame, text="Zugriff verweigert.").pack(pady=20) - return + items, error = self._get_sorted_items() + if error: ttk.Label(container_frame, text=error).pack(pady=20); return - item_width = 120 - item_height = 100 + item_width, item_height = 120, 100 frame_width = self.file_list_frame.winfo_width() col_count = max(1, frame_width // item_width) row, col = 0, 0 - for name in sorted(items, key=str.lower): - if not self.show_hidden_files.get() and name.startswith('.'): - continue + 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 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 = 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 = 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 = 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)) - - col += 1 - if col >= col_count: - col = 0 - row += 1 + 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 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.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) + 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) - try: - items = os.listdir(self.current_dir) - except PermissionError: - return + items, error = self._get_sorted_items() + if error: self.tree.insert("", "end", text=error); return - for name in sorted(items, key=str.lower): - if not self.show_hidden_files.get() and name.startswith('.'): - continue + 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 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') + modified_time = datetime.fromtimestamp(stat.st_mtime).strftime('%d.%m.%Y %H:%M') if is_dir: - icon = self.icons['folder_small'] - file_type = "Ordner" - size = "" + icon, file_type, size = self.icons['folder_small'], "Ordner", "" else: - icon = self.get_file_icon(name, 'small') - file_type = "Datei" - size = 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 and self.selected_item_frame.winfo_exists(): + if hasattr(self, 'selected_item_frame') and self.selected_item_frame.winfo_exists(): self.selected_item_frame.state(['!selected']) - # Also revert the style of the labels inside the old frame for child in self.selected_item_frame.winfo_children(): - if isinstance(child, ttk.Label): - child.state(['!selected']) - + if isinstance(child, ttk.Label): child.state(['!selected']) item_frame.state(['selected']) - # Also apply the style to the labels inside the new frame for child in item_frame.winfo_children(): - if isinstance(child, ttk.Label): - child.state(['selected']) - + 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) self.update_status_bar() + def _handle_unsupported_file(self, path): + if path.lower().endswith('.svg'): + 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: @@ -449,11 +364,11 @@ class CustomFileDialog(tk.Toplevel): 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: @@ -469,10 +384,16 @@ class CustomFileDialog(tk.Toplevel): self.populate_files() def navigate_to(self, path): - home_dir = os.path.expanduser("~") - abs_path = os.path.abspath(path) - if os.path.isdir(abs_path): # and abs_path.startswith(home_dir): - self.current_dir = abs_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 + if not os.access(real_path, os.R_OK): + 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 not self.history or self.history[-1] != self.current_dir: @@ -480,9 +401,8 @@ class CustomFileDialog(tk.Toplevel): self.history_pos = len(self.history) - 1 self.populate_files() self.update_nav_buttons() - # else: - # print( - # f"Info: Navigation außerhalb von {home_dir} ist nicht erlaubt.") + except Exception as e: + self.status_bar.config(text=f"Fehler: {e}") def go_back(self): if self.history_pos > 0: @@ -499,10 +419,8 @@ class CustomFileDialog(tk.Toplevel): 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: @@ -512,36 +430,40 @@ class CustomFileDialog(tk.Toplevel): 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) - status_text += f" | Dateigröße: {size_str}" + 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") def on_open(self): if self.selected_file and os.path.isfile(self.selected_file): + if self._handle_unsupported_file(self.selected_file): return self.destroy() def on_cancel(self): self.selected_file = None self.destroy() - def get_selected_file(self): return self.selected_file + def get_selected_file(self): + return self.selected_file def _matches_filetype(self, filename): if self.current_filter_pattern == "*.*": return True - return filename.lower().endswith(self.current_filter_pattern.lower().replace("*", "")) + 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 + 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[:max_len-3] + "..." if len(text) > max_len else text + return text if len(text) <= max_len else text[:max_len-3] + "..." \ No newline at end of file diff --git a/mainwindow.py b/mainwindow.py index d5a7d1c..6f115d3 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 import tkinter as tk +import os from tkinter import ttk from custom_file_dialog import CustomFileDialog @@ -9,22 +10,48 @@ class GlotzMol(tk.Tk): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.geometry('800x400') - ttk.Label(text="Custodialog-teschdfeschda").grid(row=0, column=0) - self.columnconfigure(1, weight=1) - self.iso_path_entry = ttk.Entry(self) + self.title("Custom File Dialog Test") + + container = ttk.Frame(self, padding=10) + container.pack(fill="both", expand=True) + + ttk.Label(container, text="Ausgewählte Datei:").grid( + row=0, column=0, sticky="w") + + self.iso_path_entry = ttk.Entry(container) self.iso_path_entry.grid( - row=1, column=0, columnspan=2, padx=15, pady=5, sticky="ew") - ttk.Button(self, text="Öffnen", command=self.customtest).grid( - row=2, column=0, padx=5, pady=5) + row=1, column=0, columnspan=2, padx=(0, 10), pady=5, sticky="ew") - def customtest(self): - dialog = CustomFileDialog(self, initial_dir="/home/punix/Downloads", - filetypes=[("ISO files", "*.iso"), ("All files", "*.*")]) - path = dialog.get_selected_file() + self.open_button = ttk.Button( + container, text="Datei auswählen...", command=self.open_custom_dialog) + self.open_button.grid(row=1, column=2, pady=5, sticky="e") - if path: + 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"), + ("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) + + # Now, get the result + selected_path = dialog.get_selected_file() + + if selected_path: self.iso_path_entry.delete(0, tk.END) - self.iso_path_entry.insert(0, path) + self.iso_path_entry.insert(0, selected_path) + print(f"Die ausgewählte Datei ist: {selected_path}") + else: + print("Keine Datei ausgewählt.") if __name__ == "__main__": @@ -33,7 +60,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() diff --git a/warning.png b/warning.png new file mode 100644 index 0000000..8b015d3 Binary files /dev/null and b/warning.png differ