import os import tkinter as tk from whisper_app import config as cfg def open(root: tk.Tk) -> None: """Open the vocabulary window as a Toplevel of *root*.""" _open_main(root) def _open_main(root: tk.Tk) -> None: BG = "#18181f" BG2 = "#22222c" BG3 = "#2c2c38" BORDER = "#38384a" FG = "#e8e8f0" FG2 = "#7878a0" AMBER = "#f5a623" AMBER2 = "#c8831a" RED = "#f87171" _mono = "Consolas" if os.name == "nt" else "monospace" _sans = "Segoe UI" if os.name == "nt" else "sans-serif" FONT = (_sans, 11) FONT_B = (_sans, 11, "bold") FONT_S = (_sans, 9) FONT_H = (_sans, 14, "bold") FONT_M = (_mono, 10) win = tk.Toplevel(root) win.title("Vokabular") win.configure(bg=BG) win.attributes("-topmost", True) win.resizable(True, True) win.minsize(600, 480) win.option_add("*Menu.background", BG3) win.option_add("*Menu.foreground", FG) win.option_add("*Menu.activeBackground", AMBER) win.option_add("*Menu.activeForeground", BG) # ── Header ── hdr = tk.Frame(win, bg=BG2) hdr.pack(fill="x") tk.Frame(hdr, bg=AMBER, height=3).pack(fill="x") tk.Label(hdr, text="VOKABULAR & KORREKTUREN", font=FONT_H, bg=BG2, fg=FG, pady=14).pack() tk.Label(hdr, text="Wörter lernen · Ersetzungen definieren", font=FONT_S, bg=BG2, fg=FG2).pack(pady=(0, 10)) content = tk.Frame(win, bg=BG, padx=28, pady=12) content.pack(fill="both", expand=True) # ── Add-word form ───────────────────────────────────────────────────────── is_correction = tk.BooleanVar(value=False) form = tk.Frame(content, bg=BG3, padx=16, pady=14) form.pack(fill="x", pady=(0, 16)) # Toggle row tog_row = tk.Frame(form, bg=BG3) tog_row.pack(fill="x", pady=(0, 10)) tk.Label(tog_row, text="Korrektur (falsch → richtig)", font=FONT, bg=BG3, fg=FG).pack(side="left") def toggle_form(*_): if is_correction.get(): entry_from.pack(side="left", padx=(0, 6)) arrow_lbl.pack(side="left", padx=4) entry_to.pack(side="left") entry_word.pack_forget() else: entry_word.pack(side="left", fill="x", expand=True) entry_from.pack_forget() arrow_lbl.pack_forget() entry_to.pack_forget() tog_btn = tk.Checkbutton(tog_row, variable=is_correction, command=toggle_form, bg=BG3, fg=FG2, activebackground=BG3, selectcolor=AMBER, relief="flat", bd=0, indicatoron=True) tog_btn.pack(side="right") # Input row inp_row = tk.Frame(form, bg=BG3) inp_row.pack(fill="x") entry_style = dict(font=FONT_M, bg=BG, fg=FG, insertbackground=AMBER, relief="flat", bd=6, highlightbackground=BORDER, highlightthickness=1) entry_word = tk.Entry(inp_row, width=32, **entry_style) entry_word.insert(0, "") entry_from = tk.Entry(inp_row, width=14, **entry_style) arrow_lbl = tk.Label(inp_row, text="→", font=("Segoe UI", 14), bg=BG3, fg=AMBER) entry_to = tk.Entry(inp_row, width=14, **entry_style) entry_word.pack(side="left", fill="x", expand=True) def add_entry(): if is_correction.get(): frm = entry_from.get().strip() to = entry_to.get().strip() if frm and to: cfg.vocab["replacements"].append({"from": frm, "to": to}) entry_from.delete(0, tk.END) entry_to.delete(0, tk.END) else: w = entry_word.get().strip() if w and w not in cfg.vocab["words"]: cfg.vocab["words"].append(w) entry_word.delete(0, tk.END) cfg.save_vocab() refresh_lists() win.bind("", lambda _: add_entry()) add_btn = tk.Button(inp_row, text="Hinzufügen", command=add_entry, bg=AMBER, fg=BG, font=FONT_B, relief="flat", padx=14, pady=5, cursor="hand2", bd=0) add_btn.pack(side="right", padx=(10, 0)) add_btn.bind("", lambda _: add_btn.config(bg=AMBER2)) add_btn.bind("", lambda _: add_btn.config(bg=AMBER)) # ── Lists ───────────────────────────────────────────────────────────────── lists_frame = tk.Frame(content, bg=BG) lists_frame.pack(fill="both", expand=True) lists_frame.columnconfigure(0, weight=1) lists_frame.columnconfigure(1, weight=2) def section_label(parent, text): tk.Label(parent, text=text, font=("Consolas", 9, "bold"), bg=BG, fg=AMBER).pack(anchor="w", pady=(0, 6)) # Words column col_w = tk.Frame(lists_frame, bg=BG) col_w.grid(row=0, column=0, sticky="nsew", padx=(0, 12)) section_label(col_w, "WÖRTER") words_box = tk.Listbox(col_w, font=FONT_M, bg=BG3, fg=FG, selectbackground=AMBER, selectforeground=BG, relief="flat", bd=0, highlightthickness=0, activestyle="none", height=10) words_box.pack(fill="both", expand=True) def del_word(): sel = words_box.curselection() if sel: cfg.vocab["words"].pop(sel[0]) cfg.save_vocab() refresh_lists() tk.Button(col_w, text="− Entfernen", command=del_word, bg=BG3, fg=RED, font=FONT_S, relief="flat", padx=8, pady=3, cursor="hand2", bd=0).pack(anchor="e", pady=(4, 0)) # Replacements column col_r = tk.Frame(lists_frame, bg=BG) col_r.grid(row=0, column=1, sticky="nsew") section_label(col_r, "KORREKTUREN") repl_box = tk.Listbox(col_r, font=FONT_M, bg=BG3, fg=FG, selectbackground=AMBER, selectforeground=BG, relief="flat", bd=0, highlightthickness=0, activestyle="none", height=10) repl_box.pack(fill="both", expand=True) def del_repl(): sel = repl_box.curselection() if sel: cfg.vocab["replacements"].pop(sel[0]) cfg.save_vocab() refresh_lists() tk.Button(col_r, text="− Entfernen", command=del_repl, bg=BG3, fg=RED, font=FONT_S, relief="flat", padx=8, pady=3, cursor="hand2", bd=0).pack(anchor="e", pady=(4, 0)) def refresh_lists(): words_box.delete(0, tk.END) for w in cfg.vocab.get("words", []): words_box.insert(tk.END, f" {w}") repl_box.delete(0, tk.END) for r in cfg.vocab.get("replacements", []): repl_box.insert(tk.END, f" {r['from']} → {r['to']}") refresh_lists() # ── Footer ── tk.Frame(win, bg=BORDER, height=1).pack(fill="x") tk.Label(win, text="Wörter fließen als Kontext in Whisper ein · Korrekturen werden nach der Transkription angewendet", font=FONT_S, bg=BG2, fg=FG2, pady=8).pack() # Center on screen after layout win.update_idletasks() sw = win.winfo_screenwidth() sh = win.winfo_screenheight() w = max(win.winfo_reqwidth(), 600) h = min(max(win.winfo_reqheight(), 480), sh - 100) win.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")