"""
RFerencias V2.7 – Generador de enlaces para QElectroTech
Modificado: 12-jun-2025

Requiere:
    • PyMuPDF  (pip install pymupdf)
    • ttkbootstrap (pip install ttkbootstrap)

Funcionalidades principales:
–––––––––––––––––––––––––––
✔ Tema oscuro minimalista ("darkly")
✔ Interfaz reorganizada y optimizada
✔ Selectores de color para resaltados
✔ Verificación mejorada de PDF
✔ Botones de selección corregidos
"""


import fitz  # PyMuPDF
import re, os, threading
import tkinter as tk
from tkinter import filedialog, messagebox, colorchooser
from ttkbootstrap import (Window, Frame, Label, Entry, Button,
                          Notebook, Checkbutton, Progressbar, Panedwindow, Combobox)
from ttkbootstrap.constants import *

# ──────────────────────────────────────────────────────────────────────────────
# Configuración inicial
# ──────────────────────────────────────────────────────────────────────────────
config = {
    "cell_width": 45.5, # No tocar
    "cell_height": 63.0, # No tocar
    "x_margin": -100.0,
    "y_margin": 0.0,
    "zoom": 4.0,
    "highlight_links": False,
    "highlight_rects": False,
    "ref_format": "X-YXX",
    "link_highlight_color": "#00FF00",  # Verde
    "rect_highlight_color": "#FF0000",   # Rojo
    # NUEVOS PARÁMETROS:
    "num_columns": 17,
    "pixels_columns": 78,
    "num_rows": 8,
    "pixels_rows": 108,
    "use_full_page": False
}

# ──────────────────────────────────────────────────────────────────────────────
# Textos en ambos idiomas
# ──────────────────────────────────────────────────────────────────────────────
texts = {
    "es": {
        "window_title": "GNerador PDFLINK para QET V1",
        "menu_language": "Idioma",
        "lang_es": "Español",
        "lang_en": "English",
        "pdf_select": "Archivo PDF:",
        "browse": "Buscar…",
        "scan_tab": "Escanear",
        "settings_tab": "Configuración",
        "scan_start": "Iniciar escaneo",
        "verify": "Verificar PDF",
        "exit": "Salir",
        "log_pdf_selected": "PDF seleccionado: {filepath}",
        "pdf_success": "PDF {filepath} abierto ({pages} páginas)",
        "error_no_pdf": "Seleccione un PDF antes de continuar.",
        "found_refs": "Nº de enlaces detectados: {count}",
        "created_links": "Nº de vínculos creados: {count}",
        "success_pdf_saved": "PDF con enlaces guardado en:\n{save_pdf_path}",
        "verification_ok": "Verificación finalizada: sin errores.",
        "verification_fail": "Verificación: {bad} problema(s) encontrado(s).",
        "error_creating_link": "Error creando vínculo para {ref}: {error}",
        "error": "ERROR",
        "page_selector": "Páginas",
        "select_all": "Todas",
        "deselect_all": "Ninguna",
        "calibration_title": "Guía rápida de calibración",
        "calibration_text": (
            "• Abra un PDF de referencia y pulse «Todas».\n"
            "• Ajuste Columnas y Filas (Propiedades hoja de QET).\n"
            "• Ajuste X/Y OffSet y Zoom para mover la vista."
            "• Ajuste el formato de los textos."
            "• Haga clic en «Iniciar escaneo» y compruebe dónde caen los enlaces.\n"  
        ),
        "highlight_links": "Resaltar textos de enlaces",
        "highlight_rects": "Resaltar borde de vínculos",
        "ref_format": "Formato de referencia:",
        "format_X-YXX": "X-YXX (8-G12)",
        "format_X.YXX": "X.YXX (8.G12)",
        "format_slashX.YXX": "/X.YXX (/8.G12)",
        "cell_width": "Ancho de celda",
        "cell_height": "Alto de celda",
        "x_margin": "OffSet X",
        "y_margin": "OffSet Y",
        "zoom": "Zoom",
        "verify_no_refs": "No se encontraron referencias. Revisa el tipo de Formato de enlace en la pestaña de configuración.",
        "verify_refs_found": "Referencias encontradas: {count}",
        "choose_color": "Elegir color",
        "link_color": "Color texto:",
        "rect_color": "Color borde:",
        # Nuevos textos
        "columns_section": "Columna",
        "num_columns": "Nº de columnas",
        "pixels_columns": "Nº de píxeles",
        "rows_section": "Filas",
        "num_rows": "Nº de filas",
        "pixels_rows": "Nº de píxeles",
        "use_full_page": "Utilizar toda la hoja",
    },
    "en": {
        "window_title": "GNerator PDFLINK for QET V1",
        "menu_language": "Language",
        "lang_es": "Spanish",
        "lang_en": "English",
        "pdf_select": "PDF file:",
        "browse": "Browse…",
        "scan_tab": "Scan",
        "settings_tab": "Settings",
        "scan_start": "Start scan",
        "verify": "Verify PDF",
        "exit": "Quit",
        "log_pdf_selected": "Selected PDF: {filepath}",
        "pdf_success": "PDF {filepath} opened ({pages} pages)",
        "error_no_pdf": "Please choose a PDF file first.",
        "found_refs": "Links detected: {count}",
        "created_links": "Links created: {count}",
        "success_pdf_saved": "Linked PDF saved to:\n{save_pdf_path}",
        "verification_ok": "Verification finished – no issues.",
        "verification_fail": "Verification: {bad} issue(s) found.",
        "error_creating_link": "Error creating link for {ref}: {error}",
        "error": "ERROR",
        "page_selector": "Pages",
        "select_all": "All",
        "deselect_all": "None",
        "calibration_title": "Quick calibration guide",
        "calibration_text": (
            "• Open a reference PDF and click “All”.\n"
            "• Adjust Columns and Rows (QET sheet properties).\n"
            "• Adjust X/Y OffSet and Zoom to move the view."
            "• Adjust the text format."
            "• Click ’Start Scan‘ and check where the links fall.\n"
        ),
        "highlight_links": "Highlight link texts",
        "highlight_rects": "Highlight link border",
        "ref_format": "Reference format:",
        "format_X-YXX": "X-YXX (8-G12)",
        "format_X.YXX": "X.YXX (8.G12)",
        "format_slashX.YXX": "/X.YXX (/8.G12)",
        "cell_width": "Cell width",
        "cell_height": "Cell height",
        "x_margin": "X OffSet",
        "y_margin": "Y OffSet",
        "zoom": "Zoom",
        "verify_no_refs": "No references found. Check the link Format type in the Settings tab.",
        "verify_refs_found": "References found: {count}",
        "choose_color": "Choose color",
        "link_color": "Text color:",
        "rect_color": "Border color:",
        # New texts
        "columns_section": "Column",
        "num_columns": "Number of columns",
        "pixels_columns": "Pixels",
        "rows_section": "Rows",
        "num_rows": "Number of rows",
        "pixels_rows": "Pixels",
        "use_full_page": "Use entire page",
    }
}
lang = texts["es"]

selected_pdf = ""
check_vars = []

def translate():
    app.title(lang["window_title"])
    lbl_pdf.config(text=lang["pdf_select"])
    btn_browse.config(text=lang["browse"])
    tabs.tab(0, text=lang["scan_tab"])
    tabs.tab(1, text=lang["settings_tab"])
    btn_scan.config(text=lang["scan_start"])
    btn_verify.config(text=lang["verify"])
    btn_exit.config(text=lang["exit"])
    lbl_page_selector.config(text=lang["page_selector"])
    # calibración
    txt_calib.config(state="normal")
    txt_calib.delete("1.0", tk.END)
    txt_calib.insert(tk.END, lang["calibration_text"])
    txt_calib.config(state="disabled")
    # opciones de resaltado
    chk_highlight_links.config(text=lang["highlight_links"])
    chk_highlight_rects.config(text=lang["highlight_rects"])
    lbl_ref_format.config(text=lang["ref_format"])
    lbl_link_color.config(text=lang["link_color"])
    lbl_rect_color.config(text=lang["rect_color"])
    # formato de referencia
    combo_ref_format.configure(values=[
        lang["format_X-YXX"],
        lang["format_X.YXX"],
        lang["format_slashX.YXX"]
    ])
    # idioma en configuración
    btn_lang_es.config(text=lang["lang_es"])
    btn_lang_en.config(text=lang["lang_en"])
    # NUEVOS CAMPOS
    lbl_columns.config(text=lang["columns_section"])
    lbl_rows.config(text=lang["rows_section"])
    chk_use_full_page.config(text=lang["use_full_page"])
    lbl_num_columns.config(text=lang["num_columns"])
    lbl_pixels_columns.config(text=lang["pixels_columns"])
    lbl_num_rows.config(text=lang["num_rows"])
    lbl_pixels_rows.config(text=lang["pixels_rows"])

def set_language(code: str):
    global lang
    lang = texts[code]
    translate()

def log(msg: str):
    txt_log.config(state="normal")
    txt_log.insert(tk.END, msg + "\n")
    txt_log.see(tk.END)
    txt_log.config(state="disabled")
    status.set(msg.splitlines()[0])

def choose_color(color_var, button, config_key):
    color = colorchooser.askcolor(title=lang["choose_color"])
    if color[1]:
        color_var.set(color[1])
        button.config(background=color[1])
        config[config_key] = color[1]

def ref_to_coord(ref: str):
    try:
        if config["ref_format"] == "X.YXX":
            page_str, pos_str = ref.split('.')
        elif config["ref_format"] == "/X.YXX":
            page_str, pos_str = ref.lstrip('/').split('.')
        else:
            page_str, pos_str = ref.split('-')
        dest_page = int(page_str) - 1
        row_idx = ord(pos_str[0].upper()) - ord('A')
        if not (0 <= row_idx < config["num_rows"]):
            return None, None
        col_idx = int(pos_str[1:]) - 1
        if not (0 <= col_idx < config["num_columns"]):
            return None, None
        x = config["x_margin"] + col_idx * config["cell_width"]
        y = config["y_margin"] + row_idx * config["cell_height"]
        return dest_page, fitz.Point(x, y)
    except Exception:
        return None, None

def build_page_selector(total_pages: int):
    for w in frame_pages.winfo_children():
        if w not in [lbl_page_selector]:
            w.destroy()
    check_vars.clear()
    for i in range(total_pages):
        var = tk.BooleanVar(value=True)
        chk = Checkbutton(frame_pages, text=f"{i+1:02d}", variable=var,
                          width=3, bootstyle="primary-round-toggle")
        chk.grid(row=1 + i//3, column=i%3, padx=2, pady=2, sticky="w")
        check_vars.append(var)
    final_row = 1 + (total_pages-1)//3 + 1
    frame_btn_sel = Frame(frame_pages)
    frame_btn_sel.grid(row=final_row, column=0, columnspan=6, sticky="w", padx=4, pady=6)
    btn_sel_all = Button(
        frame_btn_sel,
        text=lang["select_all"],
        width=8,
        bootstyle="success",
        command=lambda: select_all(True)
    )
    btn_sel_all.pack(side=LEFT, padx=4)
    btn_des_all = Button(
        frame_btn_sel,
        text=lang["deselect_all"],
        width=8,
        bootstyle="danger",
        command=lambda: select_all(False)
    )
    btn_des_all.pack(side=LEFT, padx=4)

def select_all(state: bool):
    for v in check_vars:
        v.set(state)

def open_pdf():
    global selected_pdf
    path = filedialog.askopenfilename(filetypes=[("PDF files", "*.pdf")])
    if not path:
        return
    selected_pdf = path
    entry_pdf.delete(0, tk.END)
    entry_pdf.insert(0, path)
    log(lang["log_pdf_selected"].format(filepath=path))
    try:
        with fitz.open(path) as doc:
            build_page_selector(len(doc))
            log(lang["pdf_success"].format(filepath=os.path.basename(path), pages=len(doc)))
    except Exception as e:
        messagebox.showerror(lang["error"], str(e))

def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16)/255 for i in (0, 2, 4))

def draw_visual_elements(page, rect, ref):
    if config["highlight_links"]:
        highlight = page.add_highlight_annot(rect)
        rgb = hex_to_rgb(config["link_highlight_color"])
        highlight.set_colors(stroke=rgb)
        highlight.update()
    if config["highlight_rects"]:
        border = page.add_rect_annot(rect)
        rgb = hex_to_rgb(config["rect_highlight_color"])
        border.set_colors(stroke=rgb)
        border.set_border(width=1.5)
        border.update()

def build_ref_pattern():
    """Construye el regex según nº de filas (letras) y columnas"""
    # Permitir letras desde A hasta la letra correspondiente al num_rows
    end_row = chr(ord('A') + config["num_rows"] - 1)
    if config["ref_format"] == "X.YXX":
        return re.compile(rf"\b\d{{1,2}}\.[A-{end_row}a-{end_row.lower()}]\d{{1,2}}\b")
    elif config["ref_format"] == "/X.YXX":
        return re.compile(rf"/\d{{1,2}}\.[A-{end_row}a-{end_row.lower()}]\d{{1,2}}\b")
    else:
        return re.compile(rf"\b\d{{1,2}}-[A-{end_row}a-{end_row.lower()}]\d{{1,2}}\b")

def scan_and_link():
    if not selected_pdf:
        messagebox.showwarning(lang["error"], lang["error_no_pdf"])
        return
    pattern = build_ref_pattern()
    try:
        doc = fitz.open(selected_pdf)
        pages = [i for i, v in enumerate(check_vars) if v.get()]
        progress.configure(maximum=len(pages), value=0)
        refs_found, links_created = 0, 0
        for idx, pnum in enumerate(pages):
            page = doc.load_page(pnum)
            blocks = page.get_text("dict")["blocks"]
            for blk in blocks:
                if blk.get("type") != 0:
                    continue
                for line in blk["lines"]:
                    for span in line["spans"]:
                        for m in pattern.finditer(span["text"]):
                            ref = m.group(0)
                            refs_found += 1
                            dest_page, dest_pt = ref_to_coord(ref)
                            if dest_page is None or dest_page >= len(doc):
                                continue
                            rect = fitz.Rect(span["bbox"])
                            draw_visual_elements(page, rect, ref)
                            try:
                                page.insert_link({
                                    "kind": fitz.LINK_GOTO,
                                    "page": dest_page,
                                    "from": rect,
                                    "to": dest_pt,
                                    "zoom": config["zoom"]
                                })
                                links_created += 1
                            except Exception as e:
                                log(lang["error_creating_link"].format(ref=ref, error=e))
            progress.configure(value=idx + 1)
            app.update_idletasks()
        log(lang["found_refs"].format(count=refs_found))
        log(lang["created_links"].format(count=links_created))
        pdf_out = os.path.splitext(selected_pdf)[0] + "_linked.pdf"
        doc.save(pdf_out)
        log(lang["success_pdf_saved"].format(save_pdf_path=pdf_out))
        doc.close()
    except Exception as e:
        log(f"{lang['error']}: {e}")

def threaded_scan():
    threading.Thread(target=scan_and_link, daemon=True).start()

def verify_pdf():
    if not selected_pdf:
        messagebox.showwarning(lang["error"], lang["error_no_pdf"])
        return
    pattern = build_ref_pattern()
    refs_found = 0
    try:
        with fitz.open(selected_pdf) as doc:
            for page in doc:
                blocks = page.get_text("dict")["blocks"]
                for blk in blocks:
                    if blk.get("type") != 0:
                        continue
                    for line in blk["lines"]:
                        for span in line["spans"]:
                            refs_found += len(pattern.findall(span["text"]))
        if refs_found == 0:
            log(lang["verify_no_refs"])
        else:
            log(lang["verify_refs_found"].format(count=refs_found))
    except Exception as e:
        log(f"{lang['error']}: {e}")

# ──────────────────────────────────────────────────────────────────────────────
# GUI
# ──────────────────────────────────────────────────────────────────────────────
app = Window(themename="darkly", size=(950, 720))
status = tk.StringVar(value="")

# Menú idioma
menubar = tk.Menu(app)
lang_menu = tk.Menu(menubar, tearoff=0)
lang_menu.add_command(label=texts["es"]["lang_es"], command=lambda: set_language("es"))
lang_menu.add_command(label=texts["en"]["lang_en"], command=lambda: set_language("en"))
menubar.add_cascade(label=texts["es"]["menu_language"], menu=lang_menu)
app.config(menu=menubar)

# Superior – selección de PDF
top = Frame(app)
top.pack(fill=X, padx=10, pady=8)
lbl_pdf = Label(top)
lbl_pdf.pack(side=LEFT)
entry_pdf = Entry(top, width=60)
entry_pdf.pack(side=LEFT, padx=5)
btn_browse = Button(top, command=open_pdf)
btn_browse.pack(side=LEFT)

tabs = Notebook(app)
tabs.pack(fill=BOTH, expand=True, padx=10, pady=10)

# ▸ Pestaña Escanear
tab_scan = Frame(tabs)
tabs.add(tab_scan, text=lang["scan_tab"])
pw = Panedwindow(tab_scan, orient=HORIZONTAL)
pw.pack(fill=BOTH, expand=True)
frame_pages = Frame(pw, width=500)
frame_pages.pack_propagate(False)
lbl_page_selector = Label(frame_pages)
lbl_page_selector.grid(row=0, column=0, columnspan=6, sticky="w", padx=4, pady=(4, 1))
pw.add(frame_pages, weight=2)
frame_actions = Frame(pw)
pw.add(frame_actions, weight=1)
btn_scan = Button(frame_actions, bootstyle="success", width=18, command=threaded_scan)
btn_scan.pack(pady=(4, 2))
btn_verify = Button(frame_actions, bootstyle="secondary", width=18, command=verify_pdf)
btn_verify.pack(pady=2)
progress = Progressbar(frame_actions, length=300)
progress.pack(pady=6)
txt_log = tk.Text(frame_actions, height=20, state="disabled", font=("Consolas", 9))
txt_log.pack(fill=BOTH, expand=True, padx=4, pady=(0, 4))

# ▸ Pestaña Configuración
tab_cfg = Frame(tabs)
tabs.add(tab_cfg, text=lang["settings_tab"])

# Panel izquierdo - parámetros
frm_left = Frame(tab_cfg)
frm_left.pack(side=LEFT, fill=BOTH, expand=True, padx=8, pady=4)
frm_cfg = Frame(frm_left)
frm_cfg.pack(fill=X, pady=4)

# Parámetros numéricos
frm_cfg = Frame(frm_left)
frm_cfg.pack(fill=X, pady=4)



# === NUEVA SECCIÓN COLUMNAS/FILAS ===
lbl_columns = Label(frm_cfg, text=lang["columns_section"], font="-weight bold")
lbl_columns.grid(row=0, column=0, columnspan=3, sticky="w", pady=(0, 2))
lbl_num_columns = Label(frm_cfg, text=lang["num_columns"])
lbl_num_columns.grid(row=1, column=0, sticky="e", pady=2)
ent_num_columns = Entry(frm_cfg, width=8)
ent_num_columns.insert(0, config["num_columns"])
ent_num_columns.grid(row=1, column=1, padx=4, pady=2)
lbl_pixels_columns = Label(frm_cfg, text=lang["pixels_columns"])
lbl_pixels_columns.grid(row=1, column=2, sticky="e", pady=2)
ent_pixels_columns = Entry(frm_cfg, width=8)
ent_pixels_columns.insert(0, config["pixels_columns"])
ent_pixels_columns.grid(row=1, column=3, padx=4, pady=2)

lbl_rows = Label(frm_cfg, text=lang["rows_section"], font="-weight bold")
lbl_rows.grid(row=2, column=0, columnspan=3, sticky="w", pady=(8, 2))
lbl_num_rows = Label(frm_cfg, text=lang["num_rows"])
lbl_num_rows.grid(row=3, column=0, sticky="e", pady=2)
ent_num_rows = Entry(frm_cfg, width=8)
ent_num_rows.insert(0, config["num_rows"])
ent_num_rows.grid(row=3, column=1, padx=4, pady=2)
lbl_pixels_rows = Label(frm_cfg, text=lang["pixels_rows"])
lbl_pixels_rows.grid(row=3, column=2, sticky="e", pady=2)
ent_pixels_rows = Entry(frm_cfg, width=8)
ent_pixels_rows.insert(0, config["pixels_rows"])
ent_pixels_rows.grid(row=3, column=3, padx=4, pady=2)

var_use_full_page = tk.BooleanVar(value=config["use_full_page"])
chk_use_full_page = Checkbutton(
    frm_cfg,
    text=lang["use_full_page"],
    variable=var_use_full_page,
    bootstyle="round-toggle"
)
chk_use_full_page.grid(row=4, column=0, columnspan=4, sticky="w", pady=8)

# ===== Campos de OffSet X, OffSet Y y Zoom =====
lbl_offset = Label(frm_cfg, text="")  # Solo separador visual

lbl_offset.grid(row=6, column=0, columnspan=4, sticky="w", pady=(8, 2))

Label(frm_cfg, text=lang["x_margin"]).grid(row=7, column=0, sticky="e", pady=2)
ent_x_margin = Entry(frm_cfg, width=8)
ent_x_margin.insert(0, config["x_margin"])
ent_x_margin.grid(row=7, column=1, padx=4, pady=2)

Label(frm_cfg, text=lang["y_margin"]).grid(row=7, column=2, sticky="e", pady=2)
ent_y_margin = Entry(frm_cfg, width=8)
ent_y_margin.insert(0, config["y_margin"])
ent_y_margin.grid(row=7, column=3, padx=4, pady=2)

Label(frm_cfg, text=lang["zoom"]).grid(row=8, column=0, sticky="e", pady=2)
ent_zoom = Entry(frm_cfg, width=8)
ent_zoom.insert(0, config["zoom"])
ent_zoom.grid(row=8, column=1, padx=4, pady=2)

def apply_cell_settings():
    try:
        config["num_columns"] = int(ent_num_columns.get())
        config["pixels_columns"] = int(ent_pixels_columns.get())
        config["num_rows"] = int(ent_num_rows.get())
        config["pixels_rows"] = int(ent_pixels_rows.get())
        config["use_full_page"] = var_use_full_page.get()
        # Campos OffSet y Zoom
        config["x_margin"] = float(ent_x_margin.get())
        config["y_margin"] = float(ent_y_margin.get())
        config["zoom"] = float(ent_zoom.get())
        factor = 0.582
        divisor = 0.93 if config["use_full_page"] else 1
        config["cell_width"] = config["pixels_columns"] * factor / divisor
        config["cell_height"] = config["pixels_rows"] * factor / divisor
        log(f"Cell size set: {config['num_columns']} cols, {config['cell_width']:.2f} width | {config['num_rows']} rows, {config['cell_height']:.2f} height | Offset X={config['x_margin']} Y={config['y_margin']} Zoom={config['zoom']}")
    except Exception as e:
        messagebox.showwarning(lang["error"], f"Config error: {e}")

# Reubica el botón OK a la fila correcta (por ejemplo, fila=9)
Button(frm_cfg, text="OK", width=8, command=apply_cell_settings).grid(row=9, column=0, columnspan=4, pady=2)


# Opciones de resaltado
frm_visual = Frame(frm_left)
frm_visual.pack(fill=X, pady=4)
frame_links = Frame(frm_visual)
frame_links.pack(fill=X, pady=2)
chk_highlight_links = Checkbutton(
    frame_links,
    text=lang["highlight_links"],
    variable=tk.BooleanVar(value=config["highlight_links"]),
    command=lambda: config.update({"highlight_links": not config["highlight_links"]}),
    bootstyle="primary-round-toggle"
)
chk_highlight_links.pack(side=LEFT)
lbl_link_color = Label(frame_links, text=lang["link_color"])
lbl_link_color.pack(side=LEFT, padx=5)
var_link_color = tk.StringVar(value=config["link_highlight_color"])
btn_link_color = tk.Button(
    frame_links,
    width=3,
    background=config["link_highlight_color"],
    command=lambda: choose_color(var_link_color, btn_link_color, "link_highlight_color")
)
btn_link_color.pack(side=LEFT)
frame_rects = Frame(frm_visual)
frame_rects.pack(fill=X, pady=2)
chk_highlight_rects = Checkbutton(
    frame_rects,
    text=lang["highlight_rects"],
    variable=tk.BooleanVar(value=config["highlight_rects"]),
    command=lambda: config.update({"highlight_rects": not config["highlight_rects"]}),
    bootstyle="primary-round-toggle"
)
chk_highlight_rects.pack(side=LEFT)
lbl_rect_color = Label(frame_rects, text=lang["rect_color"])
lbl_rect_color.pack(side=LEFT, padx=5)
var_rect_color = tk.StringVar(value=config["rect_highlight_color"])
btn_rect_color = tk.Button(
    frame_rects,
    width=3,
    background=config["rect_highlight_color"],
    command=lambda: choose_color(var_rect_color, btn_rect_color, "rect_highlight_color")
)
btn_rect_color.pack(side=LEFT)

frm_ref_format = Frame(frm_left)
frm_ref_format.pack(fill=X, pady=4)
lbl_ref_format = Label(frm_ref_format, text=lang["ref_format"])
lbl_ref_format.pack(anchor="w", pady=2)
combo_ref_format = Combobox(
    frm_ref_format,
    values=[lang["format_X-YXX"], lang["format_X.YXX"], lang["format_slashX.YXX"]],
    state="readonly",
    width=20
)
combo_ref_format.pack(anchor="w", pady=2)
combo_ref_format.set(lang["format_X-YXX"])

def set_ref_format(event):
    selected = combo_ref_format.get()
    if selected == lang["format_X.YXX"]:
        config["ref_format"] = "X.YXX"
    elif selected == lang["format_slashX.YXX"]:
        config["ref_format"] = "/X.YXX"
    else:
        config["ref_format"] = "X-YXX"
combo_ref_format.bind("<<ComboboxSelected>>", set_ref_format)

frm_lang = Frame(frm_left)
frm_lang.pack(fill=X, pady=4)
Label(frm_lang, text=lang["menu_language"]).pack(anchor="w", pady=2)
frame_lang_buttons = Frame(frm_lang)
frame_lang_buttons.pack(anchor="w", pady=2)
btn_lang_es = Button(frame_lang_buttons, text=lang["lang_es"], command=lambda: set_language("es"), width=8)
btn_lang_es.pack(side=LEFT, padx=2)
btn_lang_en = Button(frame_lang_buttons, text=lang["lang_en"], command=lambda: set_language("en"), width=8)
btn_lang_en.pack(side=LEFT, padx=2)

frm_right = Frame(tab_cfg)
frm_right.pack(side=LEFT, fill=BOTH, expand=True, padx=8, pady=4)
Label(frm_right, text=lang["calibration_title"], font="-size 10 -weight bold").pack(anchor="w")
txt_calib = tk.Text(frm_right, height=8, wrap="word", state="disabled")
txt_calib.pack(fill=BOTH, expand=True)

bottom = Frame(app)
bottom.pack(fill=X, padx=4, pady=4)
Label(bottom, textvariable=status).pack(side=LEFT)
btn_exit = Button(bottom, command=app.destroy, width=8)
btn_exit.pack(side=RIGHT)

translate()
app.mainloop()
