import os
import win32gui
import win32con
import pyautogui
import time
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, simpledialog
import subprocess
import configparser

# ----------------------------------------
# Config file and default paths
# ----------------------------------------
CONFIG_FILE = "macro_paths.ini"

# Default hardcoded paths (used if no config file exists)
DEFAULT_MACRO_PATH_CATScript = r"S:\\00_Allgemein\\08_Macros\\HPL\\PaythonCAT.CATScript"
DEFAULT_MACRO_PATH_CATVBS = r"S:\\00_Allgemein\\08_Macros\\HPL\\PaythonVBA.catvbs"
DEFAULT_MACRO_FOLDER = r"S:\\00_Allgemein\\08_Macros\\HPL"
DEFAULT_EXE_PATHS = []  # Liste vide par défaut


def load_config():
    """
    Charge le fichier INI et retourne :
    - Chemin du .CATScript
    - Chemin du .catvbs
    - Dossier par défaut des macros
    - Liste de chemins .exe (EXE_PATHS)
    """
    config = configparser.ConfigParser()
    if os.path.exists(CONFIG_FILE):
        try:
            config.read(CONFIG_FILE)
            cat_script = config.get("Paths", "MACRO_PATH_CATScript", fallback=DEFAULT_MACRO_PATH_CATScript)
            cat_vbs = config.get("Paths", "MACRO_PATH_CATVBS", fallback=DEFAULT_MACRO_PATH_CATVBS)
            default_folder = config.get("Paths", "DEFAULT_MACRO_FOLDER", fallback=DEFAULT_MACRO_FOLDER)
            exe_paths_str = config.get("Paths", "EXE_PATHS", fallback="")
            if exe_paths_str:
                exe_paths = exe_paths_str.split(';')
            else:
                exe_paths = DEFAULT_EXE_PATHS.copy()
        except configparser.MissingSectionHeaderError:
            # Si le fichier est mal formé, le supprimer
            os.remove(CONFIG_FILE)
            cat_script = DEFAULT_MACRO_PATH_CATScript
            cat_vbs = DEFAULT_MACRO_PATH_CATVBS
            default_folder = DEFAULT_MACRO_FOLDER
            exe_paths = DEFAULT_EXE_PATHS.copy()
    else:
        cat_script = DEFAULT_MACRO_PATH_CATScript
        cat_vbs = DEFAULT_MACRO_PATH_CATVBS
        default_folder = DEFAULT_MACRO_FOLDER
        exe_paths = DEFAULT_EXE_PATHS.copy()

    return cat_script, cat_vbs, default_folder, exe_paths


def save_config(cat_script, cat_vbs, default_folder, exe_paths):
    """
    Écrit les chemins dans le fichier INI sous la section [Paths].
    - exe_paths est une liste de chemins complets, séparés par ';'.
    """
    config = configparser.ConfigParser()
    config["Paths"] = {
        "MACRO_PATH_CATScript": cat_script,
        "MACRO_PATH_CATVBS": cat_vbs,
        "DEFAULT_MACRO_FOLDER": default_folder,
        "EXE_PATHS": ";".join(exe_paths)
    }
    with open(CONFIG_FILE, "w") as configfile:
        config.write(configfile)

# ----------------------------------------
# Informations sur les macros CATIA
# ----------------------------------------
macro_info = {
    "001_GEOMETRICAL SET_to_Product": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "Creates a new Product and copies each geometric set from an open CATPart into separate new Parts inside that Product"
    },
    "002_Part_to_Product": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "Creates a new Product and copies each Body from an open CATPart into separate new Parts inside that Product"
    },
    "003_Point_Selection": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "Export points coordinates to excel file"
    },
    "007_Screenshots": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": ""
    },
    "008_e3roughStock": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": ""
    },
    "009_Weischer_Stuecklisten_Format": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "define stk Weisher format before export"
    },
    "010_add parameters": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "remove old parameters and formulas and add new weisher parameters and formulas"
    },
    "010A_add parameters": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "add weisher parameters and formulas"
    },
    "010B_Remove all": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "Remove all parameters and formulas"
    },
    "011_ AGD Weischer": {
        "Type": "Macro CATIA / external application",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "Generate catparts, images with part dimensions in weisher AGD standards. This macro execute an external application named \"principal\""
    },
    "012_RENAME_PartNumber": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "change et save the part with a new proposed name"
    },
    "15_add parameters to a folder": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "add weisher parameters and formulas to all part in folder"
    },
    "020_Export Step": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "export CATPART to STEP"
    },
    "021_Export Pdf": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "export drawing to PDF"
    },
    "022_Export Dxf": {
        "Type": "Macro CATIA",
        "Client": "SBM",
        "Status": "ok",
        "Version": "1",
        "Description": "export drawing toDXF"
    },
    "30_A_Conversion of CAT To STP subfolders with mirror": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "duplicate a hole folder and convert all catpart to STEP"
    },
    "30_B_Conversion of stp files to CATPart subfolders": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "convert all STEP to Catpart in the same folder"
    },
    "30_C_define parameters subfolders": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "TEST",
        "Version": "1",
        "Description": "ADD parameters to all parts in a folder"
    },
    "30_D_define parameters subfolders": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "TEST",
        "Version": "1",
        "Description": "ADD parameters to all parts in a folder"
    },
    "40_CheckMissingParameters": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "CheckMissingParameters"
    },
    "41_Ultimate Checker": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "ok",
        "Version": "1",
        "Description": "check for missing parameters , wrong names , duplucated POS number …."
    },
    "42_AGD Parts": {
        "Type": "Macro CATIA",
        "Client": "weisher",
        "Status": "TEST",
        "Version": "1",
        "Description": "Copy parts needed for AGD"
    },
    "50_HPL_QF60_add parameters": {
        "Type": "Macro CATIA",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Add parameters for parts"
    },
    "51_HPL_QF60_CATPartExcelUpdater": {
        "Type": "Macro CATIA / external application",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Genarates QF60 files (xls) from parts. This macro execute an external application named \"excel_picture_processor\""
    },
    "55_HPL_AutoDrawPartInfo": {
        "Type": "Macro CATIA",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Genarates drawings form parts"
    },
    "56_BatchPartDuplicator": {
        "Type": "Macro CATIA",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Creates multiples parts from a start model according to HPL standard naming"
    },
    "57_ReplaceTextInExcel": {
        "Type": "Macro CATIA",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Replace text in excel QF60. it works an all files in folder"
    },
    "58_ReplaceTextInSheetViews _FOLDER": {
        "Type": "Macro CATIA",
        "Client": "HPL",
        "Status": "ok",
        "Version": "1",
        "Description": "Replace text in drawing. it works an all files in folder"
    },
    "001_Ausblenden_Elemente": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "hide elements"
    },
    "002_OEM_Bearbeitungsfarben": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "colors palette ( list)"
    },
    "003_Daimler_Lehrenfarben": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "DAIMLER colors palette ( list)"
    },
    "004_Instancenamen_syncronisieren_V1.1": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "Synchronize_instance_names"
    },
    "005_Saveas": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": ""
    },
    "007_PARTtoPRODUCT": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "Creates a new Product and copies each geometric set and body from an open CATPart into separate new Parts inside that Product"
    },
    "008_Schriftkopfmakro": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "Create a frame with a title block according to ISO 7200 + folding guidelines *"
    },
    "009_ITH_Stuecklisten_Format": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "define stk ITH format before export"
    },
    "011_Stuecklistenparameter_einfuegen": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "add ITH parameters and formulas"
    },
    "014_Abfrage_Bearbeitungsfarbe": {
        "Type": "Macro CATIA",
        "Client": "ITH",
        "Status": "ok",
        "Version": "1",
        "Description": "Detect face color and RGB code"
    }
    # Ajoutez d'autres macros si nécessaire
}

# ----------------------------------------
# Classe principale de l’application
# ----------------------------------------
class MacroExeApp:
    def __init__(self, root):
        self.root = root
        self.root.title("CATIA Macro & EXE Launcher")
        self.root.geometry("1300x900")
        self.root.configure(bg="#1e1e2f")

        # Charger les chemins depuis le fichier INI
        (self.macro_path_catscript,
         self.macro_path_catvbs,
         self.default_macro_folder,
         self.exe_paths) = load_config()

        # Variables pour l’onglet macros
        self.macro_folder = self.default_macro_folder
        self.macro_files = []
        self.search_var = tk.StringVar()

        # Barre de menu
        self.build_menubar()

        # Construire l’interface à onglets
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Onglet 1 : Macros
        self.tab_macros = tk.Frame(self.notebook, bg="#1e1e2f")
        self.notebook.add(self.tab_macros, text="Macros")
        self.build_macros_tab()

        # Onglet 2 : EXE Runner
        self.tab_exe = tk.Frame(self.notebook, bg="#1e1e2f")
        self.notebook.add(self.tab_exe, text="EXE Runner")
        self.build_exe_tab()

        # Version et notice en bas
        bottom_frame = tk.Frame(self.root, bg="#1e1e2f")
        bottom_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(0,10))
        lblVersion = tk.Label(bottom_frame, text="Version V1.6", font=("Times New Roman", 10), fg="white", bg="#1e1e2f")
        lblVersion.pack(side=tk.LEFT, padx=10)
        notice_text = "This program is the exclusive property of CADit and SBM Design. Unauthorized production or distribution of this program is strictly prohibited and may result in legal action. Any reproduction or use of this program must be authorized in writing by CADit and SBM Design. Thank you for your understanding."
        lblNotice = tk.Label(bottom_frame, text=notice_text, font=("Times New Roman", 8), fg="white", bg="#1e1e2f", wraplength=1000, justify=tk.LEFT)
        lblNotice.pack(side=tk.LEFT, padx=10)

        # Charger la liste des macros au démarrage
        self.load_macros()

        # Lorsque l’application se ferme, sauvegarder le config
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    def build_menubar(self):
        menubar = tk.Menu(self.root)
        filemenu = tk.Menu(menubar, tearoff=0)
        filemenu.add_command(label="Exit", command=self.root.quit)
        menubar.add_cascade(label="File", menu=filemenu)
        helpmenu = tk.Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", command=self.show_about)
        menubar.add_cascade(label="Help", menu=helpmenu)
        self.root.config(menu=menubar)

    def show_about(self):
        messagebox.showinfo("About", "CATIA Macro & EXE Launcher\nVersion V1.6\n\nThis program is the exclusive property of CADit and SBM Design. Unauthorized use is prohibited.")

    # ----------------------------------------
    # Méthodes pour l’onglet Macros
    # ----------------------------------------
    def is_catia_window(self, hwnd):
        if win32gui.IsWindowVisible(hwnd):
            title = win32gui.GetWindowText(hwnd)
            return "CATIA V5" in title
        return False

    def get_catia_hwnd(self):
        catia_hwnds = []
        def enum_handler(hwnd, _):
            if self.is_catia_window(hwnd):
                catia_hwnds.append(hwnd)
        win32gui.EnumWindows(enum_handler, None)
        return catia_hwnds[0] if catia_hwnds else None

    def bring_to_front(self, hwnd):
        win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
        win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
                              win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
        win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0,
                              win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
        win32gui.SetForegroundWindow(hwnd)

    def bring_catia_and_send_hotkey(self, hotkey):
        hwnd = self.get_catia_hwnd()
        if hwnd:
            self.bring_to_front(hwnd)
            time.sleep(1)
            pyautogui.hotkey(*hotkey)
            return True
        else:
            return False

    def build_macros_tab(self):
        # Top frame : dossier de macros + browse + bouton verrouillé
        top_frame = tk.Frame(self.tab_macros, bg="#1e1e2f")
        top_frame.pack(fill=tk.X, padx=10, pady=10)

        tk.Label(top_frame, text="Macro Folder:", font=("Times New Roman", 12), bg="#cbdaf2").pack(side=tk.LEFT)

        self.folder_var = tk.StringVar(value=self.macro_folder)
        folder_entry = tk.Entry(top_frame, textvariable=self.folder_var, width=55, font=("Times New Roman", 12))
        folder_entry.pack(side=tk.LEFT, padx=8)

        browse_btn = tk.Button(top_frame, text="Browse...", command=self.browse_folder, font=("Times New Roman", 11))
        browse_btn.pack(side=tk.LEFT)

        self.lock_btn = tk.Button(
            top_frame, text="🔒", font=("Segoe UI Emoji", 14, "bold"),
            bg="#b8860b", fg="white", relief=tk.RAISED,
            command=self.open_lock_window
        )
        self.lock_btn.pack(side=tk.LEFT, padx=(8, 0))

        # Barre de recherche
        search_frame = tk.Frame(self.tab_macros, bg="#cbdaf2")
        search_frame.pack(fill=tk.X, padx=10, pady=(0, 10))

        tk.Label(search_frame, text="Search:", font=("Times New Roman", 12), bg="#cbdaf2").pack(side=tk.LEFT)

        self.search_var.trace_add("write", self.filter_list)
        search_entry = tk.Entry(search_frame, textvariable=self.search_var, font=("Times New Roman", 12))
        search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=8)

        # Main frame : Listbox + Description
        main_frame = tk.Frame(self.tab_macros, bg="#cbdaf2")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        list_frame = tk.Frame(main_frame, bg="#cbdaf2")
        list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.listbox = tk.Listbox(list_frame, font=("Consolas", 13),
                                  activestyle='none', selectbackground="#3399ff")
        self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.listbox.bind('<<ListboxSelect>>', self.on_macro_select)

        scrollbar = tk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.listbox.config(yscrollcommand=scrollbar.set)

        desc_frame = tk.Frame(main_frame, bg="#cbdaf2")
        desc_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(12, 0))

        tk.Label(desc_frame, text="Macro Details", font=("Times New Roman", 15, "bold"), bg="#cbdaf2").pack(anchor="w")

        self.desc_text = tk.Text(desc_frame, wrap=tk.WORD, font=("Times New Roman", 12),
                                 state=tk.DISABLED, width=60, bg="#ffffff", relief=tk.SUNKEN, bd=2)
        self.desc_text.pack(fill=tk.BOTH, expand=True)

        # Boutons en bas
        run_btn = tk.Button(
            self.tab_macros, text="Load & Run Selected Macro", font=("Times New Roman", 14),
            command=self.run_selected_macro, bg="#4caf50", fg="white", activebackground="#45a049"
        )
        run_btn.pack(pady=12)

        create_macro_btn = tk.Button(
            self.tab_macros, text="Create Default CATIA Macros", font=("Times New Roman", 12),
            command=self.create_macros, bg="#007acc", fg="white", activebackground="#005a99"
        )
        create_macro_btn.pack(pady=6)

        self.status_label = tk.Label(self.tab_macros, text="", font=("Times New Roman", 11), fg="red", bg="#cbdaf2")
        self.status_label.pack(pady=(0, 10))

    def browse_folder(self):
        folder_selected = filedialog.askdirectory(initialdir=self.macro_folder, title="Select Macro Folder")
        if folder_selected:
            self.macro_folder = folder_selected
            self.folder_var.set(self.macro_folder)
            self.load_macros()

    def load_macros(self):
        try:
            files = os.listdir(self.macro_folder)
            self.macro_files = [f for f in files if f.lower().endswith((".catscript", ".catvbs"))]
            self.macro_files.sort()
        except Exception:
            self.macro_files = []
            self.update_listbox()
            self.status_label.config(text="Error reading macro folder.")
            return

        self.filter_list()
        self.status_label.config(text=f"Loaded {len(self.macro_files)} macro(s) from folder.", fg="black")

    def update_listbox(self):
        self.listbox.delete(0, tk.END)
        for macro in self.macro_files:
            self.listbox.insert(tk.END, macro)

    def filter_list(self, *args):
        search_text = self.search_var.get().lower()
        self.listbox.delete(0, tk.END)

        for macro in self.macro_files:
            macro_name = os.path.splitext(macro)[0]
            info = macro_info.get(macro_name, {})
            description = info.get("Description", "").lower()
            client = info.get("Client", "").lower()

            if (search_text in macro.lower()
                or search_text in description
                or search_text in client):
                self.listbox.insert(tk.END, macro)

        # Effacer la description chaque fois qu’on filtre
        self.desc_text.config(state=tk.NORMAL)
        self.desc_text.delete(1.0, tk.END)
        self.desc_text.config(state=tk.DISABLED)
        self.status_label.config(text="")

    def on_macro_select(self, event):
        selection = self.listbox.curselection()
        if not selection:
            return
        filename = self.listbox.get(selection[0])
        macro_name = os.path.splitext(filename)[0]

        info = macro_info.get(macro_name)
        self.desc_text.config(state=tk.NORMAL)
        self.desc_text.delete(1.0, tk.END)
        if info:
            detail_str = (f"Name: {macro_name}\n"
                          f"Type: {info.get('Type', '')}\n"
                          f"Client: {info.get('Client', '')}\n"
                          f"Status: {info.get('Status', '')}\n"
                          f"Version: {info.get('Version', '')}\n\n"
                          f"Description:\n{info.get('Description', '')}")
            self.desc_text.insert(tk.END, detail_str)
        else:
            self.desc_text.insert(tk.END, "No description available for this macro.")
        self.desc_text.config(state=tk.DISABLED)

    def run_selected_macro(self):
        selection = self.listbox.curselection()
        if not selection:
            self.status_label.config(text="No macro selected.")
            return
        filename = self.listbox.get(selection[0])
        source_path = os.path.join(self.macro_folder, filename)

        ext = filename.lower().split('.')[-1]
        if ext == "catscript":
            target_path = self.macro_path_catscript
            hotkey = ('alt', 'f9')
        elif ext == "catvbs":
            target_path = self.macro_path_catvbs
            hotkey = ('alt', 'f10')
        else:
            self.status_label.config(text="Unsupported macro type.")
            return

        try:
            with open(source_path, "r", encoding="utf-8") as f:
                content = f.read()
            with open(target_path, "w", encoding="utf-8") as f:
                f.write(content)
        except Exception as e:
            self.status_label.config(text=f"Error copying macro file: {e}")
            return

        success = self.bring_catia_and_send_hotkey(hotkey)
        if success:
            self.status_label.config(text=f"Macro '{filename}' loaded and hotkey sent to CATIA.", fg="green")
        else:
            self.status_label.config(text="Could not find CATIA window.", fg="red")

    def create_macros(self):
        cat_script_content = '''Sub CATMain()
    MsgBox "PaythonCAT macro launched!", vbInformation, "CATIA"
End Sub
'''
        vba_script_content = '''MsgBox "PaythonVBA macro launched!", vbInformation, "CATIA"
'''
        macro_path = self.macro_folder

        try:
            if not os.path.exists(macro_path):
                os.makedirs(macro_path)

            cat_file = os.path.join(macro_path, "PaythonCAT.CATScript")
            vba_file = os.path.join(macro_path, "PaythonVBA.catvbs")

            with open(cat_file, 'w', encoding='utf-8') as f:
                f.write(cat_script_content)

            with open(vba_file, 'w', encoding='utf-8') as f:
                f.write(vba_script_content)

            messagebox.showinfo("Success", f"Macros created in:\n{macro_path}\n\nAssign them in CATIA manually.")
            self.load_macros()  # Recharge la liste des macros
        except Exception as e:
            messagebox.showerror("Error", f"Failed to create macros:\n{e}")

    def open_lock_window(self):
        """
        Ouvre une fenêtre modale pour demander le mot de passe,
        puis ouvre le Path Editor si le mot de passe est correct.
        """
        def check_password():
            if password_entry.get() == "CADadmin":  # Modifier le mot de passe si besoin
                password_win.destroy()
                self.show_path_editor()
            else:
                messagebox.showerror("Error", "Incorrect password!")

        password_win = tk.Toplevel(self.root)
        password_win.title("Enter Password")
        password_win.geometry("300x100")
        password_win.configure(bg="#1e1e2f")
        password_win.grab_set()  # Fenêtre modale

        tk.Label(password_win, text="Enter password:", font=("Times New Roman", 12),
                 bg="#1e1e2f", fg="white").pack(pady=5)
        password_entry = tk.Entry(password_win, show="*", font=("Times New Roman", 12))
        password_entry.pack(pady=5)
        password_entry.focus()

        btn_frame = tk.Frame(password_win, bg="#1e1e2f")
        btn_frame.pack()

        tk.Button(btn_frame, text="OK", font=("Times New Roman", 11),
                  command=check_password).pack(side=tk.LEFT, padx=10)
        tk.Button(btn_frame, text="Cancel", font=("Times New Roman", 11),
                  command=password_win.destroy).pack(side=tk.LEFT)

    def show_path_editor(self):
        """
        Ouvre une fenêtre modale permettant d’éditer :
        - MACRO_PATH_CATScript
        - MACRO_PATH_CATVBS
        - DEFAULT_MACRO_FOLDER
        """
        def save_paths():
            nonlocal cat_script_var, cat_vbs_var, default_folder_var

            cat_script = cat_script_var.get().strip()
            cat_vbs = cat_vbs_var.get().strip()
            default_folder = default_folder_var.get().strip()

            if not (cat_script and cat_vbs and default_folder):
                messagebox.showerror("Error", "All macro paths must be filled!")
                return

            # Mettre à jour les variables de l’application
            self.macro_path_catscript = cat_script
            self.macro_path_catvbs = cat_vbs
            self.default_macro_folder = default_folder

            # Mettre à jour la liste des macros
            self.macro_folder = self.default_macro_folder
            self.folder_var.set(self.default_macro_folder)
            self.load_macros()

            # Sauvegarder dans le fichier INI (conserver exe_paths inchangés)
            save_config(self.macro_path_catscript,
                        self.macro_path_catvbs,
                        self.default_macro_folder,
                        self.exe_paths)

            messagebox.showinfo("Saved", "Paths saved successfully!")
            editor_win.destroy()

        editor_win = tk.Toplevel(self.root)
        editor_win.title("Edit Paths")
        editor_win.geometry("700x220")
        editor_win.configure(bg="#1e1e2f")
        editor_win.grab_set()  # Fenêtre modale

        # CATScript
        tk.Label(editor_win, text="MACRO_PATH_CATScript:", font=("Times New Roman", 12, "bold"),
                 bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
        cat_script_var = tk.StringVar(value=self.macro_path_catscript)
        cat_script_entry = tk.Entry(editor_win, textvariable=cat_script_var,
                                    font=("Times New Roman", 12), width=80)
        cat_script_entry.pack(padx=10, pady=5)

        # CATVBS
        tk.Label(editor_win, text="MACRO_PATH_CATVBS:", font=("Times New Roman", 12, "bold"),
                 bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
        cat_vbs_var = tk.StringVar(value=self.macro_path_catvbs)
        cat_vbs_entry = tk.Entry(editor_win, textvariable=cat_vbs_var,
                                 font=("Times New Roman", 12), width=80)
        cat_vbs_entry.pack(padx=10, pady=5)

        # DEFAULT_MACRO_FOLDER
        tk.Label(editor_win, text="DEFAULT_MACRO_FOLDER:", font=("Times New Roman", 12, "bold"),
                 bg="#1e1e2f", fg="white").pack(anchor="w", padx=10, pady=(10, 0))
        default_folder_var = tk.StringVar(value=self.default_macro_folder)
        default_folder_entry = tk.Entry(editor_win, textvariable=default_folder_var,
                                        font=("Times New Roman", 12), width=80)
        default_folder_entry.pack(padx=10, pady=5)

        save_btn = tk.Button(editor_win, text="Save Paths", font=("Times New Roman", 12, "bold"),
                              bg="#4caf50", fg="white", command=save_paths)
        save_btn.pack(pady=15)

    # ----------------------------------------
    # Méthodes pour l’onglet EXE Runner
    # ----------------------------------------
    def build_exe_tab(self):
        """
        Construit l’interface du deuxième onglet, permettant de gérer une liste de .exe :
        - Ajouter un .exe via Browse
        - Lister les .exe ajoutés (nom fichier)
        - Lancer l’exécutable sélectionné
        - Retirer l’exécutable sélectionné
        """
        frame = tk.Frame(self.tab_exe, bg="#cbdaf2")
        frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

        # Listbox pour afficher les noms des .exe ajoutés
        tk.Label(frame, text="EXE Files:", font=("Times New Roman", 12), bg="#cbdaf2").grid(row=0, column=0, sticky="nw")
        self.exe_listbox = tk.Listbox(frame, font=("Consolas", 12), height=10)
        self.exe_listbox.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(5, 10))

        # Scrollbar pour la listbox
        exe_scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL, command=self.exe_listbox.yview)
        exe_scrollbar.grid(row=1, column=3, sticky="ns", pady=(5, 10))
        self.exe_listbox.config(yscrollcommand=exe_scrollbar.set)

        # Bouton Browse pour ajouter un nouveau .exe
        browse_exe_btn = tk.Button(
            frame, text="📂 Add EXE File", font=("Times New Roman", 11),
            command=self.add_exe_file, bg="#007acc", fg="white", activebackground="#005a99"
        )
        browse_exe_btn.grid(row=2, column=0, pady=10, sticky="w")

        # Bouton Run EXE
        run_exe_btn = tk.Button(
            frame, text="▶️ Run Selected EXE", font=("Times New Roman", 11),
            command=self.run_selected_exe, bg="#4caf50", fg="white", activebackground="#45a049"
        )
        run_exe_btn.grid(row=2, column=1, pady=10, sticky="w", padx=(10,0))

        # Bouton Remove EXE
        remove_exe_btn = tk.Button(
            frame, text="🗑️ Remove Selected EXE", font=("Times New Roman", 11),
            command=self.remove_selected_exe, bg="#ff5555", fg="white", activebackground="#cc4444"
        )
        remove_exe_btn.grid(row=2, column=2, pady=10, sticky="w", padx=(10,0))

        # Configuration de la grille pour redimensionner
        frame.grid_rowconfigure(1, weight=1)
        frame.grid_columnconfigure(2, weight=1)

        # Remplir la listbox avec les chemins déjà chargés
        self.update_exe_listbox()

    def add_exe_file(self):
        """
        Ouvre un dialogue pour sélectionner un fichier .exe,
        puis l’ajoute à self.exe_paths et met à jour la listbox.
        """
        file_path = filedialog.askopenfilename(
            title="Select EXE File",
            filetypes=[("Executable Files", "*.exe")]
        )
        if file_path:
            if file_path not in self.exe_paths:
                self.exe_paths.append(file_path)
                self.update_exe_listbox()
            else:
                messagebox.showinfo("Info", "This EXE is already in the list.")

    def update_exe_listbox(self):
        """
        Met à jour la listbox pour afficher uniquement les noms de fichier
        extraits de self.exe_paths.
        """
        self.exe_listbox.delete(0, tk.END)
        for path in self.exe_paths:
            filename = os.path.basename(path)
            self.exe_listbox.insert(tk.END, filename)

    def run_selected_exe(self):
        """
        Lancer le .exe sélectionné dans la listbox.
        """
        selection = self.exe_listbox.curselection()
        if not selection:
            messagebox.showerror("Error", "No EXE selected.")
            return
        index = selection[0]
        exe_path = self.exe_paths[index]
        if not os.path.exists(exe_path):
            messagebox.showerror("Error", f"File not found:\n{exe_path}")
            return
        try:
            subprocess.Popen([exe_path], shell=False)
        except Exception as e:
            messagebox.showerror("Error", f"Failed to run EXE:\n{e}")

    def remove_selected_exe(self):
        """
        Supprime l’exécutable sélectionné de la liste et met à jour la listbox.
        """
        selection = self.exe_listbox.curselection()
        if not selection:
            messagebox.showerror("Error", "No EXE selected to remove.")
            return
        index = selection[0]
        del self.exe_paths[index]
        self.update_exe_listbox()

    # ----------------------------------------
    # Méthode appelée à la fermeture de l’application
    # ----------------------------------------
    def on_close(self):
        """
        Sauvegarder automatiquement les chemins dans le config avant de quitter.
        """
        save_config(
            self.macro_path_catscript,
            self.macro_path_catvbs,
            self.default_macro_folder,
            self.exe_paths
        )
        self.root.destroy()

# ----------------------------------------
# Point d’entrée
# ----------------------------------------
if __name__ == "__main__":
    root = tk.Tk()
    app = MacroExeApp(root)
    root.mainloop()
( what this code do in short )