# --- FILE: gitsync_tool/gui/purge_dialog.py --- import tkinter as tk from tkinter import ttk, messagebox, simpledialog from typing import List, Dict, Any, Optional # Importa usando il percorso assoluto from gitutility.logging_setup import log_handler from gitutility.gui.tooltip import Tooltip def _format_size(size_bytes: int) -> str: """Formats a size in bytes to a human-readable string (KB, MB, GB).""" if size_bytes < 1024: return f"{size_bytes} B" kb = size_bytes / 1024 if kb < 1024: return f"{kb:.1f} KB" mb = kb / 1024 if mb < 1024: return f"{mb:.2f} MB" gb = mb / 1024 return f"{gb:.2f} GB" class PurgeConfirmationDialog(simpledialog.Dialog): """ A modal dialog to show files that can be purged from Git history and to get explicit user confirmation for this destructive action. """ def __init__( self, master: tk.Misc, files_to_purge: List[Dict[str, Any]], repo_path: str, title: str = "Confirm History Purge", ): """ Initialize the dialog. Args: master: The parent window. files_to_purge: A list of dicts, each with 'path' and 'size' keys. repo_path: The path of the repository being cleaned. title (str): The title for the dialog window. """ self.files_to_purge = files_to_purge self.repo_path = repo_path self.confirmation_var = tk.BooleanVar(value=False) self.result: bool = False # The final result will be a boolean super().__init__(master, title=title) def body(self, master: tk.Frame) -> Optional[tk.Widget]: """Creates the dialog body.""" # Main container frame container = ttk.Frame(master, padding="10") container.pack(fill=tk.BOTH, expand=True) # --- 1. Warning Section --- warning_frame = ttk.Frame(container, style="Card.TFrame", padding=10) warning_frame.pack(fill=tk.X, pady=(0, 10)) # Use a custom style for the warning frame if available try: s = ttk.Style() s.configure( "Card.TFrame", background="#FFF5F5", relief="solid", borderwidth=1 ) except tk.TclError: pass # Fallback to default frame style warning_icon = ttk.Label(warning_frame, text="⚠️", font=("Segoe UI", 16)) warning_icon.pack(side=tk.LEFT, padx=(0, 10), anchor="n") warning_text_frame = ttk.Frame(warning_frame, style="Card.TFrame") warning_text_frame.pack(side=tk.LEFT, fill=tk.X, expand=True) ttk.Label( warning_text_frame, text="DESTRUCTIVE ACTION", font=("Segoe UI", 11, "bold"), foreground="#D9534F", style="Card.TLabel", ).pack(anchor="w") warning_message = ( "This will permanently remove the files listed below from your entire Git history. " "This operation rewrites history and requires a force push, which can disrupt collaboration.\n" "Ensure all team members have pushed their changes before proceeding." ) ttk.Label( warning_text_frame, text=warning_message, wraplength=500, justify=tk.LEFT, style="Card.TLabel", ).pack(anchor="w", pady=(5, 0)) try: s.configure("Card.TLabel", background="#FFF5F5") except tk.TclError: pass # --- 2. File List Section --- files_frame = ttk.LabelFrame( container, text=f"Files to Purge ({len(self.files_to_purge)} found)", padding=10, ) files_frame.pack(fill=tk.BOTH, expand=True, pady=10) files_frame.rowconfigure(0, weight=1) files_frame.columnconfigure(0, weight=1) columns = ("path", "size") self.tree = ttk.Treeview(files_frame, columns=columns, show="headings") self.tree.heading("path", text="File Path", anchor="w") self.tree.heading("size", text="Size", anchor="e") self.tree.column("path", width=400, stretch=tk.YES, anchor="w") self.tree.column("size", width=100, stretch=tk.NO, anchor="e") scrollbar = ttk.Scrollbar( files_frame, orient=tk.VERTICAL, command=self.tree.yview ) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.grid(row=0, column=0, sticky="nsew") scrollbar.grid(row=0, column=1, sticky="ns") # Populate the treeview for file_info in self.files_to_purge: path = file_info.get("path", "N/A") size_bytes = file_info.get("size", 0) formatted_size = _format_size(size_bytes) self.tree.insert("", tk.END, values=(path, formatted_size)) # --- 3. Confirmation Checkbox Section --- confirm_frame = ttk.Frame(container, padding=(0, 10, 0, 0)) confirm_frame.pack(fill=tk.X) self.confirm_check = ttk.Checkbutton( confirm_frame, text="I understand the risks and want to permanently delete these files from history.", variable=self.confirmation_var, command=self._on_confirm_toggle, ) self.confirm_check.pack(anchor="w") return self.tree # Initial focus on the list def _on_confirm_toggle(self): """Enables/disables the 'Confirm' button based on the checkbox state.""" # Find the OK button in the buttonbox ok_button = self.ok_button if ok_button: if self.confirmation_var.get(): ok_button.config(state=tk.NORMAL) else: ok_button.config(state=tk.DISABLED) def buttonbox(self): """Creates the OK and Cancel buttons.""" box = ttk.Frame(self) self.ok_button = ttk.Button( box, text="Confirm and Purge", width=20, command=self.ok, state=tk.DISABLED, # Initially disabled style="Danger.TButton", ) self.ok_button.pack(side=tk.LEFT, padx=5, pady=5) try: s = ttk.Style() s.configure("Danger.TButton", foreground="white", background="#D9534F") except tk.TclError: pass # Fallback to default button style Tooltip( self.ok_button, "This button is enabled only after you check the confirmation box.", ) cancel_button = ttk.Button(box, text="Cancel", width=10, command=self.cancel) cancel_button.pack(side=tk.LEFT, padx=5, pady=5) self.bind( "", lambda e: self.ok() if self.confirmation_var.get() else None ) self.bind("", lambda e: self.cancel()) box.pack() def validate(self) -> bool: """Validation is handled by the confirmation checkbox state.""" # The OK button is only enabled if the checkbox is checked, so if `ok` is # called, we can assume the user has confirmed. if not self.confirmation_var.get(): messagebox.showwarning( "Confirmation Required", "You must check the box to confirm you understand the risks before proceeding.", parent=self, ) return False return True def apply(self): """Sets the result to True as the action has been confirmed.""" self.result = True