233 lines
9.4 KiB
Python
233 lines
9.4 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
|
|
from cfd_app_config import LocaleStrings, _
|
|
|
|
|
|
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 = LocaleStrings.FILE["move_to_trash"] if use_trash else LocaleStrings.FILE["delete_permanently"]
|
|
item_name = os.path.basename(self.dialog.selected_file)
|
|
|
|
if not confirm:
|
|
dialog = MessageDialog(
|
|
master=self.dialog,
|
|
title=LocaleStrings.FILE["confirm_delete_title"],
|
|
text=f"{LocaleStrings.FILE['are_you_sure']} '{item_name}' {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}' {LocaleStrings.FILE['was_successfully_removed']}")
|
|
|
|
except Exception as e:
|
|
MessageDialog(
|
|
master=self.dialog,
|
|
title=LocaleStrings.FILE["error_title"],
|
|
text=f"{LocaleStrings.FILE['error_removing']} '{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 = LocaleStrings.FILE["new_folder_title"] if is_folder else LocaleStrings.FILE["new_document_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"{LocaleStrings.FILE['error_creating']}: {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)}' {LocaleStrings.FILE['copied_to_clipboard']}")
|
|
|
|
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=LocaleStrings.UI["copy_filename_to_clipboard"],
|
|
command=lambda: self._copy_to_clipboard(os.path.basename(item_path)))
|
|
self.dialog.context_menu.add_command(
|
|
label=LocaleStrings.UI["copy_path_to_clipboard"], command=lambda: self._copy_to_clipboard(item_path))
|
|
|
|
self.dialog.context_menu.add_separator()
|
|
self.dialog.context_menu.add_command(
|
|
label=LocaleStrings.UI["open_file_location"], 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.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}' {LocaleStrings.FILE['folder_exists_error']}")
|
|
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"{LocaleStrings.FILE['error_renaming']}: {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}' {LocaleStrings.FILE['folder_exists_error']}")
|
|
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"{LocaleStrings.FILE['error_renaming']}: {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)
|
|
|