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