GEM-Kurs Teil 1

Dieser Kurs soll eine Lücke in der bisherigen Flut der Veröffentlichungen zum ATARI schließen. — In praktisch jeder Zeitschrift, die Artikel zum Thema ATARI bringt, findet man irgendwelche Spielzeugprogramme, die z. B. ein Fenster öffnen, dann freundlich Hallo zum Benutzer sagen und sich wieder verabschieden. Meist werden wohl Platzgründe eine Rolle spielen, oder die Programme sind aus pädagogischen Erwägungen kurz und knapp, wie in meinem C-Kurs. Aber inzwischen können Sie ja alle perfekt in C programmieren und lassen sich von einem 1000-Zeilenprogramm nicht mehr sonderlich beeindrucken. Deshalb werde ich in diesem Kurs nur vollständige und (hoffentlich) brauchbare Programme vorstellen. Besonders werde ich versuchen, Ihnen den Weg von der Idee bis zum fertigen Programm zu zeigen und die Entwurfsentscheidungen, die auf diesem Weg zu fällen sind, begründen.

Da die Programme schnell ziemlich riesig werden - das Programm in diesem Teil hat knapp 1500 Zeilen -, ist es natürlich nicht möglich, das gesamte Listing abzudrucken, das ist aber auch nicht notwendig. Im Heft werden immer nur die Ausschnitte des Listings abgedruckt, die wichtige Prinzipien oder Methoden zeigen, oder die man leicht in eigene Programme einbauen kann. Wer den gesamten Quelltext + lauffähigem Programm haben will, kann sich an den Verlag oder an die Redaktion wenden.

1. Die Idee

Das Programm in dieser Ausgabe habe ich BROWSER genannt. Browse bedeutet soviel wie blättern, herumblättern. Die Germanisten unter den Lesern werden jetzt sicher fragen: muß es denn immer Englisch sein? Darauf kann ich nur erwidern: mir ist kein passender deutscher Name eingefallen. Oder 'was würden Sie zu „Blätterer" sagen? Der Name Verrät auf jeden Fall schon, was das Programm können soll:

  1. Man soll sich Dateien anschauen können, und zwar nicht sequentiell, sondern beliebig an jeder Stelle. Man soll also vor- und zurückblättern können, ganz bestimmte Zeilen anzeigen, nach irgendwelchen Wörtern suchen. Es soll sich sowohl mit der Maus, als auch über die Tastatur bedienen lassen.

  2. Es soll alles so schnell und einfach wie nur irgend möglich gehen.

  3. Es soll immer funktionieren, sowohl vom Desktop aus, als auch von jeder anderen (GEM) Anwendung aus. Außerdem soll man nicht gezwungen sein, das Programm zu verlassen, wenn man etwas anderes machen will.

  4. Es soll sich jede Datei laden lassen, ob sie nun druckbare oder nicht druckbare Zeichen enthält, ob sie nur einige Zeilen lang ist oder vielleicht 300 KB auf die Waage bringt.

Durch die Sache mit der Maus in Punkt eins ist man auf jeden Fall schon einmal auf eine GEM-Anwen-dung festgelegt. Punkt drei zwingt dazu, ein Desktop-Accessory zu basteln. Das heißt, das Programm steht immer links oben in der Menüleiste und kann jederzeit durch einen Tastendruck initialisiert werden. Dadurch ergeben sich gleich einige andere Dinge. Acces-sories belegen den Speicher die ganze Zeit und sollten deshalb möglichst sparsam programmiert werden. Daraus ergibt sich wiederum die Forderung, möglichst keine Funktionen aus der Standardbibliothek zu verwenden, da diese meist einen ganzen Rattenschwanz anderer Funktionen hinter sich herziehen. Punkt zwei zwingt ebenfalls dazu, auf die Verwendung der Standardfunktionen zu verzichten, da diese viel zu langsam sind.

Wenn man so weit gekommen ist, wird es Zeit, sich einige konkrete Gedanken über das tatsächliche Erscheinungsbild des Programms zu machen. Man kommt dann vielleicht zu folgender Liste:

  1. Das Ganze läuft in einem Fenster ab. Es muß ein Fenster mit allen „Schikanen" sein, die GEM zu bieten hat, als da wären: horizontale und vertikale Scrollbalken, Titel und Infozeile, das Fenster muß sich frei verschieben und (in gewissen Grenzen) vergrößern und verkleinern lassen.

  2. Das Laden der Datei und die Reservierung des dazu notwendigen Speicherplatz' muß auf unterster, sprich T^J' Ebene programmiert werden, um die Ansprüche an Geschwindigkeit und beliebige Größe der Datei zu befriedigen.

  3. Eine Einteilung in vier relativ unabhängige Module bietet sich geradezu an: ein Initialisierungsmodul, in dem die gesamte GEM Initialisierung versteckt wird, das Hauptmodul, das die Fensterverwaltung und die Interaktion mit dem Benutzer abwickelt* ein Modul, das die eigentliche Arbeit des Blätterns und An-zeigens im Arbeitsbereich des Fensters übernimmt, und ein letztes Modul, in dem Funktionen zum Laden einer Datei stehen.

Die Aufteilung in Module können Sie in Bild l sehen. Unter jedem Modul stehen bereits die Namen der Funktionen, die es beherbergt.

2. Das Programm

Jedes der angeführten Module ist in der Realität eine C Quelldatei, die unabhängig von den anderen übersetzt werden kann. Am Schluß werden die entstandenen Objektmodule mit dem Linker zu. einem ablauffähigen Programm gebunden. Ich habe das Programm mit dem MEGAMAX C Compiler entwickelt, mit dem man phantastische Turnaroundzeiten (das ist die Zeit von einem Ausprobieren zum nächsten) erreicht. Wer den MEGAMAX hat, kann das Programm direkt mit dem make Kommando der Shell übersetzen und binden. Die notwendigen makefiles finden Sie in Bild 2. Besitzer des Entwicklungspakets von DR müssen etwas mehr Arbeit (und Geduld) in die Sache stecken. Besondere Schwierigkeiten entstehen aber nicht. Sie sollten bloß darauf achten, das File accstart.o als erstes in der Linkeranweisung anzugeben. Für Besitzer des LATTICE OCompilers sehe ich ziemlich schwarz. Deines Wissens werden Accessories von diesem Compiler im Moment überhaupt nicht unterstützt. Außerdem bereitet es auch sonst erhebliche Probleme, GEM Programme unter diesem System zum Laufen zu bringen.

Eine rein handwerkliche Bemerkung: wenn Sie Accessories programmieren, empfiehlt es sich unbedingt, das Programm so anzulegen, daß es auch als ganz normale GEM-Applikation übersetzt und gelinkt werden kann. Wie man das macht, können Sie auch dem BROWSER Programm entnehmen. Wenn Sie das Programm als normale GEM Applikation übersetzen wollei', müssen Sie nur die Zeile 39 des Moduls BROWSE.C (#define ACC...) löschen. Der C Preprozessor erledigt dann alles Weitere. Der Grund dafür liegt natürlich in der schlechten Test-barkeit von Accessories - sie müssen ja jedesmal das fertige Programm erst auf die Systemdiskette kopieren und dann den Atari neu booten.

2.1 Die main() Funktion

Die mainQ Funktion Listing l ist der ideale Ausgangspunkt für alle weiteren Betrachtungen. Ich werde sie ziemlich ausführlich erklären, da sie in dieser oder einer leicht geänderten Form in jedem Programm auftaucht. An ihr sieht man gleich, wo die essentiellen Unterschiede zwischen einer normalen Applikation und einem Accessory liegen. Allen GEM-Programmen gemeinsam ist der Aufruf der Funktion appl init(). Diese macht die Anwendung beim System bekannt und liefert mit gl__apid die Nummer zurück, unter der GEM die Anwendung kennt. Mit der Funktion graf__handleQ erhält man außerdem die systemweite Kennzeichnung des physikalischen Ausgabegeräts (hier Bildschirm), die man benötigt, um für die Anwendung ein sogenanntes virtuelles Ausgabegerät öffnen zu können. Virtuell heißt in diesem Fall soviel wie privat. Man erhält durch den open virtual Workstation Aufruf einen privaten Satz der GEM-Parameter. Ohne dieses Konzept wäre das Chaos bald komplett. Stellen Sie sich vor, irgendein Programm schaltete auf dicke Liniendarstellung um; ohne die privaten Parameterblöcke würden dann alle anderen Programme ebenfalls dicke Linien malen.

In den nächsten Zeilen können Sie sehen, wie man mit dem Preprozessor bedingt übersetzen kann. Nur wenn das Symbol ACC definiert ist, wird die Funktion menu _ register() aufgerufen. Diese Funktion sorgt dafür, daß der String, der ihr als zweiter Parameter mitgegeben wird, -in die Menüleiste zu den anderen Accessories eingetragen wird. Es sind nur maximal 6 Accessories gleichzeitig möglich; wenn kein Platz mehr zur Verfügung steht, liefert die Funktion — l zurück. Klappt aber alles, erhält man eine positive Zahl zurück, die das Accessory eindeutig identifiziert. Jetzt muß man nur noch wissen, wie groß das größtmögliche Fenster ist. Dies (und noch vieles mehr) erledigt die Funktion wind_get(). Ihr erster Parameter ist eine Fensterkennzeichnung (handle). Die Null ist die Handle des großen Desktop Fensters - jenes, wo die Diskettensymbole und der Papierkorb ihr Unwesen treiben. Und die Ausmaße dieses Fensters sind eben genau die des größtmöglichen, das GEM zu bieten hat. Bei einem Accessory ist der Rest von main besonders einfach. Es folgt nur noch der Aufruf der Funktion multi(), in der alles, was sonst noch passiert, abgewickelt wird. Da ein Accessory „nie" endet, kehrt das Programm nie mehr nach main() zurück. Um es noch einmal ganz deutlich zu machen: beim Booten der Systemdiskette werden die vorhandenen Accessories eingelesen und sofort gestartet. Die main Funktion wird (einmal) durchlaufen und dann in multi auf Aktionen des Benutzers gewartet (meist wird dies das Anklicken des Namens in der Menüleiste sein). Die Funktionen init__browser() und clear__browser() tun genau das, was ihre Namen versprechen. Mit einigem, was in ihnen passiert, werden wir uns weiter unten noch beschäftigen.

2.2 multi() - Das Messagekonzept des ATARI

Die multi() Funktion ist das Kernstück des ganzen Programms. In ihr werden alle Interaktionen des Benutzers mit Maus, Fenstern, Menüleiste und Tastatur gemanagt. Etwas genauer: es werden je nach Ereignis die entsprechenden Funktionen aufgerufen. Aus diesem Grund wird sie hier auch in voller Länge abgedruckt (Listing 2).

Die ganze Funktion ist eine riesige do...while Schleife, im Fall des Accessories sogar eine von der endlosen Sorte. Die erste Funktion, die aufgerufen wird, ist evnt__multi(). Sie darf sich mit stolzgeschwellter Brust als die GEM-Funktion mit den meisten Parametern bezeichnen. Um ihre Funktion zu verstehen, muß man ein paar Dinge über die Struktur von GEM wissen.

GEM ist ein vollkommen ereignisgesteuertes Betriebssystem. Event heißt übrigens nichts anderes als Ereignis. Unter anderen Betriebssystemen ist ein Programm selbst dafür verantwortlich, um z. B. festzustellen, ob eine Taste gedrückt wurde, ob die Maus bewegt wurde oder ob eine ganz bestimmte Zeit vergangen ist. Unter GEM funktioniert das ganz anders. Hier sagt ein Programm z. B.: „Hör mal Betriebssystem! Ich zeige meinem Benutzer gerade einen Text. Ich denke, in 2 Sekunden wird er fertig sein, dann will ich weitermachen. Mach du solange mit dem Prozessor, was du willst. Ich ruhe mich aus." Das Anwenderprogramm sagt das natürlich nicht im Plauderton, sondern macht den Funktionsaufruf evnt__timer (2000,0), der allerdings genau das bewirkt, was ich eben beschrieben habe. Oder das Programm sagt: „Check doch mal ab, ob mein Benutzer irgendwas mit den Fenstern macht. Wenn ja, teile mir das doch bitte mit". Der Aufruf dafür sähe dann in etwa so aus: evnt__mesag (msgbuf). msgbuf wäre dabei ein int Array, in dem bei Rückkehr aus der Funktion steht, was denn so alles passiert ist. Ein letztes Beispiel: „Solange sich die Maus in dem Gebiet des Bildschirms befindet, brauchst Du dich überhaupt nicht mehr bei mir zu melden. Interessiert mich echt nicht die Bohne." Etwas weniger prosaisch hieße das dann: evnt__mouse(einige Parameter...). Dieses Verhalten ist auch der Grund, warum solche Dinge wie Accessories überhaupt funktionieren. Bis jetzt ist das Ganze aber noch etwas unbefriedigend: stellen Sie sich vor, der Benutzer möchte seinem Programm dringend etwas über die Tastatur mitteilen, aber das dumme Ding stellt sich einfach tot. Grund: sein Programm hat seelenruhig einen mouse_evnt()-Aufruf gemacht und denkt gar nicht daran, sich um solche Trivialitäten wie Tastatureingaben zu kümmern. Aber da kriegen wir es sofort am Wickel: jetzt kommt nämlich der gigantische evnt_multi Aufruf. Sie können sich jetzt sicher schon vorstellen, was der tut. evnt__multi() ist einfach eine Zusammenfassung aller möglichen anderen event Aufrufe. Damit könnte der Dialog dann so aussehen: „Äh, Hallo, GEM, ich muß da mal für ein Minüt-chen wohin, pass doch auf, was so alles passiert. Also wenn mein Benutzer die Maus zu den Icons dort rüberschickt, sag mir Bescheid, aber wenn er auf irgendeine Taste klopft, interessiert mich das auch brennend. Und wenn er digkeit halber aufgenommen. AC_CLOSE ist wahrscheinlich dazu gedacht, daß ein Benutzerprogramm in die Lage versetzt wird, mittels der Funktion appl_write() ein laufendes Accessory abzubrechen.

Alle anderen Nachrichten beziehen sich auf den Umgang mit Fenstern; wir kommen gleich noch dazu.

In der zweiten Hälfte von multi() werden Tastaturereignisse ausgewertet.

Evnt_multi() liefert dazu in keycode einen Integerwert, dessen niedrigwertiges Byte den ASCII-Code und das hö-herwertige Byte den Scancode der gedrückten Taste enthält. In shiftstate erhalten Sie den Zustand der Shift-, Alt- und Ctrl-Tasten zurück.

2.3 Das Windowhandling

Eines der schwierigsten Kapitel ist ohne Zweifel die richtige Behandlung der Fenster. Wie immer ist das wichtigste, die grundlegenden Prinzipien verstanden zu haben, dann ist der Rest halb so wild. Ein Fenster unter GEM zerfällt zuerst einmal in zwei wesentliche Teile. Zuerst ist da der Randbereich. Im Minimalfall ist er überhaupt nicht da, und im Maximalfall besteht er aus den schönen Dingen, die Sie vom Desktop her uestimmt schon kennen. Links oben sitzt der Closebox, dann gibt es die beiden Scrollbereiche, den Titelbalken, die Infozeile, die Sizebox und den Verschiebebalken. In meinem Browserprogramm habe ich alle Teile dazugenommen, um Ihnen die Fensterbehandlung so vollständig wie möglich zeigen zu können. Der Randbereich fällt voll in die Zuständigkeit von GEM - im speziellen Fall in die Zuständigkeit eines Sklaven von GEM, des Screenmanagers. Der Screenmana-ger überwacht alle Aktionen des Benutzers im Randbereich des Fensters und schickt dem Anwenderprogramm Nachrichten darüber, was im Einzelnen passiert ist.

Der zweite Teil des Fensters ist der Arbeitsbereich, also der Teil des Fensters, in dem das passieren soll, was der Anlaß dazu war, sich überhaupt der Mühe zu unterziehen, ein Fenster zu öffnen. Mit dem Arbeitsbereich ist das Anwenderprogramm völlig alleingelassen. Die einzige Information, die GEM dazu rausrückt, ist seine Größe. Halt! Ganz so schlimm ist es nicht! GEM paßt immerhin auf, welcher Teil des Arbeitsbereichs überhaupt sichtbar ist. Für diesen Zweck führt es für jedes Fenster intern eine sogenannte Rechteckliste mit - das ist keine rechteckige Liste, sondern eine Liste von Rechtecken -, in der genau der Teil des Arbeitsbereiches steht, der im Moment sichtbar ist. Schauen Sie sich am besten einmal Bild 3 an; es zeigt eine Hard-copy des Bildschirms, als das Browserfenster gerade von zwei Directoryfenstern des Desktops teilweise verdeckt wird. Übrigens hatte ich in diesem Moment gerade TOS.IMG geladen. Wie Sie sehen, verkraftet das der Browser problemlos. In den Arbeitsbereich des Browsers habe ich einmal von Hand die Rechteckliste in dieser Situation eingezeichnet. - Für die Puristen unter Ihnen: ich weiß natürlich nicht, ob das wirklich die Rechteckliste in diesem Moment war, es sind ja noch andere Kombinationen möglich. Aber es geht ja nur ums Prinzip. -Die Ausmaße der drei Rechtecke könnte man nun zu jedem Zeitpunkt vom System bekommen. Das managt alles der wind_get() Aufruf. Und man braucht die Information auch - spätestens dann, wenn der Screenmanager meldet, daß der Benutzer - wahrscheinlich aus bloßem Jux und Tollerei - ein Fenster verschoben habe und man, um sich nicht furchtbar zu blamieren, jetzt doch bitte seinen Arbeitsbereich neu zeichnen möge. In dieser Lage darf man natürlich nicht den ganzen Arbeitsbereich neu zeichnen - eine Katastrophe wäre die Folge -sondern nur die im Moment sichtbaren Teilstücke, und das sind eben genau die in der Rechteckliste.

Die Meldung macht der Screenmanager übrigens mit der Nachricht WM_REDRAW und liefert gleichzeitig die Ausmaße des Bildschirmrechtecks, in dem sich etwas verändert hat. Man muß jetzt nur noch dieses Rechteck mit allen Rechtecken in der Liste seines Fensters zuschneiden und das sich ergebende Schnittrechteck neu zeichnen - ist doch trivial. Oder nicht? Wie man es genau macht, entnehmen Sie bitte der Funktion do_redraw() in Listing 3.

Bemerkenswert in diesem Zusammenhang ist noch die Funktion wind_update(). Sie muß immer gerufen werden, wenn man irgendetwas am Arbeitsbereich macht, und sorgt dafür, daß alle anderen Aktivitäten auf dem Bildschirm für diesen Moment eingefroren werden, so daß einem niemand ins Handwerk pfuschen kann.

Aus dem eben Gesagten ergibt sich eine wichtige Grundregel für das Schreiben aller Programme auf dem ATARI.

Selbst wenn Sie im Moment nicht beabsichtigen, Ihr Programm mit einer GEM Oberfläche zu versehen, tragen Sie von Anfang an Sorge, daß seine Ausgabe nicht an einen bestimmten Bereich des Bilsdchirms oder an eine bestimmte Größe gebunden ist, sondern programmieren Sie immer so, daß die Ausgabe in Abhängigkeit von Variablen erfolgt, die ein beliebiges Rechteck auf dem Bildschirm beschreiben, wie z. B. xw, yw, ww und hw. Um es noch mehr zu verdeutlichen, wenn in Ihrem Programm eine Diagonale durch das gesamte Ausgabegebiet gezeichnet werden soll, dann programmieren Sie nicht: Linie von Punkt 0,0 zum Punkt 639,399 sondern Linie von Punkt xw,yw zum Punkt xw+ww- l,yw + hw-1 und setzen die Variablen bei der Initialisierung auf irgendwelche fixen Werte, z. B. xw=yw = 0 und ww = 640 und hw=400. Dadurch wird es fast zum Kinderspiel, das Programm dann nachher doch unter GEM zu bringen.

Mit der WM_REDRAW Message muß man vorsichtig sein. Sie wird nicht immer erzeugt, wenn man es vielleicht erwarten würde. Als Faustregel gilt: WM_REDRAW wird dann erzeugt, wenn ein Stück des Arbeitsbereichs sichtbar wird, der vorher irgendwie verdeckt war. Wenn man ein Fenster verkleinert, wird man z. B. nicht zum Neuzeichnen aufgefordert, beim Vergrößern aber sehr wohl. Das ist der Grund, warum unter WM_SIZED im Browser explizit festgestellt wird, ob das Fenster verkleinert oder vergrößert wurde.

Nachdem wir das Neuzeichnen des Arbeitsbereiches damit bravourös hinter uns gebracht hätten, bleiben noch die anderen Nachrichten, die das Herumklicken an einem Fenster so zur Folge haben kann. Diese sind zum großen Teil trivial, und Sie sollten alles Notwendige dem Listing der multi() Funktion entnehmen können. Die Scrollbalken sehen z. B. auch schwieriger aus, als sie in Wahrheit sind. Man muß sich nur merken, daß GEM alle Informationen, die man gerne in die Scrollbalken hineinstecken will, in einem Bereich von 0-1000 erwartet. 0 bedeutet z. B. für die Balkenposition: der Balken ist ganz oben und bei 1000 ist er natürlich ganz unten. Worauf man achten muß, ist dies: je nach Anwendung kommen bei der Umrechnung auf den 1000er Bereich Zwischenwerte heraus, die nicht mehr in einem kurzen int-Wert dargestellt werden können — es empfiehlt sich also generell, die Zwischenresultate in long Werte zu packen.

Bevor man mit einem Fenster spielen kann, muß man es natürlich erst einmal auf den Bildschirm bringen. Was dazu nötig ist, habe ich in der Funktion open__windowQ zusammengefaßt (Listing 4).

Zuallererst muß man GEM sagen, daß man ein Fenster will, und wie dieses aussehen soll. Das erledigt die Funktion wind_create(). Sie erhält als Parameter einen im-Wert; der angibt, was für Randbereiche das Fenster haben soll, und vier weitere int-Werte, die die Größe des Fensters bestimmen, wenn die fulled-box angeklickt wird. Hier wird man normalerweise die Werte des größtmöglichen Fensters übergeben. Wenn die Funktion eine positive Zahl liefert, ist dies die Fen-sterkennung; ist die Zahl negativ, ist kein Fenster mehr verfügbar. Hier liegt neben anderen Dingen eine große Schwäche von GEM. Es kann maximal nur acht Fenster verwalten, und das Schwerwiegendste ist, daß das Desktop allein bereits vier davon beansprucht.

Hat man endlich eine Windowhandle erhalten, muß man - falls vorhanden - die Titel- und die Infozeile initialisieren. Hier gilt als wichtige Bemerkung: es müssen unbedingt Strings übergeben werden, die der Maximallänge der Texte entsprechen, die man in die Zeilen schreiben will. Wenn man später versucht, einen String einzutragen, der länger ist als der erste, gibt es einen Crash.

Bis jetzt spielt sich noch alles tief im Speicher ab. Auf dem Bildschirm regt sich noch kein Blättchen — sprich Fensterflügel. Das Fesnter wird erst sichtbar, wenn die Funktion wind_open() es auf dem Bildschirm erscheinen läßt.

Beim Schließen eines Fensters gilt das gleiche, bloß umgekehrt: mit wind_close() verschwindet ein Fenster vom Bildschirm, ist aber intern immer noch vorhanden. Seine Handle wird erst durch den Aufruf wind_delete() wieder verfügbar.

Das wäre momentan alles, was zu Fenstern zu sagen wäre. Mehrere Fenster werden prinzipiell genauso verwaltet. Nur, daß bei jeder Nachricht, die man empfängt, explizit geprüft werden muß, welches Fenster gemeint ist (über die Handle).

Abschließend bliebe noch zu sagen, daß der eigentliche Browser zwar auch noch einige interessante Einzelheiten bietet, aber doch größtenteils Routineprogrammierung ist. Wenn es Sie interessiert, wenden Sie sich bitte direkt an den Verlag, und haben Sie sonst noch Fragen, Anregungen oder Verbesserungsvorschläge, können Sie meine Adresse über die Redaktion der ST Computer erfahren.

Download listing3.c


Thomas Weinstein
Aus: ST-Computer 12 / 1986, Seite

Links

Copyright-Bestimmungen: siehe Über diese Seite