236 lines
8.0 KiB
Python
236 lines
8.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
===============================================================================
|
||
nvWindow.py – Tkinter-Vorschlagsfenster für NV_MASTER-Abgleich (LibreOffice)
|
||
===============================================================================
|
||
|
||
Zweck:
|
||
Dieses Skript öffnet ein Tkinter-Fenster, das Live-Vorschläge zu Begriffen
|
||
aus der Datei NV_MASTER.ods anzeigt. Es ist so ausgelegt, dass es direkt
|
||
mit LibreOffice Calc über UNO kommunizieren kann – sofern möglich.
|
||
|
||
Robustheit:
|
||
Das Skript erkennt automatisch, ob UNO verfügbar und kompatibel ist:
|
||
- wenn ja → direkte Integration mit LibreOffice (bidirektional)
|
||
- wenn nein → Fallback in isolierten Modus, aber weiterhin lauffähig
|
||
Alle Ereignisse, Fehler und Systemzustände werden geloggt.
|
||
|
||
Start LibreOffice (empfohlen):
|
||
soffice --calc --accept="socket,host=localhost,port=2002;urp;" --nologo --norestore &
|
||
|
||
Start der Anwendung:
|
||
/usr/lib/libreoffice/program/python3 ~/projects/nvWindow/nvWindow.py
|
||
(oder notfalls:) python3 ~/projects/nvWindow/nvWindow.py
|
||
|
||
Abhängigkeiten:
|
||
pip install rapidfuzz odfpy
|
||
|
||
===============================================================================
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import traceback
|
||
import tkinter as tk
|
||
from datetime import datetime
|
||
from rapidfuzz import process, fuzz
|
||
from odf.opendocument import load
|
||
from odf.table import Table, TableRow, TableCell
|
||
from odf.text import P
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 1. Verzeichnis-Setup und Logging
|
||
# ------------------------------------------------------------------------------
|
||
|
||
BASE_DIR = os.path.expanduser("~/projects/nvWindow")
|
||
LOG_FILE = os.path.join(BASE_DIR, "nvWindow.log")
|
||
NV_MASTER_PATH = os.path.join(BASE_DIR, "NV_MASTER.ods")
|
||
|
||
|
||
def log(msg):
|
||
"""Schreibt Zeitstempel und Nachricht in die Logdatei."""
|
||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
try:
|
||
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||
f.write(f"[{timestamp}] {msg}\n")
|
||
except Exception:
|
||
# im absoluten Notfall auf stdout ausweichen
|
||
print(f"[{timestamp}] {msg}")
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 2. UNO-Initialisierung (robust, mit Fallback)
|
||
# ------------------------------------------------------------------------------
|
||
|
||
uno_available = False
|
||
desktop = None
|
||
|
||
def init_uno():
|
||
"""Versucht UNO-Bridge zu initialisieren, erkennt Systemumgebung automatisch."""
|
||
global uno_available, desktop
|
||
|
||
# bereits aktiv in LibreOffice? (XSCRIPTCONTEXT vorhanden)
|
||
if "XSCRIPTCONTEXT" in globals():
|
||
try:
|
||
ctx = XSCRIPTCONTEXT.getComponentContext()
|
||
smgr = ctx.ServiceManager
|
||
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
|
||
uno_available = True
|
||
log("UNO erkannt: Skript läuft innerhalb von LibreOffice.")
|
||
return
|
||
except Exception as e:
|
||
log(f"UNO innerhalb LO-Session nicht initialisierbar: {e}")
|
||
uno_available = False
|
||
return
|
||
|
||
# außerhalb LibreOffice: versuche Systemintegration
|
||
try:
|
||
sys.path.append("/usr/lib/python3/dist-packages")
|
||
sys.path.append("/usr/lib/libreoffice/program")
|
||
os.environ["URE_BOOTSTRAP"] = (
|
||
"vnd.sun.star.pathname:/usr/lib/libreoffice/program/fundamentalrc"
|
||
)
|
||
|
||
import uno
|
||
from com.sun.star.beans import PropertyValue
|
||
|
||
try:
|
||
ctx = uno.getComponentContext()
|
||
smgr = ctx.ServiceManager
|
||
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx)
|
||
uno_available = True
|
||
log("UNO erfolgreich initialisiert – externe Verbindung aktiv.")
|
||
except Exception as inner:
|
||
log(f"UNO verfügbar, aber keine Desktop-Instanz: {inner}")
|
||
uno_available = False
|
||
except Exception as outer:
|
||
log(f"UNO-Import fehlgeschlagen: {outer}\n{traceback.format_exc()}")
|
||
uno_available = False
|
||
|
||
|
||
init_uno()
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 3. NV_MASTER laden
|
||
# ------------------------------------------------------------------------------
|
||
|
||
def load_nv_master(filepath):
|
||
"""Liest NV_MASTER.ods mit odfpy ein, extrahiert alle Textinhalte."""
|
||
entries = []
|
||
if not os.path.exists(filepath):
|
||
log(f"NV_MASTER.ods nicht gefunden: {filepath}")
|
||
return []
|
||
|
||
try:
|
||
doc = load(filepath)
|
||
for table in doc.getElementsByType(Table):
|
||
for row in table.getElementsByType(TableRow):
|
||
cells = row.getElementsByType(TableCell)
|
||
if not cells:
|
||
continue
|
||
cell_text = []
|
||
for c in cells:
|
||
ps = c.getElementsByType(P)
|
||
for p in ps:
|
||
if p.firstChild:
|
||
text = str(p.firstChild.data).strip()
|
||
if text:
|
||
cell_text.append(text)
|
||
if cell_text:
|
||
entries.append(" ".join(cell_text))
|
||
log(f"NV_MASTER geladen – {len(entries)} Begriffe erkannt.")
|
||
return entries
|
||
except Exception as e:
|
||
log(f"Fehler beim Lesen NV_MASTER: {e}\n{traceback.format_exc()}")
|
||
return []
|
||
|
||
|
||
NV_MASTER_LIST = load_nv_master(NV_MASTER_PATH)
|
||
|
||
if not NV_MASTER_LIST:
|
||
log("Abbruch: NV_MASTER.ods konnte nicht gelesen werden oder war leer.")
|
||
sys.exit("NV_MASTER.ods fehlt oder enthält keine Daten.")
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 4. Tkinter-GUI
|
||
# ------------------------------------------------------------------------------
|
||
|
||
root = tk.Tk()
|
||
root.title("Objektbeschreibung – Vorschläge")
|
||
root.attributes("-topmost", True)
|
||
root.geometry("420x260+1500+800")
|
||
root.configure(bg="#202020")
|
||
|
||
label = tk.Label(
|
||
root,
|
||
text="Live-Vorschläge für Objektbeschreibung:",
|
||
fg="white",
|
||
bg="#202020",
|
||
font=("Arial", 10, "bold"),
|
||
)
|
||
label.pack(pady=(10, 5))
|
||
|
||
entry = tk.Entry(root, width=52, font=("Arial", 11))
|
||
entry.pack(pady=(0, 10))
|
||
|
||
listbox = tk.Listbox(root, width=52, height=9, font=("Arial", 10))
|
||
listbox.pack(pady=5)
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 5. Matching + UNO-Kommunikation
|
||
# ------------------------------------------------------------------------------
|
||
|
||
def update_suggestions(event=None):
|
||
"""Aktualisiert Vorschläge bei jeder Tasteneingabe."""
|
||
query = entry.get().strip()
|
||
listbox.delete(0, tk.END)
|
||
|
||
if not query:
|
||
return
|
||
|
||
try:
|
||
matches = process.extract(query, NV_MASTER_LIST, scorer=fuzz.token_set_ratio, limit=8)
|
||
for m in matches:
|
||
item, score, _ = m
|
||
listbox.insert(tk.END, f"{item} ({score:.1f}%)")
|
||
|
||
if uno_available and desktop:
|
||
try:
|
||
doc = desktop.getCurrentComponent()
|
||
if not doc:
|
||
log("UNO: Kein aktives Dokument.")
|
||
return
|
||
sheet = doc.CurrentController.ActiveSheet
|
||
cell = sheet.getCurrentSelection()
|
||
if cell:
|
||
cell.String = query
|
||
except Exception as e:
|
||
log(f"UNO-Update-Fehler: {e}")
|
||
log(f"Suchanfrage '{query}' – {len(matches)} Vorschläge generiert.")
|
||
except Exception as e:
|
||
log(f"Fehler in update_suggestions(): {e}\n{traceback.format_exc()}")
|
||
|
||
|
||
entry.bind("<KeyRelease>", update_suggestions)
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 6. Laufzeit / Beenden
|
||
# ------------------------------------------------------------------------------
|
||
|
||
log("nvWindow gestartet. Tkinter-Fenster aktiv.")
|
||
print("nvWindow läuft – Details siehe nvWindow.log")
|
||
|
||
try:
|
||
root.mainloop()
|
||
except KeyboardInterrupt:
|
||
log("Manuell abgebrochen.")
|
||
except Exception as e:
|
||
log(f"Fehler in mainloop(): {e}\n{traceback.format_exc()}")
|
||
finally:
|
||
log("nvWindow beendet.")
|