219 lines
11 KiB
Python
219 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) |