2026-03-25 22:03:39 +01:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
AzaSettingsMixin – Einstellungsfenster (KG-Modell, Templates, Autotext, Add-ons, etc.).
|
2026-05-28 18:58:38 +02:00
|
|
|
|
Modernes AzA-Blau-Design, scrollbar, KG-Modell-Auswahl ausgeblendet.
|
2026-03-25 22:03:39 +01:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
from tkinter import ttk, messagebox
|
|
|
|
|
|
from tkinter.scrolledtext import ScrolledText
|
|
|
|
|
|
|
|
|
|
|
|
from aza_audit_log import log_event as _audit_log
|
|
|
|
|
|
from aza_persistence import (
|
|
|
|
|
|
load_settings_geometry,
|
|
|
|
|
|
save_settings_geometry,
|
|
|
|
|
|
load_templates_text,
|
|
|
|
|
|
save_templates_text,
|
|
|
|
|
|
save_autotext,
|
|
|
|
|
|
save_model,
|
|
|
|
|
|
_clamp_geometry_str,
|
|
|
|
|
|
load_signature_name,
|
|
|
|
|
|
save_signature_name,
|
|
|
|
|
|
load_user_profile,
|
|
|
|
|
|
)
|
|
|
|
|
|
from aza_ui_helpers import add_resize_grip, add_font_scale_control
|
|
|
|
|
|
from aza_config import MODEL_LABELS, ALLOWED_SUMMARY_MODELS
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# ── Design-Konstanten ─────────────────────────────────────────────────────────
|
|
|
|
|
|
_WIN_BG = "#EEF4F8"
|
|
|
|
|
|
_HDR_BG = "#1A4D6D"
|
|
|
|
|
|
_HDR_FG = "#FFFFFF"
|
|
|
|
|
|
_HDR_SUB = "#A0C0DC"
|
|
|
|
|
|
_CARD_BG = "#FFFFFF"
|
|
|
|
|
|
_CARD_BD = "#C8D8E8"
|
|
|
|
|
|
_TEXT = "#1A3D55"
|
|
|
|
|
|
_TEXT_SUB = "#607890"
|
|
|
|
|
|
_BTN_PRI = "#1A8ACC"
|
|
|
|
|
|
_BTN_PRI_F = "#FFFFFF"
|
|
|
|
|
|
_BTN_SEC = "#D4E7F5"
|
|
|
|
|
|
_BTN_SEC_F = "#1A4D6D"
|
|
|
|
|
|
_FF = "Segoe UI"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _darken(hex_color: str, amount: int = 18) -> str:
|
|
|
|
|
|
try:
|
|
|
|
|
|
r = max(0, int(hex_color[1:3], 16) - amount)
|
|
|
|
|
|
g = max(0, int(hex_color[3:5], 16) - amount)
|
|
|
|
|
|
b = max(0, int(hex_color[5:7], 16) - amount)
|
|
|
|
|
|
return f"#{r:02X}{g:02X}{b:02X}"
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return hex_color
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_btn(parent, text: str, command, primary: bool = False, **kw):
|
|
|
|
|
|
bg = _BTN_PRI if primary else _BTN_SEC
|
|
|
|
|
|
fg = _BTN_PRI_F if primary else _BTN_SEC_F
|
|
|
|
|
|
b = tk.Button(
|
|
|
|
|
|
parent, text=text, command=command,
|
|
|
|
|
|
bg=bg, fg=fg, activebackground=_darken(bg),
|
|
|
|
|
|
activeforeground=fg, font=(_FF, 9),
|
|
|
|
|
|
bd=0, padx=14, pady=6, cursor="hand2", relief="flat", **kw,
|
|
|
|
|
|
)
|
|
|
|
|
|
b.bind("<Enter>", lambda e, _b=b, _bg=bg: _b.configure(bg=_darken(_bg)))
|
|
|
|
|
|
b.bind("<Leave>", lambda e, _b=b, _bg=bg: _b.configure(bg=_bg))
|
|
|
|
|
|
return b
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_cb(parent, text: str, variable, command=None, card_bg: str = _CARD_BG):
|
|
|
|
|
|
kw: dict = {}
|
|
|
|
|
|
if command:
|
|
|
|
|
|
kw["command"] = command
|
|
|
|
|
|
return tk.Checkbutton(
|
|
|
|
|
|
parent, text=text, variable=variable,
|
|
|
|
|
|
bg=card_bg, fg=_TEXT, activebackground=card_bg,
|
|
|
|
|
|
activeforeground=_TEXT, selectcolor=card_bg,
|
|
|
|
|
|
font=(_FF, 9), anchor="w", **kw,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
class AzaSettingsMixin:
|
|
|
|
|
|
"""Mixin für das Einstellungsfenster."""
|
|
|
|
|
|
|
|
|
|
|
|
def _open_settings(self):
|
2026-05-28 18:58:38 +02:00
|
|
|
|
SETTINGS_MIN_W, SETTINGS_MIN_H = 720, 720
|
|
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
win = tk.Toplevel(self)
|
|
|
|
|
|
win.title("Einstellungen")
|
|
|
|
|
|
win.transient(self)
|
|
|
|
|
|
win.attributes("-topmost", True)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
win.configure(bg=_WIN_BG)
|
|
|
|
|
|
win.minsize(SETTINGS_MIN_W, SETTINGS_MIN_H)
|
|
|
|
|
|
try:
|
|
|
|
|
|
win.attributes("-alpha", 0.0)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2026-03-25 22:03:39 +01:00
|
|
|
|
if hasattr(self, "_aza_windows"):
|
|
|
|
|
|
self._aza_windows.add(win)
|
|
|
|
|
|
self._register_window(win)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
saved_geom = load_settings_geometry()
|
|
|
|
|
|
if saved_geom:
|
|
|
|
|
|
try:
|
2026-05-28 18:58:38 +02:00
|
|
|
|
clamped = _clamp_geometry_str(saved_geom, SETTINGS_MIN_W, SETTINGS_MIN_H)
|
|
|
|
|
|
win.geometry(clamped)
|
|
|
|
|
|
# y-Position immer auf oben-mittig korrigieren,
|
|
|
|
|
|
# damit das Fenster nicht ausserhalb oder tief unten erscheint.
|
|
|
|
|
|
win.update_idletasks()
|
|
|
|
|
|
sw = win.winfo_screenwidth()
|
|
|
|
|
|
w = win.winfo_width()
|
|
|
|
|
|
x = max(0, (sw - w) // 2)
|
|
|
|
|
|
win.geometry(f"+{x}+40")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
except Exception:
|
|
|
|
|
|
win.geometry(f"{SETTINGS_MIN_W}x{SETTINGS_MIN_H}")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
else:
|
2026-03-25 22:03:39 +01:00
|
|
|
|
win.update_idletasks()
|
2026-05-28 18:58:38 +02:00
|
|
|
|
sw, sh = win.winfo_screenwidth(), win.winfo_screenheight()
|
|
|
|
|
|
x = max(0, (sw - SETTINGS_MIN_W) // 2)
|
|
|
|
|
|
y = 40 # oben-mittig: horizontal zentriert, nah am oberen Rand
|
|
|
|
|
|
win.geometry(f"{SETTINGS_MIN_W}x{SETTINGS_MIN_H}+{x}+{y}")
|
|
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
add_font_scale_control(win)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
|
|
# ── Header ────────────────────────────────────────────────────────────
|
|
|
|
|
|
hdr = tk.Frame(win, bg=_HDR_BG)
|
|
|
|
|
|
hdr.pack(fill="x")
|
|
|
|
|
|
tk.Label(hdr, text="Einstellungen", bg=_HDR_BG, fg=_HDR_FG,
|
|
|
|
|
|
font=(_FF, 13, "bold"), pady=14, padx=20).pack(anchor="w")
|
|
|
|
|
|
|
|
|
|
|
|
sep = tk.Frame(win, bg="#C8D8E8", height=1)
|
|
|
|
|
|
sep.pack(fill="x")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Scrollable body ───────────────────────────────────────────────────
|
|
|
|
|
|
body = tk.Frame(win, bg=_WIN_BG)
|
|
|
|
|
|
body.pack(fill="both", expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
vbar = tk.Scrollbar(body, orient="vertical")
|
|
|
|
|
|
vbar.pack(side="right", fill="y")
|
|
|
|
|
|
|
|
|
|
|
|
canvas = tk.Canvas(body, bg=_WIN_BG, highlightthickness=0, bd=0,
|
|
|
|
|
|
yscrollcommand=vbar.set)
|
|
|
|
|
|
canvas.pack(side="left", fill="both", expand=True)
|
|
|
|
|
|
vbar.configure(command=canvas.yview)
|
|
|
|
|
|
|
|
|
|
|
|
inner = tk.Frame(canvas, bg=_WIN_BG)
|
|
|
|
|
|
cwin_id = canvas.create_window((0, 0), window=inner, anchor="nw")
|
|
|
|
|
|
|
|
|
|
|
|
def _on_inner_resize(event):
|
|
|
|
|
|
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
|
|
|
|
|
|
|
|
|
|
def _on_canvas_resize(event):
|
|
|
|
|
|
canvas.itemconfigure(cwin_id, width=event.width)
|
|
|
|
|
|
|
|
|
|
|
|
inner.bind("<Configure>", _on_inner_resize)
|
|
|
|
|
|
canvas.bind("<Configure>", _on_canvas_resize)
|
|
|
|
|
|
|
|
|
|
|
|
def _scroll(event):
|
|
|
|
|
|
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
|
|
|
|
|
|
|
|
|
|
|
win.bind_all("<MouseWheel>", _scroll)
|
|
|
|
|
|
|
|
|
|
|
|
# Content area inside scrollable frame
|
|
|
|
|
|
pad = tk.Frame(inner, bg=_WIN_BG, padx=20, pady=16)
|
|
|
|
|
|
pad.pack(fill="both", expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Card builder ──────────────────────────────────────────────────────
|
|
|
|
|
|
def _card(title: str = "") -> tk.Frame:
|
|
|
|
|
|
outer = tk.Frame(pad, bg=_WIN_BG)
|
|
|
|
|
|
outer.pack(fill="x", pady=(0, 10))
|
|
|
|
|
|
if title:
|
|
|
|
|
|
tk.Label(outer, text=title.upper(), bg=_WIN_BG, fg=_TEXT_SUB,
|
|
|
|
|
|
font=(_FF, 8, "bold")).pack(anchor="w", padx=2, pady=(0, 4))
|
|
|
|
|
|
c = tk.Frame(outer, bg=_CARD_BG,
|
|
|
|
|
|
highlightbackground=_CARD_BD, highlightthickness=1,
|
|
|
|
|
|
padx=14, pady=10)
|
|
|
|
|
|
c.pack(fill="x")
|
|
|
|
|
|
return c
|
|
|
|
|
|
|
|
|
|
|
|
# ── KG-Modell (hidden – variable kept for on_ok compatibility) ────────
|
2026-03-25 22:03:39 +01:00
|
|
|
|
display_values = [MODEL_LABELS[m] for m in ALLOWED_SUMMARY_MODELS]
|
2026-05-28 18:58:38 +02:00
|
|
|
|
current_model_label = MODEL_LABELS.get(self.model_var.get(), display_values[0])
|
|
|
|
|
|
model_var_dialog = tk.StringVar(value=current_model_label)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Templates + Reset ─────────────────────────────────────────────────
|
|
|
|
|
|
c_tmpl = _card("Vorlagen")
|
|
|
|
|
|
row_tmpl = tk.Frame(c_tmpl, bg=_CARD_BG)
|
|
|
|
|
|
row_tmpl.pack(anchor="w")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
def open_templates():
|
|
|
|
|
|
tw = tk.Toplevel(win)
|
|
|
|
|
|
tw.title("Templates")
|
|
|
|
|
|
tw.transient(win)
|
|
|
|
|
|
tw.geometry("620x370")
|
|
|
|
|
|
tw.configure(bg="#B9ECFA")
|
|
|
|
|
|
tw.minsize(450, 280)
|
|
|
|
|
|
tw.attributes("-topmost", True)
|
|
|
|
|
|
self._register_window(tw)
|
|
|
|
|
|
add_resize_grip(tw, 450, 280)
|
|
|
|
|
|
add_font_scale_control(tw)
|
|
|
|
|
|
tf = ttk.Frame(tw, padding=12)
|
|
|
|
|
|
tf.pack(fill="both", expand=True)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
ttk.Label(tf, text=(
|
|
|
|
|
|
"Kontext fuer die KI (z. B. Ich bin ein Dermatologe). "
|
|
|
|
|
|
"Wird bei der KG-Erstellung beruecksichtigt:"
|
|
|
|
|
|
)).pack(anchor="w")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
ttxt = ScrolledText(tf, wrap="word", font=self._text_font, bg="#F5FCFF", height=8)
|
|
|
|
|
|
ttxt.pack(fill="both", expand=True, pady=(4, 8))
|
|
|
|
|
|
ttxt.insert("1.0", load_templates_text())
|
|
|
|
|
|
self._bind_autotext(ttxt)
|
|
|
|
|
|
btn_f = ttk.Frame(tf)
|
|
|
|
|
|
btn_f.pack(fill="x")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
def _save_close():
|
2026-03-25 22:03:39 +01:00
|
|
|
|
save_templates_text(ttxt.get("1.0", "end").strip())
|
|
|
|
|
|
tw.destroy()
|
2026-05-28 18:58:38 +02:00
|
|
|
|
ttk.Button(btn_f, text="OK", command=_save_close).pack(side="left", padx=(0, 8))
|
2026-03-25 22:03:39 +01:00
|
|
|
|
ttk.Button(btn_f, text="Abbrechen", command=tw.destroy).pack(side="left")
|
|
|
|
|
|
|
|
|
|
|
|
def do_reset():
|
|
|
|
|
|
save_templates_text("")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
messagebox.showinfo("Reset", "Template-Text wurde zurückgesetzt und ist jetzt leer.",
|
|
|
|
|
|
parent=win)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_btn(row_tmpl, "Templates", open_templates).pack(side="left", padx=(0, 8))
|
|
|
|
|
|
_make_btn(row_tmpl, "Reset", do_reset).pack(side="left")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# ── Startverhalten / Fenster ──────────────────────────────────────────
|
|
|
|
|
|
c_start = _card("Startverhalten / Fenster")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
diktat_auto_var = tk.BooleanVar(value=self._autotext_data.get("diktat_auto_start", True))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_start, "Diktat startet sofort (wenn aus: Aufnahme manuell starten)",
|
|
|
|
|
|
diktat_auto_var).pack(anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
notizen_open_var = tk.BooleanVar(value=self._autotext_data.get("notizen_open_on_start", True))
|
|
|
|
|
|
_make_cb(c_start, "Audionotiz beim Programmstart automatisch öffnen",
|
|
|
|
|
|
notizen_open_var).pack(anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
kommentare_auto_var = tk.BooleanVar(value=self._autotext_data.get("kommentare_auto_open", False))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_start, "Kommentare-Fenster beim Programmstart automatisch öffnen",
|
|
|
|
|
|
kommentare_auto_var).pack(anchor="w", pady=2)
|
2026-04-19 20:41:37 +02:00
|
|
|
|
|
|
|
|
|
|
empfang_auto_var = tk.BooleanVar(value=self._autotext_data.get("empfang_auto_open", False))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_start, "Empfang-Fenster beim Programmstart automatisch öffnen",
|
|
|
|
|
|
empfang_auto_var).pack(anchor="w", pady=2)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Darstellung ───────────────────────────────────────────────────────
|
|
|
|
|
|
c_display = _card("Darstellung")
|
|
|
|
|
|
|
|
|
|
|
|
def _live_textbloecke_visible(*_):
|
2026-03-25 22:03:39 +01:00
|
|
|
|
vis = bool(textbloecke_visible_var.get())
|
|
|
|
|
|
self._autotext_data["textbloecke_visible"] = vis
|
|
|
|
|
|
try:
|
|
|
|
|
|
if vis:
|
|
|
|
|
|
self._textbloecke_container.pack(fill="x", before=self._textbloecke_anchor)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._textbloecke_container.pack_forget()
|
|
|
|
|
|
self.update_idletasks()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
textbloecke_visible_var = tk.BooleanVar(
|
|
|
|
|
|
value=self._autotext_data.get("textbloecke_visible", True))
|
|
|
|
|
|
_make_cb(c_display,
|
|
|
|
|
|
"Textblöcke anzeigen (Inhalt bleibt gespeichert, wenn ausgeblendet)",
|
|
|
|
|
|
textbloecke_visible_var, command=_live_textbloecke_visible).pack(anchor="w", pady=2)
|
|
|
|
|
|
|
|
|
|
|
|
def _live_addon_visible(*_):
|
2026-03-25 22:03:39 +01:00
|
|
|
|
vis = bool(addon_visible_var.get())
|
|
|
|
|
|
self._autotext_data["addon_visible"] = vis
|
|
|
|
|
|
try:
|
|
|
|
|
|
if vis:
|
|
|
|
|
|
self._addon_container.pack(fill="x", before=self._addon_anchor)
|
|
|
|
|
|
self._update_addon_buttons_visibility()
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._addon_container.pack_forget()
|
|
|
|
|
|
self.update_idletasks()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
addon_visible_var = tk.BooleanVar(value=self._autotext_data.get("addon_visible", True))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_display, "Add-ons anzeigen",
|
|
|
|
|
|
addon_visible_var, command=_live_addon_visible).pack(anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
def _live_logo_visible(*_):
|
2026-03-25 22:03:39 +01:00
|
|
|
|
vis = bool(logo_visible_var.get())
|
|
|
|
|
|
self._autotext_data["logo_visible"] = vis
|
|
|
|
|
|
try:
|
|
|
|
|
|
if vis:
|
|
|
|
|
|
self._logo_frame.place(relx=0.01, rely=0.97, anchor="sw")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._logo_frame.place_forget()
|
|
|
|
|
|
self.update_idletasks()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
logo_visible_var = tk.BooleanVar(value=self._autotext_data.get("logo_visible", True))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_display, "Logo anzeigen (Klick auf Logo startet/stoppt Aufnahme)",
|
|
|
|
|
|
logo_visible_var, command=_live_logo_visible).pack(anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# ── Add-on-Buttons ────────────────────────────────────────────────────
|
|
|
|
|
|
c_addon = _card("Welche Add-on-Buttons anzeigen?")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
addon_buttons = self._autotext_data.get("addon_buttons", {})
|
2026-05-28 18:58:38 +02:00
|
|
|
|
addon_button_vars: dict = {}
|
2026-03-25 22:03:39 +01:00
|
|
|
|
addon_button_options = [
|
|
|
|
|
|
("uebersetzer", "Übersetzer (provisorisch)"),
|
|
|
|
|
|
("email", "E-Mail"),
|
|
|
|
|
|
("autotext", "Autotext"),
|
|
|
|
|
|
("whatsapp", "WhatsApp"),
|
|
|
|
|
|
("docapp", "MedWork"),
|
|
|
|
|
|
("todo", "To-do"),
|
|
|
|
|
|
("macro", "Makro starten"),
|
|
|
|
|
|
("kongresse", "Kongresse"),
|
|
|
|
|
|
("news", "News"),
|
2026-04-19 20:41:37 +02:00
|
|
|
|
("empfang", "An Empfang senden"),
|
2026-03-25 22:03:39 +01:00
|
|
|
|
]
|
|
|
|
|
|
todo_auto_open_var = tk.BooleanVar(
|
|
|
|
|
|
value=self._autotext_data.get("todo_auto_open", True))
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
def _live_addon_toggle(*_):
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self._autotext_data["addon_buttons"] = {
|
|
|
|
|
|
bid: bool(v.get()) for bid, v in addon_button_vars.items()
|
|
|
|
|
|
}
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._update_addon_buttons_visibility()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
for button_id, label in addon_button_options:
|
|
|
|
|
|
var = tk.BooleanVar(value=addon_buttons.get(button_id, True))
|
|
|
|
|
|
addon_button_vars[button_id] = var
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_addon, label, var, command=_live_addon_toggle).pack(
|
|
|
|
|
|
anchor="w", padx=(0, 0), pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
if button_id == "todo":
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_addon, " ↳ To-do beim Start automatisch öffnen",
|
|
|
|
|
|
todo_auto_open_var).pack(anchor="w", pady=(0, 2))
|
|
|
|
|
|
|
|
|
|
|
|
# ── Diverses ──────────────────────────────────────────────────────────
|
|
|
|
|
|
c_misc = _card("Diverses")
|
|
|
|
|
|
|
|
|
|
|
|
kg_auto_delete_var = tk.BooleanVar(
|
|
|
|
|
|
value=self._autotext_data.get("kg_auto_delete_old", False))
|
|
|
|
|
|
_make_cb(c_misc, "KG-Einträge älter als 2 Wochen automatisch löschen (Speicher schonen)",
|
|
|
|
|
|
kg_auto_delete_var).pack(anchor="w", pady=2)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Statusanzeige ─────────────────────────────────────────────────────
|
|
|
|
|
|
c_status = _card("Statusanzeige")
|
|
|
|
|
|
_status_color_options = {
|
|
|
|
|
|
"Standard (Orange)": "#BD4500",
|
|
|
|
|
|
"Blau": "#1a4d6d",
|
|
|
|
|
|
"Ausblenden": "hidden",
|
|
|
|
|
|
}
|
2026-03-25 22:03:39 +01:00
|
|
|
|
_current_sc = self._autotext_data.get("status_color", "#BD4500")
|
|
|
|
|
|
_sc_label = "Standard (Orange)"
|
|
|
|
|
|
for _lbl, _val in _status_color_options.items():
|
|
|
|
|
|
if _val == _current_sc:
|
|
|
|
|
|
_sc_label = _lbl
|
|
|
|
|
|
break
|
|
|
|
|
|
status_color_var = tk.StringVar(value=_sc_label)
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
def _live_status_color(*_):
|
|
|
|
|
|
sc_v = _status_color_options.get(status_color_var.get(), "#BD4500")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self._autotext_data["status_color"] = sc_v
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._apply_status_color()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
row_sc = tk.Frame(c_status, bg=_CARD_BG)
|
|
|
|
|
|
row_sc.pack(anchor="w")
|
|
|
|
|
|
for sc_label in _status_color_options:
|
|
|
|
|
|
tk.Radiobutton(
|
|
|
|
|
|
row_sc, text=sc_label, variable=status_color_var,
|
|
|
|
|
|
value=sc_label, command=_live_status_color,
|
|
|
|
|
|
bg=_CARD_BG, fg=_TEXT, activebackground=_CARD_BG,
|
|
|
|
|
|
selectcolor=_CARD_BG, font=(_FF, 9),
|
|
|
|
|
|
).pack(side="left", padx=(0, 12))
|
|
|
|
|
|
|
|
|
|
|
|
# ── Autotext ──────────────────────────────────────────────────────────
|
|
|
|
|
|
c_at = _card("Autotext")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
autotext_var = tk.BooleanVar(value=self._autotext_data.get("enabled", True))
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_at,
|
|
|
|
|
|
"Autotext aktiv (Abkuerzungen z. B. mfg -> mit freundlichen Gruessen)",
|
|
|
|
|
|
autotext_var).pack(anchor="w", pady=(0, 6))
|
|
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
def open_autotext_manage():
|
|
|
|
|
|
self._open_autotext_dialog(win)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
|
|
_make_btn(c_at, "Autotext verwalten", open_autotext_manage).pack(anchor="w")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Tastatur / Eingabe ────────────────────────────────────────────────
|
|
|
|
|
|
c_input = _card("Tastatur / Eingabe")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
autocopy_var = tk.BooleanVar(
|
2026-05-28 18:58:38 +02:00
|
|
|
|
value=self._autotext_data.get("autocopy_after_diktat", True))
|
|
|
|
|
|
_make_cb(c_input,
|
|
|
|
|
|
"Autocopy: Nach Diktat/Transkription automatisch in Zwischenablage kopieren",
|
|
|
|
|
|
autocopy_var).pack(anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
if not hasattr(self, "_rclick_paste_var"):
|
|
|
|
|
|
self._rclick_paste_var = tk.BooleanVar(
|
2026-05-28 18:58:38 +02:00
|
|
|
|
value=bool(self._autotext_data.get("global_right_click_paste", False)))
|
|
|
|
|
|
_make_cb(c_input,
|
|
|
|
|
|
"Global: Rechtsklick fügt direkt ein (ohne Kontextmenü, nur externe Apps)",
|
|
|
|
|
|
self._rclick_paste_var, command=self._toggle_rclick_paste).pack(
|
|
|
|
|
|
anchor="w", pady=2)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# ── Unterschrift / Signatur ───────────────────────────────────────────
|
|
|
|
|
|
c_sig = _card("Unterschrift / Signatur")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
profile_name = self._user_profile.get("name", "")
|
|
|
|
|
|
current_sig = load_signature_name(fallback_to_profile=False)
|
|
|
|
|
|
use_profile = not bool(current_sig)
|
|
|
|
|
|
|
|
|
|
|
|
sig_auto_var = tk.BooleanVar(value=use_profile)
|
|
|
|
|
|
sig_name_var = tk.StringVar(value=current_sig if current_sig else profile_name)
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_cb(c_sig,
|
|
|
|
|
|
f"Profilname verwenden: {profile_name}" if profile_name else "Profilname verwenden",
|
|
|
|
|
|
sig_auto_var).pack(anchor="w", pady=(0, 4))
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
row_sig = tk.Frame(c_sig, bg=_CARD_BG)
|
|
|
|
|
|
row_sig.pack(anchor="w", fill="x")
|
|
|
|
|
|
tk.Label(row_sig, text="Abweichender Name:", bg=_CARD_BG, fg=_TEXT,
|
|
|
|
|
|
font=(_FF, 9)).pack(side="left", padx=(0, 8))
|
|
|
|
|
|
ent_sig = tk.Entry(row_sig, textvariable=sig_name_var, width=34,
|
|
|
|
|
|
font=(_FF, 9), bg="#F5FAFF", relief="flat",
|
|
|
|
|
|
highlightbackground=_CARD_BD, highlightthickness=1)
|
|
|
|
|
|
ent_sig.pack(side="left")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
def _update_sig_entry(*_):
|
|
|
|
|
|
if sig_auto_var.get():
|
|
|
|
|
|
ent_sig.configure(state="disabled")
|
|
|
|
|
|
sig_name_var.set(profile_name)
|
|
|
|
|
|
else:
|
|
|
|
|
|
ent_sig.configure(state="normal")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
sig_auto_var.trace_add("write", _update_sig_entry)
|
|
|
|
|
|
_update_sig_entry()
|
|
|
|
|
|
|
|
|
|
|
|
self._sig_auto_var = sig_auto_var
|
|
|
|
|
|
self._sig_name_var = sig_name_var
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# ── Audio / Mikrofon ──────────────────────────────────────────────────
|
|
|
|
|
|
c_audio = _card("Audio / Mikrofon")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
audio_status_var = tk.StringVar(value="")
|
|
|
|
|
|
|
|
|
|
|
|
def _run_audio_test():
|
|
|
|
|
|
audio_status_var.set("Test läuft …")
|
|
|
|
|
|
win.update_idletasks()
|
|
|
|
|
|
try:
|
|
|
|
|
|
from aza_audio import test_audio_device
|
|
|
|
|
|
result = test_audio_device(duration_sec=1.5)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
audio_status_var.set(
|
|
|
|
|
|
("✓ " if result["ok"] else "✗ ") + result["message"]
|
|
|
|
|
|
)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
audio_status_var.set(f"✗ Fehler: {exc}")
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
row_audio = tk.Frame(c_audio, bg=_CARD_BG)
|
|
|
|
|
|
row_audio.pack(fill="x")
|
|
|
|
|
|
_make_btn(row_audio, "Audio-Test starten", _run_audio_test).pack(
|
|
|
|
|
|
side="left", padx=(0, 12))
|
|
|
|
|
|
tk.Label(row_audio, textvariable=audio_status_var, bg=_CARD_BG, fg=_TEXT,
|
|
|
|
|
|
font=(_FF, 9), wraplength=400, justify="left").pack(side="left")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Datenschutz & Recht ───────────────────────────────────────────────
|
|
|
|
|
|
c_legal = _card("Datenschutz & Recht")
|
|
|
|
|
|
|
|
|
|
|
|
row_legal1 = tk.Frame(c_legal, bg=_CARD_BG)
|
|
|
|
|
|
row_legal1.pack(anchor="w", pady=(0, 6))
|
|
|
|
|
|
_make_btn(row_legal1, "Datenschutzerklärung anzeigen",
|
|
|
|
|
|
lambda: self._show_legal_text(win, "Datenschutzerklärung",
|
|
|
|
|
|
"privacy_policy.md")
|
|
|
|
|
|
).pack(side="left", padx=(0, 8))
|
|
|
|
|
|
_make_btn(row_legal1, "KI-Einwilligung anzeigen",
|
|
|
|
|
|
lambda: self._show_legal_text(win, "KI-Einwilligung",
|
|
|
|
|
|
"ai_consent.md")
|
|
|
|
|
|
).pack(side="left")
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
from aza_consent import get_consent_status, record_revoke, has_valid_consent, record_consent, export_consent_log
|
|
|
|
|
|
uid = self._user_profile.get("name", "default")
|
|
|
|
|
|
consent_ok = has_valid_consent(uid)
|
|
|
|
|
|
consent_status_var = tk.StringVar(
|
|
|
|
|
|
value=f"KI-Einwilligung: {'Erteilt' if consent_ok else 'Nicht erteilt / widerrufen'}")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
tk.Label(c_legal, textvariable=consent_status_var, bg=_CARD_BG, fg=_TEXT,
|
|
|
|
|
|
font=(_FF, 9)).pack(anchor="w", pady=(0, 4))
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
def toggle_consent():
|
|
|
|
|
|
nonlocal consent_ok
|
|
|
|
|
|
_uid = self._user_profile.get("name", "default")
|
|
|
|
|
|
if has_valid_consent(_uid):
|
2026-05-28 18:58:38 +02:00
|
|
|
|
if messagebox.askyesno(
|
|
|
|
|
|
"Einwilligung widerrufen",
|
2026-03-25 22:03:39 +01:00
|
|
|
|
"Möchten Sie Ihre KI-Einwilligung widerrufen?\n\n"
|
|
|
|
|
|
"KI-Funktionen (Transkription, KG-Erstellung,\n"
|
|
|
|
|
|
"Interaktionsprüfung) werden danach gesperrt.",
|
|
|
|
|
|
parent=win):
|
|
|
|
|
|
record_revoke(_uid, source="ui")
|
|
|
|
|
|
_audit_log("CONSENT_REVOKE", _uid)
|
|
|
|
|
|
consent_ok = False
|
|
|
|
|
|
consent_status_var.set("KI-Einwilligung: Widerrufen")
|
|
|
|
|
|
btn_consent.configure(text="KI-Einwilligung erteilen")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
messagebox.showinfo("Widerruf",
|
|
|
|
|
|
"Ihre KI-Einwilligung wurde widerrufen und protokolliert.",
|
|
|
|
|
|
parent=win)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
else:
|
|
|
|
|
|
if self._check_ai_consent():
|
|
|
|
|
|
consent_ok = True
|
|
|
|
|
|
consent_status_var.set("KI-Einwilligung: Erteilt")
|
|
|
|
|
|
btn_consent.configure(text="KI-Einwilligung widerrufen")
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
row_legal2 = tk.Frame(c_legal, bg=_CARD_BG)
|
|
|
|
|
|
row_legal2.pack(anchor="w")
|
|
|
|
|
|
btn_consent = _make_btn(
|
|
|
|
|
|
row_legal2,
|
|
|
|
|
|
"KI-Einwilligung widerrufen" if consent_ok else "KI-Einwilligung erteilen",
|
|
|
|
|
|
toggle_consent,
|
|
|
|
|
|
)
|
|
|
|
|
|
btn_consent.pack(side="left", padx=(0, 8))
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
def do_export():
|
|
|
|
|
|
from aza_audit_log import export_audit_log
|
|
|
|
|
|
try:
|
|
|
|
|
|
path_consent = export_consent_log()
|
|
|
|
|
|
path_audit = export_audit_log()
|
|
|
|
|
|
_audit_log("EXPORT", uid, detail="consent+audit log")
|
2026-05-28 18:58:38 +02:00
|
|
|
|
messagebox.showinfo(
|
|
|
|
|
|
"Export",
|
2026-03-25 22:03:39 +01:00
|
|
|
|
f"Consent-Log exportiert:\n{path_consent}\n\n"
|
2026-05-28 18:58:38 +02:00
|
|
|
|
f"Audit-Log exportiert:\n{path_audit}",
|
|
|
|
|
|
parent=win,
|
|
|
|
|
|
)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
messagebox.showerror("Export-Fehler", str(e), parent=win)
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_btn(row_legal2, "Logs exportieren (Audit)", do_export).pack(side="left")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Updates ───────────────────────────────────────────────────────────
|
|
|
|
|
|
c_upd = _card("Updates")
|
|
|
|
|
|
|
|
|
|
|
|
from aza_update_core import get_auto_update_prompt_enabled, set_auto_update_prompt_enabled
|
|
|
|
|
|
auto_upd_var = tk.BooleanVar(value=get_auto_update_prompt_enabled())
|
|
|
|
|
|
|
|
|
|
|
|
def _save_auto_upd(*_):
|
|
|
|
|
|
set_auto_update_prompt_enabled(bool(auto_upd_var.get()))
|
|
|
|
|
|
|
|
|
|
|
|
_make_cb(c_upd, "Automatisch nach Updates fragen (beim Start)",
|
|
|
|
|
|
auto_upd_var, command=_save_auto_upd).pack(anchor="w", pady=(0, 6))
|
|
|
|
|
|
tk.Label(c_upd,
|
|
|
|
|
|
text="Wenn deaktiviert, koennen Sie hier manuell nach Updates suchen.",
|
|
|
|
|
|
bg=_CARD_BG, fg=_TEXT_SUB, font=(_FF, 8)).pack(anchor="w", pady=(0, 6))
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
def _manual_check():
|
|
|
|
|
|
try:
|
|
|
|
|
|
from aza_updater import check_updates_interactive
|
|
|
|
|
|
check_updates_interactive(parent=win)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
from tkinter import messagebox as _mb
|
|
|
|
|
|
_mb.showerror("Fehler", str(exc), parent=win)
|
|
|
|
|
|
|
|
|
|
|
|
_make_btn(c_upd, "Updates suchen", _manual_check, primary=True).pack(anchor="w")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Spacing below last card ───────────────────────────────────────────
|
|
|
|
|
|
tk.Frame(pad, bg=_WIN_BG, height=8).pack()
|
|
|
|
|
|
|
|
|
|
|
|
# ── Footer with OK button (outside scroll) ────────────────────────────
|
|
|
|
|
|
footer_sep = tk.Frame(win, bg="#C8D8E8", height=1)
|
|
|
|
|
|
footer_sep.pack(fill="x", side="bottom")
|
|
|
|
|
|
footer = tk.Frame(win, bg=_WIN_BG, pady=10, padx=20)
|
|
|
|
|
|
footer.pack(fill="x", side="bottom")
|
|
|
|
|
|
|
|
|
|
|
|
# ── Save / close helpers ──────────────────────────────────────────────
|
2026-03-25 22:03:39 +01:00
|
|
|
|
def save_and_close():
|
2026-05-28 18:58:38 +02:00
|
|
|
|
try:
|
|
|
|
|
|
win.unbind_all("<MouseWheel>")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2026-03-25 22:03:39 +01:00
|
|
|
|
try:
|
|
|
|
|
|
save_settings_geometry(win.geometry())
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
if hasattr(self, "_aza_windows"):
|
|
|
|
|
|
self._aza_windows.discard(win)
|
|
|
|
|
|
win.destroy()
|
|
|
|
|
|
|
|
|
|
|
|
def on_ok():
|
2026-05-28 18:58:38 +02:00
|
|
|
|
# KG-Modell: keep current (dialog hidden, no change)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
selected_label = model_var_dialog.get().strip()
|
|
|
|
|
|
for model_id, label in MODEL_LABELS.items():
|
|
|
|
|
|
if label == selected_label:
|
|
|
|
|
|
self.model_var.set(model_id)
|
|
|
|
|
|
save_model(model_id)
|
|
|
|
|
|
break
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self._autotext_data["enabled"] = bool(autotext_var.get())
|
|
|
|
|
|
self._autotext_data["diktat_auto_start"] = bool(diktat_auto_var.get())
|
2026-05-28 18:58:38 +02:00
|
|
|
|
self._autotext_data["notizen_open_on_start"] = bool(notizen_open_var.get())
|
|
|
|
|
|
self._autotext_data["kommentare_auto_open"] = bool(kommentare_auto_var.get())
|
|
|
|
|
|
self._autotext_data["empfang_auto_open"] = bool(empfang_auto_var.get())
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self._autotext_data["textbloecke_visible"] = bool(textbloecke_visible_var.get())
|
|
|
|
|
|
self._autotext_data["addon_visible"] = bool(addon_visible_var.get())
|
2026-05-28 18:58:38 +02:00
|
|
|
|
self._autotext_data["logo_visible"] = bool(logo_visible_var.get())
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self._autotext_data["addon_buttons"] = {
|
2026-05-28 18:58:38 +02:00
|
|
|
|
bid: bool(v.get()) for bid, v in addon_button_vars.items()
|
2026-03-25 22:03:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
self._autotext_data["kg_auto_delete_old"] = bool(kg_auto_delete_var.get())
|
|
|
|
|
|
self._autotext_data["todo_auto_open"] = bool(todo_auto_open_var.get())
|
|
|
|
|
|
self._autotext_data["autocopy_after_diktat"] = bool(autocopy_var.get())
|
|
|
|
|
|
self._autotext_data["global_right_click_paste"] = bool(self._rclick_paste_var.get())
|
2026-05-28 18:58:38 +02:00
|
|
|
|
self._autotext_data["status_color"] = _status_color_options.get(
|
|
|
|
|
|
status_color_var.get(), "#BD4500"
|
|
|
|
|
|
)
|
2026-03-25 22:03:39 +01:00
|
|
|
|
|
|
|
|
|
|
if self._sig_auto_var.get():
|
|
|
|
|
|
save_signature_name("")
|
|
|
|
|
|
else:
|
|
|
|
|
|
save_signature_name(self._sig_name_var.get().strip())
|
|
|
|
|
|
|
|
|
|
|
|
save_autotext(self._autotext_data)
|
|
|
|
|
|
save_and_close()
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
def _apply_ui():
|
|
|
|
|
|
try:
|
|
|
|
|
|
if self._autotext_data["textbloecke_visible"]:
|
|
|
|
|
|
self._textbloecke_container.pack(fill="x", before=self._textbloecke_anchor)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._textbloecke_container.pack_forget()
|
|
|
|
|
|
if self._autotext_data["addon_visible"]:
|
|
|
|
|
|
self._addon_container.pack(fill="x", before=self._addon_anchor)
|
|
|
|
|
|
self._update_addon_buttons_visibility()
|
|
|
|
|
|
self.update_idletasks()
|
|
|
|
|
|
h = self.winfo_height()
|
|
|
|
|
|
if h < 500:
|
|
|
|
|
|
self.geometry(f"{self.winfo_width()}x500")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._addon_container.pack_forget()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._apply_status_color()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
self.update_idletasks()
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
self.after(50, _apply_ui)
|
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
|
_make_btn(footer, "OK", on_ok, primary=True).pack(side="right", padx=(8, 0))
|
|
|
|
|
|
_make_btn(footer, "Abbrechen", save_and_close).pack(side="right")
|
|
|
|
|
|
|
2026-03-25 22:03:39 +01:00
|
|
|
|
win.protocol("WM_DELETE_WINDOW", save_and_close)
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
win.attributes("-alpha", 1.0)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2026-03-25 22:03:39 +01:00
|
|
|
|
win.focus_set()
|
|
|
|
|
|
|
|
|
|
|
|
def _show_legal_text(self, parent, title: str, filename: str):
|
|
|
|
|
|
"""Zeigt einen Rechtstext (Markdown) in einem Lesefenster an."""
|
|
|
|
|
|
import os
|
|
|
|
|
|
legal_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "legal")
|
|
|
|
|
|
filepath = os.path.join(legal_dir, filename)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(filepath, "r", encoding="utf-8") as fh:
|
|
|
|
|
|
content = fh.read()
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
messagebox.showerror("Fehler", f"Datei nicht gefunden:\n{filepath}", parent=parent)
|
|
|
|
|
|
return
|
|
|
|
|
|
except OSError as e:
|
|
|
|
|
|
messagebox.showerror("Fehler", f"Datei konnte nicht gelesen werden:\n{e}", parent=parent)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
tw = tk.Toplevel(parent)
|
|
|
|
|
|
tw.title(title)
|
|
|
|
|
|
tw.transient(parent)
|
|
|
|
|
|
tw.geometry("720x600")
|
|
|
|
|
|
tw.minsize(500, 400)
|
|
|
|
|
|
tw.attributes("-topmost", True)
|
|
|
|
|
|
self._register_window(tw)
|
|
|
|
|
|
|
|
|
|
|
|
add_resize_grip(tw, 500, 400)
|
|
|
|
|
|
add_font_scale_control(tw)
|
|
|
|
|
|
|
|
|
|
|
|
frame = ttk.Frame(tw, padding=12)
|
|
|
|
|
|
frame.pack(fill="both", expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
txt = ScrolledText(frame, wrap="word", font=("Segoe UI", 10), bg="#FAFAFA")
|
|
|
|
|
|
txt.pack(fill="both", expand=True, pady=(0, 8))
|
|
|
|
|
|
txt.insert("1.0", content)
|
|
|
|
|
|
txt.configure(state="disabled")
|
|
|
|
|
|
|
|
|
|
|
|
ttk.Button(frame, text="Schliessen", command=tw.destroy).pack(anchor="e")
|