Files
shared_libs/cfd_file_operations.py
Désiré Werner Menrath b8d46fb547 Refactor: Decompose CustomFileDialog class
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.
2025-08-09 11:51:58 +02:00

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)