""" Progress Modal - Basis-Klasse für Prozess-Modals """ import logging from typing import Optional, Dict, Any from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QGraphicsBlurEffect ) from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve from PyQt5.QtGui import QFont, QMovie, QPainter, QBrush, QColor from views.widgets.forge_animation_widget import ForgeAnimationWidget from styles.modal_styles import ModalStyles logger = logging.getLogger("progress_modal") class ProgressModal(QDialog): """ Basis-Klasse für Progress-Modals während Automatisierungsprozessen. Zeigt den Benutzer eine nicht-unterbrechbare Fortschrittsanzeige. """ # Signale force_closed = pyqtSignal() # Wird ausgelöst wenn Modal zwangsweise geschlossen wird def __init__(self, parent=None, modal_type: str = "generic", language_manager=None, style_manager=None): super().__init__(parent) self.modal_type = modal_type self.language_manager = language_manager self.style_manager = style_manager or ModalStyles() self.is_process_running = False self.auto_close_timer = None self.fade_animation = None # Modal-Texte laden self.modal_texts = self.style_manager.get_modal_texts() self.init_ui() self.setup_animations() if self.language_manager: self.language_manager.language_changed.connect(self.update_texts) self.update_texts() def init_ui(self): """Initialisiert die UI nach AccountForger Styleguide""" # Modal-Eigenschaften self.setModal(True) self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) # Transparenz entfernt - solider Hintergrund # self.setAttribute(Qt.WA_TranslucentBackground) self.setFixedSize( self.style_manager.SIZES['modal_width'], self.style_manager.SIZES['modal_height'] ) # Zentriere auf Parent oder Bildschirm if self.parent(): parent_rect = self.parent().geometry() x = parent_rect.x() + (parent_rect.width() - self.width()) // 2 y = parent_rect.y() + (parent_rect.height() - self.height()) // 2 self.move(x, y) # Hauptlayout layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Modal-Container mit solidem Hintergrund self.modal_container = QFrame() self.modal_container.setObjectName("modal_container") self.modal_container.setStyleSheet(self.style_manager.get_modal_container_style()) # Container-Layout container_layout = QVBoxLayout(self.modal_container) padding = self.style_manager.SIZES['padding_large'] container_layout.setContentsMargins(padding, padding, padding, padding) container_layout.setSpacing(self.style_manager.SIZES['spacing_default']) # Titel self.title_label = QLabel() self.title_label.setAlignment(Qt.AlignCenter) self.title_label.setObjectName("modal_title") self.title_label.setFont(self.style_manager.create_font('title')) self.title_label.setStyleSheet(self.style_manager.get_title_label_style()) container_layout.addWidget(self.title_label) # Animation Widget (Spinner/Forge Animation) self.animation_widget = ForgeAnimationWidget() animation_size = self.style_manager.SIZES['animation_size'] self.animation_widget.setFixedSize(animation_size, animation_size) self.animation_widget.setAlignment(Qt.AlignCenter) animation_layout = QHBoxLayout() animation_layout.addStretch() animation_layout.addWidget(self.animation_widget) animation_layout.addStretch() container_layout.addLayout(animation_layout) # Subtitle/Status self.status_label = QLabel() self.status_label.setAlignment(Qt.AlignCenter) self.status_label.setObjectName("modal_status") self.status_label.setFont(self.style_manager.create_font('status')) self.status_label.setStyleSheet(self.style_manager.get_status_label_style()) container_layout.addWidget(self.status_label) # Detail-Status (optional) self.detail_label = QLabel() self.detail_label.setAlignment(Qt.AlignCenter) self.detail_label.setObjectName("modal_detail") self.detail_label.setVisible(False) self.detail_label.setFont(self.style_manager.create_font('detail')) self.detail_label.setStyleSheet(self.style_manager.get_detail_label_style()) container_layout.addWidget(self.detail_label) layout.addWidget(self.modal_container) # Failsafe Timer self.failsafe_timer = QTimer() self.failsafe_timer.setSingleShot(True) self.failsafe_timer.timeout.connect(self._force_close) def setup_animations(self): """Richtet Fade-Animationen ein""" self.fade_animation = QPropertyAnimation(self, b"windowOpacity") self.fade_animation.setDuration(self.style_manager.ANIMATIONS['fade_duration']) self.fade_animation.setEasingCurve(QEasingCurve.OutCubic) def _get_modal_texts(self) -> Dict[str, Dict[str, str]]: """Gibt die Modal-Texte zurück""" # Diese Methode ist jetzt nur für Rückwärtskompatibilität # Die echten Texte kommen vom style_manager return self.style_manager.get_modal_texts() def show_process(self, process_type: Optional[str] = None): """ Zeigt das Modal für einen bestimmten Prozess-Typ an. Args: process_type: Optional - überschreibt den Modal-Typ """ if process_type: self.modal_type = process_type self.is_process_running = True # Texte aktualisieren self.update_texts() # Animation starten self.animation_widget.start_animation() # Failsafe Timer starten self.failsafe_timer.start(self.style_manager.ANIMATIONS['failsafe_timeout']) # Modal anzeigen ohne Fade-In (immer voll sichtbar) self.setWindowOpacity(1.0) self.show() # Fade-Animation deaktiviert für volle Sichtbarkeit # if self.fade_animation: # self.fade_animation.setStartValue(0.0) # self.fade_animation.setEndValue(1.0) # self.fade_animation.start() logger.info(f"Progress Modal angezeigt für: {self.modal_type}") def hide_process(self): """Versteckt das Modal mit Fade-Out Animation""" if not self.is_process_running: return self.is_process_running = False # Timer stoppen self.failsafe_timer.stop() # Animation stoppen self.animation_widget.stop_animation() # Fade-Out Animation deaktiviert - sofort verstecken self._finish_hide() # if self.fade_animation: # self.fade_animation.setStartValue(1.0) # self.fade_animation.setEndValue(0.0) # self.fade_animation.finished.connect(self._finish_hide) # self.fade_animation.start() # else: # self._finish_hide() logger.info(f"Progress Modal versteckt für: {self.modal_type}") def _finish_hide(self): """Beendet das Verstecken des Modals""" self.hide() if self.fade_animation: self.fade_animation.finished.disconnect() def update_status(self, status: str, detail: str = None): """ Aktualisiert den Status-Text des Modals. Args: status: Haupt-Status-Text detail: Optional - Detail-Text """ self.status_label.setText(status) if detail: self.detail_label.setText(detail) self.detail_label.setVisible(True) else: self.detail_label.setVisible(False) def show_error(self, error_message: str, auto_close_seconds: int = 3): """ Zeigt eine Fehlermeldung im Modal an. Args: error_message: Fehlermeldung auto_close_seconds: Sekunden bis automatisches Schließen """ self.title_label.setText("❌ Fehler aufgetreten") self.status_label.setText(error_message) self.detail_label.setVisible(False) # Animation stoppen self.animation_widget.stop_animation() # Auto-Close Timer if auto_close_seconds > 0: self.auto_close_timer = QTimer() self.auto_close_timer.setSingleShot(True) self.auto_close_timer.timeout.connect(self.hide_process) self.auto_close_timer.start(auto_close_seconds * 1000) def _force_close(self): """Zwangsschließung nach Timeout""" logger.warning(f"Progress Modal Timeout erreicht für: {self.modal_type}") self.force_closed.emit() self.hide_process() def update_texts(self): """Aktualisiert die Texte gemäß der aktuellen Sprache""" if not self.language_manager: # Fallback zu Standardtexten texts = self.modal_texts.get(self.modal_type, self.modal_texts['generic']) self.title_label.setText(texts['title']) self.status_label.setText(texts['status']) if texts['detail']: self.detail_label.setText(texts['detail']) self.detail_label.setVisible(True) return # Multilingual texts (für zukünftige Erweiterung) title_key = f"modal.{self.modal_type}.title" status_key = f"modal.{self.modal_type}.status" detail_key = f"modal.{self.modal_type}.detail" # Fallback zu Standardtexten wenn Übersetzung nicht vorhanden texts = self.modal_texts.get(self.modal_type, self.modal_texts['generic']) self.title_label.setText( self.language_manager.get_text(title_key, texts['title']) ) self.status_label.setText( self.language_manager.get_text(status_key, texts['status']) ) detail_text = self.language_manager.get_text(detail_key, texts['detail']) if detail_text: self.detail_label.setText(detail_text) self.detail_label.setVisible(True) else: self.detail_label.setVisible(False) def paintEvent(self, event): """Custom Paint Event für soliden Hintergrund""" painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Solider Hintergrund (komplett undurchsichtig) overlay_color = QColor(self.style_manager.COLORS['overlay']) brush = QBrush(overlay_color) painter.fillRect(self.rect(), brush) super().paintEvent(event) def keyPressEvent(self, event): """Verhindert das Schließen mit Escape während Prozess läuft""" if self.is_process_running and event.key() == Qt.Key_Escape: event.ignore() return super().keyPressEvent(event) def closeEvent(self, event): """Verhindert das Schließen während Prozess läuft""" if self.is_process_running: event.ignore() return super().closeEvent(event)