SXXXXXXX_GitUtility/gitutility/gui/diff_summary_viewer.py
2025-05-05 10:28:19 +02:00

310 lines
12 KiB
Python

# --- 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("<Double-1>", 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("<<TreeviewSelect>>", 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 ---