von Jürgen Klawitter
Mit dem Begriff "EditHook" konnte ich bis vor kurzem wenig anfangen. Das änderte sich, als ich für eines meiner Programme ein Eingabefenster zu basteln begann. Es sollte ein Stringgadget und darunter zwei Boolgadgets (Ok, Cancel) aufweisen. Für das Cancel-Gadget sollte es einen Shortcut geben (RAmiga-C). Natürlich sollte die gadtools.library zum Einsatz kommen.
Leider werden Shortcuts von Gadtools kaum unterstützt. Man kann lediglich mit dem Tag GT_Underscore erreichen, daß _der_ Buchstabe im GadgetText, der als Shorcut vorgesehen ist, unterstrichen wird. Mehr ist nicht drin. Wenn der User RAmiga-C drückt, bekommt das Programm keine entsprechende IntuiMessage, weil das Stringgadget alle Tasteneingaben absorbiert, solange es aktiv ist. Dem User zuzumuten, erst das Stringgadget zu deaktivieren, um dann den Shorcut einzugeben, wäre ein Witz (Es gibt aber nicht wenige Pro- gramme, die so verfahren). Man bräuchte eine Möglichkeit, Usereingaben zu empfangen und zu bearbeiten, _bevor_ sie im Stringgadget Wirkung zeigen. Genau dies leistet ein EditHook.
Ein EditHook ist eine Subroutine, die von Intuition immer dann angesprungen wird, wenn eine Usereingabe erfolgt. Der Hook wird mit Informationen darüber versorgt, welches Zeichen eingegeben wurde und welche Aktion von Intuition vorgesehen ist. Der Hook kann dann die Aktion zulassen, unterbinden oder modifizieren. Damit Intuition weiß, ob ein EditHook angesprungen werden soll, muß eine Standard-Hookstruktur ausgefüllt werden, und der Hook muß angemeldet werden. Letzteres gestaltet sich bei Verwendung von Gadtools recht einfach: Man setzt bei CreateNewGadgetA() den Tag GTST_EditHook und fügt die Adresse der Hookstruktur an. Gadtools alloziert daraufhin den sog. Workbuffer und initialisiert auch die StringExtend-Struktur entsprechend.
Die Hookstruktur hat folgenden Aufbau (utility/hooks.h):
struct Hook struct h_MinNode ;auf Null setzen ULONG h_Entry ;Adresse der Hookroutine ULUNG h_SubEntry ;hat bei Assemblerprogrammen keine Bedeutung VOID h_Data ;Programmdaten, Länge beliebig
h_SubEntry und h_Data werden von Intuition nicht beachtet. SubEntry ist nur für Hochsprachen von Bedeutung, die mit den CPU-Registern nicht direkt um- gehen können. Der Eintrag ist hier ein Zeiger auf die eigentliche Routine, die von einer Assemblerroutine aus (Zeiger darauf in h_Entry) angesprungen wird. Die Assemblerroutine packt lediglich die Register a0 bis a2 auf den Stack, damit sie von der Hochsprachenroutine in h_SubEntry ausgewertet werden können.
h_Data wird man mit Daten initialisieren, die die Hookroutine benötig. Sie hat nämlich, da im Kontext von Intuition laufend, keinen direkten Zugriff auf programmeigene Daten. In h_Data könnte man z.B. die Zeichen eintragen, die als Shortcut dienen sollen.
Wenn die Hookroutine angesprungen wird, bekommt sie in den Registern a0-a2 Zeiger auf verschiedene Datenbereiche:
a0: Zeiger auf die Hookstruktur a1: Zeiger auf eine "Message" a2: Zeiger auf SGWork-Struktur
Man sieht, daß über a0 sehr leicht auf h_Data zugegriffen werden kann. Register a1 weist auf ein Longword, das entweder den Wert SGH_KEY (1L) oder SGH_CLICK (2L) aufweist. SGH_KEY steht für Tasteneingabe, SGH_CLICK für Mausklick ins Gadget. Am interessantesten ist der Zeiger auf SGWork. Hier findet man die meisten Informationen. Diese Struktur hat folgenden Aufbau:
struct SGWork struct Gadget ;Zeiger auf Stringgadget struct StringInfo ;Zeiger UBYTE Workbuffer ;Zeiger UBYTE Prevbuffer ;Zeiger auf Puffer mit bisherigen Eingaben ULONG Modes struct IEvent ;Zeiger auf InputEvent UWORD Code ;1 Zeichen nach Übersetzung mit KeyMap WORD Bufferpos ;aktuelle Position des Cursors WORD NumChars ULONG Actions ;was Intuition tun soll struct GadgetInfo UWORD EditOp ;was Intuition zu tun beabsichtigt
Es würde zu weit führen, hier die Bedeutung der Elemente von SGWork im Detail zu erläutern. Wer einen EditHook schreiben will, wird ohnehin nicht daran vorbeikommen, das Manual zu wälzen. Nur so viel: Der Workbuffer enthält, was Intuition in das Stringgadget auszugeben beabsichtigt, also alle bisher eingegebenen Zeichen einschließlich des aktuellen, noch nicht ausgegebenen. Der Hook darf den Workbuffer verändern. Das gilt auch für NumChars, BufferPos und Actions. Die anderen Felder dürfen nur gelesen werden.
Um es anschaulich zu machen, will ich nachfolgend beschreiben, wie der Hook
auf die Eingabe RAmiga-
(1) | Message = SGH_KEY? Falls ja, bei (2) weiter, sonst Ende. |
(2) | Steht in sgw_Code ein Shortcut? Dazu mit h_Data nach Umwandlung in Großbuchstabe vergleichen. Bei Nichtübereinstimmung raus. |
(3) | Wurde gleichzeitig die rechte Amiga-Taste gedrückt? Dazu ie_Qualifier im InputEvent untersuchen. Hier muß $8080 stehen. |
(4) | Es wurde ein Shortcut eingegeben. Wir müssen nun Intuition veranlassen, das Stringgadget zu deaktivieren und eine IntuiMessage auszugeben. Dazu werden in sgw_Actions die Bits SGA_END|SGA_REUSE gesetzt, die anderen (z.B. SGA_USE) werden gelöscht. SGA_REUSE bewirkt, daß Intuition das InputEvent wiederverwendet und eine GADGETUP-Message sendet, die im in_Code-Feld den Wert enthält, der in sgw_Code steht. |
(5) | Beim Verlassen der Hook-Routine soll Register d0=Null sein, wenn man die "Message" nicht verstanden hat. Der Sinn dieser Empfehlung wird im Manual nicht weiter erläutert. Vermutlich wurde sie in Hinblick auf zukünftige Erweiterungen getroffen. Nach meiner Erfahrung funktioniert alles bestens, wenn man d0 immer ungleich Null setzt. Falls man eine Message nicht kennt, wird man ohnehin nichts verändern und Intuition führt dann die jeweilige Aktion standardmäßig durch. |
Im folgenden Beispielhook wird die Bearbeitung von Shortcuts (RAmiga-C und ein weiterer) gezeigt. Man sieht, daß hierfür nur wenige Zeilen Code nötig sind.
Außerdem wird das Zeichen 31 an Cursorposition eingefügt, wenn der User die Help-Taste gedrückt hat. Normalerweise hat sie keinen Effekt. Wenn der Hook ein Help-Ivent entdeckt, wird sgw_Actions nicht verändert, da Intuition hier bereits SGA_USE eingetragen hat. Wenn dieses Bit gesetzt ist, verwendet Intuition den Workbuffer so, wie er vom Hook zurückgelassen wurde.
_EditHook move.l (a1),d0 ;Message subq.l #1,d0 bne \h_exit ;nicht SGH_Key move.l sgw_IEvent(a2),a1 cmp.w #$5f,ie_Code(a1) ;Help? beq \ishelp tst.b ie_Qualifier+1(a1) ;RAmiga? bpl \h_exit ;rechte Amiga gedrückt move.w sgw_Code(a2),d0 beq \h_exit bclr #5,d0 ;umwandeln in Großbuchstabe cmp.b #'C',d0 beq \is ;Cancel gewünscht cmp.b 16(a0),d0 ;EditHook.h_Data bne \h_exit ;kein Shortcut \is moveq #$0a,d0 ;SGA_End!SGA_Reuse move.l d0,sgw_Actions(a2) bra \h_exit \ishelp move.l sgw_WorkBuffer(a2),a0 move.w sgw_BufferPos(a2),d0 move.w sgw_NumChars(a2),d1 addq.w #1,d1 move.l 4(a2),a1 ;stringinfo cmp.w 10(a1),d1 ;MaxChars bcc \h_exit ;Puffer voll subq.w #1,d1 lea 0(a0,d1),a1 ;Workbuffer kann noch aufnehmen add.w d0,a0 ;jetzt Zeichen 31 an Cursorpos. \lp cmp.l a0,a1 ;einfügen. Dazu alle Zeichen rechts beq \insert ;vom Cursor um 1 nach rechts ver- move.b -(a1),1(a1) ;verschieben bra \lp \insert move.b #31,(a0) lea sgw_BufferPos(a2),a0 addq.w #1,(a0)+ ;BufferPos+1 addq.w #1,(a0) ;NumChars+1 \h_exit moveq #1,d0 ;d0 ungleich Null, alles verstanden rts
Mit diesem Beispiel sind die Möglichkeiten natürlich noch längst nicht erschöpft. Man könnte z.B. durch Setzen von SGA_BEEP den Screen aufblitzen lassen, wenn der User eine falsche Eingabe macht oder eingegebene Zeichen durch andere ersetzen. Was nicht geht, ist innerhalb des Hooks eine Dosroutine aufzurufen, weil Intuition kein Prozeß ist.
Viel Erfolg beim Entwickeln eigener EditHooks.
Jürgen