prog

Implementierung eines EditHooks

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- reagieren muß.

(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


Zurück