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: 'CustomFileDialog') -> None: """ Initializes the NavigationManager. Args: dialog: The main CustomFileDialog instance. """ self.dialog = dialog def handle_path_entry_return(self, event: tk.Event) -> None: """ Handles the Return key press in the path entry field. """ path_text = self.dialog.widget_manager.path_entry.get().strip() is_sftp = self.dialog.current_fs_type == "sftp" if is_sftp: self.navigate_to(path_text) else: potential_path = os.path.realpath(os.path.expanduser(path_text)) if os.path.isdir(potential_path): self.navigate_to(potential_path) elif os.path.isfile(potential_path): directory = os.path.dirname(potential_path) filename = os.path.basename(potential_path) self.navigate_to(directory, file_to_select=filename) else: 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: str, file_to_select: Optional[str] = None) -> None: """ Navigates to a specified directory path, supporting both local and SFTP filesystems. """ try: is_sftp = self.dialog.current_fs_type == "sftp" if is_sftp: # Resolve tilde to the remote home directory for SFTP if path == '~' or path.startswith('~/'): home_dir = self.dialog.sftp_manager.home_dir if home_dir: # Manual path joining with forward slashes if path.startswith('~/'): # home_dir might be '/', so avoid '//' path = home_dir.rstrip('/') + '/' + path[2:] else: path = home_dir else: # Fallback if home_dir is not set path = '/' # The SFTP manager will handle path validation. if not self.dialog.sftp_manager.path_is_dir(path): self.dialog.widget_manager.search_status_label.config( text=f"Error: Directory '{os.path.basename(path)}' not found on SFTP server.") return real_path = path else: # Local filesystem logic real_path = os.path.realpath(os.path.abspath(os.path.expanduser(path))) if not os.path.isdir(real_path): self.dialog.widget_manager.search_status_label.config( text=f"{LocaleStrings.CFD['error_title']}: {LocaleStrings.CFD['directory']} '{os.path.basename(path)}' {LocaleStrings.CFD['not_found']}") return if not os.access(real_path, os.R_OK): self.dialog.widget_manager.search_status_label.config( text=f"{LocaleStrings.CFD['access_to']} '{os.path.basename(path)}' {LocaleStrings.CFD['denied']}") return self.dialog.current_dir = real_path if self.dialog.history_pos < len(self.dialog.history) - 1: self.dialog.history = self.dialog.history[:self.dialog.history_pos + 1] if not self.dialog.history or self.dialog.history[-1] != self.dialog.current_dir: self.dialog.history.append(self.dialog.current_dir) self.dialog.history_pos = len(self.dialog.history) - 1 self.dialog.widget_manager.search_animation.stop() self.dialog.selected_item_frames.clear() self.dialog.result = None self.dialog.view_manager.populate_files(item_to_select=file_to_select) self.update_nav_buttons() self.dialog.update_selection_info() self.dialog.update_action_buttons_state() except Exception as e: error_message = f"Error navigating to '{path}': {e}" self.dialog.widget_manager.search_status_label.config(text=error_message) def go_back(self) -> None: """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] self._update_ui_after_navigation() 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 self.dialog.current_dir = self.dialog.history[self.dialog.history_pos] self._update_ui_after_navigation() def go_up_level(self) -> None: """Navigates to the parent directory of the current directory.""" if self.dialog.current_fs_type == "sftp": if self.dialog.current_dir and self.dialog.current_dir != "/": new_path = self.dialog.current_dir.rsplit('/', 1)[0] if not new_path: new_path = "/" self.navigate_to(new_path) else: new_path = os.path.dirname(self.dialog.current_dir) if new_path != self.dialog.current_dir: self.navigate_to(new_path) def _update_ui_after_navigation(self) -> None: """Updates all necessary UI components after a navigation action.""" self.dialog.view_manager.populate_files() self.update_nav_buttons() self.dialog.update_selection_info() self.dialog.update_action_buttons_state() 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) self.dialog.widget_manager.forward_button.config(state=tk.NORMAL if self.dialog.history_pos < len( self.dialog.history) - 1 else tk.DISABLED)