add doctring and typhint in custom_file_dialog and add type-hints on cfd_view_manager, cfd_ui_setup, cfd_settings_dialog, cfd_search_manager, cfd_navigation_manager
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import os
|
||||
import tkinter as tk
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from cfd_app_config import LocaleStrings, _
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from custom_file_dialog import CustomFileDialog
|
||||
|
||||
|
||||
class NavigationManager:
|
||||
"""Manages directory navigation, history, and path handling."""
|
||||
|
||||
def __init__(self, dialog):
|
||||
def __init__(self, dialog: 'CustomFileDialog') -> None:
|
||||
"""
|
||||
Initializes the NavigationManager.
|
||||
|
||||
@@ -14,7 +20,7 @@ class NavigationManager:
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def handle_path_entry_return(self, event):
|
||||
def handle_path_entry_return(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Handles the Return key press in the path entry field.
|
||||
|
||||
@@ -37,7 +43,7 @@ class NavigationManager:
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"{LocaleStrings.CFD['path_not_found']}: {self.dialog.shorten_text(path_text, 50)}")
|
||||
|
||||
def navigate_to(self, path, file_to_select=None):
|
||||
def navigate_to(self, path: str, file_to_select: Optional[str] = None) -> None:
|
||||
"""
|
||||
Navigates to a specified directory path.
|
||||
|
||||
@@ -77,7 +83,7 @@ class NavigationManager:
|
||||
except Exception as e:
|
||||
self.dialog.widget_manager.search_status_label.config(text=f"{LocaleStrings.CFD['error_title']}: {e}")
|
||||
|
||||
def go_back(self):
|
||||
def go_back(self) -> None:
|
||||
"""Navigates to the previous directory in the history."""
|
||||
if self.dialog.history_pos > 0:
|
||||
self.dialog.history_pos -= 1
|
||||
@@ -87,7 +93,7 @@ class NavigationManager:
|
||||
self.dialog.update_status_bar()
|
||||
self.dialog.update_action_buttons_state()
|
||||
|
||||
def go_forward(self):
|
||||
def go_forward(self) -> None:
|
||||
"""Navigates to the next directory in the history."""
|
||||
if self.dialog.history_pos < len(self.dialog.history) - 1:
|
||||
self.dialog.history_pos += 1
|
||||
@@ -97,13 +103,13 @@ class NavigationManager:
|
||||
self.dialog.update_status_bar()
|
||||
self.dialog.update_action_buttons_state()
|
||||
|
||||
def go_up_level(self):
|
||||
def go_up_level(self) -> None:
|
||||
"""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):
|
||||
def update_nav_buttons(self) -> None:
|
||||
"""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)
|
||||
|
@@ -4,14 +4,20 @@ import subprocess
|
||||
from datetime import datetime
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from shared_libs.message import MessageDialog
|
||||
from cfd_ui_setup import get_xdg_user_dir
|
||||
from cfd_app_config import LocaleStrings, _
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from custom_file_dialog import CustomFileDialog
|
||||
|
||||
|
||||
class SearchManager:
|
||||
"""Manages the file search functionality, including UI and threading."""
|
||||
|
||||
def __init__(self, dialog):
|
||||
def __init__(self, dialog: 'CustomFileDialog') -> None:
|
||||
"""
|
||||
Initializes the SearchManager.
|
||||
|
||||
@@ -20,12 +26,12 @@ class SearchManager:
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def show_search_ready(self, event=None):
|
||||
def show_search_ready(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""Shows the static 'full circle' to indicate search is ready."""
|
||||
if not self.dialog.search_mode:
|
||||
self.dialog.widget_manager.search_animation.show_full_circle()
|
||||
|
||||
def activate_search(self, event=None):
|
||||
def activate_search(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""
|
||||
Activates the search entry or cancels an ongoing search.
|
||||
|
||||
@@ -39,7 +45,7 @@ class SearchManager:
|
||||
else:
|
||||
self.execute_search()
|
||||
|
||||
def show_search_bar(self, event=None):
|
||||
def show_search_bar(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Activates search mode and displays the search bar upon user typing.
|
||||
|
||||
@@ -54,7 +60,7 @@ class SearchManager:
|
||||
self.dialog.widget_manager.filename_entry.insert(0, event.char)
|
||||
self.dialog.widget_manager.search_animation.show_full_circle()
|
||||
|
||||
def hide_search_bar(self, event=None):
|
||||
def hide_search_bar(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""
|
||||
Deactivates search mode, clears the search bar, and restores the file view.
|
||||
"""
|
||||
@@ -65,7 +71,7 @@ class SearchManager:
|
||||
self.dialog.view_manager.populate_files()
|
||||
self.dialog.widget_manager.search_animation.hide()
|
||||
|
||||
def execute_search(self, event=None):
|
||||
def execute_search(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""
|
||||
Initiates a file search in a background thread.
|
||||
|
||||
@@ -83,7 +89,7 @@ class SearchManager:
|
||||
self.dialog.search_thread = threading.Thread(target=self._perform_search_in_thread, args=(search_term,))
|
||||
self.dialog.search_thread.start()
|
||||
|
||||
def _perform_search_in_thread(self, search_term):
|
||||
def _perform_search_in_thread(self, search_term: str) -> None:
|
||||
"""
|
||||
Performs the actual file search in a background thread.
|
||||
|
||||
@@ -157,7 +163,7 @@ class SearchManager:
|
||||
seen = set()
|
||||
self.dialog.search_results = [x for x in all_files if not (x in seen or seen.add(x))]
|
||||
|
||||
def update_ui():
|
||||
def update_ui() -> None:
|
||||
if self.dialog.search_results:
|
||||
self.show_search_results_treeview()
|
||||
folder_count = sum(1 for p in self.dialog.search_results if os.path.isdir(p))
|
||||
@@ -179,7 +185,7 @@ class SearchManager:
|
||||
self.dialog.after(0, self.dialog.widget_manager.search_animation.stop)
|
||||
self.dialog.search_process = None
|
||||
|
||||
def show_search_results_treeview(self):
|
||||
def show_search_results_treeview(self) -> None:
|
||||
"""Displays the search results in a dedicated Treeview."""
|
||||
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
@@ -232,7 +238,7 @@ class SearchManager:
|
||||
except (FileNotFoundError, PermissionError):
|
||||
continue
|
||||
|
||||
def on_search_select(event):
|
||||
def on_search_select(event: tk.Event) -> None:
|
||||
selection = search_tree.selection()
|
||||
if selection:
|
||||
item = search_tree.item(selection[0])
|
||||
@@ -254,7 +260,7 @@ class SearchManager:
|
||||
|
||||
search_tree.bind("<<TreeviewSelect>>", on_search_select)
|
||||
|
||||
def on_search_double_click(event):
|
||||
def on_search_double_click(event: tk.Event) -> None:
|
||||
selection = search_tree.selection()
|
||||
if selection:
|
||||
item = search_tree.item(selection[0])
|
||||
@@ -266,7 +272,7 @@ class SearchManager:
|
||||
|
||||
search_tree.bind("<Double-1>", on_search_double_click)
|
||||
|
||||
def show_context_menu(event):
|
||||
def show_context_menu(event: tk.Event) -> str:
|
||||
iid = search_tree.identify_row(event.y)
|
||||
if not iid:
|
||||
return "break"
|
||||
@@ -280,7 +286,7 @@ class SearchManager:
|
||||
|
||||
search_tree.bind("<ButtonRelease-3>", show_context_menu)
|
||||
|
||||
def _open_file_location(self, search_tree):
|
||||
def _open_file_location(self, search_tree: ttk.Treeview) -> None:
|
||||
"""
|
||||
Navigates to the directory of the selected item in the search results.
|
||||
|
||||
|
@@ -1,8 +1,13 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cfd_app_config import CfdConfigManager, LocaleStrings, _
|
||||
from animated_icon import PIL_AVAILABLE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from custom_file_dialog import CustomFileDialog
|
||||
|
||||
try:
|
||||
import send2trash
|
||||
SEND2TRASH_AVAILABLE = True
|
||||
@@ -13,7 +18,7 @@ except ImportError:
|
||||
class SettingsDialog(tk.Toplevel):
|
||||
"""A dialog window for configuring the application settings."""
|
||||
|
||||
def __init__(self, parent, dialog_mode="save"):
|
||||
def __init__(self, parent: 'CustomFileDialog', dialog_mode: str = "save") -> None:
|
||||
"""
|
||||
Initializes the SettingsDialog.
|
||||
|
||||
@@ -154,7 +159,7 @@ class SettingsDialog(tk.Toplevel):
|
||||
ttk.Button(button_frame, text=LocaleStrings.SET["cancel_button"],
|
||||
command=self.destroy).pack(side="right")
|
||||
|
||||
def save_settings(self):
|
||||
def save_settings(self) -> None:
|
||||
"""
|
||||
Saves the current settings to the configuration file and closes the dialog.
|
||||
|
||||
@@ -175,7 +180,7 @@ class SettingsDialog(tk.Toplevel):
|
||||
self.master.reload_config_and_rebuild_ui()
|
||||
self.destroy()
|
||||
|
||||
def reset_to_defaults(self):
|
||||
def reset_to_defaults(self) -> None:
|
||||
"""Resets all settings in the dialog to their default values."""
|
||||
defaults = CfdConfigManager._default_settings
|
||||
self.button_box_pos.set(defaults["button_box_pos"])
|
||||
|
@@ -2,12 +2,19 @@ import os
|
||||
import shutil
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any
|
||||
|
||||
# To avoid circular import with custom_file_dialog.py
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from custom_file_dialog import CustomFileDialog
|
||||
|
||||
from shared_libs.common_tools import Tooltip
|
||||
from animated_icon import AnimatedIcon
|
||||
from cfd_app_config import LocaleStrings, _
|
||||
|
||||
|
||||
def get_xdg_user_dir(dir_key, fallback_name):
|
||||
def get_xdg_user_dir(dir_key: str, fallback_name: str) -> str:
|
||||
"""
|
||||
Retrieves a user directory path from the XDG user-dirs.dirs config file.
|
||||
|
||||
@@ -43,7 +50,7 @@ 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):
|
||||
def __init__(self, dialog: 'CustomFileDialog') -> None:
|
||||
"""
|
||||
Initializes the StyleManager.
|
||||
|
||||
@@ -53,7 +60,7 @@ class StyleManager:
|
||||
self.dialog = dialog
|
||||
self.setup_styles()
|
||||
|
||||
def setup_styles(self):
|
||||
def setup_styles(self) -> None:
|
||||
"""
|
||||
Configures all the ttk styles for the dialog based on a light or dark theme.
|
||||
"""
|
||||
@@ -133,7 +140,7 @@ class StyleManager:
|
||||
|
||||
class WidgetManager:
|
||||
"""Manages the creation, layout, and management of all widgets in the dialog."""
|
||||
def __init__(self, dialog, settings):
|
||||
def __init__(self, dialog: 'CustomFileDialog', settings: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Initializes the WidgetManager.
|
||||
|
||||
@@ -146,7 +153,7 @@ class WidgetManager:
|
||||
self.settings = settings
|
||||
self.setup_widgets()
|
||||
|
||||
def _setup_top_bar(self, parent_frame):
|
||||
def _setup_top_bar(self, parent_frame: ttk.Frame) -> None:
|
||||
"""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")
|
||||
@@ -231,7 +238,7 @@ class WidgetManager:
|
||||
self.more_button = ttk.Button(right_controls_container, text="...",
|
||||
command=self.dialog.show_more_menu, style="Header.TButton.Borderless.Round", width=3)
|
||||
|
||||
def _setup_sidebar(self, parent_paned_window):
|
||||
def _setup_sidebar(self, parent_paned_window: ttk.PanedWindow) -> None:
|
||||
"""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)
|
||||
@@ -250,7 +257,7 @@ class WidgetManager:
|
||||
|
||||
self._setup_sidebar_storage(sidebar_frame)
|
||||
|
||||
def _setup_sidebar_bookmarks(self, sidebar_frame):
|
||||
def _setup_sidebar_bookmarks(self, sidebar_frame: ttk.Frame) -> None:
|
||||
"""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")
|
||||
@@ -269,7 +276,7 @@ class WidgetManager:
|
||||
btn.pack(fill="x", pady=1)
|
||||
self.sidebar_buttons.append((btn, f" {config['name']}"))
|
||||
|
||||
def _setup_sidebar_devices(self, sidebar_frame):
|
||||
def _setup_sidebar_devices(self, sidebar_frame: ttk.Frame) -> None:
|
||||
"""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)
|
||||
@@ -298,7 +305,7 @@ class WidgetManager:
|
||||
self.devices_scrollable_frame.bind(
|
||||
"<Leave>", self.dialog._on_devices_leave)
|
||||
|
||||
def _configure_devices_canvas(event):
|
||||
def _configure_devices_canvas(event: tk.Event) -> None:
|
||||
self.devices_canvas.configure(
|
||||
scrollregion=self.devices_canvas.bbox("all"))
|
||||
canvas_width = event.width
|
||||
@@ -309,7 +316,7 @@ class WidgetManager:
|
||||
scrollregion=self.devices_canvas.bbox("all")))
|
||||
self.devices_canvas.bind("<Configure>", _configure_devices_canvas)
|
||||
|
||||
def _on_devices_mouse_wheel(event):
|
||||
def _on_devices_mouse_wheel(event: tk.Event) -> None:
|
||||
if event.num == 4:
|
||||
delta = -1
|
||||
elif event.num == 5:
|
||||
@@ -358,7 +365,7 @@ class WidgetManager:
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
|
||||
def _setup_sidebar_storage(self, sidebar_frame):
|
||||
def _setup_sidebar_storage(self, sidebar_frame: ttk.Frame) -> None:
|
||||
"""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)
|
||||
@@ -367,7 +374,7 @@ class WidgetManager:
|
||||
self.storage_bar = ttk.Progressbar(storage_frame, orient="horizontal", length=100, mode="determinate")
|
||||
self.storage_bar.pack(fill="x", pady=(2, 5), padx=15)
|
||||
|
||||
def _setup_bottom_bar(self):
|
||||
def _setup_bottom_bar(self) -> None:
|
||||
"""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)
|
||||
@@ -429,7 +436,7 @@ class WidgetManager:
|
||||
|
||||
self._layout_bottom_buttons(button_box_pos)
|
||||
|
||||
def _layout_bottom_buttons(self, button_box_pos):
|
||||
def _layout_bottom_buttons(self, button_box_pos: str) -> None:
|
||||
"""Lays out the bottom action buttons based on user settings."""
|
||||
# Configure container weights
|
||||
self.left_container.grid_rowconfigure(0, weight=1)
|
||||
@@ -463,7 +470,7 @@ class WidgetManager:
|
||||
self.filter_combobox.grid(in_=self.center_container, row=1, column=1, sticky="e", pady=(5, 0))
|
||||
|
||||
|
||||
def setup_widgets(self):
|
||||
def setup_widgets(self) -> None:
|
||||
"""Creates and arranges all widgets in the main dialog window."""
|
||||
# Main container
|
||||
main_frame = ttk.Frame(self.dialog, style='Accent.TFrame')
|
||||
|
@@ -2,12 +2,19 @@ import os
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Tuple, Callable, Any
|
||||
|
||||
# To avoid circular import with custom_file_dialog.py
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from custom_file_dialog import CustomFileDialog
|
||||
|
||||
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):
|
||||
def __init__(self, dialog: 'CustomFileDialog'):
|
||||
"""
|
||||
Initializes the ViewManager.
|
||||
|
||||
@@ -16,7 +23,7 @@ class ViewManager:
|
||||
"""
|
||||
self.dialog = dialog
|
||||
|
||||
def populate_files(self, item_to_rename=None, item_to_select=None):
|
||||
def populate_files(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
|
||||
"""
|
||||
Populates the main file display area.
|
||||
|
||||
@@ -42,7 +49,7 @@ class ViewManager:
|
||||
else:
|
||||
self.populate_icon_view(item_to_rename, item_to_select)
|
||||
|
||||
def _get_sorted_items(self):
|
||||
def _get_sorted_items(self) -> Tuple[List[str], Optional[str], Optional[str]]:
|
||||
"""
|
||||
Gets a sorted list of items from the current directory.
|
||||
|
||||
@@ -66,7 +73,7 @@ class ViewManager:
|
||||
except FileNotFoundError:
|
||||
return ([], LocaleStrings.CFD["directory_not_found"], None)
|
||||
|
||||
def _get_folder_content_count(self, folder_path):
|
||||
def _get_folder_content_count(self, folder_path: str) -> Optional[int]:
|
||||
"""
|
||||
Counts the number of items in a given folder.
|
||||
|
||||
@@ -89,7 +96,7 @@ class ViewManager:
|
||||
except (PermissionError, FileNotFoundError):
|
||||
return None
|
||||
|
||||
def _get_item_path_from_widget(self, widget):
|
||||
def _get_item_path_from_widget(self, widget: tk.Widget) -> Optional[str]:
|
||||
"""
|
||||
Traverses up the widget hierarchy to find the item_path attribute.
|
||||
|
||||
@@ -103,7 +110,7 @@ class ViewManager:
|
||||
widget = widget.master
|
||||
return getattr(widget, 'item_path', None)
|
||||
|
||||
def _handle_icon_click(self, event):
|
||||
def _handle_icon_click(self, event: tk.Event) -> None:
|
||||
"""Handles a single click on an icon view item."""
|
||||
item_path = self._get_item_path_from_widget(event.widget)
|
||||
if item_path:
|
||||
@@ -112,19 +119,19 @@ class ViewManager:
|
||||
item_frame = item_frame.master
|
||||
self.on_item_select(item_path, item_frame)
|
||||
|
||||
def _handle_icon_double_click(self, event):
|
||||
def _handle_icon_double_click(self, event: tk.Event) -> None:
|
||||
"""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):
|
||||
def _handle_icon_context_menu(self, event: tk.Event) -> None:
|
||||
"""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):
|
||||
def _handle_icon_rename_request(self, event: tk.Event) -> None:
|
||||
"""Handles a rename request on an icon view item."""
|
||||
item_path = self._get_item_path_from_widget(event.widget)
|
||||
if item_path:
|
||||
@@ -133,7 +140,7 @@ class ViewManager:
|
||||
item_frame = item_frame.master
|
||||
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):
|
||||
def populate_icon_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
|
||||
"""
|
||||
Populates the file display with items in an icon grid layout.
|
||||
|
||||
@@ -157,7 +164,7 @@ class ViewManager:
|
||||
container_frame.bind("<Configure>", lambda e: self.dialog.icon_canvas.configure(
|
||||
scrollregion=self.dialog.icon_canvas.bbox("all")))
|
||||
|
||||
def _on_mouse_wheel(event):
|
||||
def _on_mouse_wheel(event: tk.Event) -> None:
|
||||
if event.num == 4:
|
||||
delta = -1
|
||||
elif event.num == 5:
|
||||
@@ -191,7 +198,7 @@ class ViewManager:
|
||||
break
|
||||
|
||||
if widget_to_focus:
|
||||
def scroll_to_widget():
|
||||
def scroll_to_widget() -> None:
|
||||
self.dialog.update_idletasks()
|
||||
if not widget_to_focus.winfo_exists():
|
||||
return
|
||||
@@ -207,7 +214,7 @@ 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):
|
||||
def _load_more_items_icon_view(self, container: ttk.Frame, scroll_handler: Callable, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> Optional[ttk.Frame]:
|
||||
"""
|
||||
Loads a batch of items into the icon view.
|
||||
|
||||
@@ -293,7 +300,7 @@ class ViewManager:
|
||||
self.dialog.currently_loaded_count = end_index
|
||||
return widget_to_focus
|
||||
|
||||
def populate_list_view(self, item_to_rename=None, item_to_select=None):
|
||||
def populate_list_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
|
||||
"""
|
||||
Populates the file display with items in a list (Treeview) layout.
|
||||
|
||||
@@ -334,7 +341,7 @@ class ViewManager:
|
||||
v_scrollbar.grid(row=0, column=1, sticky='ns')
|
||||
h_scrollbar.grid(row=1, column=0, sticky='ew')
|
||||
|
||||
def _on_scroll(*args):
|
||||
def _on_scroll(*args: Any) -> None:
|
||||
if self.dialog.currently_loaded_count < len(self.dialog.all_items) and self.dialog.tree.yview()[1] > 0.9:
|
||||
self._load_more_items_list_view()
|
||||
v_scrollbar.set(*args)
|
||||
@@ -358,7 +365,7 @@ class ViewManager:
|
||||
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):
|
||||
def _load_more_items_list_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Loads a batch of items into the list view.
|
||||
|
||||
@@ -414,7 +421,7 @@ class ViewManager:
|
||||
self.dialog.currently_loaded_count = end_index
|
||||
return item_found
|
||||
|
||||
def on_item_select(self, path, item_frame):
|
||||
def on_item_select(self, path: str, item_frame: ttk.Frame) -> None:
|
||||
"""
|
||||
Handles the selection of an item in the icon view.
|
||||
|
||||
@@ -439,7 +446,7 @@ class ViewManager:
|
||||
self.dialog.widget_manager.filename_entry.delete(0, tk.END)
|
||||
self.dialog.widget_manager.filename_entry.insert(0, os.path.basename(path))
|
||||
|
||||
def on_list_select(self, event):
|
||||
def on_list_select(self, event: tk.Event) -> None:
|
||||
"""Handles the selection of an item in the list view."""
|
||||
if not self.dialog.tree.selection():
|
||||
return
|
||||
@@ -453,7 +460,7 @@ class ViewManager:
|
||||
self.dialog.widget_manager.filename_entry.delete(0, tk.END)
|
||||
self.dialog.widget_manager.filename_entry.insert(0, item_text)
|
||||
|
||||
def on_list_context_menu(self, event):
|
||||
def on_list_context_menu(self, event: tk.Event) -> str:
|
||||
"""Shows the context menu for a list view item."""
|
||||
iid = self.dialog.tree.identify_row(event.y)
|
||||
if not iid:
|
||||
@@ -464,7 +471,7 @@ class ViewManager:
|
||||
self.dialog.file_op_manager._show_context_menu(event, item_path)
|
||||
return "break"
|
||||
|
||||
def on_item_double_click(self, path):
|
||||
def on_item_double_click(self, path: str) -> None:
|
||||
"""
|
||||
Handles a double-click on an icon view item.
|
||||
|
||||
@@ -482,7 +489,7 @@ class ViewManager:
|
||||
0, os.path.basename(path))
|
||||
self.dialog.on_save()
|
||||
|
||||
def on_list_double_click(self, event):
|
||||
def on_list_double_click(self, event: tk.Event) -> None:
|
||||
"""Handles a double-click on a list view item."""
|
||||
if not self.dialog.tree.selection():
|
||||
return
|
||||
@@ -499,7 +506,7 @@ class ViewManager:
|
||||
self.dialog.widget_manager.filename_entry.insert(0, item_text)
|
||||
self.dialog.on_save()
|
||||
|
||||
def _select_file_in_view(self, filename):
|
||||
def _select_file_in_view(self, filename: str) -> None:
|
||||
"""
|
||||
Programmatically selects a file in the current view.
|
||||
|
||||
@@ -524,7 +531,7 @@ class ViewManager:
|
||||
if hasattr(widget, 'item_path') and widget.item_path == target_path:
|
||||
self.on_item_select(widget.item_path, widget)
|
||||
|
||||
def scroll_to_widget():
|
||||
def scroll_to_widget() -> None:
|
||||
self.dialog.update_idletasks()
|
||||
if not widget.winfo_exists(): return
|
||||
y = widget.winfo_y()
|
||||
@@ -540,7 +547,7 @@ class ViewManager:
|
||||
self.dialog.after(100, scroll_to_widget)
|
||||
break
|
||||
|
||||
def _update_view_mode_buttons(self):
|
||||
def _update_view_mode_buttons(self) -> None:
|
||||
"""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(
|
||||
@@ -553,19 +560,19 @@ class ViewManager:
|
||||
self.dialog.widget_manager.icon_view_button.configure(
|
||||
style="Header.TButton.Borderless.Round")
|
||||
|
||||
def set_icon_view(self):
|
||||
def set_icon_view(self) -> None:
|
||||
"""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):
|
||||
def set_list_view(self) -> None:
|
||||
"""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):
|
||||
def toggle_hidden_files(self) -> None:
|
||||
"""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():
|
||||
@@ -580,7 +587,7 @@ class ViewManager:
|
||||
LocaleStrings.UI["show_hidden_files"])
|
||||
self.populate_files()
|
||||
|
||||
def on_filter_change(self, event):
|
||||
def on_filter_change(self, event: tk.Event) -> None:
|
||||
"""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:
|
||||
@@ -589,7 +596,7 @@ class ViewManager:
|
||||
break
|
||||
self.populate_files()
|
||||
|
||||
def _unbind_mouse_wheel_events(self):
|
||||
def _unbind_mouse_wheel_events(self) -> None:
|
||||
"""Unbinds all mouse wheel events from the dialog."""
|
||||
self.dialog.unbind_all("<MouseWheel>")
|
||||
self.dialog.unbind_all("<Button-4>")
|
||||
|
@@ -6,6 +6,8 @@ from datetime import datetime
|
||||
import subprocess
|
||||
import json
|
||||
import threading
|
||||
from typing import Optional, List, Tuple, Any, Dict
|
||||
|
||||
from shared_libs.message import MessageDialog
|
||||
from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools
|
||||
from cfd_app_config import AppConfig, CfdConfigManager, LocaleStrings, _
|
||||
@@ -18,11 +20,27 @@ from cfd_navigation_manager import NavigationManager
|
||||
from cfd_view_manager import ViewManager
|
||||
|
||||
class CustomFileDialog(tk.Toplevel):
|
||||
def __init__(self, parent, initial_dir=None, filetypes=None, dialog_mode="open", title=LocaleStrings.CFD["title"]):
|
||||
"""
|
||||
A custom file dialog window that provides functionalities for file selection,
|
||||
directory navigation, search, and file operations.
|
||||
"""
|
||||
def __init__(self, parent: tk.Widget, initial_dir: Optional[str] = None,
|
||||
filetypes: Optional[List[Tuple[str, str]]] = None,
|
||||
dialog_mode: str = "open", title: str = LocaleStrings.CFD["title"]):
|
||||
"""
|
||||
Initializes the CustomFileDialog.
|
||||
|
||||
Args:
|
||||
parent: The parent widget.
|
||||
initial_dir: The initial directory to display.
|
||||
filetypes: A list of filetype tuples, e.g., [('Text files', '*.txt')].
|
||||
dialog_mode: The dialog mode, either "open" or "save".
|
||||
title: The title of the dialog window.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
self.my_tool_tip = None
|
||||
self.dialog_mode = dialog_mode
|
||||
self.my_tool_tip: Optional[Tooltip] = None
|
||||
self.dialog_mode: str = dialog_mode
|
||||
|
||||
self.load_settings()
|
||||
|
||||
@@ -32,45 +50,45 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.minsize(min_width, min_height)
|
||||
|
||||
self.title(title)
|
||||
self.image = IconManager()
|
||||
self.image: IconManager = IconManager()
|
||||
width, height = map(
|
||||
int, self.settings["window_size_preset"].split('x'))
|
||||
LxTools.center_window_cross_platform(self, width, height)
|
||||
self.parent = parent
|
||||
self.parent: tk.Widget = parent
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
self.selected_file = None
|
||||
self.current_dir = os.path.abspath(
|
||||
self.selected_file: Optional[str] = None
|
||||
self.current_dir: str = os.path.abspath(
|
||||
initial_dir) if initial_dir else os.path.expanduser("~")
|
||||
self.filetypes = filetypes if filetypes else [(LocaleStrings.CFD["all_files"], "*.* ")]
|
||||
self.current_filter_pattern = self.filetypes[0][1]
|
||||
self.history = []
|
||||
self.history_pos = -1
|
||||
self.view_mode = tk.StringVar(
|
||||
self.filetypes: List[Tuple[str, str]] = filetypes if filetypes else [(LocaleStrings.CFD["all_files"], "*.* ")]
|
||||
self.current_filter_pattern: str = self.filetypes[0][1]
|
||||
self.history: List[str] = []
|
||||
self.history_pos: int = -1
|
||||
self.view_mode: tk.StringVar = tk.StringVar(
|
||||
value=self.settings.get("default_view_mode", "icons"))
|
||||
self.show_hidden_files = tk.BooleanVar(value=False)
|
||||
self.resize_job = None
|
||||
self.last_width = 0
|
||||
self.search_results = []
|
||||
self.search_mode = False
|
||||
self.original_path_text = ""
|
||||
self.items_to_load_per_batch = 250
|
||||
self.item_path_map = {}
|
||||
self.responsive_buttons_hidden = None
|
||||
self.search_job = None
|
||||
self.search_thread = None
|
||||
self.search_process = None
|
||||
self.show_hidden_files: tk.BooleanVar = tk.BooleanVar(value=False)
|
||||
self.resize_job: Optional[str] = None
|
||||
self.last_width: int = 0
|
||||
self.search_results: List[str] = []
|
||||
self.search_mode: bool = False
|
||||
self.original_path_text: str = ""
|
||||
self.items_to_load_per_batch: int = 250
|
||||
self.item_path_map: Dict[int, str] = {}
|
||||
self.responsive_buttons_hidden: Optional[bool] = None
|
||||
self.search_job: Optional[str] = None
|
||||
self.search_thread: Optional[threading.Thread] = None
|
||||
self.search_process: Optional[subprocess.Popen] = None
|
||||
|
||||
self.icon_manager = IconManager()
|
||||
self.style_manager = StyleManager(self)
|
||||
self.icon_manager: IconManager = IconManager()
|
||||
self.style_manager: StyleManager = StyleManager(self)
|
||||
|
||||
self.file_op_manager = FileOperationsManager(self)
|
||||
self.search_manager = SearchManager(self)
|
||||
self.navigation_manager = NavigationManager(self)
|
||||
self.view_manager = ViewManager(self)
|
||||
self.file_op_manager: FileOperationsManager = FileOperationsManager(self)
|
||||
self.search_manager: SearchManager = SearchManager(self)
|
||||
self.navigation_manager: NavigationManager = NavigationManager(self)
|
||||
self.view_manager: ViewManager = ViewManager(self)
|
||||
|
||||
self.widget_manager = WidgetManager(self, self.settings)
|
||||
self.widget_manager: WidgetManager = WidgetManager(self, self.settings)
|
||||
|
||||
self.widget_manager.filename_entry.bind("<Return>", self.search_manager.execute_search)
|
||||
|
||||
@@ -78,7 +96,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
|
||||
self.view_manager._update_view_mode_buttons()
|
||||
|
||||
def initial_load():
|
||||
def initial_load() -> None:
|
||||
"""Performs the initial loading and UI setup."""
|
||||
self.update_idletasks()
|
||||
self.last_width = self.widget_manager.file_list_frame.winfo_width()
|
||||
self._handle_responsive_buttons(self.winfo_width())
|
||||
@@ -94,18 +113,29 @@ class CustomFileDialog(tk.Toplevel):
|
||||
if self.dialog_mode == "save":
|
||||
self.bind("<Delete>", self.file_op_manager.delete_selected_item)
|
||||
|
||||
def load_settings(self):
|
||||
def load_settings(self) -> None:
|
||||
"""Loads settings from the configuration file."""
|
||||
self.settings = CfdConfigManager.load()
|
||||
size_preset = self.settings.get("window_size_preset", "1050x850")
|
||||
self.settings["window_size_preset"] = size_preset
|
||||
if hasattr(self, 'view_mode'):
|
||||
self.view_mode.set(self.settings.get("default_view_mode", "icons"))
|
||||
|
||||
def get_min_size_from_preset(self, preset):
|
||||
def get_min_size_from_preset(self, preset: str) -> Tuple[int, int]:
|
||||
"""
|
||||
Calculates the minimum window size based on a preset string.
|
||||
|
||||
Args:
|
||||
preset: The size preset string (e.g., "1050x850").
|
||||
|
||||
Returns:
|
||||
A tuple containing the minimum width and height.
|
||||
"""
|
||||
w, h = map(int, preset.split('x'))
|
||||
return max(650, w - 400), max(450, h - 400)
|
||||
|
||||
def reload_config_and_rebuild_ui(self):
|
||||
def reload_config_and_rebuild_ui(self) -> None:
|
||||
"""Reloads the configuration and rebuilds the entire UI."""
|
||||
self.load_settings()
|
||||
|
||||
self.geometry(self.settings["window_size_preset"])
|
||||
@@ -139,10 +169,12 @@ class CustomFileDialog(tk.Toplevel):
|
||||
else:
|
||||
self.navigation_manager.navigate_to(self.current_dir)
|
||||
|
||||
def open_settings_dialog(self):
|
||||
def open_settings_dialog(self) -> None:
|
||||
"""Opens the settings dialog."""
|
||||
SettingsDialog(self, dialog_mode=self.dialog_mode)
|
||||
|
||||
def update_animation_settings(self):
|
||||
def update_animation_settings(self) -> None:
|
||||
"""Updates the search animation icon based on current settings."""
|
||||
use_pillow = self.settings.get('use_pillow_animation', False)
|
||||
anim_type = self.settings.get('animation_type', 'double')
|
||||
is_running = self.widget_manager.search_animation.running
|
||||
@@ -168,7 +200,17 @@ class CustomFileDialog(tk.Toplevel):
|
||||
if is_running:
|
||||
self.widget_manager.search_animation.start()
|
||||
|
||||
def get_file_icon(self, filename, size='large'):
|
||||
def get_file_icon(self, filename: str, size: str = 'large') -> tk.PhotoImage:
|
||||
"""
|
||||
Gets the appropriate icon for a given filename.
|
||||
|
||||
Args:
|
||||
filename: The name of the file.
|
||||
size: The desired icon size ('large' or 'small').
|
||||
|
||||
Returns:
|
||||
A PhotoImage object for the corresponding file type.
|
||||
"""
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
|
||||
if ext == '.py':
|
||||
@@ -188,7 +230,13 @@ class CustomFileDialog(tk.Toplevel):
|
||||
return self.icon_manager.get_icon(f'iso_{size}')
|
||||
return self.icon_manager.get_icon(f'file_{size}')
|
||||
|
||||
def on_window_resize(self, event):
|
||||
def on_window_resize(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Handles the window resize event.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
if event.widget is self:
|
||||
if self.view_mode.get() == "icons" and not self.search_mode:
|
||||
new_width = self.widget_manager.file_list_frame.winfo_width()
|
||||
@@ -196,7 +244,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
if self.resize_job:
|
||||
self.after_cancel(self.resize_job)
|
||||
|
||||
def repopulate_icons():
|
||||
def repopulate_icons() -> None:
|
||||
"""Repopulates the file list icons."""
|
||||
self.update_idletasks()
|
||||
self.view_manager.populate_files()
|
||||
|
||||
@@ -205,7 +254,13 @@ class CustomFileDialog(tk.Toplevel):
|
||||
|
||||
self._handle_responsive_buttons(event.width)
|
||||
|
||||
def _handle_responsive_buttons(self, window_width):
|
||||
def _handle_responsive_buttons(self, window_width: int) -> None:
|
||||
"""
|
||||
Shows or hides buttons based on the window width.
|
||||
|
||||
Args:
|
||||
window_width: The current width of the window.
|
||||
"""
|
||||
threshold = 850
|
||||
container = self.widget_manager.responsive_buttons_container
|
||||
more_button = self.widget_manager.more_button
|
||||
@@ -221,7 +276,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
container.pack(side="left")
|
||||
self.responsive_buttons_hidden = should_be_hidden
|
||||
|
||||
def show_more_menu(self):
|
||||
def show_more_menu(self) -> None:
|
||||
"""Displays a 'more options' menu."""
|
||||
more_menu = tk.Menu(self, tearoff=0, background=self.style_manager.header, foreground=self.style_manager.color_foreground,
|
||||
activebackground=self.style_manager.selection_color, activeforeground=self.style_manager.color_foreground, relief='flat', borderwidth=0)
|
||||
|
||||
@@ -250,7 +306,13 @@ class CustomFileDialog(tk.Toplevel):
|
||||
y = more_button.winfo_rooty() + more_button.winfo_height()
|
||||
more_menu.tk_popup(x, y)
|
||||
|
||||
def on_sidebar_resize(self, event):
|
||||
def on_sidebar_resize(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Handles the sidebar resize event, adjusting button text visibility.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
current_width = event.width
|
||||
threshold_width = 100
|
||||
|
||||
@@ -265,11 +327,23 @@ class CustomFileDialog(tk.Toplevel):
|
||||
for btn, original_text in self.widget_manager.device_buttons:
|
||||
btn.config(text=original_text, compound="left")
|
||||
|
||||
def _on_devices_enter(self, event):
|
||||
def _on_devices_enter(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Shows the scrollbar when the mouse enters the devices area.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
self.widget_manager.devices_scrollbar.grid(
|
||||
row=1, column=1, sticky="ns")
|
||||
|
||||
def _on_devices_leave(self, event):
|
||||
def _on_devices_leave(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Hides the scrollbar when the mouse leaves the devices area.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
x, y = event.x_root, event.y_root
|
||||
widget_x = self.widget_manager.devices_canvas.winfo_rootx()
|
||||
widget_y = self.widget_manager.devices_canvas.winfo_rooty()
|
||||
@@ -281,7 +355,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
widget_y - buffer <= y <= widget_y + widget_height + buffer):
|
||||
self.widget_manager.devices_scrollbar.grid_remove()
|
||||
|
||||
def toggle_recursive_search(self):
|
||||
def toggle_recursive_search(self) -> None:
|
||||
"""Toggles the recursive search option on or off."""
|
||||
self.widget_manager.recursive_search.set(
|
||||
not self.widget_manager.recursive_search.get())
|
||||
if self.widget_manager.recursive_search.get():
|
||||
@@ -291,7 +366,13 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.widget_manager.recursive_button.configure(
|
||||
style="Header.TButton.Borderless.Round")
|
||||
|
||||
def update_status_bar(self, selected_path=None):
|
||||
def update_status_bar(self, selected_path: Optional[str] = None) -> None:
|
||||
"""
|
||||
Updates the status bar with disk usage and selected item information.
|
||||
|
||||
Args:
|
||||
selected_path: The path of the currently selected item.
|
||||
"""
|
||||
try:
|
||||
total, used, free = shutil.disk_usage(self.current_dir)
|
||||
free_str = self._format_size(free)
|
||||
@@ -320,6 +401,206 @@ class CustomFileDialog(tk.Toplevel):
|
||||
text=f"{LocaleStrings.CFD["free_space"]}: {LocaleStrings.CFD["unknown"]}")
|
||||
self.widget_manager.storage_bar['value'] = 0
|
||||
|
||||
def on_open(self) -> None:
|
||||
"""Handles the 'Open' action, closing the dialog if a file is selected."""
|
||||
if self.selected_file and os.path.isfile(self.selected_file):
|
||||
self.destroy()
|
||||
|
||||
def on_save(self) -> None:
|
||||
"""Handles the 'Save' action, setting the selected file and closing the dialog."""
|
||||
file_name = self.widget_manager.filename_entry.get()
|
||||
if file_name:
|
||||
self.selected_file = os.path.join(self.current_dir, file_name)
|
||||
self.destroy()
|
||||
|
||||
def on_cancel(self) -> None:
|
||||
"""Handles the 'Cancel' action, clearing the selection and closing the dialog."""
|
||||
self.selected_file = None
|
||||
self.destroy()
|
||||
|
||||
def get_selected_file(self) -> Optional[str]:
|
||||
"""
|
||||
Returns the path of the selected file.
|
||||
|
||||
Returns:
|
||||
The selected file path, or None if no file was selected.
|
||||
"""
|
||||
return self.selected_file
|
||||
|
||||
def update_action_buttons_state(self) -> None:
|
||||
"""Updates the state of action buttons (e.g., 'New Folder') based on directory permissions."""
|
||||
is_writable = os.access(self.current_dir, os.W_OK)
|
||||
state = tk.NORMAL if is_writable and self.dialog_mode != "open" else tk.DISABLED
|
||||
self.widget_manager.new_folder_button.config(state=state)
|
||||
self.widget_manager.new_file_button.config(state=state)
|
||||
|
||||
def _matches_filetype(self, filename: str) -> bool:
|
||||
"""
|
||||
Checks if a filename matches the current filetype filter.
|
||||
|
||||
Args:
|
||||
filename: The name of the file to check.
|
||||
|
||||
Returns:
|
||||
True if the file matches the filter, False otherwise.
|
||||
"""
|
||||
if self.current_filter_pattern == "*.*":
|
||||
return True
|
||||
|
||||
patterns = self.current_filter_pattern.lower().split()
|
||||
fn_lower = filename.lower()
|
||||
|
||||
for p in patterns:
|
||||
if p.startswith('*.'):
|
||||
if fn_lower.endswith(p[1:]):
|
||||
return True
|
||||
elif p.startswith('.'):
|
||||
if fn_lower.endswith(p):
|
||||
return True
|
||||
else:
|
||||
if fn_lower == p:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _format_size(self, size_bytes: Optional[int]) -> str:
|
||||
"""
|
||||
Formats a size in bytes into a human-readable string (KB, MB, GB).
|
||||
|
||||
Args:
|
||||
size_bytes: The size in bytes.
|
||||
|
||||
Returns:
|
||||
A formatted string representing the size.
|
||||
"""
|
||||
if size_bytes is None:
|
||||
return ""
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
if size_bytes < 1024**2:
|
||||
return f"{size_bytes/1024:.1f} KB"
|
||||
if size_bytes < 1024**3:
|
||||
return f"{size_bytes/1024**2:.1f} MB"
|
||||
return f"{size_bytes/1024**3:.1f} GB"
|
||||
|
||||
def shorten_text(self, text: str, max_len: int) -> str:
|
||||
"""
|
||||
Shortens a string to a maximum length, adding '...' if truncated.
|
||||
|
||||
Args:
|
||||
text: The text to shorten.
|
||||
max_len: The maximum allowed length.
|
||||
|
||||
Returns:
|
||||
The shortened text.
|
||||
"""
|
||||
return text if len(text) <= max_len else text[:max_len-3] + "..."
|
||||
|
||||
def _get_mounted_devices(self) -> List[Tuple[str, str, bool]]:
|
||||
"""
|
||||
Retrieves a list of mounted devices on the system.
|
||||
|
||||
Returns:
|
||||
A list of tuples, where each tuple contains the display name,
|
||||
mount point, and a boolean indicating if it's removable.
|
||||
"""
|
||||
devices: List[Tuple[str, str, bool]] = []
|
||||
root_disk_name: Optional[str] = None
|
||||
try:
|
||||
result = subprocess.run(['lsblk', '-J', '-o', 'NAME,MOUNTPOINT,FSTYPE,SIZE,RO,RM,TYPE,LABEL,PKNAME'],
|
||||
capture_output=True, text=True, check=True)
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
for block_device in data.get('blockdevices', []):
|
||||
if 'children' in block_device:
|
||||
for child_device in block_device['children']:
|
||||
if child_device.get('mountpoint') == '/':
|
||||
root_disk_name = block_device.get('name')
|
||||
break
|
||||
if root_disk_name:
|
||||
break
|
||||
|
||||
for block_device in data.get('blockdevices', []):
|
||||
if block_device.get('mountpoint') and
|
||||
block_device.get('type') not in ['loop', 'rom'] and
|
||||
block_device.get('mountpoint') != '/':
|
||||
|
||||
if block_device.get('name').startswith(root_disk_name) and not block_device.get('rm', False):
|
||||
pass
|
||||
else:
|
||||
name = block_device.get('name')
|
||||
mountpoint = block_device.get('mountpoint')
|
||||
label = block_device.get('label')
|
||||
removable = block_device.get('rm', False)
|
||||
|
||||
display_name = label if label else name
|
||||
devices.append((display_name, mountpoint, removable))
|
||||
|
||||
if 'children' in block_device:
|
||||
for child_device in block_device['children']:
|
||||
if child_device.get('mountpoint') and
|
||||
child_device.get('type') not in ['loop', 'rom'] and
|
||||
child_device.get('mountpoint') != '/':
|
||||
|
||||
if block_device.get('name') == root_disk_name and not child_device.get('rm', False):
|
||||
pass
|
||||
else:
|
||||
name = child_device.get('name')
|
||||
mountpoint = child_device.get('mountpoint')
|
||||
label = child_device.get('label')
|
||||
removable = child_device.get('rm', False)
|
||||
|
||||
display_name = label if label else name
|
||||
devices.append(
|
||||
(display_name, mountpoint, removable))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error getting mounted devices: {e}")
|
||||
return devices
|
||||
|
||||
def _show_tooltip(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Displays a tooltip for the search animation icon.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
if hasattr(self, 'tooltip_window') and self.tooltip_window.winfo_exists():
|
||||
return
|
||||
|
||||
tooltip_text = LocaleStrings.UI["start_search"] if not self.widget_manager.search_animation.running else LocaleStrings.UI["cancel_search"]
|
||||
|
||||
x = self.widget_manager.search_animation.winfo_rootx() + 25
|
||||
y = self.widget_manager.search_animation.winfo_rooty() + 25
|
||||
self.tooltip_window = tk.Toplevel(self)
|
||||
self.tooltip_window.wm_overrideredirect(True)
|
||||
self.tooltip_window.wm_geometry(f"+{x}+{y}")
|
||||
label = tk.Label(self.tooltip_window, text=tooltip_text, relief="solid", borderwidth=1)
|
||||
label.pack()
|
||||
|
||||
def _hide_tooltip(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Hides the tooltip.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
if hasattr(self, 'tooltip_window') and self.tooltip_window.winfo_exists():
|
||||
self.tooltip_window.destroy()
|
||||
"'{os.path.basename(selected_path)}' ({content_count} {LocaleStrings.CFD["entries"]})"
|
||||
else:
|
||||
status_text = f"'{os.path.basename(selected_path)}'"
|
||||
else:
|
||||
size = os.path.getsize(selected_path)
|
||||
size_str = self._format_size(size)
|
||||
status_text = f"'{os.path.basename(selected_path)}' {LocaleStrings.VIEW["size"]}: {size_str}"
|
||||
self.widget_manager.search_status_label.config(text=status_text)
|
||||
except FileNotFoundError:
|
||||
self.widget_manager.search_status_label.config(
|
||||
text=LocaleStrings.CFD["directory_not_found"])
|
||||
self.widget_manager.storage_label.config(
|
||||
text=f"{LocaleStrings.CFD["free_space"]}: {LocaleStrings.CFD["unknown"]}")
|
||||
self.widget_manager.storage_bar['value'] = 0
|
||||
|
||||
def on_open(self):
|
||||
if self.selected_file and os.path.isfile(self.selected_file):
|
||||
self.destroy()
|
||||
|
Reference in New Issue
Block a user