SXXXXXXX_GitUtility/gui.py

1458 lines
57 KiB
Python

# --- START OF FILE gui.py ---
# gui.py
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext, filedialog, messagebox, simpledialog
# Rimosso import logging
import os
import re # Needed for validation in dialogs
import sys # Per fallback print
# Importa il gestore della coda log (anche se gui.py non logga molto direttamente)
import log_handler
# Import constant from the central location if available
try:
from config_manager import DEFAULT_BACKUP_DIR
except ImportError:
DEFAULT_BACKUP_DIR = os.path.join(os.path.expanduser("~"), "backup_fallback")
# Usa print qui perché il sistema di log potrebbe non essere inizializzato
print(
f"WARNING: gui.py could not import DEFAULT_BACKUP_DIR. Using fallback: {DEFAULT_BACKUP_DIR}",
file=sys.stderr,
)
# --- Tooltip Class Definition (invariata rispetto all'ultima versione, non logga) ---
class Tooltip:
"""Simple tooltip implementation for Tkinter widgets."""
def __init__(self, widget, text):
self.widget = widget
self.text = text
self.tooltip_window = None
self.id = None
if self.widget and self.widget.winfo_exists():
self.widget.bind("<Enter>", self.enter, add="+")
self.widget.bind("<Leave>", self.leave, add="+")
self.widget.bind("<ButtonPress>", self.leave, add="+")
def enter(self, event=None):
self.unschedule()
id = None
# PEP8 Fix: assignment on separate line
def leave(self, event=None):
self.unschedule()
self.hidetip()
def unschedule(self):
id_to_cancel = self.id
self.id = None
if id_to_cancel:
try:
if self.widget and self.widget.winfo_exists():
self.widget.after_cancel(id_to_cancel)
except Exception:
pass
def showtip(self):
if not self.widget or not self.widget.winfo_exists():
return
self.hidetip()
x_cursor = 0
y_cursor = 0 # Init vars
try:
x_cursor = self.widget.winfo_pointerx() + 15
y_cursor = self.widget.winfo_pointery() + 10
except Exception:
try:
x_root = self.widget.winfo_rootx()
y_root = self.widget.winfo_rooty()
x_cursor = x_root + self.widget.winfo_width() // 2
y_cursor = y_root + self.widget.winfo_height() + 5
except Exception:
return
self.tooltip_window = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(True)
try:
tw.wm_geometry(f"+{int(x_cursor)}+{int(y_cursor)}")
except tk.TclError:
tw.destroy()
self.tooltip_window = None
return
label = tk.Label(
tw,
text=self.text,
justify=tk.LEFT,
background="#ffffe0",
relief=tk.SOLID,
borderwidth=1,
font=("tahoma", "8", "normal"),
wraplength=350,
)
label.pack(ipadx=3, ipady=3)
def hidetip(self):
tw = self.tooltip_window
self.tooltip_window = None
if tw:
try:
if tw.winfo_exists():
tw.destroy()
except Exception:
pass
# --- Gitignore Editor Window Class ---
class GitignoreEditorWindow(tk.Toplevel):
"""Toplevel window for editing the .gitignore file. Uses log_handler."""
# Rimosso logger da __init__ e fallback
def __init__(
self, master, gitignore_path, logger_ignored=None, on_save_success_callback=None
):
"""Initialize the Gitignore Editor window."""
super().__init__(master)
self.gitignore_path = gitignore_path
# Non c'è più self.logger
self.original_content = ""
self.on_save_success_callback = on_save_success_callback
self.title(f"Edit {os.path.basename(gitignore_path)}")
self.geometry("600x450")
self.minsize(400, 300)
self.grab_set()
self.transient(master)
self.protocol("WM_DELETE_WINDOW", self._on_close)
main_frame = ttk.Frame(self, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
main_frame.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
self.text_editor = scrolledtext.ScrolledText(
main_frame,
wrap=tk.WORD,
font=("Consolas", 10),
undo=True,
padx=5,
pady=5,
borderwidth=1,
relief=tk.SUNKEN,
)
self.text_editor.grid(row=0, column=0, sticky="nsew", pady=(0, 10))
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=1, column=0, sticky="ew")
button_frame.columnconfigure(0, weight=1) # Push buttons right
self.save_button = ttk.Button(
button_frame, text="Save and Close", command=self._save_and_close
)
self.save_button.grid(row=0, column=2, padx=5)
self.cancel_button = ttk.Button(
button_frame, text="Cancel", command=self._on_close
)
self.cancel_button.grid(row=0, column=1, padx=5)
self._load_file()
self._center_window(master)
self.wait_window()
def _center_window(self, parent):
"""Centers the Toplevel window relative to its parent."""
func_name = "_center_window (Gitignore)"
try:
self.update_idletasks()
px = parent.winfo_rootx()
py = parent.winfo_rooty()
pw = parent.winfo_width()
ph = parent.winfo_height()
ww = self.winfo_width()
wh = self.winfo_height()
x = px + (pw // 2) - (ww // 2)
y = py + (ph // 2) - (wh // 2)
sw = self.winfo_screenwidth()
sh = self.winfo_screenheight()
x = max(0, min(x, sw - ww))
y = max(0, min(y, sh - wh))
self.geometry(f"+{int(x)}+{int(y)}")
except Exception as e:
# Usa log_handler se possibile, altrimenti print
try:
log_handler.log_error(
f"Could not center GitignoreEditor: {e}", func_name=func_name
)
except NameError:
print(f"ERROR: Could not center GitignoreEditor: {e}", file=sys.stderr)
def _load_file(self):
"""Loads the content of the .gitignore file. Uses log_handler."""
func_name = "_load_file (Gitignore)"
log_handler.log_info(
f"Loading gitignore: {self.gitignore_path}", func_name=func_name
)
content = ""
try:
if os.path.exists(self.gitignore_path):
with open(
self.gitignore_path, "r", encoding="utf-8", errors="replace"
) as f:
content = f.read()
else:
log_handler.log_info(
f".gitignore does not exist at: {self.gitignore_path}",
func_name=func_name,
)
self.original_content = content
self.text_editor.config(state=tk.NORMAL)
self.text_editor.delete("1.0", tk.END)
self.text_editor.insert(tk.END, self.original_content)
self.text_editor.edit_reset()
self.text_editor.focus_set()
except IOError as e:
log_handler.log_error(
f"I/O error loading .gitignore: {e}", func_name=func_name
)
messagebox.showerror(
"Load Error", f"Error reading .gitignore:\n{e}", parent=self
)
except Exception as e:
log_handler.log_exception(
f"Unexpected error loading .gitignore: {e}", func_name=func_name
)
messagebox.showerror(
"Load Error", f"Unexpected error loading:\n{e}", parent=self
)
self.text_editor.config(state=tk.DISABLED)
def _has_changes(self):
"""Checks if the editor content differs from the original."""
try:
return self.text_editor.get("1.0", "end-1c") != self.original_content
except Exception:
return True # Assume changes on error
def _save_file(self):
"""Saves the current content to the .gitignore file. Uses log_handler."""
func_name = "_save_file (Gitignore)"
if not self._has_changes():
log_handler.log_info(
"No changes to save in .gitignore.", func_name=func_name
)
return True
current_content = self.text_editor.get("1.0", "end-1c")
log_handler.log_info(
f"Saving changes to: {self.gitignore_path}", func_name=func_name
)
try:
with open(self.gitignore_path, "w", encoding="utf-8", newline="\n") as f:
f.write(current_content)
log_handler.log_info(".gitignore saved successfully.", func_name=func_name)
self.original_content = current_content
self.text_editor.edit_modified(False)
return True
except IOError as e:
log_handler.log_error(
f"I/O error saving .gitignore: {e}", func_name=func_name
)
messagebox.showerror(
"Save Error", f"Error writing .gitignore:\n{e}", parent=self
)
return False
except Exception as e:
log_handler.log_exception(
f"Unexpected error saving .gitignore: {e}", func_name=func_name
)
messagebox.showerror(
"Save Error", f"Unexpected error saving:\n{e}", parent=self
)
return False
def _save_and_close(self):
"""Saves the file and closes the window, calling callback on success."""
func_name = "_save_and_close (Gitignore)"
save_successful = self._save_file()
if save_successful:
log_handler.log_debug(
"Save successful, attempting callback.", func_name=func_name
)
if self.on_save_success_callback and callable(
self.on_save_success_callback
):
try:
self.on_save_success_callback()
except Exception as cb_e:
log_handler.log_exception(
f"Error in on_save_success_callback: {cb_e}",
func_name=func_name,
)
messagebox.showwarning(
"Callback Error",
"Saved, but post-save action failed.\nCheck logs.",
parent=self,
)
self.destroy() # Close window
def _on_close(self):
"""Handles window close event (X or Cancel button)."""
func_name = "_on_close (Gitignore)"
if self._has_changes():
res = messagebox.askyesnocancel(
"Unsaved Changes", "Save changes?", parent=self
)
if res is True:
self._save_and_close() # Yes - Save
elif res is False:
log_handler.log_warning(
"Discarding .gitignore changes.", func_name=func_name
)
self.destroy() # No - Discard
# else: Cancel - do nothing
else:
self.destroy() # No changes - just close
# --- Create Tag Dialog (invariata, non logga) ---
class CreateTagDialog(simpledialog.Dialog):
"""Dialog to get tag name and message from the user."""
def __init__(self, parent, title="Create New Tag", suggested_tag_name=""):
self.tag_name_var = tk.StringVar()
self.tag_message_var = tk.StringVar()
self.result = None
self.suggested_tag_name = suggested_tag_name
super().__init__(parent, title=title)
def body(self, master):
frame = ttk.Frame(master, padding="10")
frame.pack(fill="x")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="Tag Name:").grid(
row=0, column=0, padx=5, pady=5, sticky="w"
)
self.name_entry = ttk.Entry(frame, textvariable=self.tag_name_var, width=40)
self.name_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
if self.suggested_tag_name:
self.tag_name_var.set(self.suggested_tag_name)
ttk.Label(frame, text="Tag Message:").grid(
row=1, column=0, padx=5, pady=5, sticky="w"
)
self.message_entry = ttk.Entry(
frame, textvariable=self.tag_message_var, width=40
)
self.message_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
return self.name_entry
def validate(self):
name = self.tag_name_var.get().strip()
msg = self.tag_message_var.get().strip()
if not name:
messagebox.showwarning("Input Error", "Tag name empty.", parent=self)
return 0
if not msg:
messagebox.showwarning("Input Error", "Tag message empty.", parent=self)
return 0
# GitCommands handles detailed name validation
return 1
def apply(self):
self.result = (
self.tag_name_var.get().strip(),
self.tag_message_var.get().strip(),
)
# --- Create Branch Dialog (invariata, non logga) ---
class CreateBranchDialog(simpledialog.Dialog):
"""Dialog to get a new branch name from the user."""
def __init__(self, parent, title="Create New Branch"):
self.branch_name_var = tk.StringVar()
self.result = None
super().__init__(parent, title=title)
def body(self, master):
frame = ttk.Frame(master, padding="10")
frame.pack(fill="x")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="New Branch Name:").grid(
row=0, column=0, padx=5, pady=10, sticky="w"
)
self.name_entry = ttk.Entry(frame, textvariable=self.branch_name_var, width=40)
self.name_entry.grid(row=0, column=1, padx=5, pady=10, sticky="ew")
return self.name_entry
def validate(self):
name = self.branch_name_var.get().strip()
if not name:
messagebox.showwarning("Input Error", "Branch name empty.", parent=self)
return 0
pattern = (
r"^(?!\.| |.*[/.]\.|\.|.*\\|.*@\{|.*[/]$|.*\.\.)[^ \t\n\r\f\v~^:?*[\\]+$"
)
if not re.match(pattern, name) or name.lower() == "head":
messagebox.showwarning(
"Input Error", "Invalid branch name format.", parent=self
)
return 0
return 1
def apply(self):
self.result = self.branch_name_var.get().strip()
# --- Main Application Frame ---
class MainFrame(ttk.Frame):
"""The main frame using ttk.Notebook. Does not log directly."""
GREEN = "#90EE90"
RED = "#F08080" # Color constants
def __init__(
self,
master,
load_profile_settings_cb,
browse_folder_cb,
update_svn_status_cb,
prepare_svn_for_git_cb,
create_git_bundle_cb,
fetch_from_git_bundle_cb,
config_manager_instance,
profile_sections_list,
add_profile_cb,
remove_profile_cb,
manual_backup_cb,
open_gitignore_editor_cb,
save_profile_cb,
commit_changes_cb,
refresh_tags_cb,
create_tag_cb,
checkout_tag_cb,
refresh_history_cb,
refresh_branches_cb,
checkout_branch_cb,
create_branch_cb,
refresh_changed_files_cb,
open_diff_viewer_cb,
add_selected_file_cb,
):
"""Initializes the MainFrame."""
super().__init__(master)
self.master = master
# Store callbacks provided by the controller
self.load_profile_settings_callback = load_profile_settings_cb
self.browse_folder_callback = browse_folder_cb
self.update_svn_status_callback = update_svn_status_cb
self.prepare_svn_for_git_callback = prepare_svn_for_git_cb
self.create_git_bundle_callback = create_git_bundle_cb
self.fetch_from_git_bundle_callback = fetch_from_git_bundle_cb
self.manual_backup_callback = manual_backup_cb
self.add_profile_callback = add_profile_cb
self.remove_profile_callback = remove_profile_cb
self.save_profile_callback = save_profile_cb
self.open_gitignore_editor_callback = open_gitignore_editor_cb
self.commit_changes_callback = commit_changes_cb
self.refresh_tags_callback = refresh_tags_cb
self.create_tag_callback = create_tag_cb
self.checkout_tag_callback = checkout_tag_cb
self.refresh_history_callback = refresh_history_cb
self.refresh_branches_callback = refresh_branches_cb
self.checkout_branch_callback = checkout_branch_cb
self.create_branch_callback = create_branch_cb
self.refresh_changed_files_callback = refresh_changed_files_cb
self.open_diff_viewer_callback = open_diff_viewer_cb
self.add_selected_file_callback = add_selected_file_cb
self.config_manager = config_manager_instance
self.initial_profile_sections = profile_sections_list
# Configure style (invariato)
self.style = ttk.Style()
available = self.style.theme_names()
preferred = ["vista", "xpnative", "clam"]
theme = "clam"
for t in preferred:
if t in available:
theme = t
break
try:
self.style.theme_use(theme)
except tk.TclError:
print(f"Warning: Theme '{theme}' not found.", file=sys.stderr)
theme = self.style.theme_use()
# print(f"Using ttk theme: {theme}") # Rimosso log diretto
self.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
# --- Tkinter Variables ---
self.profile_var = tk.StringVar()
self.autobackup_var = tk.BooleanVar()
self.backup_dir_var = tk.StringVar()
self.backup_exclude_extensions_var = tk.StringVar()
self.backup_exclude_dirs_var = tk.StringVar()
self.autocommit_var = tk.BooleanVar()
self.status_bar_var = tk.StringVar()
# --- Create UI Elements ---
self._create_profile_frame()
self.notebook = ttk.Notebook(self, padding=(0, 5, 0, 0))
self.notebook.pack(pady=(5, 0), padx=0, fill="both", expand=True)
self.repo_tab_frame = self._create_repo_tab()
self.backup_tab_frame = self._create_backup_tab()
self.commit_tab_frame = self._create_commit_tab()
self.tags_tab_frame = self._create_tags_tab()
self.branch_tab_frame = self._create_branch_tab()
self.history_tab_frame = self._create_history_tab()
self.notebook.add(self.repo_tab_frame, text=" Repository / Bundle ")
self.notebook.add(self.backup_tab_frame, text=" Backup Settings ")
self.notebook.add(self.commit_tab_frame, text=" Commit / Changes ")
self.notebook.add(self.tags_tab_frame, text=" Tags ")
self.notebook.add(self.branch_tab_frame, text=" Branches ")
self.notebook.add(self.history_tab_frame, text=" History ")
log_frame_container = ttk.Frame(self)
log_frame_container.pack(
side=tk.TOP, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0)
) # Pack sopra status bar
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)
# --- Initial State ---
self._initialize_profile_selection()
self.toggle_backup_dir()
self.update_status_bar("Ready.")
# --- Frame Creation Methods (_create_profile_frame, _create_repo_tab, etc.) ---
# (Questi metodi rimangono invariati rispetto all'ultima versione valida,
# non contengono chiamate dirette al logger. Li ometto per brevità qui.)
# --- >> INCOLLA QUI I METODI _create_* DAL FILE GUI.PY PRECEDENTE << ---
def _create_profile_frame(self):
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)
self.profile_dropdown.bind(
"<<ComboboxSelected>>",
lambda e: self.load_profile_settings_callback(self.profile_var.get()),
)
self.profile_var.trace_add(
"write",
lambda n, i, m: self.load_profile_settings_callback(self.profile_var.get()),
)
self.create_tooltip(self.profile_dropdown, "Select config profile.")
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=self.save_profile_callback
)
self.save_settings_button.pack(side=tk.LEFT, padx=(0, 2), pady=5)
self.create_tooltip(self.save_settings_button, "Save settings to profile.")
self.add_profile_button = ttk.Button(
button_subframe, text="Add New", width=8, command=self.add_profile_callback
)
self.add_profile_button.pack(side=tk.LEFT, padx=(2, 2), pady=5)
self.create_tooltip(self.add_profile_button, "Add new profile.")
self.remove_profile_button = ttk.Button(
button_subframe,
text="Remove",
width=8,
command=self.remove_profile_callback,
)
self.remove_profile_button.pack(side=tk.LEFT, padx=(2, 0), pady=5)
self.create_tooltip(self.remove_profile_button, "Remove selected profile.")
def _create_repo_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.columnconfigure(1, weight=1)
paths_frame = ttk.LabelFrame(
frame, text="Paths & Bundle Names", padding=(10, 5)
)
paths_frame.pack(pady=5, fill="x")
paths_frame.columnconfigure(1, weight=1)
cl, ce, cb, ci = 0, 1, 2, 3 # Column indices
ttk.Label(paths_frame, text="Working Directory Path:").grid(
row=0, column=cl, sticky=tk.W, padx=5, pady=3
)
self.svn_path_entry = ttk.Entry(paths_frame, width=60)
self.svn_path_entry.grid(row=0, column=ce, sticky=tk.EW, padx=5, pady=3)
self.svn_path_entry.bind(
"<FocusOut>",
lambda e: self.update_svn_status_callback(self.svn_path_entry.get()),
)
self.svn_path_entry.bind(
"<Return>",
lambda e: self.update_svn_status_callback(self.svn_path_entry.get()),
)
self.create_tooltip(self.svn_path_entry, "Path to Git repo.")
self.svn_path_browse_button = ttk.Button(
paths_frame,
text="Browse...",
width=9,
command=lambda: self.browse_folder_callback(self.svn_path_entry),
)
self.svn_path_browse_button.grid(
row=0, column=cb, sticky=tk.W, padx=(0, 5), pady=3
)
self.svn_status_indicator = tk.Label(
paths_frame,
text="",
width=2,
height=1,
relief=tk.SUNKEN,
background=self.RED,
anchor=tk.CENTER,
)
self.svn_status_indicator.grid(
row=0, column=ci, sticky=tk.E, padx=(0, 5), pady=3
)
self.create_tooltip(
self.svn_status_indicator, "Git repo status (Red=No, Green=Yes)"
)
ttk.Label(paths_frame, text="Bundle Target Directory:").grid(
row=1, column=cl, sticky=tk.W, padx=5, pady=3
)
self.usb_path_entry = ttk.Entry(paths_frame, width=60)
self.usb_path_entry.grid(row=1, column=ce, sticky=tk.EW, padx=5, pady=3)
self.create_tooltip(self.usb_path_entry, "Dir for bundle files.")
self.usb_path_browse_button = ttk.Button(
paths_frame,
text="Browse...",
width=9,
command=lambda: self.browse_folder_callback(self.usb_path_entry),
)
self.usb_path_browse_button.grid(
row=1, column=cb, sticky=tk.W, padx=(0, 5), pady=3
)
ttk.Label(paths_frame, text="Create Bundle Filename:").grid(
row=2, column=cl, sticky=tk.W, padx=5, pady=3
)
self.bundle_name_entry = ttk.Entry(paths_frame, width=60)
self.bundle_name_entry.grid(
row=2, column=ce, columnspan=2, sticky=tk.EW, padx=5, pady=3
)
self.create_tooltip(self.bundle_name_entry, "Filename for bundle creation.")
ttk.Label(paths_frame, text="Fetch Bundle Filename:").grid(
row=3, column=cl, sticky=tk.W, padx=5, pady=3
)
self.bundle_updated_name_entry = ttk.Entry(paths_frame, width=60)
self.bundle_updated_name_entry.grid(
row=3, column=ce, columnspan=2, sticky=tk.EW, padx=5, pady=3
)
self.create_tooltip(
self.bundle_updated_name_entry, "Filename for bundle fetch."
)
actions_frame = ttk.LabelFrame(
frame, text="Repository Actions", padding=(10, 5)
)
actions_frame.pack(pady=10, fill="x")
self.prepare_svn_button = ttk.Button(
actions_frame,
text="Prepare Repository",
command=self.prepare_svn_for_git_callback,
state=tk.DISABLED,
)
self.prepare_svn_button.pack(side=tk.LEFT, padx=(0, 5), pady=5)
self.create_tooltip(self.prepare_svn_button, "Initialize Git & .gitignore.")
self.create_bundle_button = ttk.Button(
actions_frame,
text="Create Bundle",
command=self.create_git_bundle_callback,
state=tk.DISABLED,
)
self.create_bundle_button.pack(side=tk.LEFT, padx=5, pady=5)
self.create_tooltip(self.create_bundle_button, "Create Git bundle file.")
self.fetch_bundle_button = ttk.Button(
actions_frame,
text="Fetch from Bundle",
command=self.fetch_from_git_bundle_callback,
state=tk.DISABLED,
)
self.fetch_bundle_button.pack(side=tk.LEFT, padx=5, pady=5)
self.create_tooltip(self.fetch_bundle_button, "Fetch & merge from bundle.")
self.edit_gitignore_button = ttk.Button(
actions_frame,
text="Edit .gitignore",
width=12,
command=self.open_gitignore_editor_callback,
state=tk.DISABLED,
)
self.edit_gitignore_button.pack(side=tk.LEFT, padx=5, pady=5)
self.create_tooltip(self.edit_gitignore_button, "Edit .gitignore file.")
return frame
def _create_backup_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.columnconfigure(1, weight=1)
config_frame = ttk.LabelFrame(
frame, text="Backup Configuration", padding=(10, 5)
)
config_frame.pack(pady=5, fill="x", expand=False)
config_frame.columnconfigure(1, weight=1)
cl, ce, cb = 0, 1, 2 # Column indices
self.autobackup_checkbox = ttk.Checkbutton(
config_frame,
text="Enable Auto Backup before Actions",
variable=self.autobackup_var,
command=self.toggle_backup_dir,
)
self.autobackup_checkbox.grid(
row=0, column=0, columnspan=3, sticky=tk.W, padx=5, pady=(5, 5)
)
self.create_tooltip(
self.autobackup_checkbox, "Auto ZIP backup before bundle ops."
)
backup_dir_label = ttk.Label(config_frame, text="Backup Directory:")
backup_dir_label.grid(row=1, column=cl, sticky=tk.W, padx=5, pady=3)
self.backup_dir_entry = ttk.Entry(
config_frame, textvariable=self.backup_dir_var, width=60, state=tk.DISABLED
)
self.backup_dir_entry.grid(row=1, column=ce, sticky=tk.EW, padx=5, pady=3)
self.create_tooltip(self.backup_dir_entry, "Where backups are stored.")
self.backup_dir_button = ttk.Button(
config_frame,
text="Browse...",
width=9,
command=self.browse_backup_dir,
state=tk.DISABLED,
)
self.backup_dir_button.grid(row=1, column=cb, sticky=tk.W, padx=(0, 5), pady=3)
exclude_ext_label = ttk.Label(config_frame, text="Exclude File Exts:")
exclude_ext_label.grid(row=2, column=cl, sticky=tk.W, padx=5, pady=3)
self.backup_exclude_entry = ttk.Entry(
config_frame, textvariable=self.backup_exclude_extensions_var, width=60
)
self.backup_exclude_entry.grid(
row=2, column=ce, columnspan=2, sticky=tk.EW, padx=5, pady=3
)
self.create_tooltip(
self.backup_exclude_entry, "Comma-sep extensions (.log,.tmp)"
)
exclude_dir_label = ttk.Label(config_frame, text="Exclude Dirs (Name):")
exclude_dir_label.grid(row=3, column=cl, sticky=tk.W, padx=5, pady=3)
self.backup_exclude_dirs_entry = ttk.Entry(
config_frame, textvariable=self.backup_exclude_dirs_var, width=60
)
self.backup_exclude_dirs_entry.grid(
row=3, column=ce, columnspan=2, sticky=tk.EW, padx=5, pady=3
)
self.create_tooltip(
self.backup_exclude_dirs_entry, "Comma-sep dir names (__pycache__,build)"
)
action_frame = ttk.LabelFrame(frame, text="Manual Backup", padding=(10, 5))
action_frame.pack(pady=10, fill="x", expand=False)
self.manual_backup_button = ttk.Button(
action_frame,
text="Backup Now (ZIP)",
command=self.manual_backup_callback,
state=tk.DISABLED,
)
self.manual_backup_button.pack(side=tk.LEFT, padx=5, pady=5)
self.create_tooltip(self.manual_backup_button, "Create ZIP backup now.")
return frame
def _create_commit_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.rowconfigure(3, weight=1)
frame.columnconfigure(0, weight=1) # Changes frame (row 3) expands
self.autocommit_checkbox = ttk.Checkbutton(
frame,
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)
)
self.create_tooltip(
self.autocommit_checkbox, "Auto commit before create bundle."
)
ttk.Label(frame, text="Commit Message:").grid(
row=1, column=0, columnspan=3, sticky="w", padx=5
)
self.commit_message_text = scrolledtext.ScrolledText(
frame,
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)
)
self.create_tooltip(self.commit_message_text, "Commit message.")
changes_frame = ttk.LabelFrame(
frame, 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)
self.create_tooltip(
self.changed_files_listbox, "Changed files list (Double-click to diff)."
)
self.changed_files_context_menu = tk.Menu(
self.changed_files_listbox, tearoff=0
) # Context menu
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)
)
self.create_tooltip(self.refresh_changes_button, "Refresh file list.")
self.commit_button = ttk.Button(
frame,
text="Commit Staged Changes",
command=self.commit_changes_callback,
state=tk.DISABLED,
)
self.commit_button.grid(row=4, column=2, sticky="se", padx=5, pady=5)
self.create_tooltip(self.commit_button, "Commit staged changes manually.")
return frame
def _create_tags_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.columnconfigure(0, weight=1)
frame.rowconfigure(1, weight=1)
ttk.Label(frame, text="Existing Tags (Newest First):").grid(
row=0, column=0, columnspan=2, sticky="w", padx=5, pady=(0, 2)
)
list_frame = ttk.Frame(frame)
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.tag_listbox = tk.Listbox(
list_frame,
height=10,
exportselection=False,
selectmode=tk.SINGLE,
font=("Consolas", 9),
borderwidth=1,
relief=tk.SUNKEN,
)
self.tag_listbox.grid(row=0, column=0, sticky="nsew")
scrollbar = ttk.Scrollbar(
list_frame, orient=tk.VERTICAL, command=self.tag_listbox.yview
)
scrollbar.grid(row=0, column=1, sticky="ns")
self.tag_listbox.config(yscrollcommand=scrollbar.set)
self.create_tooltip(self.tag_listbox, "Select tag to checkout.")
button_frame = ttk.Frame(frame)
button_frame.grid(row=1, column=1, sticky="ns", padx=(10, 5), pady=(0, 5))
bw = 18 # Button width
self.refresh_tags_button = ttk.Button(
button_frame,
text="Refresh Tags",
width=bw,
command=self.refresh_tags_callback,
state=tk.DISABLED,
)
self.refresh_tags_button.pack(side=tk.TOP, fill=tk.X, pady=(0, 5))
self.create_tooltip(self.refresh_tags_button, "Reload tag list.")
self.create_tag_button = ttk.Button(
button_frame,
text="Create New Tag...",
width=bw,
command=self.create_tag_callback,
state=tk.DISABLED,
)
self.create_tag_button.pack(side=tk.TOP, fill=tk.X, pady=5)
self.create_tooltip(self.create_tag_button, "Create annotated tag.")
self.checkout_tag_button = ttk.Button(
button_frame,
text="Checkout Selected Tag",
width=bw,
command=self.checkout_tag_callback,
state=tk.DISABLED,
)
self.checkout_tag_button.pack(side=tk.TOP, fill=tk.X, pady=5)
self.create_tooltip(self.checkout_tag_button, "Switch to tag (Detached HEAD).")
return frame
def _create_branch_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.columnconfigure(0, weight=1)
frame.rowconfigure(1, weight=1)
ttk.Label(frame, text="Local Branches (* = Current):").grid(
row=0, column=0, columnspan=2, sticky="w", padx=5, pady=(0, 2)
)
list_frame = ttk.Frame(frame)
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")
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)
self.create_tooltip(self.branch_listbox, "Select branch to checkout.")
button_frame = ttk.Frame(frame)
button_frame.grid(row=1, column=1, sticky="ns", padx=(10, 5), pady=(0, 5))
bw = 18 # Button width
self.refresh_branches_button = ttk.Button(
button_frame,
text="Refresh Branches",
width=bw,
command=self.refresh_branches_callback,
state=tk.DISABLED,
)
self.refresh_branches_button.pack(side=tk.TOP, fill=tk.X, pady=(0, 5))
self.create_tooltip(self.refresh_branches_button, "Reload branch list.")
self.create_branch_button = ttk.Button(
button_frame,
text="Create New Branch...",
width=bw,
command=self.create_branch_callback,
state=tk.DISABLED,
)
self.create_branch_button.pack(side=tk.TOP, fill=tk.X, pady=5)
self.create_tooltip(self.create_branch_button, "Create new local branch.")
self.checkout_branch_button = ttk.Button(
button_frame,
text="Checkout Selected Branch",
width=bw,
command=self.checkout_branch_callback,
state=tk.DISABLED,
)
self.checkout_branch_button.pack(side=tk.TOP, fill=tk.X, pady=5)
self.create_tooltip(self.checkout_branch_button, "Switch to selected branch.")
return frame
def _create_history_tab(self):
frame = ttk.Frame(self.notebook, padding=(10, 10))
frame.rowconfigure(2, weight=1)
frame.columnconfigure(0, weight=1)
filter_frame = ttk.Frame(frame)
filter_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
filter_frame.columnconfigure(1, weight=1)
ttk.Label(filter_frame, text="Filter History:").pack(side=tk.LEFT, padx=(0, 5))
self.history_branch_filter_var = tk.StringVar()
self.history_branch_filter_combo = ttk.Combobox(
filter_frame,
textvariable=self.history_branch_filter_var,
state="readonly",
width=30,
)
self.history_branch_filter_combo.pack(
side=tk.LEFT, expand=True, fill=tk.X, padx=5
)
self.history_branch_filter_combo.bind(
"<<ComboboxSelected>>", lambda e: self.refresh_history_callback()
)
self.create_tooltip(
self.history_branch_filter_combo, "Filter history by branch/tag."
)
self.refresh_history_button = ttk.Button(
filter_frame,
text="Refresh History",
command=self.refresh_history_callback,
state=tk.DISABLED,
)
self.refresh_history_button.pack(side=tk.LEFT, padx=5)
self.create_tooltip(self.refresh_history_button, "Load commit history.")
ttk.Label(frame, text="Commit History (Recent First):").grid(
row=1, column=0, sticky="w", padx=5, pady=(5, 0)
)
self.history_text = scrolledtext.ScrolledText(
frame,
height=15,
width=100,
font=("Consolas", 9),
wrap=tk.NONE,
state=tk.DISABLED,
padx=5,
pady=5,
undo=False,
borderwidth=1,
relief=tk.SUNKEN,
)
self.history_text.grid(row=2, column=0, sticky="nsew", padx=5, pady=(0, 5))
history_xscroll = ttk.Scrollbar(
frame, orient=tk.HORIZONTAL, command=self.history_text.xview
)
history_xscroll.grid(row=3, column=0, sticky="ew", padx=5)
self.history_text.config(xscrollcommand=history_xscroll.set)
return frame
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):
if not hasattr(self, "config_manager"):
return
try:
from config_manager import DEFAULT_PROFILE
except ImportError:
DEFAULT_PROFILE = "default"
current_profiles = self.profile_dropdown.cget("values")
if not isinstance(current_profiles, (list, tuple)):
current_profiles = []
if DEFAULT_PROFILE in current_profiles:
self.profile_var.set(DEFAULT_PROFILE)
elif current_profiles:
self.profile_var.set(current_profiles[0])
else:
self.profile_var.set("")
self.update_status_bar("No profiles found.")
# --- GUI Update Methods (non logganti) ---
def toggle_backup_dir(self):
state = tk.NORMAL if self.autobackup_var.get() else tk.DISABLED
if hasattr(self, "backup_dir_entry") and self.backup_dir_entry.winfo_exists():
self.backup_dir_entry.config(state=state)
if hasattr(self, "backup_dir_button") and self.backup_dir_button.winfo_exists():
self.backup_dir_button.config(state=state)
def browse_backup_dir(self):
curr = self.backup_dir_var.get()
init = curr if os.path.isdir(curr) else DEFAULT_BACKUP_DIR
if not os.path.isdir(init):
init = os.path.expanduser("~")
sel = filedialog.askdirectory(
initialdir=init, title="Select Backup Dir", parent=self.master
)
if sel:
self.backup_dir_var.set(sel)
def update_svn_indicator(self, is_prepared):
color = self.GREEN if is_prepared else self.RED
tip = "Prepared" if is_prepared else "Not prepared"
if (
hasattr(self, "svn_status_indicator")
and self.svn_status_indicator.winfo_exists()
):
self.svn_status_indicator.config(background=color)
self.update_tooltip(self.svn_status_indicator, tip)
def update_profile_dropdown(self, sections):
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:
try:
from config_manager import DEFAULT_PROFILE
except ImportError:
DEFAULT_PROFILE = "default"
if DEFAULT_PROFILE in sections:
self.profile_var.set(DEFAULT_PROFILE)
else:
self.profile_var.set(sections[0])
else:
self.profile_var.set("")
def update_tag_list(self, tags_data):
if not hasattr(self, "tag_listbox") or not self.tag_listbox.winfo_exists():
return
try:
self.tag_listbox.config(state=tk.NORMAL)
self.tag_listbox.delete(0, tk.END)
if tags_data:
try:
if self.tag_listbox.cget("fg") == "grey":
self.tag_listbox.config(
fg=self.style.lookup("TListbox", "foreground")
)
except tk.TclError:
pass
for name, subj in tags_data:
self.tag_listbox.insert(tk.END, f"{name}\t({subj})")
else:
self.tag_listbox.insert(tk.END, "(No tags found)")
self.tag_listbox.config(fg="grey")
self.tag_listbox.config(state=tk.NORMAL)
self.tag_listbox.yview_moveto(0.0)
except Exception as e: # Log to console as log_handler might not be available
print(f"ERROR updating tag list GUI: {e}", file=sys.stderr)
try:
self.tag_listbox.delete(0, tk.END)
self.tag_listbox.insert(tk.END, "(Error)")
self.tag_listbox.config(fg="red")
except:
pass
def update_branch_list(self, branches, current_branch):
if (
not hasattr(self, "branch_listbox")
or not self.branch_listbox.winfo_exists()
):
return
try:
self.branch_listbox.config(state=tk.NORMAL)
self.branch_listbox.delete(0, tk.END)
sel_idx = -1
if branches:
try:
if self.branch_listbox.cget("fg") == "grey":
self.branch_listbox.config(
fg=self.style.lookup("TListbox", "foreground")
)
except tk.TclError:
pass
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:
sel_idx = i
else:
self.branch_listbox.insert(tk.END, "(No local branches)")
self.branch_listbox.config(fg="grey")
if sel_idx >= 0:
self.branch_listbox.selection_set(sel_idx)
self.branch_listbox.see(sel_idx)
self.branch_listbox.config(state=tk.NORMAL)
self.branch_listbox.yview_moveto(0.0)
except Exception as e:
print(f"ERROR updating branch list GUI: {e}", file=sys.stderr)
try:
self.branch_listbox.delete(0, tk.END)
self.branch_listbox.insert(tk.END, "(Error)")
self.branch_listbox.config(fg="red")
except:
pass
def get_selected_tag(self):
if hasattr(self, "tag_listbox") and self.tag_listbox.winfo_exists():
idx = self.tag_listbox.curselection()
if idx:
item = self.tag_listbox.get(idx[0])
if "\t" in item and not item.startswith("("):
return item.split("\t", 1)[0].strip()
elif not item.startswith("("):
return item.strip()
return None
def get_selected_branch(self):
if hasattr(self, "branch_listbox") and self.branch_listbox.winfo_exists():
idx = self.branch_listbox.curselection()
if idx:
item = self.branch_listbox.get(idx[0])
name = item.lstrip("* ").strip()
if not name.startswith("("):
return name
return None
def get_commit_message(self):
if (
hasattr(self, "commit_message_text")
and self.commit_message_text.winfo_exists()
):
return self.commit_message_text.get("1.0", "end-1c").strip()
return ""
def clear_commit_message(self):
if (
hasattr(self, "commit_message_text")
and self.commit_message_text.winfo_exists()
):
try:
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()
except Exception:
pass
def update_history_display(self, log_lines):
if not hasattr(self, "history_text") or not self.history_text.winfo_exists():
return
try:
self.history_text.config(state=tk.NORMAL)
self.history_text.delete("1.0", tk.END)
self.history_text.insert(
tk.END, "\n".join(log_lines) if log_lines else "(No history found)"
)
self.history_text.config(state=tk.DISABLED)
self.history_text.yview_moveto(0.0)
self.history_text.xview_moveto(0.0)
except Exception as e:
print(f"ERROR updating history GUI: {e}", file=sys.stderr)
try:
self.history_text.config(state=tk.NORMAL)
self.history_text.delete("1.0", tk.END)
self.history_text.insert(tk.END, "(Error)")
self.history_text.config(state=tk.DISABLED, fg="red")
except:
pass
def update_history_branch_filter(self, branches_tags, current_ref=None):
if (
not hasattr(self, "history_branch_filter_combo")
or not self.history_branch_filter_combo.winfo_exists()
):
return
opts = ["-- All History --"] + sorted(branches_tags)
self.history_branch_filter_combo["values"] = opts
self.history_branch_filter_var.set(
current_ref if current_ref and current_ref in opts else opts[0]
)
def update_changed_files_list(self, files_status_list):
if (
not hasattr(self, "changed_files_listbox")
or not self.changed_files_listbox.winfo_exists()
):
return
try:
self.changed_files_listbox.config(state=tk.NORMAL)
self.changed_files_listbox.delete(0, tk.END)
if files_status_list:
try:
if self.changed_files_listbox.cget("fg") == "grey":
self.changed_files_listbox.config(
fg=self.style.lookup("TListbox", "foreground")
)
except tk.TclError:
pass
for line in files_status_list:
self.changed_files_listbox.insert(tk.END, line)
else:
self.changed_files_listbox.insert(tk.END, "(No changes detected)")
self.changed_files_listbox.config(fg="grey")
self.changed_files_listbox.config(state=tk.NORMAL)
self.changed_files_listbox.yview_moveto(0.0)
except Exception as e:
print(f"ERROR updating changes list GUI: {e}", file=sys.stderr)
try:
self.changed_files_listbox.delete(0, tk.END)
self.changed_files_listbox.insert(tk.END, "(Error)")
self.changed_files_listbox.config(fg="red")
except:
pass
def _on_changed_file_double_click(self, event):
widget = event.widget
sel = widget.curselection()
if sel:
line = widget.get(sel[0])
if hasattr(self, "open_diff_viewer_callback") and callable(
self.open_diff_viewer_callback
):
self.open_diff_viewer_callback(line)
def _show_changed_files_context_menu(self, event):
try:
idx = self.changed_files_listbox.nearest(event.y)
self.changed_files_listbox.selection_clear(0, tk.END)
self.changed_files_listbox.selection_set(idx)
self.changed_files_listbox.activate(idx)
except tk.TclError:
return
sel = self.changed_files_listbox.curselection()
if not sel:
return
line = self.changed_files_listbox.get(sel[0])
self.changed_files_context_menu.delete(0, tk.END)
cleaned = line.strip()
is_untracked = cleaned.startswith("??")
can_add = (
is_untracked
and hasattr(self, "add_selected_file_callback")
and callable(self.add_selected_file_callback)
)
add_state = tk.NORMAL if can_add else tk.DISABLED
self.changed_files_context_menu.add_command(
label="Add to Staging Area",
state=add_state,
command=lambda l=line: (
self.add_selected_file_callback(l) if can_add else None
),
)
can_diff = hasattr(self, "open_diff_viewer_callback") and callable(
self.open_diff_viewer_callback
)
diff_state = (
tk.DISABLED
if cleaned.startswith("??") or cleaned.startswith(" D") or not can_diff
else tk.NORMAL
)
self.changed_files_context_menu.add_command(
label="View Changes (Diff)",
state=diff_state,
command=lambda l=line: (
self.open_diff_viewer_callback(l) if diff_state == tk.NORMAL else None
),
)
try:
self.changed_files_context_menu.tk_popup(event.x_root, event.y_root)
finally:
self.changed_files_context_menu.grab_release()
def update_status_bar(self, message):
if hasattr(self, "status_bar_var"):
try:
self.master.after(0, self.status_bar_var.set, message)
except Exception as e:
print(f"ERROR updating status bar: {e}", file=sys.stderr)
def ask_new_profile_name(self):
return simpledialog.askstring("Add Profile", "Enter name:", parent=self.master)
def show_error(self, title, message):
messagebox.showerror(title, message, parent=self.master)
def show_info(self, title, message):
messagebox.showinfo(title, message, parent=self.master)
def show_warning(self, title, message):
messagebox.showwarning(title, message, parent=self.master)
def ask_yes_no(self, title, message):
return messagebox.askyesno(title, message, parent=self.master)
def create_tooltip(self, widget, text):
if widget and isinstance(widget, tk.Widget) and widget.winfo_exists():
Tooltip(widget, text)
def update_tooltip(self, widget, text):
self.create_tooltip(widget, text)
def set_action_widgets_state(self, state):
if state not in [tk.NORMAL, tk.DISABLED]:
return # Invalid state
widgets = [
self.save_settings_button,
self.remove_profile_button,
self.prepare_svn_button,
self.create_bundle_button,
self.fetch_bundle_button,
self.edit_gitignore_button,
self.manual_backup_button,
self.commit_button,
self.refresh_changes_button,
self.refresh_tags_button,
self.create_tag_button,
self.checkout_tag_button,
self.refresh_branches_button,
self.create_branch_button,
self.checkout_branch_button,
self.refresh_history_button,
self.history_branch_filter_combo,
]
# log_handler.log_debug(f"Setting action widgets state to: {state}", func_name="set_action_widgets_state") # Usa log_handler
for widget in widgets:
# Check attribute exists before get/check widget
widget_attr_name = None
for attr, value in self.__dict__.items():
if value is widget:
widget_attr_name = attr
break
if (
widget_attr_name
and hasattr(self, widget_attr_name)
and widget
and widget.winfo_exists()
):
try:
w_state = (
"readonly"
if isinstance(widget, ttk.Combobox) and state == tk.NORMAL
else state
)
widget.config(state=w_state)
except Exception as e:
pass # log_handler.log_error(f"Error setting state for {widget_attr_name}: {e}", func_name="set_action_widgets_state")
if hasattr(self, "profile_dropdown") and self.profile_dropdown.winfo_exists():
try:
dd_state = "readonly" if state == tk.NORMAL else tk.DISABLED
self.profile_dropdown.config(state=dd_state)
except Exception as e:
pass # log_handler.log_error(f"Error setting state for profile dropdown: {e}", func_name="set_action_widgets_state")
# --- END OF FILE gui.py ---