Files
aza/AzA march 2026/aza_settings_mixin.py
2026-05-28 18:58:38 +02:00

693 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
AzaSettingsMixin Einstellungsfenster (KG-Modell, Templates, Autotext, Add-ons, etc.).
Modernes AzA-Blau-Design, scrollbar, KG-Modell-Auswahl ausgeblendet.
"""
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
# ── 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,
)
class AzaSettingsMixin:
"""Mixin für das Einstellungsfenster."""
def _open_settings(self):
SETTINGS_MIN_W, SETTINGS_MIN_H = 720, 720
win = tk.Toplevel(self)
win.title("Einstellungen")
win.transient(self)
win.attributes("-topmost", True)
win.configure(bg=_WIN_BG)
win.minsize(SETTINGS_MIN_W, SETTINGS_MIN_H)
try:
win.attributes("-alpha", 0.0)
except Exception:
pass
if hasattr(self, "_aza_windows"):
self._aza_windows.add(win)
self._register_window(win)
saved_geom = load_settings_geometry()
if saved_geom:
try:
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")
except Exception:
win.geometry(f"{SETTINGS_MIN_W}x{SETTINGS_MIN_H}")
else:
win.update_idletasks()
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}")
add_font_scale_control(win)
# ── 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) ────────
display_values = [MODEL_LABELS[m] for m in ALLOWED_SUMMARY_MODELS]
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")
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)
ttk.Label(tf, text=(
"Kontext fuer die KI (z. B. Ich bin ein Dermatologe). "
"Wird bei der KG-Erstellung beruecksichtigt:"
)).pack(anchor="w")
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")
def _save_close():
save_templates_text(ttxt.get("1.0", "end").strip())
tw.destroy()
ttk.Button(btn_f, text="OK", command=_save_close).pack(side="left", padx=(0, 8))
ttk.Button(btn_f, text="Abbrechen", command=tw.destroy).pack(side="left")
def do_reset():
save_templates_text("")
messagebox.showinfo("Reset", "Template-Text wurde zurückgesetzt und ist jetzt leer.",
parent=win)
_make_btn(row_tmpl, "Templates", open_templates).pack(side="left", padx=(0, 8))
_make_btn(row_tmpl, "Reset", do_reset).pack(side="left")
# ── Startverhalten / Fenster ──────────────────────────────────────────
c_start = _card("Startverhalten / Fenster")
diktat_auto_var = tk.BooleanVar(value=self._autotext_data.get("diktat_auto_start", True))
_make_cb(c_start, "Diktat startet sofort (wenn aus: Aufnahme manuell starten)",
diktat_auto_var).pack(anchor="w", pady=2)
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)
kommentare_auto_var = tk.BooleanVar(value=self._autotext_data.get("kommentare_auto_open", False))
_make_cb(c_start, "Kommentare-Fenster beim Programmstart automatisch öffnen",
kommentare_auto_var).pack(anchor="w", pady=2)
empfang_auto_var = tk.BooleanVar(value=self._autotext_data.get("empfang_auto_open", False))
_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(*_):
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
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(*_):
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))
_make_cb(c_display, "Add-ons anzeigen",
addon_visible_var, command=_live_addon_visible).pack(anchor="w", pady=2)
def _live_logo_visible(*_):
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))
_make_cb(c_display, "Logo anzeigen (Klick auf Logo startet/stoppt Aufnahme)",
logo_visible_var, command=_live_logo_visible).pack(anchor="w", pady=2)
# ── Add-on-Buttons ────────────────────────────────────────────────────
c_addon = _card("Welche Add-on-Buttons anzeigen?")
addon_buttons = self._autotext_data.get("addon_buttons", {})
addon_button_vars: dict = {}
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"),
("empfang", "An Empfang senden"),
]
todo_auto_open_var = tk.BooleanVar(
value=self._autotext_data.get("todo_auto_open", True))
def _live_addon_toggle(*_):
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
_make_cb(c_addon, label, var, command=_live_addon_toggle).pack(
anchor="w", padx=(0, 0), pady=2)
if button_id == "todo":
_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",
}
_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)
def _live_status_color(*_):
sc_v = _status_color_options.get(status_color_var.get(), "#BD4500")
self._autotext_data["status_color"] = sc_v
try:
self._apply_status_color()
except Exception:
pass
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")
autotext_var = tk.BooleanVar(value=self._autotext_data.get("enabled", True))
_make_cb(c_at,
"Autotext aktiv (Abkuerzungen z. B. mfg -> mit freundlichen Gruessen)",
autotext_var).pack(anchor="w", pady=(0, 6))
def open_autotext_manage():
self._open_autotext_dialog(win)
_make_btn(c_at, "Autotext verwalten", open_autotext_manage).pack(anchor="w")
# ── Tastatur / Eingabe ────────────────────────────────────────────────
c_input = _card("Tastatur / Eingabe")
autocopy_var = tk.BooleanVar(
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)
if not hasattr(self, "_rclick_paste_var"):
self._rclick_paste_var = tk.BooleanVar(
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)
# ── Unterschrift / Signatur ───────────────────────────────────────────
c_sig = _card("Unterschrift / Signatur")
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)
_make_cb(c_sig,
f"Profilname verwenden: {profile_name}" if profile_name else "Profilname verwenden",
sig_auto_var).pack(anchor="w", pady=(0, 4))
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")
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")
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
# ── Audio / Mikrofon ──────────────────────────────────────────────────
c_audio = _card("Audio / Mikrofon")
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)
audio_status_var.set(
("" if result["ok"] else "") + result["message"]
)
except Exception as exc:
audio_status_var.set(f"✗ Fehler: {exc}")
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")
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'}")
tk.Label(c_legal, textvariable=consent_status_var, bg=_CARD_BG, fg=_TEXT,
font=(_FF, 9)).pack(anchor="w", pady=(0, 4))
def toggle_consent():
nonlocal consent_ok
_uid = self._user_profile.get("name", "default")
if has_valid_consent(_uid):
if messagebox.askyesno(
"Einwilligung widerrufen",
"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")
messagebox.showinfo("Widerruf",
"Ihre KI-Einwilligung wurde widerrufen und protokolliert.",
parent=win)
else:
if self._check_ai_consent():
consent_ok = True
consent_status_var.set("KI-Einwilligung: Erteilt")
btn_consent.configure(text="KI-Einwilligung widerrufen")
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))
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")
messagebox.showinfo(
"Export",
f"Consent-Log exportiert:\n{path_consent}\n\n"
f"Audit-Log exportiert:\n{path_audit}",
parent=win,
)
except Exception as e:
messagebox.showerror("Export-Fehler", str(e), parent=win)
_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))
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 ──────────────────────────────────────────────
def save_and_close():
try:
win.unbind_all("<MouseWheel>")
except Exception:
pass
try:
save_settings_geometry(win.geometry())
except Exception:
pass
if hasattr(self, "_aza_windows"):
self._aza_windows.discard(win)
win.destroy()
def on_ok():
# KG-Modell: keep current (dialog hidden, no change)
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
self._autotext_data["enabled"] = bool(autotext_var.get())
self._autotext_data["diktat_auto_start"] = bool(diktat_auto_var.get())
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())
self._autotext_data["textbloecke_visible"] = bool(textbloecke_visible_var.get())
self._autotext_data["addon_visible"] = bool(addon_visible_var.get())
self._autotext_data["logo_visible"] = bool(logo_visible_var.get())
self._autotext_data["addon_buttons"] = {
bid: bool(v.get()) for bid, v in addon_button_vars.items()
}
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())
self._autotext_data["status_color"] = _status_color_options.get(
status_color_var.get(), "#BD4500"
)
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()
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()
self.after(50, _apply_ui)
_make_btn(footer, "OK", on_ok, primary=True).pack(side="right", padx=(8, 0))
_make_btn(footer, "Abbrechen", save_and_close).pack(side="right")
win.protocol("WM_DELETE_WINDOW", save_and_close)
try:
win.attributes("-alpha", 1.0)
except Exception:
pass
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")