312 Zeilen
12 KiB
Python
312 Zeilen
12 KiB
Python
"""
|
|
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) |