# --- FILE: gitsync_tool/gui/diff_summary_viewer.py --- import tkinter as tk from tkinter import ttk, messagebox # Aggiunto messagebox import os from typing import ( List, Tuple, Optional, TYPE_CHECKING, ) # Aggiunto TYPE_CHECKING e Optional # ---<<< MODIFICA IMPORT >>>--- from gitutility.logging_setup import log_handler # Importa DiffViewerWindow e Tooltip dal nuovo percorso from gitutility.gui.tooltip import Tooltip from gitutility.gui.diff_viewer import DiffViewerWindow # Forward declaration per GitCommands se non importato if TYPE_CHECKING: from gitutility.commands.git_commands import GitCommands # ---<<< FINE MODIFICA IMPORT >>>--- class DiffSummaryWindow(tk.Toplevel): """ Toplevel window to display a summary of changed files between two Git references. Allows opening the detailed DiffViewerWindow for a selected file. """ # STATUS_MAP rimane invariato STATUS_MAP = { "A": "Added", "M": "Modified", "D": "Deleted", "R": "Renamed", "C": "Copied", "T": "Type Change", "U": "Unmerged", "X": "Unknown", "B": "Broken", } # __init__ invariato (usa DiffViewerWindow e Tooltip importati) def __init__( self, master, git_commands: "GitCommands", # Usa forward declaration repo_path: str, ref1: str, ref2: str, changed_files_status: List[str], ): # ... (codice __init__ invariato) ... super().__init__(master) func_name = "__init__ (DiffSummary)" if not git_commands: raise ValueError("git_commands instance is required.") if not repo_path: raise ValueError("repo_path is required.") if not ref1 or not ref2: raise ValueError("Both ref1 and ref2 are required.") self.master = master self.git_commands = git_commands self.repo_path = repo_path self.ref1 = ref1 self.ref2 = ref2 self.changed_files_data = self._parse_diff_tree_output(changed_files_status) log_handler.log_info( f"Opening Diff Summary: '{ref1}' vs '{ref2}' ({len(self.changed_files_data)} files)", func_name=func_name, ) self.title(f"Differences: {ref1} vs {ref2}") self.geometry("700x450") self.minsize(450, 300) self.grab_set() self.transient(master) self._create_widgets() self._populate_treeview() self._center_window(master) if hasattr(self, "tree"): self.tree.focus_set() def _parse_diff_tree_output( self, diff_tree_lines: List[str] ) -> List[Tuple[str, str, str, str, str]]: # Aggiornato tipo ritorno # ... (codice _parse_diff_tree_output invariato) ... parsed_data = [] for line in diff_tree_lines: parts = line.split("\t") if not parts: continue status_char = parts[0].strip() if status_char.startswith(("R", "C")) and len(status_char) > 1: status_char = status_char[0] display_status = self.STATUS_MAP.get(status_char, "Unknown") file_path_display = "Error parsing path" old_p, new_p = "", "" # Init path vars if status_char in ("R", "C"): if len(parts) == 3: old_p, new_p = parts[1], parts[2] file_path_display = f"{old_p} -> {new_p}" else: log_handler.log_warning( f"Could not parse Renamed/Copied line: {line}", func_name="_parse_diff_tree_output", ) elif status_char in ("A", "M", "D", "T"): if len(parts) == 2: file_path = parts[1] file_path_display = file_path old_p = file_path new_p = file_path else: log_handler.log_warning( f"Could not parse A/M/D/T line: {line}", func_name="_parse_diff_tree_output", ) else: log_handler.log_warning( f"Unknown status or parse error for line: {line}", func_name="_parse_diff_tree_output", ) file_path_display = line parsed_data.append( (status_char, display_status, file_path_display, old_p, new_p) ) return parsed_data def _create_widgets(self): # ... (codice _create_widgets invariato, usa Tooltip importato) ... main_frame = ttk.Frame(self, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) main_frame.rowconfigure(0, weight=1) main_frame.columnconfigure(0, weight=1) columns = ("status", "file") self.tree = ttk.Treeview( main_frame, columns=columns, show="headings", selectmode="browse" ) self.tree.heading( "status", text="Status", command=lambda: self._sort_column("status", False) ) self.tree.heading( "file", text="File Path", command=lambda: self._sort_column("file", False) ) self.tree.column("status", width=80, stretch=tk.NO, anchor="w") self.tree.column("file", width=550, stretch=tk.YES, anchor="w") tree_scrollbar = ttk.Scrollbar( main_frame, orient=tk.VERTICAL, command=self.tree.yview ) self.tree.configure(yscrollcommand=tree_scrollbar.set) self.tree.grid(row=0, column=0, sticky="nsew") tree_scrollbar.grid(row=0, column=1, sticky="ns") self.tree.bind("", self._on_item_double_click) button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0)) button_frame.grid(row=1, column=0, columnspan=2, sticky="ew") button_frame.columnconfigure(0, weight=1) self.view_diff_button = ttk.Button( button_frame, text="View File Diff", command=self._open_selected_diff, state=tk.DISABLED, ) self.view_diff_button.grid(row=0, column=1, padx=5) Tooltip( self.view_diff_button, "Show detailed differences for the selected file." ) self.tree.bind("<>", self._on_selection_change) def _populate_treeview(self): # ... (codice _populate_treeview invariato) ... if not hasattr(self, "tree"): return for item in self.tree.get_children(): self.tree.delete(item) for idx, data_tuple in enumerate(self.changed_files_data): status_char, display_status, file_path_display, path1, path2 = data_tuple self.tree.insert( "", tk.END, iid=idx, values=(display_status, file_path_display) ) self._sort_column("file", False) def _sort_column(self, col, reverse): # ... (codice _sort_column invariato) ... try: data = [ (self.tree.set(child, col), child) for child in self.tree.get_children("") ] data.sort( key=lambda t: t[0].lower() if isinstance(t[0], str) else t[0], reverse=reverse, ) for index, (val, child) in enumerate(data): self.tree.move(child, "", index) self.tree.heading(col, command=lambda: self._sort_column(col, not reverse)) except Exception as e: log_handler.log_error( f"Error sorting Treeview column '{col}': {e}", func_name="_sort_column" ) def _on_selection_change(self, event=None): # ... (codice _on_selection_change invariato) ... if hasattr(self, "view_diff_button"): selected_items = self.tree.selection() state = tk.NORMAL if selected_items else tk.DISABLED self.view_diff_button.config(state=state) def _on_item_double_click(self, event=None): # ... (codice _on_item_double_click invariato) ... self._open_selected_diff() def _open_selected_diff(self): # ... (codice _open_selected_diff invariato, usa messagebox e DiffViewerWindow importati) ... if not hasattr(self, "tree"): return selected_items = self.tree.selection() if not selected_items: log_handler.log_warning( "View Diff clicked but no item selected.", func_name="_open_selected_diff", ) return selected_iid = selected_items[0] try: item_index = int(selected_iid) if 0 <= item_index < len(self.changed_files_data): data_tuple = self.changed_files_data[item_index] status_char, _, _, path1, path2 = data_tuple file_to_diff = "" if status_char == "D": file_to_diff = path1 else: file_to_diff = path2 # A, M, T, R, C usa path destinazione if not file_to_diff: log_handler.log_error( f"Could not determine file path for diff for item {selected_iid}, data: {data_tuple}", func_name="_open_selected_diff", ) messagebox.showerror( "Error", "Could not determine the file path for comparison.", parent=self, ) return log_handler.log_info( f"Opening detailed diff for: '{file_to_diff}' ({self.ref1} vs {self.ref2})", func_name="_open_selected_diff", ) DiffViewerWindow( master=self, git_commands=self.git_commands, repo_path=self.repo_path, relative_file_path=file_to_diff, ref1=self.ref1, ref2=self.ref2, ) else: log_handler.log_error( f"Selected Treeview item IID '{selected_iid}' out of range for data.", func_name="_open_selected_diff", ) messagebox.showerror( "Error", "Internal error: Selected item data not found.", parent=self, ) except ValueError: log_handler.log_error( f"Invalid Treeview item IID: '{selected_iid}'", func_name="_open_selected_diff", ) messagebox.showerror( "Error", "Internal error: Invalid item selected.", parent=self ) except Exception as e: log_handler.log_exception( f"Error opening detailed diff: {e}", func_name="_open_selected_diff" ) messagebox.showerror( "Error", f"Could not open detailed diff viewer:\n{e}", parent=self ) def _center_window(self, parent): # ... (codice _center_window invariato) ... func_name = "_center_window (DiffSummary)" try: self.update_idletasks() px = parent.winfo_rootx() py = parent.winfo_rooty() pw = parent.winfo_width() ph = parent.winfo_height() ww = self.winfo_width() wh = self.winfo_height() x = px + (pw // 2) - (ww // 2) y = py + (ph // 2) - (wh // 2) sw = self.winfo_screenwidth() sh = self.winfo_screenheight() x = max(0, min(x, sw - ww)) y = max(0, min(y, sh - wh)) self.geometry(f"+{int(x)}+{int(y)}") except Exception as e: log_handler.log_error( f"Could not center DiffSummaryWindow: {e}", func_name=func_name ) # --- END OF FILE gitsync_tool/gui/diff_summary_viewer.py ---