336 lines
14 KiB
Python
336 lines
14 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
AzaOrdnerMixin – Ordner-Fenster (Ablage: KG, Briefe, Rezepte, KOGU, Diktat; Export/Import).
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import tkinter as tk
|
|||
|
|
from tkinter import ttk, messagebox
|
|||
|
|
|
|||
|
|
from aza_persistence import (
|
|||
|
|
ensure_ablage_dirs,
|
|||
|
|
_ablage_base_path,
|
|||
|
|
load_ordner_geometry,
|
|||
|
|
save_ordner_geometry,
|
|||
|
|
list_ablage_files,
|
|||
|
|
get_ablage_content,
|
|||
|
|
save_to_ablage,
|
|||
|
|
count_entries_older_than,
|
|||
|
|
delete_entries_older_than,
|
|||
|
|
save_autotext,
|
|||
|
|
_clamp_geometry_str,
|
|||
|
|
)
|
|||
|
|
from aza_ui_helpers import center_window, add_resize_grip, add_font_scale_control, RoundedButton
|
|||
|
|
from aza_config import ABLAGE_SUBFOLDERS
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AzaOrdnerMixin:
|
|||
|
|
"""Mixin für das Ordner-/Ablage-Fenster (Speichern, Laden, Export/Import)."""
|
|||
|
|
|
|||
|
|
def open_ordner_window(self):
|
|||
|
|
"""Fenster für Ablage: KG, Briefe, Rezepte, Kostengutsprachen in Unterordnern; Export/Import. Bleibt sichtbar bis es geschlossen wird."""
|
|||
|
|
ensure_ablage_dirs()
|
|||
|
|
base_path = _ablage_base_path()
|
|||
|
|
ORDNER_MIN_W, ORDNER_MIN_H = 750, 600
|
|||
|
|
win = tk.Toplevel(self)
|
|||
|
|
win.title("Ordner – Ablage & Export/Import")
|
|||
|
|
win.minsize(ORDNER_MIN_W, ORDNER_MIN_H)
|
|||
|
|
win.configure(bg="#B9ECFA")
|
|||
|
|
win.attributes("-topmost", True)
|
|||
|
|
if hasattr(self, "_aza_windows"):
|
|||
|
|
self._aza_windows.add(win)
|
|||
|
|
self._register_window(win)
|
|||
|
|
|
|||
|
|
# Fensterposition: gespeichert laden oder zentrieren
|
|||
|
|
saved_geom = load_ordner_geometry()
|
|||
|
|
if saved_geom:
|
|||
|
|
try:
|
|||
|
|
win.geometry(_clamp_geometry_str(saved_geom, ORDNER_MIN_W, ORDNER_MIN_H))
|
|||
|
|
except Exception:
|
|||
|
|
win.geometry("800x650")
|
|||
|
|
center_window(win, 800, 650)
|
|||
|
|
else:
|
|||
|
|
# Keine gespeicherte Position → zentrieren
|
|||
|
|
win.geometry("640x500")
|
|||
|
|
center_window(win, 640, 500)
|
|||
|
|
|
|||
|
|
def save_ordner_geom():
|
|||
|
|
try:
|
|||
|
|
save_ordner_geometry(win.geometry())
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
_ordner_geom_after_id = [None]
|
|||
|
|
def on_ordner_configure(e):
|
|||
|
|
if e.widget is win and _ordner_geom_after_id[0]:
|
|||
|
|
self.after_cancel(_ordner_geom_after_id[0])
|
|||
|
|
if e.widget is win:
|
|||
|
|
_ordner_geom_after_id[0] = self.after(400, save_ordner_geom)
|
|||
|
|
win.bind("<Configure>", on_ordner_configure)
|
|||
|
|
def on_ordner_close():
|
|||
|
|
try:
|
|||
|
|
save_ordner_geometry(win.geometry())
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
if hasattr(self, "_aza_windows"):
|
|||
|
|
self._aza_windows.discard(win)
|
|||
|
|
win.destroy()
|
|||
|
|
win.protocol("WM_DELETE_WINDOW", on_ordner_close)
|
|||
|
|
add_resize_grip(win, ORDNER_MIN_W, ORDNER_MIN_H)
|
|||
|
|
add_font_scale_control(win)
|
|||
|
|
main_f = ttk.Frame(win, padding=12)
|
|||
|
|
main_f.pack(fill="both", expand=True)
|
|||
|
|
ttk.Label(main_f, text=f"Ablage: {base_path}").pack(anchor="w")
|
|||
|
|
auto_delete_var = tk.BooleanVar(
|
|||
|
|
value=bool(getattr(self, "_autotext_data", {}).get("ablage_auto_delete_old", True))
|
|||
|
|
)
|
|||
|
|
cb_auto_delete = ttk.Checkbutton(
|
|||
|
|
main_f,
|
|||
|
|
text="Automatisch löschen nach 2 Wochen (mit Nachfrage)",
|
|||
|
|
variable=auto_delete_var,
|
|||
|
|
)
|
|||
|
|
cb_auto_delete.pack(anchor="w", pady=(6, 2))
|
|||
|
|
|
|||
|
|
def _persist_auto_delete_pref():
|
|||
|
|
try:
|
|||
|
|
if hasattr(self, "_autotext_data"):
|
|||
|
|
self._autotext_data["ablage_auto_delete_old"] = bool(auto_delete_var.get())
|
|||
|
|
save_autotext(self._autotext_data)
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
cb_auto_delete.configure(command=_persist_auto_delete_pref)
|
|||
|
|
# ─── Tab-Leiste (blaues Design) ───
|
|||
|
|
_tab_style_active = {"bg": "#E8F4FA", "fg": "#1a4d6d", "font": ("Segoe UI", 10, "bold")}
|
|||
|
|
_tab_style_inactive = {"bg": "#A8D8E8", "fg": "#5A90B0", "font": ("Segoe UI", 10)}
|
|||
|
|
|
|||
|
|
tab_bar = tk.Frame(main_f, bg="#A8D8E8")
|
|||
|
|
tab_bar.pack(fill="x", pady=(8, 0))
|
|||
|
|
|
|||
|
|
_ordner_pages = {}
|
|||
|
|
_ordner_tab_btns = {}
|
|||
|
|
_active_ordner_tab = [None]
|
|||
|
|
|
|||
|
|
for cat in ABLAGE_SUBFOLDERS:
|
|||
|
|
page = tk.Frame(main_f, bg="#E8F4FA")
|
|||
|
|
_ordner_pages[cat] = page
|
|||
|
|
style = _tab_style_active if _active_ordner_tab[0] is None else _tab_style_inactive
|
|||
|
|
if _active_ordner_tab[0] is None:
|
|||
|
|
_active_ordner_tab[0] = cat
|
|||
|
|
btn = tk.Label(tab_bar, text=cat, cursor="hand2", padx=14, pady=5, **style)
|
|||
|
|
btn.pack(side="left")
|
|||
|
|
_ordner_tab_btns[cat] = btn
|
|||
|
|
|
|||
|
|
def _switch_ordner_tab(tab_name):
|
|||
|
|
_active_ordner_tab[0] = tab_name
|
|||
|
|
for key, btn in _ordner_tab_btns.items():
|
|||
|
|
btn.configure(**(_tab_style_active if key == tab_name else _tab_style_inactive))
|
|||
|
|
for key, page in _ordner_pages.items():
|
|||
|
|
page.pack_forget()
|
|||
|
|
_ordner_pages[tab_name].pack(fill="both", expand=True)
|
|||
|
|
|
|||
|
|
for cat in ABLAGE_SUBFOLDERS:
|
|||
|
|
_ordner_tab_btns[cat].bind("<Button-1>", lambda e, c=cat: _switch_ordner_tab(c))
|
|||
|
|
|
|||
|
|
_ordner_pages[_active_ordner_tab[0]].pack(fill="both", expand=True)
|
|||
|
|
|
|||
|
|
def refresh_list(listbox, category):
|
|||
|
|
listbox.delete(0, "end")
|
|||
|
|
for f in list_ablage_files(category):
|
|||
|
|
display_name = f[:-4] if (f and str(f).endswith(".txt")) else f
|
|||
|
|
listbox.insert("end", display_name)
|
|||
|
|
|
|||
|
|
def save_current(category):
|
|||
|
|
if category == "KG":
|
|||
|
|
content = self.txt_output.get("1.0", "end").strip()
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Keine Krankengeschichte zum Speichern.")
|
|||
|
|
return
|
|||
|
|
elif category == "Briefe":
|
|||
|
|
content = self._last_brief_text
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Zuerst einen Brief erstellen (Button Brief).")
|
|||
|
|
return
|
|||
|
|
elif category == "Rezepte":
|
|||
|
|
content = self._last_rezept_text
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Zuerst ein Rezept erstellen (Button Rezept).")
|
|||
|
|
return
|
|||
|
|
elif category == "Kostengutsprachen":
|
|||
|
|
content = self._last_kogu_text
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Zuerst eine Kostengutsprache erstellen (Button KOGU).")
|
|||
|
|
return
|
|||
|
|
elif category == "Diktat":
|
|||
|
|
content = self.txt_transcript.get("1.0", "end").strip()
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Kein Transkript zum Speichern vorhanden.")
|
|||
|
|
return
|
|||
|
|
elif category == "Transkript":
|
|||
|
|
content = self.txt_transcript.get("1.0", "end").strip()
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Kein Transkript zum Speichern vorhanden.")
|
|||
|
|
return
|
|||
|
|
else:
|
|||
|
|
return
|
|||
|
|
try:
|
|||
|
|
path = save_to_ablage(category, content)
|
|||
|
|
if path:
|
|||
|
|
messagebox.showinfo("Gespeichert", f"Gespeichert unter:\n{path}")
|
|||
|
|
for lb in listboxes:
|
|||
|
|
refresh_list(lb["listbox"], lb["category"])
|
|||
|
|
else:
|
|||
|
|
messagebox.showwarning("Hinweis", "Nichts gespeichert (Inhalt war leer).")
|
|||
|
|
except Exception as e:
|
|||
|
|
messagebox.showerror("Fehler", str(e))
|
|||
|
|
|
|||
|
|
def load_file_into_app(category, filename):
|
|||
|
|
content = get_ablage_content(category, filename)
|
|||
|
|
if not content:
|
|||
|
|
messagebox.showinfo("Hinweis", "Datei ist leer oder nicht gefunden.")
|
|||
|
|
return
|
|||
|
|
# Immer in neuem Fenster öffnen; Ordner-Fenster bleibt offen
|
|||
|
|
if category == "KG":
|
|||
|
|
self._show_text_window("KG (geladen)", content, buttons="kg")
|
|||
|
|
self.set_status("KG in neuem Fenster geöffnet.")
|
|||
|
|
elif category == "Briefe":
|
|||
|
|
self._last_brief_text = content
|
|||
|
|
self._show_text_window("Brief (geladen)", content, buttons="brief")
|
|||
|
|
self.set_status("Brief in neuem Fenster geöffnet.")
|
|||
|
|
elif category == "Rezepte":
|
|||
|
|
self._last_rezept_text = content
|
|||
|
|
self._show_text_window("Rezept (geladen)", content, buttons="rezept")
|
|||
|
|
self.set_status("Rezept in neuem Fenster geöffnet.")
|
|||
|
|
elif category == "Kostengutsprachen":
|
|||
|
|
self._last_kogu_text = content
|
|||
|
|
self._show_text_window("KOGU (geladen)", content, buttons="kogu")
|
|||
|
|
self.set_status("KOGU in neuem Fenster geöffnet.")
|
|||
|
|
elif category == "Diktat":
|
|||
|
|
self._show_text_window("Diktat (geladen)", content, buttons=None)
|
|||
|
|
self.set_status("Diktat in neuem Fenster geöffnet.")
|
|||
|
|
elif category == "Transkript":
|
|||
|
|
self._show_text_window("Transkript (geladen)", content, buttons=None)
|
|||
|
|
self.set_status("Transkript in neuem Fenster geöffnet.")
|
|||
|
|
|
|||
|
|
listboxes = []
|
|||
|
|
for cat in ABLAGE_SUBFOLDERS:
|
|||
|
|
page = _ordner_pages[cat]
|
|||
|
|
frame = tk.Frame(page, bg="#E8F4FA", padx=8, pady=4)
|
|||
|
|
frame.pack(fill="both", expand=True)
|
|||
|
|
tk.Label(frame, text="Aktuelles als neue Datei speichern (Nummer + Datum/Uhrzeit):",
|
|||
|
|
font=("Segoe UI", 9), bg="#E8F4FA", fg="#1a4d6d").pack(anchor="w")
|
|||
|
|
btn_row = tk.Frame(frame, bg="#E8F4FA")
|
|||
|
|
btn_row.pack(fill="x", pady=(0, 4))
|
|||
|
|
RoundedButton(
|
|||
|
|
btn_row, "Aktuelles speichern", command=lambda c=cat: save_current(c),
|
|||
|
|
width=140, height=26, canvas_bg="#E8F4FA",
|
|||
|
|
).pack(side="left")
|
|||
|
|
tk.Label(frame, text="Gespeicherte Dateien:",
|
|||
|
|
font=("Segoe UI", 9), bg="#E8F4FA", fg="#1a4d6d").pack(anchor="w", pady=(4, 0))
|
|||
|
|
lb = tk.Listbox(frame, height=10, font=("Segoe UI", 10))
|
|||
|
|
lb.pack(fill="both", expand=True, pady=(2, 4))
|
|||
|
|
refresh_list(lb, cat)
|
|||
|
|
listboxes.append({"listbox": lb, "category": cat})
|
|||
|
|
|
|||
|
|
def on_select(evt, category=cat, listbox=lb):
|
|||
|
|
sel = listbox.curselection()
|
|||
|
|
if not sel:
|
|||
|
|
return
|
|||
|
|
idx = sel[0]
|
|||
|
|
files = list_ablage_files(category)
|
|||
|
|
if 0 <= idx < len(files):
|
|||
|
|
load_file_into_app(category, files[idx])
|
|||
|
|
|
|||
|
|
lb.bind("<Double-Button-1>", on_select)
|
|||
|
|
def load_selected(lbx=lb, c=cat):
|
|||
|
|
sel = lbx.curselection()
|
|||
|
|
if not sel:
|
|||
|
|
messagebox.showinfo("Hinweis", "Bitte eine Datei auswählen.")
|
|||
|
|
return
|
|||
|
|
files = list_ablage_files(c)
|
|||
|
|
if 0 <= sel[0] < len(files):
|
|||
|
|
load_file_into_app(c, files[sel[0]])
|
|||
|
|
|
|||
|
|
RoundedButton(
|
|||
|
|
frame, "Ausgewählte Datei in App laden", command=load_selected,
|
|||
|
|
width=220, height=26, canvas_bg="#E8F4FA",
|
|||
|
|
).pack(fill="x", pady=(0, 4))
|
|||
|
|
|
|||
|
|
def _maybe_cleanup_old_entries():
|
|||
|
|
if not bool(auto_delete_var.get()):
|
|||
|
|
return
|
|||
|
|
total_old = 0
|
|||
|
|
for cat in ABLAGE_SUBFOLDERS:
|
|||
|
|
try:
|
|||
|
|
total_old += int(count_entries_older_than(cat, days=14))
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
if total_old <= 0:
|
|||
|
|
return
|
|||
|
|
if not messagebox.askyesno(
|
|||
|
|
"Auto-Löschen",
|
|||
|
|
"Sollen Ablage-Dateien älter als 2 Wochen jetzt gelöscht werden?"
|
|||
|
|
):
|
|||
|
|
return
|
|||
|
|
total_deleted = 0
|
|||
|
|
for cat in ABLAGE_SUBFOLDERS:
|
|||
|
|
try:
|
|||
|
|
total_deleted += int(delete_entries_older_than(cat, days=14))
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
for lb in listboxes:
|
|||
|
|
refresh_list(lb["listbox"], lb["category"])
|
|||
|
|
if total_deleted > 0:
|
|||
|
|
messagebox.showinfo("Auto-Löschen", f"{total_deleted} alte Einträge wurden gelöscht.")
|
|||
|
|
else:
|
|||
|
|
messagebox.showinfo("Auto-Löschen", "Keine Einträge älter als 2 Wochen gefunden.")
|
|||
|
|
|
|||
|
|
# Nachfrage beim Öffnen des Fensters (nur wenn Checkbox aktiv).
|
|||
|
|
win.after(250, _maybe_cleanup_old_entries)
|
|||
|
|
|
|||
|
|
btn_bottom = ttk.Frame(main_f)
|
|||
|
|
btn_bottom.pack(fill="x", pady=(8, 0))
|
|||
|
|
|
|||
|
|
def do_export():
|
|||
|
|
from tkinter import filedialog
|
|||
|
|
import zipfile
|
|||
|
|
dest = filedialog.asksaveasfilename(
|
|||
|
|
title="Ablage exportieren (ZIP)",
|
|||
|
|
defaultextension=".zip",
|
|||
|
|
filetypes=[("ZIP", "*.zip"), ("Alle", "*.*")],
|
|||
|
|
)
|
|||
|
|
if not dest:
|
|||
|
|
return
|
|||
|
|
try:
|
|||
|
|
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
|
|||
|
|
for root, dirs, files in os.walk(base_path):
|
|||
|
|
for f in files:
|
|||
|
|
if not f.endswith(".txt"):
|
|||
|
|
continue
|
|||
|
|
path = os.path.join(root, f)
|
|||
|
|
arcname = os.path.relpath(path, base_path)
|
|||
|
|
zf.write(path, arcname)
|
|||
|
|
messagebox.showinfo("Export", f"Exportiert nach:\n{dest}")
|
|||
|
|
except Exception as e:
|
|||
|
|
messagebox.showerror("Export fehlgeschlagen", str(e))
|
|||
|
|
|
|||
|
|
def do_import():
|
|||
|
|
from tkinter import filedialog
|
|||
|
|
import zipfile
|
|||
|
|
src = filedialog.askopenfilename(
|
|||
|
|
title="ZIP importieren (Inhalt in Ablage entpacken)",
|
|||
|
|
filetypes=[("ZIP", "*.zip"), ("Alle", "*.*")],
|
|||
|
|
)
|
|||
|
|
if not src:
|
|||
|
|
return
|
|||
|
|
try:
|
|||
|
|
with zipfile.ZipFile(src, "r") as zf:
|
|||
|
|
zf.extractall(base_path)
|
|||
|
|
messagebox.showinfo("Import", "Import abgeschlossen.")
|
|||
|
|
for lb in listboxes:
|
|||
|
|
refresh_list(lb["listbox"], lb["category"])
|
|||
|
|
except Exception as e:
|
|||
|
|
messagebox.showerror("Import fehlgeschlagen", str(e))
|
|||
|
|
|
|||
|
|
RoundedButton(btn_bottom, "Export (ZIP)", command=do_export, width=120, height=26, canvas_bg="#B9ECFA").pack(side="left", padx=(0, 8))
|
|||
|
|
RoundedButton(btn_bottom, "Import (ZIP)", command=do_import, width=120, height=26, canvas_bg="#B9ECFA").pack(side="left")
|