Skip to content

Architektur Blattschutz

Michael Massee edited this page Aug 6, 2026 · 2 revisions

Architektur: Blattschutz im Turnier-Modus

Diese Seite richtet sich an Entwickler, die am Plugin-Code arbeiten. Für die Bedienung des fertigen Plugins siehe Turnieransicht. Diese Seite ist ein Überblick; die vollständige, im Code-Repository gepflegte Referenz (inkl. Named-Ranges-Pflichtregeln beim Registrieren neuer Blattschutz-Konfigurationen und Lebenszyklus-Verhalten bei Sheet-Umbenennung/ -Verschiebung/-Kopie) steht in turniersysteme/BLATTSCHUTZ.md.

Zweck

Der Turnier-Modus (Kiosk-Modus) dient der vereinfachten Ergebnis-Erfassung durch Laien, nicht der Darstellung. Beim Aktivieren werden alle nicht dafür benötigten Calc-Bedienelemente ausgeblendet; die Sheets des aktiven Turniersystems werden zusätzlich tab-geschützt. Ausschließlich die für die Erfassung nötigen Zellen bleiben über CellProtection.IsLocked = false editierbar.

Zentrale Klassen

Klasse Paket Zweck
TurnierModus toolbar/ Orchestriert das Aus-/Einblenden der UI-Elemente (Kiosk-Modus) und stößt den Blattschutz an
IBlattschutzKonfiguration helper/sheet/blattschutz/ Interface – eine Implementierung pro Turniersystem
SheetSchutzInfo helper/sheet/blattschutz/ Record: Sheet + editierbare Bereiche
BlattschutzManager helper/sheet/blattschutz/ Singleton-Orchestrator
BlattschutzRegistry helper/sheet/blattschutz/ Registry (Open/Closed Principle)

TurnierModus.aktivierenIntern() / deaktivierenIntern() delegieren per Registry — kein if (SUPERMELEE) nötig, neue Systeme werden nur per BlattschutzRegistry.register() eingetragen.

Pflicht-Reihenfolge beim Sperren

  1. zelleStylesAktualisieren(ws)vor jedem protect(), sonst LibreOffice-RuntimeException
  2. Sheet ggf. entsperren (BlattschutzManager.entsperreSheet, idempotent via XProtectable.isProtected())
  3. Editierbare Bereiche mit CellProtection.IsLocked = false freigeben
  4. XProtectable.protect("")

UNO-Hinweis: CellProtection

  • Klasse: com.sun.star.util.CellProtection (nicht sheet!)
  • Editierbar-Flag: IsLocked (nicht IsProtected)
  • Immer den alten Wert lesen, ein neues CellProtection-Objekt schreiben und dabei alle übrigen Flags (IsHidden, IsFormulaHidden, IsPrintHidden) unverändert übernehmen – nicht einfach ein frisches Objekt nur mit IsLocked setzen, sonst gehen die anderen Flags verloren

Command-Scope (Lazy-Unprotect)

BlattschutzManager bündelt die Pflicht-Reihenfolge pro SheetRunner-Kommando in einem thread-lokalen, referenzgezählten Scope, damit ein Kommando höchstens ein physisches Entsperren und garantiert ein abschließendes Schützen auslöst:

Methode Zweck
beginCommandScope(konfig, ws) Öffnet den Scope; entsperrt noch nicht
ensureUnprotectedInScope() Entsperrt lazy beim ersten Bedarf; weitere Aufrufe im selben Scope sind No-Ops
endCommandScope() Schließt den äußersten Scope; führt immer ein abschließendes doSchuetzen() aus – auch wenn ensureUnprotectedInScope() nie gefeuert hat
scopeFuer(TurnierSystem, WorkingSpreadsheet) Convenience-AutoCloseable für Aufrufer außerhalb eines SheetRunner (z. B. modale Dialoge im ProtocolHandler-Pfad)
mitFallbackEntsperrt(sheet, Runnable) Physische Absicherung direkt am Schreibpunkt, falls der globale TurnierModus.istAktiv()-Flag vom tatsächlichen Sheet-Zustand abweicht

Innerhalb eines aktiven Scopes sind die öffentlichen schuetzen()/entsperren()-Aufrufe No-Ops. Außerhalb eines Scopes wirft ensureUnprotectedInScope() eine IllegalStateException, wenn der Turnier-Modus aktiv ist — Style-/Conditional-Format-Mutationen müssen also innerhalb eines SheetRunner.run() bzw. eines scopeFuer(...)-Blocks laufen.

Bedingte Formatierung und Sheet-Schutz — kritische LO-Einschränkung

xPropSet.setPropertyValue("ConditionalFormat", xEntries) ruft intern ReplaceConditionalFormat auf. Bei einem tab-geschützten Sheet kehrt die Methode lautlos ohne Exception zurück — LO hat aber zuvor bereits alle CF-Daten gelöscht, sodass alle bedingten Formatierungen spurlos verschwinden.

Operation Verhalten bei Sheet-Schutz
CellStyleHelper.apply() (Styles) Wirft RuntimeException → gefangen → WARN
setPropertyValue("ConditionalFormat", ...) return; ohne Exception → lautlos, kein Log-Eintrag

Aktuelles Muster für alle Style-/CF-mutierenden Operationen (ConditionalFormatHelper, RangeHelper.clearRange/setDataInRange):

BlattschutzManager.get().ensureUnprotectedInScope();
// ... Style-/ConditionalFormat-Mutation ...
// Schützen NICHT hier – passiert automatisch am Ende des umschließenden
// BlattschutzManager.endCommandScope() (bzw. scopeFuer(...)-try-with-resources)

Global gesperrte Sheets

BlattschutzManager.mitGlobalenSchutzInfos() ergänzt automatisch das Teilnehmer-Sheet als vollständig gesperrt — unabhängig von der systemspezifischen IBlattschutzKonfiguration. Eine neue Konfiguration muss dieses Sheet also nicht separat behandeln.

Neues Turniersystem anschließen

  1. FooBlattschutzKonfiguration implements IBlattschutzKonfiguration in foo/blattschutz/ anlegen — Vorbild: supermelee/blattschutz/SupermeleeBlattschutzKonfiguration.java
  2. In BlattschutzRegistry, static-Block: REGISTRY.put(TurnierSystem.FOO, FooBlattschutzKonfiguration.get())
  3. Editierbare Bereiche per SheetMetadataHelper.findeSheet() + getSchluesselMitPrefix() ermitteln
  4. Zeilengrenzen aus vorhandenen Konstanten übernehmen (z. B. MeldungenSpalte.MAX_ANZ_MELDUNGEN) — keine Magic Numbers

Clone this wiki locally