# --- FILE: gitsync_tool/gui/tabs/submodules_tab.py --- import tkinter as tk from tkinter import ttk from typing import Callable, List, Dict, Optional, Any from gitutility.gui.tooltip import Tooltip from gitutility.logging_setup import log_handler class SubmodulesTab(ttk.Frame): """ The 'Submodules' tab in the main application notebook. This tab provides an interface for viewing, adding, updating, and removing Git submodules within the current repository. """ def __init__(self, master: tk.Misc, **kwargs): """ Initializes the Submodules tab. """ super().__init__(master, padding=(10, 10)) # Store callbacks self.refresh_submodules_callback = kwargs.get("refresh_submodules_cb") self.add_submodule_callback = kwargs.get("add_submodule_cb") self.init_missing_submodules_callback = kwargs.get("init_missing_submodules_cb") self.sync_all_submodules_callback = kwargs.get("sync_all_submodules_cb") self.remove_submodule_callback = kwargs.get("remove_submodule_cb") self.view_submodule_changes_callback = kwargs.get("view_submodule_changes_cb") self.check_for_updates_callback = kwargs.get( "check_for_updates_cb" ) # <-- Callback per il nuovo pulsante self.main_frame = self.master.master # Stato interno per la GUI self.remote_update_statuses: Dict[str, str] = {} self.submodules_data_cache: Optional[List[Dict[str, str]]] = None self.columnconfigure(0, weight=1) self.rowconfigure(1, weight=1) self._create_widgets() def _create_widgets(self) -> None: """Creates and arranges all widgets for this tab.""" actions_frame = ttk.LabelFrame(self, text="Submodule Actions", padding=(10, 5)) actions_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) # Pulsanti (invariati) self.refresh_submodules_button = ttk.Button( actions_frame, text="Refresh List", command=self.refresh_submodules_callback, state=tk.DISABLED, ) self.refresh_submodules_button.pack(side=tk.LEFT, padx=5, pady=5) self.add_submodule_button = ttk.Button( actions_frame, text="Add Submodule...", command=self.add_submodule_callback, state=tk.DISABLED, ) self.add_submodule_button.pack(side=tk.LEFT, padx=5, pady=5) self.init_submodules_button = ttk.Button( actions_frame, text="Initialize Missing", command=self.init_missing_submodules_callback, state=tk.DISABLED, ) self.init_submodules_button.pack(side=tk.LEFT, padx=5, pady=5) self.sync_submodules_button = ttk.Button( actions_frame, text="Sync/Update All", command=self.sync_all_submodules_callback, state=tk.DISABLED, ) self.sync_submodules_button.pack(side=tk.LEFT, padx=5, pady=5) self.check_updates_button = ttk.Button( actions_frame, text="Check for Updates", command=self.check_for_updates_callback, state=tk.DISABLED, ) self.check_updates_button.pack(side=tk.LEFT, padx=5, pady=5) # --- List Frame (MODIFICATO) --- list_frame = ttk.LabelFrame(self, text="Repository Submodules", padding=(10, 5)) list_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5) # Assicura che la riga contenente la treeview si espanda list_frame.rowconfigure(0, weight=1) list_frame.columnconfigure(0, weight=1) # --- MODIFICA CHIAVE: Legenda integrata nel titolo del frame --- # Creiamo un frame per contenere il titolo e l'icona della legenda title_widget = ttk.Frame(list_frame) title_label = ttk.Label(title_widget, text="Repository Submodules ") title_label.pack(side=tk.LEFT) legend_icon = ttk.Label( title_widget, text="(Legend ?)", foreground="blue", cursor="question_arrow" ) legend_icon.pack(side=tk.LEFT) # Assegniamo questo widget composito come "etichetta" del LabelFrame list_frame.configure(labelwidget=title_widget) tooltip_text = ( "Status Legend:\n\n" "Local Status:\n" "• OK: Synced with the main repository.\n" "• Not Initialized: Submodule folder is empty. Needs 'Initialize'.\n" "• Ahead: Submodule has new local commits. Needs 'Sync/Update'.\n" "• Modified: Submodule has uncommitted file changes.\n" "• Conflict: Merge conflict inside the submodule.\n\n" "Remote Status:\n" "• Unknown: Click 'Check for Updates' to fetch remote status.\n" "• Up-to-date: Local version is the latest.\n" "• Update available: New commits are on the remote. Use 'Sync/Update'.\n" "• Tracking N/A: Submodule is not configured to track a specific branch." ) Tooltip(legend_icon, tooltip_text) Tooltip( title_label, tooltip_text ) # Applica il tooltip anche al testo per un'area più ampia # --- FINE MODIFICA --- tree_frame = ttk.Frame(list_frame) tree_frame.grid( row=0, column=0, columnspan=2, sticky="nsew" ) # La griglia parte dalla riga 0 tree_frame.rowconfigure(0, weight=1) tree_frame.columnconfigure(0, weight=1) columns = ("path", "commit", "status", "remote_status") self.submodules_tree = ttk.Treeview( tree_frame, columns=columns, show="headings", selectmode="browse" ) self.submodules_tree.heading("path", text="Path", anchor="w") self.submodules_tree.heading("commit", text="Current Commit", anchor="w") self.submodules_tree.heading("status", text="Local Status", anchor="w") self.submodules_tree.heading("remote_status", text="Remote Status", anchor="w") self.submodules_tree.column("path", width=250, stretch=tk.YES, anchor="w") self.submodules_tree.column("commit", width=100, stretch=tk.NO, anchor="w") self.submodules_tree.column("status", width=200, stretch=tk.NO, anchor="w") self.submodules_tree.column( "remote_status", width=200, stretch=tk.NO, anchor="w" ) scrollbar = ttk.Scrollbar( tree_frame, orient=tk.VERTICAL, command=self.submodules_tree.yview ) self.submodules_tree.configure(yscrollcommand=scrollbar.set) self.submodules_tree.grid(row=0, column=0, sticky="nsew") scrollbar.grid(row=0, column=1, sticky="ns") self.submodules_tree.bind("", self._show_submodule_context_menu) # Tag (invariati) self.submodules_tree.tag_configure("status_ok", foreground="green") self.submodules_tree.tag_configure("status_warning", foreground="orange") self.submodules_tree.tag_configure("status_error", foreground="red") self.submodules_tree.tag_configure( "remote_update", foreground="blue", font=("Segoe UI", 9, "bold") ) def set_action_widgets_state(self, state: str) -> None: """Sets the state of all action widgets in this tab.""" widgets_to_toggle = [ self.refresh_submodules_button, self.add_submodule_button, self.init_submodules_button, self.sync_submodules_button, self.check_updates_button, # <-- Aggiunto qui ] for widget in widgets_to_toggle: if widget and widget.winfo_exists(): widget.config(state=state) def update_submodules_list( self, submodules_data: Optional[List[Dict[str, str]]] ) -> None: """Populates the submodules Treeview with the latest status.""" self.submodules_data_cache = submodules_data # Salva sempre i dati più recenti for item in self.submodules_tree.get_children(): self.submodules_tree.delete(item) if submodules_data is None: self.submodules_tree.insert( "", "end", values=("(Error)", "", "Could not retrieve status", ""), tags=("status_error",), ) return if not submodules_data: self.submodules_tree.insert( "", "end", values=("(No submodules found)", "", "", "") ) return for i, sub in enumerate(submodules_data): path = sub.get("path", "N/A") commit = sub.get("commit", "N/A") status_char = sub.get("status_char", "?") description = sub.get("description", "N/A") dirty_details = sub.get("dirty_details", "") status_text = "OK" tag = "status_ok" if status_char == "-": status_text = "Not Initialized" tag = "status_warning" elif status_char == "U": status_text = "Merge Conflict" tag = "status_error" elif status_char == "+": status_text = "Ahead (New Commits)" tag = "status_warning" if dirty_details == "modified content": if status_text != "OK": status_text += " + Local Changes" else: status_text = "Modified (Local Changes)" tag = "status_warning" if status_text == "OK": status_text = f"OK ({description})" remote_status = self.remote_update_statuses.get(path, "Unknown") remote_tag = "remote_update" if "Update available" in remote_status else "" self.submodules_tree.insert( "", "end", iid=i, values=(path, commit[:10], status_text, remote_status), tags=(tag, remote_tag), ) def update_remote_statuses(self, statuses: Dict[str, str]): """Updates the internal state for remote statuses and redraws the list.""" self.remote_update_statuses = statuses self.update_submodules_list(self.submodules_data_cache) def get_selected_submodule_path(self) -> Optional[str]: # ... (invariato) ... selection = self.submodules_tree.selection() if not selection: return None item_data = self.submodules_tree.item(selection[0]) values = item_data.get("values") if values and len(values) > 0: return values[0] return None def _show_submodule_context_menu(self, event: tk.Event) -> None: # ... (invariato) ... iid = self.submodules_tree.identify_row(event.y) if not iid: return self.submodules_tree.selection_set(iid) path = self.get_selected_submodule_path() if not path: return menu = self.main_frame.submodule_context_menu if not menu: return menu.delete(0, tk.END) menu.add_command( label=f"View Local Changes in '{path}'", command=lambda p=path: self.view_submodule_changes_callback(p), ) menu.add_separator() menu.add_command( label=f"Remove Submodule '{path}'...", command=lambda p=path: self.remove_submodule_callback(p), ) menu.add_separator() menu.add_command(label="Cancel") menu.tk_popup(event.x_root, event.y_root)