396 lines
15 KiB
Python
396 lines
15 KiB
Python
# --- FILE: gitsync_tool/gui/main_frame.py ---
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from tkinter import messagebox, simpledialog, scrolledtext
|
|
import os
|
|
import sys
|
|
from typing import Tuple, Dict, List, Callable, Optional, Any
|
|
|
|
from gitutility.logging_setup import log_handler
|
|
from gitutility.gui.tooltip import Tooltip
|
|
from gitutility.config.config_manager import DEFAULT_PROFILE
|
|
|
|
# --- Importazioni dai moduli dei tab ---
|
|
from .tabs.repo_tab import RepoTab
|
|
from .tabs.submodules_tab import SubmodulesTab
|
|
from .tabs.remote_tab import RemoteTab
|
|
from .tabs.backup_tab import BackupTab
|
|
from .tabs.commit_tab import CommitTab
|
|
from .tabs.tags_tab import TagsTab
|
|
from .tabs.branch_tab import BranchTab
|
|
from .tabs.automation_tab import AutomationTab
|
|
from .tabs.history_tab import HistoryTab
|
|
|
|
|
|
class MainFrame(ttk.Frame):
|
|
"""
|
|
The main application frame, which acts as a container for all UI components.
|
|
It assembles the profile bar, the tabbed notebook, the log area, and the status bar.
|
|
It delegates tab-specific logic and UI updates to individual tab classes.
|
|
"""
|
|
|
|
GREEN: str = "#90EE90"
|
|
RED: str = "#F08080"
|
|
STATUS_YELLOW: str = "#FFFACD"
|
|
STATUS_RED: str = "#FFA07A"
|
|
STATUS_GREEN: str = "#98FB98"
|
|
STATUS_DEFAULT_BG: Optional[str] = None
|
|
|
|
def __init__(self, master: tk.Misc, **kwargs):
|
|
super().__init__(master)
|
|
self.master: tk.Misc = master
|
|
self.initial_profile_sections = kwargs.get("profile_sections_list", [])
|
|
self.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|
|
|
# --- Store Callbacks ---
|
|
self.load_profile_settings_callback = kwargs.get("load_profile_settings_cb")
|
|
# ... (gli altri callback non sono necessari qui, vengono passati ai tab)
|
|
|
|
# --- Tkinter Variables (ONLY global/shared ones) ---
|
|
self.profile_var = tk.StringVar()
|
|
self.status_bar_var = tk.StringVar()
|
|
self.current_local_branch: Optional[str] = None
|
|
|
|
# --- Context Menus (owned by the main frame) ---
|
|
self.remote_branch_context_menu = tk.Menu(self.master, tearoff=0)
|
|
self.local_branch_context_menu = tk.Menu(self.master, tearoff=0)
|
|
self.changed_files_context_menu = tk.Menu(self.master, tearoff=0)
|
|
self.submodule_context_menu = tk.Menu(self.master, tearoff=0)
|
|
|
|
# --- Create Main UI Structure ---
|
|
self._create_profile_frame(
|
|
kwargs.get("save_profile_cb"),
|
|
kwargs.get("add_profile_cb"),
|
|
kwargs.get("clone_remote_repo_cb"),
|
|
kwargs.get("remove_profile_cb"),
|
|
)
|
|
|
|
self.notebook = ttk.Notebook(self, padding=(0, 5, 0, 0))
|
|
self.notebook.pack(pady=(5, 0), padx=0, fill="both", expand=True)
|
|
|
|
# --- Instantiate and Add Tabs ---
|
|
self.repo_tab = RepoTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.repo_tab, text=" Repository / Bundle ")
|
|
|
|
self.submodules_tab = SubmodulesTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.submodules_tab, text=" Submodules ")
|
|
|
|
self.remote_tab = RemoteTab(
|
|
self.notebook, **kwargs
|
|
) # <-- Questo probabilmente era già giusto
|
|
self.notebook.add(self.remote_tab, text=" Remote Repository ")
|
|
|
|
self.backup_tab = BackupTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.backup_tab, text=" Backup Settings ")
|
|
|
|
self.commit_tab = CommitTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.commit_tab, text=" Commit / Changes ")
|
|
|
|
self.tags_tab = TagsTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.tags_tab, text=" Tags ")
|
|
|
|
self.branch_tab = BranchTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.branch_tab, text=" Branches (Local Ops) ")
|
|
|
|
self.automation_tab = AutomationTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.automation_tab, text=" Automation ")
|
|
|
|
self.history_tab = HistoryTab(self.notebook, **kwargs)
|
|
self.notebook.add(self.history_tab, text=" History ")
|
|
|
|
# --- Log and Status Bar ---
|
|
log_frame_container = ttk.Frame(self)
|
|
log_frame_container.pack(
|
|
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0)
|
|
)
|
|
self._create_log_area(log_frame_container)
|
|
|
|
self.status_bar = ttk.Label(
|
|
self,
|
|
textvariable=self.status_bar_var,
|
|
relief=tk.SUNKEN,
|
|
anchor=tk.W,
|
|
padding=(5, 2),
|
|
)
|
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, pady=(2, 0), padx=0)
|
|
|
|
self._status_reset_timer: Optional[str] = None
|
|
self._initialize_profile_selection()
|
|
|
|
def _create_profile_frame(self, save_cb, add_cb, clone_cb, remove_cb):
|
|
profile_outer_frame = ttk.Frame(self, padding=(0, 0, 0, 5))
|
|
profile_outer_frame.pack(fill="x", side=tk.TOP)
|
|
frame = ttk.LabelFrame(
|
|
profile_outer_frame, text="Profile Management", padding=(10, 5)
|
|
)
|
|
frame.pack(fill="x")
|
|
frame.columnconfigure(1, weight=1)
|
|
|
|
ttk.Label(frame, text="Select Profile:").grid(
|
|
row=0, column=0, sticky=tk.W, padx=5, pady=5
|
|
)
|
|
self.profile_dropdown = ttk.Combobox(
|
|
frame,
|
|
textvariable=self.profile_var,
|
|
state="readonly",
|
|
width=35,
|
|
values=self.initial_profile_sections,
|
|
)
|
|
self.profile_dropdown.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
|
|
|
|
# MODIFICA: Bind all'handler intermediario
|
|
self.profile_dropdown.bind("<<ComboboxSelected>>", self._on_profile_change)
|
|
self.profile_var.trace_add("write", self._on_profile_change)
|
|
|
|
button_subframe = ttk.Frame(frame)
|
|
button_subframe.grid(row=0, column=2, sticky=tk.E, padx=(10, 0))
|
|
|
|
self.save_settings_button = ttk.Button(
|
|
button_subframe, text="Save Profile", command=save_cb
|
|
)
|
|
self.save_settings_button.pack(side=tk.LEFT, padx=(0, 2), pady=5)
|
|
|
|
self.add_profile_button = ttk.Button(
|
|
button_subframe, text="Add New", width=8, command=add_cb
|
|
)
|
|
self.add_profile_button.pack(side=tk.LEFT, padx=(2, 2), pady=5)
|
|
|
|
self.clone_profile_button = ttk.Button(
|
|
button_subframe, text="Clone from Remote", width=18, command=clone_cb
|
|
)
|
|
self.clone_profile_button.pack(side=tk.LEFT, padx=5, pady=5)
|
|
|
|
self.remove_profile_button = ttk.Button(
|
|
button_subframe, text="Remove", width=8, command=remove_cb
|
|
)
|
|
self.remove_profile_button.pack(side=tk.LEFT, padx=(2, 0), pady=5)
|
|
|
|
def _on_profile_change(self, *args):
|
|
"""Intermediate handler for profile selection changes."""
|
|
if callable(self.load_profile_settings_callback):
|
|
self.load_profile_settings_callback(self.profile_var.get())
|
|
|
|
def _create_log_area(self, parent_frame):
|
|
log_frame = ttk.LabelFrame(
|
|
parent_frame, text="Application Log", padding=(10, 5)
|
|
)
|
|
log_frame.pack(fill=tk.BOTH, expand=True)
|
|
log_frame.rowconfigure(0, weight=1)
|
|
log_frame.columnconfigure(0, weight=1)
|
|
|
|
self.log_text = scrolledtext.ScrolledText(
|
|
log_frame,
|
|
height=8,
|
|
width=100,
|
|
font=("Consolas", 9),
|
|
wrap=tk.WORD,
|
|
state=tk.DISABLED,
|
|
padx=5,
|
|
pady=5,
|
|
borderwidth=1,
|
|
relief=tk.SUNKEN,
|
|
)
|
|
self.log_text.grid(row=0, column=0, sticky="nsew")
|
|
|
|
self.log_text.tag_config("INFO", foreground="black")
|
|
self.log_text.tag_config("DEBUG", foreground="grey")
|
|
self.log_text.tag_config("WARNING", foreground="orange")
|
|
self.log_text.tag_config("ERROR", foreground="red")
|
|
self.log_text.tag_config(
|
|
"CRITICAL", foreground="red", font=("Consolas", 9, "bold")
|
|
)
|
|
|
|
def _initialize_profile_selection(self):
|
|
current_profiles = self.profile_dropdown.cget("values")
|
|
if not isinstance(current_profiles, (list, tuple)):
|
|
current_profiles = []
|
|
target_profile = ""
|
|
if DEFAULT_PROFILE in current_profiles:
|
|
target_profile = DEFAULT_PROFILE
|
|
elif current_profiles:
|
|
target_profile = current_profiles[0]
|
|
|
|
if target_profile:
|
|
self.profile_var.set(target_profile)
|
|
else:
|
|
self.profile_var.set("")
|
|
self.update_status_bar("No profiles found. Please add or clone a profile.")
|
|
|
|
# --- Delegated GUI Update Methods ---
|
|
def update_profile_dropdown(self, sections: List[str]):
|
|
if hasattr(self, "profile_dropdown") and self.profile_dropdown.winfo_exists():
|
|
curr = self.profile_var.get()
|
|
self.profile_dropdown["values"] = sections
|
|
if sections:
|
|
if curr in sections:
|
|
self.profile_var.set(curr)
|
|
else:
|
|
self.profile_var.set(sections[0] if sections else "")
|
|
else:
|
|
self.profile_var.set("")
|
|
|
|
def update_tag_list(self, tags_data: List[Tuple[str, str]]):
|
|
if hasattr(self, "tags_tab"):
|
|
self.tags_tab.update_tag_list(tags_data)
|
|
|
|
def update_branch_list(self, branches: List[str], current_branch: Optional[str]):
|
|
self.current_local_branch = current_branch
|
|
if hasattr(self, "branch_tab"):
|
|
self.branch_tab.update_branch_list(branches, current_branch)
|
|
if hasattr(self, "remote_tab"):
|
|
self.remote_tab.update_local_branch_list(branches, current_branch)
|
|
|
|
def update_history_display(self, log_lines: List[str]):
|
|
if hasattr(self, "history_tab"):
|
|
self.history_tab.update_history_display(log_lines)
|
|
|
|
def update_history_branch_filter(self, branches_tags: List[str]):
|
|
if hasattr(self, "history_tab"):
|
|
self.history_tab.update_history_branch_filter(branches_tags)
|
|
|
|
def update_changed_files_list(self, files_status_list: List[str]):
|
|
if hasattr(self, "commit_tab"):
|
|
self.commit_tab.update_changed_files_list(files_status_list)
|
|
|
|
def update_remote_branches_list(self, remote_branch_list: List[str]):
|
|
if hasattr(self, "remote_tab"):
|
|
self.remote_tab.update_remote_branches_list(remote_branch_list)
|
|
|
|
def update_submodules_list(self, submodules_data: Optional[List[Dict[str, str]]]):
|
|
if hasattr(self, "submodules_tab"):
|
|
self.submodules_tab.update_submodules_list(submodules_data)
|
|
|
|
# --- Delegated Getter/Action Methods ---
|
|
def get_commit_message(self) -> str:
|
|
return (
|
|
self.commit_tab.get_commit_message() if hasattr(self, "commit_tab") else ""
|
|
)
|
|
|
|
def clear_commit_message(self):
|
|
if hasattr(self, "commit_tab"):
|
|
self.commit_tab.clear_commit_message()
|
|
|
|
def get_selected_tag(self) -> Optional[str]:
|
|
return self.tags_tab.get_selected_tag() if hasattr(self, "tags_tab") else None
|
|
|
|
def get_selected_branch(self) -> Optional[str]:
|
|
active_tab_index = self.notebook.index(self.notebook.select())
|
|
# Safely determine the index of the branch_tab inside the notebook.
|
|
try:
|
|
branch_tab_index = self.notebook.index(self.branch_tab)
|
|
except Exception:
|
|
# Fallback: find by comparing widget objects for each tab id
|
|
branch_tab_index = None
|
|
for i, tab_id in enumerate(self.notebook.tabs()):
|
|
try:
|
|
w = self.notebook.nametowidget(tab_id)
|
|
except Exception:
|
|
w = None
|
|
if w is self.branch_tab:
|
|
branch_tab_index = i
|
|
break
|
|
|
|
if branch_tab_index is not None and active_tab_index == branch_tab_index:
|
|
return (
|
|
self.branch_tab.get_selected_branch()
|
|
if hasattr(self, "branch_tab")
|
|
else None
|
|
)
|
|
elif active_tab_index == self.notebook.tabs().index(self.remote_tab):
|
|
return (
|
|
self.remote_tab.get_selected_local_branch()
|
|
if hasattr(self, "remote_tab")
|
|
else None
|
|
)
|
|
return None
|
|
|
|
# --- Global State Management ---
|
|
def set_action_widgets_state(self, state: str):
|
|
for btn_name in ["save_settings_button", "remove_profile_button"]:
|
|
widget = getattr(self, btn_name, None)
|
|
if widget and widget.winfo_exists():
|
|
widget.config(state=state)
|
|
|
|
for tab_name in [
|
|
"repo_tab",
|
|
"submodules_tab",
|
|
"remote_tab",
|
|
"backup_tab",
|
|
"commit_tab",
|
|
"tags_tab",
|
|
"branch_tab",
|
|
"automation_tab",
|
|
"history_tab",
|
|
'transformation_tab'
|
|
]:
|
|
tab_instance = getattr(self, tab_name, None)
|
|
if tab_instance and hasattr(tab_instance, "set_action_widgets_state"):
|
|
tab_instance.set_action_widgets_state(state)
|
|
|
|
def update_status_bar(
|
|
self,
|
|
message: str,
|
|
bg_color: Optional[str] = None,
|
|
duration_ms: Optional[int] = None,
|
|
):
|
|
if (
|
|
hasattr(self, "status_bar_var")
|
|
and hasattr(self, "status_bar")
|
|
and self.status_bar.winfo_exists()
|
|
):
|
|
if self._status_reset_timer:
|
|
self.master.after_cancel(self._status_reset_timer)
|
|
self._status_reset_timer = None
|
|
|
|
actual_bg = bg_color if bg_color else self.STATUS_DEFAULT_BG
|
|
self.status_bar_var.set(message)
|
|
self.status_bar.config(background=actual_bg)
|
|
|
|
if bg_color and duration_ms and duration_ms > 0:
|
|
self._status_reset_timer = self.master.after(
|
|
duration_ms, self.reset_status_bar_color
|
|
)
|
|
|
|
def reset_status_bar_color(self):
|
|
self._status_reset_timer = None
|
|
if hasattr(self, "status_bar") and self.status_bar.winfo_exists():
|
|
if MainFrame.STATUS_DEFAULT_BG is None:
|
|
MainFrame.STATUS_DEFAULT_BG = self.status_bar.cget("background")
|
|
self.status_bar.config(background=MainFrame.STATUS_DEFAULT_BG)
|
|
|
|
# --- Passthrough for Dialogs ---
|
|
def ask_new_profile_name(self) -> Optional[str]:
|
|
return simpledialog.askstring("Add Profile", "Enter name:", parent=self.master)
|
|
|
|
def ask_branch_name(
|
|
self, title: str, prompt: str, default: Optional[str] = None
|
|
) -> Optional[str]:
|
|
"""Ask the user for a branch name with an optional default value."""
|
|
if default:
|
|
return simpledialog.askstring(
|
|
title, prompt, initialvalue=default, parent=self.master
|
|
)
|
|
return simpledialog.askstring(title, prompt, parent=self.master)
|
|
|
|
def ask_new_submodule_details(self) -> Optional[Tuple[str, str]]:
|
|
url = simpledialog.askstring("Add Submodule", "Repository URL:", parent=self)
|
|
if not url:
|
|
return None
|
|
path = simpledialog.askstring("Add Submodule", "Local Path:", parent=self)
|
|
if not path:
|
|
return None
|
|
return url.strip(), path.strip()
|
|
|
|
def show_error(self, title: str, message: str):
|
|
messagebox.showerror(title, message, parent=self.master)
|
|
|
|
def show_info(self, title: str, message: str):
|
|
messagebox.showinfo(title, message, parent=self.master)
|
|
|
|
def show_warning(self, title: str, message: str):
|
|
messagebox.showwarning(title, message, parent=self.master)
|
|
|
|
def ask_yes_no(self, title: str, message: str) -> bool:
|
|
return messagebox.askyesno(title, message, parent=self.master)
|