add docstring in cfd_ui_setup, cfd_view_manager

This commit is contained in:
2025-08-10 11:33:19 +02:00
parent 14a50616a3
commit 4e7ef8d348
4 changed files with 148 additions and 1 deletions

View File

@@ -8,6 +8,17 @@ from cfd_app_config import LocaleStrings, _
def get_xdg_user_dir(dir_key, fallback_name):
"""
Retrieves a user directory path from the XDG user-dirs.dirs config file.
Args:
dir_key (str): The key for the directory (e.g., "XDG_DOWNLOAD_DIR").
fallback_name (str): The name of the directory to use as a fallback
if the key is not found (e.g., "Downloads").
Returns:
str: The absolute path to the user directory.
"""
home = os.path.expanduser("~")
fallback_path = os.path.join(home, fallback_name)
config_path = os.path.join(home, ".config", "user-dirs.dirs")
@@ -31,11 +42,21 @@ def get_xdg_user_dir(dir_key, fallback_name):
class StyleManager:
"""Manages the visual styling of the application using ttk styles."""
def __init__(self, dialog):
"""
Initializes the StyleManager.
Args:
dialog: The main CustomFileDialog instance.
"""
self.dialog = dialog
self.setup_styles()
def setup_styles(self):
"""
Configures all the ttk styles for the dialog based on a light or dark theme.
"""
style = ttk.Style(self.dialog)
base_bg = self.dialog.cget('background')
self.is_dark = sum(self.dialog.winfo_rgb(base_bg)) / 3 < 32768
@@ -111,13 +132,22 @@ class StyleManager:
class WidgetManager:
"""Manages the creation, layout, and management of all widgets in the dialog."""
def __init__(self, dialog, settings):
"""
Initializes the WidgetManager.
Args:
dialog: The main CustomFileDialog instance.
settings (dict): The application settings.
"""
self.dialog = dialog
self.style_manager = dialog.style_manager
self.settings = settings
self.setup_widgets()
def _setup_top_bar(self, parent_frame):
"""Sets up the top bar with navigation and control buttons."""
top_bar = ttk.Frame(parent_frame, style='Accent.TFrame', padding=(0, 5, 0, 5))
top_bar.grid(row=0, column=0, columnspan=2, sticky="ew")
top_bar.grid_columnconfigure(1, weight=1)
@@ -202,6 +232,7 @@ class WidgetManager:
command=self.dialog.show_more_menu, style="Header.TButton.Borderless.Round", width=3)
def _setup_sidebar(self, parent_paned_window):
"""Sets up the sidebar with bookmarks and devices."""
sidebar_frame = ttk.Frame(parent_paned_window, style="Sidebar.TFrame", padding=(0, 0, 0, 0), width=200)
sidebar_frame.grid_propagate(False)
sidebar_frame.bind("<Configure>", self.dialog.on_sidebar_resize)
@@ -220,6 +251,7 @@ class WidgetManager:
self._setup_sidebar_storage(sidebar_frame)
def _setup_sidebar_bookmarks(self, sidebar_frame):
"""Sets up the bookmark buttons in the sidebar."""
sidebar_buttons_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame", padding=(0, 15, 0, 0))
sidebar_buttons_frame.grid(row=0, column=0, sticky="nsew")
sidebar_buttons_config = [
@@ -238,6 +270,7 @@ class WidgetManager:
self.sidebar_buttons.append((btn, f" {config['name']}"))
def _setup_sidebar_devices(self, sidebar_frame):
"""Sets up the mounted devices section in the sidebar."""
mounted_devices_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame")
mounted_devices_frame.grid(row=2, column=0, sticky="nsew", padx=10)
mounted_devices_frame.grid_columnconfigure(0, weight=1)
@@ -326,6 +359,7 @@ class WidgetManager:
pass
def _setup_sidebar_storage(self, sidebar_frame):
"""Sets up the storage indicator in the sidebar."""
storage_frame = ttk.Frame(sidebar_frame, style="Sidebar.TFrame")
storage_frame.grid(row=5, column=0, sticky="sew", padx=10, pady=10)
self.storage_label = ttk.Label(storage_frame, text=f"{LocaleStrings.CFD["free_space"]}:", background=self.style_manager.freespace_background)
@@ -334,7 +368,7 @@ class WidgetManager:
self.storage_bar.pack(fill="x", pady=(2, 5), padx=15)
def _setup_bottom_bar(self):
"""Sets up the bottom bar including containers and widgets based on dialog mode."""
"""Sets up the bottom bar including filename entry, action buttons, and status info."""
self.action_status_frame = ttk.Frame(self.content_frame, style="AccentBottom.TFrame")
self.action_status_frame.grid(row=1, column=0, sticky="ew", pady=(5, 5), padx=10)
self.status_container = ttk.Frame(self.action_status_frame, style="AccentBottom.TFrame")
@@ -396,6 +430,7 @@ class WidgetManager:
self._layout_bottom_buttons(button_box_pos)
def _layout_bottom_buttons(self, button_box_pos):
"""Lays out the bottom action buttons based on user settings."""
# Configure container weights
self.left_container.grid_rowconfigure(0, weight=1)
self.right_container.grid_rowconfigure(0, weight=1)
@@ -429,6 +464,7 @@ class WidgetManager:
def setup_widgets(self):
"""Creates and arranges all widgets in the main dialog window."""
# Main container
main_frame = ttk.Frame(self.dialog, style='Accent.TFrame')
main_frame.pack(fill="both", expand=True)

View File

@@ -6,10 +6,29 @@ from shared_libs.common_tools import Tooltip
from cfd_app_config import AppConfig, LocaleStrings, _
class ViewManager:
"""Manages the display of files and folders in list and icon views."""
def __init__(self, dialog):
"""
Initializes the ViewManager.
Args:
dialog: The main CustomFileDialog instance.
"""
self.dialog = dialog
def populate_files(self, item_to_rename=None, item_to_select=None):
"""
Populates the main file display area.
This method clears the current view and then calls the appropriate
method to populate either the list or icon view.
Args:
item_to_rename (str, optional): The name of an item to immediately
put into rename mode. Defaults to None.
item_to_select (str, optional): The name of an item to select
after populating. Defaults to None.
"""
self._unbind_mouse_wheel_events()
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
@@ -24,6 +43,12 @@ class ViewManager:
self.populate_icon_view(item_to_rename, item_to_select)
def _get_sorted_items(self):
"""
Gets a sorted list of items from the current directory.
Returns:
tuple: A tuple containing (list of items, error message, warning message).
"""
try:
items = os.listdir(self.dialog.current_dir)
num_items = len(items)
@@ -42,6 +67,15 @@ class ViewManager:
return ([], LocaleStrings.CFD["directory_not_found"], None)
def _get_folder_content_count(self, folder_path):
"""
Counts the number of items in a given folder.
Args:
folder_path (str): The path to the folder.
Returns:
int or None: The number of items, or None if an error occurs.
"""
try:
if not os.path.isdir(folder_path) or not os.access(folder_path, os.R_OK):
return None
@@ -56,11 +90,21 @@ class ViewManager:
return None
def _get_item_path_from_widget(self, widget):
"""
Traverses up the widget hierarchy to find the item_path attribute.
Args:
widget: The widget to start from.
Returns:
str or None: The associated file path, or None if not found.
"""
while widget and not hasattr(widget, 'item_path'):
widget = widget.master
return getattr(widget, 'item_path', None)
def _handle_icon_click(self, event):
"""Handles a single click on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
item_frame = event.widget
@@ -69,16 +113,19 @@ class ViewManager:
self.on_item_select(item_path, item_frame)
def _handle_icon_double_click(self, event):
"""Handles a double click on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
self.on_item_double_click(item_path)
def _handle_icon_context_menu(self, event):
"""Handles a context menu request on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
self.dialog.file_op_manager._show_context_menu(event, item_path)
def _handle_icon_rename_request(self, event):
"""Handles a rename request on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
item_frame = event.widget
@@ -87,6 +134,13 @@ class ViewManager:
self.dialog.file_op_manager.on_rename_request(event, item_path, item_frame)
def populate_icon_view(self, item_to_rename=None, item_to_select=None):
"""
Populates the file display with items in an icon grid layout.
Args:
item_to_rename (str, optional): Item to enter rename mode.
item_to_select (str, optional): Item to select.
"""
self.dialog.all_items, error, warning = self._get_sorted_items()
self.dialog.currently_loaded_count = 0
@@ -154,6 +208,18 @@ class ViewManager:
self.dialog.after(100, scroll_to_widget)
def _load_more_items_icon_view(self, container, scroll_handler, item_to_rename=None, item_to_select=None):
"""
Loads a batch of items into the icon view.
Args:
container: The parent widget for the items.
scroll_handler: The function to handle mouse wheel events.
item_to_rename (str, optional): Item to enter rename mode.
item_to_select (str, optional): Item to select.
Returns:
The widget that was focused (renamed or selected), or None.
"""
start_index = self.dialog.currently_loaded_count
end_index = min(len(self.dialog.all_items), start_index +
self.dialog.items_to_load_per_batch)
@@ -228,6 +294,13 @@ class ViewManager:
return widget_to_focus
def populate_list_view(self, item_to_rename=None, item_to_select=None):
"""
Populates the file display with items in a list (Treeview) layout.
Args:
item_to_rename (str, optional): Item to enter rename mode.
item_to_select (str, optional): Item to select.
"""
self.dialog.all_items, error, warning = self._get_sorted_items()
self.dialog.currently_loaded_count = 0
@@ -286,6 +359,16 @@ class ViewManager:
break
def _load_more_items_list_view(self, item_to_rename=None, item_to_select=None):
"""
Loads a batch of items into the list view.
Args:
item_to_rename (str, optional): Item to enter rename mode.
item_to_select (str, optional): Item to select.
Returns:
bool: True if the item to rename/select was found and processed.
"""
start_index = self.dialog.currently_loaded_count
end_index = min(len(self.dialog.all_items), start_index +
self.dialog.items_to_load_per_batch)
@@ -332,6 +415,13 @@ class ViewManager:
return item_found
def on_item_select(self, path, item_frame):
"""
Handles the selection of an item in the icon view.
Args:
path (str): The path of the selected item.
item_frame: The widget frame of the selected item.
"""
if hasattr(self.dialog, 'selected_item_frame') and self.dialog.selected_item_frame.winfo_exists():
self.dialog.selected_item_frame.state(['!selected'])
for child in self.dialog.selected_item_frame.winfo_children():
@@ -350,6 +440,7 @@ class ViewManager:
self.dialog.widget_manager.filename_entry.insert(0, os.path.basename(path))
def on_list_select(self, event):
"""Handles the selection of an item in the list view."""
if not self.dialog.tree.selection():
return
item_id = self.dialog.tree.selection()[0]
@@ -363,6 +454,7 @@ class ViewManager:
self.dialog.widget_manager.filename_entry.insert(0, item_text)
def on_list_context_menu(self, event):
"""Shows the context menu for a list view item."""
iid = self.dialog.tree.identify_row(event.y)
if not iid:
return "break"
@@ -373,6 +465,12 @@ class ViewManager:
return "break"
def on_item_double_click(self, path):
"""
Handles a double-click on an icon view item.
Args:
path (str): The path of the double-clicked item.
"""
if os.path.isdir(path):
self.dialog.navigation_manager.navigate_to(path)
elif self.dialog.dialog_mode == "open":
@@ -385,6 +483,7 @@ class ViewManager:
self.dialog.on_save()
def on_list_double_click(self, event):
"""Handles a double-click on a list view item."""
if not self.dialog.tree.selection():
return
item_id = self.dialog.tree.selection()[0]
@@ -401,6 +500,12 @@ class ViewManager:
self.dialog.on_save()
def _select_file_in_view(self, filename):
"""
Programmatically selects a file in the current view.
Args:
filename (str): The name of the file to select.
"""
if self.dialog.view_mode.get() == "list":
for item_id in self.dialog.tree.get_children():
if self.dialog.tree.item(item_id, "text").strip() == filename:
@@ -436,6 +541,7 @@ class ViewManager:
break
def _update_view_mode_buttons(self):
"""Updates the visual state of the view mode toggle buttons."""
if self.dialog.view_mode.get() == "icons":
self.dialog.widget_manager.icon_view_button.configure(
style="Header.TButton.Active.Round")
@@ -448,16 +554,19 @@ class ViewManager:
style="Header.TButton.Borderless.Round")
def set_icon_view(self):
"""Switches to icon view and repopulates the files."""
self.dialog.view_mode.set("icons")
self._update_view_mode_buttons()
self.populate_files()
def set_list_view(self):
"""Switches to list view and repopulates the files."""
self.dialog.view_mode.set("list")
self._update_view_mode_buttons()
self.populate_files()
def toggle_hidden_files(self):
"""Toggles the visibility of hidden files and refreshes the view."""
self.dialog.show_hidden_files.set(not self.dialog.show_hidden_files.get())
if self.dialog.show_hidden_files.get():
self.dialog.widget_manager.hidden_files_button.config(
@@ -472,6 +581,7 @@ class ViewManager:
self.populate_files()
def on_filter_change(self, event):
"""Handles a change in the file type filter combobox."""
selected_desc = self.dialog.widget_manager.filter_combobox.get()
for desc, pattern in self.dialog.filetypes:
if desc == selected_desc:
@@ -480,6 +590,7 @@ class ViewManager:
self.populate_files()
def _unbind_mouse_wheel_events(self):
"""Unbinds all mouse wheel events from the dialog."""
self.dialog.unbind_all("<MouseWheel>")
self.dialog.unbind_all("<Button-4>")
self.dialog.unbind_all("<Button-5>")