commit two
5
GEMINI.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Gemini Project Configuration
|
||||
|
||||
## Language
|
||||
Please respond in German.
|
||||
|
@@ -1,7 +1,56 @@
|
||||
import os
|
||||
import shutil
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import os
|
||||
from functools import partial
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Tooltip:
|
||||
def __init__(self, widget, text, wraplength=250):
|
||||
self.widget = widget
|
||||
self.text = text
|
||||
self.wraplength = wraplength
|
||||
self.tooltip_window = None
|
||||
self.id = None
|
||||
self.widget.bind("<Enter>", self.enter)
|
||||
self.widget.bind("<Leave>", self.leave)
|
||||
self.widget.bind("<ButtonPress>", self.leave)
|
||||
|
||||
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 unschedule(self):
|
||||
id = self.id
|
||||
self.id = None
|
||||
if id:
|
||||
self.widget.after_cancel(id)
|
||||
|
||||
def show_tooltip(self, event=None):
|
||||
x, y, _, _ = self.widget.bbox("insert")
|
||||
x += self.widget.winfo_rootx() + 25
|
||||
y += self.widget.winfo_rooty() + 20
|
||||
self.tooltip_window = tw = tk.Toplevel(self.widget)
|
||||
tw.wm_overrideredirect(True)
|
||||
tw.wm_geometry(f"+{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,
|
||||
relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength, padding=(4, 2, 4, 2))
|
||||
label.pack(ipadx=1)
|
||||
|
||||
def hide_tooltip(self):
|
||||
tw = self.tooltip_window
|
||||
self.tooltip_window = None
|
||||
if tw:
|
||||
tw.destroy()
|
||||
|
||||
|
||||
class CustomFileDialog(tk.Toplevel):
|
||||
@@ -9,249 +58,384 @@ class CustomFileDialog(tk.Toplevel):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
self.title("Datei auswählen")
|
||||
self.geometry("800x600") # Standardgröße, kann angepasst werden
|
||||
# Macht das Dialogfenster modal zum Hauptfenster
|
||||
self.geometry("900x650")
|
||||
self.minsize(650, 400)
|
||||
self.transient(parent)
|
||||
self.grab_set() # Fängt alle Ereignisse ab, bis der Dialog geschlossen wird
|
||||
self.grab_set()
|
||||
|
||||
self.selected_file = None
|
||||
self.current_dir = os.path.abspath(
|
||||
initial_dir) if initial_dir else os.getcwd()
|
||||
self.filetypes = filetypes if filetypes else [("All files", "*.*")]
|
||||
self.current_filter_pattern = "*.*" # Standardfilter
|
||||
|
||||
# Icons (Platzhalter, müssten geladen werden)
|
||||
# Sie müssen hier Ihre eigenen Icons laden oder generieren
|
||||
# Beispiel: self.folder_icon = tk.PhotoImage(file="path/to/your/folder_icon.png")
|
||||
# Für den Anfang verwenden wir leere PhotoImages
|
||||
self.folder_icon = tk.PhotoImage(
|
||||
file="./water-folder.png")
|
||||
self.file_icon = tk.PhotoImage(
|
||||
file="./text-x-generic.png")
|
||||
self.iso_icon = tk.PhotoImage(
|
||||
file="./media-optical.png")
|
||||
|
||||
self._create_widgets()
|
||||
|
||||
# Filter-Combobox initialisieren
|
||||
filter_descriptions = [desc for desc, pattern in self.filetypes]
|
||||
self.filter_combobox["values"] = filter_descriptions
|
||||
# Ersten Filter als Standard setzen
|
||||
self.filter_combobox.set(filter_descriptions[0])
|
||||
# Den initialen Filter explizit setzen, da <<ComboboxSelected>> nicht immer ausgelöst wird
|
||||
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.view_mode = tk.StringVar(value="icons")
|
||||
self.resize_job = None
|
||||
self.last_width = 0
|
||||
|
||||
self._populate_files()
|
||||
self.load_icons()
|
||||
self.create_styles()
|
||||
self.create_widgets()
|
||||
self.update_status_bar()
|
||||
self.after(50, self.populate_files)
|
||||
|
||||
# Behandelt das Schließen des Fensters
|
||||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
self.wait_window(self) # Wartet, bis das Fenster geschlossen wird
|
||||
def load_icons(self):
|
||||
try:
|
||||
self.folder_icon_large = tk.PhotoImage(
|
||||
file="./folder-water.png").zoom(1)
|
||||
self.file_icon_large = tk.PhotoImage(
|
||||
file="./document.png").zoom(1)
|
||||
self.iso_icon_large = tk.PhotoImage(
|
||||
file="./media-optical.png").zoom(1)
|
||||
self.folder_icon_small = tk.PhotoImage(file="./folder-water.png")
|
||||
self.file_icon_small = tk.PhotoImage(file="./document.png")
|
||||
self.iso_icon_small = tk.PhotoImage(file="./media-optical.png")
|
||||
except tk.TclError:
|
||||
self.folder_icon_large = tk.PhotoImage(width=48, height=48)
|
||||
self.file_icon_large = tk.PhotoImage(width=48, height=48)
|
||||
self.iso_icon_large = tk.PhotoImage(width=48, height=48)
|
||||
self.folder_icon_small = tk.PhotoImage(width=16, height=16)
|
||||
self.file_icon_small = tk.PhotoImage(width=16, height=16)
|
||||
self.iso_icon_small = tk.PhotoImage(width=16, height=16)
|
||||
|
||||
def _create_widgets(self):
|
||||
# Hauptframe
|
||||
def create_styles(self):
|
||||
style = ttk.Style(self)
|
||||
self.selection_color = "#0078D7"
|
||||
style.map('Item.TFrame', background=[
|
||||
('selected', self.selection_color)])
|
||||
style.configure("Treeview.Heading", relief="raised",
|
||||
borderwidth=1, font=('TkDefaultFont', 10, 'bold'))
|
||||
style.configure("Treeview", rowheight=24)
|
||||
style.layout("Treeview.Row",
|
||||
[('Treeview.row', {'children': [('Treeview.padding', {'children': [('Treeview.indicator', {'side': 'left', 'sticky': 'ns'}), ('Treeview.image', {'side': 'left', 'sticky': 'ns'}), ('Treeview.text', {'side': 'left', 'sticky': 'ns'})], 'sticky': 'nsew'})], 'sticky': 'nsew'})])
|
||||
|
||||
def create_widgets(self):
|
||||
main_frame = ttk.Frame(self, padding="10")
|
||||
main_frame.pack(fill="both", expand=True)
|
||||
# Canvas-Bereich soll sich ausdehnen
|
||||
main_frame.grid_rowconfigure(1, weight=1)
|
||||
main_frame.grid_columnconfigure(0, weight=1)
|
||||
paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
|
||||
paned_window.pack(fill="both", expand=True)
|
||||
|
||||
# Pfad-Navigation
|
||||
path_frame = ttk.Frame(main_frame)
|
||||
path_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
|
||||
path_frame.grid_columnconfigure(1, weight=1)
|
||||
sidebar_frame = ttk.Frame(paned_window, padding=5)
|
||||
paned_window.add(sidebar_frame, weight=0)
|
||||
sidebar_frame.grid_rowconfigure(1, weight=1)
|
||||
|
||||
ttk.Button(path_frame, text="Up", command=self._go_up_dir).grid(
|
||||
row=0, column=0, padx=(0, 5))
|
||||
self.path_entry = ttk.Entry(path_frame, state="readonly")
|
||||
self.path_entry.grid(row=0, column=1, sticky="ew")
|
||||
# --- 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.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)
|
||||
|
||||
ttk.Label(path_frame, text="Dateityp:").grid(
|
||||
row=0, column=2, padx=(10, 5))
|
||||
self.filter_combobox = ttk.Combobox(path_frame, state="readonly")
|
||||
self.filter_combobox.grid(row=0, column=3, sticky="ew")
|
||||
sidebar_buttons_frame = ttk.Frame(sidebar_frame)
|
||||
sidebar_buttons_frame.grid(row=1, column=0, sticky="nsew")
|
||||
sidebar_buttons_config = [
|
||||
{'name': 'Downloads', 'unicode': '📥', 'path': os.path.join(
|
||||
os.path.expanduser("~"), "Downloads")},
|
||||
{'name': 'Dokumente', 'unicode': '📄', 'path': os.path.join(
|
||||
os.path.expanduser("~"), "Documents")},
|
||||
{'name': 'Bilder', 'unicode': '🖼️', 'path': os.path.join(
|
||||
os.path.expanduser("~"), "Pictures")},
|
||||
{'name': 'Musik', 'unicode': '🎵', 'path': os.path.join(
|
||||
os.path.expanduser("~"), "Music")},
|
||||
{'name': 'Videos', 'unicode': '🎬', 'path': os.path.join(
|
||||
os.path.expanduser("~"), "Videos")},
|
||||
]
|
||||
for config in sidebar_buttons_config:
|
||||
btn = ttk.Button(sidebar_buttons_frame, text=f" {config['unicode']} {config['name']}",
|
||||
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)
|
||||
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")
|
||||
view_switch = ttk.Frame(top_bar, padding=(5, 0))
|
||||
view_switch.grid(row=0, column=1)
|
||||
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)
|
||||
self.filter_combobox.grid(row=0, column=2, padx=5)
|
||||
self.filter_combobox.bind(
|
||||
"<<ComboboxSelected>>", self._on_filter_select)
|
||||
# Optional: Pfad im System-Explorer öffnen
|
||||
# Canvas für Dateianzeige (Kachelansicht)
|
||||
self.canvas_frame = ttk.Frame(main_frame)
|
||||
self.canvas_frame.grid(row=1, column=0, sticky="nsew")
|
||||
self.canvas_frame.grid_rowconfigure(0, weight=1)
|
||||
self.canvas_frame.grid_columnconfigure(0, weight=1)
|
||||
"<<ComboboxSelected>>", self.on_filter_change)
|
||||
self.filter_combobox.set(self.filetypes[0][0])
|
||||
|
||||
self.canvas = tk.Canvas(
|
||||
self.canvas_frame, bg="white", highlightthickness=0)
|
||||
self.canvas.grid(row=0, column=0, sticky="nsew")
|
||||
self.file_list_frame = ttk.Frame(content_frame)
|
||||
self.file_list_frame.grid(row=1, column=0, sticky="nsew")
|
||||
self.bind("<Configure>", self.on_window_resize)
|
||||
|
||||
self.v_scrollbar = ttk.Scrollbar(
|
||||
self.canvas_frame, orient="vertical", command=self.canvas.yview)
|
||||
self.v_scrollbar.grid(row=0, column=1, sticky="ns")
|
||||
self.canvas.configure(yscrollcommand=self.v_scrollbar.set)
|
||||
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)
|
||||
|
||||
self.h_scrollbar = ttk.Scrollbar(
|
||||
self.canvas_frame, orient="horizontal", command=self.canvas.xview)
|
||||
self.h_scrollbar.grid(row=1, column=0, sticky="ew")
|
||||
self.canvas.configure(xscrollcommand=self.h_scrollbar.set)
|
||||
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)
|
||||
self.resize_job = self.after(200, self.populate_files)
|
||||
self.last_width = new_width
|
||||
|
||||
# Frame zum Halten der Kacheln
|
||||
self.inner_frame = ttk.Frame(self.canvas)
|
||||
self.canvas.create_window((0, 0), window=self.inner_frame, anchor="nw")
|
||||
|
||||
self.inner_frame.bind("<Configure>", lambda e: self.canvas.configure(
|
||||
scrollregion=self.canvas.bbox("all")))
|
||||
# Für Klicks auf leeren Bereich
|
||||
self.canvas.bind("<Button-1>", self._on_canvas_click)
|
||||
|
||||
# Buttons
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.grid(row=2, column=0, sticky="ew", pady=(5, 0))
|
||||
button_frame.grid_columnconfigure(0, weight=1) # Leerraum links
|
||||
ttk.Button(button_frame, text="Öffnen", command=self._on_open).grid(
|
||||
row=0, column=1, padx=(0, 5))
|
||||
ttk.Button(button_frame, text="Abbrechen",
|
||||
command=self._on_cancel).grid(row=0, column=2)
|
||||
|
||||
def _populate_files(self):
|
||||
# Lösche alle vorhandenen Kacheln
|
||||
for widget in self.inner_frame.winfo_children():
|
||||
def populate_files(self):
|
||||
for widget in self.file_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
self.path_entry.config(state="normal")
|
||||
self.path_entry.delete(0, tk.END)
|
||||
self.path_entry.insert(0, self.current_dir)
|
||||
self.path_entry.config(state="readonly")
|
||||
self.selected_file = None
|
||||
self.update_status_bar()
|
||||
if self.view_mode.get() == "list":
|
||||
self.populate_list_view()
|
||||
else:
|
||||
self.populate_icon_view()
|
||||
|
||||
def populate_icon_view(self):
|
||||
canvas = tk.Canvas(self.file_list_frame, highlightthickness=0)
|
||||
v_scrollbar = ttk.Scrollbar(
|
||||
self.file_list_frame, orient="vertical", command=canvas.yview)
|
||||
style = ttk.Style(self)
|
||||
bg_color = style.lookup("TFrame", "background")
|
||||
canvas.configure(bg=bg_color)
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
v_scrollbar.pack(side="right", fill="y")
|
||||
container_frame = ttk.Frame(canvas, style="TFrame")
|
||||
self.container_frame = container_frame
|
||||
canvas.create_window((0, 0), window=container_frame, anchor="nw")
|
||||
container_frame.bind("<Configure>", lambda e: canvas.configure(
|
||||
scrollregion=canvas.bbox("all")))
|
||||
|
||||
files_and_dirs = []
|
||||
try:
|
||||
for item in os.listdir(self.current_dir):
|
||||
full_path = os.path.join(self.current_dir, item)
|
||||
if os.path.isdir(full_path):
|
||||
files_and_dirs.append((item, "dir"))
|
||||
elif os.path.isfile(full_path):
|
||||
# Dateien nur hinzufügen, wenn sie dem aktuellen Filter entsprechen
|
||||
if self._matches_filetype(item):
|
||||
files_and_dirs.append((item, "file"))
|
||||
items = os.listdir(self.current_dir)
|
||||
except PermissionError:
|
||||
# Fehlerbehandlung für nicht zugängliche Verzeichnisse
|
||||
ttk.Label(self.inner_frame,
|
||||
text="Zugriff verweigert.").pack(pady=20)
|
||||
return
|
||||
except Exception as e:
|
||||
ttk.Label(self.inner_frame, text=f"Fehler: {e}").pack(pady=20)
|
||||
ttk.Label(container_frame, text="Zugriff verweigert.").pack(pady=20)
|
||||
return
|
||||
|
||||
# Sortieren: Ordner zuerst, dann Dateien, alphabetisch
|
||||
files_and_dirs.sort(key=lambda x: (x[1] == "file", x[0].lower()))
|
||||
item_width = 120
|
||||
item_height = 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):
|
||||
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
|
||||
|
||||
# Kachel-Layout (Beispiel: 4 Spalten)
|
||||
col_count = 4
|
||||
for i, (name, item_type) in enumerate(files_and_dirs):
|
||||
row = i // col_count
|
||||
col = i % col_count
|
||||
|
||||
icon = self.folder_icon if item_type == "dir" else (
|
||||
self.iso_icon if name.lower().endswith(".iso") else self.file_icon)
|
||||
|
||||
# Kachel-Frame
|
||||
item_frame = ttk.Frame(
|
||||
self.inner_frame, relief="solid", borderwidth=1, padding=5)
|
||||
item_frame.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
|
||||
# Text soll sich ausdehnen
|
||||
item_frame.grid_rowconfigure(1, weight=1)
|
||||
item_frame.grid_columnconfigure(
|
||||
0, weight=1) # Icon/Text zentrieren
|
||||
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)
|
||||
|
||||
# Daten an den Frame anhängen
|
||||
item_frame.file_path = full_path
|
||||
item_frame.item_type = item_type
|
||||
|
||||
# Icon
|
||||
icon = self.folder_icon_large if is_dir else (
|
||||
self.iso_icon_large if name.lower().endswith(".iso") else self.file_icon_large)
|
||||
icon_label = ttk.Label(item_frame, image=icon)
|
||||
icon_label.grid(row=0, column=0, pady=(0, 5))
|
||||
# Daten an das Icon-Label anhängen
|
||||
icon_label.file_path = full_path
|
||||
icon_label.item_type = item_type
|
||||
icon_label.pack(pady=(10, 5))
|
||||
name_label = ttk.Label(
|
||||
item_frame, text=self.shorten_text(name, 15), anchor="center")
|
||||
name_label.pack(fill="x", expand=True)
|
||||
|
||||
# Dateiname
|
||||
# wraplength für Zeilenumbruch
|
||||
name_label = ttk.Label(item_frame, text=name,
|
||||
wraplength=100, anchor="n")
|
||||
name_label.grid(row=1, column=0, sticky="ew")
|
||||
# Daten an das Name-Label anhängen
|
||||
name_label.file_path = full_path
|
||||
name_label.item_type = item_type
|
||||
Tooltip(item_frame, name)
|
||||
for widget in [item_frame, icon_label, name_label]:
|
||||
widget.bind("<Double-Button-1>", lambda e,
|
||||
p=path: self.on_item_double_click(p))
|
||||
widget.bind("<Button-1>", lambda e,
|
||||
p=path: self.on_item_select(p, item_frame))
|
||||
|
||||
# Bindungen für Klicks (mit functools.partial)
|
||||
item_frame.bind(
|
||||
"<Button-1>", partial(self._on_item_click, full_path, item_type))
|
||||
icon_label.bind(
|
||||
"<Button-1>", partial(self._on_item_click, full_path, item_type))
|
||||
name_label.bind(
|
||||
"<Button-1>", partial(self._on_item_click, full_path, item_type))
|
||||
col += 1
|
||||
if col >= col_count:
|
||||
col = 0
|
||||
row += 1
|
||||
|
||||
item_frame.bind(
|
||||
"<Double-Button-1>", partial(self._on_item_double_click, full_path, item_type))
|
||||
icon_label.bind(
|
||||
"<Double-Button-1>", partial(self._on_item_double_click, full_path, item_type))
|
||||
name_label.bind(
|
||||
"<Double-Button-1>", partial(self._on_item_double_click, full_path, item_type))
|
||||
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("<Double-1>", self.on_list_double_click)
|
||||
self.tree.bind("<<TreeviewSelect>>", self.on_list_select)
|
||||
|
||||
def _go_up_dir(self):
|
||||
parent_dir = os.path.dirname(self.current_dir)
|
||||
if parent_dir != self.current_dir: # Verhindert, dass man über das Root-Verzeichnis hinausgeht
|
||||
self.current_dir = parent_dir
|
||||
self._populate_files()
|
||||
try:
|
||||
items = os.listdir(self.current_dir)
|
||||
except PermissionError:
|
||||
return
|
||||
|
||||
def _on_item_click(self, path, item_type, event):
|
||||
print(f"DEBUG: _on_item_click - Path: {path}, Type: {item_type}")
|
||||
# Hier können Sie eine visuelle Auswahl hervorheben
|
||||
self.selected_file = path # Temporär speichern
|
||||
for name in sorted(items, key=str.lower):
|
||||
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')
|
||||
if is_dir:
|
||||
icon = self.folder_icon_small
|
||||
file_type = "Ordner"
|
||||
size = ""
|
||||
else:
|
||||
icon = self.iso_icon_small if name.lower().endswith(
|
||||
".iso") else self.file_icon_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
|
||||
|
||||
def _on_item_double_click(self, path, item_type, event):
|
||||
print(
|
||||
f"DEBUG: _on_item_double_click - Path: {path}, Type: {item_type}")
|
||||
if item_type == "dir":
|
||||
self.current_dir = path
|
||||
self._populate_files()
|
||||
def on_item_select(self, path, item_frame):
|
||||
for child in self.container_frame.winfo_children():
|
||||
child.state(['!selected'])
|
||||
item_frame.state(['selected'])
|
||||
self.selected_file = path
|
||||
self.update_status_bar()
|
||||
|
||||
def on_list_select(self, event):
|
||||
if not self.tree.selection():
|
||||
return
|
||||
item_text = self.tree.item(self.tree.selection()[0])['text']
|
||||
self.selected_file = os.path.join(self.current_dir, item_text)
|
||||
self.update_status_bar()
|
||||
|
||||
def on_item_double_click(self, path):
|
||||
if os.path.isdir(path):
|
||||
self.navigate_to(path)
|
||||
else:
|
||||
self.selected_file = path
|
||||
self.destroy() # Dialog schließen und ausgewählte Datei zurückgeben
|
||||
|
||||
def _on_open(self):
|
||||
if self.selected_file and os.path.isfile(self.selected_file):
|
||||
self.destroy()
|
||||
|
||||
def on_list_double_click(self, event):
|
||||
if not self.tree.selection():
|
||||
return
|
||||
item_text = self.tree.item(self.tree.selection()[0])['text']
|
||||
path = os.path.join(self.current_dir, item_text)
|
||||
if os.path.isdir(path):
|
||||
self.navigate_to(path)
|
||||
else:
|
||||
# Optional: Fehlermeldung, wenn keine Datei ausgewählt oder Ordner doppelt geklickt wurde
|
||||
print("Bitte eine Datei auswählen oder einen Ordner doppelt klicken.")
|
||||
self.selected_file = path
|
||||
self.destroy()
|
||||
|
||||
def _on_cancel(self):
|
||||
self.selected_file = None # Nichts ausgewählt
|
||||
self.destroy()
|
||||
|
||||
def _on_closing(self):
|
||||
self.selected_file = None # Nichts ausgewählt, wenn Fenster geschlossen wird
|
||||
self.destroy()
|
||||
|
||||
def _matches_filetype(self, filename):
|
||||
# Wenn der aktuelle Filter "*.*" ist, passen alle Dateien
|
||||
if self.current_filter_pattern == "*.*":
|
||||
return True
|
||||
|
||||
# Ansonsten prüfen, ob die Datei zum aktuellen spezifischen Filter passt
|
||||
ext = self.current_filter_pattern[2:].lower()
|
||||
return filename.lower().endswith("." + ext)
|
||||
|
||||
def _on_filter_select(self, event):
|
||||
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
|
||||
self._populate_files() # Dateiliste neu laden mit neuem Filter
|
||||
self.populate_files()
|
||||
|
||||
def _on_canvas_click(self, event):
|
||||
# Wenn auf den leeren Bereich des Canvas geklickt wird, Auswahl aufheben
|
||||
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
|
||||
if self.history_pos < len(self.history) - 1:
|
||||
self.history = self.history[:self.history_pos + 1]
|
||||
if self.history[-1] != self.current_dir:
|
||||
self.history.append(self.current_dir)
|
||||
self.history_pos += 1
|
||||
self.populate_files()
|
||||
self.update_nav_buttons()
|
||||
else:
|
||||
print(
|
||||
f"Info: Navigation außerhalb von {home_dir} ist nicht erlaubt.")
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
def update_status_bar(self):
|
||||
try:
|
||||
_, _, 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)
|
||||
status_text += f" | Dateigröß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):
|
||||
self.destroy()
|
||||
|
||||
def on_cancel(self):
|
||||
self.selected_file = None
|
||||
# Optional: Visuelle Hervorhebung entfernen
|
||||
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("*", ""))
|
||||
|
||||
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"
|
||||
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
|
||||
|
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 612 B |
BIN
file-python.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water-documents.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water-download.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water-music.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water-pictures.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water-video.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
folder-water.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
picture.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
water-folder.png
Before Width: | Height: | Size: 1.3 KiB |