GEM-Kurs Teil 2

Hier nun der zweite Teil von “GEM unter C“. Ich hoffe, Sie haben den ersten Teil gut verdaut, so daß ich auf das dort angelegte Grundwissen aufbauen kann.

Mit Erscheinen dieses Teils sollte es wohl möglich sein, den gesamten Quellcode - sowohl von FULLPLOT, als auch von BROWSER - über den Verlag zu erhalten.

Bevor es losgeht, noch ein paar Worte in eigener Sache: Die in dieser Artikelserie vorgestellten Programme sind alle bereits von einem erheblichen Umfang. Es ist klar, daß mit der Länge die Fehlermöglichkeiten gewaltig steigen.

Bei „normaler“ Benutzung sollte eigentlich nichts passieren. Sollte Ihnen trotzdem etwas auffallen, wäre ich Ihnen für eine entsprechende Mitteilung an mich oder die Redaktion sehr verbunden.

1. Die Idee

Auf fast jedem Rechner - und das sind einige, - an dem ich bis heute gearbeitet habe, habe ich ein Programm geschrieben, das es ermöglichte, sich die trockene Gestalt einer Funktion y = f(x) als mehr oder minder hübsche Kurve vom Computer zeichnen zu lassen.

Die erste Maschine, auf der ich das tat, war übrigens ein Großrechner der Firma Burroughs. Das Programm wurde über Lochkarten eingelesen und die Kurve kam als Ausdruck, bestehend aus Sternchen und Leerzeichen, aus einem 132 spaltigen Schnelldrucker. Das ist jetzt etwa 8 Jahre her, und ich erzähle Ihnen das, um zu zeigen, was für Fortschritte die „Computerei“ doch inzwischen gemacht hat. Wer hätte es sich damals träumen lassen, daß es einmal Rechner wie den ATARI ST geben würde, die man sich, zu einem erschwinglichen Preis, daheim auf den Schreibtisch stellen kann.

Mit den gewachsenen Ansprüchen der letzten acht Jahre im Rücken stellte ich mir folgenden Katalog an Fähigkeiten zusammen, die das Programm haben sollte:

  1. Es sollte auf jeden Fall möglich sein, die Funktion direkt ins laufende Programm einzugeben. Wenn man bei jeder Änderung alles neu übersetzen muß, wird das ganze schnell langweilig.
  2. Es sollte nicht nur die Visualisierung der Funktionsgleichung, sondern auch ihrer Ableitung und (einer) Integralkurve möglich sein.
  3. Ich wollte mehrere Funktionen gleichzeitig sehen können, um z. B. Funktion und Ableitung gegenüberstellen oder verschiedene Ausschnitte aus einer Kurve gleichzeitig sehen zu können.
  4. Es sollten verschiedene Methoden der Skalierung vorhanden sein.
  5. Um die Sache nicht zu sehr zu komplizieren, sollte nur der rein qualitative Verlauf einer Kurve gezeigt werden, also keine Achsenbeschriftung oder ähnliches.
Bild 1: Hardcopy vom Bildschirm

Es ist vielleicht nicht so offensichtlich, aber der Punkt 1 ist der am schwierigsten zu realisierende am ganzen Programm.

Von Interpretersprachen aus, wie z. B. BASIC, ist es relativ simpel, während des Programmlaufs sich beliebig ändernde arithmetische Ausdrücke einzugeben, da das Programm ja nicht in übersetzter, sondern in einer mehr oder weniger textuellen Form vorliegt. Man kann also dem Interpreter den Text meist irgendwie der neuen Funktion unterschieben und ihm die Arbeit der Auswertung überlassen.

Ganz anders sieht die Angelegenheit in Compilersprachen wie ’C’ aus. Wenn das Programm übersetzt ist, liegt es in der Sprache des jeweiligen Prozessors vor und ist eigentlich nicht mehr veränderbar. zur Lösung dieses Problems später noch ein paar Bemerkungen.

Punkt 2 ist relativ einfach, wenn man auf große Genauigkeit keinen Wert legt.

Punkt 3 schreit geradezu nach Fenstern und wurde in FUNPLOT auch so verwirklicht. Da einer Applikation nur 4 Fenster zur Verfügung stehen, mußte ich mich auf diese Zahl beschränken. Es ist also möglich, maximal 4 verschiedene Funktionen gleichzeitig zu betrachten.

Auf die restlichen Punkte werde ich bei der Besprechung des Programms näher eingehen.

Was aus den obigen Überlegungen am Ende geworden ist, können Sie in Bild 1 sehen. Ich habe noch ein paar Spielereien eingebaut, so kann man etwa bei der Darstellung zwischen dünnen und dicken Linien wählen.

Die Benutzung wird sich normalerweise so gestalten:

Y gleich X:
Der Y Bereich wir dem X-Bereich angepaßt.

Y-Intervall:
Der Benutzer kann ein beliebiges Intervall angeben, in dem die Werte dargestellt werden.

Optimal:
Das System errechnet das optimale Y-Intervall selbst, so daß die ganze Kurve zu sehen ist.

Bild 2: Die Module von FUNPLOT
/*
* PARSE.C *
* Version 1.0 vom 30.08.86
* von Thomas Weinstein
* geschrieben unter MEGAMAX C Entwicklungssystem
*
* Realisiert einen Parser fuer arithmetische Ausdruecke der Form
*
*            y = f (x,a,b,c,d)
*
* wobei a,b,c,d Konstanten sind und x die unabhaengige Veraenderliche
*
* Der Zerteiler arbeitet mit folgender Grammatik:
*
*        ZEILE         ::=    AUSDRUCK EOL.
*        AUSDRUCK      ::=    TERM MEHR_TEPME.
*
*        MEHR_TERME    ::=    + TERM MEHR_TERME |
*                             - TERM MEHR_TERME |
*                             EPSILON.
*
*        TERM          ::=    SFAKTOR MEHR_FAKTOREN.
*
*        MEHR_FAKTOREN ::=    * SFAKTOR MEHR_FAKTOREN |
*                             / SFAKTOR MEHR_FAKTOREN |
*                             EPSILON.
*
*        SFAKTOR       ::=    + FAKTOR |
*                             - FAKTOR |
*                               FAKTOR.
*
*        FAKTOR        ::=    BEZEICHNER     |
*                             ZAHL           |
*                             (AUSDRUCK)     |
*                             FUN(AUSDRUCK).
*
* Die folgenden Produktionen werden von ’lexscan’ verarbeitet:
*
*        BEZEICHNER    ::=    ’X’ | ’A’ | ’B’ | ’C’ | ’D’.
*
*        ZAHL          ::=    GANZZAHL | GANZZAHL | GANZZAHL'.’GANZZAHL,
*
*        GANZZAHL      ::=    ZIFFER | ZIFFER GANZZAHL.
*
*        FUN           ::=    ’SIN’ | 'COS’ | ’TAN’ | ’ATAN’ | ’EXP’ |
*                             ’LOG’ | ’SQRT’| ’INT’ | ’ABS’ .
*/

Listing 4

2. Das Programm

Jetzt aber zum Programm. In Bild 2 sehen Sie die Aufteilung des ganzen in einzelne Module. Das Hauptmodul FUNPLOT ist in Listing 1 in voller Länge abgedruckt, da es eigentlich lauter Dinge enthält, die für die GEM Programmierung wichtig sind.

INIGEM ist dasselbe wie in der letzten Folge und im Teil 3 des C Kurses. Es übernimmt, wie üblich, solche Dinge wie Öffnen und Schließen von Workstations etc.

PLOT enthält die Funktionen zum Zeichnen der Kurve. Es soll hier nicht näher betrachtet werden, da es außer ein paar einfachen VDI Aufrufen nichts Aufregendes hergibt.

MENU enthält alle Funktionen zur Interaktion über die Menüleiste. Ich werde später noch detaillierter darauf eingehen.

Das Modul DIALOG managt die Interaktion mit den verschiedenen Dialogboxen, da sie zusammen mit den Menüs das Hauptthema dieser Folge darstellen, werde ich später noch intensiv darauf eingehen.

Von Dialog aus wird der Parser aufgerufen. Er versteckt sich im Modul PARSE und stellt die Mechanismen bereit, die notwendig sind, um solche Monstren wie

	sin(x + cos(sqrt(abs(x * x +10)))

zu verarbeiten. Leider paßt der Parser nicht zum Thema des Kurses, so daß ich an dieser Stelle nicht weiter darauf eingehen werde. Sollte genügend Interesse im Leserkreis vorhanden sein, könnte zu diesem Thema gelegentlich ein eigener Artikel in der ST Computer erscheinen.

Wer sich selbst einmal an der Thematik versuchen will, findet im berühmten „Drachenbuch“ von Aho, Sethi, Ullmann (Compilers, Principles, Tech-niques and Tools, Addison Wesley) viele Anregungen.

In Listing 4 habe ich die kontextfreie Grammatik, nach der ich die eingegebenen Funktionen abarbeite, angegeben. Aus ihr können Sie direkt ablesen, welche Ausdrücke das Programm verarbeiten kann. Dieser Teil des Programms läßt sich sehr leicht erweitern; besorgen Sie sich einfach den Quelltext und schauen Sie sich an, wie ich es gemacht habe.

/***************************************************/
/* ATARI ST Funktionenplotter V1.0       05.09.86  */
/*                                                 */
/* geschrieben von Thomas Weinstein                */
/*                 Koenigsberger Str. 29d          */
/*                 7500 Karlsruhe 1                */
/*                                                 */
/* MODULE: FUNPLOT.C                               */
/*                                                 */
/***************************************************/

/***************************************************/
/* INCLUDE FILES                                   */
/***************************************************/

#include <define.h> 
#include <obdefs.h>
#include <gemdeFs.h>
#include "Funplot.h"
#include "globals.h"

#ifdef MEGAMAX /* Ist bei mir in define.h vereinbart */
over1ay "FUNPLOT"
#endif

/***************************************************/
/* DEFINES                                         */
/***************************************************/

#define MIN_WIDTH (15 * gl_wchar)
#define MIN_HEIGHT (5 * g1_hchar)
#define WI_KIND (CLOSER|NAME|SIZER|MOVER)

/***************************************************/

extern int gl_apid;
extern int handle;
extern int pnys_handle;

/***************************************************/
/* GLOBAL VARIABLES                                */
/***************************************************/

/***********************************************/
/* Globale Variable Fuer virtuelle Workstation */
/***********************************************/

int gl_hchar; 
int gl_wchar;
int gl_wbox;
int gl_hbox; /* System sizes */
int xwork,ywork,wwork,hwork; /* Grossst mögliche Workarea */

/***************************************************/
/* Globale Variable -fuer Event Handling           */
/***************************************************/

int msgbuff[8];			/* Ereignispuffer */
int mx,my;				/* Maus x and y Position. */
int butdown;			/* Zustand der Mausknoepfe */
int ret; 				/* Dummy variable */
int quit; 				/* Wenn True wird Fontedit beendet */

/**********************************************/
/* Globale Variable Fuer Objektverwaltung     */ 
/**********************************************/

OBJECT *menuaddr;		/* Adresse des Menueleistenbaums */
OBJECT *infoaddr;		/* Adresse des Deskinfo Dialogs */
OBJECT *funinaddr;		/* Adresse des Funin Dialogs */
OBJECT *xnewaddr;		/* Adresse des Xinter Dialogs */
OBJECT *scaleaddr;		/* Adresse des Scale Dialogs */

/**********************************************/
/* Globale Variable Fuer Fensterverwaltung    */ 
/**********************************************/

int act_window;			/* Aktives Fenster */
WI_DESC windows[4];		/* Datenstru.ktur zur Fensterverwaltung */
char act_function[80];	/* Ueber Dialogbox eingestellt Funktion */

POINT points[4][MAXVALS]; /* Punkteliste zu jedem Fenster der Einfachheit */
						/* halber statisch angelegt. */

Es bleiben noch die beiden Module CALC und GETVAL. CALC berechnet die eigentlichen Funktionswerte und bedient sich dabei des Moduls GETVAL, das eine sogenannte virtuelle Stackmaschine realisiert, die den durch PARSE gewonnenen Postfixausdruck abarbeitet.

2.1 Die Fensterverwaltung

Nun in die Details. In FUNPLOT finden Sie die Teile wieder, die wir beim letzten Mal schon ausführlich besprochen haben. Die main() Funktion, die alles initialisiert und die multi() Schleife, in der das Programm dann bis zum Schluß läuft und auf Ereignisse wartet.

Was neu ist, ist die Verwendung von vier Fenstern statt nur einem, wie im Browser Programm. Um mehrere Fenster in den Griff zu bekommen, sollte man sich eine geeignete Datenstruktur überlegen, die die Verwaltung möglichst einfach macht. Schauen Sie sich dazu bitte Listing 2 an. Es enthält außer einigen wichtigen Konstantendefinitionen die Definitionen für zwei wichtige Structs, die Sie im ganzen Programm überall wiederfinden werden. Ganz grundlegend zuerst der struct POINT. Er steht für einen Punkt in der X-Y-Ebene.

Wichtiger ist aber der Typ WI_DESC. In dieser Struktur werden alle Informationen zu einem Fenster zusammengefaßt. An erster Stelle steht die Ihnen schon wohlbekannte Window Handle, die das jeweilige Fenster identifiziert. Dann folgen mit xw, yw, ww, hw die Abmessungen des Arbeitsbereiches des Fensters. points zeigt entweder auf ein Array von Koordinaten, steht also für die dem Fenster aktuell zugeordnete Funktion, oder hat den Wert Null. Dieser Wert steht für die Information, daß dem Fenster momentan keine Funktion zugeordnet ist. Durch das Aufbewahren der Funktionswerte kann man sich die fortwährende Neuberechnung sparen.

Außerdem enthält WI_DESC noch Angaben zum eingestellten Skalierungsmodus und, falls der Modus BESTFIT eingestellt ist, den minimalen und maximalen Y-Wert. Im Array Title wird die Fenstertitelzeile gehalten.

Da wir vier Fenster verwalten wollen, legen wir uns ein Array aus vier solcher Strukturen an, es trägt den Namen windows[].

Diese Datenstruktur wird am Anfang auf Defaultwerte initialisiert - Listing 1, Zeile 177, Funktion init_wi_desc() - und dann an allen Stellen, an denen das Fenster verändert wird, auf den neuesten Stand gebracht. Die Information wird dann von der Funktion plot_function() benutzt, um die Kurve an die richtige Stelle des Bildschirms zu zeichnen.

Die Verwaltung der Fenster, was Vergrößern, Verkleinern, Verschieben oder Redraw betrifft, ist die gleiche wie im Browser Beispiel. Der einzige Unterschied ist der, daß man jetzt beim Auftreten eines Ereignisses (s. multi()) zuerst feststellen muß, welches der vier möglichen Fenster der Auslöser ist. Alles andere, was ich Ihnen im Teil 1 über die Rechtecklisten und das Neuzeichnen der Fenster gesagt habe, gilt hier natürlich ebenso.

2.2 Die Resourcenverwaltung

Das Konzept der Resourcen stellt ein wichtiges Hilfsmittel in GEM dar. Mein Englischwörterbuch übersetzt Resource mit Mittel oder Hilfsmittel. Und das sind sie auch, nämlich Hilfsmittel, die GEM dem Anwendungsprogrammierer zur Verfügung stellt um ihm die Interakton mit dem späteren Benutzer des Programms zu ermöglichen. Die bekanntesten Resourcen sind die Menüleiste, die Dialogboxen und die Alarmboxen.

Wie stellen sich die Resourcen für den Programmierer dar? In der Dokumentation zu GEM werden sie als Bäume (trees) bezeichnet. In Wirklichkeit sind es jedoch gerichtete Graphen, was aber im Moment nur von untergeordneter Bedeutung ist, daher werde ich sie weiterhin als Bäume bezeichnen. Falls Sie sich nichts unter Bäumen vorstellen können (zumindest im Zusammenhang mit Computern) sollten Sie sich an den Teil 4 meines C Kurses erinnern, in dem ich für das Crossreferenzprogramm ebenfalls einen Baum aufgebaut habe.

Bei GEM besteht ein solcher Baum aus Objekten, die untereinander verzeigert sind. Als Beispiel sehen wir uns die Dialogbox zur Funktionseingabe an (Bild 3). Die Wurzel, also das oberste Objekt des Baumes ist die Dialogbox selbst, das heißt der Rahmen, der um das ganze Geschehen gezogen wird. Von der Dialogbox aus gehen jetzt Verweise auf weitere Objekte, z. B. auf Strings („FUNKTIONSEINGABE“) oder Buttons („Alles in Ordnung“, „Nichts ändern“) oder editierbare Textfelder wie etwa das Feld, in dem der Benutzer die neue Funktion eingibt. Der Gag an der Sache ist jetzt der: Ein Objekt ist programmintern eine C Struktur (Sie wissen ja, daß GEM in C geschrieben wurde) und kann daher mit den üblichen Methoden für Strukturen bearbeitet werden.

Jedes Objekt enthält Angaben über seine Art (Box, String, Button...) und über die relative Lage zum übergeordneten Objekt. Diese Lage wird einmal durch Koordinaten ausgedrückt, di'e relativ zur linken oberen Ecke des übergeordneten Objekts angegeben sind, andererseits durch die Verzeigerung der Objekte untereinander. Durch diese Relativbeziehung zwischen den Objekten ist es sehr leicht möglich, Objekte zu verschieben. Man muß nur die Koordinaten des obersten Objektes ändern, die übrigens auch relativ sind, nämlich zur linken oberen Ecke des Bildschirms, und automatisch verschieben sich alle anderen untergeordneten Objekte mit. Je nach Art des Objekts gehen von der Objektdatenstruktur Zeiger auf weitere Strukturen aus.

Bei einem editierbaren Textfeld gibt es z. B. einen Zeiger auf eine sogenannte TEDINFO Struktur in der Angaben über den Text, seine Länge, erlaubte Eingabezeichen etc. stehen. Ist das Objekt ein ICON, gibt es einen Zeiger auf eine ICONBLK Struktur, in der das Bitmuster des Icons und seiner Maske steht.

In der nächsten Folge des GEM Kurses will ich ganz detailliert auf die Beeinflussungsmöglichkeiten eingehen, die man durch Zugriff auf diese internen Strukturen hat.

In dieser Folge werden wir aber die Dienste des Resource Construction Sets (RCS) benutzen, der es uns erlaubt, die Resourcen interaktiv zu erzeugen und der die ganze Koordinatenrechnerei übernimmt.

Der RCS erzeugt eine Resourcedatei, in der die oben geschilderten Bäume und Objekte in einer geräte-unabhängigen Darstellung stehen. Die Aufgabe des Programmierers ist es nun, mit Hilfe der Funktion rsrc_load() diese Datei zu laden. Sie finden diese Stelle in Listing 1 ab Zeile 254. rsc_load() reserviert Platz für die Resourcedaten, lädt die Datei und nimmt alle Umrechnungen von Einheiten und Adressen vor, die notwendig sind. Der rsrc_load() Aufruf liefert 0 zurück, falls ein Fehler aufgetreten ist.

Wenn rsrc_load() erfolgreich zurückkehrt, ist die Resourcedatei fest im Programm verankert und kann benutzt werden. Der nächste Schrit ist, sich die Adressen der Objektbäume zu besorgen. Dies übernimmt die Funktion rsrc_gaddr().

Es ist übrigens ein grober Fehler und überdies schlechter Programmierstil, den Datentyp der Objektadressen als long anzugeben. Der korrekte Typ ist OBJECT ★ und normalerweise in ob-defs.h vereinbart. Durch die Vereinbarung eines long-Werts gibt man viele Beeinflussungsmöglichkeiten auf.

Als Beispiel schauen wir uns das Holen der Adresse zum Funktionseingabedialog an. Zuerst wurde in Zeile 71 von FUNPLOT folgende Vereinbarung getroffen:

OBJECT ★ funinaddr;

Damit hat man eine Variable vereinbart, die einen Zeiger auf ein Object vom Typ OBJECT speichern kann. Hinter OBJECT versteckt sich natürlich genau die Datenstruktur, die ich weiter oben schon einige Male angesprochen habe.

Jetzt können wir dieser Variablen in Zeile 263 die Adresse des Dialogbaumes zuweisen. Dies ist natürlich genau die Adresse des obersten Objektes in der Dialogbox, nämlich des umrahmenden Rechtecks:

rsrc_gaddr(R_TREE,FUNIN,&funinaddr);

R_TREE ist eine Konstante, die wiederum in obdefs.h vereinbart ist und der Funktion sagt, daß sie die Adresse eines Baumes liefern soll. Die nächste Konstante FUNIN wurde vom RCS in der Datei funplot.h angelegt und ist der Index in das Array aller Baumadressen.

Klar dürfte sein, daß wir die Adresse der Variablen funinaddr übergeben müssen. Sie erinnern sich sicher an die Grundregel: Wenn eine Variable in einer Funktion geändert werden soll, muß man ihre Adresse als Parameter übergeben.

Nachdem wir die Resource geladen und uns die Adressen der einzelnen Bäume geholt haben, müssen wir jetzt nur noch wissen, wie man den Dialog selbst programmiert.

Wir werden uns wieder die Funktionseingabe als Beispiel ansehen. Schauen Sie sich dazu zuerst einmal in Listing 3 die Zeilen 49-89 an. Die Funktion, die den Dialog managt, heißt funin_dialog().

Zuerst wird eine Funktion hndl_dialog() aufgerufen, in der ich alles, was zum eigentlichen Dialog gehört, verborgen habe - Näheres gleich. Diese Funktion liefert als Ergebnis zurück, welcher der Exit-Buttons betätigt wurde (OK oder CANCEL). Die Exit-Buttons stellen die einzige Möglichkeit dar, einen Dialog wieder zu verlassen. Solange kein Exit-Button angeklickt wurde, kann der Benutzer beliebig in der Dialogbox herumändern und editieren.

Einer der Exit-Buttons kann als Default deklariert werden (natürlich alles im RCS) und wird dann beim Drücken der Return-Taste ausgelöst. Einen Default Exit-Button erkennt man übrigens daran, daß er einen breiteren Rand hat.

Im funin_dialog() müssen nun die beiden möglichen Exit_Buttons abgehandelt werden. Wenn der Benutzer den „Alles in Ordnung“ Button angeklickt hat, müssen die geänderten Werte aus der Objektdatenstruktur herausgeholt und dem Programm bekannt gemacht werden. Hat er andrerseits den „Nichts ändern“ Knopf betätigt, müssen die eventuell geänderten Werte wieder in den alten Zustand versetzt werden. Aus Platzmangel werde ich nur den Fall, daß eine geänderte Funktionseingabezeile geholt werden muß, näher behandeln, alle anderen Fälle laufen entsprechend ab.

In funin_dialog() wird in Zeile 69 folgender Aufruf gemacht:

	get_string(funinaddr,FUNDEF,function);

Funinaddr ist, wie Sie ja schon wissen, die Adresse des Objektbaumes, der den Funktionseingabedialog darstellt. FUNDEF ist der Index des Objektes, das für das editierbare Textfeld mit der Funktionszeile steht. Function ist ein Rückgabeparameter, in dem Funktionsstring übergeben wird.

In den Zeilen 93 -108 können Sie sehen, wie get_string() realisiert wurde. Da die Eingabezeile ein editierbares Textfeld ist, zeigt das Feld ob_spec in der Objektdatenstruktur der Eingabezeile auf eine schon oben erwähnte TEDINFO Struktur, die ihrerseits im Feld te_ptext die neue Funktionszeile als String enthält. Man muß sich also zweistufig an die gewünschte Information herantasten.

text_desc = (TEDINFO *) funinaddr[item].ob_spec;

text_desc zeigt nun auf die TEDINFO Struktur der Eingabezeile.

text = text_desc—te_ptext;

Damit zeigt text auf den gesuchten String, der mit strcpy() in den Rückgabeparameter befördert wird.

Beim Zurückschreiben eines alten Wertes passiert im Prinzip das Gleiche, bloß rückwärts. Sie können sich selbst anschauen wie in den Zeilen 110-123.

Nun noch ein kurzer Blick auf die Funktion hndl_dialog(). In ihr ist das ganze notwendige Drumherum des eigentlichen Dialogs versteckt. Sie können die Funktion für eigene Anwendungen ungeändert übernehmen. Nur wenn Sie ganz spezielle Dinge tun wollen, kann es notwendig sein, etwas zu ändern.

Die Dialogboxen sind normalerweise ganz in die linke obere Ecke des Bildschirms verschoben. Da man den Dialog aber eigentlich immer in der Mitte haben will, muß man die Funktion form_center() bemühen, die einem die Koordinaten entsprechend umrechnet. Achten Sie wieder darauf, die Adressen der Variablen zu übergeben.

Als Nächstes folgt die Funktion **form_dial()v. Sie wird während des ganzen Prozesses viermal aufgerufen und tut ganz verschiedene Dinge, je nachdem, welchen Wert der erste Parameter hat. Beim Aufruf mit FMD_START wird der zum Darstellen des Dialogs nötige Platz „gemerkt“. Allerdings wird der Platz nicht gerettet, wie man aus manchen Büchern entnehmen kann.

Beim zweiten Aufruf mit FMD_GROW wird ein immer größer werdendes Rechteck gezeichnet, also ein rein optisch-ästhetischer Effekt.

Jetzt ist alles bereit, den Dialog tatsächlich zu zeichnen. Das übernimmt die Funktion objc_draw(). Es folgt form_do(). Hier erfolgt jetzt der eigentliche Dialog. Die Funktion erhält als ersten Parameter die Adresse des Dialogbaums und als zweiten den Index des ersten editierbaren Textfeldes. Es ist wichtig, daß es wirklich das erste ist, sonst gibt’s CHAOS. Gibt es keine editierbaren Textfelder im Dialog, wird hier eine Null übergeben.

Form_do() liefert die Exit Condition zurück, und die Aufräumarbeiten macht jetzt wieder form_dial(). Zuerst wird mit FMD_SHRINK ein schrumpfendes Rechteck gemalt und dann mit FMD_FINISH die Restaurierung des Bildschirms eingeleitet. Und hier liegt der Hase im Pfeffer: Entgegen verschiedener Spekulationen wird von form_dial() der Bildschirm nicht neu gezeichnet. Alles, was form_do() tut, ist, eine Redraw-Nachricht an den Desktopmanager und die Applikation zu schicken. Wenn Ihr Programm nun Redraw-Nachrichten ignoriert, weil Sie vielleicht gar keine Fenster offen haben, hat der Bildschirm nachher an Stelle der Dialogbox ein Loch. In solchen Fällen müssen Sie sich also explizit um die Wiederherstellung des Bildschirms kümmern.

Bild 3: Dialogbox zur Funktionseingabe

Ein wirksamer Trick ist es, unter der Dialogbox ein Fenster aufzumachen oder mit einem Blocktransfer das gefährdete Gebiet zu retten und nachher wieder zurückzuschreiben. Sie können in manchen Fällen auch einfach explizit das zerstörte Gebiet neu zeichnen, falls es nicht zu lange dauert und Sie in der jeweiligen Situation überhaupt noch wissen, was dort war. Falls Sie die letztere Methode wählen, sollten Sie das neu zu zeichnende Gebiet etwa 3 Punkt größer wählen als die Dialogbox war, da sonst ein Rand stehen bleibt.

2.3 Die Menüleiste

Bei der Menüleiste kann ich mich sehr kurz fassen. Zu diesem Thema hat Dirk Owerfeldt in der Oktoberausgabe der ST Computer schon alles gesagt, was es zu sagen gibt. Kritik kann man nur daran üben, daß er die Adressen seiner Objektbäume als long vereinbart (s. o.), andererseits stört es in seinem Programm nicht sonderlich, da die Adressen - außer zum Zeichnen der Boxen - sowieso nicht weiter verwendet werden.

In FUNPLOT können Sie sehen, daß man bei größeren Programmen aus Gründen der Übersichtlichkeit am besten das ganze Menühandling in ein separates Modul packt und in der multi() Schleife nach Erhalt der MN_SELECTED Nachricht nur noch eine entsprechende Funktion aufruft. Ansonsten werden fast alle Möglichkeiten, die die Menüleiste bietet, verwendet, so daß Sie sich die Einzelheiten im Modul MENU noch einmal ansehen können. Leider ist hier nicht genug Platz, um das Modul abzudrucken, so daß ich Sie nochmals auf den Verlag verweisen muß, der den gesamten (ziemlich ausführlich kommentierten) Quelltext auf der Monatsdiskette vorliegen hat.

In der nächsten Folge will ich Ihnen zeigen, wie man sich eigene Objekte kreieren und verwalten kann.

Und nun viel Spaß (engl. ~ FUN) beim plotten von Funktionen.

*************************************/
/* Globale Variable fuer Plot        */
/*************************************/

int scalemode = BESTFIT; /* Art der Skalierung */
int show_what = FUNCTION; /* Was wird gezeigt (FUNK, ABL, INTEGRAL) */

float xfrom = -3.0,		/* Defaultbereiche in X- und Y-Richtung */
      yfrom = -3.0, 
      xto   = 3.0,
      yto   = 3.0;
						/* Kleinster resp. groesster Y Wert */

float ymin, ymax;

float a,b,c,d; 

char function[41];

int width = 1; 			/* Liniendicke */

/**********************************************/ 
/* Open Windows                               */
/*                                            */
/* Oeffnet Fenster ’index’ (0 - 3)            */
/**********************************************/ 
open_window (index)
{
	int hndl; 
	int x,y,w,h;

	x = windows[index].xw; 
	y = windows[index].yw; 
	w = windows[index].ww; 
	h = windows[index].hw;

	if ((hndl = windows[index],wi_hndl =
			wind_create(WI_KIND,xwork,ywork,wwork,hwork)) < 0)
	{
		form_alert (1, "[1][Kein Fenster mehriverfügbar] [  ]"); 
		return;
	}
	
	wind_set(hndl, WF_NAME,act_function,0,0);

	hide_mouse();
	wind_open(hndl,x,y,w,h);
	wind_get(hndl, WF_WORKXYWH, &x, &y, &w, &h);

	windows[index].xw = x;
	windows[index].yw = y;
	Windows[index].ww = w;
	windows[index].hw = h;
	windows[index].points = NULL; /* NULL bedeutet: Diesem Fenster ist im */
							/* Moment keine Funktion zugeordnet. */

	clear_area(x,y,w,h); /* Arbeitsbereich loeschen */

	show_mouse();

} /* open_window() */

/***************************************************/
/* find and redraw all clipping rectangles         */
/***************************************************/
do_redraw(wi_h,xc, yc,wc,hc) 
int wi_h,xc,yc,wc,hc;
{
GRECT tl,t2;

	hide_mouse();
	wind_update(TRUE);
	t2.g_x=xc;
	t2.g_y=yc;
	t2.g_w=wc;
	t2.g_h=hc;
	wind_get (wi_h,WF_FIRSTXYWH,&t1.g_x,&t1.g_y,&t1.g_w,&t1.g_h); 
	while (tl.g_w && tl.g_h)
	{
		if (rc_intersect (&t2,&t1))
		{
			set_c1ip(t1.g_x,t1.g_y,t1.g_w,t1.g_h); 
			redraw(wi_h,t1.g_x,t1.g_y,t1.g_w,t1.g_h);
		}
		wind_get (wi_h, WF_NEXTXYWH,&t1.g_x,&t1.g_y,&t1.g_w,&t1.g_h);
	}
	wind_update(FALSE); 
	show_mouse();
} /* do_redraw */

/*
 * Initialisieren der Fensterdatenstruktur 
 */

init_wi_desc()
{
	int i;

	for (i = 0; i < 4; i++) 
		init_window(i);

}

/*
 * Initialisiert das Fensterobjekt i 
 */
init_.window (i) 
int i;
{
	windows[i].wi_hndl = NO_WINDOW;
	windows[i].points = NULL;
	windows[i].xw = xwork + (i % 2) * wwork/2;
	windows[i].yw = ywork + (i > 1) * hwork/2;
	windows[i].ww = wwork/2;
	windows[i].hw = hwork/2;
	windows[i].scalemode = BESTFIT;
}

/*
 * Holt den Index der zu hndl gehoerigen Datenstruktur.
 * Falls nicht existent wird -1 geliefert.
 */

int get_index(hndl) 
int hndl;
{
	int i;
	for (i = 0; i < 4; i++)
		if (windows[i].wi_hndl == hndl) break;

	return((i == 4) ? -1 : i);
}

/*
 * Beendet Programm 
 */
ex_app ()
{
	close_all_windows(); 
	v_clsvwk(handle); 
	appl_exit();
} /* ex_app() */

/*
 * Schliesst alle offenen Fenster 
 */
close_all_windows() 
{
	int i;

	for (i =0; i < 4; i++)
		if (windows[i].wi_hndl != NO_WINDOW)
		{
			wind_close(windows[i].wi_hndl); 
			wind_delete(windows[i].wi_hndl); 
			windows[i].wi_hndl = NO_WINDOW; 
			windows[i].points = NULL;
		}
}

/*************************************************************/
/*     FUNPLOT INITIALISIERUNG BIS ZUM ERSTEN MULTI AUFRUF   */
/*************************************************************/

main ()
{

	appl_init();
	phys_handle=graf _handle(&gl_wchar, &gl_hchar, &gl_wbox, &gl_hbox); 
	wind_get(0, WF_WORKXYWH, &xwork, &ywork, &wwork, &hwork); 
	open_vwork();

	if (!rsrc_load("funplot.rsc"))
	{
		form_alert(1,"[1][Kann RSC File nicht finden][ABBRUCH]"); close_vwork(); 
		appl_exit();
	}

	rsrc_gaddr(R_TREE, MENU, &menuaddr); /* Initialisieren der */
	rsrc_gaddr(R_TREE,INFO, &infoaddr);	/* Objektadressen */
	rsrc_gaddr(R_TREE, FUNIN, &funinaddr ); 
	rsrc_gaddr(R_TREE, XINTER, &xnewaddr); 
	rsrc_gaddr(R_TREE, SCALING, &scaleaddr);

	/* Dieser kleine Trick sorgt dafür, dass der Cursor bei der */
	/* ersten Funktionseir.gabe ganz links steht.               */
	get_string(funinadr, FUNDEF, function);
	sprintf(function, "@040                             ");
	set_string(funinaddr,FUNDEF,Function);

	act_function[0] = '\0'; 
	function[0] = '\0';
	
	init_wi_desc();

	menu_bar(menuaddr,1);	/* Anzeigen der Menueleiste */

	graf_mouse(ARROW,0x0L):	/* Maus auf Pfeil umschalten */

	butdown=TRUE; 
	
	multi();
}


/*************************************************************/
/* Koordiniert alle Benutzeraktionen                         */
/*************************************************************/
multi()
{
	int event, no_c1icks, old;
	int i, x,y,w,h; 
	int hndl;

	do
	{
		event = evnt_multi (MU_MESAG|MU_BUTTON,
							3,1,butdown,
							0,0,0,0,0,
							0,0,0,0,0,	msgbuff,0,0,&mx,&my,&ret,&ret,&ret,&no_clicks);
		wind_update(TRUE);

		if (event & MU_MESAG) 
			switch (msgbuff[0])
			{
				case MN_SELECTED:
					hndl_menu(msgbuff[3],msgbuff[4]); 
					menu_tnormal(menuaddr,msgbuff[3],1);
				break;

				case WM_REDRAW:
					do_redraw (msgbuff[3], msgbuff[4],
							msgbuff[5],msgbuff[6],msgbuff[7]);
				break;

				case WM_NEWTOP:
					if ((i = get_index(msgbuff[3])) != -1)
					{
						wind_set (msgbuff[3], WF_TOP,0,0,0,0);
						act_window = i; 
						windows[i].points = NULL; 
						plot_function(i);
					}
				break;

				case WM_TOPPED:
					if ((i = get_index(msgbuff[3])) != -1)
					{
						wind_set(msgbuff[3],WF_TOP,0,0,0,0); 
						act_window = i;
					}
				break;

				case WM_SIZED: 
				case WM_MOVED:
					if ((i = get_index(msgbuff[3])) != -1)
					{
						hndl = windows[i].wi_hndl;

						/* Ist Veraenderung zulaessig ? */
						if(msgbuff[6]<MIN_WIDTH) msgbuff[6] = MIN_WIDTH;
						if(msgbuff[7]<MIN_HEIGHT) msgbuff[7] = MIN_HEIGHT;
	
						/* Setze neue Fenstergroesse/position */ 
						wind_set(hndl, WF_CURRXYWH, msgbuff[4], msgbuff [5], msgbuff[6],msgbuff[7]);

						/* Groesse des neuen Arbeitsbereichs teststellen */ 
						wind_get(hndl, WF_WORKXYWH, &x,&y,&w,&h);

						windows[i].xw = y; 
						windows[i].yw = y; 
						windows[i].ww = w; 
						windows[i].hw = h;
					}
				break;

				case WM_CLOSED:
					if < (i = get_index (msgbuff[3])) != -1)
					{
						wind_close(msgbuff[3]);
						wind_delete(msgbuff[3]); 
						windows[i].wi_hndl = NO_WINDOW;
					}
				break;
			} /* switch (msgbuff[0]) */

			wind_update(FALSE);

			if (event & MU_BUTTON) 
				if (no_clicks == 2)
				{
					if (hndl = wind_find(mx,my))
					{
						i = get_index(hndl); 
						windows[i].points = NULL; 
						clear_area(windows[i].xw, 
									windows[i].yw, 
									windows[i].ww, 
									windows[i].hw); 
						plot_function(i);
					}
				}
				else
				if (no_clicks > 2) /* Kleiner Scherz am Rande */ 
						message("Nur nicht so stürmisch !");
	} while( !quit );

	ex_app();
}	/* multi */

/*
 * Zeichnet sichtbaren Arfceitsbereich von Fenster mit hndl neu.
 */
redraw(hndl,x,y,w,h) 
int x,y,w,h;
{
	int i;

	if ((i = get_index(hndl)) != -1)
	{
		clear_area(x,y,w,h); 
		plot_function(i);
	}
}

/*
 * Loescht rechteckigen Bi1dschirmbereich 
 */
clear_area(x,y,w,h) 
int x,y,w,h;
{
	int pxy[4];

	pxy[O] = x;
	pxy[1] = y;
	pxy[23 = x + w - 1;
	pxy[33 = y + h - 1;

	hide_mouse(); 
	vsf_color(handle,0); 
	set_clip(x,y,w,h); 
	v_bar(handle,pxy); 
	show_mouse());

} /* clear_area */

/*
 *	Eigentliche Zeichenfunktion. Veranlasst alles um Kurve im Fenster
 *	darzustellen.
 */

plot_function(index) 
int index;
{
	int i,x,y,w,h,s; 
	char *what = "";
	POINT *p;

	if (*function == '\0') return;

	x	=	windows[index].xw;
	y	=	windows[index].yw;
	w	=	windows[index].ww;
	h	=	windows[index].hw;
	s	=	windows[index].scalemade;

	if (windows[index].points == NULL) /* Fenster hat keine Funktion zugeord. */ 
	{
		windows[index].points = points[index];

		if (!calc_vals(index)) return; /* Berechnen der Werte */

		s = windows[index].scalemode = scalemode;

		/* Was soll gezeichnet werden ? */ 
		if (show_what == DERIVATE)
		{
			what = "D/DX ";
			calc_der(index);	/* Berechne Ableitung */
		}
		else
		if (show_what. == INTEGRAL)
		{
			what = "INT von"; 
			calc_int(index);	/*
								 * Berechne Integral (rein qualitativ).
								 * Linke Intervallgrenze wird zu 0 an-
								 * genommen.
								*/
		}
		else
			what = "f (x) = ";
		
		p = windows[index].points; 
		
		ymin = ymax - p[0].y;

		/* ymin, ymax nur bei Bestfit Methode berechnen */ 
		if (s == BESTFIT)
			for (i = 0; i < MAXVALS; i++)
			{
				if ((p[i].y - ymin) < 0.0) ymin = p[i].y; 
				else
				if ((ymax - p[i].y) < 0.0) ymax = p[i].y;
			}
		windows[index].ymin = ymin;
		windows[index].ymax = ymax;

		sprintf(windows[index].title, "%s %s ",what,function);
		wind_set(windows[index].wi_hndl, WF_NAME,windows[index]. title,0,0);
	}
	else
	{
		ymin = windows[index].ymin;
		ymax = windows[index].ymax;
	}

		hide_mouse();
		plot(MAXVALS,windows[index].points,x,y,w,h,s);	/* s. PLOT.C */
		show_mouse();

}

/*
 *	Berechnet Ableitung (nicht sonderlich genau).
 *	Methode:
 *	f’(x) ~ ( f (x+dx) - f(x) ) f dx
 *
 */

calc_der(index)
int index;
{
	int i;
	POINT *p = windows[index].points;

	for (i = 0; i < MAXVALS-1; i++)
		p[i].y = (p[i + 1].y - P[i].y) / (p[i + 1].x - p[i].x);

	p[MAXVALS-1].y = p[MAXVALS-1].y; 
	p[MAXVALS-1].x = p[MAXVALS-2].x;
}
/*
 * Berechnet Integral nach Rechteckver-fahren. Wert an linker Interval1 grenze 
 * wird willkuerlich zu 0 angenommen. Damit rein qualitativer Verlauf.
 */

calc_int(index) 
int index:
{
	int i ;

	POINT tp = windows[index].points;

	p[0].y = 0;	/*	Anfangswert */

	for (i = 1; i < MAXVALS; i++)
		p[i].y = p[i-1].y + (p[i].y * (p[i+1].x - p[i].x));

	p[MAXVALS-1].y = p[MAXVALS-2].y; 
	p[MAXVALS-1].x = p[MAXVALS-2].x;
}

/*
 * Gibt mit Hille von form_alert Nachricht an Benutzer
 */
message(m) 
char *m;
{
	char show[MAXVALS];

	sprintf(show,"[1][%s][ OK ]",m); 
	form_alert(1,show);
}

/* ENDE von FUNPLOT.C */

Listing 1

/*
 * GLOBALS.H
*/

#define	YEQUALX		MEQUAL
#define	YINTERVALL	MINTER
#define	BESTFIT		MFIT

#define	FUNCTION	MFUNC
#define	DERIVATE	MDERIV
#define	INTEGRAL	MINT

#define	NO_WINDOW	-1
#define	MAXVALS	100

/**************************************************/
/* TYPEN                                          */
/**************************************************/

typedef struct point { 
	float x,y;
} POINT, PARRAY[];

typedef struct window_desciptor {
		int		wi_hndl;		/* Window Handle */
		int		xw,yw,ww,hw;	/* Groesse des Arbeitsbereichs */
		POINT	tpoints;		/* Wertetabelle der Funktion */
		int		scalemode;		/* Art der Skalierung */
		float	ymin,yms,x;		/* kleinster, groesster YWert	*/
		char	title[40];		/* Titel des Fensters */
} WI_DESC;

/* Ende von GLOBALS.H */ 

Listing 2

										.
										.
							Ausschnitt aus DIALOG.C
										.
										.
/*
 *	Managt den gesamten Dialog mit einer Dialogbox, einschliesslich Zeichnen
 * des Dialogs und wiederherstellen des Bildschirms am Schluss.
 *
 * EINGABE:
 * addr ist die Adresse eines Dialogbaums
 * edit_item ist der Index des ersten editierbaren Textfeldes im Baum und
 * 0 falls keines vorhanden.
 *
 * AUSGABE:
 * exit_cond des Dialogs, d.h Index des Exit Buttons 
 */

int hndl_dialog (addr,edit_item)
OBJECT *addr; 
int	edit_item;
{
	int ex_cond; 
	int x,y,w,h;

	x = addr->ob_x; 
	y = addr->ob_y; 
	w = addr->ob_width; 
	h = addr->ob_height;

	form_center(addr,&x,&y,&w,&h);

	form_dial(FMD_START,318,198,4,4,x,y,w,h); 
	form_dial(FMD_GROW,318,198,4,4,x,y,w,h);

	objc_draw(addr,0,MAX_DEPTH,x,y,w,h);

	ex_cond = form_do(addr,edit_item);

	form_dial(FMD_SHRINK,318,198,4,4,x,y,w,h): form_dial(FMD_FINISH,318,198,4,4,x,y,w,h);

	return(ex_cond);

} /* hndl_dialog */

/*
 * Dialog zum Einlesen einer neuen Funktion 
 */

funin_dialog()
{
	int ex_cond;

	ex_cond = hndl_dialog(funinaddr,FUNDEF);

	if (ex_cond == FDEFOK) /* OK Button wurde betaetigt. Neue Werte holen */
	{
		funinaddr[FDEFOK].objstate &= ~SELECTED;
		get_float(funinaddr, APARAM,&a);
		identifiers[0] = a;
		get_float(funinaddr,BPARAM,&b);
		identifiers[1] = b;
		get_float(funinaddr,CPARAM,&c);
		identifiers[2] = c;
		get_float(funinaddr,DPARAM,&d);
		identifiers[3] = d;
		get_string(funinaddr,FUNDEF,Function);

		/* Hier folgt der Aufruf an den PARSER */ 
		if (parse(function))
		{
			form_alert(1,"[1][Die Funktion ist|syntaktisch nicht korrekt][ OK ]"); 
			*function = '\0';
		}
	}
	else /* CANCEL Button wurde betaetigt. Alte Werte beibehalten */
	{
		funinaddr[FDEFCAN].ob_state &= ~SELECTED; 
		set_float(funinaddr,APARAM,a); 
		set_float(funinaddr,BPARAM,b); 
		set_float(funinaddr,CPARAM,c); 
		set_float(funinaddr,DPARAM,d); 
		set_string(funinaddr,FUNDEF,function); 
		windows[act_window].points = NULL;
	}
}

/*
 * Liefert in string die Adresse des Textstrings im Dialog addr mit Index
 * item. ACHTUNG ! Die Funktion behandelt nur editierbare Textfelder 
 */
get_string(addr,item,string)
OBJECT	*addr; 
int		item;
char	*string;
{
	TEDINFO *text_desc; 
	char *text;

	text_desc = (TEDINFO *) addr[item].ob_spec; 
	text = text_desc->te_ptext; 
	strcpy(string,text);
}

/*
 * Gegenstueck zu get_string. Setzt die Adresse des Textes, des editierbaren
 * Textfeldes item im Dialog addr auf string.
 */
set_string(addr,item,string)
OBJECT	*addr; 
int		item;
char	*string;
{
	TEDINFO *text_desc;

	text_desc = (TEDINFO *) addr[item].ob_spec; 
	strcpy(text_desc->te_ptext,string);
}

Listing 3


Thomas Weinstein
Aus: ST-Computer 01 / 1987, Seite 34

Links

Copyright-Bestimmungen: siehe Über diese Seite