commit 36 mit common tools

This commit is contained in:
2025-08-01 09:29:26 +02:00
parent 2404a60b6c
commit e3bb68f7e2
5 changed files with 1032 additions and 116 deletions

View File

@@ -5,16 +5,14 @@ from tkinter import ttk
from datetime import datetime
import subprocess
import json
from shared_libs.message import MessageDialog
from shared_libs.common_tools import IconManager, Tooltip
# Helper to make icon paths robust, so the script can be run from anywhere
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MAX_ITEMS_TO_DISPLAY = 1000
def get_icon_path(icon_name):
return os.path.join(SCRIPT_DIR, icon_name)
def get_xdg_user_dir(dir_key, fallback_name):
home = os.path.expanduser("~")
fallback_path = os.path.join(home, fallback_name)
@@ -38,47 +36,6 @@ def get_xdg_user_dir(dir_key, fallback_name):
return fallback_path
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(250, 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}")
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()
class CustomFileDialog(tk.Toplevel):
def __init__(self, parent, initial_dir=None, filetypes=None):
super().__init__(parent)
@@ -100,56 +57,18 @@ class CustomFileDialog(tk.Toplevel):
self.show_hidden_files = tk.BooleanVar(value=False)
self.resize_job = None
self.last_width = 0
self.sidebar_buttons = []
self.device_buttons = []
self.search_results = [] # Store search results
self.search_mode = False # Track if in search mode
self.original_path_text = "" # Store original path text
self.load_icons()
self.icon_manager = IconManager()
self.icons = self.icon_manager.icons
self.create_styles()
self.create_widgets()
self.navigate_to(self.current_dir)
def load_icons(self):
self.icons = {}
icon_files = {
'computer_small': '/usr/share/icons/lx-icons/32/computer-32.png',
'computer_large': '/usr/share/icons/lx-icons/48/computer-48.png',
'device_small': '/usr/share/icons/lx-icons/32/device-32.png',
'device_large': '/usr/share/icons/lx-icons/48/device-48.png',
'usb_small': '/usr/share/icons/lx-icons/32/usb-32.png',
'usb_large': '/usr/share/icons/lx-icons/48/usb-48.png',
'downloads_small': '/usr/share/icons/lx-icons/32/folder-water-download-32.png',
'downloads_large': '/usr/share/icons/lx-icons/48/folder-water-download-48.png',
'documents_small': '/usr/share/icons/lx-icons/32/folder-water-documents-32.png',
'documents_large': '/usr/share/icons/lx-icons/48/folder-water-documents-48.png',
'pictures_small': '/usr/share/icons/lx-icons/32/folder-water-pictures-32.png',
'pictures_large': '/usr/share/icons/lx-icons/48/folder-water-pictures-48.png',
'music_small': '/usr/share/icons/lx-icons/32/folder-water-music-32.png',
'music_large': '/usr/share/icons/lx-icons/48/folder-water-music-48.png',
'video_small': '/usr/share/icons/lx-icons/32/folder-water-video-32.png',
'video_large_folder': '/usr/share/icons/lx-icons/48/folder-water-video-48.png',
'warning_small': '/usr/share/icons/lx-icons/32/warning.png', 'warning_large': '/usr/share/icons/lx-icons/64/warning.png',
'folder_large': '/usr/share/icons/lx-icons/64/folder-water-64.png', 'file_large': '/usr/share/icons/lx-icons/64/document-64.png',
'python_large': '/usr/share/icons/lx-icons/64/file-python-64.png', 'pdf_large': '/usr/share/icons/lx-icons/64/pdf-64.png',
'archive_large': '/usr/share/icons/lx-icons/64/tar-64.png', 'audio_large': '/usr/share/icons/lx-icons/64/audio-64.png',
'video_large': '/usr/share/icons/lx-icons/64/video-64.png', 'picture_large': '/usr/share/icons/lx-icons/64/picture-64.png',
'iso_large': '/usr/share/icons/lx-icons/64/media-optical-64.png', 'folder_small': '/usr/share/icons/lx-icons/32/folder-water-32.png',
'file_small': '/usr/share/icons/lx-icons/32/document-32.png', 'python_small': '/usr/share/icons/lx-icons/32/file-python-32.png',
'pdf_small': '/usr/share/icons/lx-icons/32/pdf-32.png', 'archive_small': '/usr/share/icons/lx-icons/32/tar-32.png',
'audio_small': '/usr/share/icons/lx-icons/32/audio-32.png', 'video_small_file': '/usr/share/icons/lx-icons/32/video-32.png',
'picture_small': '/usr/share/icons/lx-icons/32/picture-32.png', 'iso_small': '/usr/share/icons/lx-icons/32/media-optical-32.png',
'list_view': '/usr/share/icons/lx-icons/32/list-32.png',
'icon_view': '/usr/share/icons/lx-icons/32/carrel-32.png',
'hide': '/usr/share/icons/lx-icons/32/hide-32.png',
'unhide': '/usr/share/icons/lx-icons/32/unhide-32.png',
'back': '/usr/share/icons/lx-icons/32/arrow-left-32.png',
'forward': '/usr/share/icons/lx-icons/32/arrow-right-32.png',
'home': '/usr/share/icons/lx-icons/32/home-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 or 'view' in key or 'hide' in key or 'unhide' in key or 'back' in key or 'forward' in key or 'home' 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 == '.svg':
@@ -207,6 +126,17 @@ class CustomFileDialog(tk.Toplevel):
style.map("Header.TButton.Borderless.Round", background=[
('active', self.hover_extrastyle)])
# Style for active/pressed header buttons
style.configure("Header.TButton.Active.Round",
background=self.selection_color)
# Copy layout from the base style
style.layout("Header.TButton.Active.Round",
style.layout("Header.TButton.Borderless.Round"))
style.map("Header.TButton.Active.Round", background=[
('active', self.selection_color)])
style.configure("Dark.TButton.Borderless", anchor="w",
background=self.sidebar_color, foreground=self.color_foreground, padding=(20, 5, 0, 5))
@@ -278,21 +208,43 @@ class CustomFileDialog(tk.Toplevel):
self.path_entry.bind(
"<Return>", lambda e: self.navigate_to(self.path_entry.get()))
# View switch and hidden files button
# Search, view switch and hidden files button
right_top_bar_frame = ttk.Frame(top_bar, style='Accent.TFrame')
right_top_bar_frame.grid(row=0, column=2, sticky="e")
# Search button and options container
search_container = ttk.Frame(
right_top_bar_frame, style='Accent.TFrame')
search_container.pack(side="left", padx=(0, 10))
self.search_button = ttk.Button(search_container, image=self.icons['search_small'],
command=self.toggle_search_mode, style="Header.TButton.Borderless.Round")
self.search_button.pack(side="left")
Tooltip(self.search_button, "Suchen")
# Search options frame (initially hidden, next to search button)
self.search_options_frame = ttk.Frame(
search_container, style='Accent.TFrame')
# Recursive search toggle button
self.recursive_search = tk.BooleanVar(value=True)
self.recursive_button = ttk.Button(self.search_options_frame, image=self.icons['recursive_small'],
command=self.toggle_recursive_search,
style="Header.TButton.Active.Round")
self.recursive_button.pack(side="left", padx=2)
Tooltip(self.recursive_button, "Rekursive Suche ein/ausschalten")
view_switch = ttk.Frame(right_top_bar_frame,
padding=(5, 0), style='Accent.TFrame')
view_switch.pack(side="left")
self.icon_view_button = ttk.Button(view_switch, image=self.icons['icon_view'], command=lambda: (
self.view_mode.set("icons"), self.populate_files()), style="Header.TButton.Borderless.Round")
self.icon_view_button = ttk.Button(view_switch, image=self.icons['icon_view'],
command=self.set_icon_view, style="Header.TButton.Active.Round")
self.icon_view_button.pack(side="left", padx=(50, 10))
Tooltip(self.icon_view_button, "Kachelansicht")
self.list_view_button = ttk.Button(view_switch, image=self.icons['list_view'], command=lambda: (
self.view_mode.set("list"), self.populate_files()), style="Header.TButton.Borderless.Round")
self.list_view_button = ttk.Button(view_switch, image=self.icons['list_view'],
command=self.set_list_view, style="Header.TButton.Borderless.Round")
self.list_view_button.pack(side="left")
Tooltip(self.list_view_button, "Listenansicht")
@@ -312,14 +264,14 @@ class CustomFileDialog(tk.Toplevel):
# Sidebar
sidebar_frame = ttk.Frame(
paned_window, style="Sidebar.TFrame", padding=(0, 0, 0, 0))
paned_window, style="Sidebar.TFrame", padding=(0, 0, 0, 0), width=200)
# Prevent content from resizing the frame
# sidebar_frame.grid_propagate(False)
sidebar_frame.grid_propagate(False)
sidebar_frame.bind("<Configure>", self.on_sidebar_resize)
# Use weight=0 to give it a fixed size
paned_window.add(sidebar_frame, weight=0)
sidebar_frame.grid_rowconfigure(2, weight=1)
# No weight on any row - let storage stay at bottom
sidebar_buttons_frame = ttk.Frame(
sidebar_frame, style="Sidebar.TFrame", padding=(0, 15, 0, 0))
sidebar_buttons_frame.grid(
@@ -342,43 +294,121 @@ class CustomFileDialog(tk.Toplevel):
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="Dark.TButton.Borderless")
btn.pack(fill="x", pady=1)
self.sidebar_buttons.append((btn, f" {config['name']}"))
# Horizontal separator
separator_color = "#a9a9a9" if self.is_dark else "#7c7c7c"
tk.Frame(sidebar_frame, height=1, bg=separator_color).grid(
row=1, column=0, sticky="ew", padx=20, pady=15)
# Mounted devices
# Mounted devices with scrollable frame
mounted_devices_frame = ttk.Frame(
sidebar_frame, style="Sidebar.TFrame")
mounted_devices_frame.grid(row=2, column=0, sticky="nsew", padx=10)
ttk.Label(mounted_devices_frame, text="Geräte:", background=self.sidebar_color,
foreground=self.color_foreground).pack(fill="x", padx=10, pady=(5, 0))
# Don't expand devices frame so storage stays in position
mounted_devices_frame.grid_columnconfigure(0, weight=1)
ttk.Label(mounted_devices_frame, text="Geräte:", background=self.sidebar_color,
foreground=self.color_foreground).grid(row=0, column=0, sticky="ew", padx=10, pady=(5, 0))
# Create scrollable canvas for devices
self.devices_canvas = tk.Canvas(mounted_devices_frame, highlightthickness=0,
bg=self.sidebar_color, height=150, width=180)
self.devices_scrollbar = ttk.Scrollbar(mounted_devices_frame, orient="vertical",
command=self.devices_canvas.yview)
self.devices_canvas.configure(
yscrollcommand=self.devices_scrollbar.set)
self.devices_canvas.grid(row=1, column=0, sticky="nsew")
# Scrollbar initially hidden
# Create scrollable frame inside canvas
self.devices_scrollable_frame = ttk.Frame(
self.devices_canvas, style="Sidebar.TFrame")
self.devices_canvas_window = self.devices_canvas.create_window(
(0, 0), window=self.devices_scrollable_frame, anchor="nw")
# Bind events for showing/hiding scrollbar on hover
self.devices_canvas.bind("<Enter>", self._on_devices_enter)
self.devices_canvas.bind("<Leave>", self._on_devices_leave)
self.devices_scrollable_frame.bind("<Enter>", self._on_devices_enter)
self.devices_scrollable_frame.bind("<Leave>", self._on_devices_leave)
# Bind canvas width to scrollable frame width
def _configure_devices_canvas(event):
self.devices_canvas.configure(
scrollregion=self.devices_canvas.bbox("all"))
canvas_width = event.width
self.devices_canvas.itemconfig(
self.devices_canvas_window, width=canvas_width)
self.devices_scrollable_frame.bind("<Configure>", lambda e: self.devices_canvas.configure(
scrollregion=self.devices_canvas.bbox("all")))
self.devices_canvas.bind("<Configure>", _configure_devices_canvas)
# Mouse wheel scrolling for devices area
def _on_devices_mouse_wheel(event):
if event.num == 4: # Scroll up on Linux
delta = -1
elif event.num == 5: # Scroll down on Linux
delta = 1
else: # MouseWheel event for Windows/macOS
delta = -1 * int(event.delta / 120)
self.devices_canvas.yview_scroll(delta, "units")
# Bind mouse wheel to canvas and scrollable frame
for widget in [self.devices_canvas, self.devices_scrollable_frame]:
widget.bind("<MouseWheel>", _on_devices_mouse_wheel)
widget.bind("<Button-4>", _on_devices_mouse_wheel)
widget.bind("<Button-5>", _on_devices_mouse_wheel)
# Populate devices
for device_name, mount_point, removable in self._get_mounted_devices():
icon = self.icons['usb_small'] if removable else self.icons['device_small']
button_text = f" {device_name}"
if len(device_name) > 15: # Static wrapping for long names
button_text = f" {device_name[:15]}\n{device_name[15:]}"
btn = ttk.Button(mounted_devices_frame, text=button_text, image=icon,
btn = ttk.Button(self.devices_scrollable_frame, text=button_text, image=icon,
compound="left", command=lambda p=mount_point: self.navigate_to(p), style="Dark.TButton.Borderless")
btn.pack(fill="x", pady=1)
self.device_buttons.append((btn, button_text))
# Bind mouse wheel to device buttons too
btn.bind("<MouseWheel>", _on_devices_mouse_wheel)
btn.bind("<Button-4>", _on_devices_mouse_wheel)
btn.bind("<Button-5>", _on_devices_mouse_wheel)
# Bind hover events for scrollbar visibility
btn.bind("<Enter>", self._on_devices_enter)
btn.bind("<Leave>", self._on_devices_leave)
try:
total, used, _ = shutil.disk_usage(mount_point)
progress_bar = ttk.Progressbar(
mounted_devices_frame, orient="horizontal", length=100, mode="determinate", style='Small.Horizontal.TProgressbar')
self.devices_scrollable_frame, orient="horizontal", length=100, mode="determinate", style='Small.Horizontal.TProgressbar')
progress_bar.pack(fill="x", pady=(2, 8), padx=25)
progress_bar['value'] = (used / total) * 100
# Bind mouse wheel to progress bars too
progress_bar.bind("<MouseWheel>", _on_devices_mouse_wheel)
progress_bar.bind("<Button-4>", _on_devices_mouse_wheel)
progress_bar.bind("<Button-5>", _on_devices_mouse_wheel)
# Bind hover events for scrollbar visibility
progress_bar.bind("<Enter>", self._on_devices_enter)
progress_bar.bind("<Leave>", self._on_devices_leave)
except (FileNotFoundError, PermissionError):
# In case of errors (e.g., unreadable drive), just skip the progress bar
pass
# Separator before storage
tk.Frame(sidebar_frame, height=1, bg=separator_color).grid(
row=3, column=0, sticky="ew", padx=20, pady=15)
# Storage section at bottom - use pack instead of grid to stay at bottom
storage_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame")
storage_frame.grid(row=4, column=0, sticky="ew", padx=10)
storage_frame.grid(row=4, column=0, sticky="sew", padx=10, pady=10)
self.storage_label = ttk.Label(
storage_frame, text="Freier Speicher:", background=self.freespace_background)
self.storage_label.pack(fill="x", padx=10)
@@ -441,6 +471,280 @@ class CustomFileDialog(tk.Toplevel):
self.resize_job = self.after(200, self.populate_files)
self.last_width = new_width
def on_sidebar_resize(self, event):
current_width = event.width
# Define a threshold for when to hide/show text
threshold_width = 100 # Adjust this value as needed
if current_width < threshold_width:
# Hide text, show only icons
for btn, original_text in self.sidebar_buttons:
btn.config(text="", compound="top")
for btn, original_text in self.device_buttons:
btn.config(text="", compound="top")
else:
# Show text
for btn, original_text in self.sidebar_buttons:
btn.config(text=original_text, compound="left")
for btn, original_text in self.device_buttons:
btn.config(text=original_text, compound="left")
def _on_devices_enter(self, event):
"""Show scrollbar when mouse enters devices area"""
self.devices_scrollbar.grid(row=1, column=1, sticky="ns")
def _on_devices_leave(self, event):
"""Hide scrollbar when mouse leaves devices area"""
# Check if mouse is really leaving the devices area
x, y = event.x_root, event.y_root
widget_x = self.devices_canvas.winfo_rootx()
widget_y = self.devices_canvas.winfo_rooty()
widget_width = self.devices_canvas.winfo_width()
widget_height = self.devices_canvas.winfo_height()
# Add small buffer to prevent flickering
buffer = 5
if not (widget_x - buffer <= x <= widget_x + widget_width + buffer and
widget_y - buffer <= y <= widget_y + widget_height + buffer):
self.devices_scrollbar.grid_remove()
def toggle_search_mode(self):
"""Toggle between search mode and normal mode"""
if not self.search_mode:
# Enter search mode
self.search_mode = True
self.original_path_text = self.path_entry.get()
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, "Suchbegriff eingeben...")
self.path_entry.bind("<Return>", self.execute_search)
self.path_entry.bind("<FocusIn>", self.clear_search_placeholder)
# Show search options
self.search_options_frame.pack(side="left", padx=(5, 0))
else:
# Exit search mode
self.search_mode = False
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, self.original_path_text)
self.path_entry.bind(
"<Return>", lambda e: self.navigate_to(self.path_entry.get()))
self.path_entry.unbind("<FocusIn>")
# Hide search options
self.search_options_frame.pack_forget()
# Return to normal file view
self.populate_files()
def toggle_recursive_search(self):
"""Toggle recursive search on/off and update button style"""
self.recursive_search.set(not self.recursive_search.get())
if self.recursive_search.get():
self.recursive_button.configure(
style="Header.TButton.Active.Round")
else:
self.recursive_button.configure(
style="Header.TButton.Borderless.Round")
def set_icon_view(self):
"""Set icon view and update button styles"""
self.view_mode.set("icons")
self.icon_view_button.configure(style="Header.TButton.Active.Round")
self.list_view_button.configure(
style="Header.TButton.Borderless.Round")
self.populate_files()
def set_list_view(self):
"""Set list view and update button styles"""
self.view_mode.set("list")
self.list_view_button.configure(style="Header.TButton.Active.Round")
self.icon_view_button.configure(
style="Header.TButton.Borderless.Round")
self.populate_files()
def clear_search_placeholder(self, event):
"""Clear placeholder text when focus enters search field"""
if self.path_entry.get() == "Suchbegriff eingeben...":
self.path_entry.delete(0, tk.END)
def execute_search(self, event):
"""Execute search when Enter is pressed in search mode"""
search_term = self.path_entry.get().strip()
if not search_term or search_term == "Suchbegriff eingeben...":
return
# Clear previous search results
self.search_results.clear()
# Determine search directories
search_dirs = [self.current_dir]
# If searching from home directory, also include XDG directories
home_dir = os.path.expanduser("~")
if os.path.abspath(self.current_dir) == os.path.abspath(home_dir):
xdg_dirs = [
get_xdg_user_dir("XDG_DOWNLOAD_DIR", "Downloads"),
get_xdg_user_dir("XDG_DOCUMENTS_DIR", "Documents"),
get_xdg_user_dir("XDG_PICTURES_DIR", "Pictures"),
get_xdg_user_dir("XDG_MUSIC_DIR", "Music"),
get_xdg_user_dir("XDG_VIDEO_DIR", "Videos")
]
# Add XDG directories that exist and are not already in home
for xdg_dir in xdg_dirs:
if (os.path.exists(xdg_dir) and
os.path.abspath(xdg_dir) != os.path.abspath(home_dir) and
xdg_dir not in search_dirs):
search_dirs.append(xdg_dir)
try:
all_files = []
# Search in each directory
for search_dir in search_dirs:
if not os.path.exists(search_dir):
continue
# Change to directory and use relative paths to avoid path issues
original_cwd = os.getcwd()
try:
os.chdir(search_dir)
# Build find command based on recursive setting (use . for current directory)
if self.recursive_search.get():
find_cmd = ['find', '.', '-iname',
f'*{search_term}*', '-type', 'f']
else:
find_cmd = ['find', '.', '-maxdepth', '1',
'-iname', f'*{search_term}*', '-type', 'f']
result = subprocess.run(
find_cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
files = result.stdout.strip().split('\n')
# Convert relative paths back to absolute paths
directory_files = []
for f in files:
if f and f.startswith('./'):
abs_path = os.path.join(
search_dir, f[2:]) # Remove './' prefix
if os.path.isfile(abs_path):
directory_files.append(abs_path)
all_files.extend(directory_files)
finally:
os.chdir(original_cwd)
# Remove duplicates while preserving order
seen = set()
unique_files = []
for file_path in all_files:
if file_path not in seen:
seen.add(file_path)
unique_files.append(file_path)
# Filter based on currently selected filter pattern
self.search_results = []
for file_path in unique_files:
filename = os.path.basename(file_path)
if self._matches_filetype(filename):
self.search_results.append(file_path)
# Show search results in TreeView
if self.search_results:
self.show_search_results_treeview()
else:
MessageDialog(
message_type="info",
text=f"Keine Dateien mit '{search_term}' gefunden.",
title="Suche",
master=self
).show()
except subprocess.TimeoutExpired:
MessageDialog(
message_type="error",
text="Suche dauert zu lange und wurde abgebrochen.",
title="Suche",
master=self
).show()
except Exception as e:
MessageDialog(
message_type="error",
text=f"Fehler bei der Suche: {e}",
title="Suchfehler",
master=self
).show()
def show_search_results_treeview(self):
"""Show search results in TreeView format"""
# Clear current file list and replace with search results
for widget in self.file_list_frame.winfo_children():
widget.destroy()
# Create TreeView for search results
tree_frame = ttk.Frame(self.file_list_frame)
tree_frame.pack(fill='both', expand=True)
tree_frame.grid_rowconfigure(0, weight=1)
tree_frame.grid_columnconfigure(0, weight=1)
columns = ("path", "size", "modified")
search_tree = ttk.Treeview(
tree_frame, columns=columns, show="tree headings")
# Configure columns
search_tree.heading("#0", text="Dateiname", anchor="w")
search_tree.column("#0", anchor="w", width=200, stretch=True)
search_tree.heading("path", text="Pfad", anchor="w")
search_tree.column("path", anchor="w", width=300, stretch=True)
search_tree.heading("size", text="Größe", anchor="e")
search_tree.column("size", anchor="e", width=100, stretch=False)
search_tree.heading("modified", text="Geändert am", anchor="w")
search_tree.column("modified", anchor="w", width=160, stretch=False)
# Add scrollbars
v_scrollbar = ttk.Scrollbar(
tree_frame, orient="vertical", command=search_tree.yview)
h_scrollbar = ttk.Scrollbar(
tree_frame, orient="horizontal", command=search_tree.xview)
search_tree.configure(yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set)
search_tree.grid(row=0, column=0, sticky='nsew')
v_scrollbar.grid(row=0, column=1, sticky='ns')
h_scrollbar.grid(row=1, column=0, sticky='ew')
# Populate with search results
for file_path in self.search_results:
try:
filename = os.path.basename(file_path)
directory = os.path.dirname(file_path)
stat = os.stat(file_path)
size = self._format_size(stat.st_size)
modified_time = datetime.fromtimestamp(
stat.st_mtime).strftime('%d.%m.%Y %H:%M')
icon = self.get_file_icon(filename, 'small')
search_tree.insert("", "end", text=f" {filename}", image=icon,
values=(directory, size, modified_time))
except (FileNotFoundError, PermissionError):
continue
# Bind double-click to select file
def on_search_double_click(event):
selection = search_tree.selection()
if selection:
item = search_tree.item(selection[0])
filename = item['text'].strip()
directory = item['values'][0]
full_path = os.path.join(directory, filename)
# Select the file and close dialog
self.selected_file = full_path
self.destroy()
search_tree.bind("<Double-1>", on_search_double_click)
def _unbind_mouse_wheel_events(self):
# Unbind all mouse wheel events from the root window
self.unbind_all("<MouseWheel>")
@@ -811,12 +1115,9 @@ class CustomFileDialog(tk.Toplevel):
name = block_device.get('name')
mountpoint = block_device.get('mountpoint')
label = block_device.get('label')
# size = block_device.get('size')
removable = block_device.get('rm', False)
display_name = label if label else name
# if size:
# display_name += f" ({size})"
devices.append((display_name, mountpoint, removable))
# Process children (partitions)
@@ -833,12 +1134,9 @@ class CustomFileDialog(tk.Toplevel):
name = child_device.get('name')
mountpoint = child_device.get('mountpoint')
label = child_device.get('label')
# size = child_device.get('size')
removable = child_device.get('rm', False)
display_name = label if label else name
# if size:
# display_name += f" ({size})"
devices.append(
(display_name, mountpoint, removable))