commit 67
This commit is contained in:
BIN
__pycache__/cfd_animated_icon.cpython-312.pyc
Normal file
BIN
__pycache__/cfd_animated_icon.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
82
cfd_animated_icon.py
Normal file
82
cfd_animated_icon.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
import math
|
||||||
|
|
||||||
|
class AnimatedSearchIcon(tk.Canvas):
|
||||||
|
def __init__(self, parent, bg_color, style="single", *args, **kwargs):
|
||||||
|
kwargs.setdefault('width', 22)
|
||||||
|
kwargs.setdefault('height', 22)
|
||||||
|
super().__init__(parent, *args, **kwargs)
|
||||||
|
self.configure(bg=bg_color, highlightthickness=0)
|
||||||
|
self.width = self.winfo_reqwidth()
|
||||||
|
self.height = self.winfo_reqheight()
|
||||||
|
|
||||||
|
self.angle1 = 0
|
||||||
|
self.angle2 = 0
|
||||||
|
self.color_angle = 0
|
||||||
|
self.base_color = (81, 149, 255) # #5195ff
|
||||||
|
self.is_animating = False
|
||||||
|
self.style = style
|
||||||
|
|
||||||
|
self.draw_initial_state()
|
||||||
|
|
||||||
|
def start_animation(self):
|
||||||
|
if self.is_animating:
|
||||||
|
return
|
||||||
|
self.is_animating = True
|
||||||
|
self.update_animation()
|
||||||
|
|
||||||
|
def stop_animation(self):
|
||||||
|
self.is_animating = False
|
||||||
|
self.draw_initial_state()
|
||||||
|
|
||||||
|
def update_animation(self):
|
||||||
|
if not self.is_animating:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.style == "single":
|
||||||
|
self.angle1 = (self.angle1 - 6) % 360
|
||||||
|
elif self.style == "double":
|
||||||
|
self.angle1 = (self.angle1 - 6) % 360
|
||||||
|
self.angle2 = (self.angle2 + 6) % 360
|
||||||
|
|
||||||
|
self.color_angle = (self.color_angle + 0.15) % (2 * math.pi)
|
||||||
|
self.draw_animated_arc()
|
||||||
|
self.after(25, self.update_animation)
|
||||||
|
|
||||||
|
def get_pulsating_color(self):
|
||||||
|
factor = 0.5 * (1 + math.sin(self.color_angle))
|
||||||
|
r = int(self.base_color[0] + (255 - self.base_color[0]) * factor * 0.6)
|
||||||
|
g = int(self.base_color[1] + (255 - self.base_color[1]) * factor * 0.6)
|
||||||
|
b = int(self.base_color[2] + (255 - self.base_color[2]) * factor * 0.6)
|
||||||
|
return f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
def draw_animated_arc(self):
|
||||||
|
self.delete("all")
|
||||||
|
color = self.get_pulsating_color()
|
||||||
|
if self.style == "single":
|
||||||
|
x0, y0 = 3, 3
|
||||||
|
x1, y1 = self.width - 3, self.height - 3
|
||||||
|
self.create_arc(x0, y0, x1, y1, start=self.angle1, extent=300, style=tk.ARC, width=4, outline=color)
|
||||||
|
elif self.style == "double":
|
||||||
|
# Outer arc
|
||||||
|
x0_outer, y0_outer = 3, 3
|
||||||
|
x1_outer, y1_outer = self.width - 3, self.height - 3
|
||||||
|
self.create_arc(x0_outer, y0_outer, x1_outer, y1_outer, start=self.angle1, extent=150, style=tk.ARC, width=2, outline=color)
|
||||||
|
# Inner arc
|
||||||
|
x0_inner, y0_inner = 7, 7
|
||||||
|
x1_inner, y1_inner = self.width - 7, self.height - 7
|
||||||
|
self.create_arc(x0_inner, y0_inner, x1_inner, y1_inner, start=self.angle2, extent=150, style=tk.ARC, width=2, outline=color)
|
||||||
|
|
||||||
|
def draw_initial_state(self):
|
||||||
|
self.delete("all")
|
||||||
|
if self.style == "single":
|
||||||
|
x0, y0 = 3, 3
|
||||||
|
x1, y1 = self.width - 3, self.height - 3
|
||||||
|
self.create_oval(x0, y0, x1, y1, outline="#5195ff", width=4, fill=self.cget("bg"))
|
||||||
|
elif self.style == "double":
|
||||||
|
x0_outer, y0_outer = 3, 3
|
||||||
|
x1_outer, y1_outer = self.width - 3, self.height - 3
|
||||||
|
self.create_oval(x0_outer, y0_outer, x1_outer, y1_outer, outline="#5195ff", width=2, fill=self.cget("bg"))
|
||||||
|
x0_inner, y0_inner = 7, 7
|
||||||
|
x1_inner, y1_inner = self.width - 7, self.height - 7
|
||||||
|
self.create_oval(x0_inner, y0_inner, x1_inner, y1_inner, outline="#5195ff", width=2, fill=self.cget("bg"))
|
||||||
@@ -3,6 +3,7 @@ import shutil
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from shared_libs.common_tools import Tooltip
|
from shared_libs.common_tools import Tooltip
|
||||||
|
from cfd_animated_icon import AnimatedSearchIcon
|
||||||
|
|
||||||
|
|
||||||
def get_xdg_user_dir(dir_key, fallback_name):
|
def get_xdg_user_dir(dir_key, fallback_name):
|
||||||
@@ -357,6 +358,15 @@ class WidgetManager:
|
|||||||
self.settings_button = ttk.Button(self.action_status_frame, image=self.dialog.icon_manager.get_icon('settings-2_small'),
|
self.settings_button = ttk.Button(self.action_status_frame, image=self.dialog.icon_manager.get_icon('settings-2_small'),
|
||||||
command=self.dialog.open_settings_dialog, style="Bottom.TButton.Borderless.Round")
|
command=self.dialog.open_settings_dialog, style="Bottom.TButton.Borderless.Round")
|
||||||
|
|
||||||
|
self.search_animation = AnimatedSearchIcon(self.status_container,
|
||||||
|
bg_color=self.style_manager.bottom_color,
|
||||||
|
style="double",
|
||||||
|
width=23, height=23)
|
||||||
|
self.search_animation.grid(row=0, column=0, sticky='w', padx=(0, 5), pady=(4,0))
|
||||||
|
self.search_animation.bind("<Button-1>", lambda e: self.dialog.activate_search())
|
||||||
|
self.search_status_label.grid(row=0, column=1, sticky="w")
|
||||||
|
|
||||||
|
|
||||||
button_box_pos = self.settings.get("button_box_pos", "left")
|
button_box_pos = self.settings.get("button_box_pos", "left")
|
||||||
|
|
||||||
if self.dialog.dialog_mode == "save":
|
if self.dialog.dialog_mode == "save":
|
||||||
@@ -417,7 +427,7 @@ class WidgetManager:
|
|||||||
self.center_container.grid_columnconfigure(1, weight=1)
|
self.center_container.grid_columnconfigure(1, weight=1)
|
||||||
self.filter_combobox.grid(in_=self.center_container, row=1, column=1, sticky="e", pady=(5, 0))
|
self.filter_combobox.grid(in_=self.center_container, row=1, column=1, sticky="e", pady=(5, 0))
|
||||||
|
|
||||||
self.search_status_label.grid(row=0, column=0, sticky="w", pady=(5, 0), padx=(5, 0))
|
#self.search_status_label.grid(row=0, column=0, sticky="w", pady=(5, 0), padx=(5, 0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from tkinter import ttk
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
import threading
|
||||||
from shared_libs.message import MessageDialog
|
from shared_libs.message import MessageDialog
|
||||||
from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools
|
from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools
|
||||||
from cfd_app_config import AppConfig, CfdConfigManager
|
from cfd_app_config import AppConfig, CfdConfigManager
|
||||||
@@ -201,6 +202,8 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
self.item_path_map = {}
|
self.item_path_map = {}
|
||||||
self.responsive_buttons_hidden = None # State for responsive buttons
|
self.responsive_buttons_hidden = None # State for responsive buttons
|
||||||
self.search_job = None
|
self.search_job = None
|
||||||
|
self.search_thread = None
|
||||||
|
self.search_process = None
|
||||||
|
|
||||||
self.icon_manager = IconManager()
|
self.icon_manager = IconManager()
|
||||||
self.style_manager = StyleManager(self)
|
self.style_manager = StyleManager(self)
|
||||||
@@ -235,34 +238,112 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def activate_search(self, event=None):
|
||||||
|
"""Activates the search entry or cancels an ongoing search."""
|
||||||
|
if self.widget_manager.search_animation.is_animating:
|
||||||
|
# If animating, it means a search is active, so cancel it
|
||||||
|
if self.search_thread and self.search_thread.is_alive():
|
||||||
|
if self.search_process:
|
||||||
|
try:
|
||||||
|
os.killpg(os.getpgid(self.search_process.pid), 9) # Send SIGKILL to process group
|
||||||
|
except (ProcessLookupError, AttributeError):
|
||||||
|
pass # Process might have already finished or not started
|
||||||
|
self.widget_manager.search_animation.stop_animation()
|
||||||
|
self.widget_manager.search_status_label.config(text="Suche abgebrochen.")
|
||||||
|
self.hide_search_bar() # Reset UI after cancellation
|
||||||
|
else:
|
||||||
|
# If not animating, activate search entry
|
||||||
|
self.widget_manager.filename_entry.focus_set()
|
||||||
|
self.search_mode = True # Ensure search mode is active
|
||||||
|
self.widget_manager.filename_entry.bind("<Return>", self.execute_search)
|
||||||
|
self.widget_manager.filename_entry.bind("<Escape>", self.hide_search_bar)
|
||||||
|
|
||||||
def show_search_bar(self, event=None):
|
def show_search_bar(self, event=None):
|
||||||
# Ignore key presses if they are coming from an entry widget or have no character
|
|
||||||
if isinstance(event.widget, (ttk.Entry, tk.Entry)) or not event.char.strip():
|
if isinstance(event.widget, (ttk.Entry, tk.Entry)) or not event.char.strip():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.search_mode = True
|
self.search_mode = True
|
||||||
self.widget_manager.filename_entry.focus_set()
|
self.widget_manager.filename_entry.focus_set()
|
||||||
# Clear the field before inserting the new character to start a fresh search
|
|
||||||
self.widget_manager.filename_entry.delete(0, tk.END)
|
self.widget_manager.filename_entry.delete(0, tk.END)
|
||||||
self.widget_manager.filename_entry.insert(0, event.char)
|
self.widget_manager.filename_entry.insert(0, event.char)
|
||||||
self.widget_manager.filename_entry.bind("<Return>", self.execute_search)
|
self.widget_manager.filename_entry.bind("<Return>", self.execute_search)
|
||||||
self.widget_manager.filename_entry.bind("<Escape>", self.hide_search_bar)
|
self.widget_manager.filename_entry.bind("<Escape>", self.hide_search_bar)
|
||||||
|
# Removed: self.widget_manager.search_animation.start_animation()
|
||||||
|
|
||||||
def hide_search_bar(self, event=None):
|
def hide_search_bar(self, event=None):
|
||||||
self.search_mode = False
|
self.search_mode = False
|
||||||
self.widget_manager.filename_entry.delete(0, tk.END)
|
self.widget_manager.filename_entry.delete(0, tk.END)
|
||||||
self.widget_manager.search_status_label.config(text="")
|
self.widget_manager.search_status_label.config(text="")
|
||||||
# Unbind search-specific events to restore normal behavior
|
|
||||||
self.widget_manager.filename_entry.unbind("<Return>")
|
self.widget_manager.filename_entry.unbind("<Return>")
|
||||||
self.widget_manager.filename_entry.unbind("<Escape>")
|
self.widget_manager.filename_entry.unbind("<Escape>")
|
||||||
# Re-bind the default save action for the save dialog
|
|
||||||
if self.dialog_mode == "save":
|
if self.dialog_mode == "save":
|
||||||
self.widget_manager.filename_entry.bind("<Return>", lambda e: self.on_save())
|
self.widget_manager.filename_entry.bind("<Return>", lambda e: self.on_save())
|
||||||
self.populate_files()
|
self.populate_files()
|
||||||
|
self.widget_manager.search_animation.stop_animation()
|
||||||
|
|
||||||
def toggle_search_mode(self, event=None):
|
def execute_search(self, event=None):
|
||||||
# This method might not be needed anymore if search is always active in the entry
|
if self.search_thread and self.search_thread.is_alive():
|
||||||
pass
|
return
|
||||||
|
search_term = self.widget_manager.filename_entry.get().strip()
|
||||||
|
if not search_term:
|
||||||
|
self.hide_search_bar()
|
||||||
|
return
|
||||||
|
self.widget_manager.search_status_label.config(text=f"Suche nach '{search_term}'...")
|
||||||
|
self.widget_manager.search_animation.start_animation()
|
||||||
|
self.update_idletasks()
|
||||||
|
self.search_thread = threading.Thread(target=self._perform_search_in_thread, args=(search_term,))
|
||||||
|
self.search_thread.start()
|
||||||
|
|
||||||
|
def _perform_search_in_thread(self, search_term):
|
||||||
|
self.search_results.clear()
|
||||||
|
search_dirs = [self.current_dir]
|
||||||
|
home_dir = os.path.expanduser("~")
|
||||||
|
if os.path.abspath(self.current_dir) == os.path.abspath(home_dir):
|
||||||
|
xdg_dirs = [get_xdg_user_dir(d, f) for d, f in [("XDG_DOWNLOAD_DIR", "Downloads"), ("XDG_DOCUMENTS_DIR", "Documents"), ("XDG_PICTURES_DIR", "Pictures"), ("XDG_MUSIC_DIR", "Music"), ("XDG_VIDEO_DIR", "Videos")]]
|
||||||
|
search_dirs.extend([d for d in xdg_dirs if os.path.exists(d) and os.path.abspath(d) != home_dir and d not in search_dirs])
|
||||||
|
|
||||||
|
try:
|
||||||
|
all_files = []
|
||||||
|
for search_dir in search_dirs:
|
||||||
|
if not (self.search_thread and self.search_thread.is_alive()): break
|
||||||
|
if not os.path.exists(search_dir): continue
|
||||||
|
original_cwd = os.getcwd()
|
||||||
|
try:
|
||||||
|
os.chdir(search_dir)
|
||||||
|
cmd = ['find', '-L', '.', '-iname', f'*{search_term}*']
|
||||||
|
if not self.settings.get("recursive_search", True):
|
||||||
|
cmd.insert(3, '-maxdepth')
|
||||||
|
cmd.insert(4, '1')
|
||||||
|
self.search_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, preexec_fn=os.setsid)
|
||||||
|
stdout, _ = self.search_process.communicate()
|
||||||
|
if self.search_process.returncode == 0:
|
||||||
|
all_files.extend([os.path.join(search_dir, f[2:]) for f in stdout.strip().split('\n') if f and f.startswith('./') and os.path.exists(os.path.join(search_dir, f[2:]))])
|
||||||
|
finally:
|
||||||
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
|
if not (self.search_thread and self.search_thread.is_alive()): raise subprocess.SubprocessError("Search cancelled by user")
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
unique_files = [x for x in all_files if not (x in seen or seen.add(x))]
|
||||||
|
search_hidden = self.settings.get("search_hidden_files", False)
|
||||||
|
self.search_results = [p for p in unique_files if (search_hidden or not any(part.startswith('.') for part in p.split(os.sep))) and (self._matches_filetype(os.path.basename(p)) or os.path.isdir(p))]
|
||||||
|
|
||||||
|
def update_ui():
|
||||||
|
if self.search_results:
|
||||||
|
self.show_search_results_treeview()
|
||||||
|
folder_count = sum(1 for p in self.search_results if os.path.isdir(p))
|
||||||
|
file_count = len(self.search_results) - folder_count
|
||||||
|
self.widget_manager.search_status_label.config(text=f"{folder_count} Ordner und {file_count} Dateien gefunden.")
|
||||||
|
else:
|
||||||
|
self.widget_manager.search_status_label.config(text=f"Keine Ergebnisse für '{search_term}'.")
|
||||||
|
self.after(0, update_ui)
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, subprocess.SubprocessError):
|
||||||
|
self.after(0, lambda: self.widget_manager.search_status_label.config(text="Suche abgebrochen."))
|
||||||
|
else:
|
||||||
|
self.after(0, lambda: MessageDialog(message_type="error", text=f"Fehler bei der Suche: {e}", title="Suchfehler", master=self).show())
|
||||||
|
finally:
|
||||||
|
self.after(0, self.widget_manager.search_animation.stop_animation)
|
||||||
|
self.search_process = None
|
||||||
|
|
||||||
def handle_path_entry_return(self, event):
|
def handle_path_entry_return(self, event):
|
||||||
"""Handles the Enter key in the path entry to navigate.
|
"""Handles the Enter key in the path entry to navigate.
|
||||||
@@ -313,7 +394,7 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
|
|
||||||
# If search was active, reset it to avoid inconsistent state
|
# If search was active, reset it to avoid inconsistent state
|
||||||
if self.search_mode:
|
if self.search_mode:
|
||||||
self.toggle_search_mode() # This will correctly reset the UI
|
self.hide_search_bar() # This will correctly reset the UI
|
||||||
|
|
||||||
self.navigate_to(self.current_dir)
|
self.navigate_to(self.current_dir)
|
||||||
|
|
||||||
@@ -507,134 +588,6 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def execute_search(self, event=None):
|
|
||||||
search_term = self.widget_manager.filename_entry.get().strip()
|
|
||||||
|
|
||||||
if not search_term:
|
|
||||||
self.hide_search_bar()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.widget_manager.search_status_label.config(text=f"Suche nach '{search_term}'...")
|
|
||||||
self.update_idletasks()
|
|
||||||
|
|
||||||
# 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.settings.get("recursive_search", True):
|
|
||||||
# Find both files and directories, following symlinks
|
|
||||||
find_cmd = ['find', '-L', '.', '-iname', f'*{search_term}*']
|
|
||||||
else:
|
|
||||||
# Find both files and directories, but only in the current level, following symlinks
|
|
||||||
find_cmd = ['find', '-L', '.', '-maxdepth', '1',
|
|
||||||
'-iname', f'*{search_term}*']
|
|
||||||
|
|
||||||
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
|
|
||||||
# Check if the path exists, as it might be a broken symlink or deleted
|
|
||||||
if os.path.exists(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 and hidden file setting
|
|
||||||
self.search_results = []
|
|
||||||
search_hidden = self.settings.get("search_hidden_files", False)
|
|
||||||
|
|
||||||
for file_path in unique_files:
|
|
||||||
# Check if path contains a hidden component (e.g., /.config/ or /some/path/to/.hidden_file)
|
|
||||||
if not search_hidden:
|
|
||||||
if any(part.startswith('.') for part in file_path.split(os.sep)):
|
|
||||||
continue # Skip hidden files/files in hidden directories
|
|
||||||
|
|
||||||
# Check if the path exists (it might have been deleted during the search)
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
filename = os.path.basename(file_path)
|
|
||||||
if self._matches_filetype(filename) or os.path.isdir(file_path):
|
|
||||||
self.search_results.append(file_path)
|
|
||||||
|
|
||||||
# Show search results in TreeView
|
|
||||||
if self.search_results:
|
|
||||||
self.show_search_results_treeview()
|
|
||||||
folder_count = sum(1 for p in self.search_results if os.path.isdir(p))
|
|
||||||
file_count = len(self.search_results) - folder_count
|
|
||||||
self.widget_manager.search_status_label.config(text=f"{folder_count} Ordner und {file_count} Dateien gefunden.")
|
|
||||||
else:
|
|
||||||
self.widget_manager.search_status_label.config(text=f"Keine Ergebnisse für '{search_term}'.")
|
|
||||||
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):
|
def show_search_results_treeview(self):
|
||||||
"""Show search results in TreeView format"""
|
"""Show search results in TreeView format"""
|
||||||
# Clear current file list and replace with search results
|
# Clear current file list and replace with search results
|
||||||
@@ -969,13 +922,13 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
Tooltip(item_frame, name)
|
Tooltip(item_frame, name)
|
||||||
|
|
||||||
for widget in [item_frame, icon_label, name_label]:
|
for widget in [item_frame, icon_label, name_label]:
|
||||||
widget.bind("<Double-Button-1>", lambda e,
|
widget.bind("<Double-Button-1>", lambda e,
|
||||||
p=path: self.on_item_double_click(p))
|
p=path: self.on_item_double_click(p))
|
||||||
widget.bind("<Button-1>", lambda e, p=path,
|
widget.bind("<Button-1>", lambda e, p=path,
|
||||||
f=item_frame: self.on_item_select(p, f))
|
f=item_frame: self.on_item_select(p, f))
|
||||||
widget.bind("<ButtonRelease-3>", lambda e,
|
widget.bind("<ButtonRelease-3>", lambda e,
|
||||||
p=path: self._show_context_menu(e, p))
|
p=path: self._show_context_menu(e, p))
|
||||||
widget.bind("<F2>", lambda e, p=path,
|
widget.bind("<F2>", lambda e, p=path,
|
||||||
f=item_frame: self.on_rename_request(e, p, f))
|
f=item_frame: self.on_rename_request(e, p, f))
|
||||||
|
|
||||||
if name == item_to_select:
|
if name == item_to_select:
|
||||||
@@ -1387,7 +1340,7 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
filename = os.path.basename(file_path)
|
filename = os.path.basename(file_path)
|
||||||
|
|
||||||
if self.search_mode:
|
if self.search_mode:
|
||||||
self.toggle_search_mode()
|
self.hide_search_bar()
|
||||||
|
|
||||||
self.navigate_to(directory)
|
self.navigate_to(directory)
|
||||||
self.after(100, lambda: self._select_file_in_view(filename))
|
self.after(100, lambda: self._select_file_in_view(filename))
|
||||||
@@ -1578,4 +1531,4 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting mounted devices: {e}")
|
print(f"Error getting mounted devices: {e}")
|
||||||
return devices
|
return devices
|
||||||
@@ -55,7 +55,7 @@ if __name__ == "__main__":
|
|||||||
style = ttk.Style(root)
|
style = ttk.Style(root)
|
||||||
root.tk.call('source', f"{theme_path}/water.tcl")
|
root.tk.call('source', f"{theme_path}/water.tcl")
|
||||||
try:
|
try:
|
||||||
root.tk.call('set_theme', 'dark')
|
root.tk.call('set_theme', 'light')
|
||||||
except tk.TclError:
|
except tk.TclError:
|
||||||
pass
|
pass
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|||||||
Reference in New Issue
Block a user