Shortcuts

Wer heute als Programmierer ein Programm abliefert, das sich nicht über Tastenkürzel bedienen lässt, wird schon mal schief angesehen. Mausbedienung ist fein für den ersten und zweiten Kontakt mit neuer Software, doch der versierte Benutzer bevorzugt nach der Einarbeitungszeit meist die Tastaturbedienung.

An sich ist es nicht schwierig, eine Tastenbedienung zu implementieren. Der EVNT_MULTI()-Aufruf, der für die Menü-Nachrichten sorgt, läßt auch ein Warten auf Tastendrücke zu. Doch wie soll man die weiterverarbeiten?

Üblicherweise wird man wohl feststellen, ob die passende Umschalttaste (‘Shift’, ‘Control’ oder ‘Alternate’) gedrückt wurde, und dann in einer längeren CASE-Anweisung entsprechend der gedrückten Taste zu den Behandlungsroutinen springen. Dabei entsteht das erste Problem: Der Tastencode ist abhängig von 'Capslock’ und den Umschalttasten selbst (Groß- und Kleinbuchstaben!). Die Lösung liegt, das ist nichts neues, in der Verwendung der sogenannten Scan-Codes.

Das zweite Problem, und dieses ist schwerwiegender, liegt in der Verwendung zweier Sprungleisten, eine für Menünachrichten und eine für Tastencodes. Es ist mühsam, die Konsistenz zu gewährleisten.

Das dritte Problem ist die Änderbarkeit der Tastenkürzel. Wie schon mehrmals in diversen Artikeln veröffentlicht, möge man den Tastencode irgendwo in der Objektstruktur in unbenutzten Bits verstecken. Immerhin ist diese Lösung bereits für den Eingeweihten mit Hilfe eines Resource-Construction-Sets änderbar.

Speziell für Pull-Down-Menüs bietet sich jedoch ein anderer, einfacherer Weg an: Es ist allgemein üblich, das Tastenkürzel direkt hinter den Menüpunkt zu schreiben. So ist der Lernprozeß am kürzesten, wenn man jedesmal beim Anklicken mit der Maus die ‘Abkürzung’ lesen kann. Warum also sollte man nicht diesen Texteintrag heranziehen? Die Änderung, wiederum mit Hilfe eines Resource-Construction-Sets, ist einfacher und besser nachvollziehbar durchzuführen.

Auch das oben angesprochene zweite Problem ist lösbar: Man muß einen eintreffenden Tastencode so vorverarbeiten, daß man in dieselbe Sprungleiste wie für Menünachrichten verzweigen kann.

Genau dies erledigt die Routine, die als GFA-BASIC-Listing abgedruckt ist. Zu ihrer Funktion:

Mit dieser Routine ist es übrigens wirklich sehr einfach, ältere eigene Programme mit einer Tastaturbedienung zu versehen. Die Vorgehensweise ist im Listing skizziert. Beim Festlegen der Tastenkürzel gelten nur wenige Restriktionen:

[1] H. D. Jankowski, J. F. Reschke, D. Rabich ATARI-ST-Profibuch, 7. Auflage 1989

'
' ************* SHORTCUTS **************
'
' komfortable Routine zum (auch nachträglichen)
' Ermöglichen der Pull-Down-Menübedienung über 
' Tastenkürzel.
'
' Idee von M. Steinle, Implementation von 
' R. Grothe und M. Steinle
'
' Programmiersprache: GFA-BASIC 3.XX
'
' (c) 1991 MAXON Computer GmbH
'
' **********************************************
'
' Skizze des Rahmenprogramms:
' 1) RSC laden oder Menübaum mit MENU string$()
'    erzeugen
' 2) Objektindices zuweisen etc.
' 3) Zusätzlich zur bisherigen Menüabfrage (mit 
'    ON MENU GOSUB oder EVNT_MULTI() eine Tasta-
'    turabfrage einbauen (ON MENU KEY GOSUB
'    keymenu) bzw. entsprechende Maske für
'    EVNT_MULTI())
' 4) Menü-Handler ggf. auf folgende Struktur um-
'    stricken:
'
PROCEDURE menu_handle(titel&,index&)
    SELECT index$ 
    CASE dieses&
        ' nun tun wir dies 
    CASE jenes&
        ' nun tun wir jenes 
    CASE auch_das_noch&
        ' nun tun wir auch das noch 
    CASE etc&
        ' ...
    ENDSELECT
    ~MENU_TNORMAL(menu%,titel&, 1)
    ' Titel wieder weiß machen 
RETURN
'
'   wer ON MENU GOSUB menu benutzt, braucht 
'   noch folgenden 'Durchlauferhitzer1:
'
PROCEDURE menu 
    menu%=MENU(-1)
    menu_handle(MENU(4),MENU(5))
RETURN
'
'
'
' Jetzt folgt die eigentliche Routine:
'
PROCEDURE keymenu       ! ggf. keymenu(menu%)
    '
    LOCAL scan&,state&,key_tabs%,dummy&,box& 
    LOCAL titel&,i&,last_i!,ok!,last_box!
    '
    ' Die Routine verwendet als einzige globale 
    ' Variable die Adresse des Menübaums ’menu%'
    ' Sollte im Hauptprogramm ein anderer Name 
    ' verwendet werden, muß er in den folgenden 
    ' Zeilen angepaßt werden. Die Adresse ließe 
    ' sich auch als Parameter übergeben, doch ließe 
    ' sich die Prozedur dann nicht in Verbindung 
    ' mit den komfortablen ON XXX GOSUB ...-Befeh-
    ' len des GFA-Basic verwenden. Sollte der eine 
    ' oder andere die Original-EVNT-Aufrufe be-
    ' nutzen, stellt die Übergabe als Parameter 
    ' kein Problem dar. Weiterhin muß eine Menü-
    ' behandlungsprozedur namens
    ' MENU_HANDLE(titel,index) existieren, da diese 
    ' bei Erfolg aufgerufen wird.
    '
    scan&=SHR&(GINTOUT(5),8) AND &HFF 
    ' Scancode der gerade gedrückten Taste 
    IF scan&>=&H78 AND scan&=<&H83 
        SUB scan&,&H76 
    ENDIF
    ' für die Tasten <alt>"!' bis <alt>''' kommen 
    ' eigene Scancodes (bloß wozu???) 
    state&=GINTOUT(4) AND &HF 
    ' Status der Tastatur-Umschalttasten 
    key_tabs%=XBI0S(16,L:-1,L:-1,L:-1)
    ' Adresse der Zeigertabelle für die Umkodie-
    ' rungstabellen für Tastendrücke
    '
    ' In der folgenden CASE-Anweisung können durch-
    ' aus eigene Suchzeichen eingesetzt werden. Die 
    ' Tabelle darf auch erweitert werden! Die vor-
    ' geschlagenen Zeichen haben sich mittlerweile 
    ' zu einer Art Standard entwickelt.
    ' Bei gedrückter 'Shift'-Taste wird die Shift-
    ' Konvertierungstabelle benutzt, sonst die 
    ' Capslock-Tabelle. An der Adresse 'key_tabs' 
    ' stehen die Adressen der drei Tabellen direkt 
    ' hintereinander. Der Scancode dient auch dem 
    ' Betriebssystem als Index in diese Tabellen.
    ' Daher machen wir's auch so.
    '
    SELECT state&
    CASE 1 TO 3     ! 'Shift'
        code&=BYTE{scan&+{key_tabs%+4)} 
        such$=CHR$(1)
        ' Pfeil nach oben 
    CASE 4          ! 'Control'
        code&=BYTE{scan&+{key_tabs%+8}} 
        such$="A"
    CASE 5 TO 7     ! 'Shift/Control'
        code&=BYTE{scan&+{key_tabs%+4}} 
        such$-CHR$(1)+"^"
        ' Pfeil nach oben zuerst!
    CASE 8          ! 'Alternate'
        code&=BYTE{scan&+{key_tabs%+8}) 
        such$=CHR$(7)
        ' Window-Close-Box 
    CASE 9 TO 11    ! 'Shift/Alt'
        code&=BYTE{scan&+{key_tabs%+4}} 
        such$=CHR$(1)+CHR$(7)
        ' Pfeil nach oben zuerst!
    DEFAULT
        such$=""
    ENDSELECT
    '
    ' die Suchroutine verläPt sich auf die fest-
    ' gelegte und dokumentierte Struktur eines 
    ' Pull-Down-Menüs unter GEM. Jede Menüzeile,
    ' die sich mit MENU_BAR() anmelden und verwal-
    ' ten läßt, sollte diese Routine nicht aus dem 
    ' Tritt bringen.
    '
    IF such$<>"" ! was zu suchen?
        such$=" "+such$+CHR$(code&) ! String komplett
        dummy&=OB_HEAD(menu%,0)
        ' Index des ersten Kinds des (Urgroß-)mutter-
        ' Objekts
        titel& =OB_HEAD(menu%,OB_HEAD(menu%,dummy&))
        ' Index des ersten Titels
        box&=OB_HEAD(menu%,OB_NEXT(menu%,dummy&))
        ' Index der ersten Klappbox 
        ok!=FALSE               ! bisher!
        REPEAT
            i&=OB_HEAD(menu%,box&) ! erster Eintrag
            REPEAT
                IF OB_TYPE(menu%,i&)=28 ! STRING??
                    '
                    ' falls das aktuelle Objekt ein 
                    ' String-Objekt ist, wird der 'such$'
                    ' darin gesucht.
                    '
                    ok!=INSTR(CHAR{OB_SPEC(menu%,i&)},such$) 
                ENDIF 
                IF ok!
                    '
                    ' falls ein Menüstring mit der pas-
                    ' senden Tastenkombination gefunden 
                    ' wurde, wird der zugehörige Menütitel 
                    ' schwarz gemacht und der ganz normale 
                    ' Menü-Handler aufgerufen.
                    '
                    IF NOT BTST(OB_STATE(menu%,i&),3)
                        ' Eintrag nicht gesperrt
                        IF NOT BTST(OB_STATE(menu%,titel&),3)
                            ' Titel auch nicht 
                            ~MENU_TNORMAL(menu%,titel&,0)
                            ' Titel schwarz machen 
                            menu_handle(titel&,i&)
                            ' normalen Menühandler anspringen 
                        ENDIF
                    ENDIF
                ELSE
                    ' überprüfen, ob das aktuelle Objekt 
                    ' vielleicht das letzte Kind seines
                    ' Mutterobjekts (seiner Klappbox) ist.
                    ' Das ist genau dann der Fall, wenn
                    ' OB_NEXT des aktuellen Objekts auf's 
                    ' Mutterobjekt 'zurück'zeigt.
                    ' OB_TAIL des Mutterobjekts zeigt eh 
                    ' auf das letzte Kind. So kommt man zu 
                    ' folgender wundervollen Bedingung:
                    '
                    last_i!=(i&=OB_TAIL(menu%,OB_NEXT(menu%,i&))) 
                    IF NOT last_i!
                        i&=OB_NEXT(menu%,i&)
                    ENDIF
                ENDIF
            UNTIL ok! OR last_i!
            '
            ' ganz analog muß man darauf achten, die 
            ' letzte Klappbox nicht zu verpassen:
            '
            last_box!=(box&=OB_TAIL(menu%,OB_NEXT(menu%,box&))) 
            IF NOT last_box!
                box&=OB_NEXT(menu%,box&)
                ' zur nächsten Klappbox 
                tite1& =OB_NEXT(menu%,titel&)
                ' gleichzeitig zum nächsten Titel 
            ENDIF
        UNTIL ok! OR last_box!
    ENDIF
RETURN

Michael Steinle
Aus: ST-Computer 10 / 1991, Seite 84

Links

Copyright-Bestimmungen: siehe Über diese Seite