The `CustomFileDialog` class had become too large and complex, making it difficult to maintain. This change refactors the monolithic class into several specialized manager classes, each responsible for a specific area of concern: - `SettingsDialog`: Moved to its own file, `cfd_settings_dialog.py`. - `FileOperationsManager`: Handles file/folder creation, deletion, and renaming. - `SearchManager`: Encapsulates all search-related logic. - `NavigationManager`: Manages directory navigation and history. - `ViewManager`: Controls the rendering of file and folder views. The main `CustomFileDialog` class has been streamlined and now acts as an orchestrator for these managers. This improves readability, separation of concerns, and the overall maintainability of the code.
232 lines
9.1 KiB
Python
232 lines
9.1 KiB
Python
import os
|
|
import shutil
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
|
|
try:
|
|
import send2trash
|
|
SEND2TRASH_AVAILABLE = True
|
|
except ImportError:
|
|
SEND2TRASH_AVAILABLE = False
|
|
|
|
from shared_libs.message import MessageDialog
|
|
|
|
|
|
class FileOperationsManager:
|
|
def __init__(self, dialog):
|
|
self.dialog = dialog
|
|
|
|
def delete_selected_item(self, event=None):
|
|
"""Deletes or moves the selected item to trash based on settings."""
|
|
if not self.dialog.selected_file or not os.path.exists(self.dialog.selected_file):
|
|
return
|
|
|
|
use_trash = self.dialog.settings.get(
|
|
"use_trash", False) and SEND2TRASH_AVAILABLE
|
|
confirm = self.dialog.settings.get("confirm_delete", False)
|
|
|
|
action_text = "in den Papierkorb verschieben" if use_trash else "endgültig löschen"
|
|
item_name = os.path.basename(self.dialog.selected_file)
|
|
|
|
if not confirm:
|
|
dialog = MessageDialog(
|
|
master=self.dialog,
|
|
title="Bestätigung erforderlich",
|
|
text=f"Möchten Sie '{item_name}' wirklich {action_text}?",
|
|
message_type="question"
|
|
)
|
|
if not dialog.show():
|
|
return
|
|
|
|
try:
|
|
if use_trash:
|
|
send2trash.send2trash(self.dialog.selected_file)
|
|
else:
|
|
if os.path.isdir(self.dialog.selected_file):
|
|
shutil.rmtree(self.dialog.selected_file)
|
|
else:
|
|
os.remove(self.dialog.selected_file)
|
|
|
|
self.dialog.view_manager.populate_files()
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"'{item_name}' wurde erfolgreich entfernt.")
|
|
|
|
except Exception as e:
|
|
MessageDialog(
|
|
master=self.dialog,
|
|
title="Fehler",
|
|
text=f"Fehler beim Entfernen von '{item_name}':\n{e}",
|
|
message_type="error"
|
|
).show()
|
|
|
|
def create_new_folder(self):
|
|
self._create_new_item(is_folder=True)
|
|
|
|
def create_new_file(self):
|
|
self._create_new_item(is_folder=False)
|
|
|
|
def _create_new_item(self, is_folder):
|
|
base_name = "Neuer Ordner" if is_folder else "Neues Dokument.txt"
|
|
new_name = self._get_unique_name(base_name)
|
|
new_path = os.path.join(self.dialog.current_dir, new_name)
|
|
|
|
try:
|
|
if is_folder:
|
|
os.mkdir(new_path)
|
|
else:
|
|
open(new_path, 'a').close()
|
|
self.dialog.view_manager.populate_files(item_to_rename=new_name)
|
|
except Exception as e:
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"Fehler beim Erstellen: {e}")
|
|
|
|
def _get_unique_name(self, base_name):
|
|
name, ext = os.path.splitext(base_name)
|
|
counter = 1
|
|
new_name = base_name
|
|
while os.path.exists(os.path.join(self.dialog.current_dir, new_name)):
|
|
counter += 1
|
|
new_name = f"{name} {counter}{ext}"
|
|
return new_name
|
|
|
|
def _copy_to_clipboard(self, data):
|
|
self.dialog.clipboard_clear()
|
|
self.dialog.clipboard_append(data)
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"'{self.dialog.shorten_text(data, 50)}' in Zwischenablage kopiert.")
|
|
|
|
def _show_context_menu(self, event, item_path):
|
|
if not item_path:
|
|
return "break"
|
|
|
|
if hasattr(self.dialog, 'context_menu') and self.dialog.context_menu.winfo_exists():
|
|
self.dialog.context_menu.destroy()
|
|
|
|
self.dialog.context_menu = tk.Menu(self.dialog, tearoff=0, background=self.dialog.style_manager.header, foreground=self.dialog.style_manager.color_foreground,
|
|
activebackground=self.dialog.style_manager.selection_color, activeforeground=self.dialog.style_manager.color_foreground, relief='flat', borderwidth=0)
|
|
|
|
self.dialog.context_menu.add_command(label="Dateiname in Zwischenablage",
|
|
command=lambda: self._copy_to_clipboard(os.path.basename(item_path)))
|
|
self.dialog.context_menu.add_command(
|
|
label="Pfad in Zwischenablage", command=lambda: self._copy_to_clipboard(item_path))
|
|
|
|
self.dialog.context_menu.add_separator()
|
|
self.dialog.context_menu.add_command(
|
|
label="Speicherort öffnen", command=lambda: self._open_file_location_from_context(item_path))
|
|
|
|
self.dialog.context_menu.tk_popup(event.x_root, event.y_root)
|
|
return "break"
|
|
|
|
def _open_file_location_from_context(self, file_path):
|
|
directory = os.path.dirname(file_path)
|
|
filename = os.path.basename(file_path)
|
|
|
|
if self.dialog.search_mode:
|
|
self.dialog.search_manager.hide_search_bar()
|
|
|
|
self.dialog.navigation_manager.navigate_to(directory)
|
|
self.dialog.after(100, lambda: self.dialog.view_manager._select_file_in_view(filename))
|
|
|
|
def on_rename_request(self, event, item_path=None, item_frame=None):
|
|
if self.dialog.view_mode.get() == "list":
|
|
if not self.dialog.tree.selection():
|
|
return
|
|
item_id = self.dialog.tree.selection()[0]
|
|
item_path = os.path.join(
|
|
self.dialog.current_dir, self.dialog.tree.item(item_id, "text").strip())
|
|
self.start_rename(item_id, item_path)
|
|
else: # icon view
|
|
if item_path and item_frame:
|
|
self.start_rename(item_frame, item_path)
|
|
|
|
def start_rename(self, item_widget, item_path):
|
|
if self.dialog.view_mode.get() == "icons":
|
|
self._start_rename_icon_view(item_widget, item_path)
|
|
else: # list view
|
|
self._start_rename_list_view(item_widget) # item_widget is item_id
|
|
|
|
def _start_rename_icon_view(self, item_frame, item_path):
|
|
for child in item_frame.winfo_children():
|
|
child.destroy()
|
|
|
|
entry = ttk.Entry(item_frame)
|
|
entry.insert(0, os.path.basename(item_path))
|
|
entry.select_range(0, tk.END)
|
|
entry.pack(fill="x", expand=True, padx=5, pady=5)
|
|
entry.focus_set()
|
|
|
|
def finish_rename(event):
|
|
new_name = entry.get()
|
|
new_path = os.path.join(self.dialog.current_dir, new_name)
|
|
if new_name and new_path != item_path:
|
|
if os.path.exists(new_path):
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"'{new_name}' existiert bereits.")
|
|
self.dialog.view_manager.populate_files(
|
|
item_to_select=os.path.basename(item_path))
|
|
return
|
|
try:
|
|
os.rename(item_path, new_path)
|
|
self.dialog.view_manager.populate_files(item_to_select=new_name)
|
|
except Exception as e:
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"Fehler beim Umbenennen: {e}")
|
|
self.dialog.view_manager.populate_files()
|
|
else:
|
|
self.dialog.populate_files(item_to_select=os.path.basename(item_path))
|
|
|
|
def cancel_rename(event):
|
|
self.dialog.view_manager.populate_files()
|
|
|
|
entry.bind("<Return>", finish_rename)
|
|
entry.bind("<FocusOut>", finish_rename)
|
|
entry.bind("<Escape>", cancel_rename)
|
|
|
|
def _start_rename_list_view(self, item_id):
|
|
self.dialog.tree.see(item_id)
|
|
self.dialog.tree.update_idletasks()
|
|
|
|
bbox = self.dialog.tree.bbox(item_id, column="#0")
|
|
|
|
if not bbox:
|
|
return
|
|
|
|
x, y, width, height = bbox
|
|
entry = ttk.Entry(self.dialog.tree)
|
|
entry_width = self.dialog.tree.column("#0", "width")
|
|
entry.place(x=x, y=y, width=entry_width, height=height)
|
|
|
|
item_text = self.dialog.tree.item(item_id, "text").strip()
|
|
entry.insert(0, item_text)
|
|
entry.select_range(0, tk.END)
|
|
entry.focus_set()
|
|
|
|
def finish_rename(event):
|
|
new_name = entry.get()
|
|
old_path = os.path.join(self.dialog.current_dir, item_text)
|
|
new_path = os.path.join(self.dialog.current_dir, new_name)
|
|
|
|
if new_name and new_path != old_path:
|
|
if os.path.exists(new_path):
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"'{new_name}' existiert bereits.")
|
|
self.dialog.view_manager.populate_files(item_to_select=item_text)
|
|
else:
|
|
try:
|
|
os.rename(old_path, new_path)
|
|
self.dialog.view_manager.populate_files(item_to_select=new_name)
|
|
except Exception as e:
|
|
self.dialog.widget_manager.search_status_label.config(
|
|
text=f"Fehler beim Umbenennen: {e}")
|
|
self.dialog.view_manager.populate_files()
|
|
else:
|
|
self.dialog.populate_files(item_to_select=item_text)
|
|
entry.destroy()
|
|
|
|
def cancel_rename(event):
|
|
entry.destroy()
|
|
|
|
entry.bind("<Return>", finish_rename)
|
|
entry.bind("<FocusOut>", finish_rename)
|
|
entry.bind("<Escape>", cancel_rename)
|