GEM-Kurs (3)

In Teil 3 und 4 meines Kurses „GEM unter C“ werde ich recht intensiv in die Objektdatenstrukturen eindringen. Ich will Ihnen zeigen, wie ein Objektbaum aus der Sicht des C-Programmierers aussieht, wie man einen Baum durchlaufen kann, um irgendwelche Operationen auf seine Knoten anzuwenden, und wie man sich eigene Objekte definieren und dann wie Kuckuckseier dem Dialogmanager von AES unterschieben kann. Auch diesmal gibt es wieder Beispiele, die etwas kompakter ausgefallen sind. Dies hat den Vorteil, daß sie vollständig abgedruckt werden können - mit Ausnahme jener Teile, die schon in den ersten beiden Folgen nachzulesen waren.

In der letzten Folge haben Sie gesehen, wie man die Möglichkeiten des Resource Construction Set (RCS) benutzt, um sich schnell einfache Dialoge zu basteln. Diesmal zeige ich Ihnen die darunterliegenden Datenstrukturen und was man mit ihnen machen kann.

Bild 1a: Beispielresource

Zur Demonstration habe ich einen einfachen Beispieldialog entwickelt, den sie auf Bild 1a sehen. Jedes Objekt im Dialog hat einen Namen bekommen, so daß Sie immer wissen, wovon geredet wird. Normalerweise versieht man nur diejenigen Objekte mit Namen, auf die man im Programm explizit Bezug nehmen will.

Sehen Sie sich bitte parallel zu 1a Bild 1b an. Dort habe ich versucht, die Abhängigkeiten der einzelnen Objekte untereinander zu verdeutlichen. Lassen Sie sich nicht durch die vielen Pfeile verwirren, sondern achten Sie zuerst auf die Anordnung in „Ebenen“.

Ganz oben steht ein Objekt mit Namen BEISPIEL. Es ist die sogenannte Wurzel des Objektbaumes. Die Wurzel ist immer dasjenige Objekt im Baum, das mit dem Index 0 bezeichnet wird, sozusagen der Einstieg oder Zugang zu allen anderen Objekten. Bei einem Dialogbaum steht die Wurzel gleichzeitig für den Rahmen um den Dialog. Alle anderen Teile müssen vollständig in diesen Rahmen passen. Das führt uns gleich zu einem weiteren wichtigen Begriff. Zu jedem Objekt gehört eine sogenannte „Bounding Box“, also ein umschließendes Rechteck. Die Regeln für GEM-Objektbäume sagen nun, daß jedes im Baum tieferliegende Objekt vollständig innerhalb der Bounding Box seines Vorgängers liegen muß. Außerdem dürfen sich die Bounding Boxes von Objekten auf gleicher Ebene normalerweise nicht überlappen. Aber zurück zum Beispiel: Nach der Wurzel stehen nebeneinander auf gleicher Ebene drei Objekte. Ein String (TITEL), ein Quadrat (SMILEY; Warum ich es so genannt habe, wird erst im nächsten Teil verraten) und ein großes Rechteck mit dickem Rand (BIGBOX). BIGBOX selber hat wiederum zwei untergeordnete Objekte (OK und CANCEL), die üblichen Exit Buttons, die Sie ja schon aus der letzten Folge kennen.

Nachdem die Aufteilung in Ebenen soweit bekannt ist, kommen wir jetzt zu den Pfeilen. Um den Dialog zu zeichnen, muß GEM ja irgendwie von einem Objekt zum Anderen gelangen (möglichst in der richtigen Reihenfolge) und am Ende auch wieder irgendwo definiert aufhören. Dafür werden die Pfeile gebraucht. Sie stellen sozusagen die Wege dar, die den Malalgorithmus von Objekt zu Objekt bringen. Wie Sie sehen, gehen von jedem Objekt maximal drei Wege weiter. Die drei schwarzen Punkte sind jeweils die „Ausgänge“ aus dem Objekt. Wenn von einem Objekt kein Weg weiterführt, ist das für GEM ein Zeichen, daß es in einer anderen Richtung weitersuchen muß. Die drei Ausgänge aus einem Objekt haben von links nach rechts die Namen HEAD (Kopf), NEXT (nächstes) und TAIL (Schwanz). Head führt dabei immer zum ersten (am weitesten links stehenden) Objekt in der eins tieferliegenden Ebene. Tail zeigt immer auf das letzte (ganz rechts stehende) Objekt in der nächsten Ebene. Diese beiden Zeiger klammern also sozusagen den Unterbaum eines Objektes ein. Der Headzeiger von BEISPIEL zeigt, wie in Bild 1b zu sehen ist, auf TITEL, der Tailzeiger auf BIGBOX.

Bild 1b: Objektbaum zur Beispielresource

Etwas schwieriger ist es mit dem NEXT-Zeiger. Innerhalb einer Ebene zeigt er auf den rechten Nachfolger des jeweiligen Objekts. Der NEXT-Zeiger des am weitesten rechts stehenden Objekts zeigt eine Stufe höher auf seinen Vater. Auf diese Weise kann man sehr leicht feststellen, daß eine Ebene vollständig abgearbeitet ist. Nur dann nämlich zeigt der TAIL-Zeiger des Objektes, auf das NEXT zeigt, auf das Objekt zurück, dem der NEXT-Zeiger gehört. Das hört sich schwieriger an, als es ist. In Bild lb z. B. zeigt der NEXT-Zeiger von BIG-BOX auf BEISPIEL und dessen TAIL wiederum auf BIGBOX.

Das einzige Objekt in einem Baum, dessen NEXT-Zeiger auf nichts (auf NIL) zeigt, ist die Wurzel (ROOT) des Baumes.

Es gibt verschiedene Möglichkeiten, diese Struktur auf einem Rechner abzubilden. Üblicherweise stellt man Bäume im Speicher des Computers als eine Ansammlung von Knotenobjekten dar, in deren Zeigerkomponenten die ADRESSEN von anderen Objekten im Baum stehen. Digital Research ist bei GEM einen etwas anderen Weg gegangen.

Alle Objekte stehen brav hintereinander in einem großen Array. Die Zeiger der Objekte sind keine Adressen mehr, sondern Indizes, die innerhalb des Arrays auf andere Objekte verweisen. Um diesen Sachverhalt weiter zu erhellen, habe ich die Beispielresource als C Sourcefile ausgedruckt. Das Ergebnis sehen Sie in Listing 1.

Ganz oben sehen Sie das Include File, das der RCS erzeugt, in einer etwas aufbereiteten Form. Es enthält als C Defines die Namen, die ich den Objekten gegeben habe, und dahinter die Indizes, die direkt zum entsprechenden Eintrag im Objekt-Array führen. Die Indizes werden dabei von Null aufwärts gezählt. Um also ein bestimmtes Objekt zu finden, schauen Sie unter dem zugehörigen Index nach (für BIGBOX z. B. 3) und zählen dann von 0 ausgehend zeilenweise, bis Sie den Index im Array erreicht haben. Eine Zeile (in Listing 1) im Objektarray steht also immer für ein Objekt.

Schauen wir uns jetzt einen Objekteintrag näher an. In den ersten drei Spalten finden Sie in der Reihenfolge Net, Head und Tail die oben besprochenen Zeiger wieder. Eine -1 steht dabei für leer oder NIL. Den Einstieg in den Baum findet man, ohne die Kommentare anzuschauen, in jener Zeile, in der Next auf -1 steht. Wie oben bemerkt, ist ja die Wurzel das einzige Objekt, auf das dies zutrifft. Versuchen Sie doch selbst einmal, aus dem Objektarray den in Bild 1b gezeichneten Baum zu rekonstruieren.

Die 4. Spalte enthält den Typ des Objekts, im Fall der Wurzel G_BOX.

Ausgehend von diesem Typ werden die weiteren Einträge entsprechend interpretiert. Neben den aufgelisteten Typen gibt es noch einige andere, z. B. G_ICON oder G_BOXTEXT.

Eine vollständige Übersicht über alle Typen und die im folgenden noch zu besprechenden Konstanten und Datenstrukturen finden Sie übrigens in obdefs.h, das bei Ihrem C-Compiler dabei sein sollte, falls er GEM unterstützt.

Die fünfte Spalte enthält in Ergänzung zum Typ verschiedene Flags, die durch gesetzte oder nicht gesetzte Bits in einem Integer-Wert symbolisiert werden. In unserem Beispiel können Sie sehen, daß die ersten vier Objekte keine gesetzten Flags haben (NONE). Nur die beiden Buttons (OK, CANCEL) haben Flags gesetzt. Die 0x5 bei CANCEL bedeutet z. B., daß der Button SELECTABLE ist (also auf Anklicken reagiert) und außerdem ein EXIT Button ist. Das heißt der Dialog wird verlassen, wenn er angeklickt wird. Bei OK sind zusätzlich noch die Flags für DEFAULT (er reagiert auf die Return Taste) und LASTOB (er ist das letzte Objekt im Baum) gesetzt.

In der nächsten Spalte steht der augenblickliche Zustand des Objekts. Aus diesem Eintrag erkennt die Funktion objc_draw, wie sie ein Objekt zu zeichnen hat. Die Dialogbox hat z. B. den Zustand OUTLINED, wird also mit einem dicken Rand gezeichnet. Wie Sie sich vielleicht erinnern, haben wir in der letzten Folge aus dem Statusfeld eines Objektes (ob_state) herausgelesen, welche Objekte der Benutzer während dem Dialog angeklickt hat.

Das Feld ob_spec ist das komplizierteste. Es hat je nach Objekttyp ganz spezielle Funktionen. Bei einer G_BOX z. B. enthält es Informationen über die Breite des Rahmens, sowie Farbe und Muster des Hintergrunds.

Bei den Objekten G_STRING und G_BUTTON enthält es den Index auf ein weiteres Array mit dem Namen rs_strings. Sie finden es in der Mitte von Listing 1. Dort stehen die Texte, die nachher tatsächlich im Objekt auftauchen. Für kompliziertere Objekte wie ICONS oder editierbare Textfelder verweist ob_spec auf weitere Datenstrukturen, die das Objekt näher spezifizieren. Das ob_spec Feld ist, wie wir im nächsten Teil noch sehen werden, der Schlüssel zu selbstdefinierten Objekten.

Die letzten 4 Spalten in einer Objektzeile enthalten die Koordinaten der Bounding Box des Objekts in der Reihenfolge X,Y (linke obere Ecke), W und H (Breite und Höhe). Wie sie leicht sehen können, sind die Werte nicht in Bildschirmpunkten eingetragen (dann müßten sie nämlich viel größer sein). Um ein Resourcefile möglichst geräteunabhängig zu halten, werden sie in Zeichenzeilen und Spalten angegeben; erst die Funktion rsrc_load rechnet sie in Punkte um.

Ein weiterer bemerkenswerter Punkt ist, daß nur die Koordinaten der Wurzel absolut angegeben sind. Alle anderen Objekte sind relativ zum umschließenden Objekt angegeben. Angenehme Folgerung ist, daß man durch Veränderung der Wurzelkoordinaten das gesamte Objekt verschieben kann. Unangenehm ist, daß man, um ein Objekt zu zeichnen, den Baum von der Wurzel aus durchlaufen muß, um die relativen Koordinaten aufzuaddieren (das erledigt jedoch zum Glück objc_draw).

Alle aufgeführten Daten werden also in ein Resourcefile gepackt und müssen dann von der Funktion rsrc_load entsprechend interpretiert werden.

Damit Sie ein Gefühl für Resourcen bekommen und verschiedene Aspekte selbst nachvollziehen können, habe ich ein kleines Beispielprogramm geschrieben. Sie finden es in Fisting 2. Sie können sich damit beliebige Re-sourcefiles - Ihre eigenen oder die von anderen Programmen - schrittweise anschauen. Es ist vollständig abgedruckt, bis auf die vier Funktionen open_vwork, close_work, hide_mouse und show_mouse, die schon in Teil 1 des Kurses erklärt wurden.

Als nächstes werde ich die interessanten Teile daraus erklären. Zuerst ein paar Worte zur Bedienung. Das Programm liest jede beliebige Resourcedatei ein. Einzige Bedingung ist, daß sie den Namen TEST.RSC hat. Zuerst müssen Sie also die Resourcedatei, die Sie anschauen wollen, umbenennen.

/*********************  BRAIN-VORKS' Resource-Table ************************
 *                                                                         *
 * Dies ist das zur Beispielresource gehörende INCLUDE File nach Sortieren *
 * mit der zum MEGAMAX gehörenden Utility MMTREES.                         *
 *                                                                         *
 ***************************************************************************/

#define BEISPIEL 0	/* Baumnr. 0	= Dialog */
#define	TITEL	1	/* Objekt	«	STRING */
#define	SHILEY	2	/* Objekt	=	BOX */
#define	BIGBOX	3	/* Objekt	=	BOX */
#define	CANCEL	4	/* Objekt	=	BUTTON */
#define	OK	5	/* Objekt	=	BUTTON */

/**********************************************************************
 * Ab hier folgt ein Ausschnitt aus der C Source, die man mit dem RCS *
 * im ATARI Entwicklungspaket erzeugen kann. Alle Datenstrukturen, die*
 * in der Beispielresource nicht benutzt werden sind weggelassen.     *
 * Die Kommentare wurden nachträglich von Hand ergänzt.               *
 **********************************************************************/

/* Hier sind alle in der Resource vorkommenden Strings gesammelt */ 
char *rs_strings[] = { 
 "GEM-Kurs 3 Beispielresource",
 "CANCEL",
 "OK"};

/* Der Kernpunkt: Das Array mit den Objekten */
OBJECT rs_object[] = { 
/*
 N   H   T  TYP       FLAGS STATUS    OB_SPEC     X  Y   W   H  NAME
-----------------------------------------------------------------------*/
-1,  1,  3, G_BOX,    NONE, OUTLINED, 0x21100L,   0, 0, 58, 14, /* BEISPIEL*/
 2, -1, -1, G_STRING, NONE, NORMAL,   0x0L,      15, 1, 27, 1, /* TITEL
 3, -1, -1, G_BOX,    NONE, NORMAL,   0xFF1100L, 23, 3, 10, 4, /* SMILEY *
 0,  4,  5, G_BOX,    NONE, NORMAL,   0xFD1100L, 10, 8, 36, 4, /* BIGBOX *
 5, -1, -1, G_BUTTON, 0x5,  NORMAL,   0x1L,      23, 1, 10, 2, /* CANCEL *
 3, -1, -1, G_BUTTON, 0x27, NORMAL,   0x2L,       4, 1, 10, 21; /* OK */

/*---------------------------------------------------------------------*/

/* Einstieg ins Objektarray. Für jeden Baum einen Eintrag */ 
long rs_trindex[] = {
0L};


/*
 * GEMKURS TEIL 3
 *
 * Beispiel fuer das Durchlaufen eines Objektbaumes und das Arbeiten mit
 * der Headerinformation
 *
 * Compiler: MEGAMAX 
 */

#include	<define.h>
#include	<stdio.h>
#include	<gemdefs.h> 
#include	<obdefs.h>
#include	tosbind.h>

#define	NIL	-1
#define Wait() {printf("\033p Weiter - Return \033q"); gemdos(1);}

extern	int	handle;
extern	int	phys_handle;
extern	int	global[];	/* AES Global Array s. Text */

RSHDR header;	/* Resourceheader def in obdefs.h */

main(argc,argv) 
int argc; 
char **argv;
{
	int	i, ret, hndl, height, room;
	int *help;
	int	print_object();
	OBJECT **treeadress,
		*ob;

	setbuf(stdout,0L); /* Ungepufferte Ausgabe */ 
	
	appl_init();
	phys_handle = graf_handle(&ret, &ret, Sret, &ret); 
	open_vwork();

	/* Einlesen und Ausdrucken des Resourceheaders */ 
	if ((hndl = Fopen("test.rsc",0)) < 0)
	{
		form_alert(1,"[1][Fehler beim öffnen von test.rsc][ CRASH ]"); 
		goto ENDE;
	}
	Fread(hndl,(long) sizeof(RSHDR),&header); 
	print_rshdr(&header);
	Fclose(hndl);
	Wait();

	/* Laden der Resource */ 
	if (!rsrc_load("test.rsc"))
	{
		form_alert(1,"[1][FATAL lieber Thomas.!Keine Resource !][ CRASH ]"); 
		goto ENDE;
	}

	hide_mouse();
	/* Adresse des Arrays mit den Adressen der Objektbaeume aus dem AES */
	/* GLOBAL Array holen */
	help = (int *) &treeadress; /* Trick 17 */ 
	help[0] = global[5]; 
	help[1] = global [6];

	/* Informationen ueber Objektbaeume ausgeben */ 
	for (i = 0; i < header.rsh_ntree; i++)
		treewalk(treeadress[i],ROOT,NIL,print_object);

ENDE:
	show_mouse() ; 
	close_vwork();
} /* main() */

/*
 * Druckt Informationen zum Objekt index im Baum tree aus und zeichnet
 * das Objekt 
 */
print_object(tree,index)
OBJECT *tree; 
int index;
{
	OBJECT *o;

	o = tree + index; /* Objektadresse */

	printf("\033E"); /* CLEAR HOME */ 
	printf("Adresse des Objekts: 0x%081x\n",o); 
	printf("OB_NEXT	= %d\n",	o->ob_next);
	printf("OB_HEAD	= %d\n",	o->ob_head);
	printf("OB_TAIL	= %d\n",	o->ob_tail);
	printf("OB_TYPE	= %u\n",	o->ob_type);
	printf("OB_FLAGS	= %04x\n",	o->ob_flags);
	printf("OB_STATE	= %04x\n",	o->ob_state);
	printf("OB_SPEC = 0x%081x\n",o->ob_spec); 
	printf("OB_X	= %d\n",	o->ob_x);
	printf("OB_Y	= %d\n”,	o->ob_y);
	printf("OB_WIDTH	= %d\n",	o->ob_width);
	printf("OB_HEIGHT	= %d\n\n",	o->ob_height);

	o->ob_x = 639 - o->ob_width; /* In die rechte untere Ecke legen */ o->ob_y = 399 - o->ob_height;
	objc_draw(o,0,0,0,0,640,400);	/* Zeichnen und zwar nur das Objekt*/
									/* allein. */
	Wait(); return(TRUE);	/* Auf Taste warten */
}

/*
 * Resourceheader ausgeben 
 */
print_rshdr(h)
RSHDR *h;
{
	printf("\033ERESOURCE HEADER\n\n"); /* CLEAR HOME */ 
	printf("RSH_VRSN = %d\n",h->rsh_vrsn); 
	printf("RSH_OBJECT = %d\n",h->rsh_object); 
	printf("RSH_TEDINFO = %d\n",h->rsh_tedinfo); 
	printf("RSH_ICONBLK = %d\n",h->rsh_iconblk); 
	printf("RSH_BITBLK = %d\n",h->rsh_bitblk); 
	printf("RSH_FRSTR = %d\n",h->rsh_frstr); 
	printf("RSH_STRING = %d\n",h->rsh_string); 
	printf("RSH_IMDATA = %d\n",h->rsh_imdata); 
	printf("RSH_FRIMG = %d\n",h->rsh_frimg); 
	printf("RSH_TRINDEX = %d\n",h->rsh_trindex); 
	printf("RSH_NOBS	= %d\n",h->rsh_nobs);
	printf("RSH_NTREE	= %d\n",h->rsh_ntree);
	printf("RSH_NTED	= %d\n",h->rsh_nted);
	printf("RSH_NIB	= %d\n",h->rsh_nib) ;
	printf("RSH_NBB	=	%d\n",h->rsh_nbb);
	printf("RSH_NSTRING = %d\n",h->rsh_nstring); 
	printf("RSH_NIMAGES = %d\n",h->rsh_nimages); 
	printf("RSH_RSSIZE = %d\n\n",h->rsh_rssize);
}

Das Programm zeigt Ihnen dann, was im Header der Datei steht, und druckt anschließend für jeden Baum und jedes Objekt in der Reihenfolge, in der sie gezeichnet werden, die Objektdatenstruktur aus, und zeichnet das Objekt außerdem noch in der rechten unteren Bildschirmecke. Schauen Sie es sich einfach an. Es heißt RSC-LOOK und steht wie üblich inklusive Source und allem anderen auf der vom Verlag erhältlichen Monats-Diskette.

Nach der üblichen Initialisierung am Anfang wird in den Zeilen 40-49 zuerst der Header der Resourcedatei gelesen und ausgegeben. Neben dem angenehmen Effekt, daß man sich die Daten anschauen kann, hat das folgenden Grund: Da jede beliebige Resourcedatei geladen werden kann, muß man sich irgendwo die Information besorgen, wieviele Bäume überhaupt vorhanden sind. Genau diese Information findet sich im Resourceheader. Fällt Ihnen noch eine andere Methode ein? Aus der Erklärung der Verzeigerung weiter oben kann man eine alternative Methode ableiten.

Dann wird die Resource ganz normal mit rsrc_load geladen. Das nimmt uns die Arbeit ab, die Verzeigerung selbst einrichten zu müssen. Jetzt folgt ein weiterer Trick. Jede AES-Applikation hat ein globales Array GLOBAL, in dem allgemeine Informationen zur Applikation eingetragen werden. Unter anderem steht in GLOBAL[5] und GLOABL[6] die Adresse eines Arrays, in dem die Adressen der Wurzeln sämtlicher Bäume stehen. Mit dieser Information und der Anzahl aus dem Header kann man die Bäume jetzt nacheinander durchlaufen.

Damit stoßen wir zum Kernpunkt des Programms vor: Dem Durchlaufen eines Baumes. Ich muß an dieser Stelle zugeben, daß dieser Programmteil nicht allein von mir stammt. Ich habe ihn aus einem Artikel von Tim Oren, dem Autor des DR RCS entnommen, ausführlicher kommentiert und die Variablen etwas sinniger benannt. Mir war zuerst die Idee zu einem rekursiven Algorithmus gekommen, der zwar wunderbar funktionierte, doch ebenso umfangreich wie schwer verständlich war.

Die Funktion von Tim Oren ist ziemlich allgemein angelegt und kann immer dann benutzt werden, wenn ein Objektebaum oder Teile davon, Knoten für Knoten, bearbeitet werden müssen.

Für die weiteren Erklärungen sollten Sie sich Listing 2 ab Zeile 135 vornehmen.

Die Funktion treewalk wird mit drei Parametern aufgerufen. Zuerst wird die Adresse des zu übertragenden Baums übergeben, dann der Index des ersten zu bearbeitenden Objekts (meistens wird das das Objekt ROOT also die Wurzel sein). Der dritte Parameter steht für den Index des letzten zu bearbeitenden Objekts (wenn hier NIL übergeben wird, wird der gesamte Baum abgearbeitet). An vierter Stelle wird die Adresse einer Funktion übergeben, die jedesmal dann aufgerufen wird, wenn ein neuer Knoten im Baum erreicht wird. Diese Funktion hat den Nebeneffekt, daß, wenn sie als Ergebnis FALSE liefert, der gesamte Unterbaum, also der Baum, auf den die HEAD Komponente des Objekts zeigt, nicht bearbeitet wird.

Falls Sie bis jetzt nicht wußten, wofür es in C Funktionszeiger gibt - hier sehen Sie ein Beispiel. In Pascal müßte man sich für jeden Anwendungsfall eine eigene Prozedur schreiben.

Das Interessante an der Funktion ist die Tatsache, daß sie vollkommen iterativ (schrittweise), also ohne Rekursion arbeitet und trotzdem sehr elegant - in programmtechnischem Sinne - ist.

Da sich die Funktion mit Worten nur schlecht erklären läßt, werde ich nur kurz die Idee skizzieren. Sie sind dann aufgefordert, sich mit einem Bleistift und einem Blatt Papier zu bewaffnen und C-Interpreter zu spielen; vielleicht mit Hilfe des Beispielbaums in Bild lb.

Solange das aktuelle Objekt (this) ungleich dem Letzten (last) und ungleich NIL (-1) ist, wird die while-Schleife durchlaufen.

Der eigentliche Trick an der Sache ist es, immer den Index des Letzten (prev) vor dem aktuellen behandelten Objektes mitzuziehen und damit festzustellen, wann eine Hierarchieebene abgearbeitet ist. Sie wissen ja noch: Wenn der Tailzeiger des aktuellen Objekts auf das zuletzt bearbeitete Objekt zeigt, ist man eine Stufe im Baum aufgestiegen. Ansonsten versucht der Algorithmus, sich immer möglichst tief in den Baum zu schaffen, indem er solange als möglich dem Pfad folgt, der durch den HEAD-Zeiger vorgegeben ist. Erst wenn es in dieser Richtung nicht mehr weitergeht, wird über NEXT ein Schritt in „horizontaler“ Richtung gemacht. Der Fachausdruck, für diese Art einen Baum zu durchlaufen, heißt Tiefensuche oder Depth First oder auch „den Baum in Pre-order“ durchlaufen.

treewalk wird in Listing 2 verwendet, um für jeden Knoten die Funktion print_object aufzurufen. An print_object ist nur die Art und Weise bemerkenswert, wie draw_objc benutzt wird, um genau ein Objekt zu zeichnen.

Spielen Sie etwas mit dem Programm und schauen Sie sich mal die Resourcen zu verschiedenen Programmen an. Interessant ist z. B. die Art und Weise, in der Pulldown-Menüs gemacht werden.

So, ich denke, es ist mal wieder genug. Sie wissen jetzt so ziemlich alles über den globalen Aufbau von Objektbäumen. Wie die Objekte im Einzelnen aussehen (Icons, Textfelder, Buttons), wurde schon an verschiedenen Stellen in aller Ausführlichkeit abgedruckt.

In der nächsten Folge zeige ich Ihnen, wie man beliebige eigene Objekte in die Resourcen einbauen kann und wie man mit dem TOUCHEXIT-Attribut den form_do Aufruf leicht erweitern kann.

/*
 * TREEWALK Funktion nach Tim Oren 
 */

treewalk(tree, first, last, routine)
OBJECT	*tree;	/* Adresse des zu durchlaufenden Baums */
int	first,	/* Index des Startobjekts */
	last;	/* Index des letzten Objekts */
int	(*routine)();	/* Zeiger auf Funktion,	die	bei jedem */
			/* Objekt aufgerufen werden	soll */
{
	register int	this,	/* Index des aktuellen Objekts */
					prev;	/* Index des vor 'this' zuletzt	be- */
							/* suchten Objekts */

	prev = this = first;	/* Initialisierung! Tail kann nie auf */
							/* sich selbst zeigen !!! */
	/*
	 * Solange weitermachen, bis 'last' Objekt oder Ende des Baums 
	 */
	while ((this != last) && (this != NIL))
		/*
		 * Wenn wir bei 'this' schon mal waren muss der Tailpointer
		 * von 'this' auf 'prev' zeigen.
		 */
		if (tree[this].ob_tail != prev)
		{
			prev = this;	/* Ein neuer Knoten! Vorgaenger */
			this = NIL;		/* neu setzen und 'this' auf */
							/* NIL (Grund s.u.) */
			/*
			 * Jetzt fuer den neuen Knoten Funktion aufrufen
			 * Falls die Funktion FALSE liefert wird der gesamte
			 * Unterbaum des Knotens nicht besucht.
			 */
			if ((*routine)(tree, prev))
					/*
					 * Hat der Knoten einen Nachfolger ?
					 * Sonst ist 'this = NIL.
					 */
					this = tree[prev].ob_head;
			/*
			 * Hat keinen Nachfolger, oder Unterbaum soll
			 * nicht besucht werden.
			 */
			if (this == NIL)
			/*
			 * Weiter in der Geschwisterliste des Knotens 
			 */
				this = tree[prev].obnext;
		}
		else
		/*
		 * Sonst ist Unterbaum komplett abgearbeitet und es geht weiter
		 * in der Geschwisterliste des Knotens.
		 */
		{
			prev = this;
			this = tree(prev).ob_next;
		}

} /* End of TREEWALK() */

Thomas Weinstein
Aus: ST-Computer 02 / 1987, Seite 24

Links

Copyright-Bestimmungen: siehe Über diese Seite