634 lines
21 KiB
Python
634 lines
21 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""Verwaltungsfenster für Autotext und Textblöcke — ruhige Office-Hülle, ohne alte Hauptfenster-Optik."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import threading
|
||
|
|
import tkinter as tk
|
||
|
|
from tkinter import messagebox
|
||
|
|
from tkinter.scrolledtext import ScrolledText
|
||
|
|
from typing import Dict
|
||
|
|
|
||
|
|
FF = "Segoe UI"
|
||
|
|
|
||
|
|
_AUTOTEXT_ATTR = "_aza_workspace_autotext_toplevel"
|
||
|
|
_TB_WIN_DICT_ATTR = "_aza_workspace_tb_toplevels"
|
||
|
|
|
||
|
|
|
||
|
|
def _palette_for_app(app) -> Dict[str, str]:
|
||
|
|
sh = getattr(app, "_aza_office_v1", None)
|
||
|
|
p = getattr(sh, "_palette", None) if sh is not None else None
|
||
|
|
if isinstance(p, dict) and p:
|
||
|
|
return dict(p)
|
||
|
|
try:
|
||
|
|
from aza_office_shell_v1 import PALETTE_LIGHT
|
||
|
|
|
||
|
|
return dict(PALETTE_LIGHT)
|
||
|
|
except Exception:
|
||
|
|
return {
|
||
|
|
"BG": "#EAF2F7",
|
||
|
|
"SURFACE": "#FFFFFF",
|
||
|
|
"BORDER": "#D6E2EB",
|
||
|
|
"TEXT": "#1A4D6D",
|
||
|
|
"SUBTLE": "#5C7A8E",
|
||
|
|
"ACCENT": "#5B8DB3",
|
||
|
|
"TEXT_AREA_BG": "#FFFFFF",
|
||
|
|
"TEXT_AREA_FG": "#1A4D6D",
|
||
|
|
"ACCENT_SOFT": "#E2EEF6",
|
||
|
|
"TEXT_STRONG": "#0F3850",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _header_bar(parent, palette: Dict[str, str], title: str) -> tk.Frame:
|
||
|
|
bar = tk.Frame(parent, bg=palette["ACCENT"], bd=0, highlightthickness=0)
|
||
|
|
bar.pack(fill="x")
|
||
|
|
tk.Label(
|
||
|
|
bar, text=title, bg=palette["ACCENT"], fg="white",
|
||
|
|
font=(FF, 10, "bold"), padx=12, pady=8, anchor="w",
|
||
|
|
).pack(side="left")
|
||
|
|
return bar
|
||
|
|
|
||
|
|
|
||
|
|
def _register_if_any(app, win: tk.Toplevel) -> None:
|
||
|
|
reg = getattr(app, "_register_window", None)
|
||
|
|
if callable(reg):
|
||
|
|
try:
|
||
|
|
reg(win)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
def _lift_above_main(win: tk.Toplevel, app: tk.Misc) -> None:
|
||
|
|
"""Hält den Dialog über dem Hauptfenster (Windows: transient + lift + topmost)."""
|
||
|
|
try:
|
||
|
|
win.transient(app)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
win.lift(app)
|
||
|
|
win.focus_force()
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
win.attributes("-topmost", True)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
def open_workspace_autotext_manager(app) -> None:
|
||
|
|
"""Genau ein Autotext-Fenster; Kürzel wie bisher über basis14 / Persistenz."""
|
||
|
|
from aza_ui_helpers import (
|
||
|
|
center_window,
|
||
|
|
load_toplevel_geometry,
|
||
|
|
save_toplevel_geometry,
|
||
|
|
)
|
||
|
|
from aza_persistence import load_autotext, save_autotext
|
||
|
|
from aza_workspace_sync import (
|
||
|
|
prune_autotext_meta,
|
||
|
|
schedule_workspace_cloud_push,
|
||
|
|
touch_autotext_entry_meta,
|
||
|
|
)
|
||
|
|
|
||
|
|
exist = getattr(app, _AUTOTEXT_ATTR, None)
|
||
|
|
if exist is not None:
|
||
|
|
try:
|
||
|
|
if exist.winfo_exists():
|
||
|
|
rf = getattr(exist, "_aza_reload_autotext_disk", None)
|
||
|
|
if callable(rf):
|
||
|
|
rf()
|
||
|
|
exist.deiconify()
|
||
|
|
_lift_above_main(exist, app)
|
||
|
|
return
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
p = _palette_for_app(app)
|
||
|
|
|
||
|
|
root = tk.Toplevel(app)
|
||
|
|
setattr(app, _AUTOTEXT_ATTR, root)
|
||
|
|
root.title("Autotext")
|
||
|
|
root.configure(bg=p["SURFACE"])
|
||
|
|
root.minsize(560, 540)
|
||
|
|
root.transient(app)
|
||
|
|
_register_if_any(app, root)
|
||
|
|
|
||
|
|
geom_name = "workspace_autotext"
|
||
|
|
saved = load_toplevel_geometry(geom_name)
|
||
|
|
if saved:
|
||
|
|
try:
|
||
|
|
root.geometry(saved)
|
||
|
|
except tk.TclError:
|
||
|
|
root.geometry("720x640")
|
||
|
|
center_window(root, 720, 640)
|
||
|
|
else:
|
||
|
|
root.geometry("720x640")
|
||
|
|
center_window(root, 720, 640)
|
||
|
|
|
||
|
|
dik_state = {"active": False}
|
||
|
|
|
||
|
|
def _clear_ref_and_close() -> None:
|
||
|
|
if dik_state["active"]:
|
||
|
|
dik_state["active"] = False
|
||
|
|
try:
|
||
|
|
rec = getattr(app, "recorder", None)
|
||
|
|
if rec is not None:
|
||
|
|
try:
|
||
|
|
rec.stop_and_save_wav()
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
if getattr(app, _AUTOTEXT_ATTR, None) is root:
|
||
|
|
setattr(app, _AUTOTEXT_ATTR, None)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
save_toplevel_geometry(geom_name, root.geometry())
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
root.attributes("-topmost", False)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
root.destroy()
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
root.protocol("WM_DELETE_WINDOW", _clear_ref_and_close)
|
||
|
|
_lift_above_main(root, app)
|
||
|
|
|
||
|
|
outer = tk.Frame(root, bg=p["SURFACE"])
|
||
|
|
outer.pack(fill="both", expand=True)
|
||
|
|
_header_bar(outer, p, "Autotext verwalten")
|
||
|
|
|
||
|
|
body = tk.Frame(outer, bg=p["SURFACE"])
|
||
|
|
body.pack(fill="both", expand=True, padx=12, pady=(0, 12))
|
||
|
|
|
||
|
|
btn_bar = tk.Frame(body, bg=p["SURFACE"])
|
||
|
|
btn_bar.pack(side="bottom", fill="x", pady=(10, 0))
|
||
|
|
|
||
|
|
top_part = tk.Frame(body, bg=p["SURFACE"])
|
||
|
|
top_part.pack(fill="both", expand=True)
|
||
|
|
|
||
|
|
tk.Label(
|
||
|
|
top_part,
|
||
|
|
text="Kürzel tippen und mit Leerzeichen, Tab oder Satzzeichen abschließen — "
|
||
|
|
"Ersetzung funktioniert in AzA und (wenn aktiv) global in anderen Programmen.",
|
||
|
|
bg=p["SURFACE"], fg=p["SUBTLE"], font=(FF, 9),
|
||
|
|
wraplength=640, justify="left",
|
||
|
|
).pack(anchor="w", pady=(10, 8))
|
||
|
|
|
||
|
|
data_ref = getattr(app, "_autotext_data", None)
|
||
|
|
if not isinstance(data_ref, dict):
|
||
|
|
messagebox.showerror("Autotext", "Interne Datenbasis fehlt.", parent=root)
|
||
|
|
_clear_ref_and_close()
|
||
|
|
return
|
||
|
|
|
||
|
|
def pull_disk_into_data_ref() -> None:
|
||
|
|
fresh = load_autotext()
|
||
|
|
if not isinstance(fresh, dict):
|
||
|
|
return
|
||
|
|
data_ref["enabled"] = bool(fresh.get("enabled", True))
|
||
|
|
fe = fresh.get("entries") if isinstance(fresh.get("entries"), dict) else {}
|
||
|
|
te = data_ref.setdefault("entries", {})
|
||
|
|
if isinstance(te, dict):
|
||
|
|
te.clear()
|
||
|
|
te.update(fe)
|
||
|
|
else:
|
||
|
|
data_ref["entries"] = dict(fe)
|
||
|
|
fm = fresh.get("entry_meta")
|
||
|
|
if isinstance(fm, dict):
|
||
|
|
tm = data_ref.setdefault("entry_meta", {})
|
||
|
|
if isinstance(tm, dict):
|
||
|
|
tm.clear()
|
||
|
|
tm.update(fm)
|
||
|
|
fts = fresh.get("workspace_backup_ts")
|
||
|
|
if fts is not None:
|
||
|
|
data_ref["workspace_backup_ts"] = fts
|
||
|
|
|
||
|
|
pull_disk_into_data_ref()
|
||
|
|
entries = data_ref.setdefault("entries", {})
|
||
|
|
|
||
|
|
enabled_var = tk.BooleanVar(value=bool(data_ref.get("enabled", True)))
|
||
|
|
|
||
|
|
tk.Checkbutton(
|
||
|
|
top_part, text="Autotext aktiv (In-App und global)",
|
||
|
|
variable=enabled_var,
|
||
|
|
command=lambda: _persist_enabled(),
|
||
|
|
bg=p["SURFACE"], fg=p["TEXT"], font=(FF, 9),
|
||
|
|
activebackground=p["SURFACE"], anchor="w", highlightthickness=0,
|
||
|
|
).pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
def _persist_enabled() -> None:
|
||
|
|
if not isinstance(data_ref, dict):
|
||
|
|
return
|
||
|
|
data_ref["enabled"] = bool(enabled_var.get())
|
||
|
|
save_autotext(data_ref)
|
||
|
|
schedule_workspace_cloud_push()
|
||
|
|
|
||
|
|
edit_grid = tk.Frame(top_part, bg=p["SURFACE"])
|
||
|
|
edit_grid.pack(fill="x", pady=(0, 6))
|
||
|
|
tk.Label(edit_grid, text="Kürzel", bg=p["SURFACE"], fg=p["TEXT"],
|
||
|
|
font=(FF, 9, "bold")).grid(row=0, column=0, sticky="w")
|
||
|
|
tk.Label(edit_grid, text="Ersetzungstext (mehrzeilig)", bg=p["SURFACE"],
|
||
|
|
fg=p["TEXT"], font=(FF, 9, "bold")).grid(row=0, column=1, sticky="w")
|
||
|
|
abbrev_var = tk.StringVar()
|
||
|
|
ae = tk.Entry(edit_grid, textvariable=abbrev_var, width=18, relief="solid", bd=1,
|
||
|
|
highlightthickness=1, highlightbackground=p["BORDER"],
|
||
|
|
fg=p["TEXT_AREA_FG"], insertbackground=p["TEXT_AREA_FG"],
|
||
|
|
bg=p["TEXT_AREA_BG"], font=(FF, 10))
|
||
|
|
ae.grid(row=1, column=0, sticky="nw", padx=(0, 12), pady=4)
|
||
|
|
repl_box = tk.Text(
|
||
|
|
edit_grid, wrap="word", height=5, width=52, relief="flat", bd=0,
|
||
|
|
highlightthickness=1, highlightbackground=p["BORDER"],
|
||
|
|
bg=p["TEXT_AREA_BG"], fg=p["TEXT_AREA_FG"], font=(FF, 10),
|
||
|
|
padx=6, pady=6,
|
||
|
|
)
|
||
|
|
repl_box.grid(row=1, column=1, sticky="ew", pady=4)
|
||
|
|
edit_grid.columnconfigure(1, weight=1)
|
||
|
|
|
||
|
|
lst_fr = tk.Frame(top_part, bg=p["SURFACE"])
|
||
|
|
lst_fr.pack(fill="both", expand=True, pady=(6, 0))
|
||
|
|
tk.Label(lst_fr, text="Gespeicherte Kürzel", bg=p["SURFACE"],
|
||
|
|
fg=p["TEXT"], font=(FF, 9, "bold")).pack(anchor="w")
|
||
|
|
|
||
|
|
lb_wrap = tk.Frame(lst_fr, bg=p["SURFACE"], highlightthickness=1,
|
||
|
|
highlightbackground=p["BORDER"])
|
||
|
|
lb_wrap.pack(fill="both", expand=False, pady=4)
|
||
|
|
|
||
|
|
scrollbar = tk.Scrollbar(lb_wrap)
|
||
|
|
scrollbar.pack(side="right", fill="y")
|
||
|
|
list_height = 5
|
||
|
|
listbox = tk.Listbox(
|
||
|
|
lb_wrap, height=list_height, font=(FF, 10), activestyle="dotbox",
|
||
|
|
yscrollcommand=scrollbar.set,
|
||
|
|
bg=p["TEXT_AREA_BG"], fg=p["TEXT_AREA_FG"],
|
||
|
|
selectbackground=p.get("ACCENT_SOFT", "#E2EEF6"),
|
||
|
|
selectforeground=p.get("TEXT_STRONG", p["TEXT"]),
|
||
|
|
borderwidth=0, highlightthickness=0,
|
||
|
|
)
|
||
|
|
scrollbar.config(command=listbox.yview)
|
||
|
|
listbox.pack(side="left", fill="both", expand=False)
|
||
|
|
|
||
|
|
def refresh_list() -> None:
|
||
|
|
listbox.delete(0, "end")
|
||
|
|
ks = sorted(entries.keys(), key=lambda s: (s.lower(), s))
|
||
|
|
for k in ks:
|
||
|
|
val = entries.get(k, "") or ""
|
||
|
|
short = val.replace("\n", " ")
|
||
|
|
suffix = (" …" + short[-28:]) if len(short) > 48 else ""
|
||
|
|
preview = short[:48] + suffix if len(short) > 48 else short
|
||
|
|
listbox.insert("end", f"«{k}» {preview}")
|
||
|
|
|
||
|
|
def persist() -> None:
|
||
|
|
ent = data_ref.setdefault("entries", {})
|
||
|
|
prune_autotext_meta(data_ref, set(ent.keys()))
|
||
|
|
save_autotext(data_ref)
|
||
|
|
schedule_workspace_cloud_push()
|
||
|
|
refresh_list()
|
||
|
|
|
||
|
|
refresh_list()
|
||
|
|
|
||
|
|
def on_pick(_evt=None) -> None:
|
||
|
|
sel = listbox.curselection()
|
||
|
|
if not sel:
|
||
|
|
return
|
||
|
|
keys_sorted = sorted(entries.keys(), key=lambda s: (s.lower(), s))
|
||
|
|
i = sel[0]
|
||
|
|
if i >= len(keys_sorted):
|
||
|
|
return
|
||
|
|
k = keys_sorted[i]
|
||
|
|
abbrev_var.set(k)
|
||
|
|
repl_box.delete("1.0", "end")
|
||
|
|
repl_box.insert("1.0", entries.get(k, ""))
|
||
|
|
|
||
|
|
listbox.bind("<<ListboxSelect>>", on_pick)
|
||
|
|
|
||
|
|
def reload_ui_full() -> None:
|
||
|
|
pull_disk_into_data_ref()
|
||
|
|
try:
|
||
|
|
enabled_var.set(bool(data_ref.get("enabled", True)))
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
refresh_list()
|
||
|
|
abbrev_var.set("")
|
||
|
|
try:
|
||
|
|
repl_box.delete("1.0", "end")
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
root._aza_reload_autotext_disk = reload_ui_full
|
||
|
|
|
||
|
|
def do_save_entry() -> None:
|
||
|
|
ab = abbrev_var.get().strip()
|
||
|
|
if not ab:
|
||
|
|
messagebox.showinfo("Autotext", "Bitte ein Kürzel eingeben.", parent=root)
|
||
|
|
return
|
||
|
|
entries[ab] = repl_box.get("1.0", "end").rstrip("\n")
|
||
|
|
touch_autotext_entry_meta(data_ref, ab)
|
||
|
|
persist()
|
||
|
|
getattr(app, "set_status", lambda *_: None)("Autotext gespeichert.")
|
||
|
|
abbrev_var.set("")
|
||
|
|
repl_box.delete("1.0", "end")
|
||
|
|
|
||
|
|
def do_delete_entry() -> None:
|
||
|
|
sel = listbox.curselection()
|
||
|
|
if not sel:
|
||
|
|
messagebox.showinfo("Autotext", "Bitte ein Kürzel in der Liste wählen.",
|
||
|
|
parent=root)
|
||
|
|
return
|
||
|
|
keys_sorted = sorted(entries.keys(), key=lambda s: (s.lower(), s))
|
||
|
|
i = sel[0]
|
||
|
|
if i >= len(keys_sorted):
|
||
|
|
return
|
||
|
|
k_del = keys_sorted[i]
|
||
|
|
if not messagebox.askyesno("Autotext", f"Kürzel «{k_del}» wirklich löschen?",
|
||
|
|
parent=root):
|
||
|
|
return
|
||
|
|
entries.pop(k_del, None)
|
||
|
|
em = data_ref.get("entry_meta")
|
||
|
|
if isinstance(em, dict):
|
||
|
|
em.pop(k_del, None)
|
||
|
|
persist()
|
||
|
|
abbrev_var.set("")
|
||
|
|
repl_box.delete("1.0", "end")
|
||
|
|
|
||
|
|
def _diktat_toggle() -> None:
|
||
|
|
if not dik_state["active"]:
|
||
|
|
if not callable(getattr(app, "ensure_ready", None)):
|
||
|
|
return
|
||
|
|
if not app.ensure_ready():
|
||
|
|
messagebox.showinfo(
|
||
|
|
"Diktat",
|
||
|
|
"KI-Verbindung noch nicht bereit.",
|
||
|
|
parent=root,
|
||
|
|
)
|
||
|
|
return
|
||
|
|
if callable(getattr(app, "_check_ai_consent", None)):
|
||
|
|
try:
|
||
|
|
if not app._check_ai_consent():
|
||
|
|
return
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
mic_fn = getattr(app, "_ensure_microphone_ready", None)
|
||
|
|
recorder = getattr(app, "recorder", None)
|
||
|
|
transcribe_fn = getattr(app, "transcribe_wav", None)
|
||
|
|
if recorder is None or transcribe_fn is None or not callable(mic_fn):
|
||
|
|
messagebox.showinfo("Mikrofon", "Recorder nicht verfügbar.", parent=root)
|
||
|
|
return
|
||
|
|
if not mic_fn():
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
recorder.start()
|
||
|
|
except Exception as exc:
|
||
|
|
messagebox.showwarning("Aufnahme", str(exc), parent=root)
|
||
|
|
return
|
||
|
|
dik_state["active"] = True
|
||
|
|
try:
|
||
|
|
btn_diktat.configure(text="Stoppen")
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
return
|
||
|
|
|
||
|
|
dik_state["active"] = False
|
||
|
|
try:
|
||
|
|
btn_diktat.configure(text="Diktat")
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
wav_path = None
|
||
|
|
try:
|
||
|
|
rec = getattr(app, "recorder", None)
|
||
|
|
if rec is None:
|
||
|
|
return
|
||
|
|
wav_path = rec.stop_and_save_wav()
|
||
|
|
except Exception:
|
||
|
|
return
|
||
|
|
|
||
|
|
def worker():
|
||
|
|
try:
|
||
|
|
from aza_audio import persist_audio_safe
|
||
|
|
|
||
|
|
transcribe_fn = getattr(app, "transcribe_wav", None)
|
||
|
|
if wav_path is None or transcribe_fn is None:
|
||
|
|
return
|
||
|
|
safe_path = None
|
||
|
|
try:
|
||
|
|
safe_path = persist_audio_safe(wav_path)
|
||
|
|
except Exception:
|
||
|
|
safe_path = wav_path
|
||
|
|
text_raw = transcribe_fn(safe_path)
|
||
|
|
raw = text_raw if isinstance(text_raw, str) else str(text_raw or "")
|
||
|
|
txt = raw.strip()
|
||
|
|
|
||
|
|
dik_apply = getattr(app, "_diktat_apply_punctuation", None)
|
||
|
|
if callable(dik_apply):
|
||
|
|
try:
|
||
|
|
txt = dik_apply(txt)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
if safe_path and safe_path != wav_path:
|
||
|
|
try:
|
||
|
|
import os
|
||
|
|
os.remove(wav_path)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def ins() -> None:
|
||
|
|
try:
|
||
|
|
if root.winfo_exists():
|
||
|
|
chunk = (txt or "").strip()
|
||
|
|
if chunk:
|
||
|
|
cur = repl_box.get("1.0", "end").rstrip()
|
||
|
|
if not cur:
|
||
|
|
repl_box.insert("1.0", chunk)
|
||
|
|
else:
|
||
|
|
if not cur.endswith("\n"):
|
||
|
|
repl_box.insert(tk.END, "\n")
|
||
|
|
repl_box.insert(tk.END, chunk)
|
||
|
|
repl_box.see(tk.INSERT)
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
app.after(0, ins)
|
||
|
|
except Exception as exc_inner:
|
||
|
|
|
||
|
|
def err() -> None:
|
||
|
|
messagebox.showerror(
|
||
|
|
"Fehler bei Transkription", str(exc_inner), parent=root,
|
||
|
|
)
|
||
|
|
|
||
|
|
app.after(0, err)
|
||
|
|
|
||
|
|
threading.Thread(target=worker, daemon=True).start()
|
||
|
|
|
||
|
|
tk.Button(btn_bar, text="Speichern / Aktualisieren", command=do_save_entry,
|
||
|
|
bg=p["ACCENT"], fg="white", font=(FF, 9), relief="flat", padx=12, pady=6,
|
||
|
|
cursor="hand2", activebackground="#4A7A9E",
|
||
|
|
).pack(side="left", padx=(0, 6))
|
||
|
|
|
||
|
|
btn_diktat = tk.Button(
|
||
|
|
btn_bar, text="Diktat", command=_diktat_toggle,
|
||
|
|
bg=p["SURFACE"], fg=p["TEXT"], font=(FF, 9), relief="solid", bd=1,
|
||
|
|
padx=10, pady=5, cursor="hand2",
|
||
|
|
)
|
||
|
|
btn_diktat.pack(side="left", padx=(0, 6))
|
||
|
|
|
||
|
|
tk.Button(btn_bar, text="Löschen", command=do_delete_entry,
|
||
|
|
bg=p["SURFACE"], fg=p["TEXT"], font=(FF, 9),
|
||
|
|
relief="solid", bd=1, padx=10, pady=5, cursor="hand2",
|
||
|
|
).pack(side="left", padx=(0, 6))
|
||
|
|
tk.Button(btn_bar, text="Schließen", command=_clear_ref_and_close,
|
||
|
|
bg=p["SURFACE"], fg=p["SUBTLE"], font=(FF, 9),
|
||
|
|
relief="solid", bd=1, padx=10, pady=5, cursor="hand2",
|
||
|
|
).pack(side="right")
|
||
|
|
|
||
|
|
|
||
|
|
def _tb_windows_map(app):
|
||
|
|
d = getattr(app, _TB_WIN_DICT_ATTR, None)
|
||
|
|
if not isinstance(d, dict):
|
||
|
|
d = {}
|
||
|
|
setattr(app, _TB_WIN_DICT_ATTR, d)
|
||
|
|
return d
|
||
|
|
|
||
|
|
|
||
|
|
def open_workspace_textblock_editor(app, slot_key: str) -> None:
|
||
|
|
from aza_ui_helpers import (
|
||
|
|
center_window,
|
||
|
|
load_toplevel_geometry,
|
||
|
|
save_toplevel_geometry,
|
||
|
|
)
|
||
|
|
from aza_persistence import load_textbloecke, save_textbloecke
|
||
|
|
from aza_workspace_sync import schedule_workspace_cloud_push, utc_now_iso
|
||
|
|
|
||
|
|
p = _palette_for_app(app)
|
||
|
|
key = str(slot_key).strip()
|
||
|
|
if not key.isdigit():
|
||
|
|
return
|
||
|
|
|
||
|
|
wins = _tb_windows_map(app)
|
||
|
|
prev = wins.get(key)
|
||
|
|
if prev is not None:
|
||
|
|
try:
|
||
|
|
if prev.winfo_exists():
|
||
|
|
prev.deiconify()
|
||
|
|
_lift_above_main(prev, app)
|
||
|
|
return
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
data = load_textbloecke()
|
||
|
|
slot = dict(data.get(key) or {})
|
||
|
|
|
||
|
|
root = tk.Toplevel(app)
|
||
|
|
wins[key] = root
|
||
|
|
|
||
|
|
root.title(slot.get("name") or f"Textblock {key}")
|
||
|
|
root.configure(bg=p["SURFACE"])
|
||
|
|
root.minsize(500, 360)
|
||
|
|
root.transient(app)
|
||
|
|
_register_if_any(app, root)
|
||
|
|
|
||
|
|
geom_name = f"workspace_tb_{key}"
|
||
|
|
saved = load_toplevel_geometry(geom_name)
|
||
|
|
if saved:
|
||
|
|
try:
|
||
|
|
root.geometry(saved)
|
||
|
|
except tk.TclError:
|
||
|
|
root.geometry("660x520")
|
||
|
|
center_window(root, 660, 520)
|
||
|
|
else:
|
||
|
|
root.geometry("660x520")
|
||
|
|
center_window(root, 660, 520)
|
||
|
|
|
||
|
|
def _close_tb() -> None:
|
||
|
|
wm = _tb_windows_map(app)
|
||
|
|
if wm.get(key) is root:
|
||
|
|
wm.pop(key, None)
|
||
|
|
try:
|
||
|
|
save_toplevel_geometry(geom_name, root.geometry())
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
root.attributes("-topmost", False)
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
try:
|
||
|
|
root.destroy()
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
|
||
|
|
root.protocol("WM_DELETE_WINDOW", _close_tb)
|
||
|
|
_lift_above_main(root, app)
|
||
|
|
|
||
|
|
outer = tk.Frame(root, bg=p["SURFACE"])
|
||
|
|
outer.pack(fill="both", expand=True)
|
||
|
|
|
||
|
|
ttl = slot.get("name") or f"Textblock {key}"
|
||
|
|
_header_bar(outer, p, ttl)
|
||
|
|
|
||
|
|
body = tk.Frame(outer, bg=p["SURFACE"])
|
||
|
|
body.pack(fill="both", expand=True, padx=14, pady=12)
|
||
|
|
|
||
|
|
tk.Label(body, text="Anzeigename", bg=p["SURFACE"], fg=p["TEXT"],
|
||
|
|
font=(FF, 9, "bold")).pack(anchor="w")
|
||
|
|
name_var = tk.StringVar(value=str(slot.get("name") or ttl))
|
||
|
|
tk.Entry(body, textvariable=name_var, width=40,
|
||
|
|
relief="solid", bd=1, bg=p["TEXT_AREA_BG"], fg=p["TEXT_AREA_FG"],
|
||
|
|
insertbackground=p["TEXT_AREA_FG"],
|
||
|
|
highlightthickness=1, highlightbackground=p["BORDER"],
|
||
|
|
font=(FF, 10)).pack(fill="x", pady=(2, 10))
|
||
|
|
|
||
|
|
tk.Label(body, text="Inhalt", bg=p["SURFACE"], fg=p["TEXT"],
|
||
|
|
font=(FF, 9, "bold")).pack(anchor="w")
|
||
|
|
txt = ScrolledText(
|
||
|
|
body, wrap="word", height=16, font=(FF, 10),
|
||
|
|
bg=p["TEXT_AREA_BG"], fg=p["TEXT_AREA_FG"],
|
||
|
|
insertbackground=p["TEXT_AREA_FG"],
|
||
|
|
relief="flat", bd=0, highlightthickness=1,
|
||
|
|
highlightbackground=p["BORDER"], padx=8, pady=8,
|
||
|
|
)
|
||
|
|
txt.pack(fill="both", expand=True, pady=(4, 10))
|
||
|
|
txt.insert("1.0", slot.get("content") or "")
|
||
|
|
|
||
|
|
def do_save() -> None:
|
||
|
|
fresh = load_textbloecke()
|
||
|
|
nm = name_var.get().strip() or f"Textblock {key}"
|
||
|
|
fresh[key] = {
|
||
|
|
"name": nm,
|
||
|
|
"content": txt.get("1.0", "end").rstrip("\n"),
|
||
|
|
"updated_at": utc_now_iso(),
|
||
|
|
}
|
||
|
|
save_textbloecke(fresh)
|
||
|
|
schedule_workspace_cloud_push()
|
||
|
|
shell = getattr(app, "_aza_office_v1", None)
|
||
|
|
if shell is not None and hasattr(shell, "refresh_sidebar_textbloecke_section"):
|
||
|
|
try:
|
||
|
|
shell.refresh_sidebar_textbloecke_section()
|
||
|
|
except Exception:
|
||
|
|
pass
|
||
|
|
getattr(app, "set_status", lambda *_: None)(f"Textblock {key} gespeichert.")
|
||
|
|
try:
|
||
|
|
root.title(nm)
|
||
|
|
except tk.TclError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
bar_fr = tk.Frame(body, bg=p["SURFACE"])
|
||
|
|
bar_fr.pack(fill="x")
|
||
|
|
tk.Button(bar_fr, text="Speichern", command=do_save,
|
||
|
|
bg=p["ACCENT"], fg="white", font=(FF, 9), relief="flat",
|
||
|
|
padx=14, pady=6, cursor="hand2",
|
||
|
|
).pack(side="left", padx=(0, 8))
|
||
|
|
tk.Button(bar_fr, text="Schließen", command=_close_tb,
|
||
|
|
bg=p["SURFACE"], fg=p["SUBTLE"], font=(FF, 9), relief="solid",
|
||
|
|
bd=1, padx=12, pady=5, cursor="hand2",
|
||
|
|
).pack(side="left")
|
||
|
|
|
||
|
|
|
||
|
|
__all__ = [
|
||
|
|
"open_workspace_autotext_manager",
|
||
|
|
"open_workspace_textblock_editor",
|
||
|
|
]
|