JUMP: Dem Turbo C-Editor auf die Sprünge geholfen

Wir schreiben das Jahr 1990. Die Zeiten unbequemen Programmierens mit lahmen Editoren ohne jeden Komfort sind endgültig vorbei. In ganz Deutschland hat sich die Erkenntnis durchgesetzt, dass eine marken -funktion so selbstverständlich zur Ausrüstung eines Quelltexteditors gehört wie eine Block- oder Such-Funktion. In ganz Deutschland? Nein! Tief im Süden der Republik wehrt sich ein kleines Häuflein unentwegter standhaft gegen die Zeichen der Zeit und die Ansprüche der Benutzer.

Ihr Produkt, Turbo C, ist in jeder Beziehung erstklassig: schneller, kompakter Code, kurze Turnaround-Zeiten, optimale Zusammenarbeit zwischen Editor, Compiler und Linker. Erstklassig in wirklich jeder Beziehung? Nein! Der Editor ist lahm wie weiland Wordplus. Und - er besitzt keine Markenfunktion!

Diesem bedauernswerten Zustand wird jetzt (endlich!) ein Ende gesetzt, denn Jump ist da. Jump hilft TC auf die Sprünge. Mit Jump kann man neun Marken beliebig im Quelltext definieren und blitzschnell anspringen. Und Jump springt ebenso schnell an den Ausgangspunkt zurück. Zusätzlich zu dieser konventionellen Markenfunktion kann Jump den Editor veranlassen, die aktuelle Cursor-Zeile in den Zwischenspeicher zu übernehmen. Am Zielort wird diese Zeile dann mit Insert z.B. an eine Liste von Funktionsdeklarationen angefügt. Natürlich ist Jump ein Accessory.

Da die Funktionstasten des ST vom Editor ohnehin ignoriert werden, bietet es sich an, diese für die Marken zu nutzen. Drückt man eine Funktionstaste zusammen mit Shift, merkt sich Jump die aktuelle Cursor-Zeile. Ohne Shift wird eine solche Marke angesprungen. Hält man die Control-Taste gedrückt, wird die aktuelle Zeile in den Zwischenspeicher kopiert und dann gesprungen. Die Taste F10 kann nicht mit einer Marke belegt werden. Hier speichert Jump die Ausgangszeile, damit man bequem dorthin zurückkehren kann.

Alle Funktionen des Accessories basieren letztendlich auf den Funktionen des Editors. Eine zentrale Rolle spielt dabei die Find line-Funktion. Sie wird per Control-L aufgerufen, und es erscheint eine Dialogbox, in die die aktuelle Zeile vom Editor eingetragen ist. Jump liest diese Zeilennummer aus und trägt die neue ein, indem es einfach Tastendrücke auf die entsprechenden Zifferntasten simuliert. Schließlich kommt noch die Simulation von Return - und ab geht die Post. Um diese Aktionen auszuführen, muß Jump:

  1. einen Druck auf eine Funktionstaste abfangen können,
  2. Tastendrücke simulieren können und
  3. die Adresse wissen, unter der TC die aktuelle Zeilennummer einträgt, damit diese ausgelesen werden kann.

Tastendrücke

Ein Tastendruck wird üblicherweise mit Hilfe der AES-Funktion evnt_keybd() abgewartet. Leider liefert aber das AES Tastendrücke grundsätzlich nur an die Hauptapplikation (also TC in diesem Falle) und nicht an Accessories. Dieser einfache Weg, die Funktionstasten zu überwachen, ist also für Jump verbaut.

Glücklicherweise werden Tastendrücke intern in einem Tastaturpuffer zwischengespeichert. Der Tastaturpuffer wird als Ringpuffer verwaltet, und zwar mit Hilfe zweier „Zeiger“, die jeweils auf die aktuelle Schreib- und Leseposition zeigen. Mit Hilfe der Betriebssystemfunktion Iorec() (XBIOS 14) bekommt man einen Zeiger auf die in Bild 1 beschriebene Struktur.

Es gibt drei solcher Puffer im ST, für MIDI, die Tastatur und die serielle Schnittstelle. Die letzten beiden Einträge der Struktur sind nur für die serielle Schnittstelle interessant. Der Aufruf von Iorec(1) liefert einen Zeiger auf die Iorec-Struktur für den Tastaturpuffer.

In seiner Hauptschleife überwacht Jump den Puffer, indem es sich einfach iorec->ibufhd merkt, ein bißchen wartet und kontrolliert, ob der Wert sich verändert hat. Wenn ja, wurde etwas in den Puffer geschrieben, also eine Taste gedrückt. Jump kontrolliert, ob es sich um eine Funktionstaste handelt, und tritt bei Bedarf in Aktion.

Der Tastaturcode ist immer ein Langwort, und zwar steht im oberen Wort der Scan- und im unteren der ASCII-Code der gedrückten Taste. Bei Funktionstasten ist der ASCII-Code natürlich 0. Sie sind übrigens die einzigen Tasten, bei denen sich der Scancode mit Shift von dem ohne Shift unterscheidet. Jump lauert in main() auf Tastendrücke. Da natürlich auch auf eine Anwahl des Accessories im Menü reagiert werden muß, geschieht dies mit evnt_multi().

Hat man einmal einen Zeiger auf die Iorec-Struktur, ist es ein Leichtes, einen Tastendruck zu simulieren [s. Funktion tastendruck() in Listing 1], Man schreibt einfach den betreffenden Code als Langwort an die Adresse iorec->ibuf (Anfang des Tastaturpuffers) und paßt iorec->ibufhd und iorec->ibuftl entsprechend an, um dem Rest der Welt vorzugaukeln, daß soeben ein Tastendruck stattgefunden hat. Allerdings muß man dann dem Rest der Welt auch Gelegenheit geben, den Tastendruck zur Kenntnis zu nehmen. Solange nämlich ein Accessory seiner Arbeit nachgeht, liegt das Hauptprogramm auf Eis. Erst ein beliebiger evnt...-Aufruf sorgt dafür, daß andere auch mal was erledigen können. In der Funktion tastendruck() werden also erst einmal Pausen eingelegt, und zwar so lange, bis der „Tastendruck“ auch wirklich angekommen ist. Hier eine konstante Zeitspanne zu warten, ist nicht angebracht. TC braucht nämlich unterschiedlich lange, um auf einen Tastendruck zu reagieren, je nachdem, was es gerade tut. Auf das Problem der Synchronisierung von Jump und TC werde ich an anderer Stelle noch einmal zurückkommen.

typedef struct 
    {
    void *ibuf; /* Zeiger auf den Pufferanfang */
    int ibufsiz; /* Größe des Puffers */
    int ibufhd; /* aktuelle Schreibposition
    int ibuftl; /* aktuelle Leseposition */
    int ibuflow; /* untere "WasserMarke"
    int ibufhi; /* obere "Wassermarke" */
    } IOREC;

Bild 1: IOREC beschreibt einen internen Puffer

typedef struct 
    {
    int ob_next;        /* nächstes Objekt */
    int ob_head;        /* erstes "Kind-"objekt */
    int ob_tail;        /* letztes "Kind-"objekt */
    unsigned ob_type;   /* Art des Objekts */
    unsigned ob_flags;  /* Manipulationsflags */
    unsigned ob_state;  /* Zustand des Objekts */
    OBSPEC *ob_spec;    /* zeiger auf weitere Struktur */
    int ob_x;           /* x-Pos. linke, obere Ecke (relativ) */
    int ob_y;           /* y-Pos. */
    int ob_width;       /* Breite des Objekts */
    int ob_height;      /* Höhe des Objekts */
    } OBJECT;

Bild 2: OBJEKT beschreibt ein Grafikobjekt

Bild 3: Die "Find Line" Dialogbox des TC Editors

Suche Nummer

Nachdem nun die minderen Probleme erledigt sind, wenden wir uns der Hauptsache zu: Wie erfährt Jump die jeweils aktuelle Zeilennummer? Der Hauptangriffspunkt ist hier, wie gesagt, die Funktion Find line des Editors. Ein Aufruf der Funktion ist leicht: Man simuliert einfach einen Tastendruck auf Control-L. Um allerdings zu verstehen, wie die Zeilennummer ausgelesen wird, muß man wissen, was im einzelnen abläuft, wenn man die Funktion aufruft.

Üblicherweise bedient man sich der AES-Funktion objc_draw(), um eine Dialogbox, wie sie auch Find line benutzt, auf den Bildschirm zu bringen. Dazu übergibt man (unter anderem) die Adresse eines sogenannten Objektbaums, in dem die einzelnen Elemente der Box codiert sind. Das AES zeichnet dann die Box mit Hilfe der im Objektbaum vorhandenen Informationen. Die Standardelemente der grafischen Benutzerführung des ST (also Menüleisten, Dialogboxen, aber auch Fenster) werden intern aus sogenannten Objekten aufgebaut, die einheitlich durch die Struktur in Bild 2 beschrieben wird.

Die meisten Einträge sind in diesem Zusammenhang uninteressant. Wer sich näher über Objekte und Objektbäume informieren will, sei z.B. auf [1] und [2] verwiesen. Ich werde an dieser Stelle nur auf drei Einträge näher eingehen: ob_type, ob_flags und ob_spec.

Es gibt insgesamt 13 Objekttypen. Sie werden durch eine entsprechende Nummer in ob_type unterschieden. So ist z.B. der äußerste Kasten des Find line-Formulars ein Objekt vom Typ G_BOX (Nr. 20), der „Titel“ des Formulars und die Eingabezeile gehören zum Typ G_BOXTEXT (Nr. 22) usw. Die Eigenschaften der einzelnen Objekte (z.B. SELECTABLE, EDITABLE usw.) sind in ob_flags bitweise codiert. So sind z.B. die beiden Buttons, „OK“ und „Cancel“ anwählbar und Exit-Objekte; „OK“ ist zusätzlich noch ein Default-Objekt, da es auch mit der Return-Taste angewählt werden kann. Also sind in den Objektstrukturen für die Buttons die Bits 0 und 2, beim „OK“-Button zusätzlich das Bit 1 gesetzt. Die Eingabezeile ist edierbar, d.h. daß das Bit 3 in ob_flags gesetzt ist (siehe Bild 3).

Offensichtlich werden alle Elemente der Dialogbox von der äußeren Box umfaßt; sie alle sind „Kind“-Objekte dieser äußeren Box. Die Adresse der äußeren Box ist die Anfangsadresse des gesamten Objektbaums. Dort fängt das AES beim Zeichnen an und „hangelt“ sich per ob_next bzw. ob_head durch den gesamten Baum, um alle Objekte zu zeichnen.

Hat man erst einmal die Anfangsadresse des Baums ermittelt, kann man ebenso verfahren, bis man ein Objekt gefunden hat, das EDITABLE ist. Da es in diesem speziellen Fall nur ein solches Objekt gibt, ist damit das Objekt gefunden, in dem sich die Eingabezeile befindet. Allerdings hat man damit immer noch nicht den Zeilen-String selbst. Dessen Adresse ist nämlich nicht direkt in die Objektstruktur eingetragen, sondern muß indirekt über ob spec ermittelt werden. Die Einträge in ob_spec sind je nach Objekttyp verschieden. Bei dem hier vorliegenden Typ G_BOXTEXT zeigt ob_spec auf eine weitere Struktur (Bild 4).

Der erste Eintrag der TEDINFO-Struktur ist endlich die Adresse des gesuchten Eingabe-Strings.

Die Eingabezeile wird im zweiten Teil der Funktion get_inputAdr() ermittelt. Wie Sie sehen, wird die While-Schleife abgebrochen, sobald ein edierbares Objekt gefunden ist. Die Anfangsadresse des Baums wird aus der globalen Variablen TCobjAdr (Zeiger auf OBJECT) übernommen. Um zu verstehen, wie diese ermittelt werden kann, muß man noch ein wenig tiefer in die Eingeweide des Betriebssystems vordringen.

typedef struct 
    {
    char *te_ptext;     /* Zeiger auf den Text */
    char *te_ptmplt;    /* Zeiger auf die Textwaske */
    char *te_pvalid;    /* Zeiger auf die Typmaske */
    int te_font;        /* Zeichensatz */
    int te_resvd1;
    int te_just;        /* Justierung des Textes */
    int te_color;       /* Farbe des Rechtecks */
    int te_resvd2;
    int te_thickness;   /* Rahmendicke */
    int te_txtlen;      /* Länge des Textes */
    int te_tmplen;      /* Länge der Textmaske */
    } TEDINFO;

Bild 4: Die "Textinformationsstruktur"

typedef struct 
    {
    long cb_pcontrol;   /* Zeiger auf control[] */
    long cb_pglobal;    /* Zeiger auf global[] */
    long cb-pinfin;     /* Zeiger auf int_in[] */
    long cb_pintout;    /* Zeiger auf int_out[] */
    long cb_padrin;     /* Zeiger auf adr_in[] */
    long cb_padrout;    /* Zeiger auf adr_out[] */
    } AESPB;

Bild 5: Der AES-Parameterblock

Ins wilde GEMDOS

Bekanntlich ist das Betriebssystem des ST in zwei Teile gegliedert, das TOS und das GEM. TOS interessiert hier nicht weiter, wohl aber GEM, das seinerseits in zwei Teile zerfällt, das VDI und das AES. Das AES stellt dem Programmierer unter anderem eine umfangreiche Sammlung von Funktionen zur Darstellung und Verwaltung grafischer Objekte zur Verfügung.

Da TC leider nicht freiwillig die Adresse der Find Line-Dialogbox herausrückt, muß man sie sich selbst direkt vom AES besorgen. Der Trick dabei ist, daß TC die Adresse ja ans AES übergeben muß, um die Dialogbox zeichnen zu lassen. Hier schmuggelt sich Jump hinein und liest sie aus, bevor das AES mit seiner Arbeit beginnt.

Beim Aufruf einer AES-Funktion müssen dieser jede Menge Parameter übergeben werden. Dies geschieht nicht über den Stack (wie bei allen Funktionen des TOS), sondern mit Hilfe von sechs Feldern, die vor jedem Aufruf entsprechend zu belegen sind. Die Adressen der Felder werden in den sogenannten AES-Parameterblock eingetragen. In Turbo C gibt es keinen AES PB, vielmehr wird für VDI und AES zusammen intern der sogenannte GEMPARMBLK verwendet (Bild 5).

Alle Felder müssen vom aufrufenden Programm zur Verfügung gestellt werden. Programmiert man in einer Hochsprache, bemerkt man davon allerdings meistens nichts, da der Compiler das erledigt. Das global[]-Feld wird vom AES [bei Aufruf der Funktion appl_init()] belegt. Die Felder Control[], int_in[] und adr_in[] dienen zur Übergabe von Werten an das AES, während Rückgabewerte von diesem in int_out[] bzw. adr_out[] geschrieben werden. Dabei sind die int...-Felder für Integerwerte und die adr...-Felder entsprechend für Adressen vorgesehen. Das Feld Control[] enthält unter anderem den Opcode für die gewünschte AES-Funktion.

Nachdem ein Programm die Felder belegt und deren Adressen in den AES-Parameterblock eingetragen hat, wird dessen Adresse in das Register D1 geladen. In D0 muß ein Opcode eingetragen werden, und zwar 115 beim Aufruf von VDI-Funktionen und 200 für das AES. Jetzt erst erfolgt der eigentliche Aufruf des AES, und zwar (wie bei allen Betriebssystemfunktionen) über einen sogenannten „Trap“ (engl. „Falle“), hier Trap 2.

Fallen im ST

Traps sind waschechte Maschinenbefehle. Wenn die CPU in eine solche Falle rennt, schaltet sie zunächst in den Supervisormodus und rettet PC und Statusregister auf den Stack. Dann wird der PC mit einer neuen Adresse geladen. Diese neue Adresse ist in einem Block von Systemvariablen zu finden, der sich ganz am Anfang des RAM befindet. Normalerweise zeigt die betreffende Variable auf den Trap Dispatcher im ROM, also auf den Teil des Betriebssystems, der die GEM-Systemroutinen verwaltet. Da aber die Adresse selbst im RAM liegt, kann man sie ändern (den Exception-Vektor „verbiegen“) und auf eine eigene Routine zeigen lassen.

Genau dies macht Jump in der Funktion call_formular(). Dazu bedient es sich der Routine Setexc() (BIOS 5). Ihr werden die Nummer (nicht die Adresse!) des zu ändernden Exception-Vektors und der neue Vektor übergeben, der dort eingetragen werden soll. Man erhält den alten Vektor zurück, der bei Jump in der globalen Variablen trapZwoAdr gesichert wird. (Alle Systemvariablen und die Nummern der Exception-Vektoren sind übrigens z.B. in [1] zu finden)

In call_formular() ändert Jump also zunächst den Exception-Vektor für den Trap 2, der für AES- und VDI-Aufrufe zuständig ist. Dann wird ein Tastendruck Control-L simuliert. Der Editor schreibt daraufhin zunächst die aktuelle Zeilennummer in die Find Line-Dialogbox und versucht dann, sie auf den Bildschirm zu bringen, nach dem oben beschriebenen Muster. Allerdings wird jetzt zunächst nicht der Trap Dispatcher, sondern die kleine Maschinenroutine angesprungen, auf die Jump den Vektor verbogen hat. Diese kleine, private Routine [kuckma-rein()] sieht sich zunächst den Inhalt des Registers D0 an und stellt anhand des Opcodes fest, ob eine AES-Funktion aufgerufen werden soll. Falls ja, wird die Adresse des AESPB von D1 nach A0 geladen. Da das erste Element der Struktur die Adresse des Control[]-Feldes ist, gelangt mit

move.l (a0),a1

dessen Adresse nach A1. In Control [0] muß der Opcode der aufzurufenden Funktion eingetragen sein. Jump interessieren nur zwei Funktionen: objc_draw()(Opcode 42) und form_do() (Opcode 50).

Beim Aufruf dieser Funktionen muß sich in adr_in[0] ein Zeiger auf die Adresse des betreffenden Baums befinden. Da A0 die Adresse des AESPB enthält und adr_in[] der vierte Eintrag in der Struktur ist, kopiert

move.l 16(a0),TCobjAdr

den Zeiger in die globale Variable TCobjAdr. Danach wird das Flag objcDraw gesetzt. Falls die Routine form_do() aufgerufen werden sollte, wird nur das Flag formDo gesetzt. Wozu die beiden Flags gebraucht werden, erkläre ich gleich. Nachdem kuckma_rein() seine Arbeit getan hat, verzweigt es zur Originaladresse des Trap Dispatchers, die bekanntlich in TrapZwoAdr zu erfahren ist. Dort wird dann fortgefahren, als sei nichts passiert.

Synchronisierung

Warum wird aber in call_formular() nach dem Tastendruck in einer Schleife auf die Reaktion des Editors gewartet? Genügt es nicht, einfach einmal evnt_timer() aufzurufen? Das Problem der Synchronisierung der beiden Programme habe ich bei der Funktion tastendruck() schon kurz erwähnt. Der Editor braucht unterschiedlich lange, um die Dialogbox auf den Bildschirm zu bringen. Bei sehr langen Quelltexten kann es schon eine ganze Weile dauern, bevor sie endlich erscheint. Natürlich wäre es möglich, einfach sehr lange zu warten, nachdem der Tastendruck simuliert wurde. Aber wie lange wäre das? Legte man eine bestimmte Zeit fest, würde in den meisten Fällen vermutlich zu lange gewartet, aber in einigen Fällen zu kurz. Die Lösung mit dem Flag objcDraw synchronisiert Jump und den Editor optimal, solange man mit einem guten Wert für Pausen sicherstellt, daß nicht zu viele evnt_timer()-Aufrufe stattfinden, denn die kosten ja auch Zeit. Außerdem: was würde passieren, wenn man Jump einschaltete, ohne sich tatsächlich in der Entwicklungsumgebung zu befinden? Die Adresse TCobjAdr wäre ungültig, der Versuch, über sie die Adresse der Eingabezeile zu erfahren, würde mit einiger Sicherheit zum Absturz des Systems führen. Das hier angewandte Verfahren stellt einigermaßen sicher, daß so etwas nicht passieren kann. Denn nur, wenn eine Dialogbox gezeichnet wurde, wird die Funktion bis zum Ende fortgesetzt. Wird aber MAX_GEDULD überschritten, gibt es Proteste.

Aus ähnlichen Gründen wird in call_formular() ein zweites Mal in einer Schleife gewartet. Es dauert nämlich verschieden lange, bis die Dialogbox gezeichnet ist, abhängig davon, ob ein Blitter vorhanden ist oder nicht. Sobald aber durch kuckma_rein() das Flag formDo gesetzt ist [TC hat also form_do() aufgerufen], kann man sicher sein, daß die Box tatsächlich fertig gezeichnet wurde. Andernfalls würde Return ignoriert werden, und Jump käme ganz schön ins Schwitzen.

Eine Zeile kopieren

Die Kopierfunktion von Jump [zeile_merken()] macht sich die Tatsache zunutze, daß sich eine Zeile, die mit Control-Y gelöscht wurde, danach im Zwischenspeicher des Editors befindet. Also muß man die gewünschte Zeile nur löschen, sie mit Insert an derselben Stelle wieder einfügen und schon hat man erreicht, was man wollte. Hier allerdings scheint es wieder ein Synchronisationsproblem zu geben. Ich muß gestehen, daß mir dazu bisher noch keine vernünftige Lösung eingefallen ist. Zwar ruft TC nach dem Befehl Control-Y reichlich VDI-Funktionen auf. Man sollte also meinen, eine Synchronisation, wie sie bei der Find line-Dialogbox so tadellos funktioniert, wäre auch hier brauchbar. Tatsächlich aber klappt der Trick mit einer kuckma_rein-Routine aus unerfindlichen Gründen hier nicht. Also bleibt nur, statt der kleinen Pause eine längere Pause einzulegen. Leider funktioniert dies nicht immer; in seltenen Fällen gelangt die betreffende Zeile nicht in den Zwischenspeicher. Es ist mir allerdings noch nie passiert, daß zwar Control-Y, aber nicht Insert funktioniert hätte, so daß also nicht zu befürchten ist, daß plötzlich eine Zeile im Quelltext verschwunden wäre. Man hat lediglich eine Zeile zuviel. Sollte das passieren, genügt es, einfach die Undo-Taste zu benutzen. TC springt dann wieder an den Ausgangspunkt zurück und löscht die überflüssige Zeile. Wem diese (wie ich hoffe einzige!) Schwachstelle des Programms nicht behagt, der kann die Kopierfunktion auch einfach weglassen und die Zeilen „von Hand“ kopieren. Ich persönlich benutze die Kopierfunktion ausgiebig. Wenn es einmal nicht klappen sollte, fasse ich mich in Geduld und versuche es einfach noch einmal.

Fänger weg von Fremdprogrammen

Schließlich noch ein paar Worte zu der Funktion autoswitch(). Da man direkt aus der Entwicklungsumgebung heraus fremde Programme starten bzw. das gerade in Arbeit befindliche testen kann, ist es notwendig, dafür zu sorgen, daß sich Jump in deren Arbeit nicht einmischt. Also muß das Accessory immer dann in den Ruhestand gehen, wenn ein weiteres Programm gestartet wird. Außerdem darf natürlich auch nach dem Verlassen der Entwicklungsumgebung nicht mehr auf Funktionstastendrücke reagiert werden.

Glücklicherweise verschickt das AES beim Start und bei Beendigung eines Programms an jede Applikation eine Meldung (AC_CLOSE). Jump muß also nur auf diese Meldung reagieren und seinen Zustand (accEin) entsprechend ändern. Wenn man allerdings ein Programm beendet, das per „Execute“ gestartet wurde, gibt es nicht ein, sondern zwei AC_CLOSE. Außerdem könnte ein leichtsinniger Benutzer auf die Idee kommen, Jump in einem per „Execute“ gestarteten Fremdprogramm zu starten, was mit Sicherheit viel Verwirrung (oder vielleicht auch Bombenstimmung) an-richten würde. Dies ist trotz der Sicherheitsabfrage vor dem Einschalten von Jump möglich, da appl_find() bei Programmen, die von anderen Programmen aus gestartet wurden, den Namen des „Eltern“-Programms und nicht den des gerade aktuellen findet. [Ähnliches gilt auch für die Beendigung von TC, solange noch kein anderes Programm gestartet wurde: im Desktop findet appl_find() TC immer noch - fehlerhafterweise]

Um also zu gewährleisten, daß Jump wirklich nur da wirkt, wo es auch nützlich ist, wartet es nach dem ersten AC_CLOSE (falls es eingeschaltet war) in autoswitch() auf eine weitere Nachricht. Ist diese wiederum AC_CLOSE, wird per evnt_multi() noch ein bißchen gewartet (Verlassen eines Programms, das mit „Execute“ gestartet wurde), und erst dann wird der normale Geschäftsbetrieb wieder aufgenommen. Wie man sieht, ist es möglich. Jump auch in fremden Programmen einzuschalten, indem man Control-Shift-Alternate gedrückt hält, während man es in der Menüleiste anklickt.

Diesen NOTSTART hatte ich ursprünglich nur zum Testen vorgesehen, lasse ihn jetzt aber im Programm, man weiß ja nie. Wer Jump allerdings NOT-STARTet, sollte vorsichtig damit umgehen - ich will hinterher keine Klagen hören!

Jump++

Es ist möglich, Jump an einigen Stellen zu erweitern. So könnte man z.B. verhindern, daß die Dialogbox überhaupt gezeichnet wird, denn die Grafik ist mit Sicherheit der langsamste Teil der Funktion Find Line. Dazu müßte man im Assembler-Teil bei Aufruf von objc_draw() nach der Übernahme der Adresse und dem Setzen des Flags zunächst int_out[0] mit einem Wert ungleich Null belegen und dann die Exception mit „RTE“ beenden. [In der Entwicklungsumgebung für den Assembler den Schalter „Privileged Instructions“ (-S) setzen!] Dann sollte man allerdings auch form_do((-Aufrufe abfangen. Dazu wird der Index des OK-Buttons nach int_out[0] geschrieben, die alte Zeilennummer in einen String, eventuell eine neue Zeilennummer in die TEDINFO-Struktur kopiert und wieder „RTE“.

Außerdem wäre es sicher nützlich, nicht einen, sondern mehrere Sets von Marken mit Jump zu verwalten (wie wäre es mit sechs; für jedes Fenster, das man in der Entwicklungsumgebung öffnen kann, eins). Auf diese Weise könnte man bei Programmprojekten, die aus mehreren Modulen bestehen, für jeden Quelltext (jedes Fenster) ein eigenes Set von Marken anlegen. das gewechselt wird, wenn man das Fenster wechselt.

Schließlich könnte man noch Funktionen zum Laden und Sichern der Marken implementieren. damit man sie nicht bei jeder Arbeitssitzung wieder neu definieren muß.

Ich habe ein Jump2, das diese Funktionen beherrscht, bereits geschrieben. Allerdings ist das Listing fast fünfmal solang wie dieses hier. Die Tastenbelegung ist wesentlich unübersichtlicher geworden, denn die Befehle („Set laden“, „Set sichern“, „Set wechseln“ usw.) müssen natürlich auch über Tasten eingegeben werden können. Außerdem gab es Probleme mit dem Redraw, wenn die Fenster nicht Bildschirmgröße hatten, die zu lösen wieder eine Menge an Code erfordert hat.

Insgesamt, denke ich, ist diese einfache Version von Jump schon eine große Erleichterung im Umgang mit TCs Editor. Wer will, kann das Programm ja nach Belieben erweitern.

Die Projektdatei

Die für Jump benötigte Projektdatei finden Sie in Listing 3. Wenn Sie diese Datei benutzen, müssen Sie natürlich den C-Quelltext TCJUMP.C und das Assemblerlisting TCKUCK.S nennen. Wenn Sie dann das Programm mit Make TCJ.PRJ compilieren und linken, wird es gleich als TCJUMP.ACC (also mit dem richtigen Extender) auf Diskette abgelegt.

Ich habe Jump mit den Versionen 1.1 und 2.0 von TC getestet, und zwar auf einem alten 520+ mit TOS 1.0 (6.2.86). Seit Monaten läuft es (bis auf die Macke beim Kopieren von Zeilen) einwandfrei.

Eine Stack-Größe von 100 Bytes reicht für Jump aus. Falls Sie in Compiler und Linker alle Schalter außer „-G“ im Compiler ungesetzt lassen. sollte das Accessory eine Größe von 4157 Bytes besitzen. Viel Spaß damit!

Literatur:

[1]: Jankowski, Reschke. Rabich: „ATARI ST Profibuch“; Sybex Vertag: Düsseldorf 1989.
[2]: Stefan Höhn: „Resource Formate“: ST-Computer 7/8 1990. Seite 97ff

/*
Jump! Marken im TURBO C Editor.
Geschrieben von Stefan Dreckmann mit TURBO C V2.0 
und (ein bischen) MAS-68K V1.5 
*/

/*----Includes-------------*/
#include<aes.h>
#include<tos.h>
#include<string.h>
#include<stdlib.h>

/*----Defines--------------*/
#define ACC_NAME "  Jump! "
#define AUS 0 
#define EIN !AUS 
#define TASTATUR 1
#define MY_EVENT MU_MESAG | MU_TIMER 
#define EVNT_TIME 200 
#define TC_NAME "TC      " 
/*
TC_NAME (für appl_find()) MUSS! 8 Zeichen 
haben! (Rest: Leerzeichen)
*/

#define SCAN_F1 0x3B0000L 
#define SCAN_F10 0x440000L 
#define SCAN_SHIFT_F1 0x540000L 
#define SCAN_SHIFT_F10 0x5D0000L 
#define CONTROL 4 
#define SHIFT 1
#define NOTSTART 14
#define CONTROL_L 0x260012L 
#define RETURN 0x1C000DL 
#define ESCAPE 0x01001BL
#define SHIFT_PFEIL_LINKS 0x4B0034L 
#define CONTROL_Y 0x2C0019L 
#define INSERT 0x520000L

#define PAUSE 300 
#define PAEUSCHEN 50

#define MAX_GEDULD 10
#define MAXMARKE 10
#define RUECKSPRUNG MAXMARKE-1
#define BELEGT 1

#define TRAP_ZWO 34

/*-----globale Variablen--------*/
int accStatus=AUS;      /* Staus des Accessories */

IOREC* ioPtr;           /* Zeiger auf IOREC_Struktur */
long* bufferAdr;        /* Anfang des Tastaturpuffers */

void (*trapZwoAdr)();   /* Originaladr.Trap #2 */
OBJECT** TCobjAdr;      /* Adresse 'Find Line' Box */ 
char* inputAdr;         /* Adresse Inputzeile */
int formDo;             /* Flag für form_do() */
int objcDraw;           /* Flag für object_draw() */

typedef struct 
    {
    char string[10];    /* Platz für den Ziffern */ 
    int belegt;         /* Belegungsflag */
    }marken;
marken marke(MAXMARKE]; /* ein Feld von Marken */

/*-—Funktionsdeklarationen----------*/
extern void kuckma_rein(void);/* Assemblerteil */

void acc_init(void);
int alert(int button,int meldung);
void init_iorec(void);
void switch_acc(void);
int switch_on(void);
void switch_off(void);
void action_keybd(int lesemarke);
char* get_inputAdr(void);
void tastendruck(long code);
int call_formular(void);
void formular_entfernen(void);å
void gehezu_marke(int nr,int flag);
void marke_merken(int nr);
void zeile_merken(void);
void marke_entfernen(void);
void zeile_anpassen(int nr);
void autoswitch(void);

/*----Hauptprogramm-------------*/

main()
{
    int dummy;
    int event,alteLesemarke,keybdLesemarke; 
    int i;
    int msgBuf[8];          /* der Messagepuffer */

    acc_init(); 
    init_iorec();

    for(i=0;i<MAXMARKE;i++)/* alle Marken... */ 
        marke[i].belegt= !BELEGT;/* ...unbelegt */

    keybdLesemarke = alteLesemarke = ioPtr->ibufhd;

    for(;;) /* Endlosschleife, da Accessory */
    { /* Warte auf Message- oder Zeitereignis: */ 
        event=evnt_multi(MY_EVENT,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,msgBuf,EVNT_TIME,0,
                        &dummy,&dummy,&dummy,&dummy,
                        &dummy,&dummy);

        /* Tastendruck */
        if(accStatus && (keybdLesemarke =
                     ioPtr->ibufhd) != alteLesemarke) 
            action_keybd(alteLesemarke = keybdLesemarke); 
        else if(event & MU_MESAG) /* Nachricht */ 
            if (msgBuf[0] == AC_OPEN) /*ACC angeklickt */ 
                switch_acc(); /* ein- oder ausschalten */
            else if(msgBuf[0] == AC_CLOSE && accStatus) 
                autoswitch();/* auf AC_CLOSE reagieren */
    }
}

/*—-Funktionsdefinitionen-----------*/
/*—-Accessory installieren-----------*/
void acc_init(void)
{
    extern _app; 
    int ap_id;

    if(_app)        /* Wenn Accessory, _app == 0 */
    {
        alert(1,0); /*soll als PRG gestartet werden*/ 
        exit(0);
    }
    /* beim AES anmelden und in Menüleiste eintr. */ 
    ap_id=appl_init(); 
    menu_register(ap_id,ACC_NAME);
}

/* Iorec initialisieren,Pufferanfang ermitteln */ 
void init_iorec(void)
{
    ioPtr=Iorec(TASTATUR);/* Zeiger auf Struktur */ 
    bufferAdr=ioPtr->ibuf;/* Zeiger auf Buffer */
}

/*—Meldungen mittels form_alert()-------*/
int alert (int button,int meldung)
{
    static char* meldungen[]=
    {
        "[3][Dieses Programm nur|als Accessory starten.][Ach so]", 
        "[2][         Jump!    |hilft TC auf die Sprünge][ Ein | Aus (Löschen]", 
        "[3][Dieses Accessory nur|mit TURBO C benutzen!][Na gut]", 
        "[3][Fehler beim Einschalten|von Jump!][Mist!]", 
        "[1][Diese Taste ist nicht belegt!][Ach so!]", 
        "[3][Dies ist die Rücksprungtaste!][ Egal! |Abbruch]",
    };

    return(form_alert(button,meldungen[meldung]));
}

/*—Acc. ein- oder ausschalten---------*/
void switch_acc(void)
{
    int i,but,antwort;

    but = (accStatus == AUS) ? 1 : 2; 
    if( (antwort=alert(but,1)) == 1)    /* ein? */
        if (appl_find(TC_NAME) >= 0)    /* Hauptprogramm ist TC */ 
            if (switch_on() == 0)       /* Einschalten erfolgreich? */ 
                accStatus=EIN;          /* Flag setzen */
            else
                alert(1,3);
        else
        {
            alert(1,2); 
            accStatus=AUS;
        }
    else if(antwort==2)     /* ausschalten? */
        accStatus=AUS;      /* Flag löschen */
    else                    /* löschen */
        for (i=0;i<MAXMARKE;i++)
            marke[i].belegt = !BELEGT;
}

/*—ACC einschalten--------------*/
int switch_on(void)
{
    if(accStatus == AUS)    /* ist es jetzt aus? */
    {
        if((inputAdr=get_inputAdr()) != 0)
            return 0; 
        else
            return 1;
    }
    return 0;
}

/*---auf Tastendruck reagieren--------*/
void action_keybd(int lesemarke)
{
    long code; 
    int index; 
    int flag = AUS;

    code = *(bufferAdr + lesemarke/4); /* Code lesen */
    if(code >= SCAN_F1 && code <= SCAN_F10)
    {               /* Funktionstaste ohne SHIFT ? */
        accStatus=AUS;          /* Acc zeitweise aus */
        index=(int)*(code >>> 16) - 0x3B;/* Scancode in Feldindex umrechnen */ 
        if( marke[index].belegt )/* Taste belegt? */
        {
            if(Kbshift(-1) == CONTROL) 
                flag = EIN; 
            gehezu_marke(index,flag);/* Marke anspringen */
        }
        else
            alert(1,4);
        accStatus=EIN;              /* Acc wieder ein */
    }
    else if(code >= SCAN_SHIFT_F1 && code <= SCAN_SHIFT__F10) /* mit SHIFT? */
    {
        accStatus=AUS;
        index=(int)(code >> 16) - 0x54; /* Scancode in Feldindex umrechnen */ 
        marke_merken(index); 
        accStatus=EIN;
    }
}

/★—Adresse der Eingabezeile ermitteln-----*/
char* get_inputAdr(void)
{
    OBJECT* adr;

    TCobjAdr=0;         /* Adresse auf 0 */
    if(call_formular()) /* Dialogbox aufrufen */
    {
        if(TCobjAdr == 0 || *TCobjAdr == 0)/* Fehler (z.B im Desktop) */
        {
            formular_entfernen(); 
            return 0;
        }
        adr= *TCobjAdr;     /* Adresse übernehmen */

        /* das gesuchte Objekt muss "EDITABLE" sein: */ 
        while(adr->ob_flags != EDITABLE)
        {
            if(adr->ob_next >0) /* entweder nächstes Elternobjekt... */ 
                adr = *TCobjAdr + adr->ob_next; 
            else if(adr->ob_heaö >0) /* ...oder nächstes Kindobjekt */ 
                adr = *TCobjAdr + adr->ob_head; 
            else        /* falls keines mehr übrig... */
            {
                formular_entfernen();
                return 0;           /* Notfall!!! */
            }
        }
        formular_entfernen(); 
        marke_entfernen();
        return(adr->ob_spec.tedinfo->te_ptext); /*Adr. des Eingabestrings zurückliefern */
    }
    return 0;
}

/*---'Find Line' Formular holen------*/
int call_formular(void)
{
    int time_out=0;                 /* Rundenzähler */

    formDo=0;                       /* form_do - Flag löschen */
    objcDraw=0;                     /* object_draw - Flag löschen */

    trapZwoAdr=Setexc(TRAP_ZWO,kuckma_rein); /* Ass. Routine einhängen */ 
    Kbshift(CONTROL);   /* CONTROL */ 
    tastendruck(CONTROL_L); /* Tastendruck CONTROL L simulieren */ 
    while( !objcDraw )  /* warten bis TC reagiert */
    {
        evnt_timer(PAEUSCHEN,0);
        if(time_out++ >= MAX_GEDULD)/*klappt nicht*/
        {
            Setexc(TRAP_ZWO,trapZwoAdr);/* Original routine einhängen */ 
            Kbshift(0);             /* normal */
            return 0;
        }
    }
    Kbshift (0);                    /* Tastatur normal */
    while( !formDo )/* warten bis die Box fertig */ 
        evnt_timer(PAEUSCHEN,0);
    Setexc(TRAP_ZWO,trapZwoAdr); /* Original routine einhängen */

    return 1;
}

/*—--Formular entfernen-------------*/
void formular_entfernen(void)
{
    tastendruck(RETURN); /* RETURN simulieren */
}

/*—Simulation eines Tastendrucks-------*/
void tastendruck(long code)
{
    *(bufferAdr) = code; /**Code an den Anfang des Tastaturpuffers schreiben */ 
    ioPtr->ibuftl=0; /* Schreib- und */
    ioPtr->ibufhd=ioPtr->ibufsiz; /* Lesemarke versetzen */ 
    while( ioPtr->ibufhd == ioPtr->ibufsiz )
        evnt_timer(PAEUSCHEN,0);/* warten bis Zeichen ausgelesen */
}

/*—--Marke anspringen---------------*/
void gehezu_marke(int nr,int flag)
{
    char* position; 
    long code; 
    char rueck[10];

    if(flag)                /* falls gewünscht... */
        zeile_merken();     /* akt. Zeile merken */
    if(call_formular())     /* Formular aufrufen */
    {
        if(nr != RUECKSPRUNG)/* es ist nicht die Rücksprungtaste */
        {   /* Zeile für Rücksprung merken: */
            strcpy(marke[RUECKSPRUNG].string,inputAdr); 
            marke[RUECKSPRUNG].belegt = BELEGT;/* Belegungsflag Rücksprung */
        }
        else /* Falls es die Rücksprungraste war */ 
            strcpy(rueck,inputAdr); /* Zeilennummer Zwischenspeichern */ 
        position=marke[nr].string;/* Zeiger auf den Zeilenstring */ 
        tastendruck(ESCAPE);    /* ESCAPE-Taste simulieren */ 
        while ((code=(long)*position++) != 0) 
            tastendruck(code);  /* Tastendruck auf Zifferntaste simulieren */ 
        formular_entfernen();   /* bewirkt Sprung */
        marke_entfernen();
        if(flag)                /* falls Kopierfunktion */
            zeile_anpassen(nr); /* fertig zum Einfügen */ 
        if(nr == RUECKSPRUNG)   /* Rücksprungtaste */ 
            strcpy(marke[RUECKSPRUNG].string,rueck);
                                /* gespeicherte Nr. holen */
    }
}

/*—--Marke fest legen-------------*/
void marke_merken(int nr)
{
    if(nr == RUECKSPRUNG)   /* Rücksprungtaste sollte nicht belegt werden */ 
        if(alert(2,5) == 2) /* Warnung akzeptiert? */ 
            return;         /* keine Aktion */

    if(call_formular())     /* Formular aufrufen */
    {
        strcpy(marke[nr].string,inputAdr); /* Zeilen string übernehmen */
        formular_entfernen(); 
        marke_entfernen();
        marke[nr].belegt = BELEGT;/* Belegungsflag */ 
    }
}

/*---Zeile in den Zwischenspeicher------*/
void zeile_merken(void)
{
    Kbshift(CONTROL);       /* CONTROL */ 
    tastendruck(CONTROL_Y); /* Zeile löschen (kommmt in den Buffer) */ 
    evnt_timer(PAUSE,0);    /* Reaktion abwarten */
    tastendruck(INSERT);    /* Zeile wieder einfügen */ 
    evnt_timer(PAUSE,0);    /* wieder warten */
    Kbshift(0);             /* Tastatur normal */
}

/*----Marke entfernen-----------*/
void marke_entfernen(void)
{
    Kbshift(SHIFT);     /* SHIFT */
    tastendruck(SHIFT_PFEIL_LINKS); /* Cursor an den Anfang der Zeile...*/
    Kbshift(0);         /* Tastatur normal */
}

/*------Zeilennummer erhöhen-------*/
void zeile_anpassen(int nr)
{
    int akt_zeile; 
    int zeile,i;

    akt_zeile=atoi(marke[nr].string); 
    for(i=0;i < MAXMARKE;i++)
        if((zeile=atoi(marke[i].string))>=akt_zeile) 
            itoa(++zeile,marke[i].string,10);
}

/*—--automatisches Ein-/Ausschalten--------*/
void autoswitch(void)
{
    int msg[8]; 
    int dummy;

    for (;;)
    {
        evnt_mesag(msg);    /* auf Nachricht warten */
        if(msg[0] == AC_CLOSE)/* Neustart des ACCs */ 
        {
            evnt_multi(MY_EVENT,0,0,0,0,0,0,0,0,0,0,0, 
                       0,0,msg,PAUSE,0,&dummy,&dummy,
                       &dummy,&dummy,&dummy,&dummy); 
            if(appl_find(TC_NAME) < 0)/* neues Hauptprogramm ist nicht TC */ 
                accStatus = AUS;      /* ACC ausschalten */ 
            break;                    /* Schleife abbrechen */
        }
        else if(msg[0] == AC_OPEN) /* ACC soll gestartet werden */ 
            if(Kbshift(-1) == NOTSTART) /* aber nur per Notstart */
            {
                accStatus = AUS;        /* Flag auf Aus */
                switch_acc();           /* Startroutine */
                break;                  /* Schleife verlassen */
            }
    }
}

Listing 1: Das Hauptprogramm...

;die Assemblerroutine kuckma_rein() in Trap #2

GLOBL kuckma_rein   ;Einsprungadresse der Funktion 
GLOBL trapZwoAdr    ;alte Einsprungadresse Trap #2
GLOBL formDo        ;Flag form_do
GLOBL objcDraw      ;Flag objc__draw
GLOBL TCobjAdr      ;für Zeiger auf Adresse des
                    ;Objektbaums

vdi EQU 115         ;Opcode VDI
obdra EQU 42        ;Opcode objc_draw()
fodo EQU 50         ;Opcode form_do()

kuckma_rein:
    cmpi.w #vdi,d0      ;falls VDI Routine...
    beq ausgang         ;...nichts machen

    move.l d1,a0        ;Adresse AESPB holen
    move.l (a0),a1      ;Adresse CONTROL-Feld
                        ;holen

    cmpi.w #obdra,(a1)  ;Opcode für objc_draw()?
    bne next            ;nein: keine Aktion
    addq.w #1,objcDraw  ;Flag setzen
    move.l 16(a0),TCobjAdr ;Zeiger auf Adresse des
                        ;Objekbaums übergeben

next:
    cmpi.w #fodo,(a1)   ;Opcode für form_do()?
    bne ausgang         ;nein: keine Aktion
    addq.w #1,formDo    ;Flag setzen

ausgang:
    move.l trapZwoAdr,a0 ;alte Trap-Adresse
                        ;holen
    jmp (a0)            ;dort fortfahren

Listing 2: ...und der Assembler-Teil


Stefan Dreckmann
Links

Copyright-Bestimmungen: siehe Über diese Seite