diff --git a/AzA march 2026/_build_info.py b/AzA march 2026/_build_info.py index 93985e9d..2bd1ae83 100644 --- a/AzA march 2026/_build_info.py +++ b/AzA march 2026/_build_info.py @@ -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 diff --git a/AzA march 2026/aza_office_shell_v1.py b/AzA march 2026/aza_office_shell_v1.py index d2bbfa8f..c9876296 100644 --- a/AzA march 2026/aza_office_shell_v1.py +++ b/AzA march 2026/aza_office_shell_v1.py @@ -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("", 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( diff --git a/AzA march 2026/basis14.py b/AzA march 2026/basis14.py index 0452b7b5..d557c8e7 100644 --- a/AzA march 2026/basis14.py +++ b/AzA march 2026/basis14.py @@ -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,77 +5665,44 @@ 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: - 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) - except Exception: - pass + val = (section_values.get(key) or "").strip() + if not val: + continue + try: + if key == "ther": + push_fn(_sync_fmt_ther(val), source=key) + else: + push_fn(_sync_fmt_proc(val), source=key) + except Exception: + pass def _empfang_background_poll(self): """Prueft im Hintergrund auf neue Empfangs-Nachrichten und zeigt Toast.""" @@ -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,11 +5952,142 @@ 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("", _pat_focus_in) + patient_ent.bind("", _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("", _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("", _patient_paste_from_clipboard) + patient_ent.bind("", _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("", _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), + "ther": tk.BooleanVar(value=True), + "proc": tk.BooleanVar(value=True), } _section_rows: dict[str, tk.Frame] = {} @@ -6005,81 +6199,6 @@ WICHTIG unbedingt einhalten: _w.bind("", lambda e, ww=_w: ww.configure(fg="#1a4d6d")) _w.bind("", 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("", lambda e: canvas.unbind_all("") if e.widget is dlg else None) _field_defs_raw = [ - ("patient", "Nr.", prefs.get("last_patient", "")), - ("ther", "Therapieplan", extracted["therapieplan"]), - ("proc", "Procedere", extracted["procedere"]), + ("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,98 +6291,97 @@ 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("", _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( - "", 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 + t = tk.Text(body, height=3, wrap="word", + font=("Segoe UI", _empfang_font_size[0]), + bg="white", fg="#1a2a3a", relief="solid", bd=1, + insertbackground="#1a4d6d", padx=4, pady=4) + t.pack(fill="x", padx=4, pady=(2, 4)) + _ph = "Text eingeben oder diktieren..." + if init: + t.insert("1.0", init) else: - t = tk.Text(body, height=3, wrap="word", - font=("Segoe UI", _empfang_font_size[0]), - bg="white", fg="#1a2a3a", relief="solid", bd=1, - insertbackground="#1a4d6d", padx=4, pady=4) - t.pack(fill="x", padx=4, pady=(2, 4)) - _ph = "Text eingeben oder diktieren..." - if init: - t.insert("1.0", init) - else: - t.insert("1.0", _ph) - t.configure(fg="#aaa") + t.insert("1.0", _ph) + t.configure(fg="#aaa") - def _ph_focus_in(e, _t=t, _p=_ph): - if _t.get("1.0", "end").strip() == _p: - _t.delete("1.0", "end") - _t.configure(fg="#1a2a3a") + def _ph_focus_in(e, _t=t, _p=_ph): + if _t.get("1.0", "end").strip() == _p: + _t.delete("1.0", "end") + _t.configure(fg="#1a2a3a") - def _ph_focus_out(e, _t=t, _p=_ph): - if not _t.get("1.0", "end").strip(): - _t.insert("1.0", _p) - _t.configure(fg="#aaa") + def _ph_focus_out(e, _t=t, _p=_ph): + if not _t.get("1.0", "end").strip(): + _t.insert("1.0", _p) + _t.configure(fg="#aaa") - t.bind("", _ph_focus_in, add="+") - t.bind("", _ph_focus_out, add="+") - field_widgets[k] = t - _empfang_text_widgets.append(t) + t.bind("", _ph_focus_in, add="+") + t.bind("", _ph_focus_out, add="+") + field_widgets[k] = t + _empfang_text_widgets.append(t) if v.get(): body.pack(fill="x") @@ -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("", _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: diff --git a/AzA march 2026/kg_diktat_autotext.json b/AzA march 2026/kg_diktat_autotext.json index 2db7925b..d7bf08f4 100644 --- a/AzA march 2026/kg_diktat_autotext.json +++ b/AzA march 2026/kg_diktat_autotext.json @@ -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": "", diff --git a/AzA march 2026/kg_diktat_button_heat.json b/AzA march 2026/kg_diktat_button_heat.json index b2c5390b..b34ecda4 100644 --- a/AzA march 2026/kg_diktat_button_heat.json +++ b/AzA march 2026/kg_diktat_button_heat.json @@ -1,3 +1,4 @@ { - "⏺ Start": 16 + "⏺ Start": 12, + "⏺ Aufnahme starten": 2 } \ No newline at end of file diff --git a/AzA march 2026/kg_diktat_diktat_window.txt b/AzA march 2026/kg_diktat_diktat_window.txt index b8f7633c..da9424ad 100644 --- a/AzA march 2026/kg_diktat_diktat_window.txt +++ b/AzA march 2026/kg_diktat_diktat_window.txt @@ -1 +1 @@ -420x380+2034+450 \ No newline at end of file +420x380+1187+399 \ No newline at end of file diff --git a/AzA march 2026/kg_diktat_soap_section_levels.json b/AzA march 2026/kg_diktat_soap_section_levels.json index 50b0c6d5..c31bf74a 100644 --- a/AzA march 2026/kg_diktat_soap_section_levels.json +++ b/AzA march 2026/kg_diktat_soap_section_levels.json @@ -1 +1 @@ -{"A": 0, "S": 0, "O": 0, "B": 0, "D": 3, "T": 0, "P": 0} \ No newline at end of file +{"A": 0, "S": 0, "O": 0, "B": 0, "D": 0, "T": 0, "P": 0} \ No newline at end of file diff --git a/AzA march 2026/kg_diktat_token_usage.txt b/AzA march 2026/kg_diktat_token_usage.txt index 9dfc7343..2bbed4b6 100644 --- a/AzA march 2026/kg_diktat_token_usage.txt +++ b/AzA march 2026/kg_diktat_token_usage.txt @@ -1 +1 @@ -{"used": 534202, "total": 1000000, "budget_dollars": 0, "used_dollars": 0} \ No newline at end of file +{"used": 551259, "total": 1000000, "budget_dollars": 0, "used_dollars": 0} \ No newline at end of file diff --git a/AzA march 2026/kg_diktat_window.txt b/AzA march 2026/kg_diktat_window.txt index f3fa77d6..d0bf1a54 100644 --- a/AzA march 2026/kg_diktat_window.txt +++ b/AzA march 2026/kg_diktat_window.txt @@ -1 +1 @@ -1123 1249 2580 227 0 0 +1165 1249 1337 455 0 0 diff --git a/AzA march 2026/release/version.json b/AzA march 2026/release/version.json index d507c7cc..afdb6eca 100644 --- a/AzA march 2026/release/version.json +++ b/AzA march 2026/release/version.json @@ -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",