196 lines
8.3 KiB
Python
196 lines
8.3 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.
|
|
|
|
Args:
|
|
master: The parent widget (the ttk.Notebook).
|
|
**kwargs: Dictionary of callbacks from the main controller.
|
|
"""
|
|
super().__init__(master, padding=(10, 10))
|
|
|
|
# Store callbacks by getting them from kwargs
|
|
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')
|
|
|
|
# Get a reference to the main frame for shared components
|
|
self.main_frame = self.master.master
|
|
|
|
# Configure layout
|
|
self.columnconfigure(0, weight=1)
|
|
self.rowconfigure(1, weight=1)
|
|
|
|
# Create widgets
|
|
self._create_widgets()
|
|
|
|
def _create_widgets(self) -> None:
|
|
"""Creates and arranges all widgets for this tab."""
|
|
# --- Actions Frame ---
|
|
actions_frame = ttk.LabelFrame(self, text="Submodule Actions", padding=(10, 5))
|
|
actions_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
|
|
|
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)
|
|
Tooltip(self.refresh_submodules_button, "Reload the status of all submodules.")
|
|
|
|
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)
|
|
Tooltip(self.add_submodule_button, "Add a new submodule to this repository.")
|
|
|
|
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)
|
|
Tooltip(self.init_submodules_button, "Clone any submodules that are registered but not yet downloaded.")
|
|
|
|
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)
|
|
Tooltip(self.sync_submodules_button, "Update all initialized submodules to the latest commit on their tracked branch and commit the change.")
|
|
|
|
# --- List Frame ---
|
|
list_frame = ttk.LabelFrame(self, text="Repository Submodules", padding=(10, 5))
|
|
list_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
|
|
list_frame.rowconfigure(0, weight=1)
|
|
list_frame.columnconfigure(0, weight=1)
|
|
|
|
columns = ("path", "commit", "status")
|
|
self.submodules_tree = ttk.Treeview(
|
|
list_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="Status", anchor="w")
|
|
|
|
self.submodules_tree.column("path", width=300, stretch=tk.YES, anchor="w")
|
|
self.submodules_tree.column("commit", width=120, stretch=tk.NO, anchor="w")
|
|
self.submodules_tree.column("status", width=220, stretch=tk.NO, anchor="w")
|
|
|
|
scrollbar = ttk.Scrollbar(list_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)
|
|
Tooltip(self.submodules_tree, "List of registered submodules. Right-click on an item for more options.")
|
|
|
|
# Tag configuration for status colors
|
|
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")
|
|
|
|
def update_submodules_list(self, submodules_data: Optional[List[Dict[str, str]]]) -> None:
|
|
"""Populates the submodules Treeview with the latest status."""
|
|
func_name = "update_submodules_list (GUI)"
|
|
|
|
# Clear existing items
|
|
for item in self.submodules_tree.get_children():
|
|
self.submodules_tree.delete(item)
|
|
|
|
if submodules_data is None: # Error case
|
|
self.submodules_tree.insert("", "end", values=("(Error)", "", "Could not retrieve submodule 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", "?")
|
|
|
|
status_text = "OK (Initialized & Synced)"
|
|
tag = "status_ok"
|
|
if status_char == "-":
|
|
status_text = "Not Initialized (Needs Clone)"
|
|
tag = "status_warning"
|
|
elif status_char == "+":
|
|
status_text = "Commit Mismatch (Needs Sync/Update)"
|
|
tag = "status_warning"
|
|
elif status_char == "U":
|
|
status_text = "Conflict (Needs Manual Resolution)"
|
|
tag = "status_error"
|
|
|
|
self.submodules_tree.insert(
|
|
"", "end", iid=i, values=(path, commit[:10], status_text), tags=(tag,)
|
|
)
|
|
|
|
def get_selected_submodule_path(self) -> Optional[str]:
|
|
"""Returns the path of the selected submodule in the treeview."""
|
|
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:
|
|
"""Shows a context menu for the selected submodule."""
|
|
# Select the item under the cursor
|
|
iid = self.submodules_tree.identify_row(event.y)
|
|
if iid:
|
|
self.submodules_tree.selection_set(iid)
|
|
else:
|
|
return
|
|
|
|
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"Remove Submodule '{path}'...",
|
|
command=lambda p=path: self.remove_submodule_callback(p) if callable(self.remove_submodule_callback) else None
|
|
)
|
|
menu.add_separator()
|
|
menu.add_command(label="Cancel")
|
|
|
|
try:
|
|
menu.tk_popup(event.x_root, event.y_root)
|
|
finally:
|
|
menu.grab_release()
|
|
|
|
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,
|
|
]
|
|
|
|
for widget in widgets_to_toggle:
|
|
if widget and widget.winfo_exists():
|
|
widget.config(state=state) |