← ST-Computer 10 / 1991

Shortcuts

Programmierpraxis

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:

  • Der Status der Tastatur-Umschalttasten wird benutzt, um davon abhängig einen Such-String zusammenzubauen. Er enthält zuerst ein Leerzeichen (zur Sicherheit), ein oder mehrere Symbole für die Umschalttasten (Pfeil nach oben für ‘Shift’, Circonflexe für ‘Control’, Closebox für ‘Alternate’) und eine Taste (1,2, 3,..., Großbuchstaben, !,§,$,...).
  • Das Zeichen wird dabei aus den Zeichentabellen des BIOS geholt, wobei für Tastendrücke mit ‘Shift’ die zugehörige Tabelle, für Tastendrücke ohne die ‘Capslock’-Tabelle benutzt wird. So wird das leidige Klein-/Großbuchstabenproblem umgangen.
  • In zwei geschachteten Schleifen durchsucht die Routine alle Menü-Strings nach dem Such-String. Dabei ‘hangelt’ sie sich entlang der OB_HEAD-, OB_NEXT- und OB_TAIL-Zeiger im Objektbaum von Klappbox zu Klappbox. Dabei wird auch gleich noch der zugehörige Menütitel festgestellt, so daß dieser vor Aufruf der Menübehandlung invertiert dargestellt werden kann. Zum genauen Verständnis dieser Schleifen ist es hilfreich, in [1] über die Struktur von Menübäumen nachzulesen.

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:

  • Nur Einbuchstabenbefehle sind auf diese einfache Art möglich.
  • Buchstaben müssen als Großbuchstaben ins Menü geschrieben werden.
  • bei Verwendung zweier Umschalttasten ist die Reihenfolge der Angabe wichtig. Solche Sachen sind aber sowieso nur etwas für Fingerakrobaten!
  • Die Benutzung aller Tasten oberhalb von ‘QWERTZ’ ist möglich, da die Scancodes angepaßt werden (siehe Kommentar im Listing!)

[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