""" 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()