Files
libreoffice-paperless-conne…/pythonpath/paperless_dialogs.py
2026-09-04 19:49:43 +02:00

791 lines
28 KiB
Python

"""
paperless_dialogs.py - Dialoge, programmatisch mit UNO aufgebaut.
Bewusst ohne XDL-Dateien: Die muessten mitverpackt, über die
Erweiterungs-URL geladen und je Sprache gepflegt werden. Programmatisch
gebaute Dialoge sind ausfuehrlicher im Code, dafuer ohne Paketierungsaufwand
und ohne Ladepfad-Fallstricke.
"""
import uno
from com.sun.star.awt import Rectangle
from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK, BUTTONS_OK_CANCEL
from com.sun.star.awt.MessageBoxType import (ERRORBOX, INFOBOX, QUERYBOX,
WARNINGBOX)
# ---------------------------------------------------------------- Meldungen
def message(ctx, frame, text, title="Paperless", kind=INFOBOX):
parent = frame.getContainerWindow() if frame else None
toolkit = ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.awt.Toolkit", ctx)
box = toolkit.createMessageBox(parent, kind, BUTTONS_OK, title, text)
box.execute()
box.dispose()
def error(ctx, frame, text, title="Paperless — Fehler"):
message(ctx, frame, text, title, ERRORBOX)
def confirm(ctx, frame, text, title="Paperless"):
parent = frame.getContainerWindow() if frame else None
toolkit = ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.awt.Toolkit", ctx)
box = toolkit.createMessageBox(parent, QUERYBOX, BUTTONS_OK_CANCEL,
title, text)
result = box.execute()
box.dispose()
return result == 1 # 1 = OK
def choice(ctx, frame, title, text, options, width=360):
"""
Rückfrage mit mehr als zwei Möglichkeiten.
Die eingebaute MessageBox kennt nur OK und Abbrechen. Und
PushButtonType taugt hier nicht: Es kennt ausschließlich Standard, OK,
Abbrechen und Hilfe - bei drei Schaltflächen wäre die dritte „Hilfe“
und würde den Dialog nicht schließen.
Deshalb gewöhnliche Schaltflächen mit Ereignisbehandler, die das
Ergebnis vermerken und endExecute() aufrufen.
Liefert den Index der gewählten Schaltfläche oder None bei Abbruch.
"""
import unohelper
from com.sun.star.awt import XActionListener
zeilen = text.count("\n") + 1
th = max(30, zeilen * 11)
h = th + 46
d = DialogBuilder(ctx, title, width, h)
d.label("t", 10, 10, width - 20, th, text)
bw = max(60, min(120, (width - 20) // len(options) - 6))
x = width - 10 - len(options) * (bw + 6) + 6
for i, label_text in enumerate(options):
# PushButtonType 0 = Standard: schliesst den Dialog NICHT von selbst.
d.button("b%d" % i, x, h - 26, bw, 16, label_text, 0,
i == len(options) - 1)
x += bw + 6
d.dialog.setModel(d.model)
smgr = ctx.ServiceManager
toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
d.dialog.createPeer(toolkit, frame.getContainerWindow() if frame else None)
ergebnis = {"index": None}
class Klick(unohelper.Base, XActionListener):
def __init__(self, index):
self.index = index
def actionPerformed(self, _ev):
ergebnis["index"] = self.index
d.dialog.endExecute()
def disposing(self, _ev):
pass
# Die Behandler müssen am Leben bleiben, solange der Dialog läuft.
behandler = []
for i in range(len(options)):
k = Klick(i)
behandler.append(k)
d.get("b%d" % i).addActionListener(k)
d.dialog.execute()
d.dispose()
return ergebnis["index"]
# ------------------------------------------------------- Spaltenlayout
# Listenfelder verwenden standardmäßig eine Proportionalschrift. Mit
# Leerzeichen aufgefüllte Spalten sind darin NICHT ausgerichtet - die
# Überschrift steht dann woanders als der Inhalt. Deshalb wird für die
# Listen und ihre Überschriften eine dicht laufende Schrift gesetzt.
#
# Die Angabe ist eine Ersatzkette: LibreOffice nimmt den ersten
# verfügbaren Namen.
MONO = "Liberation Mono;DejaVu Sans Mono;Noto Sans Mono;Courier New;monospace"
SEP = "" # senkrechter Strich als dezenter Spaltentrenner
def mono(ctrl):
"""
Dicht laufende Schrift auf ein Steuerelement legen.
Die Schriftgröße wird bewusst NICHT gesetzt - so erbt das Element die
Größe des Dialogs und damit die Systemvorgabe. Ein fester Wert wäre
auf hochauflösenden Anzeigen zu klein.
"""
try:
ctrl.FontName = MONO
except Exception:
pass
return ctrl
def columns(values, widths):
"""
Werte auf feste Zeichenbreiten bringen und mit Strichen trennen.
Negative Breite bedeutet rechtsbündig, 0 bedeutet Rest der Zeile.
Nur sinnvoll zusammen mit mono().
"""
out = []
for value, width in zip(values, widths):
text = "" if value is None else str(value)
if width == 0:
out.append(text)
elif width < 0:
w = -width
out.append(text[:w].rjust(w))
else:
out.append(text[:width].ljust(width))
return (" %s " % SEP).join(out)
# ------------------------------------------------------------ Baukasten
class DialogBuilder:
"""Dünne Hülle um UnoControlDialogModel."""
def __init__(self, ctx, title, width, height):
self.ctx = ctx
smgr = ctx.ServiceManager
self.model = smgr.createInstanceWithContext(
"com.sun.star.awt.UnoControlDialogModel", ctx)
self.model.PositionX = 0
self.model.PositionY = 0
self.model.Width = width
self.model.Height = height
self.model.Title = title
self.dialog = smgr.createInstanceWithContext(
"com.sun.star.awt.UnoControlDialog", ctx)
self._smgr = smgr
def add(self, kind, name, x, y, w, h, **props):
ctrl = self.model.createInstance(
"com.sun.star.awt.UnoControl%sModel" % kind)
ctrl.PositionX, ctrl.PositionY = x, y
ctrl.Width, ctrl.Height = w, h
for key, value in props.items():
try:
setattr(ctrl, key, value)
except Exception:
pass
self.model.insertByName(name, ctrl)
return ctrl
def label(self, name, x, y, w, h, text):
return self.add("FixedText", name, x, y, w, h, Label=text)
def edit(self, name, x, y, w, h, text="", **kw):
return self.add("Edit", name, x, y, w, h, Text=text, **kw)
def button(self, name, x, y, w, h, label, action=0, default=False):
return self.add("Button", name, x, y, w, h,
Label=label, PushButtonType=action,
DefaultButton=default)
def listbox(self, name, x, y, w, h, items=(), dropdown=False):
return self.add("ListBox", name, x, y, w, h,
StringItemList=tuple(items), Dropdown=dropdown)
def combo(self, name, x, y, w, h, items=(), text=""):
return self.add("ComboBox", name, x, y, w, h,
StringItemList=tuple(items), Dropdown=True, Text=text)
def check(self, name, x, y, w, h, label, state=0):
return self.add("CheckBox", name, x, y, w, h, Label=label, State=state)
def execute(self, frame=None):
self.dialog.setModel(self.model)
toolkit = self._smgr.createInstanceWithContext(
"com.sun.star.awt.Toolkit", self.ctx)
parent = frame.getContainerWindow() if frame else None
self.dialog.createPeer(toolkit, parent)
result = self.dialog.execute()
return result
def get(self, name):
return self.dialog.getControl(name)
def dispose(self):
try:
self.dialog.dispose()
except Exception:
pass
# ------------------------------------------------------------ Einstellungen
def settings_dialog(ctx, frame, settings):
"""Verbindungsdaten. Liefert das geänderte dict oder None."""
d = DialogBuilder(ctx, "Paperless — Verbindung", 260, 172)
d.label("l1", 8, 10, 70, 12, "Basisadresse")
d.edit("base", 80, 8, 172, 14, settings.get("base_url", ""))
d.label("l1b", 80, 24, 172, 10, "z. B. https://dms.example.org")
d.label("l2", 8, 42, 70, 12, "API-Token")
d.edit("token", 80, 40, 172, 14, settings.get("token", ""),
EchoChar=ord("*"))
d.label("l2b", 80, 56, 172, 10,
"Im Benutzerprofil von Paperless erzeugen")
d.check("tls", 80, 72, 172, 12, "TLS-Zertifikat prüfen",
1 if settings.get("verify_tls", True) else 0)
d.label("l3", 8, 92, 70, 12, "Versionslabel")
d.edit("label", 80, 90, 172, 14, settings.get("version_label", ""))
d.label("l3b", 80, 106, 172, 10, "{datum} wird ersetzt")
d.label("l4", 8, 112, 70, 12, "User-Agent")
d.edit("ua", 80, 110, 172, 14, settings.get("user_agent", ""))
d.label("l4b", 80, 126, 172, 10,
"leer = Vorgabe. Nur ändern, wenn ein Proxy blockt.")
d.button("ok", 140, 146, 52, 16, "Speichern", 1, True)
d.button("cancel", 198, 146, 52, 16, "Abbrechen", 2)
if d.execute(frame) != 1:
d.dispose()
return None
out = dict(settings)
out["base_url"] = d.get("base").getText().strip()
out["token"] = d.get("token").getText().strip()
out["verify_tls"] = bool(d.get("tls").getState())
out["version_label"] = d.get("label").getText().strip()
out["user_agent"] = d.get("ua").getText().strip()
d.dispose()
return out
# ------------------------------------------------------------ Dokumentsuche
def open_dialog(ctx, frame, client):
"""
Ein Dialog von oben nach unten: Suche, Trefferliste, Versionsliste.
Die Trefferliste zeigt je Dokument genau einen Eintrag - das
Wurzeldokument. Wird darin etwas ausgewählt, füllt sich darunter die
Versionsliste mit Datum und Dateiformat je Fassung.
Liefert (dokument, version, nur_lesen) oder None.
"""
import paperless_formats as fmt
W, H = 420, 322
d = DialogBuilder(ctx, "Dokument aus Paperless öffnen", W, H)
# Spaltenbreiten in Zeichen. Negativ = rechtsbündig, 0 = Rest der Zeile.
COL_RES = (-7, 10, 0) # ASN, Erstellt, Titel
COL_VER = (-3, 10, 14, 0) # Nr., Erstellt, Format, Bezeichnung
# Listenfelder rücken ihren Text um wenige Einheiten ein. Die
# Überschrift bekommt denselben Versatz, sonst stehen die Spalten
# gegeneinander verschoben.
HX = 11
# 1. Suche
d.label("l1", 8, 10, 40, 12, "Suche")
d.edit("q", 50, 8, W - 122, 14, "")
d.button("go", W - 66, 8, 58, 14, "Suchen", 0, True)
d.label("hint1", 8, 26, W - 16, 10,
"Leer lassen für die zuletzt hinzugefügten Dokumente. "
"Archiv-Seriennummer funktioniert ebenfalls.")
# 2. Treffer - Bezeichnung, darunter Spaltenüberschrift, darunter Liste
d.label("l2", 8, 42, 200, 11, "Dokumente")
mono(d.label("head1", HX, 55, W - HX - 8, 11,
columns(("ASN", "Erstellt", "Titel"), COL_RES)))
mono(d.listbox("results", 8, 68, W - 16, 104))
# 3. Versionen
d.label("l3", 8, 180, 200, 11, "Versionen")
mono(d.label("head2", HX, 193, W - HX - 8, 11,
" " + columns(("Nr", "Erstellt", "Format", "Bezeichnung"),
COL_VER)))
mono(d.listbox("versions", 8, 206, W - 16, 66))
d.label("hint2", 8, 278, W - 16, 20, "")
d.label("l4", 8, 301, 46, 11, "Öffnen als")
d.listbox("mode", 58, 299, 130, 14,
["Bearbeitbar", "Schreibgeschützt"], dropdown=True)
d.button("ok", W - 122, 298, 54, 16, "Öffnen", 1)
d.button("cancel", W - 62, 298, 54, 16, "Abbrechen", 2)
d.dialog.setModel(d.model)
smgr = ctx.ServiceManager
toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
d.dialog.createPeer(toolkit, frame.getContainerWindow() if frame else None)
lb_res = d.get("results")
lb_ver = d.get("versions")
state = {"rows": [], "versions": [], "doc": None, "cache": {}, "last": -1}
# ---------------------------------------------------------- Trefferliste
def refresh(query):
try:
rows = client.search(query)
except Exception as exc: # noqa: BLE001
d.get("hint2").setText("Fehler bei der Suche: %s" % str(exc)[:70])
return
state["rows"] = rows
state["last"] = -1
lb_res.removeItems(0, lb_res.getItemCount())
lb_ver.removeItems(0, lb_ver.getItemCount())
entries = []
for r in rows:
asn = r.get("archive_serial_number")
entries.append(columns((
str(asn) if asn is not None else "",
(r.get("created") or "")[:10],
(r.get("title") or "")[:90]), COL_RES))
if entries:
lb_res.addItems(tuple(entries), 0)
lb_res.selectItemPos(0, True)
load_versions()
else:
d.get("hint2").setText("Keine Treffer.")
# --------------------------------------------------------- Versionsliste
def load_versions():
"""
Versionen des gewählten Dokuments nachladen.
Die Formatangabe steht nicht in der eingebetteten versions-Liste,
deshalb je Version ein Abruf. Ergebnisse werden zwischengespeichert,
damit das Blättern in der Trefferliste nicht bei jedem Tastendruck
erneut abfragt.
"""
pos = lb_res.getSelectedItemPos()
if pos == state["last"] or not (0 <= pos < len(state["rows"])):
return
state["last"] = pos
row = state["rows"][pos]
doc_id = row["id"]
lb_ver.removeItems(0, lb_ver.getItemCount())
d.get("hint2").setText("Versionen werden geladen …")
if doc_id in state["cache"]:
full, versions = state["cache"][doc_id]
else:
try:
full = client.document(doc_id)
versions = client.version_details(full)
except Exception as exc: # noqa: BLE001
d.get("hint2").setText("Fehler: %s" % str(exc)[:70])
return
state["cache"][doc_id] = (full, versions)
state["doc"], state["versions"] = full, versions
entries = []
for v in versions:
editable = fmt.is_editable(v.get("filename"), v.get("mime_type"))
entries.append((" " if editable else "") + columns((
v.get("no", "?"),
(v.get("added") or "")[:10],
fmt.label(v.get("filename"), v.get("mime_type")),
(v.get("version_label") or "")[:40]), COL_VER))
if entries:
lb_ver.addItems(tuple(entries), 0)
# Vorgabe: die aktuellste Version. Die Liste kommt absteigend,
# also Position 0.
lb_ver.selectItemPos(0, True)
update_hint()
def update_hint():
"""
Hinweis und Vorbelegung der Öffnungsart.
Die Vorbelegung ist ein Vorschlag, keine Vorschrift - die
Formaterkennung kann danebenliegen, und dann soll der Anwender
trotzdem bearbeitbar öffnen können.
"""
pos = lb_ver.getSelectedItemPos()
vers = state["versions"]
if not (0 <= pos < len(vers)):
d.get("hint2").setText("")
return
v = vers[pos]
fn, mt = v.get("filename"), v.get("mime_type")
if fmt.is_editable(fn, mt):
d.get("mode").selectItemPos(0, True)
d.get("hint2").setText(
"Bearbeitbar. Änderungen können als neue Version abgelegt "
"werden.")
elif not fmt.is_known(fn, mt):
# Unbekanntes Format: kein Grund, das Bearbeiten zu verbieten.
d.get("mode").selectItemPos(0, True)
d.get("hint2").setText(
"Format nicht erkannt: %s\n"
"Bearbeitbar öffnen ist möglich — beim Zurückschreiben "
"wird ODF verwendet." % fmt.detail(fn, mt))
else:
d.get("mode").selectItemPos(1, True)
d.get("hint2").setText(
"%s ist in LibreOffice nicht sinnvoll bearbeitbar.\n"
"Schreibgeschützt empfohlen — bearbeitbar ist trotzdem "
"wählbar." % fmt.label(fn, mt))
# ------------------------------------------------------------- Ereignisse
import unohelper
from com.sun.star.awt import XActionListener, XItemListener
class GoListener(unohelper.Base, XActionListener):
def actionPerformed(self, _ev):
refresh(d.get("q").getText().strip())
def disposing(self, _ev):
pass
class ResultListener(unohelper.Base, XItemListener):
def itemStateChanged(self, _ev):
load_versions()
def disposing(self, _ev):
pass
class VersionListener(unohelper.Base, XItemListener):
def itemStateChanged(self, _ev):
update_hint()
def disposing(self, _ev):
pass
# Die Behandler in einer Liste halten. Als Wegwerfobjekte übergeben
# könnte der Speicherbereiniger sie einsammeln, bevor der Dialog
# fertig ist - die Ereignisse liefen dann ins Leere.
behandler = [GoListener(), ResultListener(), VersionListener()]
d.get("go").addActionListener(behandler[0])
lb_res.addItemListener(behandler[1])
lb_ver.addItemListener(behandler[2])
refresh("")
if d.dialog.execute() != 1:
d.dispose()
return None
vpos = lb_ver.getSelectedItemPos()
# Die Wahl des Anwenders wird NICHT überschrieben. Wer ein als nicht
# bearbeitbar eingestuftes Format trotzdem bearbeitbar öffnen will,
# darf das - die Erkennung kann danebenliegen.
readonly = d.get("mode").getSelectedItemPos() == 1
doc, versions = state["doc"], state["versions"]
d.dispose()
if doc is None or not (0 <= vpos < len(versions)):
return None
return doc, versions[vpos], readonly
# --------------------------------------------------- Ablegeziel wählen
def store_target_dialog(ctx, frame, client, source):
"""
Wohin soll die geöffnete Datei abgelegt werden?
Drei Fälle, die sich in der Absicht unterscheiden:
* ein NEUES Dokument mit eigener Versionskette
* eine neue Version an einem ANDEREN, bereits vorhandenen Dokument
Der zweite Fall ist der, den Dokumentenverwaltungen gern vergessen:
Die Rechnung kommt als PDF, die bearbeitbare Fassung entsteht später.
Beides gehört zusammen, aber das PDF war zuerst da.
Liefert ein dict oder None.
"""
import paperless_formats as fmt
W, H = 430, 300
COL = (-7, 10, 0)
HX = 11
title = (source.get("title") or "")
d = DialogBuilder(ctx, "In Paperless ablegen", W, H)
d.label("l0", 8, 10, 34, 12, "Ziel")
d.listbox("mode", 46, 8, 250, 14,
["Neues Dokument anlegen",
"An bestehendes Dokument anhängen"], dropdown=True)
# --- Fall 1: neues Dokument
d.label("n1", 8, 36, 46, 12, "Titel")
d.edit("title", 58, 34, W - 66, 14, (title + " (Kopie)")[:120])
d.check("meta", 58, 54, W - 66, 12,
"Klassifizierung des geöffneten Dokuments übernehmen", 1)
# --- Fall 2: an bestehendes Dokument anhängen
d.label("e1", 8, 78, 46, 12, "Suche")
d.edit("q", 58, 76, W - 130, 14, "")
d.button("go", W - 66, 76, 58, 14, "Suchen", 0)
mono(d.label("head", HX, 96, W - HX - 8, 11,
columns(("ASN", "Erstellt", "Titel"), COL)))
mono(d.listbox("results", 8, 109, W - 16, 96))
d.label("e2", 8, 209, W - 16, 20, "")
d.check("switch", 8, 236, W - 16, 12,
"Danach mit dem Zieldokument weiterarbeiten", 1)
d.label("l9", 8, 250, W - 16, 20,
"Das geöffnete Fenster wird dem Ziel zugeordnet. Weitere "
"Versionen landen dann dort.")
d.button("ok", W - 122, 276, 54, 16, "Ablegen", 1, True)
d.button("cancel", W - 62, 276, 54, 16, "Abbrechen", 2)
d.dialog.setModel(d.model)
smgr = ctx.ServiceManager
toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
d.dialog.createPeer(toolkit, frame.getContainerWindow() if frame else None)
lb_res = d.get("results")
state = {"rows": []}
def set_mode():
neu_ = d.get("mode").getSelectedItemPos() == 0
for name in ("n1", "title", "meta"):
d.get(name).setEnable(neu_)
for name in ("e1", "q", "go", "head", "results"):
d.get(name).setEnable(not neu_)
if neu_:
d.get("e2").setText("")
else:
update_target_hint()
def refresh(query):
try:
rows = client.search(query)
except Exception as exc: # noqa: BLE001
d.get("e2").setText("Fehler bei der Suche: %s" % str(exc)[:70])
return
state["rows"] = rows
lb_res.removeItems(0, lb_res.getItemCount())
entries = []
for r in rows:
asn = r.get("archive_serial_number")
entries.append(columns((
str(asn) if asn is not None else "",
(r.get("created") or "")[:10],
(r.get("title") or "")[:90]), COL))
if entries:
lb_res.addItems(tuple(entries), 0)
lb_res.selectItemPos(0, True)
update_target_hint()
def update_target_hint():
pos = lb_res.getSelectedItemPos()
rows = state["rows"]
if not (0 <= pos < len(rows)):
d.get("e2").setText("Kein Zieldokument gewählt.")
return
r = rows[pos]
d.get("e2").setText(
"Die geöffnete Datei wird als neue Version an „%s“ angehängt.\n"
"Die bisherige Fassung bleibt als Version erhalten."
% (r.get("title") or "")[:60])
import unohelper
from com.sun.star.awt import XActionListener, XItemListener
class Go(unohelper.Base, XActionListener):
def actionPerformed(self, _ev):
refresh(d.get("q").getText().strip())
def disposing(self, _ev):
pass
class ModeChanged(unohelper.Base, XItemListener):
def itemStateChanged(self, _ev):
set_mode()
def disposing(self, _ev):
pass
class ResultChanged(unohelper.Base, XItemListener):
def itemStateChanged(self, _ev):
update_target_hint()
def disposing(self, _ev):
pass
behandler = [Go(), ModeChanged(), ResultChanged()]
d.get("go").addActionListener(behandler[0])
d.get("mode").addItemListener(behandler[1])
lb_res.addItemListener(behandler[2])
d.get("mode").selectItemPos(0, True)
set_mode()
if d.dialog.execute() != 1:
d.dispose()
return None
neu_ = d.get("mode").getSelectedItemPos() == 0
out = {"switch": bool(d.get("switch").getState())}
if neu_:
out["mode"] = "new"
out["title"] = d.get("title").getText().strip()
out["take_meta"] = bool(d.get("meta").getState())
d.dispose()
return out if out["title"] else None
pos = lb_res.getSelectedItemPos()
rows = state["rows"]
d.dispose()
if not (0 <= pos < len(rows)):
return None
out["mode"] = "existing"
out["target"] = rows[pos]
return out
# --------------------------------------------------------- Klassifizierung
def classify_dialog(ctx, frame, doc, tags, correspondents, doctypes):
"""
Klassifizierung bearbeiten. Liefert ein PATCH-payload-dict oder None.
Tags werden als Mehrfachauswahl gefuehrt, Korrespondent und Dokumenttyp
als Auswahlfeld mit Freitext - so lassen sich neue Werte anlegen.
"""
def names(rows):
return [r["name"] for r in rows]
def name_of(rows, ident):
for r in rows:
if r["id"] == ident:
return r["name"]
return ""
d = DialogBuilder(ctx, "Klassifizierung bearbeiten", 300, 250)
d.label("l1", 8, 10, 60, 12, "Titel")
d.edit("title", 70, 8, 222, 14, doc.get("title") or "")
d.label("l2", 8, 30, 60, 12, "Erstellt")
d.edit("created", 70, 28, 90, 14, (doc.get("created") or "")[:10])
d.label("l2b", 164, 30, 128, 10, "Format JJJJ-MM-TT")
d.label("l3", 8, 50, 60, 12, "Korrespondent")
d.combo("corr", 70, 48, 222, 14, names(correspondents),
name_of(correspondents, doc.get("correspondent")))
d.label("l4", 8, 70, 60, 12, "Dokumenttyp")
d.combo("dtype", 70, 68, 222, 14, names(doctypes),
name_of(doctypes, doc.get("document_type")))
d.label("l5", 8, 90, 60, 12, "Tags")
lb = d.listbox("tags", 70, 88, 222, 120)
lb.MultiSelection = True
d.label("l5b", 70, 210, 222, 10,
"Mehrfachauswahl mit Strg oder Umschalt")
d.button("ok", 178, 226, 52, 16, "Speichern", 1, True)
d.button("cancel", 236, 226, 56, 16, "Abbrechen", 2)
d.dialog.setModel(d.model)
smgr = ctx.ServiceManager
toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
d.dialog.createPeer(toolkit, frame.getContainerWindow() if frame else None)
tag_ctrl = d.get("tags")
tag_ctrl.addItems(tuple(names(tags)), 0)
have = set(doc.get("tags") or [])
for i, t in enumerate(tags):
if t["id"] in have:
tag_ctrl.selectItemPos(i, True)
if d.dialog.execute() != 1:
d.dispose()
return None
payload = {"title": d.get("title").getText().strip()}
created = d.get("created").getText().strip()
if created:
payload["created"] = created
selected = d.get("tags").getSelectedItemsPos()
payload["tags"] = [tags[i]["id"] for i in selected if i < len(tags)]
payload["_correspondent_name"] = d.get("corr").getText().strip()
payload["_document_type_name"] = d.get("dtype").getText().strip()
d.dispose()
return payload
# -------------------------------------------------------------- Freigabe
def share_dialog(ctx, frame, default_days=7, default_tag="Sharelink",
known_tags=()):
"""
Laufzeit, Dateivariante und optionaler Tag.
Der Tag macht geteilte Dokumente später auffindbar - Paperless zeigt
zwar die eigenen Freigaben an, aber ein Tag lässt sich in gespeicherten
Ansichten und Regeln verwenden.
Liefert (tage, variante, tagname_oder_None) oder None.
"""
d = DialogBuilder(ctx, "Dokument teilen", 300, 168)
d.label("l1", 8, 12, 90, 12, "Gültigkeit in Tagen")
d.edit("days", 104, 10, 50, 14, str(default_days))
d.label("l1b", 104, 26, 188, 10, "0 = unbegrenzt gültig")
d.label("l2", 8, 44, 90, 12, "Dateivariante")
d.listbox("ver", 104, 42, 188, 14,
["Original", "Archivfassung (PDF)"], dropdown=True)
d.check("tag", 8, 70, 284, 12,
"Dokument mit einem Tag kennzeichnen", 1)
d.combo("tagname", 104, 86, 188, 14, known_tags, default_tag)
d.label("l3b", 104, 102, 188, 20,
"Macht geteilte Dokumente auffindbar. Ist der Tag noch nicht "
"vorhanden, wird er nach Rückfrage angelegt.")
d.button("ok", 178, 144, 54, 16, "Erzeugen", 1, True)
d.button("cancel", 236, 144, 56, 16, "Abbrechen", 2)
d.dialog.setModel(d.model)
smgr = ctx.ServiceManager
toolkit = smgr.createInstanceWithContext("com.sun.star.awt.Toolkit", ctx)
d.dialog.createPeer(toolkit, frame.getContainerWindow() if frame else None)
d.get("ver").selectItemPos(0, True)
if d.dialog.execute() != 1:
d.dispose()
return None
try:
days = int(d.get("days").getText().strip() or "0")
except ValueError:
days = default_days
variant = "original" if d.get("ver").getSelectedItemPos() == 0 else "archive"
tag = d.get("tagname").getText().strip() if d.get("tag").getState() else None
d.dispose()
return days, variant, (tag or None)
def result_dialog(ctx, frame, title, text, url=None):
"""Ergebnis anzeigen, Link zum Herauskopieren in einem Textfeld."""
height = 120 if url else 90
d = DialogBuilder(ctx, title, 320, height)
d.label("l1", 8, 10, 304, 24, text)
if url:
d.label("l2", 8, 42, 304, 10, "Link (zum Kopieren markieren):")
d.edit("url", 8, 54, 304, 14, url, ReadOnly=True)
d.button("ok", 258, height - 24, 54, 16, "Schließen", 1, True)
d.execute(frame)
d.dispose()