diff --git a/launcher.py b/launcher.py index 90f8df3..13280e3 100644 --- a/launcher.py +++ b/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,87 +153,71 @@ 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(): - try: - status_var.set("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 + # Afficher tout de suite un message initial sous le logo + _splash_update("Démarrage…") - threading.Thread(target=_do_import, daemon=True).start() + # 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: + _splash_update("Chargement des dictionnaires médicaux…") + import anonymizer_core_refactored_onnx # noqa + log.info("Core imported OK") + 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']}") - # 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 + # Retirer le handler — la GUI principale utilise ses propres logs + try: + logging.getLogger().removeHandler(_handler) + except Exception: + pass + + # Fermer le splash natif maintenant que tout est prêt _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() - except Exception: - pass - if result["error"]: - try: - messagebox.showerror( - "Erreur", - f"Erreur au lancement :\n\n{result['error'].splitlines()[0]}\n\n" - f"Voir {LOG_FILE} pour les détails.", - ) - except Exception: - pass - return - # Lancement de la GUI principale - try: - import Pseudonymisation_Gui_V5 - log.info("Starting mainloop…") - root = tk.Tk() - Pseudonymisation_Gui_V5.App(root) - root.mainloop() - except Exception as e: - log.error(f"GUI error: {e}\n{traceback.format_exc()}") - try: - messagebox.showerror( - "Erreur", - f"Erreur de l'interface :\n\n{e}\n\nVoir {LOG_FILE}", - ) - except Exception: - pass - else: - splash.after(200, _poll) + if result["error"]: + try: + messagebox.showerror( + "Erreur", + f"Erreur au lancement :\n\n{result['error'].splitlines()[0]}\n\n" + f"Voir {LOG_FILE} pour les détails.", + ) + except Exception: + pass + return - splash.after(200, _poll) - splash.mainloop() + # Lancer la fenêtre principale + try: + import Pseudonymisation_Gui_V5 + log.info("Starting mainloop…") + root = tk.Tk() + Pseudonymisation_Gui_V5.App(root) + root.mainloop() + except Exception as e: + log.error(f"GUI error: {e}\n{traceback.format_exc()}") + try: + messagebox.showerror( + "Erreur", + f"Erreur de l'interface :\n\n{e}\n\nVoir {LOG_FILE}", + ) + except Exception: + pass class SetupWindow: