ST-Ecke: Klappe auf zur Vierten!

Willkommen zur Juni-Ausgabe der ST-Ecke. Viele öffnen gerade bei diesem schönen Wetter ihre Fenster. Und genau dies wollen auch wir tun: nach mehrfacher Anfrage von Lesern soll die Routine do_redraw(), die in jedem zweiten Programm vorkommt und wichtig für die Fensterverwaltung ist, genauer unter die Lupe genommen werden, damit auch auf unserem ST die Fenster geöffnet werden können. Als zweites Thema wird die variable Gestaltung von Menüleisten behandelt. Den interessantesten Punkt der heutigen ST-ECKE bietet sicherlich die Pexec-Routine: Ich werde genauer auf die Optionen dieser Routine eingehen; zum Beispiel, wie man Programme lädt und zu einem späteren Zeitpunkt startet. Zum Schluß bieten wir eine Joystick-Routine in OMIKRON-Basic an, das sich in letzter Zeit immer größerer Beliebtheit erfreut.

Öffnet die Fenster

Zunächst einige Erklärung zur Fensterverwaltung des STs. Vielfach versteht man unter einer Fensterverwaltung folgendes: Man definiert sich einen Bereich, in dem man mit seinem Fenster arbeiten möchte, und sieht die linke obere Ecke als XY-Koordinate 0,0 an. Außerdem erwartet man, daß, wenn der Fensterbereich verlassen wird, ein Überschreiben des Außenbereichs verhindert wird. Diese beiden Dinge (Koordinate 0,0 und Abfrage des Außenbereichs) werden leider vom ST nicht direkt übernommen. Allerdings bietet der ST Hilfen zur Handhabung des Fensterrahmens und dessen Objekte, wie zum Beispiel die Slider (Schieber), Fensterüberschrift etc., und gibt eine Meldung aus, wenn in ein Fenster überschrieben wurde, so daß das Programm, das das entsprechende Fenster bearbeitet, dieses Fenster wieder in Ordnung bringen kann. Das bedeutet also, daß der Inhalt des Fensters erstens immer wieder der entsprechende Fensterkoordinate angepaßt werden muß, falls das Fenster verschoben wurde, da die Koordinaten, wie eben erwähnt, absolute und nicht relative Koordinaten sind, und die Fensterinhalte bei Änderung des Bildschirms c teilweise neu gezeichnet werden müssen.

Abbildung 1: Möglichkeiten der Clippingbereiche

Zur Erklärung des Wortes 'teilweisee' schauen Sie sich bitte Bild 1 a an: Sie erkennen, daß das Fenster 1 von Fenster 2 stückweise überlappt wird. Das bedeutet, daß Fenster 1 rundherum und nicht vollständig gezeichnet werden muß. Dazu zeichnet man den Inhalt des Fensters mehrfach, aber immer nur auf einen bestimmten Bereich beschränkt. Diese Bereiche bezeichnet man als Clipping-Bereiche. Setzt man auf einen bestimmten Bereich ein Clipping, so werden alle Ausgaben (wie Linien, Kreise, Text etc.) auf diesen Bereich beschränkt. In Bild 1 sieht man Möglichkeiten wie Clipping-Bereiche eingeteilt sein können - es sei hier vorweggenommen, daß die Einteilung der Clipping-Bereiche dem GEM obliegt und dem zum Fenster gehörenden Programm mitteilt. Die Koordinaten der Clipping-Bereiche, die in X-, Y-Koordinate, Breite und Höhe übermittelt werden, können über die GEM-Routine wind_get() erfahren werden. Im Höchstfall kann es Vorkommen, daß vier Bereiche gezeichnet werden müssen. Dies geschieht in dem Fall, wenn ein Fenster wie in Bild 1 c überdeckt wird. Die Vorgehensweise: Nachdem das Programm eine REDRAW-Botschaft von GEM erhalten hat, reagiert das Programm damit, daß es sich die Clipping-Bereiche der einzelnen Fenster geben läßt und entsprechend der Anzahl der Clipping-Bereiche den Inhalt des Fensters zeichnet. Sind also drei Clipping-Bereiche vorhanden, so wird der Inhalt des Fensters dreimal gezeichnet -jedesmal mit einem anderen Clipping-Bereich. Zum Setzen des Clippingbereichs stellt GEM die Routine vs_clip() zur Verfügung. Wichtig ist zu wissen, daß die Definierung des Bereiches durch die Koordinaten der linken oberen und der rechten unteren Ecke und nicht durch Angabe der linken oberen Ecke mit Höhe und Breite gegeben ist. Da aber das GEM den Clipping-Bereich in letzter genannter Form übermittelt, müssen wir die Koordinaten erst umrechnen. Die Umrechnung erfolgt in der Routine set_clip() und wird daraufhin GEM in vs_clip() mitgeteilt.

Die Vorgehensweise soll nun am Quellcode von do_redraw() (Listing 1) erläutert werden. Die Ubergabeparameter sind die Koordinaten des Fensters, die man über wind_get (wi_handle, WF_WORKXYWH,...) übermittelt bekommt. Zunächst wird die Maus ausgeschaltet, da sie beim Zeichnen stören würde. Der nächste Schritt ist, dem GEM mitzuteilen, daß man den Bildschirm neu aufbauen möchte. Hiermit schaltet man die Funktion von GEM aus, die dem Programm mitteilt, daß der Bildschirm neu gezeichnet werden muß. Würde man diesen Befehl nicht verwenden, so sähe GEM das Neuaufbauen des Bildschirm wiederum als eine Verletzung des Bildschirminhaltes an, und...das Programm bekäme eine erneute REDRAW-Meldung. Als nächstes werden die Fensterkoordinaten in die zweite GRECT-Struktur (siehe ST-ECKE Mai) geschrieben, um sie für den späteren Vergleich in zusammengefaßter Form vorliegen zu haben. Nun muß das Programm die Clipping-Bereiche einlesen: (Die Vorgehensweise ist ähnlich wie in GEMDOS beim Einlesen der Directory, bei dem man zunächst den ersten Eintrag mit einer Funktion (FSFIRST) einliest und dann mit einer anderen (FSNEXT) die folgenden. Synonym dazu gibt es unter GEM die Funktion wind_get (wi_handle, WF_FIRST XYWH...) und wind_get (wi_handle, WF_NEXTXYWH...). In der 12. Zeile wird demnach der erste Clipping-Bereich eingelesen. Das Ende einer Clipping-Bereichsliste erkennt man daran, daß die Breite und Höhe eines Clipping-Bereiches immer ungleich Null sein müssen. Das bedeutet, daß man das Suchen von folgenden Bereichen in dem Moment abbrechen kann, wenn ein Bereich keine Breite und Höhe mehr hat, was auch in Zeile 13 geschieht. In der folgenden Zeile wird die schon im Mai (ST-Ecke) erklärte Funktion benutzt, um die Schnittmenge zwischen Fenster und Clippingbereich zu errechnen. Die Funktion gibt TRUE zurück, falls eine Überschneidung vorhanden ist und der Schnittbereich ist in der zweiten GRECT-Struktur vorhanden. Diesen Schnittbereich übergibt man der schon erwähnten set_clip()-Routine und man zeichnet seinen Fensterinhalt. Danach holt man sich den nächsten Clipping-Bereich. Diese Vorgehensweise wird solange durchgeführt, bis alle Clipping-Bereiche abgearbeitet worden sind. Danach wird der 'Update’ wieder eingeschaltet, um wieder REDRAW-Meldung von GEM zuzulassen, und die Maus aktiviert.

Soviel zur Fensterverwaltung in diesem Monat. Schließen wir unsere Fenster und wenden wir uns dem Menü zu - um genauer zu sein, wir wollen unsere Menüs (Menüleisten) etwas attraktiver gestalten.

Abbildung 2: Menüzeile, einmal anders

Ein interessanteres Menü

Viele werden sich schon gewundert haben, daß das RCS (Es wird hier auf das RCS Version 1.4 Bezug genommen) beim Gestalten von Menübäumen nur vier Objektarten (TITLE, ENTRY, — und BOX) für das Konstruieren von Menüleisten zur Verfügung stellt. Es ist aber möglich alle (!) Objektarten in Menüs unterzubringen. Was halten Sie davon, Icons in Menüs zu verwenden oder in einem DROP-DOWN-Menü zwei Einträge nebeneinander zu haben? Die Vorgehensweise ist eigentlich recht einfach; verwendet wird dabei das Clipboard des RCS.

Als Beispiel wollen wir uns mit der Einbindung von Icons in eine Menüleiste befassen. Zunächst öffnen Sie eine Dialogbox und postieren ein Icon in ihr. Dann laden Sie die Datenmenge eines Icons - Selektieren Sie dazu das Icon und wählen Sie im Menü den Punkt LOAD an. Danach nehmen Sie ihr Icon und schieben es auf das CLIPBOARD des RCS. Dieser Umweg in den Menübaum ist notwendig, da man Icons in einem Menübaum nicht laden kann! Nun machen Sie ihren Menübaum auf und fügen einen Eintrag in ein Drop-Down-Menü ein. Diesen Eintrag ziehen Sie so groß, daß das Icon hineinpaßt. Dann können Sie es in diesen Menüeintrag einfügen. Wichtig dabei ist, daß das Icon keine Berührung mit dem Rand des Eintrages hat, da bei Anwählen des Menüpunktes immer erst der Eintrag selbst angewählt werden soll und nicht das Icon, sonst wäre der Effekt, daß zwar das Icon, aber nicht der Eintrag selbst selektiert ist. Ein Beispiel für die Gestaltung einer Menüzeile mit Icons finden Sie in Bild 2. Um mehrere (auch andere) Objekte in einer Menüzeile unterzubringen, müssen Sie, genauso wie oben beschrieben, das Objekt über den Umweg des Clipboards in die Menüzeile bringen. Natürlich können Sie es dort beliebig oft kopieren. Auf diese Weise ist das rechte Drop-Down-Menü in Bild 2 entstanden. Diese Art der Menügestaltung ist dann zu empfehlen, wenn man viele Objekte in einem Drop-Down-Menü unterbringen muß, aber das Menü dadurch zu lang werden würde. Theoretisch ist es sogar möglich, selbstdefinierte Objekte in einer Menüzeile unterzubringen.

Eine kleine Anmerkung: Allgemein sollte man sich - so weit es geht - an die Standards der Menügestaltung halten, da eine 'verspielte' Menügestaltung eher von ihrem eigentlichen Sinn, ein einfaches und schnelles Anwählen von Befehlen, ablenkt. Mehr zu Standards der Menügestaltung und Möglichkeiten des Resource-Construction-Sets finden sich in unserem demnächst erscheinenden Sonderheft.

Das Ausführen von Tochterprozessen

Nun möchte ich mich ein wenig näher mit der Pexec-Routine beschäftigen. Über diese Routine des GEMDOS wurde schon mehrere Male geschrieben, aber leider wurde in den meisten Fällen nur auf die Option 0 (LOAD and GO) eingegangen. Die anderen Optionen wurden verschwiegen, wobei aber gerade Optionen 3 und 4 sehr interessant erscheinen, was ich auch an den vielen Leserzuschriften erkennen konnte.

Die Funktion Pexec des Gemdos ist eine Prozedur, die es ermöglicht, aus Ihrem eigenen Programm andere Programme aufzurufen. Dabei bietet der Aufruf ret = Pexec (modus, pfad, base, umg) durch Änderung des Modus folgende Möglichkeiten:

Modus Bedeutung

0 sogenannter LOAD ’n’ GO-Modus. Das aufgerufene Programm wird geladen und gleich gestartet.

3 Das Programm wird nur in den Speicher geladen.

4 Das im Speicher vorhandene Programm wird gestartet.

5 Die zu jedem Programm gehörige BASEPAGE wird angelegt, wobei Adressen der Daten- und Textsegmente nicht gesetzt werden.

In den nächstfolgenden Absatz nehme ich auf Listing 2 Bezug, das die Vorgehensweise beim Laden und gleichzeitigen Starten eines Programmes darlegt. Wie Sie aus der Parameterliste der Pexec-Routine entnehmen können, muß dazu der Modus 0 eingeschaltet werden. Als zweiten Parameter übergeben Sie nun den Pfad des Programmes, das geladen und gestartet werden soll. In unserem Programmbeispiel ist dies „programm.prg“. Als dritten Parameter muß man in diesem Modus die Kommandozeile übergeben. Die Kommandozeile entspricht den bei als TTP angemeldeten Programmen übergebenen Parametern. So ist es möglich, dem Tochterprozess beliebige Information mitzugeben. Wichtig dabei ist, daß der eigentliche Text erst bei String-Position 1 anfängt und nicht bei 0. Die Begründung ist darin zu suchen, daß in String-Position 0 die Länge des Strings zu finden ist. Daraus ergibt sich natürlich automatisch, daß ein Strings nicht länger als 255 Zeichen sein darf! Im Programmbeispiel werden nun der Text und die Länge in den Kommandostring kopiert und mit als Parameter an die Pexec-Routine übergeben. Der letzte Parameter enthält einen Zeiger auf die sogenannte Umgebung des Programms. Dort können zum Beispiel Suchpfade angegeben sein, die dem Tochterprozess mit übergeben werden sollen. Die Anwendung bleibt ganz allein dem Tochterprozess überlassen, der die Strings bei Bedarf verwenden kann. Nach Aufruf der Pexec-Routine wird der Tochterprozess vollständig abgearbeitet. Wird dieser beendet, so wird die Kontrolle wieder an den Start-Prozess, in diesem Fall unser Programm, zurückgegeben. Wichtig dabei sind folgende Dinge: Erstens kann der Tochterprozess wiederum Tochterprozesse ausführen. Als Beispiel möchte ich hier das DESKTOP anführen. Dieses ruft zum Beispiel als Tochterprozess eine SHELL auf, die wiederum als Tochterprozess einen Compiler aufruft usw. Zweitens muß leider darauf hingewiesen werden, daß der Speicherbereich des Programms nach dem Beenden des Tochterprozesses wieder freigegeben wird. Der Nachteil wird uns bei den folgenden beiden Modi bewußt.

#include <osbind.h>

char *pfad,	/*	Pfadname des zu ladenden Programms */
	 *base,	/*	Basepage - Programmdeskriptor */
	 *umg;	/*	Umgebung (Environment) */

char kommando[257];	/* Kommandostring */

/****************************************************************/
/*                                                              */
/* (long)ret = Pexec(int modus,char *pfad,char *base,char *umg) */
/*                                                              */
/****************************************************************/

main()
{
	int ret1;	/*	Rückgabewert des Tochterprozesses */

	appl_init();	/* wegen form_alert */

	pfad = "programm.prg";	/*	Pfadname des zu ladenden Programmes */

	strcpy(&kommando[1],"Übergabe");	/*	Übergabestring kopieren-*/
	kommando[0] = (char) strlen("*bergabe");	/*	Länge setzen */

	base = (char*) Pexec(3,pfad,kommando,01);	/*	Programm LADEN */

	if ( base < 01 )	/* Fehler beim Laden aufgetreten ? */
	{
		form_alert(1,"[1][Leider ist ein|Fehler aufgetreten.][ Abbruch ]"); 
		appl_exit();
		Pterm0();	/*	Programmabruch */
	}

	ret1 = Pexec(4,01,base,01);	/*	Programm ausführen */

	printf("Rückgabewerte des Prozesses: %d",ret1);

	Cnecin();	/*	Auf	Tastendruck	warten */

	appl_exit();
}

Listing 1

#include <osbind.h>

char *pfad,	/*	Pfadname des zu ladenden Programms */
	*base,	/*	Basepage - Programmdeskriptor */
	*umg;	/*	Umgebung (Environment) */

char kommando[257];	/* Kommandostring */

/****************************************************************/
/*                                                              */
/* (long)ret = Pexec(int modus,char *pfad,char *base,char *umg) */
/*                                                              */
/****************************************************************/

main()
{
	int ret1;	/*	Rückgabewert des Tochterprozesses */

	appl_init();	/* wegen form_alert */

	pfad = "programm.prg";	/*	Pfadname des zu ladenden Programmes */

	strcpy(&kommando[1],"Übergabe") ;	/*	Übergabestring kopieren */
	kommando[0] = (char) strlen("Übergabe");	/*	Länge setzen */

	base = (char*) Pexec(0,pfad,kommando,01);	/* Laden UND Starten */

	if ( base < 01 )	/*	Fehler beim Laden aufgetreten ? */
	{
		form_alert(1,"[1] [Leider ist ein|Fehler aufgetreten.][ Abbruch ]"); 
		appl_exit();
		Pterm0();	/*	Programmabruch */
	}

	printf("Rückgabewerte des Prozesses: %d",ret1);
	Cnecin();	/*	Auf	Tastendruck	warten */
	appl_exit();
}

Listing 2

Das Laden und Starten von Tochterprozessen

Interessant klingt das Laden und spätere Starten von Tochterprozessen, dem wir uns jetzt zuwenden wollen. Der Unterschied zu dem obigen Verfahren ist nur der, daß man sich den Rückgabeparameter der Pexec-Routine bei Modus 3 merkt. Er enthält einen Zeiger auf die sogenannte Basepage des Programms. Eine Basepage ist eine Struktur, die Zeiger auf Text- und Datensegment, deren Länge und noch einiges mehr enthält. Übergibt man nun diese Adresse der Pexec-Routine mit Modus 4, so kann man nachträglich dieses geladene Programm starten (siehe Listing 3). Hierbei sollte beachtet werden, daß bei Modus 4 der Pfadname natürlich nicht mehr berücksichtigt wird. Außerdem werden Kommandozeile und Umgebungsstrings bei Modus 3 übergeben und somit auch nicht bei Modus 4 berücksichtigt. Nun könnte man auf die interessante Idee kommen, mehrere Programme zu laden und je nach Bedarf aufzurufen, um eine Art von Accessories zu realisieren. Dies ist aber aus einem Grund leider so einfach nicht möglich: Jeder Prozessspeicherbereich wird nach Beendigung des Prozesses wieder freigegeben. Das bedeutet aber, das ein geladender Prozess nur einmal aufgerufen werden kann. Folglich ist es nicht möglich, eine einmal geladene Datenmenge mehrmals zu starten, sofern man nicht alle benötigten Daten vor dem Starten rettet. Sofern Sie an weiteren Informationen bezüglich Programm- und Speicherverwaltung des GEMDOS interessiert sind, möchte ich Sie ein zweites Mal auf das kommende Sonderheft aufmerksam machen, in dem sehr genau auf diese und andere Funktionen hingewiesen wird.

Dem Joystick auf die Kontakte gefühlt

In einer vorherigen Ausgabe der ST-Ecke wurde auf die Joystickabfrage näher eingegangen. Realisiert wurde sie damals für die Sprache C, und zwar mit Hilfe eines kleinen Inline-Assembler Programms. Will man diese Methode in Basic umsetzen, so kommen, zumindest für nicht Assembler Programmierer, einige Probleme auf. Aus diesem Grunde haben wir die Abfrage des ’Freudenknüppels’ in reinem Basic, genauer gesagt in OMIKRON-Basic, realisiert.

Das Prinzip ist recht einfach. Zuerst öffnet man einen Kanal zum Tastaturprozessor, was in OMIKRON Basic sehr einfach vonstatten geht, und schickt dem Tastaturprozessor einen Befehl, der ihm mitteilt, daß er nun automatisch die Joystickwerte senden soll. Anschließend liest man von gleichem Kanal zwei Bytes ein, in denen alle gewünschten Informationen enthalten sind. Das erste Byte gibt Aufschluß darüber, welches Joystick betätigt wurde. ’255’ entspricht dabei Joystick 1 und ’254’ Joystick 2. Enthält das erste Byte einen anderen Wert, so wurde die Tastatur bestätigt. Der Wert entspricht dann dem SCAN-Code, d. h. es können alle Tasten abgefragt werden (z. B. die Funktionstasten, HELP und UNDO). Wenn also das erste Byte ’255’ oder ’254’ betrug, so enthält das zweite Byte direkt die Richtung des betreffenden Joystick. Die Werte der entsprechenden Stellungen sind im Listing angegeben.

Die gesamte Abfrage wurde in einer Prozedur realisiert, wobei die Werte aufgrund der sauber gestalteten OMIKRON-Prozeduren - an das Hauptprogramm zurückgegeben werden. Die Umsetzung auf ST- und GFA-Basic ist prinzipiell natürlich möglich. OMIKRON-Basic hat aber einen sehr entscheidenden Vorteil: Beim Experimentieren mit dem Tastaturprozessor kommt es nicht selten zu Systemabstürzen, und der Rechner muß normalerweise neu gebootet werden. Da OMIKRON-Basic auf Modul enthalten ist, ist sowohl der Interpreter als auch das momentan bearbeitete Programm nach einem Reset noch vorhanden. Spätestens nach dem zweiten Reset wird man dies zu schätzen wissen.

Viel Spaß mit dieser ST-Ecke wünscht

(SH) + (HS)

' Joystick Abfrage 
' H. Schneider 
' OMIKRON Basic

REPEAT
	Joy(Nr.Xy)'	Aufruf
	IF Nr-255 THEN	'	Joystick 1?
		IF Xy-128 THEN PRINT TAB (40);"Bumm" ENDIF 
		PRINT "Joystick 1",Xy 
	ENDIF
	'
	IF Nr=254 THEN	'	Joystick 2?
		PRINT "Joystick 0",Xy
		IF Xy=128 THEN PRINT TAB (40);"Krach" ENDIF 
	ENDIF

	’ IF Nr<254 THEN PRINT TAB (40);Nr: ENDIF 
UNTIL Nr=57 '	Bei "SPACE" soll er aufhören
Maus_On ' Maus REAKTIVIEREN, sonst Essig
MOUSEON ' Zur Kontrolle Maus anzeigen
END

DEF PROC Joy(R Nr,R Xy) ' Die Werte zurückgeben
	OPEN "K",1 ' Tastaturprozessor "öffnen"
		PRINT #1, CHR$($14); ' automatische Joystickmeldung
		'
		'	&s	werden zwei Bytes gesendet
		'
		'	1	Byte Joystick	Nummer
		'	254 Joystick 0 (Maus)
		'	255 Joystick 1
		'	andere Codes entsprechen dem
		'	SCAN-Code	der	Tastatur
		'
		'
		'           Die gelieferten	Werte
		'           ---------------------
		'
		'                   oben
		'
		'             5       1      9
		'
		'      links  4       0      8   rechts
		'
		'             6       2     10
		'
		'                   unten
		'
		'
		Nr= ASC( INPUT$(1))' Byte 1 
		Xy= ASC( INPUT$(1))' Byte 2 
	CLOSE 1 
RETURN

DEF PROC Maus_On
	OPEN "K",1'	Tastaturprozessor	'öffnen'
	PRINT #1, CHR$($15); CHR$(8);' Joystickabfrage beenden + Maus ein 
	CLOSE 1 
RETURN

Listing 3



Aus: ST-Computer 06 / 1987, Seite 77

Links

Copyright-Bestimmungen: siehe Über diese Seite