This commit is contained in:
2026-05-04 23:43:34 +02:00
parent 03d188d119
commit 72ecd579de
10 changed files with 422 additions and 277 deletions

View File

@@ -1,6 +1,6 @@
# Auto-generated by aza_build_stamp.py DO NOT EDIT
BUILD_TIME = "2026-05-03 19:14:17"
BUILD_TIMESTAMP = "20260503_191417"
GIT_COMMIT = "d4822fc8"
BUILD_TIME = "2026-05-04 23:40:27"
BUILD_TIMESTAMP = "20260504_234027"
GIT_COMMIT = "03d188d1"
GIT_BRANCH = "main"
GIT_DIRTY = True

View File

@@ -13,10 +13,9 @@ Aufbauend auf V1.1:
``_send_to_empfang``).
* **Erscheinungsbild**: dieselbe **Transparenz-Logik** wie in der klassischen
Hauptfenster-Kopfzeile (``_opacity_var_main``, ``MIN_OPACITY``, ``save_opacity``)
sowie Zugriff auf **alle Einstellungen** (``_open_settings``). Zusätzlich
weiterhin **Hell/Dunkel** für die Office-Hülle-Farbpalette (persistiert),
da das klassische ``basis14``-Hauptfenster keine globale Farb-Umschaltung hat — nur
Transparenz und das separate Einstellungsfenster.
sowie Zugriff auf **alle Einstellungen** (``_open_settings``). Die Farbpalette
der Office-Hülle folgt der beim Start geladenen Hell/Dunkel-Präferenz;
Umschalten nur noch über das klassische Einstellungsfenster, falls dort angeboten.
* Footer-Branding und Logo ca. **30 % größer** als in V1.1.
Technische Strategie
@@ -738,7 +737,7 @@ class _OfficeShellV12:
pad = dict(
bg=acc, fg="white", font=FONT_DEFAULT,
activebackground=acc, activeforeground="white",
selectcolor="#E2EEF6", highlightthickness=0,
selectcolor="#1a4d6d", highlightthickness=0,
bd=0, anchor="w",
)
@@ -1015,6 +1014,7 @@ class _OfficeShellV12:
app = self.app
acc = self._palette["ACCENT"]
bar = self._sidebar
self._theme_switch_pop = None
for w in list(bar.winfo_children()):
try:
w.destroy()
@@ -1024,7 +1024,7 @@ class _OfficeShellV12:
cb_pad = dict(
bg=acc, fg="white", font=FONT_DEFAULT,
activebackground=acc, activeforeground="white",
selectcolor="#E2EEF6", highlightthickness=0,
selectcolor="#1a4d6d", highlightthickness=0,
bd=0, anchor="w",
)
@@ -1147,30 +1147,6 @@ class _OfficeShellV12:
lbl_full.pack(side="left")
lbl_full.bind("<Button-1>", lambda e: self._apply_opacity_percent_str("100"))
tk.Label(
self._sec_ersch_body, text="Office-Hülle hell / dunkel",
bg=acc, fg="#E2EEF6", font=FONT_DEFAULT,
).pack(anchor="w", padx=14, pady=(8, 4))
row_th = tk.Frame(self._sec_ersch_body, bg=acc)
row_th.pack(fill="x", padx=12)
tk.Label(row_th, text="Hell", bg=acc, fg="white",
font=FONT_DEFAULT).pack(side="left", padx=(0, 6))
def _flip():
self._toggle_theme_main()
if self._theme_switch_pop is not None:
self._theme_switch_pop.set_dark(self._dark_mode)
self._theme_switch_pop = PopoverThemeSwitch(
row_th, is_dark=self._dark_mode, command=_flip, bg_accent=acc,
)
self._theme_switch_pop.pack(side="left")
tk.Label(row_th, text="Dunkel", bg=acc, fg="white",
font=FONT_DEFAULT).pack(side="left", padx=(6, 0))
tk.Frame(self._sec_ersch_body, bg=acc, height=8).pack()
link = tk.Label(

View File

@@ -2066,7 +2066,8 @@ class KGDesktopApp(tk.Tk, TodoMixin, TextWindowsMixin, AzaDiktatMixin, AzaSettin
self._main_hidden = True
self.withdraw()
return
self._return_to_launcher = True
# Normales Schliessen (X): Programm beenden. Launcher nur wenn
# vorher explizit _go_to_launcher() gesetzt hat (_return_to_launcher True).
self.destroy()
def _go_to_launcher(self):
@@ -5612,11 +5613,15 @@ WICHTIG unbedingt einhalten:
return result
def _sync_empfang_after_kg_change(self, *, context: str = "Korrektur") -> None:
"""Empfang-Dialog: Felder aus aktueller KG fuellen und Auto-Sektionen
in den Chat unten einfuegen sowie Hinweis im Chat-Verlauf posten.
"""Empfang-Dialog: Felder aus aktueller KG fuellen.
Bei aktiviertem Autocopy (Therapie / Procedere) wird nur die untere
Nachrichten-Box befuellt — nicht der Chat-Verlauf.
Wird nach «Korrigieren», neuem Transkript und neuer KG aufgerufen.
Das Argument ``context`` bleibt fuer bestehende Aufrufer erhalten.
"""
_ = context
dlg = getattr(self, "_empfang_dlg", None)
if dlg is None:
return
@@ -5660,75 +5665,42 @@ WICHTIG unbedingt einhalten:
for k, v in section_values.items():
_push_field(k, v)
def _sync_fmt_ther(val: str) -> str:
t = (val or "").strip()
if not t:
return ""
lines = t.split("\n")
first = lines[0].strip().lower().rstrip(":").rstrip(".")
if first in ("therapie", "therapieplan"):
rest = "\n".join(lines[1:]).strip()
return f"Therapie\n{rest}" if rest else "Therapie"
return f"Therapie\n{t}"
def _sync_fmt_proc(val: str) -> str:
t = (val or "").strip()
if not t:
return ""
lines = t.split("\n")
first = lines[0].strip().lower().rstrip(":").rstrip(".")
if first == "procedere":
rest = "\n".join(lines[1:]).strip()
return f"Procedere\n{rest}" if rest else "Procedere"
return f"Procedere\n{t}"
push_fn = copy_fns.get("push") if isinstance(copy_fns, dict) else None
auto_pushed: list[str] = []
if callable(push_fn):
for key, label in (("ther", "Therapieplan"),
("proc", "Procedere"),
("patient", "Nr.")):
for key in ("ther", "proc"):
var = auto_vars.get(key)
if var is None or not var.get():
continue
if key == "patient":
w = fw.get("patient")
val = w.get().strip() if isinstance(w, tk.StringVar) else ""
else:
val = (section_values.get(key) or "").strip()
if val:
if not val:
continue
try:
push_fn(f"{label}:\n{val}", source=key)
auto_pushed.append(label)
except Exception:
pass
tp = section_values["ther"].strip()
pc = section_values["proc"].strip()
parts: list[str] = []
if tp:
parts.append(f"Therapieplan:\n{tp}")
if pc:
parts.append(f"Procedere:\n{pc}")
if not parts:
try:
kg = self.txt_output.get("1.0", "end").strip()
except Exception:
kg = ""
if kg:
excerpt = kg[:1200] + ("" if len(kg) > 1200 else "")
parts.append(f"Krankengeschichte:\n{excerpt}")
suffix = ""
if auto_pushed:
suffix = (
"\n\n[Auto-Kopie in Chat unten: "
+ ", ".join(auto_pushed) + "]"
)
body = (
f"Krankengeschichte wurde aktualisiert ({context}).\n\n"
+ ("\n\n".join(parts) if parts else "(Kein Text)")
+ suffix
)
overlay = getattr(self, "_empfang_local_chat_overlay", None)
if overlay is None:
overlay = []
self._empfang_local_chat_overlay = overlay
name = (self._user_profile.get("name") or "").strip() or "Benutzer"
overlay.append({
"id": f"local-{time.time_ns()}",
"absender": f"{name} (lokal)",
"zeitstempel": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"kommentar": body,
})
if len(overlay) > 25:
del overlay[:-25]
updater = getattr(self, "_empfang_update_chat_fn", None)
if callable(updater):
try:
base = getattr(self, "_empfang_last_thread_messages", None)
if base is None:
base = []
updater(base)
if key == "ther":
push_fn(_sync_fmt_ther(val), source=key)
else:
push_fn(_sync_fmt_proc(val), source=key)
except Exception:
pass
@@ -5882,6 +5854,97 @@ WICHTIG unbedingt einhalten:
outer = tk.Frame(dlg, bg="#E8F4FA")
outer.pack(fill="both", expand=True)
def _compact_nr(s: str) -> str:
return re.sub(r"\s+", "", (s or "").strip())
# --- Smart-Pick (Pinsel): vor Patienten-Zeile benoetigt ---
_pick_listener = [None]
def _do_smart_pick(btn, on_result):
if _pick_listener[0] is not None:
try:
_pick_listener[0].stop()
except Exception:
pass
_pick_listener[0] = None
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
return
try:
_old_clip = ""
if sys.platform == "win32":
_old_clip = (_win_clipboard_get() or "").strip()
if not _old_clip:
_old_clip = dlg.clipboard_get().strip()
except tk.TclError:
_old_clip = ""
btn.configure(text="\u23f9 Text markieren...", bg="#f8d7da", fg="#721c24")
_started = [time.time()]
_mouse_was_pressed = [False]
_press_time = [0.0]
def _on_click(x, y, button, pressed):
if button != MouseButton.left:
return
if pressed:
if time.time() - _started[0] > 0.3:
_mouse_was_pressed[0] = True
_press_time[0] = time.time()
return
if not _mouse_was_pressed[0]:
return
_mouse_was_pressed[0] = False
hold_duration = time.time() - _press_time[0]
was_drag = hold_duration > 0.20
time.sleep(0.05)
try:
kbd = KbdController()
if not was_drag:
from pynput.mouse import Controller as MController
mc = MController()
mc.click(MouseButton.left, 2)
time.sleep(0.1)
with kbd.pressed(Key.ctrl):
kbd.tap(KeyCode.from_char('c'))
except Exception:
pass
time.sleep(0.38)
def _grab():
cur = ""
try:
for _attempt in range(8):
cur = ""
if sys.platform == "win32":
cur = (_win_clipboard_get() or "").strip()
if not cur:
try:
cur = dlg.clipboard_get().strip()
except tk.TclError:
cur = ""
if cur and cur != _old_clip:
break
time.sleep(0.06)
if cur and cur != _old_clip:
on_result(cur)
except Exception:
pass
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
_pick_listener[0] = None
self.after(0, _grab)
return False
if _HAS_PYNPUT_MOUSE:
ml = MouseListener(on_click=_on_click)
_pick_listener[0] = ml
ml.start()
else:
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
# --- Header mit Titel und Schriftgröße ---
hdr = tk.Frame(outer, bg="#E8F4FA")
hdr.pack(fill="x", padx=14, pady=(8, 0))
@@ -5889,9 +5952,140 @@ WICHTIG unbedingt einhalten:
tk.Label(hdr, text="An Empfang senden", font=("Segoe UI", 12, "bold"),
bg="#E8F4FA", fg="#1a4d6d").pack(side="left")
toggle_vars: dict[str, tk.BooleanVar] = {}
field_widgets: dict = {}
# Patienten-Nr. nur in dieser Kopfzeile (kein eigener Nr.-Abschnitt im Scrollbereich)
nr_banner_row = tk.Frame(outer, bg="#E8F4FA")
nr_banner_row.pack(fill="x", padx=14, pady=(2, 2))
tk.Label(nr_banner_row, text="Patienten-Nr.:",
font=("Segoe UI", 9), bg="#E8F4FA", fg="#5B8DB3").pack(side="left")
_pat_ph = "(keine)"
patient_sv = tk.StringVar()
_lp_init = (prefs.get("last_patient") or "").strip()
if _lp_init:
patient_sv.set(_lp_init)
else:
patient_sv.set(_pat_ph)
patient_ent = tk.Entry(
nr_banner_row,
textvariable=patient_sv,
font=("Consolas", 11),
bg="white",
fg="#aaa" if not _lp_init else "#1a4d6d",
relief="solid",
bd=1,
width=24,
)
patient_ent.pack(side="left", padx=(8, 4), fill="x", expand=True)
field_widgets["patient"] = patient_sv
toggle_vars["patient"] = tk.BooleanVar(value=True)
copy_to_chat_fns: dict = {}
def _pat_focus_in(_e=None):
if patient_sv.get().strip() == _pat_ph:
patient_sv.set("")
patient_ent.configure(fg="#1a4d6d")
def _pat_focus_out(_e=None):
if not patient_sv.get().strip():
patient_sv.set(_pat_ph)
patient_ent.configure(fg="#aaa")
else:
patient_ent.configure(fg="#1a4d6d")
patient_ent.bind("<FocusIn>", _pat_focus_in)
patient_ent.bind("<FocusOut>", _pat_focus_out)
def _patient_entry_copy_compact(_e=None):
raw = patient_sv.get().strip()
if raw == _pat_ph:
return "break"
nn = _compact_nr(raw)
if nn:
dlg.clipboard_clear()
dlg.clipboard_append(nn)
dlg.update_idletasks()
try:
self.set_status("Nr. kopiert (ohne Leerzeichen).")
except Exception:
pass
return "break"
patient_ent.bind("<Double-Button-1>", _patient_entry_copy_compact)
def _patient_paste_from_clipboard(_e=None):
try:
txt = ""
if sys.platform == "win32":
txt = (_win_clipboard_get() or "").strip()
if not txt:
txt = dlg.clipboard_get().strip()
nn = _compact_nr(txt)
if len(nn) < 2:
return None
patient_sv.set(nn)
patient_ent.configure(fg="#1a4d6d")
return "break"
except Exception:
return None
patient_ent.bind("<Control-v>", _patient_paste_from_clipboard)
patient_ent.bind("<Control-V>", _patient_paste_from_clipboard)
def _clear_nr_top(_e=None):
patient_sv.set(_pat_ph)
patient_ent.configure(fg="#aaa")
tk.Label(nr_banner_row, text="\u2715", font=("Segoe UI", 9),
bg="#E8F4FA", fg="#aaa", cursor="hand2").pack(side="left", padx=(2, 2))
nr_banner_row.winfo_children()[-1].bind("<Button-1>", _clear_nr_top)
pick_btn_nr_top = tk.Button(
nr_banner_row,
text="\U0001f58c Pinsel",
font=("Segoe UI", 8),
bg="#dde8f0",
fg="#1a4d6d",
relief="flat",
cursor="hand2",
bd=0,
padx=6,
pady=1,
)
pick_btn_nr_top.pack(side="left", padx=(2, 0))
def _start_pick_nr_top():
def _on_pick(t):
nn = _compact_nr(t)
if nn:
patient_sv.set(nn)
patient_ent.configure(fg="#1a4d6d")
def _follow():
fn = copy_to_chat_fns.get("push")
if callable(fn):
fn(f"Nr.: {nn}", source="patient")
dlg.after(120, _follow)
_do_smart_pick(pick_btn_nr_top, _on_pick)
pick_btn_nr_top.configure(command=_start_pick_nr_top)
add_tooltip(
pick_btn_nr_top,
"Text woanders markieren Nr. erscheint hier und oben in der Nachrichten-Box.",
)
add_tooltip(
patient_ent,
"Patienten-Nr.; Doppelklick: ohne Leerzeichen kopieren.",
)
# Section-Sichtbarkeit (immer alle an, ohne Zahnrad-Menue)
gear_vis = {
"patient": tk.BooleanVar(value=True),
"ther": tk.BooleanVar(value=True),
"proc": tk.BooleanVar(value=True),
}
@@ -6005,81 +6199,6 @@ WICHTIG unbedingt einhalten:
_w.bind("<Enter>", lambda e, ww=_w: ww.configure(fg="#1a4d6d"))
_w.bind("<Leave>", lambda e, ww=_w: ww.configure(fg="#8AAFC0"))
# --- Smart-Pick: markieren + loslassen = uebernehmen ---
# Benutzer klickt Lupe → wechselt zu fremdem Fenster →
# markiert Text mit Maus → laesst los → Text wird uebernommen.
# KEIN manuelles Ctrl+C noetig.
_pick_listener = [None]
def _do_smart_pick(btn, on_result):
if _pick_listener[0] is not None:
try:
_pick_listener[0].stop()
except Exception:
pass
_pick_listener[0] = None
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
return
try:
_old_clip = dlg.clipboard_get()
except tk.TclError:
_old_clip = ""
btn.configure(text="\u23f9 Text markieren...", bg="#f8d7da", fg="#721c24")
_started = [time.time()]
_mouse_was_pressed = [False]
_press_time = [0.0]
def _on_click(x, y, button, pressed):
if button != MouseButton.left:
return
if pressed:
if time.time() - _started[0] > 0.3:
_mouse_was_pressed[0] = True
_press_time[0] = time.time()
return
if not _mouse_was_pressed[0]:
return
_mouse_was_pressed[0] = False
hold_duration = time.time() - _press_time[0]
was_drag = hold_duration > 0.20
time.sleep(0.05)
try:
kbd = KbdController()
if not was_drag:
from pynput.mouse import Controller as MController
mc = MController()
mc.click(MouseButton.left, 2)
time.sleep(0.1)
with kbd.pressed(Key.ctrl):
kbd.tap(KeyCode.from_char('c'))
except Exception:
pass
time.sleep(0.2)
def _grab():
try:
cur = dlg.clipboard_get().strip()
if cur and cur != _old_clip:
on_result(cur)
except tk.TclError:
pass
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
_pick_listener[0] = None
self.after(0, _grab)
return False
if _HAS_PYNPUT_MOUSE:
ml = MouseListener(on_click=_on_click)
_pick_listener[0] = ml
ml.start()
else:
btn.configure(text="\U0001f58c Pinsel", bg="#dde8f0", fg="#1a4d6d")
# --- Feste Knopfleiste am unteren Rand ---
bottom_bar = tk.Frame(outer, bg="#E8F4FA")
bottom_bar.pack(side="bottom", fill="x", padx=14, pady=(4, 8))
@@ -6107,20 +6226,38 @@ WICHTIG unbedingt einhalten:
dlg.bind("<Destroy>", lambda e: canvas.unbind_all("<MouseWheel>") if e.widget is dlg else None)
_field_defs_raw = [
("patient", "Nr.", prefs.get("last_patient", "")),
("ther", "Therapieplan", extracted["therapieplan"]),
("proc", "Procedere", extracted["procedere"]),
]
field_defs = list(_field_defs_raw)
toggle_vars: dict[str, tk.BooleanVar] = {}
content_frames: dict[str, tk.Frame] = {}
field_widgets: dict = {}
auto_copy_vars: dict[str, tk.BooleanVar] = {
k: tk.BooleanVar(value=bool(prefs.get(f"auto_copy_{k}", False)))
for k, _, _ in _field_defs_raw
}
copy_to_chat_fns: dict = {}
def _format_ther_for_push(raw: str) -> str:
t = (raw or "").strip()
if not t:
return ""
lines = t.split("\n")
first = lines[0].strip().lower().rstrip(":").rstrip(".")
if first in ("therapie", "therapieplan"):
rest = "\n".join(lines[1:]).strip()
return f"Therapie\n{rest}" if rest else "Therapie"
return f"Therapie\n{t}"
def _format_proc_for_push(raw: str) -> str:
t = (raw or "").strip()
if not t:
return ""
lines = t.split("\n")
first = lines[0].strip().lower().rstrip(":").rstrip(".")
if first == "procedere":
rest = "\n".join(lines[1:]).strip()
return f"Procedere\n{rest}" if rest else "Procedere"
return f"Procedere\n{t}"
for key, label, initial_val in field_defs:
has_content = bool(initial_val and initial_val.strip())
@@ -6154,72 +6291,71 @@ WICHTIG unbedingt einhalten:
if not callable(fn):
return
val = _get_text(_k)
if val:
fn(val, source=_k)
if not val:
return
if _k == "ther":
ft = _format_ther_for_push(val)
if ft:
fn(ft, source=_k)
return
fp = _format_proc_for_push(val)
if fp:
fn(fp, source=_k)
copy_btn = tk.Button(
header, text="Kopieren", font=("Segoe UI", 9),
bg="#dde8f0", fg="#1a4d6d",
activebackground="#c8d8e6", relief="flat", bd=0,
cursor="hand2", padx=10, pady=2, width=9,
command=_copy_section_to_chat,
def _on_copy_link_click(_e, _k=k):
_copy_section_to_chat(_k)
return "break"
copy_lbl = tk.Label(
header,
text="Kopieren",
font=("Segoe UI", 9, "underline"),
bg="#E8F4FA",
fg="#2563ab",
cursor="hand2",
)
copy_lbl.pack(side="left", padx=(8, 6))
copy_lbl.bind("<Button-1>", _on_copy_link_click)
add_tooltip(
copy_lbl,
"Inhalt in die Nachrichten-Box unten einfuegen (nicht in den Chat-Verlauf).",
)
copy_btn.pack(side="left", padx=(8, 6))
add_tooltip(copy_btn, "Inhalt unten in den Chat einfuegen")
def _on_auto_change(_k=k):
prefs[f"auto_copy_{_k}"] = auto_copy_vars[_k].get()
acv = auto_copy_vars.get(_k)
if acv is None:
return
prefs[f"auto_copy_{_k}"] = acv.get()
self._autotext_data["empfang_prefs"] = prefs
save_autotext(self._autotext_data)
if auto_copy_vars[_k].get():
if acv.get():
_copy_section_to_chat(_k)
auto_cb = tk.Checkbutton(
header, text="Auto", font=("Segoe UI", 9),
variable=auto_copy_vars[k], bg="#E8F4FA", fg="#1a4d6d",
activebackground="#E8F4FA", activeforeground="#1a4d6d",
selectcolor="#FFFFFF", highlightthickness=0, bd=0,
cursor="hand2", padx=4,
header,
text="Autocopy",
font=("Segoe UI", 9),
variable=auto_copy_vars[k],
bg="#E8F4FA",
fg="#1a4d6d",
activebackground="#E8F4FA",
activeforeground="#1a4d6d",
selectcolor="#FFFFFF",
highlightthickness=0,
bd=0,
cursor="hand2",
padx=4,
command=_on_auto_change,
)
auto_cb.pack(side="left")
add_tooltip(
auto_cb,
"Automatisch in den Chat unten einfuegen, wenn KG aktualisiert wird",
"Wenn aktiv: Abschnitt bei KG-Updates automatisch in die Nachrichten-Box.",
)
body = tk.Frame(r, bg="#E8F4FA")
content_frames[k] = body
if k == "patient":
sv = tk.StringVar(value=init)
pat_row = tk.Frame(body, bg="#E8F4FA")
pat_row.pack(fill="x", padx=4, pady=(2, 4))
e = ttk.Entry(pat_row, textvariable=sv, width=40)
e.pack(side="left", fill="x", expand=True)
def _start_pick_nr():
_do_smart_pick(pick_btn_nr, lambda t: sv.set(t))
def _clear_nr():
sv.set("")
tk.Label(pat_row, text="\u2715", font=("Segoe UI", 9),
bg="#E8F4FA", fg="#aaa", cursor="hand2").pack(
side="left", padx=(4, 0))
pat_row.winfo_children()[-1].bind(
"<Button-1>", lambda e: _clear_nr())
pick_btn_nr = tk.Button(
pat_row, text="\U0001f58c Pinsel",
font=("Segoe UI", 8), bg="#dde8f0",
fg="#1a4d6d", relief="flat", cursor="hand2",
command=_start_pick_nr, bd=0, padx=6, pady=1)
pick_btn_nr.pack(side="left", padx=(4, 0))
add_tooltip(pick_btn_nr,
"Text in anderer App markieren - wird automatisch uebernommen")
field_widgets[k] = sv
else:
t = tk.Text(body, height=3, wrap="word",
font=("Segoe UI", _empfang_font_size[0]),
bg="white", fg="#1a2a3a", relief="solid", bd=1,
@@ -6282,7 +6418,10 @@ WICHTIG unbedingt einhalten:
if w is None:
return ""
if isinstance(w, tk.StringVar):
return w.get().strip()
tt = w.get().strip()
if tt == _pat_ph:
return ""
return tt
val = w.get("1.0", "end").strip()
if val in _empty_placeholders:
return ""
@@ -6378,13 +6517,14 @@ WICHTIG unbedingt einhalten:
def _flash_bg(success):
color = "#e8f5e9" if success else "#fce4ec"
orig = "#E8F4FA"
for w in [outer, hdr] + list(hdr.winfo_children()):
_flash_widgets = [outer, hdr, nr_banner_row]
for w in _flash_widgets + list(hdr.winfo_children()):
try:
w.configure(bg=color)
except Exception:
pass
def _restore():
for w2 in [outer, hdr] + list(hdr.winfo_children()):
for w2 in _flash_widgets + list(hdr.winfo_children()):
try:
w2.configure(bg=orig)
except Exception:
@@ -6419,13 +6559,26 @@ WICHTIG unbedingt einhalten:
font=("Segoe UI", max(7, _empfang_font_size[0] - 2)))
chat_display.tag_configure("new_msg", background="#e8f5e9")
reply_entry = tk.Text(inner, height=4, wrap="word",
reply_holder = tk.Frame(inner, bg="#E8F4FA")
reply_holder.pack(fill="x", pady=(0, 2))
reply_entry = tk.Text(reply_holder, height=8, wrap="word",
font=("Segoe UI", _empfang_font_size[0]),
bg="white", fg="#1a2a3a", relief="solid", bd=1,
padx=4, pady=4)
reply_entry.pack(fill="x", pady=(0, 2))
reply_sb = ttk.Scrollbar(reply_holder, orient="vertical",
command=reply_entry.yview)
reply_entry.configure(yscrollcommand=reply_sb.set)
reply_entry.pack(side="left", fill="both", expand=True)
reply_sb.pack(side="right", fill="y")
_empfang_text_widgets.append(reply_entry)
def _reply_wheel(_e):
reply_entry.yview_scroll(int(-1 * (_e.delta / 120)), "units")
return "break"
reply_entry.bind("<MouseWheel>", _reply_wheel)
_reply_placeholder = "Antwort eingeben oder diktieren..."
reply_entry.insert("1.0", _reply_placeholder)
reply_entry.configure(fg="#aaa")
@@ -6489,14 +6642,27 @@ WICHTIG unbedingt einhalten:
def _push_to_chat_input(text: str, *, source: str = "") -> None:
"""Fuegt Text in das Chat-Eingabefeld unten ein (Platzhalter wird ersetzt)."""
try:
chunk = text.strip()
if source == "patient":
line = chunk if chunk.lower().startswith("nr.:") else f"Nr.: {_compact_nr(chunk)}"
cur = reply_entry.get("1.0", "end").strip()
empty = cur == _reply_placeholder or not cur
if empty:
reply_entry.delete("1.0", "end")
reply_entry.insert("1.0", line)
else:
reply_entry.insert("1.0", line + "\n")
reply_entry.configure(fg="#1a2a3a")
reply_entry.see("1.0")
return
cur = reply_entry.get("1.0", "end").strip()
if cur == _reply_placeholder or not cur:
reply_entry.delete("1.0", "end")
reply_entry.insert("1.0", text.strip())
reply_entry.insert("1.0", chunk)
else:
if not cur.endswith("\n"):
reply_entry.insert("end", "\n")
reply_entry.insert("end", text.strip())
reply_entry.insert("end", chunk)
reply_entry.configure(fg="#1a2a3a")
reply_entry.see("end")
except Exception:
@@ -6513,8 +6679,7 @@ WICHTIG unbedingt einhalten:
except Exception:
base_msgs = []
self._empfang_last_thread_messages = base_msgs
local_extra = list(getattr(self, "_empfang_local_chat_overlay", None) or [])
merged = base_msgs + local_extra
merged = base_msgs
chat_display.configure(state="normal")
chat_display.delete("1.0", "end")
for msg in merged:

View File

@@ -48,19 +48,22 @@
"dermatology"
],
"ui_font_delta": 0,
"global_right_click_paste": true,
"global_right_click_paste": false,
"todo_auto_open": false,
"autocopy_after_diktat": true,
"kommentare_auto_open": true,
"empfang_auto_open": true,
"empfang_was_open": false,
"empfang_prefs": {
"show_patient": false,
"show_ther": false,
"show_proc": false,
"show_kom": false,
"last_patient": "",
"geometry": "776x779+2208+366"
"show_patient": true,
"show_ther": true,
"show_proc": true,
"show_kom": true,
"last_patient": "24227",
"geometry": "888x974+442+450",
"auto_copy_ther": true,
"auto_copy_patient": false,
"auto_copy_proc": true
},
"medikament_quelle": "compendium.ch",
"diagnose_quelle": "",

View File

@@ -1,3 +1,4 @@
{
"⏺ Start": 16
"⏺ Start": 12,
"⏺ Aufnahme starten": 2
}

View File

@@ -1 +1 @@
420x380+2034+450
420x380+1187+399

View File

@@ -1 +1 @@
{"A": 0, "S": 0, "O": 0, "B": 0, "D": 3, "T": 0, "P": 0}
{"A": 0, "S": 0, "O": 0, "B": 0, "D": 0, "T": 0, "P": 0}

View File

@@ -1 +1 @@
{"used": 534202, "total": 1000000, "budget_dollars": 0, "used_dollars": 0}
{"used": 551259, "total": 1000000, "budget_dollars": 0, "used_dollars": 0}

View File

@@ -1 +1 @@
1123 1249 2580 227 0 0
1165 1249 1337 455 0 0

View File

@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.2.0",
"channel": "stable",
"release_date": "2026-03-14",
"minimum_supported_version": "1.0.0",