""" Progress Bar Component for Git Operations Provides visual feedback for long-running operations """ import customtkinter as ctk from typing import Optional, Callable import threading import time class ProgressBar(ctk.CTkToplevel): """Modern progress bar window for Git operations""" def __init__(self, parent, title: str = "Operation in Progress", width: int = 500): super().__init__(parent) # Window configuration self.title(title) self.geometry(f"{width}x200") self.resizable(False, False) # Center on parent window self.transient(parent) self.grab_set() # Colors from styles from gui.styles import get_colors self.colors = get_colors() # Configure window self.configure(fg_color=self.colors['bg_primary']) # Create UI elements self._setup_ui() # Variables self.is_cancelled = False self._update_callback: Optional[Callable] = None # Center window on parent self._center_on_parent(parent) def _setup_ui(self): """Create the progress bar UI""" # Main container self.container = ctk.CTkFrame( self, fg_color=self.colors['bg_secondary'], corner_radius=10 ) self.container.pack(fill="both", expand=True, padx=20, pady=20) # Title label self.title_label = ctk.CTkLabel( self.container, text="", font=("Segoe UI", 16, "bold"), text_color=self.colors['text_primary'] ) self.title_label.pack(pady=(10, 5)) # Status label self.status_label = ctk.CTkLabel( self.container, text="Initialisierung...", font=("Segoe UI", 14), text_color=self.colors['text_secondary'] ) self.status_label.pack(pady=(0, 15)) # Progress bar self.progress = ctk.CTkProgressBar( self.container, width=440, height=20, corner_radius=10, fg_color=self.colors['bg_tile'], progress_color=self.colors['accent_success'], mode="determinate" ) self.progress.pack(pady=10) self.progress.set(0) # Percentage label self.percentage_label = ctk.CTkLabel( self.container, text="0%", font=("Segoe UI", 12), text_color=self.colors['text_secondary'] ) self.percentage_label.pack(pady=(0, 10)) # Cancel button self.cancel_button = ctk.CTkButton( self.container, text="Abbrechen", width=100, height=32, corner_radius=6, fg_color=self.colors['accent_error'], hover_color="#FF5252", text_color="#FFFFFF", font=("Segoe UI", 13, "bold"), command=self._cancel_operation ) self.cancel_button.pack(pady=5) def _center_on_parent(self, parent): """Center the window on parent""" self.update_idletasks() # Get parent position and size parent_x = parent.winfo_x() parent_y = parent.winfo_y() parent_width = parent.winfo_width() parent_height = parent.winfo_height() # Get this window size width = self.winfo_width() height = self.winfo_height() # Calculate position x = parent_x + (parent_width - width) // 2 y = parent_y + (parent_height - height) // 2 # Set position self.geometry(f"+{x}+{y}") def _cancel_operation(self): """Handle cancel button click""" self.is_cancelled = True self.cancel_button.configure(state="disabled", text="Wird abgebrochen...") def update_progress(self, value: float, status: str, title: str = "", is_error: bool = False): """Update progress bar Args: value: Progress value (0.0 to 1.0) status: Status message to display title: Optional title update is_error: If True, colors the progress bar red """ # Ensure value is in range value = max(0.0, min(1.0, value)) # Update UI in main thread self.after(0, self._update_ui, value, status, title, is_error) def _update_ui(self, value: float, status: str, title: str, is_error: bool = False): """Update UI elements (must be called from main thread)""" if title: self.title_label.configure(text=title) self.status_label.configure(text=status) self.progress.set(value) self.percentage_label.configure(text=f"{int(value * 100)}%") # Change color if error if is_error: self.progress.configure(progress_color=self.colors['accent_error']) self.status_label.configure(text_color=self.colors['accent_error']) # If complete, update button if value >= 1.0 and not is_error: self.cancel_button.configure( text="Schließen", state="normal", command=self.destroy ) # Auto-close after 0.5 seconds if successful if not self.is_cancelled: self.after(500, self.destroy) def set_update_callback(self, callback: Callable): """Set callback for progress updates""" self._update_callback = callback def simulate_progress(self, steps: list): """Simulate progress for testing Args: steps: List of (duration, status, progress) tuples """ def run_simulation(): for duration, status, progress in steps: if self.is_cancelled: break self.update_progress(progress, status) time.sleep(duration) if self._update_callback: self._update_callback(progress, status) thread = threading.Thread(target=run_simulation, daemon=True) thread.start() def set_error(self, error_message: str): """Set error state and message""" self.update_progress(1.0, error_message, is_error=True) self.cancel_button.configure( text="OK", state="normal", command=self.destroy, fg_color=self.colors['accent_error'] ) class GitOperationProgress: """Helper class to manage progress for Git operations""" # Progress stages for different operations CLONE_STAGES = [ (0.0, "Verbindung zum Server wird hergestellt..."), (0.15, "Repository-Informationen werden abgerufen..."), (0.30, "Objekte werden gezählt..."), (0.50, "Objekte werden heruntergeladen..."), (0.80, "Dateien werden entpackt..."), (0.95, "Arbeitsverzeichnis wird erstellt..."), (1.0, "Repository erfolgreich geklont!") ] PUSH_STAGES = [ (0.0, "Verbindung wird hergestellt..."), (0.10, "Authentifizierung läuft..."), (0.25, "Lokale Änderungen werden analysiert..."), (0.40, "Daten werden komprimiert..."), (0.60, "Objekte werden übertragen..."), (0.85, "Remote-Repository wird aktualisiert..."), (1.0, "Push erfolgreich abgeschlossen!") ] PULL_STAGES = [ (0.0, "Verbindung zum Server wird hergestellt..."), (0.15, "Neue Änderungen werden gesucht..."), (0.35, "Änderungen werden heruntergeladen..."), (0.60, "Dateien werden entpackt..."), (0.80, "Änderungen werden zusammengeführt..."), (0.95, "Arbeitsverzeichnis wird aktualisiert..."), (1.0, "Pull erfolgreich abgeschlossen!") ] FETCH_STAGES = [ (0.0, "Verbindung wird hergestellt..."), (0.20, "Remote-Referenzen werden abgerufen..."), (0.40, "Neue Objekte werden gesucht..."), (0.70, "Objekte werden heruntergeladen..."), (0.90, "Lokale Referenzen werden aktualisiert..."), (1.0, "Fetch erfolgreich abgeschlossen!") ] COMMIT_STAGES = [ (0.0, "Änderungen werden vorbereitet..."), (0.20, "Dateistatus wird geprüft..."), (0.40, "Änderungen werden indiziert..."), (0.60, "Commit-Objekt wird erstellt..."), (0.80, "Referenzen werden aktualisiert..."), (1.0, "Commit erfolgreich erstellt!") ] @staticmethod def get_stages(operation: str) -> list: """Get progress stages for an operation""" stages_map = { 'clone': GitOperationProgress.CLONE_STAGES, 'push': GitOperationProgress.PUSH_STAGES, 'pull': GitOperationProgress.PULL_STAGES, 'fetch': GitOperationProgress.FETCH_STAGES, 'commit': GitOperationProgress.COMMIT_STAGES } return stages_map.get(operation.lower(), [])