Yeah, me

Foto: Uwe Pirr

Informatik


previous Page home toc next Page

Programmieren in HyperTalk, Teil III

HyperCard wäre natürlich kein Multimediawerkzeug, wenn es nicht auch Graphik beherrschen würde. Wie wir an der Toolbox gesehen haben, sind in dieser Abreißpalette alle notwendigen Werkzeuge vorhanden, um in HyperCard ähnlich wie in MacPaint zeichnen zu können. Wichtig für unseren Kurs aber ist, daß auch hier die HyperCard-Philosophie gilt: Alles, was mit Maus und Tastatur in HyperCard erledigt werden kann, kann in HyperTalk auch programmiert werden.

Dabei sind fast alle Grafikbefehle in HyperTalk nach dem gleichen Schema aufgebaut. Zuerst wird mit choose <Werkzeug> eines der Werkzeuge aus der Palette aufgerufen und dann wird mit drag <alte Position> to <neue Position> das Bewegen mit gedrückter Maustaste simuliert. Um z.B. ein Rechteck zu zeichnen, sind in HyperTalk nur folgende beiden Befehle nötig:

	choose rectangle tool
	drag from 30, 30 to 130, 130
Damit haben wir ein 100 x 100 Pixel großes Quadrat auf unsere Karte gezeichnet. Wie die Werkzeuge alle heißen, ist aus folgender Abbildung ersichtlich:

Abb. 1: Die Werkzeugpalette

Ebenso können mit set <Attribut> to <Wert> auch Eigenschaften gesetzt werden. So wird z.B. mit set lineSize to 8 HyperCard angewiesen, eine sehr dicke Linie von 8 Pixeln Breite zu zeichnen. Welche Attribute alle möglich sind, können Sie entweder dem Handbuch entnehmen oder sie inspizieren den Menütitel Option, der bei irgendeinem ausgewähltem Zeichentool in der HyperCard-Menüleiste erscheint.

Abb. 2: Die Füllmusterpalette

Wichtig ist noch, daß man auch die Füllmusterpalette direkt von HyperTalk aus ansprechen kann. Die Muster sind nach obigem Schema durchnumeriert. Mit

	set pattern to 37
wird z.B. jede nachfolgende füllbare Fläche mit dem Flechtmuster ausgefüllt.

Wenn sie nun von HyperTalk aus mit vielen möglichen Mustern und Attributen herumgespielt haben, möchten Sie vielleicht irgendwann einmal wieder alles auf den ursprünglichen Wert (Default) setzen. Natürlich können Sie das, in dem Sie mit "set" jedes einzelne Muster und jedes einzelne Attribut auf den Defaultwert setzen. Aber erstens: Wissen Sie alle Defaultwerte? Und zweitens: Haben Sie den Überblick, welche Attribute und Muster sie verändert haben? HyperTalk bietet hier eine einfachere Alternative mit

	reset paint
werden alle Malattribute wieder auf die ursprünglichen Werte zurückgesetzt. Und wenn Sie dann nicht vergessen, auch wieder das "Browse Tool", also den Zeigefinger-Cursor auszuwählen (mit choose browse tool), dann kann der Anwender Ihres Programms anschließend auch wieder Knöpfe anklicken, Menüs auswählen oder in Feldern Eintragungen vornehmen.

Koordinatensysteme

Wir haben bei obigem drag-Befehl gesehen, daß wir von irgendwelchen (x,y)-Koordinaten zu irgendwelchen anderen (x,y)-Koordinaten die Maus ziehen. HyperCard hat nun, wie fast alle Computer-Koordinatensysteme, den 0-Punkt (0, 0) in der linken oberen Ecke. Die x-Koordinaten steigen von links nach rechts, die y-Koordinaten von oben nach unten. So ist das größte Koordinatenpaar (xMax, yMax) in der rechten unteren Ecke der Karte zu finden. Und die Maßeinheit ist Pixel. So hat das klassische HyperCard-Fenster die Ausmaße von 512 x 342 Pixeln (das sind die Ausmaße des 9"-SE-Monitors). In der rechten unteren Ecke haben wir daher dort das Koordinatenpaar (512, 342), in der linken oberen Ecke das Koordinatenpaar (0, 0).

Nun sind aber in der "realen" Welt die Koordinatensysteme in der Regel anders ausgerichtet: Das kleinste Koordinatenpaar (xWmin, yWmin) ist links unten, das größte (xWmax, yWMax) ist rechts oben zu finden. Und (xWmin, xWmax) müssen nicht (0, 0) betragen, sondern können beliebige negative oder positive Werte annehmen. Und außerdem wird in der Regel in der realen Welt nicht in Pixeln, sondern in Metern, Zentimetern, Inches oder irgend etwas anderem gemessen. Zum vierten wollen wir in der Regel nicht die gesamte reale Welt auf der HyperCard-Karte abbilden, sondern nur einen Ausschnitt daraus. Dieser Ausschnitt wird in der Literatur oft Window genannt, leider genauso wie die Fenster auf dem Apple-Desktop. Also bitte nicht verwechseln. Fünftens habe ich, um die gesamte Angelegenheit möglichst allgemein zu halten, auch nicht das gesamte HyperCard-Fenster als Zielausschnitt gewählt, sondern ebenfalls nur einen 300 x 300 Pixel großen Ausschnitt daraus. Dieser Ausschnitt wird in der Literatur Viewport genannt. Wir müssen also eine Koordinatentransformation vornehmen, eine Window-Viewport-Transformation. (Wegen der oben angesprochenen Verwechslungsgefahr bevorzuge ich die anderen in der Literatur verwendeten Begriffspaare Weltkoordinaten (WC) und Devicekoordinaten (DC). Also lassen Sie uns in Zukunft immer von einer WC -> DC-Transformation sprechen, um Verwechslungen auszuschließen. Keine Angst, dies hört sich schlimmer an, als es ist und es sind auch nur einige wenige elementare Rechnungen notwendig.

Abb. 3: WC nach DC Koordinaten

Lassen Sie uns zuerst einmal davon ausgehen, daß beide Koordinatensysteme gleichorientiert sind (siehe Abbildung). Dann legen wir den (zweidimensionalen) Ausschnitt der "wirklichen" Welt, den wir in HyperCard abbilden wollen, in ein Koordinatensystem aus Weltkoordinaten. Wir erhalten so ein Rechteck mit den Koordinatenpaaren (xWmin, yWmin) und (xWmax, yWmax). Innerhalb dieses Rechteckes liegen alle Punkte (xW, yW) unserer Grafik.

Dann betrachten wir auch in unserem HyperCard Fenster ein Rechteck, festgelegt durch die Koordinatenpaare (xMin, yMin) und (xMax, yMax). Dieses Rechteck kann das gesamte Fenster (oder den gesamten Bildschirm) ausfüllen, muß es aber nicht. Innerhalb dieses Rechteckes sollen alle unsere Bildpunkte (x, y) liegen, die den Weltpunkten (xW, yW) innerhalb unseres Ausschnittes aus den Weltkoordinaten entsprechen. Mit Hilfe obiger Zeichnung erhalten wir leicht folgende Transformationsformeln:

x = xMin + ((xMax - xMin)/(xWmax - xWmin))*(xW - xWmin)

y = yMin + ((yMax - yMin)/(yWMax - yWmin))*(yW - yWmin)

Wie Sie sehen, ist der Bruch dabei ein Skalierungsfaktor, der völlig unabhängig von der Position der einzelnen Punkte (xW, yW) ist. Wir können daher abkürzen:

xFactor = (xMax - xMin)/(xWmax - xWmin)

yFaktor = (yMax - yMin)/(yWMax - yWmin)

Jetzt müssen wir nur noch das Problem lösen, das beim Macintosh im Allgemeinen und bei HyperCard im Besonderen der Koordinatenursprung nun einmal nicht links unten, sondern links oben liegt. Betrachten wir daher folgende, leicht abgewandelte Zeichnung:

Abb. 4: WC nach DC Koordinaten mit dem DC-Urpsrung links oben

Bis auf die erwähnte Koordinatenumkehr ist sie mit der ersten identisch. Vor allen Dingen ist die x-Orientierung unverändert, daher ändert sich an dieser Gleichung auch nichts mehr. Wenn wir aber nicht wollen, daß unsere Zeichnung auf dem Kopf steht, müssen wir auf der y-Achse noch eine Umstellung vornehmen. Dann erhalten wir die beiden endgültigen Transformationsgleichungen:

x = xBmin + xFactor(xW - xWmin)

y = yBmin + yFactor((yWmax - yW).

Im folgenden Beispielstack werden diese Transformationen in einem Background-Script als Handle vorrätig gehalten. Sie sind aber so allgemein gehalten, daß Sie sie für viele Probleme anwenden können. Sie brauchen sich dieses Background-Script nur in Ihren neuen Stack zu kopieren und schon können Sie alle notwendigen Umrechnungen von HyperCard automatisch vornehmen lassen.

Business Charts

Charts gehören zu den am meisten gewünschten Computeranwendungen. Ob in einer Studienarbeit die Ergebnisse optisch aufbereitet werden sollen oder ob ein Geschäftsbericht den Gesellschaftern vorgelegt wird. Kaum jemand kommt ohne diese Grafiken aus. Ich habe oft den Eindruck, daß Tabllenkalkulationen wie Excel nicht wegen ihrer rechnerischen Fähigkeiten so populär geworden sind, sondern weil sie so schöne Business Charts erstellen. (Wie ein ehemaliger Chef von mir behauptete, sei dies dem KVI-Prinzip geschuldet: Kinder, Vorstände und Idioten bräuchten nun einmal viele bunte Bilder.) Wir werden daher als Beispielanwendung für die Graphikprogrammierung in HyperTalk solch ein Business Chart Werkzeug schreiben.

Wir legen als erstes einen neuen Stack an und schreiben ein Background Script (siehe Listing 1). In diesem Script erkennen Sie leicht obige Formeln wieder, nur xMin und yMin heißen hier xOffset und yOffset. Sie sind mit Werten versehen, die eine 300 x 300 Pixel große Zeichenfläche links in das HyperCard-Fenster einpassen. Falls Sie mit einer anderen Kartengröße arbeiten wollen, müssen sie diese Werte und gegebenenfalls auch xMax und yMax ändern. Diese Konstanten sind durch Funktionen realisiert, damit sie auf keinen Fall versehentlich andere Werte zugewiesen bekommen. Das Handle initGraph löscht die gesamte Zeichenfläche, falls Sie dieses nicht wollen (weil z.B. links noch eine Zeichnung ist, die nicht gelöscht werden soll), müssen Sie ebenfalls die Koordinaten entsprechend ändern. Das Handle closeGraph sorgt dafür, daß alles wieder zurückgesetzt wird und gibt uns die Sicherheit, daß wir nichts unaufgeräumt im Stack zurücklassen. Daher sollte er nach erfolgter Zeichnung unbedingt aufgerufen werden.

Auf die verschiedenen Achsenkreuze komme ich später bei den einzelnen Charts zurück, wo sie aufgerufen werden. Scale übernimmt die Skalierungsfunktionen, wenn Sie aber wollen, daß bei Ihrer Zeichnung die Originalproportionen erhalten bleiben sollen, sollten Sie stattdessen unConstrained benutzen. (Diese Prozedur wird in unserem Beispielstack nicht aufgerufen.) Dieses Handle berechnet das Minimum von xFactor und yFactor und rechnet mit diesem als gemeinsamen Faktor für beide Koordinatenrichtungen. Das ist dann wichtig, wenn z.B. ein Kreis ein Kreis bleiben und nicht zur Ellipse deformiert werden soll.

Da unsere Weltkoordinaten den Ursprung (0,0) nicht unbedingt links unten haben müssen - es könnte ja auch negative Koordinaten geben, habe ich auch noch die Handle xOrigin und yOrigin hinzugefügt. Sie berechnen den Nullpunkt der jeweiligen Koordinate in Devicekoordinaten um.

Als Letztes folgen noch einige Hilfsfunktionen, die mit der Graphik nichts zu tun haben. Sie sind nur für unseren Chart-Stack notwendig. Die ersten beiden Handle berechnen das Minimum resp. Maximum der eingegebenen Daten in einem Feld. Und die beiden letzten sorgen dafür, daß bei Betätigung der ENTER- oder der Return-Taste ein Mausklick in einem Button simuliert wird. Ich komme weiter unten noch einmal darauf zurück.

Balkendiagramme

Nachdem wir das Script eingegeben haben, legen wir auf unserem Hintergrund die vier Felder an, die wir in der obigen Abbildung sehen können und nennen die beiden oberen xLabel und yLabel, die Datenfelder darunter xValue und yValue. Da wir noch mindestens zwei weitere Karten mit dem gleichen Background anlegen wollen, lohnt es sich auch, die Pfeil-Buttons "pref" und "next" wie im letzten Monat als Hintergrundknöpfe anzulegen. Dann wechseln wir aber zum Vordergrund, den Knopf "Bar Chart" legen wir nämlich dort an. Dieser Knopf hat eine Neuheit aus der HyperCard Version 2.3: Er ist als Default-Button ausgewählt. Leider legt HyperCard nur das Aussehen dieses Knopfes fest, das Verhalten müssen wir programmieren. Denn wie jeder andere Macintosh-Anwender auch sind auch wir es gewohnt, daß dieser stark umrandete Knopf nicht nur mit der Maus angeklickt werden kann, sondern daß er ebenso reagiert, wenn wir die Return- oder ENTER-Taste drücken. Glücklicherweise können diese Tasten in HyperTalk abgefragt werden, es existieren die Systemmessages enterKey und returnKey.

Jetzt werden die beiden letzten Handle unseres Backgroundscripts verständlich: Wenn die Return- oder Enter-Taste gedrückt wird, soll ein Mausklick in den Button mit der dicken Umrandung simuliert werden. Dessen Position habe ich einfach in der Messagebox abgefragt:

	the loc of cd button "Bar Chart"
gab mir die Koordinaten (111, 316) zurück. Sie werden vermutlich etwas andere Koordinaten bekommen und können dann diese im Background-Script einsetzen. Eine andere Möglichkeit ist, den Knopf auf diesen Punkt zu positionieren. Geben Sie dazu einfach in der Messagebox ein:

	set the loc of cd button "Bar Chart" to 111, 316
und wie von Zauberhand springt der Knopf dorthin. Dies ist die bessere Lösung, da ja auch die "Default-Buttons" der anderen beiden Karten mit diesem Background-Script funktionieren sollen. Wir werden sie später auch auf den loc (111, 316) setzen.

The loc gibt immer den Mittelpunkt eines Objektes zurück und besteht daher aus einem Koordinatenpaar (zwei Items). Daneben existiert noch the rect, das die Koordinatenpaare des das Objekt umschließenden Rechtecks mit den Koordinaten links oben und rechts unten zurückgibt (vier Items). Diese beiden Möglichkeiten, die Position eines Objektes zu identifizieren oder festzulegen, werden häufig verwechselt und führen dan zu Verwirrungen und zu Programmfehlern. Falls also ein Objekt verschoben erscheint, prüfen Sie immer erst einmal nach, ob loc oder rect wirklich korrekt angewandt wurden.

Nun aber zu unserem Button-Script (Listing 2): Das mouseUp Handle beinhaltet nichts Aufsehenerregendes. Die Graphik wird initialisiert. Da Balkendiagramme selten negative Werte beinhalten, werden die Achsen an der unteren und linken seitlichen Begrenzung der Grafik gezeichnet. Außerdem werden die Achsen beschriftet. Dabei wird zum ersten Mal die Funktion maxLines() benutzt, die uns die maximalen Werte der Felder xValue und yValue liefert. Hier hat der Umstand, daß HyperTalk nur einen Datentyp kennt, einmal einen Vorteil. Obwohl die Werte nach unserem Verständnis numerische Werte sind, gibt HyperTalk sie ohne weiteres als Text aus. (Intern kennt HyperTalk sowieso nur den Datentyp "Text"!) Wer je einmal in PASCAL oder MODULA eine RealToString-Funktion schreiben mußte, wird dies zu schätzen wissen. (Auf der anderen Seite wollen wir aber nicht vergessen, daß die Möglichkeit, strukturierte Datentypen zu benutzen, viele Vorteile hat und zur Sicherheit und besseren Lesbarkeit von vielen Programmen beiträgt.)

Jetzt wollen wir aber unsere Balken füllen, daher das Kommando

	set filled to TRUE
(der Defaultwert ist FALSE). Das ist eigentlich schon alles.

Interessanter ist das Handle drawBars, der von mouseUp aufgerufen wird. Hier werden zuerst die Anzahl der Balken berechnet und dann deren Weite bestimmt, so daß sie alle in unsere 300 x 300 Pixel große Zeichenfläche passen. Dann wird ein Abstand zwischen den einzelnen Balken ebenfalls in Abhängigkeit von der Balkenanzahl berechnet. Und schließlich wird die Prozedur Scale aufgerufen, die uns die Skalierungsfaktoren liefert. Dann werden die einzelnen Balken gezeichnet und mit einer Füllung versehen. Rein aus ästhetischen Gründen habe ich die Füllung ab Pattern 12 gewählt, Pattern 13 und die folgenden schienen mir für die Darstellung der Balken die geeignetsten zu sein. Das Beschriften der Balkendiagramme habe ich wegen der Übersicht in eine eigene Prozedur ausgelagert, dies ist aber nicht unbedingt nötig.

Und bitte beachten Sie, das am Ende des mouseUp-Handles mit closeGraph alles wieder ordentlich zurückgesetzt wird.

Liniendiagramme

Wenn Sie jetzt eine neue Karte anlegen, ist das meiste schon erledigt. Background-Felder und -Buttons sind vorhanden. Den Button "Line Chart" legen wir wieder mit dem Style "Default" an und sorgen - wie oben besprochen - dafür, daß er an der gleichen Stelle (loc) wie sein Vorgänger steht.

Eine weitere Neuheit von HyperCard 2.3 sind die beiden (Card-) Buttons "Achsenkreuz" und "Achsen an der Seite". Sie haben den Style "Radio Button" und gehören einer "Familie" (in unserem Fall der Familie Nr. 1) an. Die Zuordnung zu einer Familie sorgt dafür, daß sich Radio Buttons auch so verhalten, wie es ein Macintosh-Anwender gewohnt ist: Nur einer der beiden Knöpfe kann gedrückt sein (Hilite of me is TRUE). Um die Zugehörigkeit der beiden Knöpfe zu einer Familie zu unterstreichen, habe ich eine Rahmen drumherum gezogen. Hier tritt eine Schwierigkeit auf: initGraph löscht jede Graphik! Daher besteht dieser Rahmen aus einem leeren Card Field mit dem Stil Rectangle, das mit Hilfe des "Send Farther" Menüs (im Menü Objekts) nach hinten hinter die beiden Buttons gebracht wurde.

Warum nun diese beiden Radio Buttons? Einmal natürlich, um deren Funktionalität zu demonstrieren. Auf der anderen Seite aber auch, um zwei verschiedene Sorten von Liniendiagrammen auf einer Karte unterbringen zu können. Liniendiagramme können oft auch negative x- oder y-Werte aufweisen. Dann sollte ein Achsenkreuz gezeichnet werden. Oft aber auch will man rein positive Werte darstellen, wie z.B. die Werte aus unserer Abbildung mit dem Balkendiagrammen. Versuchen Sie doch einmal, diese Werte mit der Option Achsenkreuz zu zeichnen! Vom Jahre 0 bis 1989 (also fast auf dem gesamten Diagramm) passiert nichts - nur auf den letzten paar Pixeln versucht HyperCard verzweifelt, unsere Linien zu zeichnen.

Nun zum Script selber (Listing 3): Wegen der ständigen Abfrage, welcher der beiden Radio Buttons gedrückt ist und der damit zusammenhängenden Frage, ob die Maximum- und Minimumwerte von x oder y negativ, Null oder positiv sind, habe ich die gesamte Funktionalität im mouseUp-Handle gelassen. Eine Auslagerung, wie im "Bar Chart"-Script hätte für meinen Geschmack zu viele globale Parametervereinbarungen bedeutet. Nur das Zeichnen der Punkte als kleine Kreise und das Verbinden der Punkte wurden (das es auch für andere Anwendungen einmal nützlich sein könnte (man sollte schließlich auch an die Wiederverwendung von Software denken) als eigene Handles ausgelagert.

Technisch gesehen birgt dieses Script nicht viel Neues: Mit allen darin vorkommenden Sprachelementen sollten sie mittlerweile vertraut sein. Etwas trickreich ist nur die Beschriftung der Achsen - hier können Sie sehen, warum ich im Background-Script xOrigin und yOrigin getrennt berechnen lasse.

Tortendiagramme

Auch diese Karte unterscheidet sich vordergründig nur durch die Knopfbeschriftung "Pie Chart" von der ersten Karte. Die Feinheiten liegen hier im Script zu diesem Button verborgen (Listing 4).

Erst einmal wird in dem mouseUp-Handle das Feld yValue aufsummiert. Tortendiagramme werden traditionsgemäß mit Prozentwerten beschriftet, und um diese auszurechnen, brauchen wir nun einmal die Gesamtsumme. Dann wird, um die Ausführung zu beschleunigen, der Wert von 2[[pi]] in eine Konstante gepackt. Dieser Wert wird in einer Schleife mehrmals benötigt, und das jeweilige neue Ausrechnen würde nur unnötig Rechenzeit vergeuden.

Um die Torte, resp. deren Umriß, selber zu zeichnen, habe ich ein eigenes Handle drawCircle geschrieben. Dieses zeichnet einen Kreis mit dem Oval Tool in der gewohnten Notation Mittelpunkt (x, y) und Radius. Da Sie diese Funktion vermutlich in Ihrem Leben als HyperTalk-Programmierer immer wieder einmal brauchen, lohnt sich das Anlegen eines eigenen Handles schon.

Die einzelnen Kreissegmente werden ab 0deg. (drei Uhr) in den Kreis eingezeichnet und laufen entgegen dem Uhrzeigersinn, da die weiteren Winkel mit Hilfe der bekannten Winkelfunktionen aus der Geometrie

x = radius*cos(winkel)

y = radius*sin(winkel)

berechnet werden. Dies geschieht in dem Handle drawRadius, in dem auch die weiteren Kreissegmente gezeichnet werden. Da die Kreissegmente kein in HyperCard füllbares Objekt sind müssen wir sie separat mit dem Farbeimer (bucket tool) füllen. Dazu wird ein Punkt innerhalb des Segments berechnet, das bucket tool wie auch das Füllmuster ausgesucht und ein Mausklick in das Segment simuliert. Diesmal waren mir die einzelnen Füllmuster ab 12 aufwärts zu ähnlich, die Füllung der einzelnen Tortenschnitten unterschied sich kaum, daher habe ich mit

	set pattern to 12 + 2*i
jedes zweite Füllmuster ausgewählt (also 14, 16, 18 etc.). Die Beschriftung der einzelnen Kreissegmente mußte in einer separaten Schleife erfolgen. Ursprünglich hatte ich alles in einem Durchgang erledigen wollen. Bei kleinen Kreissegmenten jedoch läuft die Beschriftung über die Umrandung hinaus, hierdurch entsteht ein Leck im Tortenstück, durch das der Farbeimer seine Füllung über die ganze Karte verteilt. So werden stattdessen erst die Segmente sauber gefüllt und hinterher (wenn bildlich gesprochen die Farbe getrocknet ist) erfolgt die Beschriftung.

Auch bei der Beschriftung gibt es einige Neuheiten. So wird mit dem Befehl

	set the numberFormat to 0.##
die Anzahl der Nachkommastellen auf zwei begrenzt. Und außerdem lernen wir den Operator "&&" kennen, der zwei Strings miteinander verbindet. Während der schon bekannte Operator "&" die Zeichenketten unmittelbar aneinanderhängt ("Bier" & "durst" ergibt "Bierdurst"), fügt der Operator "&&" eine Leerstelle dazwischen: "Indiana" && "Jones" wird zu "Indiana Jones".

Verbesserungen? - Verbesserungen!

Das wäre es für dieses Mal. Natürlich ist dieser Stack nicht vollkommen. Eine mögliche Fehlerquelle wäre, daß bei Bar Chart und Line Chart eigentlich die Werte in xValue daraufhin geprüft werden müßten, ob sie aufsteigend sortiert sind (bei Bar Chart würde eine andere Sortierung zwar nicht zu Fehlern, aber zu Irritationen des Lesers führen). Die Leserin oder der Leser sind aufgefordert, eine Sortierung für dieses Feld zu programmieren. HyperTalk hält dafür den leistungsstarken Befehl "sort" bereit, der Karten, Zeilen oder Items sortieren kann. Und Line Chart könnte von Ihnen zu einem Funktionenplotter erweitert werden, der Funktionen der Art y = f(x) zeichnet, die Ihnen sicherlich noch aus der Schule bekannt sind.
previous Page home toc next Page

This pages need no frames
This site is edited with Radio UserLand, the first personal Web Application server for Windows and Macintosh. © 1996 - 2001 by Jörg Kantel
Last modified: JK, 04.09.2001; 20:33:27 Uhr
email:joerg@kantel.de
This page is best viewed with a computer and a monitor

Site Meter