Files
ClaudeProjectManager-main/gui/progress_bar.py
Claude Project Manager ec92da8a64 Initial commit
2025-07-07 22:11:38 +02:00

272 Zeilen
9.0 KiB
Python

"""
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(), [])