#Titel Programmieren / Implementierung eines EditHooks
#Logo gadget34:Pinsel/AG.Prog
#Font Losse 16
#C31
 Implementierung eines EditHooks
 -------------------------------
#Font topaz 8
#C10
                von Jürgen Klawitter
#C21

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):

#C10
    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
#C21

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:
#C10
        a0: Zeiger auf die Hookstruktur
        a1: Zeiger auf eine "Message"
        a2: Zeiger auf SGWork-Struktur
#C21

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:

#C10
    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
#C21

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-<Shortcut> 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.

#C10
_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
#Y+4
\h_exit moveq   #1,d0                   ;d0 ungleich Null, alles verstanden
        rts
#C21
#Y+4
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.
#Y+4
    Viel Erfolg beim Entwickeln eigener EditHooks.
#C31
                                                                 Jürgen
