add statusbar

This commit is contained in:
VALLONGOL 2025-04-18 13:51:43 +02:00
parent 96a916ca09
commit 2416ab0b2a
3 changed files with 127 additions and 6 deletions

View File

@ -173,6 +173,7 @@ class GitSvnSyncApp:
f"Error setting up logger with GUI TextHandler: {log_e}", exc_info=True f"Error setting up logger with GUI TextHandler: {log_e}", exc_info=True
) )
self.main_frame.update_status_bar("Ready.")
self.logger.info("Git SVN Sync Tool initialization sequence complete.") self.logger.info("Git SVN Sync Tool initialization sequence complete.")
def _perform_initial_load(self): def _perform_initial_load(self):
@ -187,6 +188,9 @@ class GitSvnSyncApp:
self._clear_and_disable_fields() self._clear_and_disable_fields()
#self.logger.info("Application started and initial state set.") #self.logger.info("Application started and initial state set.")
if self.main_frame.status_bar_var.get() == "": # Imposta solo se vuota
self.main_frame.update_status_bar("Ready.")
def _handle_gitignore_save(self): def _handle_gitignore_save(self):
""" """
Callback function triggered after .gitignore is saved successfully. Callback function triggered after .gitignore is saved successfully.
@ -704,6 +708,7 @@ class GitSvnSyncApp:
def prepare_svn_for_git(self): def prepare_svn_for_git(self):
"""Handles the 'Prepare SVN Repo' action (Synchronous).""" """Handles the 'Prepare SVN Repo' action (Synchronous)."""
self.logger.info("--- Action Triggered: Prepare Repo ---") self.logger.info("--- Action Triggered: Prepare Repo ---")
self.main_frame.update_status_bar("Processing: Preparing repository...")
# Validazione input # Validazione input
svn_path = self._get_and_validate_svn_path("Prepare Repository") svn_path = self._get_and_validate_svn_path("Prepare Repository")
if not svn_path: if not svn_path:
@ -713,6 +718,7 @@ class GitSvnSyncApp:
# Chiamata diretta a ActionHandler # Chiamata diretta a ActionHandler
self.action_handler.execute_prepare_repo(svn_path) self.action_handler.execute_prepare_repo(svn_path)
self.main_frame.show_info("Success", "Repository prepared successfully.") self.main_frame.show_info("Success", "Repository prepared successfully.")
self.main_frame.update_status_bar("Repository prepared.")
except (ValueError, GitCommandError, IOError) as e: except (ValueError, GitCommandError, IOError) as e:
self.logger.error(f"Prepare Repo failed: {e}", exc_info=True) self.logger.error(f"Prepare Repo failed: {e}", exc_info=True)
# Mostra errore specifico (ValueError è spesso per 'già preparato') # Mostra errore specifico (ValueError è spesso per 'già preparato')
@ -734,6 +740,9 @@ class GitSvnSyncApp:
def create_git_bundle(self): def create_git_bundle(self):
"""Handles the 'Create Bundle' action (Synchronous).""" """Handles the 'Create Bundle' action (Synchronous)."""
self.logger.info("--- Action Triggered: Create Bundle ---") self.logger.info("--- Action Triggered: Create Bundle ---")
self.main_frame.update_status_bar("Processing: Creating bundle...")
status_final = "Ready."
# Validazione Input e Preparazione Argomenti # Validazione Input e Preparazione Argomenti
profile = self.main_frame.profile_var.get() profile = self.main_frame.profile_var.get()
if not profile: if not profile:
@ -786,23 +795,37 @@ class GitSvnSyncApp:
self.main_frame.show_info( self.main_frame.show_info(
"Bundle Created", f"Bundle created:\n{bundle_path_result}" "Bundle Created", f"Bundle created:\n{bundle_path_result}"
) )
status_final = f"Bundle created: {os.path.basename(bundle_path_result)}"
else: else:
self.main_frame.show_info( self.main_frame.show_info(
"Bundle Info", "Bundle Info",
"Bundle creation finished, but no file generated (repo empty?).", "Bundle creation finished, but no file generated (repo empty?).",
) )
status_final = "Bundle created (empty or no changes)."
except (IOError, GitCommandError, ValueError) as e: except (IOError, GitCommandError, ValueError) as e:
self.logger.error(f"Create Bundle failed: {e}", exc_info=True) self.logger.error(f"Create Bundle failed: {e}", exc_info=True)
status_final = "Error: Unexpected failure creating bundle."
self.main_frame.show_error("Create Bundle Error", f"Operation failed:\n{e}") self.main_frame.show_error("Create Bundle Error", f"Operation failed:\n{e}")
except Exception as e: except Exception as e:
self.logger.exception(f"Unexpected error during Create Bundle: {e}") self.logger.exception(f"Unexpected error during Create Bundle: {e}")
status_final = f"Error creating bundle: {type(e).__name__}"
self.main_frame.show_error( self.main_frame.show_error(
"Unexpected Error", f"An unexpected error occurred:\n{e}" "Unexpected Error", f"An unexpected error occurred:\n{e}"
) )
finally:
# --- MODIFICA: Status Bar (Fine Operazione) ---
self.main_frame.update_status_bar(status_final)
# --- FINE MODIFICA ---
# Aggiorna stato UI (invariato)
self.update_svn_status_indicator(svn_path)
def fetch_from_git_bundle(self): def fetch_from_git_bundle(self):
"""Handles the 'Fetch from Bundle' action (Synchronous).""" """Handles the 'Fetch from Bundle' action (Synchronous)."""
self.logger.info("--- Action Triggered: Fetch from Bundle ---") self.logger.info("--- Action Triggered: Fetch from Bundle ---")
self.main_frame.update_status_bar("Processing: Fetching from bundle...")
status_final = "Ready."
# Validazione Input e Preparazione Argomenti # Validazione Input e Preparazione Argomenti
profile = self.main_frame.profile_var.get() profile = self.main_frame.profile_var.get()
if not profile: if not profile:
@ -854,8 +877,10 @@ class GitSvnSyncApp:
"Fetch Complete", "Fetch Complete",
"Fetch and merge completed.\nCheck log for details.", "Fetch and merge completed.\nCheck log for details.",
) )
status_final = "Fetch/Clone from bundle complete."
# else non dovrebbe accadere se ActionHandler solleva eccezioni # else non dovrebbe accadere se ActionHandler solleva eccezioni
except FileNotFoundError as e: # Cattura se il file scompare tra check e uso except FileNotFoundError as e: # Cattura se il file scompare tra check e uso
status_final = f"Error: Bundle file not found."
self.logger.error(f"Fetch failed: {e}", exc_info=False) # Log meno verboso self.logger.error(f"Fetch failed: {e}", exc_info=False) # Log meno verboso
self.main_frame.show_error("File Not Found", f"{e}") self.main_frame.show_error("File Not Found", f"{e}")
except GitCommandError as e: # Gestione specifica conflitti except GitCommandError as e: # Gestione specifica conflitti
@ -877,10 +902,14 @@ class GitSvnSyncApp:
self.main_frame.show_error( self.main_frame.show_error(
"Unexpected Error", f"An unexpected error occurred:\n{e}" "Unexpected Error", f"An unexpected error occurred:\n{e}"
) )
finally:
self.main_frame.update_status_bar(status_final)
def manual_backup(self): def manual_backup(self):
"""Handles the 'Backup Now' button click (Synchronous).""" """Handles the 'Backup Now' button click (Synchronous)."""
self.logger.info("--- Action Triggered: Manual Backup ---") self.logger.info("--- Action Triggered: Manual Backup ---")
self.main_frame.update_status_bar("Processing: Manual Backup...")
# Validazione Input # Validazione Input
profile = self.main_frame.profile_var.get() profile = self.main_frame.profile_var.get()
if not profile: if not profile:
@ -920,11 +949,13 @@ class GitSvnSyncApp:
self.main_frame.show_info( self.main_frame.show_info(
"Backup Complete", f"Manual backup created:\n{backup_path_result}" "Backup Complete", f"Manual backup created:\n{backup_path_result}"
) )
self.main_frame.update_status_bar(f"Manual backup created:\n{backup_path_result}")
else: else:
self.main_frame.show_warning( self.main_frame.show_warning(
"Backup Info", "Backup Info",
"Backup finished, but no file generated (source empty?).", "Backup finished, but no file generated (source empty?).",
) )
self.main_frame.update_status_bar("Backup finished, but no file generated (source empty?).")
except (IOError, ValueError, PermissionError) as e: # Cattura errori specifici except (IOError, ValueError, PermissionError) as e: # Cattura errori specifici
self.logger.error(f"Manual backup failed: {e}", exc_info=True) self.logger.error(f"Manual backup failed: {e}", exc_info=True)
self.main_frame.show_error("Backup Error", f"Manual backup failed:\n{e}") self.main_frame.show_error("Backup Error", f"Manual backup failed:\n{e}")

60
ToDo.md Normal file
View File

@ -0,0 +1,60 @@
Ottima domanda! È sempre utile fare un passo indietro e pensare a possibili miglioramenti o funzionalità aggiuntive. Discutiamone:
**Potenziali Miglioramenti e Funzionalità Mancanti:**
1. **Gestione Conflitti (GUI):**
* **Situazione Attuale:** Quando "Fetch from Bundle" causa un conflitto, lo script lo rileva, logga l'errore e mostra un messaggio all'utente dicendo di risolvere manualmente.
* **Possibile Miglioramento:** Potrebbe essere *molto* utile avere un'indicazione visiva più chiara nella GUI quando si verifica un conflitto (magari un'icona di stato diversa, un messaggio persistente in una status bar). Ancora più avanzato (e complesso) sarebbe integrare un semplice strumento di visualizzazione/risoluzione dei conflitti direttamente nell'applicazione, anche se questo aumenterebbe significativamente la complessità. Un primo passo potrebbe essere semplicemente elencare i file in conflitto nell'area log o in un popup dedicato.
2. **Bundle Incrementali:**
* **Situazione Attuale:** `git bundle create --all` crea un bundle completo ogni volta. Questo è sicuro ma può essere inefficiente se le modifiche tra le sincronizzazioni sono piccole e il repository è grande.
* **Possibile Miglioramento:** Esplorare l'uso dei bundle incrementali. `git bundle create mio_update.bundle <ultimo_tag_o_commit_noto_all_altra_parte>..HEAD`. Questo richiede di tenere traccia dell'ultimo stato sincronizzato (magari salvando l'hash dell'ultimo commit importato/esportato nel profilo?). Questo renderebbe i trasferimenti molto più veloci per grandi repository.
* **Complessità:** Richiede una gestione dello stato più sofisticata.
3. **Gestione Errori di `git rm --cached` nell'Untrack Automatico:**
* **Situazione Attuale:** Se un batch di `git rm --cached` fallisce, l'intero processo si interrompe.
* **Possibile Miglioramento:** Si potrebbe decidere di rendere l'operazione più resiliente: se un batch fallisce, loggare l'errore, *continuare* con i batch successivi e alla fine riportare un successo parziale o un avviso, magari elencando i file che non è riuscito a untraccare. Il commit automatico verrebbe comunque creato per i file untracciati con successo. Questo eviterebbe che un singolo file problematico blocchi l'untracking di tutti gli altri.
4. **Visualizzazione Modifiche (Diff):**
* **Situazione Attuale:** L'utente non ha modo di vedere quali file sono stati modificati direttamente dall'interno del tool prima di fare un commit (manuale o pre-tag).
* **Possibile Miglioramento:** Aggiungere una sezione (magari una nuova scheda o un pannello nella scheda "Commit") che esegua `git status --short` o `git diff --name-status` e mostri l'elenco dei file modificati, aggiunti, cancellati. Ancora più avanzato sarebbe mostrare il diff effettivo per il file selezionato. Questo aiuterebbe l'utente a scrivere messaggi di commit più accurati.
5. **Gestione Branch Remoti (se Applicabile):**
* **Situazione Attuale:** Il tool si concentra sui branch locali e sui bundle. Non gestisce direttamente interazioni con repository remoti (tipo GitHub/GitLab).
* **Considerazione:** Sebbene lo scopo principale sia la sincronizzazione offline, potrebbe esserci uno scenario in cui la macchina "origine" interagisce anche con un remote. Aggiungere funzionalità `push`/`pull`/`fetch` standard potrebbe essere utile per alcuni, ma forse snaturerebbe l'obiettivo primario del tool focalizzato sui bundle.
6. **Opzioni di Pulizia:**
* **Situazione Attuale:** Non ci sono comandi di pulizia integrati.
* **Possibile Miglioramento:** Aggiungere pulsanti per eseguire comandi Git utili come `git clean -fdx` (per rimuovere file non tracciati e ignorati - **pericoloso, da usare con cautela!**) o `git gc --prune=now --aggressive` (per ottimizzare il repository). Questi dovrebbero avere conferme molto chiare sui rischi.
7. **Interfaccia Utente (Piccoli Ritocchi):**
* **Progresso Operazioni Lunghe:** Per operazioni come la creazione di bundle grandi o il backup, una progress bar (anche indeterminata) darebbe un feedback migliore rispetto al solo log testuale. (Richiederebbe però di reintrodurre un po' di threading/async per non bloccare la GUI).
* **Stato Pulsanti:** Rivedere attentamente quando i pulsanti dovrebbero essere abilitati/disabilitati per guidare l'utente nel flusso corretto (ad esempio, il pulsante "Commit" potrebbe essere disabilitato se `git status` non riporta modifiche).
* **Status Bar:** Una piccola area in basso (sotto l'area log) per messaggi di stato rapidi ("Ready", "Operation in progress...", "Conflict detected", "Last backup: ...") potrebbe essere utile.
8. **Configurazione Avanzata Bundle:**
* **Situazione Attuale:** Usa sempre `--all` per la creazione.
* **Possibile Miglioramento:** Permettere all'utente di specificare riferimenti specifici (branch/tag) da includere nel bundle, invece di usare sempre `--all`, tramite un'interfaccia o opzioni nel profilo.
9. **Internazionalizzazione (i18n):**
* **Situazione Attuale:** L'interfaccia è solo in inglese (testi nei widget). I messaggi di log e i commenti sono in inglese, ma le interazioni con te sono in italiano.
* **Possibile Miglioramento:** Se dovesse essere usato da altri, separare le stringhe dell'interfaccia in file di traduzione per supportare più lingue.
**Discussione:**
* **Priorità Alta (Secondo Me):**
* Migliore gestione/visualizzazione dei conflitti (almeno elencare i file).
* Raffinamento dello stato dei pulsanti GUI.
* Resilienza opzionale per `git rm --cached` in batch.
* **Priorità Media:**
* Visualizzazione delle modifiche (`git status`/`diff`).
* Bundle incrementali (grande vantaggio per repo grandi, ma più complesso).
* Status bar.
* **Priorità Bassa/Opzionale:**
* Opzioni di pulizia (`git clean`, `gc`).
* Gestione remoti standard.
* Configurazione avanzata bundle.
* Internazionalizzazione.
* Progress bar (richiede più lavoro strutturale).
Cosa ne pensi? Quali di questi punti ti sembrano più interessanti o utili per i tuoi casi d'uso?

42
gui.py
View File

@ -463,7 +463,22 @@ class MainFrame(ttk.Frame):
self.notebook.add(self.history_tab_frame, text=" History ") self.notebook.add(self.history_tab_frame, text=" History ")
# Log area (always visible below tabs) # Log area (always visible below tabs)
self._create_log_area() # Modifichiamo leggermente il pack del log frame per fare spazio sotto
log_frame_container = ttk.Frame(self)
log_frame_container.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0))
self._create_log_area(log_frame_container) # Passa il container
self.status_bar_var = tk.StringVar()
self.status_bar_var.set("Ready.") # Messaggio iniziale
self.status_bar = ttk.Label(
self, # Direttamente nel MainFrame
textvariable=self.status_bar_var,
relief=tk.SUNKEN,
anchor=tk.W, # Allinea testo a sinistra
padding=(5, 2) # Un po' di padding interno
)
# Pack sotto tutto il resto, occupa la larghezza
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, pady=(2, 0), padx=0)
# --- Initial State Configuration --- # --- Initial State Configuration ---
# Set initial profile selection using the improved logic from previous discussion # Set initial profile selection using the improved logic from previous discussion
@ -1008,15 +1023,15 @@ class MainFrame(ttk.Frame):
self.history_text.config(xscrollcommand=history_xscroll.set) self.history_text.config(xscrollcommand=history_xscroll.set)
return frame return frame
def _create_log_area(self): def _create_log_area(self, parent_frame):
"""Creates the application log area at the bottom.""" """Creates the application log area within the specified parent."""
# (No changes needed here for this modification) log_frame = ttk.LabelFrame(parent_frame, text="Application Log", padding=(10, 5))
log_frame = ttk.LabelFrame(self, text="Application Log", padding=(10, 5)) log_frame.pack(fill=tk.BOTH, expand=True) # Si espande nel suo container
log_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=0, pady=(5, 0))
log_frame.rowconfigure(0, weight=1) log_frame.rowconfigure(0, weight=1)
log_frame.columnconfigure(0, weight=1) log_frame.columnconfigure(0, weight=1)
self.log_text = scrolledtext.ScrolledText( self.log_text = scrolledtext.ScrolledText(
log_frame, log_frame,
# ... (configurazione log_text invariata) ...
height=8, height=8,
width=100, width=100,
font=("Consolas", 9), font=("Consolas", 9),
@ -1026,6 +1041,7 @@ class MainFrame(ttk.Frame):
pady=5, pady=5,
) )
self.log_text.grid(row=0, column=0, sticky="nsew") self.log_text.grid(row=0, column=0, sticky="nsew")
# (configurazione tag invariata)
self.log_text.tag_config("INFO", foreground="black") self.log_text.tag_config("INFO", foreground="black")
self.log_text.tag_config("DEBUG", foreground="grey") self.log_text.tag_config("DEBUG", foreground="grey")
self.log_text.tag_config("WARNING", foreground="orange") self.log_text.tag_config("WARNING", foreground="orange")
@ -1163,6 +1179,20 @@ class MainFrame(ttk.Frame):
logging.error(f"Error tags: {e}", exc_info=True) logging.error(f"Error tags: {e}", exc_info=True)
self.tag_listbox.insert(tk.END, "(Error)") self.tag_listbox.insert(tk.END, "(Error)")
def update_status_bar(self, message):
"""Updates the text displayed in the status bar."""
if hasattr(self, "status_bar_var"):
try:
# Usiamo after(0,..) per sicurezza, anche se probabilmente non strettamente necessario
# nella maggior parte dei casi di questa app. Previene potenziali problemi se
# una chiamata arrivasse da un thread diverso (improbabile qui ma buona pratica).
self.master.after(0, self.status_bar_var.set, message)
except Exception as e:
# Logga se l'aggiornamento della status bar fallisce
# Evita di usare self.logger qui per non creare dipendenze circolari potenziali
# durante l'inizializzazione o la chiusura. Usa print o un logger di base.
print(f"ERROR: Failed to update status bar: {e}")
def get_selected_tag(self): def get_selected_tag(self):
if hasattr(self, "tag_listbox"): if hasattr(self, "tag_listbox"):
indices = self.tag_listbox.curselection() indices = self.tag_listbox.curselection()