commit six
This commit is contained in:
Binary file not shown.
BIN
computer-32.png
Normal file
BIN
computer-32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
computer-64.png
Normal file
BIN
computer-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@@ -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("<Return>", 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(
|
||||
"<<ComboboxSelected>>", self.on_filter_change)
|
||||
self.filter_combobox.bind("<<ComboboxSelected>>", 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("<Configure>", lambda e: canvas.configure(
|
||||
scrollregion=canvas.bbox("all")))
|
||||
container_frame.bind("<Configure>", 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("<Double-Button-1>", lambda e,
|
||||
p=path: self.on_item_double_click(p))
|
||||
widget.bind("<Button-1>", 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("<Double-Button-1>", lambda e, p=path: self.on_item_double_click(p))
|
||||
widget.bind("<Button-1>", 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("<Double-1>", self.on_list_double_click)
|
||||
self.tree.bind("<<TreeviewSelect>>", self.on_list_select)
|
||||
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)
|
||||
|
||||
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] + "..."
|
@@ -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()
|
||||
|
BIN
warning.png
Normal file
BIN
warning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Reference in New Issue
Block a user