# --- FILE: gitsync_tool/gui/window_handler.py --- import os import re from typing import TYPE_CHECKING, Optional, List, Dict, Any # Importazioni dal pacchetto from gitutility.logging_setup import log_handler from gitutility.gui.dialogs import ( CreateTagDialog, CreateBranchDialog, CloneFromRemoteDialog, ) from gitutility.gui.purge_dialog import PurgeConfirmationDialog from gitutility.gui.editors import GitignoreEditorWindow from gitutility.gui.diff_viewer import DiffViewerWindow from gitutility.gui.commit_detail_window import CommitDetailWindow from gitutility.gui.diff_summary_viewer import DiffSummaryWindow from gitutility.async_tasks import async_workers # Forward reference per evitare importazioni circolari if TYPE_CHECKING: from gitutility.app import GitSvnSyncApp from gitutility.commands.git_commands import GitCommands class WindowHandler: """ Handles the creation and logic for all secondary windows, dialogs, and editors within the application. """ def __init__(self, app_instance: "GitSvnSyncApp"): """ Initializes the WindowHandler. Args: app_instance: The main GitSvnSyncApp instance. """ self.app = app_instance self.master = app_instance.master self.main_frame = app_instance.main_frame self.git_commands: "GitCommands" = app_instance.git_commands log_handler.log_debug("WindowHandler initialized.", func_name="__init__") # --- Tag and Branch Dialog Handlers --- def handle_create_tag(self) -> None: """Handles tag creation: shows dialog, suggests name, starts async op.""" func_name = "handle_create_tag" log_handler.log_info(f"--- Action Triggered: Create Tag ---", func_name=func_name) svn_path = self.app._get_and_validate_svn_path("Create Tag") if not svn_path or not self.app._is_repo_ready(svn_path): log_handler.log_warning("Create Tag failed: Repo not ready.", func_name=func_name) self.main_frame.show_error("Action Failed", "Repository is not ready.") return self.main_frame.update_status_bar("Processing: Generating tag suggestion...") suggested_name = self.app._generate_next_tag_suggestion(svn_path) self.main_frame.update_status_bar("Ready for tag input.") dialog = CreateTagDialog(self.master, suggested_tag_name=suggested_name) tag_info = dialog.result if tag_info: tag_name, tag_message = tag_info log_handler.log_info(f"User provided tag: '{tag_name}'", func_name=func_name) args = (self.app.action_handler, svn_path, tag_name, tag_message) self.app._start_async_operation( worker_func=async_workers.run_create_tag_async, args_tuple=args, context_dict={ "context": "create_tag", "status_msg": f"Creating tag '{tag_name}'", "committed_flag_possible": True, }, ) else: log_handler.log_info("Tag creation cancelled.", func_name=func_name) self.main_frame.update_status_bar("Cancelled.") def handle_create_branch(self) -> None: """Handles branch creation: shows dialog, starts async operation.""" func_name = "handle_create_branch" log_handler.log_info(f"--- Action Triggered: Create Branch ---", func_name=func_name) svn_path = self.app._get_and_validate_svn_path("Create Branch") if not svn_path or not self.app._is_repo_ready(svn_path): log_handler.log_warning("Create Branch failed: Repo not ready.", func_name=func_name) self.main_frame.show_error("Action Failed", "Repository is not ready.") return self.main_frame.update_status_bar("Ready for branch name input.") dialog = CreateBranchDialog(self.master) branch_name = dialog.result if branch_name: log_handler.log_info(f"User provided branch name: '{branch_name}'", func_name=func_name) args = (self.app.action_handler, svn_path, branch_name) self.app._start_async_operation( worker_func=async_workers.run_create_branch_async, args_tuple=args, context_dict={ "context": "create_branch", "status_msg": f"Creating branch '{branch_name}'", "new_branch_name": branch_name, }, ) else: log_handler.log_info("Branch creation cancelled.", func_name=func_name) self.main_frame.update_status_bar("Cancelled.") # --- Clone and Purge Dialog Handlers --- def handle_clone_remote_repo(self) -> None: """Handles the 'Clone from Remote...' action.""" func_name = "handle_clone_remote_repo" log_handler.log_info(f"--- Action Triggered: Clone Remote Repository ---", func_name=func_name) dialog = CloneFromRemoteDialog(self.master) dialog_result = dialog.result if not dialog_result: log_handler.log_info("Clone operation cancelled by user.", func_name=func_name) self.main_frame.update_status_bar("Clone cancelled.") return remote_url, local_parent_dir, profile_name_input = dialog_result try: repo_name_from_url = os.path.basename(remote_url) if repo_name_from_url.lower().endswith(".git"): repo_name_from_url = repo_name_from_url[:-4] if not repo_name_from_url: raise ValueError("Could not derive repository name from URL.") target_clone_dir = os.path.join(local_parent_dir, repo_name_from_url) target_clone_dir = os.path.abspath(target_clone_dir) if profile_name_input: final_profile_name = profile_name_input if final_profile_name in self.app.config_manager.get_profile_sections(): raise ValueError(f"Profile name '{final_profile_name}' already exists.") else: final_profile_name = repo_name_from_url counter = 1 while final_profile_name in self.app.config_manager.get_profile_sections(): final_profile_name = f"{repo_name_from_url}_{counter}" counter += 1 if os.path.exists(target_clone_dir): raise ValueError(f"Clone failed: Target directory already exists:\n{target_clone_dir}") except ValueError as ve: log_handler.log_error(f"Clone configuration error: {ve}", func_name=func_name) self.main_frame.show_error("Configuration Error", str(ve)) return args = (self.git_commands, remote_url, target_clone_dir, final_profile_name) self.app._start_async_operation( worker_func=async_workers.run_clone_remote_async, args_tuple=args, context_dict={ "context": "clone_remote", "status_msg": f"Cloning '{repo_name_from_url}'...", "clone_success_data": { "profile_name": final_profile_name, "cloned_path": target_clone_dir, "remote_url": remote_url, } } ) def handle_purge_confirmation(self, repo_path: str, purgeable_files: List[Dict[str, Any]]): """Shows the purge confirmation dialog and starts the purge if confirmed.""" func_name = "handle_purge_confirmation" dialog = PurgeConfirmationDialog(self.master, purgeable_files, repo_path) if dialog.result: log_handler.log_warning("User confirmed DESTRUCTIVE history purge.", func_name=func_name) remote_name = self.main_frame.remote_name_var.get().strip() or "origin" remote_url = self.main_frame.remote_url_var.get().strip() if not remote_url: self.main_frame.show_error("Action Failed", "Remote URL must be configured.") self.app._reenable_widgets_after_modal() return file_paths = [item['path'] for item in purgeable_files] args = (self.app.history_cleaner, repo_path, file_paths, remote_name, remote_url) self.app._start_async_operation( worker_func=async_workers.run_purge_files_from_history_async, args_tuple=args, context_dict={ "context": "purge_history", "status_msg": "Purging files from history (this may take a while)...", } ) else: log_handler.log_info("User cancelled history purge operation.", func_name=func_name) self.main_frame.update_status_bar("History clean-up cancelled.") self.app._reenable_widgets_after_modal() # --- Editor and Viewer Handlers --- def handle_open_gitignore_editor(self) -> None: """Opens the .gitignore editor window.""" func_name = "handle_open_gitignore_editor" log_handler.log_info(f"--- Action Triggered: Edit .gitignore ---", func_name=func_name) svn_path = self.app._get_and_validate_svn_path("Edit .gitignore") if not svn_path or not self.app._is_repo_ready(svn_path): log_handler.log_warning("Cannot edit .gitignore: Repo not ready.", func_name=func_name) self.main_frame.show_error("Action Failed", "Select a valid and prepared repository.") return gitignore_path = os.path.join(svn_path, ".gitignore") try: GitignoreEditorWindow( master=self.master, gitignore_path=gitignore_path, on_save_success_callback=self.app._handle_gitignore_save ) except Exception as e: log_handler.log_exception(f"Error opening .gitignore editor: {e}", func_name=func_name) self.main_frame.show_error("Editor Error", f"Could not open editor:\n{e}") def handle_open_diff_viewer(self, file_status_line: str): """Opens the Diff Viewer for a file from the 'changed files' list.""" func_name = "handle_open_diff_viewer" log_handler.log_info(f"--- Action Triggered: Open Diff Viewer ---", func_name=func_name) svn_path = self.app._get_and_validate_svn_path("Open Diff Viewer") if not svn_path: return relative_path = self.app._extract_path_from_status_line(file_status_line) if not relative_path: log_handler.log_error(f"Could not extract path from: {file_status_line}", func_name=func_name) self.main_frame.show_error("Path Error", f"Could not parse file path from line:\n{file_status_line}") return status_code = file_status_line.strip('\x00').strip()[:2].strip() if status_code == 'D': self.main_frame.show_info("Diff Not Applicable", "Cannot diff a deleted file against the working directory.") return if status_code in ['??', '!!']: self.main_frame.show_info("Diff Not Applicable", "Cannot diff an untracked or ignored file.") return try: DiffViewerWindow( master=self.master, git_commands=self.git_commands, repo_path=svn_path, relative_file_path=relative_path, ref1='WORKING_DIR', ref2='HEAD' ) except Exception as e: log_handler.log_exception(f"Error opening diff viewer: {e}", func_name=func_name) self.main_frame.show_error("Diff Viewer Error", f"Could not display diff:\n{e}") def handle_show_commit_details(self, commit_details: Dict[str, Any]): """Opens the CommitDetailWindow to display commit details.""" func_name = "handle_show_commit_details" if not isinstance(commit_details, dict) or not commit_details.get('hash_full'): log_handler.log_error("Invalid commit details received.", func_name=func_name) self.main_frame.show_error("Display Error", "Internal error: Invalid commit data.") self.app._reenable_widgets_after_modal() return try: log_handler.log_debug(f"Opening CommitDetailWindow for {commit_details.get('hash_full')[:7]}...", func_name=func_name) CommitDetailWindow( master=self.master, commit_data=commit_details, open_diff_callback=self._handle_open_commit_file_diff ) log_handler.log_info("Commit Detail window closed.", func_name=func_name) self.main_frame.update_status_bar("Ready.") except Exception as e_detail: log_handler.log_exception(f"Error opening commit detail window: {e_detail}", func_name=func_name) self.main_frame.show_error("Display Error", f"Could not display commit details:\n{e_detail}") finally: self.app._reenable_widgets_after_modal() def _handle_open_commit_file_diff( self, commit_hash: str, file_status: str, file_path: str, old_file_path: Optional[str] = None ): """Callback to open diff for a file from the commit detail view.""" func_name = "_handle_open_commit_file_diff" log_handler.log_info(f"Requesting diff for file '{file_path}' in commit '{commit_hash[:7]}'", func_name=func_name) svn_path = self.app._get_and_validate_svn_path("Open Commit File Diff") if not svn_path: return ref1 = f"{commit_hash}^" ref2 = commit_hash if file_status == 'D': self.main_frame.show_info("Diff Not Applicable", "Diff for deleted files in this view is not supported.") return try: DiffViewerWindow( master=self.master, git_commands=self.git_commands, repo_path=svn_path, relative_file_path=file_path, ref1=ref1, ref2=ref2 ) except Exception as e_diff: log_handler.log_exception(f"Error opening commit file diff viewer: {e_diff}", func_name=func_name) self.main_frame.show_error("Diff Viewer Error", f"Could not display file changes:\n{e_diff}") def handle_show_comparison_summary( self, ref1: str, ref2: str, repo_path: str, changed_files: List[str] ): """Opens the comparison summary window.""" func_name = "handle_show_comparison_summary" log_handler.log_debug(f"Attempting to show comparison summary: {ref1} vs {ref2}", func_name=func_name) try: DiffSummaryWindow( master=self.master, git_commands=self.git_commands, repo_path=repo_path, ref1=ref1, ref2=ref2, changed_files_status=changed_files, ) self.main_frame.update_status_bar("Ready.") except Exception as e_summary: log_handler.log_exception(f"Error opening diff summary window: {e_summary}", func_name=func_name) self.main_frame.show_error("Display Error", f"Could not display comparison results:\n{e_summary}") finally: self.app._reenable_widgets_after_modal()