# -*- 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("", lambda e, _b=b, _bg=bg: _b.configure(bg=_darken(_bg))) b.bind("", 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("", _on_inner_resize) canvas.bind("", _on_canvas_resize) def _scroll(event): canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") win.bind_all("", _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("") 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")