Files
Geniusia_v2/geniusia2/gui/interactive_dialog.py
2026-03-05 00:20:25 +01:00

345 lines
11 KiB
Python

"""
InteractiveDialog - Dialogue interactif avec timeout automatique
Permet de demander confirmation à l'utilisateur de manière non-bloquante
"""
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont
from typing import Callable, Optional
import logging
class InteractiveDialog(QDialog):
"""
Dialogue interactif avec timeout automatique.
Caractéristiques:
- Non-bloquant (l'application continue)
- Timeout de 10 secondes par défaut
- Callbacks pour accept/reject
- Fermeture automatique après timeout
- Compte à rebours visible
Signals:
accepted: Émis quand l'utilisateur accepte
rejected: Émis quand l'utilisateur refuse
timeout: Émis quand le timeout est atteint
"""
accepted = pyqtSignal()
rejected = pyqtSignal()
timeout = pyqtSignal()
def __init__(
self,
title: str,
message: str,
on_accept: Optional[Callable] = None,
on_reject: Optional[Callable] = None,
timeout_seconds: int = 10,
parent=None
):
"""
Initialise le dialogue interactif.
Args:
title: Titre du dialogue
message: Message à afficher
on_accept: Callback appelé si l'utilisateur accepte
on_reject: Callback appelé si l'utilisateur refuse
timeout_seconds: Durée du timeout en secondes (défaut: 10)
parent: Widget parent (optionnel)
"""
super().__init__(parent)
self.logger = logging.getLogger(__name__)
self.on_accept_callback = on_accept
self.on_reject_callback = on_reject
self.timeout_seconds = timeout_seconds
self.remaining_seconds = timeout_seconds
self.result_value = None
# Configuration du dialogue
self.setWindowTitle(title)
self.setModal(False) # Non-bloquant
self.setWindowFlags(
Qt.Dialog |
Qt.WindowStaysOnTopHint |
Qt.WindowCloseButtonHint
)
# Initialiser l'interface
self._init_ui(title, message)
# Démarrer le timer de timeout
self._start_timeout_timer()
self.logger.info(f"InteractiveDialog créé: {title}")
def _init_ui(self, title: str, message: str):
"""Initialise l'interface utilisateur."""
# Layout principal
layout = QVBoxLayout()
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(15)
self.setLayout(layout)
# Icône et titre
title_layout = QHBoxLayout()
title_icon = QLabel("💡")
title_icon.setFont(QFont("Arial", 24))
title_layout.addWidget(title_icon)
title_label = QLabel(title)
title_label.setFont(QFont("Arial", 14, QFont.Bold))
title_label.setWordWrap(True)
title_layout.addWidget(title_label, 1)
layout.addLayout(title_layout)
# Message
message_label = QLabel(message)
message_label.setFont(QFont("Arial", 11))
message_label.setWordWrap(True)
message_label.setStyleSheet("color: #333; padding: 10px 0;")
layout.addWidget(message_label)
# Compte à rebours
self.countdown_label = QLabel(f"⏱️ Fermeture auto dans {self.remaining_seconds}s")
self.countdown_label.setFont(QFont("Arial", 9))
self.countdown_label.setStyleSheet("color: #999;")
self.countdown_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.countdown_label)
# Boutons
button_layout = QHBoxLayout()
button_layout.setSpacing(10)
# Bouton Refuser
self.reject_button = QPushButton("Non, continue")
self.reject_button.setFont(QFont("Arial", 10))
self.reject_button.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #da190b;
}
""")
self.reject_button.clicked.connect(self._on_reject)
button_layout.addWidget(self.reject_button)
# Bouton Accepter
self.accept_button = QPushButton("Oui, essaie !")
self.accept_button.setFont(QFont("Arial", 10))
self.accept_button.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #45a049;
}
""")
self.accept_button.clicked.connect(self._on_accept)
button_layout.addWidget(self.accept_button)
layout.addLayout(button_layout)
# Ajuster la taille
self.setMinimumWidth(400)
self.adjustSize()
def _start_timeout_timer(self):
"""Démarre le timer de timeout."""
self.timeout_timer = QTimer(self)
self.timeout_timer.timeout.connect(self._on_timeout_tick)
self.timeout_timer.start(1000) # 1 seconde
def _on_timeout_tick(self):
"""Gestionnaire du tick du timer."""
self.remaining_seconds -= 1
if self.remaining_seconds > 0:
# Mettre à jour le compte à rebours
self.countdown_label.setText(
f"⏱️ Fermeture auto dans {self.remaining_seconds}s"
)
else:
# Timeout atteint
self._on_timeout()
def _on_accept(self):
"""Gestionnaire du bouton Accepter."""
self.timeout_timer.stop()
self.result_value = "accept"
# Émettre le signal
self.accepted.emit()
# Appeler le callback
if self.on_accept_callback:
try:
self.on_accept_callback()
except Exception as e:
self.logger.error(f"Erreur dans on_accept callback: {e}")
self.logger.info("Dialogue accepté")
self.accept()
def _on_reject(self):
"""Gestionnaire du bouton Refuser."""
self.timeout_timer.stop()
self.result_value = "reject"
# Émettre le signal
self.rejected.emit()
# Appeler le callback
if self.on_reject_callback:
try:
self.on_reject_callback()
except Exception as e:
self.logger.error(f"Erreur dans on_reject callback: {e}")
self.logger.info("Dialogue refusé")
self.reject()
def _on_timeout(self):
"""Gestionnaire du timeout."""
self.timeout_timer.stop()
self.result_value = "timeout"
# Émettre le signal
self.timeout.emit()
# Par défaut, timeout = reject
if self.on_reject_callback:
try:
self.on_reject_callback()
except Exception as e:
self.logger.error(f"Erreur dans on_reject callback (timeout): {e}")
self.logger.info("Dialogue timeout")
self.reject()
def closeEvent(self, event):
"""Gestionnaire de fermeture du dialogue."""
# Arrêter le timer
if hasattr(self, 'timeout_timer'):
self.timeout_timer.stop()
# Si pas de résultat, considérer comme reject
if self.result_value is None:
self.result_value = "reject"
if self.on_reject_callback:
try:
self.on_reject_callback()
except Exception as e:
self.logger.error(f"Erreur dans on_reject callback (close): {e}")
event.accept()
@staticmethod
def show_dialog(
title: str,
message: str,
on_accept: Optional[Callable] = None,
on_reject: Optional[Callable] = None,
timeout_seconds: int = 10,
parent=None
) -> 'InteractiveDialog':
"""
Méthode statique pour afficher un dialogue.
Args:
title: Titre du dialogue
message: Message à afficher
on_accept: Callback appelé si l'utilisateur accepte
on_reject: Callback appelé si l'utilisateur refuse
timeout_seconds: Durée du timeout en secondes
parent: Widget parent
Returns:
Instance du dialogue créé
Examples:
>>> def on_yes():
... print("Utilisateur a accepté")
>>> def on_no():
... print("Utilisateur a refusé")
>>> dialog = InteractiveDialog.show_dialog(
... "Confirmation",
... "Voulez-vous continuer ?",
... on_yes,
... on_no
... )
"""
dialog = InteractiveDialog(
title, message, on_accept, on_reject, timeout_seconds, parent
)
dialog.show()
return dialog
if __name__ == "__main__":
"""Test de l'InteractiveDialog"""
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
print("=" * 60)
print("Test de l'InteractiveDialog")
print("=" * 60)
# Callbacks de test
def on_accept():
print("\n✅ Utilisateur a ACCEPTÉ")
def on_reject():
print("\n❌ Utilisateur a REFUSÉ")
# Test 1: Dialogue avec timeout de 10 secondes
print("\nTest 1: Dialogue avec timeout de 10 secondes")
print("Attendez 10 secondes ou cliquez sur un bouton")
dialog = InteractiveDialog.show_dialog(
"J'ai une idée !",
"J'ai remarqué que vous faites souvent:\n"
"\"Calculer 9/9 dans la calculatrice\"\n\n"
"Est-ce que je peux essayer de vous suggérer\n"
"cette action la prochaine fois ?",
on_accept,
on_reject,
timeout_seconds=10
)
# Connecter les signaux pour les tests
dialog.accepted.connect(lambda: print("Signal 'accepted' émis"))
dialog.rejected.connect(lambda: print("Signal 'rejected' émis"))
dialog.timeout.connect(lambda: print("Signal 'timeout' émis"))
# Test 2: Créer un deuxième dialogue après 3 secondes
def create_second_dialog():
print("\nTest 2: Deuxième dialogue avec timeout de 5 secondes")
dialog2 = InteractiveDialog.show_dialog(
"Changement de mode",
"Voulez-vous passer en mode Suggestions ?",
lambda: print("\n✅ Mode Suggestions activé"),
lambda: print("\n❌ Reste en mode Shadow"),
timeout_seconds=5
)
QTimer.singleShot(3000, create_second_dialog)
sys.exit(app.exec_())