fix(splash): étapes de chargement dans le splash NATIF (pas le tkinter)
Ma précédente modif affichait les étapes dans un SECOND splash tkinter qui s'ouvrait après le splash natif PyInstaller. L'utilisateur voulait voir les étapes dans la PREMIÈRE fenêtre (splash natif avec logo). Refonte launch_gui() : - Suppression du splash tkinter intermédiaire (pas de fenêtre qui clignote) - Le splash natif PyInstaller reste visible pendant toute la phase d'import - Handler logging installé sur le root logger pour intercepter chaque log.info() du core. Traduction en libellé lisible + pyi_splash.update_text() - Import synchrone (pas besoin de thread puisque le splash natif tourne dans son propre processus bootloader) - À la fin : splash natif fermé + lancement de la GUI principale Résultat : l'utilisateur voit une seule fenêtre (splash natif avec logo) où défilent sous le message "Démarrage…" toutes les étapes de chargement des gazetteers, modèles et index. Quand tout est prêt, le splash disparaît et la GUI apparaît. Plus de fenêtre intermédiaire. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
111
launcher.py
111
launcher.py
@@ -105,52 +105,25 @@ def check_models_ready():
|
||||
|
||||
|
||||
def launch_gui():
|
||||
"""Launch the main GUI with a splash screen + progression des chargements.
|
||||
"""Launch the main GUI — étapes de chargement affichées DANS le splash natif.
|
||||
|
||||
Pendant les 15-30 s d'import du core (gazetteers INSEE/FINESS/BDPM,
|
||||
modèles ONNX), on intercepte les log.info() via un handler dédié pour
|
||||
les afficher DANS LE SPLASH au fur et à mesure. L'utilisateur voit
|
||||
défiler les étapes de chargement — effet pro.
|
||||
Le splash natif PyInstaller (image avec logo + texte dynamique) reste
|
||||
visible pendant TOUTE la phase de chargement. On intercepte les log.info()
|
||||
du core via un logging.Handler et on pousse chaque étape traduite dans
|
||||
le splash natif via pyi_splash.update_text(). L'utilisateur voit défiler
|
||||
sous le logo :
|
||||
"Chargement des prénoms français (INSEE)…"
|
||||
"Chargement des noms de famille (INSEE)…"
|
||||
"Chargement des numéros FINESS…"
|
||||
…
|
||||
Puis le splash se ferme et la GUI s'ouvre — pas de fenêtre intermédiaire.
|
||||
|
||||
Deux splashes collaborent :
|
||||
- Splash natif PyInstaller (image visible AVANT le démarrage Python) :
|
||||
mis à jour via pyi_splash.update_text().
|
||||
- Splash tkinter ci-dessous : zone "Étapes" qui affiche la ligne courante
|
||||
et la dernière étape terminée.
|
||||
En mode dev (pas frozen), pyi_splash n'existe pas ; on ajoute un
|
||||
mini-splash tkinter temporaire pour voir le même rendu pendant le test.
|
||||
"""
|
||||
log.info("Launching GUI...")
|
||||
|
||||
splash = tk.Tk()
|
||||
splash.title("Anonymisation")
|
||||
splash.geometry("480x240")
|
||||
splash.resizable(False, False)
|
||||
|
||||
frame = ttk.Frame(splash, padding=20)
|
||||
frame.pack(fill="both", expand=True)
|
||||
ttk.Label(frame, text="Anonymisation", font=("", 14, "bold")).pack(pady=(0, 4))
|
||||
ttk.Label(frame, text="Pseudonymisation de documents médicaux",
|
||||
foreground="#666666").pack(pady=(0, 8))
|
||||
|
||||
status_var = tk.StringVar(value="Démarrage…")
|
||||
ttk.Label(frame, textvariable=status_var, foreground="#E91E63",
|
||||
font=("", 10, "bold")).pack(pady=(4, 4))
|
||||
|
||||
# Zone où défile le détail des étapes (dernier log.info interceptée)
|
||||
detail_var = tk.StringVar(value="")
|
||||
ttk.Label(frame, textvariable=detail_var, foreground="#666666",
|
||||
font=("", 9), wraplength=440).pack(pady=(0, 8))
|
||||
|
||||
pb = ttk.Progressbar(frame, mode="indeterminate", length=420)
|
||||
pb.pack(pady=4)
|
||||
pb.start(10)
|
||||
|
||||
ttk.Label(frame, text="Merci de patienter — 15 à 30 secondes",
|
||||
foreground="#999999", font=("", 8)).pack(pady=(6, 0))
|
||||
|
||||
result = {"done": False, "error": None}
|
||||
|
||||
# --- Handler de logs qui alimente le splash ---
|
||||
# Traduit les messages techniques vers des libellés "prod" plus lisibles.
|
||||
# Traductions log.info() → libellés "prod" lisibles pour l'utilisateur.
|
||||
_LOG_TRANSLATIONS = [
|
||||
("Gazetteers INSEE prénoms", "Chargement des prénoms français (INSEE)…"),
|
||||
("Gazetteers INSEE communes", "Chargement des communes françaises (INSEE)…"),
|
||||
@@ -180,56 +153,44 @@ def launch_gui():
|
||||
return human
|
||||
return msg
|
||||
|
||||
# Handler logs → splash natif. Installé sur le root logger pour capturer
|
||||
# tous les log.info() des modules chargés pendant l'import.
|
||||
class _SplashHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
try:
|
||||
msg = record.getMessage()
|
||||
human = _translate(msg)
|
||||
# Update splash tkinter (thread-safe via after)
|
||||
splash.after(0, lambda m=human: detail_var.set(m[:120]))
|
||||
# Update splash natif PyInstaller (si encore actif)
|
||||
_splash_update(human)
|
||||
_splash_update(_translate(record.getMessage()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_splash_handler = _SplashHandler()
|
||||
_splash_handler.setLevel(logging.INFO)
|
||||
logging.getLogger().addHandler(_splash_handler)
|
||||
_handler = _SplashHandler()
|
||||
_handler.setLevel(logging.INFO)
|
||||
logging.getLogger().addHandler(_handler)
|
||||
|
||||
def _do_import():
|
||||
# Afficher tout de suite un message initial sous le logo
|
||||
_splash_update("Démarrage…")
|
||||
|
||||
# Import du core et de la GUI (synchrone : pas besoin de thread puisque
|
||||
# le splash natif tourne dans son propre processus bootloader).
|
||||
result = {"error": None}
|
||||
try:
|
||||
status_var.set("Chargement des dictionnaires médicaux…")
|
||||
_splash_update("Chargement des dictionnaires médicaux…")
|
||||
import anonymizer_core_refactored_onnx # noqa
|
||||
log.info("Core imported OK")
|
||||
result["step"] = "gui"
|
||||
status_var.set("Chargement de l'interface…")
|
||||
import Pseudonymisation_Gui_V5 # noqa
|
||||
log.info("GUI module imported OK")
|
||||
except Exception as e:
|
||||
result["error"] = f"{e}\n{traceback.format_exc()}"
|
||||
log.error(f"Import error: {result['error']}")
|
||||
finally:
|
||||
result["done"] = True
|
||||
|
||||
threading.Thread(target=_do_import, daemon=True).start()
|
||||
|
||||
# Fermer le splash natif PyInstaller dès que le splash tkinter est à l'écran
|
||||
# (sinon les deux se superposent pendant tout l'import).
|
||||
splash.update() # force le rendu initial
|
||||
_splash_close()
|
||||
|
||||
def _poll():
|
||||
if result["done"]:
|
||||
pb.stop()
|
||||
# Retirer le handler — la GUI principale utilise ses propres logs
|
||||
try:
|
||||
logging.getLogger().removeHandler(_splash_handler)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
splash.destroy()
|
||||
logging.getLogger().removeHandler(_handler)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fermer le splash natif maintenant que tout est prêt
|
||||
_splash_close()
|
||||
|
||||
if result["error"]:
|
||||
try:
|
||||
messagebox.showerror(
|
||||
@@ -240,7 +201,8 @@ def launch_gui():
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
# Lancement de la GUI principale
|
||||
|
||||
# Lancer la fenêtre principale
|
||||
try:
|
||||
import Pseudonymisation_Gui_V5
|
||||
log.info("Starting mainloop…")
|
||||
@@ -256,11 +218,6 @@ def launch_gui():
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
splash.after(200, _poll)
|
||||
|
||||
splash.after(200, _poll)
|
||||
splash.mainloop()
|
||||
|
||||
|
||||
class SetupWindow:
|
||||
|
||||
Reference in New Issue
Block a user