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.
Das Tool
Die Klasse Tool
ist zentrale Basis für Werkzeuge und hat vier wesentliche
Aufgabenbereiche:
die Verarbeitung von Eingabeereignissen
das Anreichern der Darstellung des Views mit eigenen Elementen, das heißt die Teilname am Rendering des Views
das Beitragen von Kontextmenüelementen
die Teilnahme an der Zustandsverwaltung im Zusammenspiel mit dem
ToolManager
Bis auf den letzten Punkt sind alle Aufgaben optional. Tool
s 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.
Tool
s 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
Tool
s 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 InputEvent
s 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
. EditEvent
s 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 Tool
s 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 wiegetPoint()
, 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 vonDocument.BASE_RESOLUTION
, bezogen auf die obere linke Ecke der nächstgelegenen Seite.getConstrainedDocumentPoint()
entspricht wiedergetConstrainedPoint()
, allerdings findet hier erneut das »Einfangen« der Koordinaten wie beigetConstrainedPoint()
statt.
Die Klasse Tool
nutzt für die Übergabe von EditEvent
s
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 Tool
s mit Ereignissen versorgt werden,
beeinflussen zu können. Tool
s können weitgehend selbst steuern, wie früh oder spät
in der Reihenfolge sie mit Ereignissen versorgt werden. Die Methode
getDispatchPriority()
des Tool
s 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 Tool
s 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 Tool
s zur Verarbeitung übergeben. Im
Umkehrschluss bedeutet dies auch, dass Tool
s 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 Tool
s 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 Tool
s. Ü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. Tool
s 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 Tool
s obliegt dem ToolManager
. Der
Zustandsraum von Tool
s 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 MethodesetManager(ToolManager)
desTool
s aufgerufen.- eingeschaltet/ausgeschaltet (enabled/disabled)
Bereits bei der Registrierung eines
Tool
s kann angegeben werden, ob dasTool
danach ein- oder ausgeschaltet (enabled/disabled) sein soll. EnabledTool
s sind demToolManager
zwar bekannt, sie erhalten aber weder Ereignisse, noch die Gelegenheit zum Rendering. Ob einTool
enabled ist, kann durch die MethodesetEnabled(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 anderenTool
s 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-)Tool
s verwirklicht werden.Im
ToolManager
wird diese Auszeichnung als aktivesTool
verwaltet. EinTool
kann aktiv gesetzt werden mithilfe der Methodeactivate(...)
. Es kann immer nur einTool
gleichzeitig aktiv sein, daher führt ein Aufruf der Methodeactivate(...)
immer auch zum Inaktivieren des letzten aktivenTool
s, sofern es ein solches gab.Neben dieser ›manuellen‹ Art
Tool
s zu aktivieren, kann eineToolActivationPolicy
die Auswahl des jeweils aktivenTool
s automatisieren. Dazu kann imToolManager
die gewünschteToolActivationPolicy
mit der MethodesetActivationPolicy(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
Tool
s für einen begrenzten Zeitraum die alleinige Kontrolle zu überlassen, um Konflikte mit anderenTool
s 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 exklusiveTool
mit Ereignissen versorgt wird und Gelegenheit zum Rendering erhält. In der Regel ist es dasTool
selbst, das den Exklusivmodus mittelsToolManager.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 anderenTool
s dauerhaft blockiert wird.
Der ToolManager
Einen Teil der Funktionen des ToolManager
s haben wir oben bereits kennen gelernt,
insbesondere diejenigen aus Sicht des Tool
s. 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);