gui_utils.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import os
  2. import sys
  3. from PIL import Image, ImageTk
  4. def setup_toplevel_window(window, parent, scale=0.60):
  5. window.update_idletasks()
  6. parent.update_idletasks()
  7. # 1. Dimensions du parent
  8. p_width = parent.winfo_width()
  9. p_height = parent.winfo_height()
  10. p_x = parent.winfo_rootx()
  11. p_y = parent.winfo_rooty()
  12. # 2. Calcul de la taille cible
  13. s_width = int(p_width * scale)
  14. s_height = int(p_height * scale)
  15. # 3. SÉCURITÉ : On récupère la hauteur RÉELLE de l'écran sans barre des tâches
  16. # Sous Windows, on peut utiliser cette astuce pour ne pas déborder :
  17. screen_h = window.winfo_screenheight()
  18. max_h = screen_h - 100 # Réserve 100px pour la barre des tâches et le titre
  19. if s_height > max_h:
  20. s_height = max_h
  21. # 4. Centrage
  22. pos_x = p_x + (p_width - s_width) // 2
  23. pos_y = p_y + (p_height - s_height) // 2
  24. # Sécurité pour ne pas coller au bord haut
  25. if pos_y < 20: pos_y = 20
  26. window.geometry(f"{s_width}x{s_height}+{pos_x}+{pos_y}")
  27. window.update_idletasks() # Applique avant que apply_window_icon ne passe
  28. from utils.gui_utils import apply_window_icon
  29. apply_window_icon(window, parent)
  30. def apply_window_icon(window_instance, parent):
  31. try:
  32. # Priorité au chemin du parent
  33. path = getattr(parent, 'icon_path', None)
  34. # Recalcul du chemin si nécessaire
  35. if not path or not os.path.exists(path):
  36. # On part du dossier où se trouve gui_utils.py
  37. current_dir = os.path.dirname(os.path.abspath(__file__))
  38. # Si gui_utils est dans 'utils/' et assets est à la racine :
  39. path = os.path.abspath(os.path.join(current_dir, "..", "assets", "icone_alig.ico"))
  40. if os.path.exists(path):
  41. # On utilise un délai un peu plus long pour s'assurer que la fenêtre est prête
  42. window_instance.after(200, lambda: _set_icon_safe(window_instance, path))
  43. else:
  44. print(f"DEBUG: Fichier icône introuvable à : {path}")
  45. except Exception as e:
  46. print(f"Erreur icône : {e}")
  47. def _set_icon_safe(window_instance, path):
  48. try:
  49. if sys.platform.startswith('win'):
  50. window_instance.iconbitmap(path)
  51. else:
  52. img = Image.open(path)
  53. photo = ImageTk.PhotoImage(img)
  54. # IMPORTANT : Garder une référence pour éviter le Garbage Collector
  55. window_instance._icon_ref = photo
  56. window_instance.wm_iconphoto(True, photo)
  57. except Exception as e:
  58. print(f"Erreur application icône : {e}")
  59. def setup_app_id():
  60. """
  61. Force Windows à reconnaître l'application comme une entité distincte de Python.
  62. Indispensable pour que l'icône de la barre des tâches corresponde à l'icône de la fenêtre.
  63. """
  64. if sys.platform.startswith('win'):
  65. try:
  66. import ctypes
  67. # Identifiant unique : format 'editeur.logiciel.version'
  68. myappid = 'alig_project.engraver_v1.0'
  69. ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
  70. except Exception as e:
  71. print(f"Erreur d'initialisation de l'AppID Windows : {e}")
  72. def bind_minimize_sync(window, parent):
  73. """Synchronise la réduction et la restauration des deux fenêtres."""
  74. def on_parent_state_change(event):
  75. # On vérifie que c'est bien le parent qui change d'état (pas un enfant)
  76. if event.widget == parent:
  77. state = parent.state()
  78. if state == 'iconic':
  79. window.withdraw() # Cache la simulation si le main est réduit
  80. elif state == 'normal':
  81. window.deiconify() # Réaffiche la simulation si le main revient
  82. parent.bind("<Unmap>", on_parent_state_change)
  83. parent.bind("<Map>", on_parent_state_change)