add dochstring in cfd_file_operations, cfd_navigation_manager, cfd_search_manager, cfd_settings_dialog and fix entryfield on create new folder and new document in iconview
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,11 +14,28 @@ from cfd_app_config import LocaleStrings, _
|
||||
|
||||
|
||||
class FileOperationsManager:
|
||||
"""Manages file operations like delete, create, and rename."""
|
||||
|
||||
def __init__(self, dialog):
|
||||
"""
|
||||
Initializes the FileOperationsManager.
|
||||
|
||||
Args:
|
||||
dialog: The main CustomFileDialog instance.
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def delete_selected_item(self, event=None):
|
||||
"""Deletes or moves the selected item to trash based on settings."""
|
||||
"""
|
||||
Deletes the selected item or moves it to the trash.
|
||||
|
||||
This method checks user settings to determine whether to move the item
|
||||
to the system's trash (if available) or delete it permanently.
|
||||
It also handles the confirmation dialog based on user preferences.
|
||||
|
||||
Args:
|
||||
event: The event that triggered the deletion (optional).
|
||||
"""
|
||||
if not self.dialog.selected_file or not os.path.exists(self.dialog.selected_file):
|
||||
return
|
||||
|
||||
@@ -61,12 +78,22 @@ class FileOperationsManager:
|
||||
).show()
|
||||
|
||||
def create_new_folder(self):
|
||||
"""Creates a new folder in the current directory."""
|
||||
self._create_new_item(is_folder=True)
|
||||
|
||||
def create_new_file(self):
|
||||
"""Creates a new empty file in the current directory."""
|
||||
self._create_new_item(is_folder=False)
|
||||
|
||||
def _create_new_item(self, is_folder):
|
||||
"""
|
||||
Internal helper to create a new file or folder.
|
||||
|
||||
It generates a unique name and creates the item, then refreshes the view.
|
||||
|
||||
Args:
|
||||
is_folder (bool): True to create a folder, False to create a file.
|
||||
"""
|
||||
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)
|
||||
@@ -82,6 +109,18 @@ class FileOperationsManager:
|
||||
text=f"{LocaleStrings.FILE['error_creating']}: {e}")
|
||||
|
||||
def _get_unique_name(self, base_name):
|
||||
"""
|
||||
Generates a unique name for a file or folder.
|
||||
|
||||
If a file or folder with `base_name` already exists, it appends
|
||||
a counter (e.g., "New Folder 2") until a unique name is found.
|
||||
|
||||
Args:
|
||||
base_name (str): The initial name for the item.
|
||||
|
||||
Returns:
|
||||
str: A unique name for the item in the current directory.
|
||||
"""
|
||||
name, ext = os.path.splitext(base_name)
|
||||
counter = 1
|
||||
new_name = base_name
|
||||
@@ -91,12 +130,28 @@ class FileOperationsManager:
|
||||
return new_name
|
||||
|
||||
def _copy_to_clipboard(self, data):
|
||||
"""
|
||||
Copies the given data to the system clipboard.
|
||||
|
||||
Args:
|
||||
data (str): The text to be copied.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Displays a context menu for the selected item.
|
||||
|
||||
Args:
|
||||
event: The mouse event that triggered the menu.
|
||||
item_path (str): The full path to the item.
|
||||
|
||||
Returns:
|
||||
str: "break" to prevent further event propagation.
|
||||
"""
|
||||
if not item_path:
|
||||
return "break"
|
||||
|
||||
@@ -119,6 +174,15 @@ class FileOperationsManager:
|
||||
return "break"
|
||||
|
||||
def _open_file_location_from_context(self, file_path):
|
||||
"""
|
||||
Navigates to the location of the given file path.
|
||||
|
||||
This is used by the context menu to jump to a file's directory,
|
||||
which is especially useful when in search mode.
|
||||
|
||||
Args:
|
||||
file_path (str): The full path to the file.
|
||||
"""
|
||||
directory = os.path.dirname(file_path)
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
@@ -129,6 +193,17 @@ class FileOperationsManager:
|
||||
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):
|
||||
"""
|
||||
Handles the initial request to rename an item.
|
||||
|
||||
This method is triggered by an event (e.g., F2 key press) and
|
||||
initiates the renaming process based on the current view mode.
|
||||
|
||||
Args:
|
||||
event: The event that triggered the rename.
|
||||
item_path (str, optional): The path of the item in icon view.
|
||||
item_frame (tk.Widget, optional): The frame of the item in icon view.
|
||||
"""
|
||||
if self.dialog.view_mode.get() == "list":
|
||||
if not self.dialog.tree.selection():
|
||||
return
|
||||
@@ -141,16 +216,36 @@ class FileOperationsManager:
|
||||
self.start_rename(item_frame, item_path)
|
||||
|
||||
def start_rename(self, item_widget, item_path):
|
||||
"""
|
||||
Starts the renaming UI for an item.
|
||||
|
||||
Dispatches to the appropriate method based on the current view mode.
|
||||
|
||||
Args:
|
||||
item_widget: The widget representing the item (item_id for list view,
|
||||
item_frame for icon view).
|
||||
item_path (str): The full path to the item being renamed.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Initiates the in-place rename UI for an item in icon view.
|
||||
|
||||
It replaces the item's label with an Entry widget.
|
||||
|
||||
Args:
|
||||
item_frame (tk.Widget): The frame containing the item's icon and label.
|
||||
item_path (str): The full path to the item.
|
||||
"""
|
||||
for child in item_frame.winfo_children():
|
||||
child.destroy()
|
||||
|
||||
entry = ttk.Entry(item_frame)
|
||||
entry.pack(fill="both", expand=True, padx=2, pady=20)
|
||||
entry.insert(0, os.path.basename(item_path))
|
||||
entry.select_range(0, tk.END)
|
||||
entry.focus_set()
|
||||
@@ -173,7 +268,7 @@ class FileOperationsManager:
|
||||
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))
|
||||
self.dialog.view_manager.populate_files(item_to_select=os.path.basename(item_path))
|
||||
|
||||
def cancel_rename(event):
|
||||
self.dialog.view_manager.populate_files()
|
||||
@@ -183,6 +278,14 @@ class FileOperationsManager:
|
||||
entry.bind("<Escape>", cancel_rename)
|
||||
|
||||
def _start_rename_list_view(self, item_id):
|
||||
"""
|
||||
Initiates the in-place rename UI for an item in list view.
|
||||
|
||||
It places an Entry widget over the Treeview item's cell.
|
||||
|
||||
Args:
|
||||
item_id: The ID of the treeview item to be renamed.
|
||||
"""
|
||||
self.dialog.tree.see(item_id)
|
||||
self.dialog.tree.update_idletasks()
|
||||
|
||||
@@ -220,7 +323,7 @@ class FileOperationsManager:
|
||||
text=f"{LocaleStrings.FILE['error_renaming']}: {e}")
|
||||
self.dialog.view_manager.populate_files()
|
||||
else:
|
||||
self.dialog.populate_files(item_to_select=item_text)
|
||||
self.dialog.view_manager.populate_files(item_to_select=item_text)
|
||||
entry.destroy()
|
||||
|
||||
def cancel_rename(event):
|
||||
|
@@ -3,10 +3,27 @@ import tkinter as tk
|
||||
from cfd_app_config import LocaleStrings, _
|
||||
|
||||
class NavigationManager:
|
||||
"""Manages directory navigation, history, and path handling."""
|
||||
|
||||
def __init__(self, dialog):
|
||||
"""
|
||||
Initializes the NavigationManager.
|
||||
|
||||
Args:
|
||||
dialog: The main CustomFileDialog instance.
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def handle_path_entry_return(self, event):
|
||||
"""
|
||||
Handles the Return key press in the path entry field.
|
||||
|
||||
It attempts to navigate to the entered path. If the path is a file,
|
||||
it navigates to the containing directory and selects the file.
|
||||
|
||||
Args:
|
||||
event: The tkinter event that triggered this handler.
|
||||
"""
|
||||
path_text = self.dialog.widget_manager.path_entry.get().strip()
|
||||
potential_path = os.path.realpath(os.path.expanduser(path_text))
|
||||
|
||||
@@ -21,6 +38,18 @@ class NavigationManager:
|
||||
text=f"{LocaleStrings.CFD['path_not_found']}: {self.dialog.shorten_text(path_text, 50)}")
|
||||
|
||||
def navigate_to(self, path, file_to_select=None):
|
||||
"""
|
||||
Navigates to a specified directory path.
|
||||
|
||||
This is the core navigation method. It validates the path, checks for
|
||||
read permissions, updates the dialog's current directory, manages the
|
||||
navigation history, and refreshes the file view.
|
||||
|
||||
Args:
|
||||
path (str): The absolute path to navigate to.
|
||||
file_to_select (str, optional): If provided, this filename will be
|
||||
selected after navigation. Defaults to None.
|
||||
"""
|
||||
try:
|
||||
real_path = os.path.realpath(
|
||||
os.path.abspath(os.path.expanduser(path)))
|
||||
@@ -49,6 +78,7 @@ class NavigationManager:
|
||||
self.dialog.widget_manager.search_status_label.config(text=f"{LocaleStrings.CFD['error_title']}: {e}")
|
||||
|
||||
def go_back(self):
|
||||
"""Navigates to the previous directory in the history."""
|
||||
if self.dialog.history_pos > 0:
|
||||
self.dialog.history_pos -= 1
|
||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||
@@ -58,6 +88,7 @@ class NavigationManager:
|
||||
self.dialog.update_action_buttons_state()
|
||||
|
||||
def go_forward(self):
|
||||
"""Navigates to the next directory in the history."""
|
||||
if self.dialog.history_pos < len(self.dialog.history) - 1:
|
||||
self.dialog.history_pos += 1
|
||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||
@@ -67,11 +98,13 @@ class NavigationManager:
|
||||
self.dialog.update_action_buttons_state()
|
||||
|
||||
def go_up_level(self):
|
||||
"""Navigates to the parent directory of the current directory."""
|
||||
new_path = os.path.dirname(self.dialog.current_dir)
|
||||
if new_path != self.dialog.current_dir:
|
||||
self.navigate_to(new_path)
|
||||
|
||||
def update_nav_buttons(self):
|
||||
"""Updates the state of the back and forward navigation buttons."""
|
||||
self.dialog.widget_manager.back_button.config(
|
||||
state=tk.NORMAL if self.dialog.history_pos > 0 else tk.DISABLED)
|
||||
self.dialog.widget_manager.forward_button.config(state=tk.NORMAL if self.dialog.history_pos < len(
|
||||
|
@@ -9,7 +9,15 @@ from cfd_ui_setup import get_xdg_user_dir
|
||||
from cfd_app_config import LocaleStrings, _
|
||||
|
||||
class SearchManager:
|
||||
"""Manages the file search functionality, including UI and threading."""
|
||||
|
||||
def __init__(self, dialog):
|
||||
"""
|
||||
Initializes the SearchManager.
|
||||
|
||||
Args:
|
||||
dialog: The main CustomFileDialog instance.
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def show_search_ready(self, event=None):
|
||||
@@ -18,7 +26,11 @@ class SearchManager:
|
||||
self.dialog.widget_manager.search_animation.show_full_circle()
|
||||
|
||||
def activate_search(self, event=None):
|
||||
"""Activates the search entry or cancels an ongoing search."""
|
||||
"""
|
||||
Activates the search entry or cancels an ongoing search.
|
||||
|
||||
If a search is running, it cancels it. Otherwise, it executes a new search.
|
||||
"""
|
||||
if self.dialog.widget_manager.search_animation.running:
|
||||
if self.dialog.search_thread and self.dialog.search_thread.is_alive():
|
||||
self.dialog.search_thread.cancelled = True
|
||||
@@ -28,6 +40,12 @@ class SearchManager:
|
||||
self.execute_search()
|
||||
|
||||
def show_search_bar(self, event=None):
|
||||
"""
|
||||
Activates search mode and displays the search bar upon user typing.
|
||||
|
||||
Args:
|
||||
event: The key press event that triggered the search.
|
||||
"""
|
||||
if isinstance(event.widget, (ttk.Entry, tk.Entry)) or not event.char.strip():
|
||||
return
|
||||
self.dialog.search_mode = True
|
||||
@@ -37,6 +55,9 @@ class SearchManager:
|
||||
self.dialog.widget_manager.search_animation.show_full_circle()
|
||||
|
||||
def hide_search_bar(self, event=None):
|
||||
"""
|
||||
Deactivates search mode, clears the search bar, and restores the file view.
|
||||
"""
|
||||
self.dialog.search_mode = False
|
||||
self.dialog.widget_manager.filename_entry.delete(0, tk.END)
|
||||
self.dialog.widget_manager.search_status_label.config(text="")
|
||||
@@ -45,6 +66,11 @@ class SearchManager:
|
||||
self.dialog.widget_manager.search_animation.hide()
|
||||
|
||||
def execute_search(self, event=None):
|
||||
"""
|
||||
Initiates a file search in a background thread.
|
||||
|
||||
Prevents starting a new search if one is already running.
|
||||
"""
|
||||
if self.dialog.search_thread and self.dialog.search_thread.is_alive():
|
||||
return
|
||||
search_term = self.dialog.widget_manager.filename_entry.get().strip()
|
||||
@@ -58,6 +84,16 @@ class SearchManager:
|
||||
self.dialog.search_thread.start()
|
||||
|
||||
def _perform_search_in_thread(self, search_term):
|
||||
"""
|
||||
Performs the actual file search in a background thread.
|
||||
|
||||
Searches the current directory and relevant XDG user directories.
|
||||
Handles recursive/non-recursive and hidden/non-hidden file searches.
|
||||
Updates the UI with results upon completion.
|
||||
|
||||
Args:
|
||||
search_term (str): The term to search for.
|
||||
"""
|
||||
self.dialog.search_results.clear()
|
||||
search_dirs = [self.dialog.current_dir]
|
||||
home_dir = os.path.expanduser("~")
|
||||
@@ -144,6 +180,7 @@ class SearchManager:
|
||||
self.dialog.search_process = None
|
||||
|
||||
def show_search_results_treeview(self):
|
||||
"""Displays the search results in a dedicated Treeview."""
|
||||
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
@@ -244,6 +281,12 @@ class SearchManager:
|
||||
search_tree.bind("<ButtonRelease-3>", show_context_menu)
|
||||
|
||||
def _open_file_location(self, search_tree):
|
||||
"""
|
||||
Navigates to the directory of the selected item in the search results.
|
||||
|
||||
Args:
|
||||
search_tree: The Treeview widget containing the search results.
|
||||
"""
|
||||
selection = search_tree.selection()
|
||||
if not selection:
|
||||
return
|
||||
|
@@ -11,7 +11,17 @@ except ImportError:
|
||||
|
||||
|
||||
class SettingsDialog(tk.Toplevel):
|
||||
"""A dialog window for configuring the application settings."""
|
||||
|
||||
def __init__(self, parent, dialog_mode="save"):
|
||||
"""
|
||||
Initializes the SettingsDialog.
|
||||
|
||||
Args:
|
||||
parent: The parent widget.
|
||||
dialog_mode (str, optional): The mode of the main dialog ("open" or "save"),
|
||||
which affects available settings. Defaults to "save".
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
@@ -145,6 +155,11 @@ class SettingsDialog(tk.Toplevel):
|
||||
command=self.destroy).pack(side="right")
|
||||
|
||||
def save_settings(self):
|
||||
"""
|
||||
Saves the current settings to the configuration file and closes the dialog.
|
||||
|
||||
Triggers a UI rebuild in the parent dialog to apply the changes.
|
||||
"""
|
||||
new_settings = {
|
||||
"button_box_pos": self.button_box_pos.get(),
|
||||
"window_size_preset": self.window_size_preset.get(),
|
||||
@@ -161,6 +176,7 @@ class SettingsDialog(tk.Toplevel):
|
||||
self.destroy()
|
||||
|
||||
def reset_to_defaults(self):
|
||||
"""Resets all settings in the dialog to their default values."""
|
||||
defaults = CfdConfigManager._default_settings
|
||||
self.button_box_pos.set(defaults["button_box_pos"])
|
||||
self.window_size_preset.set(defaults["window_size_preset"])
|
||||
|
@@ -125,8 +125,16 @@ class ViewManager:
|
||||
ttk.Label(container_frame, text=error).pack(pady=20)
|
||||
return
|
||||
|
||||
widget_to_focus = self._load_more_items_icon_view(
|
||||
container_frame, _on_mouse_wheel, item_to_rename, item_to_select)
|
||||
widget_to_focus = None
|
||||
while self.dialog.currently_loaded_count < len(self.dialog.all_items):
|
||||
widget_to_focus = self._load_more_items_icon_view(
|
||||
container_frame, _on_mouse_wheel, item_to_rename, item_to_select)
|
||||
|
||||
if widget_to_focus:
|
||||
break
|
||||
|
||||
if not (item_to_rename or item_to_select):
|
||||
break
|
||||
|
||||
if widget_to_focus:
|
||||
def scroll_to_widget():
|
||||
@@ -270,7 +278,12 @@ class ViewManager:
|
||||
self.dialog.tree.insert("", "end", text=error, values=())
|
||||
return
|
||||
|
||||
self._load_more_items_list_view(item_to_rename, item_to_select)
|
||||
while self.dialog.currently_loaded_count < len(self.dialog.all_items):
|
||||
item_found = self._load_more_items_list_view(item_to_rename, item_to_select)
|
||||
if item_found:
|
||||
break
|
||||
if not (item_to_rename or item_to_select):
|
||||
break
|
||||
|
||||
def _load_more_items_list_view(self, item_to_rename=None, item_to_select=None):
|
||||
start_index = self.dialog.currently_loaded_count
|
||||
@@ -278,8 +291,9 @@ class ViewManager:
|
||||
self.dialog.items_to_load_per_batch)
|
||||
|
||||
if start_index >= end_index:
|
||||
return
|
||||
return False
|
||||
|
||||
item_found = False
|
||||
for i in range(start_index, end_index):
|
||||
name = self.dialog.all_items[i]
|
||||
if not self.dialog.show_hidden_files.get() and name.startswith('.'):
|
||||
@@ -305,14 +319,17 @@ class ViewManager:
|
||||
self.dialog.tree.focus(item_id)
|
||||
self.dialog.tree.see(item_id)
|
||||
self.dialog.file_op_manager.start_rename(item_id, path)
|
||||
item_found = True
|
||||
elif name == item_to_select:
|
||||
self.dialog.tree.selection_set(item_id)
|
||||
self.dialog.tree.focus(item_id)
|
||||
self.dialog.tree.see(item_id)
|
||||
item_found = True
|
||||
except (FileNotFoundError, PermissionError):
|
||||
continue
|
||||
|
||||
self.dialog.currently_loaded_count = end_index
|
||||
return item_found
|
||||
|
||||
def on_item_select(self, path, item_frame):
|
||||
if hasattr(self.dialog, 'selected_item_frame') and self.dialog.selected_item_frame.winfo_exists():
|
||||
|
Reference in New Issue
Block a user