GEM-Kurs (4)

Dieses Mal werde ich Ihnen, wie versprochen, einige Spezialitäten präsentieren, die der Umgang mit GEM-Objektstrukturen ermöglicht. Voraussetzung ist, daß sie die Grundlagen im vorangegangenen Teil verdaut haben.

Ich habe wieder ein kleines Beispielprogramm geschrieben, das die erweiterten Möglichkeiten der Objektbeeinflußung demonstriert. Da das Listing nicht allzu lang ist, konnte es komplett abgedruckt werden (Listing 2). Benötigt wird wiederum das (hier nicht abgedruckte) Modul inigem.c aus Teil 1 des Kurses. In Listing 1 finden sie zur besseren Orientierung das Include File mit den Namen der Objekte und Objektbäume.

Das TOUCHEXIT Flag

Wer sich die möglichen Objektflags in der GEM-Dokumentation genauer angesehen hat, dem ist sicher ein Flag namens TOUCHEXIT aufgefallen. Was hat es damit auf sich? Ein normaler Exit-Button wird erst dann wirksam und der form_do() Aufruf wird erst dann verlassen, wenn man den Mausknopfdrückt und dann wieder losläßt. Ein mit TOUCHEXIT gekennzeichnetes Objekt ermöglicht das Verlassen von form_do() bereits dann wenn der Mausknopf nur gedrückt wird, also vor dem Loslassen des Knopfes. Wenn also der Benutzer Ihres Programms den Mausknopf überreinem TOUCHEXIT-Objekt drückt erhalten Sie sofort die Kontrolle über das weitere Geschehen zurück. Meist wird man das programmtechnisch derart gestalten, daß man um den form_do() Aufruf eine Schleife programmiert und innerhalb der Schleife die TOUCHEXIT Behandlung vornimmt. Diese Schleife läuft solange, bis der Benutzer ein echtes EXIT-Objekt anklickt.

In den Zeilen 231-274 von Listing 2 können Sie sehen, wie man das aus den letzten beiden Teilen bekannte hndl_dialog() auf diese Art leicht erweitern kann. Nach der Rückkehr aus form_do() wird festgestellt o b das EXITObjekt das TOUCHEXIT-Flag gesetzt hat. In diesem Fall wird eine Funktion hndl_touchexit() aufgerufen. Sie können die Funktion hndl_dialog() ohne weiters in Ihren eigenen Anwendungen benutzen, Sie müssen nur jeweils hndl_touchexit() entsprechend anpassen.

Einen Pferdefuß hat die Sache noch: GEM prüft bei TOUCHEXIT-Objekten, ob die Maustaste sofort losgelassen und erneut gedrückt wird, ob also ein "Doppelklick" vorliegt. Wenn ja, wird bei der von form_do() zurückgelieferten Objektnummer das höchste Bit (Bit 15) gesetzt. Falls Sie dieses Feature nicht verwenden, sollten Sie Bit 15 unbedingt wegmaskieren (& 0x7FFF), sonst gibt es unangenehme Überraschungen. Eine Anwendung des Doppelklicks sehen sie beim GEM-Fileselektor. Wenn ein Filename doppelt angeklickt wird, wird der Fileselektor sofort verlassen und der entsprechende Name zurückgeliefert. Auf diese Weise spart der Benutzer das OK.

Noch eine rein technische Bemerkung: Wie schaltet man das TOUCHEXIT-Flag überhaupt ein? Möglichkeit 1: Man kann es direkt im Resource Construction Set (RCS) setzen. Möglichkeit 2: Man macht es vom Programm aus mit:

tree[item].ob_flagsl= TOUCHEXIT;

Bei Verwendung von TOUCHEXIT sollten übrigens die Flags SELECTABLE und EXIT nicht gesetzt sein.
Im Beispielprogramm wird TOUCHEXIT dazu verwendet, Schieberegler zu realisieren. Diese Schieberegler wurden mit dem RCS konstruiert. Wichtig ist, daß man einen Free Tree benutzt, da man nur dann die Objekte stufenlos vergrößern und verkleinern kann.

Ein Schieberegler besteht im wesentlichen aus drei Teilen: Einer Box, die den Reglerpfad symbolisiert (im Programm FBAR und ABAR),einer Box für den eigentlichen Reglerknopf (FSLIDE und ASLIDE) und einer Bounding Box (FEXT und AEXT),die beide anderen
Teile umschließt. Bei der Konstruktion ist darauf zu achten, daß die BARS und SLIDES direkte Nachkommen der Bounding Boxes im Objektbaum sind. Damit die einmal hergestellte Struktur nicht mehr verändert wird, sollte man das Sortieren bei Schiebereglern vermeiden. Eine einfache Möglichkeit, im RCS die Verwandschahsverhältnisse festzustellen, ist folgende: Man klickt ein Objekt an und betätigt dabei die CONTROLTaste. Dadurch wird der direkte Vorgänger des angeklickten Objekts selektiert.

Die Reglerskala ist einfach ein String vom Typ BOXTEXT. Skala und Regler habe ich wiederum in einer umfassenden Box zusammengefaßt, um denk Regler als ganzes verschieben (CONTROL Taste) und kopieren (SHIFT-CONTROL Tasten) zu können.

Die Programmierung des Schiebereglers sehen sie in der Funktion hndl_touchexit(), Zeile 20332. Der Regler kann, wie üblich, direkt mit der Maus verstellt werden, indem man entweder auf dem Reglerknopf die Maustaste drückt und dann den Knopf verschiebt, oder (schneller) durch Anklicken des Reglerpfades an der gewünschten Stelle.

Neben ein bißchen Hin- und Herrechnerei mit Objektkoordinaten wird nur noch die Funktion graf_slidebox() verwendet. Sie erlaubt es, ein Rechteck in einem anderen zu bewegen, und liefert nach Loslassen der Maustaste einen Wert zwischen 0 und 1000 zurück, der die Stellung der Rechtecke zueinander ausdrückt. Die Rechtecke sind dabei BOX Objekte in einem Objektbaum (im Beispiel bewegt sich z.B ASLIDE innerhalb von AEXT). Leider muß man die Objektkoordinaten selbst verändern (s. set_slider()).

Das INDIREKT Flag

Ein weiteres nützliches Flag ist das INDIREKT-Flag. Wenn es gesetzt ist, interpretiert GEM das ob_spec Feld einer Objektstruktur nicht als die nähere Spezifikation des Objekts, sondern als Zeiger auf sie. Man kann dies verwenden, um etwa zu einem Objekt eine Struktur mit weiteren Angaben zum Objekt anzulegen. An die erste Stelle dieser Struktur schreibt man den alten ob_spec Eintrag, und an Stelle des ob-spec schreibt man im Objekt
Das Desktop z. B. verwendet diese Methode, um zu jedem Dateiobjekt (Icon oder Text) weitere Einzelheiten zur Datei zu speichern.

In Kombination mit dem TOUCHEXIT-Flag bieten sich weitere interessante Möglichkeiten. Das Beispielprogramm benutzt das INDIREKT-Flag zum Einstellen der Kanalnummer (siehe Bild 1). Bei jedem Anklicken der Kanalnummee wird der form_do() Aufruf verlassen (TOUCHEXIT) und der ob_spec Eintrag (INDIREKT) auf eine neue Struktur vom Typ KANALTYP gerichtet. In der Struktur _Kanaltyp" ist an erster Stelle der alte ob_spec Eintrag gespeichert. Da das Objekt vom Typ BOXCHAR ist, steht im höchsten Byte derjenige Buchstabe, der im Button erscheinen soll. Außerdem stehen in der Struktur die Schiebereglerpositionen für Frequenz und Lautstärke.

Die Initialisierung der Kanalstruktur und des Buttonobjekts finden Sie in Listing 2, Zeile 105-114, ihre Verwendung in der Funktion handl_touchexit(), Zeile 288-294.

Ein eigenes Desktop

Eine nette Sache, die nur am Rande mit Objekten zu tun hat, ist das Anlegen eines eigenen Desktophintergrunds. GEM übernimmt ein vom Benutzer definiertes Desktop mit dem Aufruf:

wind_set (0,WF_NEWDESK,tree,0,0);

tree ist dabei die Adresse eines Objektbaums, den man z.B. mit dem RCS konstruieren kann. In diesem Objektbaum ist alles erlaubt, auch benutzerdefinierte Objekte, die ich Ihnen gleich erklären werde. In Listing 2 habe ich als tree einfach eine Dialogbox mit einem dunklen Füllmuster übergeben, die in der linken oberen Ecke einen BOXTEXT beinhaltet. Mit der Größe der Objekte muß man aufpassen, sonst gibt es leicht ausgefranste Ränder. Wie man das Ganze macht, sehen sie in Zeile 88-95 von Listing 2. Das Ergebnis ist auf den Bildern 1 und 2 zu sehen.

Unbedingt notwendig ist der form_dial(3,...)-Aufruf, um GEM zu einem kompletten Redraw des Desktops zu veranlassen. Der besondere Gag ist, daß von diesem Moment an GEM das neue Desktop ganz allein verwaltet.
Also: Was immer Sie übergeben haben - GEM managt alle notwendigen Redraws selbständig. Mit Hilfe von benutzerdefinierten Objekten könnten Sie sich ohne weiteres einen rosa Hintergrund mit blauen Tupfen anlegen, oder ein digitalisiertes Bild Ihrer Freundin oder Ihres Freundes.

Benutzerdefinierte Objekte

Dies ist der letzte große Abschnitt zu den Objektbäumen unter GEM. Wer sich bis hierher vorwagt, kann alles machen: Thermometerskalen, Analoganzeigen, Buttons, die beim Anklicken eine Melodie spielen, Objekte, die ihre Form verändern, aus GEM-DRAW importierte Handzeichnungen usw.

Der Schlüssel zu all diesen Wundern ist der Objekttyp G_PROGDEF. Wenn GEM diesen Typ vorfindet, erwartet es, daß der ob_spec Eintrag des Objekts auf eine Struktur vom Typ APPLBLK (Application Block) zeigt. Diese Struktur hat folgendes Aussehen:

typedef struct applblk int (*ub_code)(); long ub_parm; )APPLBLK;

Der erste Eintrag muß ein Zeiger auf eine Funktion sein, die einen int-Wert zurückliefen. Der zweite Eintrag ist beliebig und wird beim Aufruf der Zeichenfunktion verfügbar gemacht. Jedesmal, wenn nun das Objekt gezeichnet werden muß -entweder durch einen Aufruf von objc_draw() oder objc_change() - wird die angegebene Funktion ausgeführt. Als Parameter erhält sie einen Zeiger auf eine Struktur namens PARAMBLK, die so definiert ist:

typedef struct parm_blk OBJECT * pb_ tree;  
int pb_obj;  
int pb_prevstate, pb_currstate;  
int pb_ x,pb_y, pb_w,pb_h;  
int pb_ xc,pb_yc,pb_wc,pb_hc;  
long pb_parm;  
~PARAMBLK;

pb_tree ist der Objektbaum, in dem sich das Objekt befindet, pb_obj ist der Index des Objekts. pb_prevstate = = pb_currstate bedeutet, daß das Objekt gezeichnet werden soll, != heißt, daß der Zustand des Objekts geändert wurde. Die zwei Statevariablen sind so aufgebaut wie das Feld ob_state in der Objektstruktur. pb_x .. pb_h sind die Abmessungen der Bounding Box des Objekts und pb_ xc .. pb_hc ist das Cliprechteck, in dem gezeichnet werden soll. pb_parm ist identisch mit ub_parm aus dem APPLBLK. Mit diesen Informationen kann die Zeichenfunktion ihren Job erledigen. Die Struktur APPLBLK können Sie übrigens beliebig erweitern, da der benötigte Speicherplatz von Ihrem Programm reserviert wird. Die PARAMBLK Struktur darf nicht erweitert werden, da sie im Datenbereich des AES angelegt wird.

Innerhalb der Zeichenroutine gilt eine strikte Regel: Es dürfen keine AES-Aufrufe gemacht werden! VDI-Aufrufe sind dagegen erlaubt. Das kommt daher, daß Ihre Zeichenroutine von AES ausgeführt wird und AES selbst nicht reentrant (wiedereintrittsfähig) ist. Sie sollten auch darauf achten, Parameterlisten nicht zu lang zu machen oder viele verschachtelte Funktionsaufrufe zu vermeiden, da die Zeichenroutine den (sehr kleinen !?) Stackbereich des AES benutzt.

Als Beispiel habe ich Ihnen einen Smiley Button programmiert. Wie er aussieht, können Sie auf Bild 2 bewundern; wie er programmiert ist, in der Funktion draw_smiley() von Listing 2. Nicht selektiert zeigt er ein fröhliches Gesicht, selektiert ein böses.

Dabei können Sie gleich ein Problem von runden Objekten feststellen: Es gibt Stellen außerhalb des Objekts, die selektierend wirken. Es sind genau die Bereiche zwischen dem Kreis und seiner Bounding Box.
Und nun: Viel Spaß beim Entwerfen von eigenen Objekten, Desktops und allen möglichen Kombinationen!


Thomas Weinstein
Aus: ST-Computer 03 / 1987, Seite 20

Links

Copyright-Bestimmungen: siehe Über diese Seite