2026-03-25 22:03:39 +01:00
# -*- coding: utf-8 -*-
"""
AzaSettingsMixin – Einstellungsfenster ( KG - Modell , Templates , Autotext , Add - ons , etc . ) .
"""
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
class AzaSettingsMixin :
""" Mixin für das Einstellungsfenster. """
def _open_settings ( self ) :
SETTINGS_MIN_W , SETTINGS_MIN_H = 680 , 520
win = tk . Toplevel ( self )
win . title ( " Einstellungen " )
win . transient ( self )
win . minsize ( SETTINGS_MIN_W , SETTINGS_MIN_H )
win . attributes ( " -topmost " , True )
if hasattr ( self , " _aza_windows " ) :
self . _aza_windows . add ( win )
self . _register_window ( win )
saved_geom = load_settings_geometry ( )
if saved_geom :
try :
win . geometry ( _clamp_geometry_str ( saved_geom , SETTINGS_MIN_W , SETTINGS_MIN_H ) )
except Exception :
win . geometry ( f " { SETTINGS_MIN_W } x { SETTINGS_MIN_H } " )
if not saved_geom :
win . geometry ( f " { SETTINGS_MIN_W } x { SETTINGS_MIN_H } " )
win . update_idletasks ( )
sw = win . winfo_screenwidth ( )
sh = win . winfo_screenheight ( )
w , h = SETTINGS_MIN_W , SETTINGS_MIN_H
x = max ( 0 , ( sw - w ) / / 2 )
y = max ( 0 , ( sh - h ) / / 2 )
win . geometry ( f " { w } x { h } + { x } + { y } " )
add_resize_grip ( win , SETTINGS_MIN_W , SETTINGS_MIN_H )
add_font_scale_control ( win )
f = ttk . Frame ( win , padding = 16 )
f . pack ( fill = " both " , expand = True )
ttk . Label ( f , text = " KG-Modell: " ) . grid ( row = 0 , column = 0 , sticky = " w " , pady = ( 0 , 8 ) )
display_values = [ MODEL_LABELS [ m ] for m in ALLOWED_SUMMARY_MODELS ]
current = MODEL_LABELS . get ( self . model_var . get ( ) , display_values [ 0 ] )
model_var_dialog = tk . StringVar ( value = current )
model_box = ttk . Combobox (
f , textvariable = model_var_dialog , values = display_values , state = " readonly " , width = 42
)
model_box . grid ( row = 0 , column = 1 , sticky = " ew " , padx = ( 12 , 0 ) , pady = ( 0 , 8 ) )
f . columnconfigure ( 1 , weight = 1 )
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 für die KI (z. B. „Ich bin ein Dermatologe und schreibe dermatologische Berichte.“). Wird bei der KG-Erstellung berücksichtigt: " ) . 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_and_close ( ) :
save_templates_text ( ttxt . get ( " 1.0 " , " end " ) . strip ( ) )
tw . destroy ( )
ttk . Button ( btn_f , text = " OK " , command = save_and_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. " )
ttk . Button ( f , text = " Templates " , command = open_templates ) . grid ( row = 1 , column = 0 , pady = ( 8 , 4 ) , sticky = " w " )
ttk . Button ( f , text = " Reset " , command = do_reset ) . grid ( row = 1 , column = 1 , pady = ( 8 , 4 ) , sticky = " w " , padx = ( 12 , 0 ) )
start_frame = ttk . LabelFrame ( f , text = " Startverhalten / Fenster " , padding = ( 10 , 5 ) )
start_frame . grid ( row = 2 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
diktat_auto_var = tk . BooleanVar ( value = self . _autotext_data . get ( " diktat_auto_start " , True ) )
ttk . Checkbutton ( start_frame , text = " Diktat startet sofort (wenn aus: Aufnahme manuell starten) " ,
variable = diktat_auto_var ) . pack ( anchor = " w " , pady = 2 )
notizen_open_on_start_var = tk . BooleanVar ( value = self . _autotext_data . get ( " notizen_open_on_start " , True ) )
ttk . Checkbutton ( start_frame , text = " Audionotiz beim Programmstart automatisch öffnen " ,
variable = notizen_open_on_start_var ) . pack ( anchor = " w " , pady = 2 )
kommentare_auto_var = tk . BooleanVar ( value = self . _autotext_data . get ( " kommentare_auto_open " , False ) )
ttk . Checkbutton ( start_frame , text = " Kommentare-Fenster beim Programmstart automatisch öffnen " ,
variable = 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 ) )
ttk . Checkbutton ( start_frame , text = " Empfang-Fenster beim Programmstart automatisch öffnen " ,
variable = empfang_auto_var ) . pack ( anchor = " w " , pady = 2 )
2026-03-25 22:03:39 +01:00
def _live_textbloecke_visible ( * _args ) :
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 ) )
cb_textbloecke = ttk . Checkbutton ( f , text = " Textblöcke anzeigen (Inhalt bleibt gespeichert, wenn ausgeblendet) " ,
variable = textbloecke_visible_var , command = _live_textbloecke_visible )
cb_textbloecke . grid ( row = 3 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
def _live_addon_visible ( * _args ) :
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 ) )
cb_addon = ttk . Checkbutton ( f , text = " Add-ons anzeigen " , variable = addon_visible_var ,
command = _live_addon_visible )
cb_addon . grid ( row = 4 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
def _live_logo_visible ( * _args ) :
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 ) )
cb_logo = ttk . Checkbutton ( f , text = " Logo anzeigen (Klick auf Logo startet/stoppt Aufnahme) " ,
variable = logo_visible_var , command = _live_logo_visible )
cb_logo . grid ( row = 4 , column = 1 , sticky = " w " , pady = ( 4 , 2 ) , padx = ( 12 , 0 ) )
# Unterkategorie: Welche Add-on-Buttons sollen angezeigt werden?
addon_buttons_frame = ttk . LabelFrame ( f , text = " Welche Add-on-Buttons anzeigen? " , padding = ( 10 , 5 ) )
addon_buttons_frame . grid ( row = 5 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
addon_buttons = self . _autotext_data . get ( " addon_buttons " , { } )
addon_button_vars = { }
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 ) )
def _live_addon_toggle ( * _args ) :
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
grid_row = 0
for button_id , label in addon_button_options :
var = tk . BooleanVar ( value = addon_buttons . get ( button_id , True ) )
addon_button_vars [ button_id ] = var
cb = ttk . Checkbutton ( addon_buttons_frame , text = label , variable = var ,
command = _live_addon_toggle )
cb . grid ( row = grid_row , column = 0 , sticky = " w " , padx = 10 , pady = 2 )
grid_row + = 1
if button_id == " todo " :
cb_auto = ttk . Checkbutton (
addon_buttons_frame ,
text = " ↳ To-do beim Start automatisch öffnen " ,
variable = todo_auto_open_var )
cb_auto . grid ( row = grid_row , column = 0 , sticky = " w " , padx = 10 , pady = ( 0 , 2 ) )
grid_row + = 1
kg_auto_delete_var = tk . BooleanVar ( value = self . _autotext_data . get ( " kg_auto_delete_old " , False ) )
cb_kg_auto = ttk . Checkbutton ( f , text = " KG-Einträge älter als 2 Wochen automatisch löschen (Speicher schonen) " , variable = kg_auto_delete_var )
cb_kg_auto . grid ( row = 6 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
# Statusanzeige-Farbe
status_color_frame = ttk . LabelFrame ( f , text = " Statusanzeige " , padding = ( 10 , 5 ) )
status_color_frame . grid ( row = 7 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
_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 ( * _args ) :
sc_sel = status_color_var . get ( )
sc_v = _status_color_options . get ( sc_sel , " #BD4500 " )
self . _autotext_data [ " status_color " ] = sc_v
try :
self . _apply_status_color ( )
except Exception :
pass
for sc_col , ( sc_label , sc_val ) in enumerate ( _status_color_options . items ( ) ) :
ttk . Radiobutton ( status_color_frame , text = sc_label , variable = status_color_var ,
value = sc_label , command = _live_status_color ) . grid ( row = 0 , column = sc_col , padx = 8 , pady = 2 , sticky = " w " )
autotext_var = tk . BooleanVar ( value = self . _autotext_data . get ( " enabled " , True ) )
cb_autotext = ttk . Checkbutton ( f , text = " Autotext (Abkürzungen z. B. „mfg“ → „mit freundlichen Grüßen“) " , variable = autotext_var )
cb_autotext . grid ( row = 8 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
def open_autotext_manage ( ) :
self . _open_autotext_dialog ( win )
ttk . Button ( f , text = " Autotext verwalten " , command = open_autotext_manage ) . grid ( row = 9 , column = 0 , pady = ( 2 , 4 ) , sticky = " w " )
autocopy_var = tk . BooleanVar (
value = self . _autotext_data . get ( " autocopy_after_diktat " , True )
)
cb_autocopy = ttk . Checkbutton (
f ,
text = " Autocopy: Nach Diktat/Transkription automatisch in Zwischenablage kopieren " ,
variable = autocopy_var ,
)
cb_autocopy . grid ( row = 10 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
if not hasattr ( self , " _rclick_paste_var " ) :
self . _rclick_paste_var = tk . BooleanVar (
value = bool ( self . _autotext_data . get ( " global_right_click_paste " , True ) ) )
cb_global_right_click = ttk . Checkbutton (
f ,
text = " Global: Rechtsklick fügt direkt ein (ohne Kontextmenü, nur externe Apps) " ,
variable = self . _rclick_paste_var ,
command = self . _toggle_rclick_paste ,
)
cb_global_right_click . grid ( row = 11 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 4 , 2 ) )
sig_frame = ttk . LabelFrame ( f , text = " Unterschrift / Signatur " , padding = ( 10 , 5 ) )
sig_frame . grid ( row = 12 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
sig_frame . columnconfigure ( 1 , weight = 1 )
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 )
cb_sig_auto = ttk . Checkbutton ( sig_frame ,
text = f " Profilname verwenden: { profile_name } " if profile_name else " Profilname verwenden " ,
variable = sig_auto_var )
cb_sig_auto . grid ( row = 0 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 0 , 4 ) )
ttk . Label ( sig_frame , text = " Abweichender Name: " ) . grid ( row = 1 , column = 0 , sticky = " w " , padx = ( 0 , 8 ) )
ent_sig = ttk . Entry ( sig_frame , textvariable = sig_name_var , width = 36 )
ent_sig . grid ( row = 1 , column = 1 , sticky = " ew " , pady = ( 0 , 2 ) )
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-Test ---
audio_frame = ttk . LabelFrame ( f , text = " Audio / Mikrofon " , padding = ( 10 , 5 ) )
audio_frame . grid ( row = 13 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
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 )
if result [ " ok " ] :
audio_status_var . set ( " ✓ " + result [ " message " ] )
else :
audio_status_var . set ( " ✗ " + result [ " message " ] )
except Exception as exc :
audio_status_var . set ( f " ✗ Fehler: { exc } " )
ttk . Button ( audio_frame , text = " Audio-Test starten " ,
command = _run_audio_test ) . pack ( side = " left " , padx = ( 0 , 12 ) )
tk . Label ( audio_frame , textvariable = audio_status_var ,
font = ( " Segoe UI " , 9 ) , fg = " #333 " , bg = " #F0F0F0 " ,
wraplength = 400 , justify = " left " ) . pack ( side = " left " , fill = " x " , expand = True )
legal_frame = ttk . LabelFrame ( f , text = " Datenschutz & Recht " , padding = ( 10 , 5 ) )
legal_frame . grid ( row = 14 , column = 0 , columnspan = 2 , sticky = " ew " , pady = ( 8 , 4 ) )
ttk . Button ( legal_frame , text = " Datenschutzerklärung anzeigen " ,
command = lambda : self . _show_legal_text ( win , " Datenschutzerklärung " , " privacy_policy.md " )
) . grid ( row = 0 , column = 0 , padx = ( 0 , 8 ) , pady = 2 , sticky = " w " )
ttk . Button ( legal_frame , text = " KI-Einwilligung anzeigen " ,
command = lambda : self . _show_legal_text ( win , " KI-Einwilligung " , " ai_consent.md " )
) . grid ( row = 0 , column = 1 , padx = 0 , pady = 2 , sticky = " w " )
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 ' } " )
ttk . Label ( legal_frame , textvariable = consent_status_var ) . grid (
row = 1 , column = 0 , columnspan = 2 , sticky = " w " , pady = ( 6 , 2 ) )
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 " )
btn_consent = ttk . Button ( legal_frame ,
text = " KI-Einwilligung widerrufen " if consent_ok else " KI-Einwilligung erteilen " ,
command = toggle_consent )
btn_consent . grid ( row = 2 , column = 0 , padx = ( 0 , 8 ) , pady = 2 , sticky = " w " )
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 )
ttk . Button ( legal_frame , text = " Logs exportieren (Audit) " ,
command = do_export ) . grid ( row = 2 , column = 1 , padx = 0 , pady = 2 , sticky = " w " )
def save_and_close ( ) :
try :
save_settings_geometry ( win . geometry ( ) )
except Exception :
pass
if hasattr ( self , " _aza_windows " ) :
self . _aza_windows . discard ( win )
win . destroy ( )
def on_ok ( ) :
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_on_start_var . get ( ) )
self . _autotext_data [ " textbloecke_visible " ] = bool ( textbloecke_visible_var . get ( ) )
self . _autotext_data [ " addon_visible " ] = bool ( addon_visible_var . get ( ) )
# Speichere die einzelnen Button-Einstellungen
self . _autotext_data [ " addon_buttons " ] = {
button_id : bool ( var . get ( ) )
for button_id , var 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 [ " kommentare_auto_open " ] = bool ( kommentare_auto_var . get ( ) )
2026-04-19 20:41:37 +02:00
self . _autotext_data [ " empfang_auto_open " ] = bool ( empfang_auto_var . get ( ) )
2026-03-25 22:03:39 +01:00
self . _autotext_data [ " logo_visible " ] = bool ( logo_visible_var . get ( ) )
if self . _sig_auto_var . get ( ) :
save_signature_name ( " " )
else :
save_signature_name ( self . _sig_name_var . get ( ) . strip ( ) )
# Statusanzeige-Farbe speichern
sc_selected = status_color_var . get ( )
sc_value = _status_color_options . get ( sc_selected , " #BD4500 " )
self . _autotext_data [ " status_color " ] = sc_value
save_autotext ( self . _autotext_data )
save_and_close ( )
# UI-Updates nach Schließen des Einstellungsfensters (verhindert Hang)
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 )
win . protocol ( " WM_DELETE_WINDOW " , save_and_close )
ttk . Button ( f , text = " OK " , command = on_ok ) . grid ( row = 15 , column = 0 , columnspan = 2 , pady = ( 12 , 0 ) )
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 )
from aza_ui_helpers import add_resize_grip , add_font_scale_control
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 " )