267 lines
9.4 KiB
Python
267 lines
9.4 KiB
Python
# --- FILE: gitsync_tool/gui/tabs/commit_tab.py ---
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, scrolledtext
|
|
from typing import Callable, List, Optional
|
|
|
|
from gitutility.gui.tooltip import Tooltip
|
|
from gitutility.logging_setup import log_handler
|
|
|
|
|
|
class CommitTab(ttk.Frame):
|
|
"""
|
|
The 'Commit / Changes' tab in the main application notebook.
|
|
This tab provides widgets for writing commit messages, viewing changed
|
|
files, and performing commit actions.
|
|
"""
|
|
|
|
def __init__(self, master: tk.Misc, **kwargs):
|
|
"""
|
|
Initializes the Commit / Changes 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.commit_changes_callback = kwargs.get("commit_changes_cb")
|
|
self.refresh_changed_files_callback = kwargs.get("refresh_changed_files_cb")
|
|
self.open_diff_viewer_callback = kwargs.get("open_diff_viewer_cb")
|
|
self.add_selected_file_callback = kwargs.get("add_selected_file_cb")
|
|
|
|
# Get a reference to the main frame for shared components like menus
|
|
self.main_frame = self.master.master
|
|
|
|
# --- Tkinter Variables specific to this tab ---
|
|
self.autocommit_var = tk.BooleanVar()
|
|
|
|
# Configure layout
|
|
self.rowconfigure(3, weight=1)
|
|
self.columnconfigure(0, weight=1)
|
|
|
|
# Create widgets
|
|
self._create_widgets()
|
|
|
|
def _create_widgets(self) -> None:
|
|
"""Creates and arranges all widgets for this tab."""
|
|
# --- Autocommit Checkbox ---
|
|
self.autocommit_checkbox = ttk.Checkbutton(
|
|
self,
|
|
text="Enable Autocommit before 'Create Bundle'",
|
|
variable=self.autocommit_var,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.autocommit_checkbox.grid(
|
|
row=0, column=0, columnspan=3, sticky="w", padx=5, pady=(0, 5)
|
|
)
|
|
Tooltip(
|
|
self.autocommit_checkbox,
|
|
"If enabled, automatically stage all changes and commit them using the message below before creating a bundle.",
|
|
)
|
|
|
|
# --- Commit Message Area ---
|
|
ttk.Label(self, text="Commit Message:").grid(
|
|
row=1, column=0, columnspan=3, sticky="w", padx=5
|
|
)
|
|
self.commit_message_text = scrolledtext.ScrolledText(
|
|
self,
|
|
height=3,
|
|
width=60,
|
|
wrap=tk.WORD,
|
|
font=("Segoe UI", 9),
|
|
state=tk.DISABLED,
|
|
undo=True,
|
|
padx=5,
|
|
pady=5,
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
)
|
|
self.commit_message_text.grid(
|
|
row=2, column=0, columnspan=3, sticky="ew", padx=5, pady=(0, 5)
|
|
)
|
|
Tooltip(
|
|
self.commit_message_text,
|
|
"Enter the commit message here for manual commits or autocommits.",
|
|
)
|
|
|
|
# --- Changed Files Frame ---
|
|
changes_frame = ttk.LabelFrame(
|
|
self, text="Working Directory Changes", padding=(10, 5)
|
|
)
|
|
changes_frame.grid(
|
|
row=3, column=0, columnspan=3, sticky="nsew", padx=5, pady=(5, 5)
|
|
)
|
|
changes_frame.rowconfigure(0, weight=1)
|
|
changes_frame.columnconfigure(0, weight=1)
|
|
|
|
list_sub_frame = ttk.Frame(changes_frame)
|
|
list_sub_frame.grid(row=0, column=0, columnspan=2, sticky="nsew", pady=(0, 5))
|
|
list_sub_frame.rowconfigure(0, weight=1)
|
|
list_sub_frame.columnconfigure(0, weight=1)
|
|
|
|
self.changed_files_listbox = tk.Listbox(
|
|
list_sub_frame,
|
|
height=8,
|
|
exportselection=False,
|
|
selectmode=tk.SINGLE,
|
|
font=("Consolas", 9),
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
)
|
|
self.changed_files_listbox.grid(row=0, column=0, sticky="nsew")
|
|
self.changed_files_listbox.bind(
|
|
"<Double-Button-1>", self._on_changed_file_double_click
|
|
)
|
|
self.changed_files_listbox.bind(
|
|
"<Button-3>", self._show_changed_files_context_menu
|
|
)
|
|
|
|
scrollbar_list = ttk.Scrollbar(
|
|
list_sub_frame, orient=tk.VERTICAL, command=self.changed_files_listbox.yview
|
|
)
|
|
scrollbar_list.grid(row=0, column=1, sticky="ns")
|
|
self.changed_files_listbox.config(yscrollcommand=scrollbar_list.set)
|
|
Tooltip(
|
|
self.changed_files_listbox,
|
|
"List of changed, added, or deleted files. Double-click to view diff (vs HEAD). Right-click for actions.",
|
|
)
|
|
|
|
self.refresh_changes_button = ttk.Button(
|
|
changes_frame,
|
|
text="Refresh List",
|
|
command=self.refresh_changed_files_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.refresh_changes_button.grid(
|
|
row=1, column=0, sticky="w", padx=(0, 5), pady=(5, 0)
|
|
)
|
|
Tooltip(
|
|
self.refresh_changes_button,
|
|
"Refresh the list of changed files in the working directory.",
|
|
)
|
|
|
|
# --- Commit Button ---
|
|
self.commit_button = ttk.Button(
|
|
self,
|
|
text="Commit Staged Changes",
|
|
command=self.commit_changes_callback,
|
|
state=tk.DISABLED,
|
|
)
|
|
self.commit_button.grid(
|
|
row=4, column=0, columnspan=3, sticky="se", padx=5, pady=5
|
|
)
|
|
Tooltip(
|
|
self.commit_button,
|
|
"Stage all current changes (git add .) and commit them with the provided message.",
|
|
)
|
|
|
|
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.autocommit_checkbox,
|
|
self.commit_message_text,
|
|
self.refresh_changes_button,
|
|
self.commit_button,
|
|
self.changed_files_listbox,
|
|
]
|
|
|
|
for widget in widgets:
|
|
if widget and widget.winfo_exists():
|
|
widget.config(state=list_state)
|
|
|
|
def update_changed_files_list(self, files_status_list: List[str]):
|
|
"""Populates the listbox with the status of changed files."""
|
|
listbox = self.changed_files_listbox
|
|
if not listbox or not listbox.winfo_exists():
|
|
return
|
|
|
|
listbox.config(state=tk.NORMAL)
|
|
listbox.delete(0, tk.END)
|
|
|
|
if files_status_list:
|
|
listbox.config(fg="black")
|
|
for status_line in files_status_list:
|
|
sanitized_line = str(status_line).replace("\x00", "").strip()
|
|
if sanitized_line:
|
|
listbox.insert(tk.END, sanitized_line)
|
|
else:
|
|
listbox.insert(tk.END, "(No changes detected)")
|
|
listbox.config(fg="grey")
|
|
|
|
listbox.yview_moveto(0.0)
|
|
|
|
def get_commit_message(self) -> str:
|
|
"""Returns the content of the commit message text widget."""
|
|
return self.commit_message_text.get("1.0", "end-1c").strip()
|
|
|
|
def clear_commit_message(self):
|
|
"""Clears the commit message text widget."""
|
|
if self.commit_message_text.winfo_exists():
|
|
state = self.commit_message_text.cget("state")
|
|
self.commit_message_text.config(state=tk.NORMAL)
|
|
self.commit_message_text.delete("1.0", tk.END)
|
|
self.commit_message_text.config(state=state)
|
|
self.commit_message_text.edit_reset()
|
|
|
|
def _on_changed_file_double_click(self, event: tk.Event) -> None:
|
|
"""Handles double-click on a file in the changed files list."""
|
|
selection = self.changed_files_listbox.curselection()
|
|
if selection:
|
|
line = self.changed_files_listbox.get(selection[0])
|
|
if line and callable(self.open_diff_viewer_callback):
|
|
self.open_diff_viewer_callback(line)
|
|
|
|
def _show_changed_files_context_menu(self, event: tk.Event) -> None:
|
|
"""Shows a context menu for the selected file in the changed files list."""
|
|
menu = self.main_frame.changed_files_context_menu
|
|
|
|
try:
|
|
idx = self.changed_files_listbox.nearest(event.y)
|
|
if idx < 0:
|
|
return
|
|
self.changed_files_listbox.selection_clear(0, tk.END)
|
|
self.changed_files_listbox.selection_set(idx)
|
|
self.changed_files_listbox.activate(idx)
|
|
line = self.changed_files_listbox.get(idx)
|
|
except tk.TclError:
|
|
return
|
|
|
|
if not line:
|
|
return
|
|
|
|
menu.delete(0, tk.END)
|
|
cleaned = line.strip()
|
|
is_untracked = cleaned.startswith("??")
|
|
|
|
# Add "Add to Staging" option
|
|
add_state = (
|
|
tk.NORMAL
|
|
if is_untracked and callable(self.add_selected_file_callback)
|
|
else tk.DISABLED
|
|
)
|
|
menu.add_command(
|
|
label="Add to Staging Area",
|
|
state=add_state,
|
|
command=lambda: self.add_selected_file_callback(line),
|
|
)
|
|
|
|
# Add "View Changes (Diff)" option
|
|
can_diff = (
|
|
not is_untracked
|
|
and not cleaned.startswith("!!")
|
|
and not cleaned.startswith(" D")
|
|
and callable(self.open_diff_viewer_callback)
|
|
)
|
|
diff_state = tk.NORMAL if can_diff else tk.DISABLED
|
|
menu.add_command(
|
|
label="View Changes (Diff)",
|
|
state=diff_state,
|
|
command=lambda: self.open_diff_viewer_callback(line),
|
|
)
|
|
|
|
menu.tk_popup(event.x_root, event.y_root)
|