Initial commit
Dieser Commit ist enthalten in:
272
gui/progress_bar.py
Normale Datei
272
gui/progress_bar.py
Normale Datei
@ -0,0 +1,272 @@
|
||||
"""
|
||||
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(), [])
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren