310 lines
12 KiB
Python
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 ---
|