197 lines
8.0 KiB
Python
197 lines
8.0 KiB
Python
# --- FILE: gitsync_tool/gui/tabs/branch_tab.py ---
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from typing import Callable, List, Optional
|
|
|
|
from gitutility.gui.tooltip import Tooltip
|
|
from gitutility.logging_setup import log_handler
|
|
|
|
class BranchTab(ttk.Frame):
|
|
"""
|
|
The 'Branches (Local Ops)' tab in the main application notebook.
|
|
This tab provides widgets for listing, creating, checking out, and managing
|
|
local Git branches.
|
|
"""
|
|
|
|
def __init__(self, master: tk.Misc, **kwargs):
|
|
"""
|
|
Initializes the Branches 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
|
|
self.refresh_branches_callback = kwargs.get('refresh_branches_cb')
|
|
self.create_branch_callback = kwargs.get('create_branch_cb')
|
|
self.checkout_branch_callback = kwargs.get('checkout_branch_cb')
|
|
self.compare_branch_with_current_callback = kwargs.get('compare_branch_with_current_cb')
|
|
self.delete_local_branch_callback = kwargs.get('delete_local_branch_cb')
|
|
self.merge_local_branch_callback = kwargs.get('merge_local_branch_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."""
|
|
ttk.Label(self, text="Local Branches (* = Current):").grid(
|
|
row=0, column=0, columnspan=2, sticky="w", padx=5, pady=(0, 2)
|
|
)
|
|
|
|
# --- Listbox Frame ---
|
|
list_frame = ttk.Frame(self)
|
|
list_frame.grid(row=1, column=0, sticky="nsew", padx=(5, 0), pady=(0, 5))
|
|
list_frame.rowconfigure(0, weight=1)
|
|
list_frame.columnconfigure(0, weight=1)
|
|
|
|
self.branch_listbox = tk.Listbox(
|
|
list_frame,
|
|
height=10,
|
|
exportselection=False,
|
|
selectmode=tk.SINGLE,
|
|
font=("Segoe UI", 9),
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
)
|
|
self.branch_listbox.grid(row=0, column=0, sticky="nsew")
|
|
self.branch_listbox.bind("<Button-3>", self._show_local_branches_context_menu)
|
|
|
|
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.branch_listbox.yview)
|
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
self.branch_listbox.config(yscrollcommand=scrollbar.set)
|
|
Tooltip(self.branch_listbox, "List of local branches. Right-click for actions (merge, delete, compare).")
|
|
|
|
# --- Button Frame ---
|
|
button_frame = ttk.Frame(self)
|
|
button_frame.grid(row=1, column=1, sticky="ns", padx=(10, 5), pady=(0, 5))
|
|
|
|
button_width = 20
|
|
|
|
self.refresh_branches_button = ttk.Button(
|
|
button_frame, text="Refresh Branches", width=button_width, command=self.refresh_branches_callback, state=tk.DISABLED
|
|
)
|
|
self.refresh_branches_button.pack(side=tk.TOP, fill=tk.X, pady=(0, 5))
|
|
Tooltip(self.refresh_branches_button, "Reload the list of local branches.")
|
|
|
|
self.create_branch_button = ttk.Button(
|
|
button_frame, text="Create New Branch...", width=button_width, command=self.create_branch_callback, state=tk.DISABLED
|
|
)
|
|
self.create_branch_button.pack(side=tk.TOP, fill=tk.X, pady=5)
|
|
Tooltip(self.create_branch_button, "Create a new local branch starting from the current commit.")
|
|
|
|
self.checkout_branch_button = ttk.Button(
|
|
button_frame,
|
|
text="Checkout Selected Branch",
|
|
width=button_width,
|
|
command=lambda: self.checkout_branch_callback(None),
|
|
state=tk.DISABLED
|
|
)
|
|
self.checkout_branch_button.pack(side=tk.TOP, fill=tk.X, pady=5)
|
|
Tooltip(self.checkout_branch_button, "Switch the working directory to the selected local branch.")
|
|
|
|
def set_action_widgets_state(self, state: str) -> None:
|
|
"""Sets the state of all action widgets in this tab."""
|
|
list_state = tk.NORMAL if state == tk.NORMAL else tk.DISABLED
|
|
|
|
widgets = [
|
|
self.refresh_branches_button,
|
|
self.create_branch_button,
|
|
self.checkout_branch_button,
|
|
self.branch_listbox,
|
|
]
|
|
|
|
for widget in widgets:
|
|
if widget and widget.winfo_exists():
|
|
widget.config(state=list_state)
|
|
|
|
def update_branch_list(self, branches: List[str], current_branch: Optional[str]):
|
|
"""Populates the listbox with the local branches."""
|
|
if not self.branch_listbox.winfo_exists(): return
|
|
|
|
self.branch_listbox.config(state=tk.NORMAL)
|
|
self.branch_listbox.delete(0, tk.END)
|
|
selected_index = -1
|
|
|
|
if branches:
|
|
self.branch_listbox.config(fg="black")
|
|
for i, branch in enumerate(branches):
|
|
prefix = "* " if branch == current_branch else " "
|
|
self.branch_listbox.insert(tk.END, f"{prefix}{branch}")
|
|
if branch == current_branch:
|
|
selected_index = i
|
|
else:
|
|
self.branch_listbox.insert(tk.END, "(No local branches found)")
|
|
self.branch_listbox.config(fg="grey")
|
|
|
|
if selected_index != -1:
|
|
self.branch_listbox.selection_set(selected_index)
|
|
self.branch_listbox.see(selected_index)
|
|
|
|
self.branch_listbox.yview_moveto(0.0)
|
|
|
|
def get_selected_branch(self) -> Optional[str]:
|
|
"""Returns the name of the currently selected local branch, or None."""
|
|
selection = self.branch_listbox.curselection()
|
|
if not selection: return None
|
|
|
|
item_text = self.branch_listbox.get(selection[0])
|
|
branch_name = item_text.lstrip("* ").strip()
|
|
|
|
return branch_name if not branch_name.startswith("(") else None
|
|
|
|
def _show_local_branches_context_menu(self, event: tk.Event) -> None:
|
|
"""Shows a context menu for the selected local branch."""
|
|
try:
|
|
idx = self.branch_listbox.nearest(event.y)
|
|
if idx < 0: return
|
|
self.branch_listbox.selection_clear(0, tk.END)
|
|
self.branch_listbox.selection_set(idx)
|
|
self.branch_listbox.activate(idx)
|
|
selected_item_text = self.branch_listbox.get(idx).strip()
|
|
except tk.TclError:
|
|
return
|
|
|
|
menu = self.main_frame.local_branch_context_menu
|
|
menu.delete(0, tk.END)
|
|
|
|
is_current = selected_item_text.startswith("*")
|
|
branch_name = selected_item_text.lstrip("* ").strip()
|
|
current_branch = self.main_frame.current_local_branch
|
|
is_valid = not branch_name.startswith("(")
|
|
|
|
if is_valid:
|
|
menu.add_command(
|
|
label=f"Merge '{branch_name}' into '{current_branch}'...",
|
|
state=tk.DISABLED if is_current or not current_branch else tk.NORMAL,
|
|
command=lambda b=branch_name: self.merge_local_branch_callback(b)
|
|
)
|
|
menu.add_command(
|
|
label=f"Compare with current '{current_branch}'",
|
|
state=tk.DISABLED if is_current or not current_branch else tk.NORMAL,
|
|
command=lambda b=branch_name: self.compare_branch_with_current_callback(b)
|
|
)
|
|
menu.add_separator()
|
|
menu.add_command(
|
|
label=f"Delete Branch '{branch_name}'...",
|
|
state=tk.DISABLED if is_current else tk.NORMAL,
|
|
command=lambda b=branch_name: self.delete_local_branch_callback(b, False)
|
|
)
|
|
menu.add_command(
|
|
label=f"Force Delete Branch '{branch_name}'...",
|
|
state=tk.DISABLED if is_current else tk.NORMAL,
|
|
command=lambda b=branch_name: self.delete_local_branch_callback(b, True)
|
|
)
|
|
menu.add_separator()
|
|
|
|
menu.add_command(label="Cancel")
|
|
menu.tk_popup(event.x_root, event.y_root) |