SXXXXXXX_GitUtility/gitutility/gui/tabs/submodules_tab.py

288 lines
11 KiB
Python

# --- 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("<Button-3>", 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)