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

324 lines
14 KiB
Python

# --- FILE: gitsync_tool/gui/commit_detail_window.py ---
import tkinter as tk
from tkinter import ttk, messagebox # Aggiunto messagebox
from tkinter import scrolledtext
import os
from typing import Dict, Any, List, Tuple, Callable, Optional
# ---<<< MODIFICA IMPORT >>>---
# Importa log_handler dal nuovo percorso
from gitutility.logging_setup import log_handler
# Importa Tooltip dal nuovo percorso
from gitutility.gui.tooltip import Tooltip
# Non servono altri import specifici dell'applicazione qui
# ---<<< FINE MODIFICA IMPORT >>>---
class CommitDetailWindow(tk.Toplevel):
"""
Toplevel window to display detailed information about a specific Git commit,
including metadata and a list of changed files. Allows opening a diff viewer
for selected files.
"""
# STATUS_MAP rimane invariato
STATUS_MAP: Dict[str, str] = {
"A": "Added",
"M": "Modified",
"D": "Deleted",
"R": "Renamed",
"C": "Copied",
"T": "Type Change",
"U": "Unmerged",
"X": "Unknown",
"B": "Broken",
}
# __init__ rimane invariato (prende master, commit_data, open_diff_callback)
def __init__(
self,
master: tk.Misc,
commit_data: Dict[str, Any],
open_diff_callback: Callable[[str, str, str, Optional[str]], None],
):
# ... (codice __init__ invariato) ...
super().__init__(master)
func_name: str = "__init__ (CommitDetail)"
if not isinstance(commit_data, dict):
raise TypeError("commit_data must be a dictionary.")
if not callable(open_diff_callback):
raise TypeError("open_diff_callback must be callable.")
self.commit_data: Dict[str, Any] = commit_data
self.open_diff_callback: Callable[[str, str, str, Optional[str]], None] = (
open_diff_callback
)
self.commit_hash: Optional[str] = commit_data.get("hash_full")
self.short_hash: str = self.commit_hash[:7] if self.commit_hash else "N/A"
log_handler.log_info(
f"Opening Commit Detail window for {self.short_hash}", func_name=func_name
)
self.title(f"Commit Details - {self.short_hash}")
self.geometry("800x600")
self.minsize(550, 400)
self.grab_set()
self.transient(master)
self._create_widgets()
self._populate_details()
self._center_window(master)
if hasattr(self, "files_tree"):
self.files_tree.focus_set()
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.columnconfigure(0, weight=1)
main_frame.rowconfigure(1, weight=0)
main_frame.rowconfigure(3, weight=1)
main_frame.rowconfigure(5, weight=2)
meta_frame = ttk.LabelFrame(main_frame, text="Commit Info", padding=(10, 5))
meta_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
meta_frame.columnconfigure(1, weight=1)
row_idx: int = 0
ttk.Label(meta_frame, text="Commit Hash:").grid(
row=row_idx, column=0, sticky="nw", padx=5, pady=2
)
self.hash_label = ttk.Label(
meta_frame, text="", anchor="w", font=("Consolas", 9)
)
self.hash_label.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2)
Tooltip(self.hash_label, "Full commit SHA-1 hash.")
row_idx += 1
ttk.Label(meta_frame, text="Author:").grid(
row=row_idx, column=0, sticky="nw", padx=5, pady=2
)
self.author_label = ttk.Label(meta_frame, text="", anchor="w")
self.author_label.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2)
Tooltip(self.author_label, "Commit author name and email.")
row_idx += 1
ttk.Label(meta_frame, text="Date:").grid(
row=row_idx, column=0, sticky="nw", padx=5, pady=2
)
self.date_label = ttk.Label(meta_frame, text="", anchor="w")
self.date_label.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2)
Tooltip(self.date_label, "Author date.")
row_idx += 1
ttk.Label(main_frame, text="Subject:").grid(
row=1, column=0, sticky="w", padx=5, pady=(5, 0)
)
self.subject_label = ttk.Label(
main_frame,
text="",
anchor="w",
font=("Segoe UI", 10, "bold"),
relief=tk.GROOVE,
padding=5,
)
self.subject_label.grid(row=2, column=0, sticky="ew", padx=5, pady=(0, 5))
Tooltip(self.subject_label, "Commit subject line.")
ttk.Label(main_frame, text="Message Body:").grid(
row=3, column=0, sticky="w", padx=5, pady=(0, 0)
)
self.body_text = scrolledtext.ScrolledText(
master=main_frame,
height=6,
wrap=tk.WORD,
font=("Segoe UI", 9),
state=tk.DISABLED,
padx=5,
pady=5,
borderwidth=1,
relief=tk.SUNKEN,
)
self.body_text.grid(row=4, column=0, sticky="nsew", padx=5, pady=(0, 5))
Tooltip(self.body_text, "Full commit message body.")
files_frame = ttk.LabelFrame(main_frame, text="Changed Files", padding=(10, 5))
files_frame.grid(row=5, column=0, sticky="nsew", pady=(5, 0))
files_frame.rowconfigure(0, weight=1)
files_frame.columnconfigure(0, weight=1)
columns: Tuple[str, str] = ("status", "file")
self.files_tree = ttk.Treeview(
master=files_frame,
columns=columns,
show="headings",
selectmode="browse",
height=7,
)
self.files_tree.heading("status", text="Status", anchor="w")
self.files_tree.heading("file", text="File Path", anchor="w")
self.files_tree.column("status", width=80, stretch=tk.NO, anchor="w")
self.files_tree.column("file", width=600, stretch=tk.YES, anchor="w")
tree_scrollbar = ttk.Scrollbar(
master=files_frame, orient=tk.VERTICAL, command=self.files_tree.yview
)
self.files_tree.configure(yscrollcommand=tree_scrollbar.set)
self.files_tree.grid(row=0, column=0, sticky="nsew")
tree_scrollbar.grid(row=0, column=1, sticky="ns")
self.files_tree.bind("<Double-Button-1>", self._on_file_double_click)
Tooltip(self.files_tree, "Double-click a file to view changes in this commit.")
def _populate_details(self):
# ... (codice _populate_details invariato) ...
func_name: str = "_populate_details"
if hasattr(self, "hash_label"):
self.hash_label.config(text=self.commit_data.get("hash_full", "N/A"))
if hasattr(self, "author_label"):
name: str = self.commit_data.get("author_name", "N/A")
email: str = self.commit_data.get("author_email", "")
author_text: str = f"{name} <{email}>" if email else name
self.author_label.config(text=author_text)
if hasattr(self, "date_label"):
self.date_label.config(text=self.commit_data.get("author_date", "N/A"))
if hasattr(self, "subject_label"):
self.subject_label.config(
text=self.commit_data.get("subject", "(No Subject)")
)
if hasattr(self, "body_text"):
body_content: str = self.commit_data.get("body", "(No Message Body)")
try:
self.body_text.config(state=tk.NORMAL)
self.body_text.delete("1.0", tk.END)
self.body_text.insert(tk.END, body_content)
self.body_text.config(state=tk.DISABLED)
except Exception as e:
log_handler.log_error(
f"Error populating commit body text: {e}", func_name=func_name
)
try:
self.body_text.config(state=tk.NORMAL)
self.body_text.delete("1.0", tk.END)
self.body_text.insert(tk.END, f"(Error displaying body: {e})")
self.body_text.config(state=tk.DISABLED)
except Exception:
pass
if hasattr(self, "files_tree"):
for item in self.files_tree.get_children():
self.files_tree.delete(item)
files_changed: List[Tuple[str, str, Optional[str]]] = self.commit_data.get(
"files_changed", []
)
if files_changed:
for i, (status_char, path1, path2) in enumerate(files_changed):
display_status: str = self.STATUS_MAP.get(status_char, status_char)
display_path: str = ""
if status_char in ("R", "C") and path2:
display_path = f"{path1} -> {path2}"
else:
display_path = path1
item_data_tag = f"file_{i}"
self.files_tree.insert(
parent="",
index=tk.END,
iid=i,
values=(display_status, display_path),
tags=(item_data_tag,),
)
self.files_tree.tag_configure(item_data_tag, foreground="black")
self.files_tree.tag_bind(
item_data_tag,
"<Set>",
lambda e, sc=status_char, p1=path1, p2=path2: setattr(
e.widget, f"_data_{e.widget.focus()}", (sc, p1, p2)
),
)
else:
self.files_tree.insert(
parent="",
index=tk.END,
values=("", "(No files changed in commit data)"),
)
def _on_file_double_click(self, event: tk.Event):
# ... (codice _on_file_double_click invariato, usa messagebox importato) ...
func_name: str = "_on_file_double_click"
if not hasattr(self, "files_tree"):
return
selected_iid = self.files_tree.focus()
if not selected_iid:
return
try:
item_index = int(selected_iid)
files_list: list = self.commit_data.get("files_changed", [])
if 0 <= item_index < len(files_list):
status_char, path1, path2 = files_list[item_index]
file_path_to_diff: str = (
path2 if status_char in ("R", "C") and path2 else path1
)
old_file_path_for_diff: Optional[str] = (
path1 if status_char in ("R", "C") else None
)
commit_hash_full: Optional[str] = self.commit_data.get("hash_full")
if commit_hash_full:
log_handler.log_debug(
f"Calling open_diff_callback for file: {file_path_to_diff}, commit: {commit_hash_full[:7]}",
func_name=func_name,
)
self.open_diff_callback(
commit_hash_full,
status_char,
file_path_to_diff,
old_file_path_for_diff,
)
else:
log_handler.log_error(
"Cannot open diff: Commit hash is missing.", func_name=func_name
)
messagebox.showerror(
"Error",
"Internal error: Commit hash not available.",
parent=self,
)
else:
log_handler.log_error(
f"Selected Treeview item IID '{selected_iid}' out of range for data.",
func_name=func_name,
)
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=func_name
)
messagebox.showerror(
"Error", "Internal error: Invalid item selected.", parent=self
)
except Exception as e:
log_handler.log_exception(
f"Error handling file double-click: {e}", func_name=func_name
)
messagebox.showerror(
"Error", f"Could not process file selection:\n{e}", parent=self
)
def _center_window(self, parent: tk.Misc):
# ... (codice _center_window invariato) ...
func_name: str = "_center_window (CommitDetail)"
try:
self.update_idletasks()
parent_x: int = parent.winfo_rootx()
parent_y: int = parent.winfo_rooty()
parent_w: int = parent.winfo_width()
parent_h: int = parent.winfo_height()
win_w: int = self.winfo_width()
win_h: int = self.winfo_height()
pos_x: int = parent_x + (parent_w // 2) - (win_w // 2)
pos_y: int = parent_y + (parent_h // 2) - (win_h // 2)
screen_w: int = self.winfo_screenwidth()
screen_h: int = self.winfo_screenheight()
pos_x = max(0, min(pos_x, screen_w - win_w))
pos_y = max(0, min(pos_y, screen_h - win_h))
self.geometry(f"+{pos_x}+{pos_y}")
except Exception as e:
log_handler.log_error(
f"Could not center CommitDetailWindow: {e}", func_name=func_name
)
# --- END OF FILE gitsync_tool/gui/commit_detail_window.py ---