Viele der augenfälligsten Neuerungen von jadice 5 betreffen die Bedienung und die Benutzeronerfläche der Views, zuvorderst dabei sicher die Klasse PageView. Die Views selbst steuern dabei jedoch nur einen begrenzten Teil des Verhaltens bei. Der weitaus größere Teil wird von unabhängigen Komponenten bereitgestellt, die an Views angedockt werden können, um deren Funktionsumfang zu erweitern oder anzupassen. In jadice 4 existierte bereits ein API, das eine ganz ähnliche Aufgabenbenbeschreibung hatte, nämlich das Gespann aus EditEventListener und EditPane. Jadice 5 stellt hierfür eine grundlegende überarbeitete API zur Verfügung, die mächtiger und einfacher zu verwenden ist – das Tool-API.

Die Klasse Tool ist zentrale Basis für Werkzeuge und hat vier wesentliche Aufgabenbereiche:

  1. die Verarbeitung von Eingabeereignissen

  2. das Anreichern der Darstellung des Views mit eigenen Elementen, das heißt die Teilname am Rendering des Views

  3. das Beitragen von Kontextmenüelementen

  4. die Teilnahme an der Zustandsverwaltung im Zusammenspiel mit dem ToolManager

Bis auf den letzten Punkt sind alle Aufgaben optional. Tools müssen nicht auf Eingaben reagieren oder visuelle Beiträge zur Oberfläche liefern.

Auch die Erstellung eigener Tools ist recht einfach durch Erweiterung der abstrakten Klasse Tool möglich.

Tools werden von den zugehörigen Views mit Eingabeereignissen versorgt, wodurch sie die Möglichkeit erhalten, auf Eingaben zu reagieren. Die von AWT/Swing bekannte Klassenhierarchie für Ereignisse (InputEvent und die davon abgeleiteten Klassen) bildet die Basis für die Eingabeereignisse. Allerdings werden diese nicht direkt verwendet, da Tools für ihre Arbeit in der Regel weitere Kontextinformationen, wie die betroffene Seite (Page), die aktuellen RenderControls und ähnliches benötigen. Aus diesem Grund stellt jadice den InputEvents eine analoge Klassenhierarchie unterhalb der abstrakten Klasse EditEvent zur Seite: KeyEditEvent, MouseEditEvent und MouseWheelEditEvent. Jedes EditEvent bietet immer auch den Zugriff auf den usprünglich auslösenden InputEvent. EditEvents beziehen sich oft auf eine Page, zum Beispiel dann, wenn sich der Mauszeiger in der Nähe oder über der Darstellung einer Seite befindet. Allerdings können die Ereignisse auch ohne Seitenkontext auftreten, weshalb Tools mit diesem Umstand umgehen können müssen.

In der Praxis treten bei der Ereignisverarbeitung im Zusammenhang mit in Views dargestellten Seiten immer wieder ähnliche Anforderungen zutage. Die EditEvents versuchen hierbei die häufigsten Anforderungen auf komfortable Weise abzudecken. Beispielsweise bietet das MouseEditEvent fünf verschiedene Methoden zum Zugriff auf die Koordinaten der Maus:

  • getInputEvent().getPoint() entspricht den von AWT/Swing gewohnten Koordinaten in Pixeln, bezogen auf die obere linke Ecke des Views.

  • getPoint() liefert die Bildschirmkoordinaten (Pixel) bezogen auf die obere linke Ecke der Seite, auf die sich das Event bezieht. 

  • getConstrainedPoint() liefert die gleichen Werte wie getPoint(), allerdings werden diese, wenn sich die Maus außerhalb einer Seite befindet, so begrenzt, dass der nächstgelegene Punkt innerhalb der nächstgelegenen Seite geliefert wird.

  • getDocumentPoint() liefert die Dokumentkoordinaten, das heißt Koordinaten in Einheiten von Document.BASE_RESOLUTION, bezogen auf die obere linke Ecke der nächstgelegenen Seite.

  • getConstrainedDocumentPoint() entspricht wieder getConstrainedPoint(), allerdings findet hier erneut das »Einfangen« der Koordinaten wie bei getConstrainedPoint() statt.

Die Klasse Tool nutzt für die Übergabe von EditEvents zunächst die Methode handleEditEvent(...). Die Standardimplementierung dieser Methode nimmt eine Verteilung an eventspezifische Template-Methoden wie keyPressed, mouseClicked und so weiter, vor. Nachfahren der Klasse Tool können so sehr komfortabel selektiv nur diejenigen Ereignismethoden überschreiben, die das Tool tatsächlich benötigt.

Da zu einer View häufig mehr als ein Tool registriert ist, ist es wichtig, die Reihenfolge, in der die verschiedenen Tools mit Ereignissen versorgt werden, beeinflussen zu können. Tools können weitgehend selbst steuern, wie früh oder spät in der Reihenfolge sie mit Ereignissen versorgt werden. Die Methode getDispatchPriority() des Tools muss hierzu einen Wert zwischen Tool.MIN_PRIORITY und Tool.MAX_PRIORITY zurückgeben. Je näher an MAX_PRIORITY der Wert ist, desto früher wird das Tool mit Ereignissen versorgt.

Hat ein Tool ein Ereignis erhalten und hat es sich dafür entschieden, darauf zu reagieren, ist es oft sinnvoll dafür zu sorgen, dass keine anderen Tools mehr auf das Ereignis reagieren. Hierzu genügt es, beim Verarbeiten des Ereignisses dessen consume()-Methode aufzurufen. Sobald ein Ereignis konsumiert wurde, wird es nicht mehr an weitere Tools zur Verarbeitung übergeben. Im Umkehrschluss bedeutet dies auch, dass Tools nicht explizit prüfen müssen, ob ein Ereignis bereits konsumiert wurde, da sie dieses sonst überhaupt nicht erhalten hätten.

Nachdem Views die Seitendarstellungen gerendert haben, erhalten Tools die Gelegenheit, eigene visuelle Elemente beizutragen. Für jede sichtbare Seite wird zu diesem Zweck die Tool-Methode render(...) aufgerufen, die ein Parameterobjekt (RenderParameters) sowie die Information, ob das Tool gerade aktiv ist, übergeben bekommt. Die RenderParameters enthalten alle Kontextinformationen, die zum Rendering benötigt werden, zum Beispiel den Zielgrafikkontext (Graphics2D), die Seite, den Bereich, in den die Seite gerendert wurde und so weiter.

Wie es eine Dispatch-Reihenfolge für die Verteilung von Ereignissen gibt, existiert eine separate Reihenfolge für das Zeichnen der Tools. Über getRenderPriority() kann ein Tool festlegen, wie früh oder spät es beim Rendering zum Zug kommen will. In der Regel sollten die Dispatch- und Renderreihenfolge in einer inversen Beziehung zueinander stehen. Tools können konzeptionell wie Ebenen betrachtet werden können: je weiter ›oben‹, desto früher werden Ereignisse verteilt und desto später wird gerendert. Die Methode getRenderPriority() kann Werte zwischen Tool.MIN_PRIORITY und Tool.MAX_PRIORITY zurückgeben. Die Standardimplementierung von getRenderPriority() gibt den Wert DEFAULT_PRIORITY zurück, der ein Mittelwert zwischen Tool.MIN_PRIORITY und Tool.MAX_PRIORITY ist.

Die Verwaltung des Zustandes von Tools obliegt dem ToolManager. Der Zustandsraum von Tools umfasst dabei folgende Freiheitsgrade:

registriert/nicht registriert

Tools, die (noch) nicht bei einem bestimmten ToolManager registriert wurden, können in keiner Weise mit ihrem View interagieren. Erst durch die Registrierung werden sie dazu in die Lage versetzt. Bei der Registrierung wird die Methode setManager(ToolManager) des Tools aufgerufen.

eingeschaltet/ausgeschaltet (enabled/disabled)

Bereits bei der Registrierung eines Tools kann angegeben werden, ob das Tool danach ein- oder ausgeschaltet (enabled/disabled) sein soll. Enabled Tools sind dem ToolManager zwar bekannt, sie erhalten aber weder Ereignisse, noch die Gelegenheit zum Rendering. Ob ein Tool enabled ist, kann durch die Methode setEnabled(Class<? extends Tool>, boolean) gesteuert werden.

aktiv/inaktiv

In manchen Situationen kann es notwendig sein, dass ein Tool, obwohl es in der Dispatch-Reihenfolge eigentlich nicht an erster Stelle steht, temporär vorrangig vor allen anderen Tools die Gelegenheit zur Verabeitung von Ereignissen erhält. Dies kann beispielsweise dann nützlich sein, wenn eine Programmoberfläche implementiert werden soll, die dem Nutzer verschiedene Bearbeitungsmodi, die von Tools realisiert werden, anbietet. Die Umschaltung zwischen diesen Modi würde dann durch das Aktivieren des entsprechenden (Modus-)Tools verwirklicht werden.

Im ToolManager wird diese Auszeichnung als aktives Tool verwaltet. Ein Tool kann aktiv gesetzt werden mithilfe der Methode activate(...). Es kann immer nur ein Tool gleichzeitig aktiv sein, daher führt ein Aufruf der Methode activate(...) immer auch zum Inaktivieren des letzten aktiven Tools, sofern es ein solches gab.

Neben dieser ›manuellen‹ Art Tools zu aktivieren, kann eine ToolActivationPolicy die Auswahl des jeweils aktiven Tools automatisieren. Dazu kann im ToolManager die gewünschte ToolActivationPolicy mit der Methode setActivationPolicy(ToolActivationPolicy) gesetzt werden. Vor der Ereignisverarbeitung erhält die Policy eine Liste aller Tools, die sich um Aktivierung ›bewerben‹. Sie wählt daraus, welches Tool den Zuschlag bekommt und das Ereignis vorrangig verarbeiten darf.

exklusiv

Es kann sinnvoll sein, bestimmten Tools für einen begrenzten Zeitraum die alleinige Kontrolle zu überlassen, um Konflikte mit anderen Tools während komplexer Operationen zu vermeiden. Diese Exklusivität greift zum Beispiel bei der Annotationsbearbeitung, während Annotationen in der Größe verändert oder verschoben werden, sowie während der Texteditor aktiv ist. Der Exklusivmodus sorgt dafür, dass lediglich das exklusive Tool mit Ereignissen versorgt wird und Gelegenheit zum Rendering erhält. In der Regel ist es das Tool selbst, das den Exklusivmodus mittels ToolManager.setExclusive(Class<? extends Tool>) aktiviert. Bei der Verwendung des Exklusivmodus ist es entscheidend, dafür zu sorgen, dass dieser auch zuverlässig wieder zu verlassen wird, da sonst gegebenenfalls die Funktion aller anderen Tools dauerhaft blockiert wird.

Einen Teil der Funktionen des ToolManagers haben wir oben bereits kennen gelernt, insbesondere diejenigen aus Sicht des Tools. Für die Integration stellt sich aber zunächst natürlich die Frage, wie auf ToolManager überhaupt zugegriffen werden kann, und welche Funktionen er bereitstellt.

ToolManager leben immer in enger Symbiose mit einer ViewComponent: Jeder ToolManager hat immer genau eine ViewComponent und jede ViewComponent hat immer einen ToolManager. Derzeit existieren zwei konkrete Implementierungen von ViewComponent: PageView und ThumbnailView. Der ToolManager einer ViewComponent kann mit ViewComponent.getToolManager() erfragt werden. Einige der häufigsten Idiome bei der Arbeit mit dem ToolManager sind im folgenden Code-Beispiel aufgeführt.

ViewComponent viewComponent = ...;

// Registrieren eines Tools
viewComponent.getToolManager().register(MyTool.class, true);

// Erfragen und Konfigurieren eines Tools
if(viewComponent.getToolManager().hasTool(MyTool.class))
  viewComponent.getToolManager().getTool(MyTool.class).setSomeOption(true);

// Aktivieren/Deaktivieren eines Tools
viewComponent.getToolManager().setEnabled(myTool.class, enabled);

// Deregistrieren eines Tools
viewComponent.getToolManager().deregister(MyTool.class);
[jadice® document platform 5 Dokumentation. Erstellungsdatum: 2012-01-19]
loading table of contents...