Objektorientiertes Neuzeichnen von Fensterinhalten in Modula-2

Das Modul Erwin in Modula-2 realisiert das Neuzeichnen von Fensterinhalten (Redraw) nach einer Änderung der Fensterattribute mit Hilfe objektorientierter Programmierung (Modul MOBS aus ST-Computer 2/92). Die wesentliche Idee liegt darin, die Grafikroutinen, die in einem Fenster ausgeführt werden, in einer Liste zu speichern, um sie beim Redraw erneut anzuwenden.

Die Fenstertechnik, die u.a. im Betriebssystem der Atari-Computer realisiert ist, fordert vom Programmierer Prozeduren, die jederzeit in der Lage sind, den Inhalt eines Fensters wiederherzustellen. Da Fenster vom Benutzer frei auf dem Bildschirm plaziert werden können, werden sie in der Regel zeitweise jeweils von anderen Fenstern überlappt. Um die verdeckten Bereiche bei einer Aktivierung des Fensters oder dem Verschieben anderer Fenster wieder sichtbar zu machen, greift ein GEM-Programmierer meist zu folgenden Methoden: Entweder hält er den Fensterinhalt als Bitmap-Grafik im Speicher, um ihn mittels Kopierbefehlen auf den Bildschirm zurückzuschreiben, oder er stellt Bildschirminformation durch eigene Prozeduren wieder her (z.B. bei Funktions-Plottern).

Die hier vorgestellte Technik beruht auf folgendem Konzept: Fensterinhalte ergeben sich häufig durch die Ausführung mehrerer Grafikbefehle, wie Linien oder Kreise zeichnen, Text schreiben o.ä. Ein Programm (bzw. Bibliotheksmodul), das sich die Reihenfolge und die Parameter dieser Grafikbefehle merkt, ist dann in der Lage, diese beim Redraw selbständig zu wiederholen. Die benötigte Datenstruktur ist folglich eine Art Liste, deren Knoten der Reihe nach bearbeitet werden. Da jeder Knoten eine andere Grafikroutine repräsentieren können muß, liegt eine objektorientierte Lösung des Problems nahe. (Die Konstruktion solcher sogenannter „inhomogenen“ Datentypen ist ein wesentliches Konzept der OOP.)

Ich habe die Aufgaben des Programms auf folgende Module aufgeteilt: Modul Streams stellt den Datentyp einer Art inhomogenen Liste bereit; Modul Erwin realisiert die wesentlichen Routinen für das Abarbeiten von Redraw-Listen; Modul Basis schließlich ermöglicht den Aufruf von Grafikroutinen in gewohnter Art und Weise, schreibt aber alle Parameter in die Liste des zugehörigen Fensters.

Modul „Streams“

Ein Stream ist eine Datenstruktur, die vergleichbar mit einem File auf einer Diskette ist, jedoch im Hauptspeicher abgelegt wird. Mit Hilfe von MOBS können die Komponenten eines Streams unterschiedlichen Typs sein. Sie müssen lediglich Typerweiterungen eines Basistyps für Komponenten sein. Auch der Typ Stream selbst ist als MOBS-Klasse definiert, was aber in unserem Zusammenhang unwichtig ist.

Der Typ Base ist der Basistyp für alle Komponenten eines Streams. Er besteht aus dem Eintrag für die Klassenidentifikation, einem Zeiger auf die nächste Komponente und einer Delete-Routine, die aufgerufen wird, bevor die jeweilige Komponente gelöscht wird (in den Prozeduren Close und Write). Dies ist für Komponenttypen sinnvoll, die weitere Zeiger enthalten; die Delete-Routine kann diese dann freigeben.

Der Typ Stream ist ein Anker für einen Stream. Er besteht aus dem Eintrag für die Klassenidentifikation, Zeigern auf die erste, letzte und aktuelle (zuletzt gelesene/ geschriebene) Komponente sowie der Position der aktuellen Komponente. In den Prozeduren des Moduls Streams werden folgende Bedingungen eingehalten: 1. First ist genau dann NIL, wenn Length gleich Null ist. 2. Last und Length sowie Top und Index stimmen logisch immer überein, d.h. wenn z.B. Index um Eins erhöht wird, zeigt auch Top auf das nächste Element. 3. Wenn Index gleich Null bzw. Length gleich Null sind, zeigen Top bzw. Last nicht auf eine Komponente, sondern auf den Stream selbst. Dies ist zwar sehr unsauber, bringt aber Vorteile bei der Geschwindigkeit.

Sämtliche Einträge der Typen Base und Stream bis auf die Delete-Routine und die Klassenidentifikationen dürfen nicht im direkten Zugriff verändert werden, sondern immer nur über die Prozeduren des Moduls. Diese werden im Folgenden erklärt:

Open bereitet einen Stream für die Benutzung vor. Diese Prozedur muß und darf nur genau einmal für jeden Stream aufgerufen werden, da ansonsten evtl. Speicherleichen auf dem Heap Zurückbleiben.

Close löscht sämtliche gespeicherten Komponenten eines Streams, nachdem jeweils die Delete-Routinen aufgerufen wurden. Nach Close kann ein Stream sofort wieder benutzt werden. Ein erneuter Aufruf von Open richtet zwar keinen Schaden an, ist aber auch nicht notwendig.

Reset setzt die aktuelle Position eines Streams auf den Anfang (gleich Null).

Seek setzt die aktuelle Position eines Streams auf p. Ist p größer oder gleich der Länge des Streams, so wird die aktuelle Position auf die letzte Komponente des Streams gesetzt.

EOS testet, ob das Ende eines Streams erreicht ist (Index >= Length).

Read liest die nächste Komponente und erhöht die aktuelle Position eines Streams um Eins, es sei denn, das Ende des Streams ist erreicht. Dann passiert nichts.

Write schreibt die angegebene Komponente hinter die aktuelle Position eines Streams. Alle folgenden Komponenten werden korrekt gelöscht, so daß die geschriebene Komponente zur letzten des Streams wird. Auf jeden Fall wird die aktuelle Position um Eins erhöht.

Append hängt die angegebene Komponente an das Ende eines Streams an. Die aktuelle Position des Streams wird dabei nicht verändert.

COPY fertigt eine Kopie d eines Streams s an. Der Befehl „d := s“ darf nicht verwendet werden, da in diesem Fall die Einträge Last und Top in d evtl. auf s und nicht auf d zeigen. COPY berücksichtigt diesen Fall. Auf die Kopie d dürfen nicht die Routinen Close und Write angewendet werden, da hier evtl, gelöschte Elemente für das Original noch vorhanden sein müssen, was aber nicht überprüft werden kann.

NoDel schließlich ist eine Dummy-Prozedur, die als Delete-Routine für eine Komponente benutzt werden kann. Sie macht nichts.

Der Kommentar für das Implementationsmodul wird an dieser Stelle ausgespart, da es nicht das Hauptthema dieses Artikels ist.

SSWiS

Als weitere Grundlage für Erwin wurde das Modul SSWiS benutzt, das dem Entwicklungspaket des SPC-Modula-2 Systems beiliegt. Es handelt sich hierbei um eine Schnittstelle, die zum einen die Handhabung des Atari-GEM wesentlich vereinfacht und zum anderen ein einfaches Multitasking ermöglicht. In Zukunft soll SSWiS außerdem für andere Systeme (MS-Windows, X-Window) implementiert werden, so daß eine hohe Portierbarkeit gewährleistet wird. Ich bitte um Verständnis, daß eine GEM-nahe Programmierung die wesentlichen Teile von Erwin verschleiert hätte und daher zugunsten von SSWiS fallengelassen wurde. Erwin läßt sich aber wahrscheinlich recht einfach an andere GEM-Bibliotheken, wie z.B. GEM-Interface, anpassen. Diese Bibliothek ist sowohl für Atari-GEM (Hänisch Modula-2) als auch für PC-GEM (verschiedene Compiler, z.B. TopSpeed Modula-2) erhältlich und bietet ein Modul für GEM-Ereignisse an, das auf ähnliche Art und Weise vorgeht wie SSWiS.

SSWiS ist folgendermaßen strukturiert: Ein Modul, das Fenster benutzen will, muß sich zunächst als Ganzes bei SSWiS anmelden, wobei man eine sogenannte Accept-Prozedur angibt. Diese Accept-Prozedur muß auf alle Ereignisse reagieren können (also Tastatur, Maus, Menü, Zeit etc., aber nicht Redraw ). Jedes Modul hat sodann 16 Fenster zur Verfügung, die der Programmierer frei benutzen und numerieren kann. (Die Fensternummern stehen hier nicht im Zusammenhang mit den Fensternummern des GEM.) SSWiS schließt bei Bedarf automatisch Fenster, die der Benutzer längere Zeit nicht benötigt hat; diese können jedoch jederzeit vom Benutzer zurückgeholt werden. Daher sind 16 Fenster je Modul möglich, obwohl nur sieben angezeigt werden können.

Will der Programmierer ein Fenster anmelden, so muß er auch eine sogenannte Redraw-Prozedur angeben, die für das Neuzeichnen eines Fensterinhalts verantwortlich ist. SSWiS führt sämtliche Rechtecksberechnungen durch, so daß der Redraw-Prozedur nur jeweils ein Rechteck übergeben wird. Ein SSWiS-Fenster ist dabei so angelegt, daß es immer einen Ausschnitt eines imaginären Bildschirms, der sogenannten „Welt“, zeigt, dessen Größe (Breite * Höhe) beliebig ist und vom Programmierer festgelegt wird. Der Redraw-Prozedur -wird demnach das neuzuzeichnende Rechteck in Koordinaten bezüglich der Welt übergeben sowie der Abstand der linken oberen Ecke der Welt zur linken oberen Ecke des Bildschirms. Um die Bildschirmposition eines Punktes zu erhalten, addiert man also einfach diesen Abstand zur Weltposition. Bewegungen eines Fensters oder von Scroll-Balken durch den Benutzer werden von SSWiS automatisch in Redraw-Aufrufe umgerechnet.

Ist dies alles geschehen, braucht im Hauptmodul nur noch ein Schleife zu stehen, die die Prozedur SSWiS.PollEvents wiederholt aufruft. In dieser werden sämtliche GEM-Ereignisse abgefangen und in entsprechende Aufrufe der Accept- und Redraw-Prozeduren umgewandelt. Ein StopFlag vom Typ BOOLEAN kann schließlich bei bestimmten Ereignissen auf TRUE gesetzt werden und als Abbruchbedingung der oben genannten Schleife dienen. Danach meldet man alle Fenster und das Modul ab. Da der Benutzer während SSWiS.PollEvents Fenster anderer Module aktivieren kann, ist somit ein einfaches Multitasking realisiert: Erst wenn der Benutzer es ausdrücklich wünscht, wird ein Programm wieder aktiv.

Modul Erwin

Das Modul Erwin setzt bei der Redraw-Prozedur ein: Es stellt eine neue Prozedur zum Anmelden eines Fensters zur Verfügung, der keine Redraw-Prozedur übergeben wird. Stattdessen wird ein Stream geöffnet, auf dem Grafikroutinen abgespeichert werden können, die in der internen Redraw-Prozedur des Moduls Erwin durchgeführt werden. Üblicherweise sollte dann ein Modul geschrieben werden, das Grafikroutinen anbietet, die in Schreiboperationen auf den entsprechenden Stream übersetzt werden.

Das Definitionsmodul Erwin exportiert folgende Bezeichner:

Libs gibt die Zahl der möglichen Grafikbibliotheken an. Diese müssen mit Register an- bzw. mit Deregister abgemeldet werden. Man erhält von Register einen LibHandle zwischen Null und Libs. Können keine Bibliotheken mehr angemeldet werden, erhält man den Wert -1. Prolog und Epilog sind zwei Prozeduren, die Register übergeben werden müssen: Erstere wird zu Beginn, letztere am Ende eines Redraw-Aufrufes durchgeführt. Damit können Default-Zeichenmodi eingestellt oder best. Listen angelegt und wieder gelöscht werden etc. Anstelle eigener Routinen kann hier auch Nolog angegeben werden, eine Dummy-Prozedur ohne Inhalt.

Item ist eine Typerweiterung von „Streams.Base“. Dieser Typ enthält zusätzlich den Eintrag Redraw. Das ist eine Grafikroutine, die für jede Komponente anders aussehen kann. Ihr wird immer die Komponente selbst übergeben. Für eine sinnvolle Nutzung muß dieser Typ von der jeweiligen Grafikbibliothek noch um Parameter für diese Routine erweitert werden.

Während eines ‚Redraw-Zyklus’ (Abarbeiten eines Redraw-Streams) ist die Variable „Redraw“ sinnvoll belegt. Sie enthält die Parameter, die der Redraw-Prozedur übergeben wurden: „Owner“ ist das Modul, dem das Fenster „gehört“; Window ist das betroffene Fenster; WorldArea ist das neuzuzeichnende Rechteck in Koordinaten bezüglich der Welt; Offset ist der Abstand der linken oberen Ecke der Welt zur linken oberen Ecke des Bildschirms.

Mit CreateWindow wird ein SSWiS-Fenster initialisiert (aber noch nicht angezeigt) und ein Redraw-Stream geöffnet. DeleteWindow entspricht dem DeleteWindow von SSWiS, jedoch wird zusätzlich der Redraw-Stream geschlossen. Für alle anderen fensterbezogenen Manipulationen (Titelzeile, Fenster anzeigen etc.) können die üblichen Routinen des SSWiS-Moduls verwendet werden.

Switch muß vom Programmierer aufgerufen werden, bevor er Grafikbefehle benutzt. Switch wählt nämlich das gewünschte Active-Fenster, also insbesondere den Redraw-Stream des Fensters, in den die Befehle der Grafikbibliothek geschrieben werden sollen. Switch muß nach SSWiS.PollEvents unbedingt immer vor Grafikbefehlen aufgerufen werden, da ja auch andere Module Erwin benutzen können (Multitasking).

Die Grafikbibliotheken dürfen nicht direkt bei Aufruf eines Grafikbefehls auf den Bildschirm zeichnen, sondern nur in den aktiven Stream schreiben. Der Programmierer kann durch UPDATE einen Redraw-Aufruf des aktiven Fensters (also meist des Fensters, auf dessen Stream gerade geschrieben wurde) erzwingen.

Zum Implementationsmodul: Alle Grafikbibliotheken werden im Array Logs gespeichert. Es besteht aus Records mit den Einträgen Registered (Wert TRUE bedeutet: zu diesem Array-Index gehört eine aktive Bibliothek) sowie den Prozeduren Prolog und Epilog (s.o.). Die Streams, die zu den jeweiligen Fenstern gehören, werden im Array „WinStreams“ gespeichert.

Die Prozedur Restore wird als Redraw-Prozedur für jedes Fenster angemeldet. Hier wird zuerst die exportierte Variable Redraw mit den Parameterwerten belegt. Dann werden die Prolog-Prozeduren aller angemeldeten Bibliotheken ausgeführt. (Da wirklich alle Prolog-Prozeduren ausgeführt werden, kann ein Programmierer Befehle verschiedener Bibliotheken auf dasselbe Fenster an wenden!) Nachdem alle Redraw-Prozeduren der gültigen Knoten des zu dem gewünschten Fenster gehörigen Streams aufgerufen worden sind, werden anschließend die Epilog-Prozeduren aller angemeldeten Bibliotheken ausgeführt.

Der Rest des Implementationsmoduls dürfte nach den bisherigen Kommentaren leicht nachzuvollziehen sein.

Modul Basic

Das Modul Basic setzt einige wenige Grafikbefehle des Moduls BasicLib im Sinne von Erwin um. BasicLib ist ein Modul des SPC Modula-2-Entwicklungssystems, das fast alle GFA-BASIC-Befehle bereitstellt. Basic dient lediglich zu Demonstrationszwecken des Moduls Erwin und ist daher sehr kurz gehalten. Das Modul kann aber problemlos um weitere Befehle ergänzt werden. (Tatsächlich habe ich auch eine Version von Basic geschrieben, in der alle Grafikbefehle der BasicLib implementiert sind.) Folgende Befehle werden hier exportiert: DEFTEXT zur Wahl verschiedener Texteffekte, CLS zum Löschen eines Fensterinhalts, BOX zum Zeichnen eines umrandeten Rechtecks, PBOX zum Zeichnen eines gefüllten Rechtecks, PLOT und DRAWto zum Zeichnen von Punkten und Linien sowie TEXT zur Textdarstellung.

Für die Implementation eines Grafikbefehls sind im allgemeinen folgende Schritte notwendig:

  1. Definition eines neuen Knotentyps als Erweiterung von Erwin.Item mit Hilfe von MOBS. Die Erweiterung beinhaltet (meist) lediglich die Parameter des exportierten Grafikbefehls. Da aber bei Basic die Befehle PLOT und DRAWto sowie BOX und PBOX jeweils die gleichen Parameterlisten besitzen, wurde hier zusätzlich um Prozedurvariablen mit eben diesen Parameterlisten erweitert. Es wird dann für diese Typen jeweils nur eine Redraw-Prozedur benötigt, die den Eintrag dieser Prozedurvariablen aufruft.

  2. Schreiben einer Prozedur, die als Redraw-Prozedur im oben definierten Knotentyp eingetragen werden kann. Hier werden meist die Befehle der zugrunde liegenden Module, also in diesem Fall BasicLib, ausgeführt. Dabei muß darauf geachtet werden, daß zu den in den Knoten gespeicherten Weltkoordinaten die richtigen Offsets addiert werden (Erwin.Redraw.Offset).

  3. Im Fall von TEXT wurde der Knotentyp um einen Pointer auf ein Zeichen-Array erweitert, so daß nur Speicher in der Größe des übergebenen Textes belegt werden muß. Dies erfordert aber eine zusätzliche Delete-Prozedur, die diesen Speicher freigibt und dann auch im Knotentyp eingetragen werden kann. (Die Delete-Prozeduren werden ja vor dem Löschen der Knoten im Stream durch Streams.Close oder Streams.Write aufgerufen.)

  4. Schreiben der im Definitionsmodul exportierten Grafikprozedur, die einen neuen Knoten des entsprechenden Knotentyps erzeugt, d.h. mit den übergebenen Parametern sowie den korrekten Delete-, Redraw- und anderen Prozeduren belegt und an den Erwin.Active.Stream^ mit Streams.Append anhängt. Im Fall CLS kann ausnahmsweise ein anderer Weg beschritten werden: Mit Streams.Close werden einfach alle gespeicherten Befehle des Erwin.Active.Stream^ gelöscht; eine erneute Redraw-Anforderung bewirkt dann einfach, daß nichts gezeichnet wird.

  5. Evtl. Schreiben der Prolog- und Epilog-Prozeduren für den Redraw-Zyklus des Moduls Erwin. Im Fall von Basic wird nur eine Prolog-Prozedur benötigt, die globale Variablen belegt, Default-Werte der BasicLib-Befehle setzt und den neuzuzeichnenden Fensterausschnitt mittels BasicLib.PBOX löscht.

  6. Im Initialisierungsblock des Moduls muß neben der MOBS-Klassendefinition der neuen Knotentypen das eigene Modul als Bibliothek bei Erwin angemeldet werden. Außerdem muß dafür gesorgt werden, daß es bei Programmende wieder abgemeldet wird (hier durch System.On-ModuleTerminationDo).

Ein Test

Das Modul Test demonstriert die Befehle des Moduls Basic. Im Hauptprogramm wird zunächst das Modul bei SSWiS angemeldet. (Die Accept-Prozedur macht im Prinzip nichts anderes, als bei Tastendruck das StopFlag auf TRUE zu setzen, damit die Hauptschleife des Programms beendet wird.) Als nächstes wird ein Fenster mit Erwin.CreateWindow erzeugt. In DefWin werden die Attribute des Fensters gewählt (siehe Listing) und das Fenster sichtbar gemacht. Schließlich wird die Ausgabe von Erwin auf dieses Fenster geswitcht. Ein paar Befehle des Moduls Basic werden danach aufgerufen. Sichtbar sind sie allerdings erst, nachdem Erwin.UPDATE ausgeführt wurde. Es folgen die Hauptschleife des Programms, das Entfernen des Fensters und das Abmelden des Moduls von SSWiS.

Während der Hauptschleife können Sie das Fenster beliebig vergrößern, verkleinern und verschieben. Sie können die Scroll-Balken bewegen und wegen des Multitaskings von SSWiS andere Fenster überlappen: das Neuzeichnen des Fensterinhalts wird immer korrekt durchgeführt. Beendet wird das Programm, wie gesagt, durch Druck einer beliebigen Taste bei aktivem Fenster.

Abschließende Bemerkung

Bei Experimenten mit Erwin hat sich gezeigt, daß Grafiken mit sehr vielen Befehlen (z.B. Funktions-Plotter oder Fraktale/ Apfelmännchen, die sehr häufig Basic.-PLOT aufrufen) wohl aufgrund der hohen Speicherfragmentierung unakzeptabel langsam werden. Grafiken, die hauptsächlich „komplexere“ Befehle, wie Kreise, Rechtecke, Texte etc., benutzen, erscheinen hingegen sehr schnell auf dem Bildschirm. Eine Kombination mit Befehlen, die Bitmap-Grafiken kopieren, ist sicherlich möglich und sinnvoll. Denkbar sind natürlich auch andere Anwendungen von Erwin als nur das Bereitstellen von Grafikbefehlen, etwa Funktions-Plotter, die als Ganzes einen Knoten eines Fenster-Streams bilden. Es können dann bei geschickter Programmierung andere „Makrobefehle“ dieser Art, z.B. bestimmte Raster oder Hintergrundbilder, hinzugefügt werden, ohne daß eine Änderung des ursprünglichen Moduls nötig wäre. Da ein solcher Makrobefehl nur jeweils einen Knoten in einem Stream benötigt, ist dann auch die Speicherbelastung nicht sehr groß und die Geschwindigkeit entsprechend hoch.

Literatur:

Pascal Costanza: MOBS (Modula-2 OBject System) - OOP für alle Modula-2 Systeme, ST-Computer Heft 2/92

SPC Modula-2 Benutzerhandbuch, Advanced Applications Viczena GmbH, 1990

Niklaus Wirth: Algorithmen und Datenstrukturen mit Modula-2, B.G.Teubner, Stuttgart 1986

DEFINITION MODULE Streams;
(* programmed by P.Costanza *)
(* (c) 1992 MAXON Computer *)

    IMPORT MOBS;

    TYPE BasePtr = POINTER TO Base;
         DelProc = PROCEDURE( VAR MOBS.CLASS ); 
         Base    = RECORD
                    ID      : MOBS.CLASS;
                    Next    : BasePtr;
                    Delete  : DelProc 
                   END;
        Stream   = RECORD
                    ID                  : MOBS.CLASS;
                    First, Last, Top    : BasePtr; 
                    Index, Length       : LONGINT 
                   END;

    VAR BaseClass, StreamClass : MOBS.CLASS;

    PROCEDURE Open  ( VAR s : Stream );
    PROCEDURE Close ( VAR s : Stream );

    PROCEDURE Reset ( VAR s : Stream );
    PROCEDURE Seek  ( VAR s : Stream; p : LONGINT );

    PROCEDURE EOS   ( VAR S : Stream ) : BOOLEAN;

    PROCEDURE Read  ( VAR s : Stream; VAR Ptr : BasePtr ); 
    PROCEDURE Write ( VAR s : Stream; VAR BaseID : MOBS.CLASS ) ; 
    PROCEDURE Append( VAR s Stream; VAR BaseID : MOBS.CLASS );

    PROCEDURE COPY  (VAR s, d : Stream);

    PROCEDURE NoDel (VAR BaseID : MOBS.CLASS);

END Streams.

IMPLEMENTATION MODULE Streams;
(* programmed by P.Costanza *)
(* Date : 20:21 24.11.1990 *)

    IMPORT SYSTEM, MOBS;

    VAR BaseDef, StreamDef : MOBS.CLASSDEF;

    PROCEDURE Open(VAR s : Stream);
    BEGIN s.ID      := StreamClass;
          s.First   := NIL;
          s.Last    := SYSTEM.ADR(s);
          s.Top     := SYSTEM.ADR(S);
          s.Index   := 0;
          s.Length  := 0 
    END Open;

    PROCEDURE Close(VAR s : Stream);
    VAR b : BasePtr;
    BEGIN WHILE s.First # NIL DO 
            b := s.First^.Next; 
            s.First^.Delete(s.First^.ID);
            MOBS.FREE(s.First); 
            s.First := b 
          END;
          s.Last    := SYSTEM.ADR(s); 
          s.Top     := SYSTEM.ADR(s); 
          s.Index   := 0; 
          s.Length  := 0 
    END Close;

    PROCEDURE Reset(VAR s : Stream);
    BEGIN s.Top     := SYSTEM.ADR(s);
          s.Index   := 0 
    END Reset;

    PROCEDURE Seek(VAR s : Stream; p : LONGINT); 
    BEGIN IF s.Index > p THEN
            s.Top   := SYSTEM.ADR(s); 
            s.Index := 0 
          END;
          WHILE (s.Index < p) & (s.Top # s.Last) DO 
            s.Top := s.Top^.Next;
            INC(s.Index)
          END 
    END Seek;

    PROCEDURE EOS(VAR s : Stream) : BOOLEAN;
    BEGIN RETURN s.Index >= s.Length 
    END EOS;

    PROCEDURE Read(VAR s : Stream;
                   VAR Ptr : BasePtr);
    VAR v : BOOLEAN;
    BEGIN IF s.Index < s.Length THEN 
             s.Top := s.Top^.Next;
             INC(s.Index);
             Ptr := s.Top 
          END 
    END Read;

    PROCEDURE Write(VAR s : Stream;
                    VAR BaseID : MOBS.CLASS);
    VAR b1, d2 : BasePtr;
    BEGIN b1 := s.Top^.Next;
          WHILE b1 # NIL DO 
            b2 := b1^.Next; 
            b1^.Delete(b1^.ID);
            MOBS.FREE(b1); 
            b1 := b2 
          END;
          MOBS.ASSIGN(s.Top^.Next,BaseID);
          s.Top       := s.Top^.Next;
          s.Top^.Next := NIL;
          IF s.Index = 0D
          THEN s.First := s.Top
          END;
          s.Last := s.Top;
          INC(s.Index); 
          s.Length := s.Index 
    END Write;

    PROCEDURE Append (VAR s : Stream;
                      VAR BaseID : MOBS.CLASS);
    BEGIN MOBS.ASSIGN(s.Last^.Next,BaseID); 
          s.Last := s.Last^.Next;
          s.Last^.Next := NIL;
          INC(s.Length)
    END Append;

    PROCEDURE COPY(VAR s, d : Stream);
    BEGIN d := s;
          IF s.Last = SYSTEM.ADR(s)
          THEN d.Last := SYSTEM.ADR(d)
          END;
          IF s.Top = SYSTEM.ADR(s)
          THEN d.Top := SYSTEM.ADR(d)
          END 
    END COPY;

    PROCEDURE NoDel(VAR BaseID : MOBS.CLASS);
    END NoDel;

BEGIN BaseClass :=
        MOBS.NEW(BaseDef,NIL,SIZE(Base)); 
      StreamClass :=
        MOBS.NEW(StreamDef,NIL,SIZE(Stream))
END Streams.

DEFINITION MODULE Erwin;
(* Easy Restore Windows     *)
(* programmed by P.Costanza *)
(* (c) 1992 MAXON Computer  *)

    IMPORT MOBS, SSWiS, Streams;

    CONST Libs = 16;

    TYPE ItemPtr = POINTER TO Item;
         RedrawProc = PROCEDURE( VAR MOBS.CLASS ); 
         Item = RECORD
                    ID : MOBS.CLASS;
                    Next : Streams.BasePtr;
                    Delete : Streams.DelProc;
                    Redraw : RedrawProc 
                END;
    VAR ItemClass : MOBS.CLASS;

    VAR Active : RECORD
            Owner  : SSWiS.ModuleHandles;
            Window : SSWiS.WindowHandles;
            Stream : POINTER TO Streams.Stream 
        END; (* set by "Switch" *)

        Redraw : RECORD
            Owner : SSWiS.ModuleHandles;
            Window : SSWiS.WindowHandles;
            WorldArea ; SSWiS.Lines;
            Offset : SSWiS.Points 
        END; (* active during redraw cycle *)

    PROCEDURE Register( VAR LibHandle ; INTEGER; Prolog, Epilog : PROC ); 
    PROCEDURE Deregister{ LibHandle ; INTEGER ); 
    PROCEDURE Nolog;

    PROCEDURE CreateWindow(
                Owner  : SSWiS.ModuleHandles; 
                Window : SSWiS.WindowHandles ); 
    PROCEDURE DeleteWindow(
                Owner  : SSWiS.ModuleHandles; 
                Window : SSWiS.WindowHandles ); 
    PROCEDURE Switch (
                Owner  : SSWiS.ModuleHandles; 
                Window : SSWiS.WindowHandles );

    PROCEDURE UPDATE;

END Erwin.

IMPLEMENTATION MODULE Erwin;
(* programmed by P.Costanza *)
(* Date : 20:46 24.11.1990 *)

    IMPORT SYSTEM, MOBS, SSWiS, Streams;

    CONST MaxLib = Libs - 1;

    VAP Logs : ARRAY [0..MaxLib] OF RECORD 
                Registered : BOOLEAN;
                Prolog, Epilog : PROC 
               END;
        WinStreams : ARRAY SSWiS.ModuleHandles, 
                        SSWiS.WindowHandles OF Streams.Stream;
        ItemDef : MOBS.CLASSDEF;

    PROCEDURE Register( VAR LibHandle : INTEGER; Prolog, Epilog : PROC );
    BEGIN LibHandle := 0;
        WHILE LibHandle < Libs DO
            IF ~Logs[LibHandle].Registered THEN 
                Logs[LibHandle].Registered := TRUE; 
                Logs[LibHandle].Prolog := Prolog;
                Logs[LibHandle].Epilog := Epilog; 
                RETURN 
            END
        END; LibHandle := -1 
    END Register;

    PROCEDURE Deregister( LibHandle : INTEGER ); 
    BEGIN Logs[LibHandle].Registered : = FALSE 
    END Deregister;

    PROCEDURE Nolog; END Nolog;

    PROCEDURE Restore(
                Owner       : SSWiS.ModuleHandles;
                Window      : SSWiS.WindowHandles;
                WorldArea   : SSWiS.Lines;
                Offset      : SSWiS.Points );
    VAR i : INTEGER; s : Streams.Stream;
        b : Streams.BasePtr; it : ItemPtr;
    BEGIN Redraw.Owner      := Owner;
          Redraw.Window     := Window;
          Redraw.WorldArea  := WorldArea;
          Redraw.Offset     := Offset;
          i := 0;
          REPEAT
            IF Logs[i].Registered 
            THEN Logs[i].Prolog 
            END;
            INC(i)
          UNTIL i = Libs;
          s := WinStreams[Owner,Window];
          Streams.Reset(s);
          WHILE ~Streams.EOS(s) DO 
            Streams.Read(s, b); 
            it := MOBS.IS(b^.ID, ItemClass);
            IF it # NIL THEN it^.Redraw(it^.ID) END 
          END; 
          i := 0;
          REPEAT
            IF Logs[i].Registered 
            THEN Logs[i].Epilog 
            END;
            INC(i)
          UNTIL i = Libs 
    END Restore;

    PROCEDURE CreateWindow{
                Owner   : SSWiS.ModuleHandles;
                Window  : SSWiS.WindowHandles ); 
    BEGIN SSWiS.CreateWindow(Owner,Window,Restore);
          Streams.Open(WinStreams[Owner,Window])
    END CreateWindow;

    PROCEDURE DeleteWindow(
                Owner   : SSWiS.ModuleHandles;
                Window  : SSWiS.WindowHandles ); 
    BEGIN SSWiS.DeleteWindow(Owner,Window);
          Streams.Close(WinStreams[Owner,Window]) 
    END DeleteWindow;

    PROCEDURE Switch(
                Owner   : SSWiS.ModuleHandles;
                Window  : SSWiS.WindowHandles ); 
    BEGIN Active.Owner  := Owner;
          Active.Window := Window;
          Active.Stream := SYSTEM.ADR(WinStreams[Owner,Window] )
    END Switch;

    PROCEDURE UPDATE;
    BEGIN SSWiS.ExplicitRestore(
            Active.Owner,
            Active.Window,
            SSWiS.NeverClip)
    END UPDATE;

    PROCEDURE InitMODULE;
    VAR i : INTEGER;
    BEGIN i := 0;
        REPEAT
            Logs[i].Registered := FALSE;
            INC(i)
        UNTIL i = Libs;

        ItemClass :=
            MOBS.NEW( ItemDef,
                      Streams.BaseClass,
                      SIZE(Item) );

        Active.Owner    := MIN(SSWiS.ModuleHandles); 
        Active.Window   := MIN(SSWiS.WindowHandles); 
        Active.Stream   := NIL 
    END InitMODULE;

BEGIN InitMODULE 
END Erwin.

DEFINITION MODULE Basic;
(* Anpassung von BasicLib-Graphikroutinen an Erwin *)
(* programmed by P.Costanza *)
(* (c) 1992 MAXON Computer *)

    IMPORT BasicLib;

    TYPE ColourRange    = BasicLib.ColourRange;
         TextEffects    = BasicLib.TextEffects;
         TenthDegree    = BasicLib.TenthDegree;

    PROCEDURE DEFTEXT(ColourIndex : ColourRange;
                      Effect      : TextEffects;
                      Angle       : TenthDegree;
                      Height      : INTEGER);

    PROCEDURE CLS;
    PROCEDURE BOX     (x0, y0, x1, y1 : INTEGER);
    PROCEDURE PBOX    (x0, y0, x1, y1 : INTEGER);
    PROCEDURE DRAWto  (x, y           : INTEGER);
    PROCEDURE PLOT    (x, y           : INTEGER);
    PROCEDURE TEXT    (x, y           : INTEGER;
                       str      : ARRAY OF CHAR);

END Basic.

IMPLEMENTATION MODULE Basic;
(* programmed by P.Costanza *)
(* Date : 21:47 25. 5.1991 *)

    IMPORT SYSTEM, BasicLib, Erwin, MOBS,
           Rectangle, Storage, Streams, System;

    VAR Lib     : INTEGER;
        ClipArea : Rectangle.Instance;
        Offset  : RECORD X, Y : INTEGER END;

    TYPE Deftext = RECORD
            ID : MOBS.CLASS;
            Next ; Streams.BasePtr;
            Delete : Streams.DelProc;
            Redraw : Erwin.RedrawProc;
            ColourIndex : ColourRange;
            Effect : TextEffects;
            Angle : TenthDegree;
            Height : INTEGER 
        END;
        PlotProc = PROCEDURE(INTEGER,INTEGER); 
        Plot = RECORD
            ID : MOBS.CLASS;
            Next : Streams.BasePtr;
            Delete : Streams.DelProc;
            Redraw : Erwin.RedrawProc;
            Proc : PlotProc; 
            x, y : INTEGER 
        END;
        BoxProc = PROCEDURE(INTEGER,INTEGER,INTEGER,INTEGER);
        Box = RECORD
            ID : MOBS.CLASS;
            Next : Streams.BasePtr;
            Delete : Streams.DelProc;
            Redraw : Erwin.RedrawProc;
            Proc : BoxProc; 
            x0, y0, x1, y1 : INTEGER 
        END;
        CHARArray = ARRAY [0..127] OF CHAR;
        Text = RECORD
            ID : MOBS.CLASS;
            Next : Streams.BasePtr;
            Delete : Streams.DelProc;
            Redraw : Erwin.RedrawProc; 
            x, y : INTEGER; 
            str : POINTER TO CHARArray 
        END;

    VAR DeftextDef, PlotDef,
        BoxDef, TextDef : MOBS.CLASSDEF; 
        DeftextClass, PlotClass,
        BoxClass, TextClass : MOBS.CLASS;

    PROCEDURE DeftextRedraw( VAR ID : MOBS.CLASS ); 
    VAR b : POINTER TO Deftext;
    BEGIN b := MOBS.IS(ID, DeftextClass);
        BasicLib.DEFTEXT(b^.ColourIndex, b^.Effect, 
                         b^.Angle, b^.Height)
    END DeftextRedraw;

    PROCEDURE PlotRedraw( VAR ID : MOBS.CLASS );
    VAR b : POINTER TO Plot;
    BEGIN b := MOBS.IS(ID, PlotClass);
        b^.Proc(b^.x + Offset.X, b^.y + Offset.Y)
    END PlotRedraw;

    PROCEDURE BoxRedraw( VAR ID : MOBS.CLASS );
    VAR b : POINTER TO Box;
    BEGIN b := MOBS.IS(ID, BoxClass);
        b^.Proc(b^.x0 + Offset.X, b^.y0 + Offset.Y, 
                b^.x1 + Offset.X, b^.y1 + Offset.Y) 
    END BoxRedraw;

    PROCEDURE TextRedraw( VAR ID : MOBS.CLASS );
    VAR b : POINTER TO Text;
    BEGIN b := MOBS.IS(ID, TextClass);
        BasicLib.TEXT(b^.x + Offset.X, 
                      b^.y + Offset.Y, 
                      b^.str^)
    END TextRedraw;

    PROCEDURE TextDelete( VAR ID : MOBS.CLASS );
    VAR p ; POINTER TO Text;
    BEGIN p := MOBS.IS(ID, TextClass);
        Storage.DEALLOCATE(p^.str)
    END TextDelete;

    PROCEDURE CLS;
    BEGIN Streams.Close(Erwin.Active.Stream^)
    END CLS;

    PROCEDURE DEFTEXT(ColourIndex   : ColourRange;
                      Effect        : TextEffects;
                      Angle         : TenthDegree;
                      Height        : INTEGER);
    VAR b : Deftext;
    BEGIN b.ID := DeftextClass; 
        b.Next := NIL; 
        b.Delete := Streams.NoDel; 
        b.Redraw := DeftextRedraw; 
        b.ColourIndex := ColourIndex; 
        b.Effect := Effect; 
        b.Angle := Angle; 
        b.Height := Height;
        Streams.Append(Erwin.Active.Stream^,b.ID) 
    END DEFTEXT;

    PROCEDURE DRAWto(x, y : INTEGER);
    VAR b : Plot;
    BEGIN b.ID := PlotClass; 
        b.Next := NIL; 
        b.Delete := Streams.NoDel; 
        b.Redraw := PlotRedraw; 
        b.Proc := BasicLib.DRAWto; 
        b.x := x; b.y := y;
        Streams.Append(Erwin.Active.Stream^,b.ID) 
    END DRAWto;

    PROCEDURE PLOT(x, y : INTEGER);
    VAR b : Plot;
    BEGIN b.ID := PlotClass; 
        b.Next := NIL; 
        b.Delete := Streams.NoDel; 
        b.Redraw := PlotRedraw; 
        b.Proc := BasicLib.PLOT; 
        b.x := x; b.y := y;
        Streams.Append(Erwin.Active.Strearn^,b.ID) 
    END PLOT;

    PROCEDURE BOX(x0, y0, x1, y1 : INTEGER);
    VAR b : Box;
    BEGIN b.ID := BoxClass; 
          b.Next := NIL; 
          b.Delete := Streams.NoDel; 
          b.Redraw := BoxRedraw; 
          b.Proc := BasicLib.BOX; 
          b.x0 := x0; b.y0 := y0; 
          b.x1 := x1; b.y1 := y1;
          Streams.Append(Erwin.Active.Stream^,b.ID) 
    END BOX;

    PROCEDURE PBOX(x0, y0, x1, y1 : INTEGER);
    VAR b : Box;
    BEGIN b.ID := BoxClass; 
          b.Next := NIL; 
          b.Delete := Streams.NoDel; 
          b.Redraw := BoxRedraw; 
          b.Proc := BasicLib.PBOX; 
          b.x0 := x0; b.y0 := y0; 
          b.x1 := x1; b.y1 := y1;
          Streams.Append(Erwin.Active.Stream^,b.ID) 
    END PBOX;

    PROCEDURE TEXT(x, y : INTEGER;
                   str ; ARRAY OP CHAR);
    VAR b : Text; s : INTEGER;
    BEGIN b.ID := TextClass; 
          b.Next := NIL; 
          b.Delete := TextDelete; 
          b.Redraw := TextRedraw; 
          b.x := x; b.y := y;
          s := HIGH(str) + 1;
          Storage.ALLOCATE(b.str,s);
          BasicLib.BMOVE(SYSTEM.ADR(str),b.str,s); 
          Streams.Append(Erwin.Active.Stream^,b.ID) 
    END TEXT;

    PROCEDURE Prolog;
    VAR cx1, cy1 : INTEGER;
    BEGIN ClipArea.X := Erwin.Redraw.WorldArea.A.X 
                      + Erwin.Redraw.Offset.X; 
          ClipArea.Y := Erwin.Redraw.WorldArea.A.Y 
                      + Erwin.Redraw.Offset.Y; 
          ClipArea.W := Erwin.Redraw.WorldArea.B.X
                      - 1D;
          ClipArea.H := Erwin.Redraw.WorldArea.B.Y
                      - 1D;
          Offset.X := Erwin.Redraw.Offset.X;
          Offset.Y := Erwin.Redraw.Offset.Y; 
          cx1 := ClipArea.X + ClipArea.W; 
          cy1 := ClipArea.Y + ClipArea.H;
          BasicLib.CLIP( ClipArea.X, ClipArea.Y, 
                         cx1, cy1,
                         TRUE );
          BasicLib.BOUNDARY(TRUE);
          BasicLib.COLOR(1);
          BasicLib.DEFTEXT(1,0,0,13);
          BasicLib.DEFLINE(1,1,0,0);
          BasicLib.GRAPHMODE(1);
          BasicLib.SETCOLOR(FALSE); 
          BasicLib.DEFFILL(0,0,1);
          BasicLib.PBOX( ClipArea.X, ClipArea.Y, cx1, cy1 );
          BasicLib.DEFF1LL(1,1,1)
    END Prolog;

    PROCEDURE ExitMODULE;
    BEGIN Erwin.Deregister(Lib)
    END ExitMODULE;

BEGIN
    IF ~System.OnModuleTerminationDo(ExitMODULE)
    THEN HALT
    END;
    Erwin.Register(Lib,Prolog,Erwin.Nolog);
    IF Lib < 0 THEN HALT END;

    DeftextClass := MOBS.NEW(
        DeftextDef,Erwin.ItemClass,SIZE(Deftext) ); 
    PlotClass := MOBS.NEW(
        PlotDef,Erwin.ItemClass,SIZE(Plot) );
    BoxClass := MOBS.NEW(
        BoxDef, Erwin.ItemClass,SIZE(Box) );
    TextC1ass := MOBS.NEW(
        TextDef, Erwin.ItemClass,SIZE(Text) )
END Basic.

MODULE Test;
(* (c) 1992 MAXON Computer *)

    IMPORT Basic, Erwin, SSWiS;

    VAR Module : SSWiS.ModuleHandles;
        StopFlag : BOOLEAN;

    PROCEDURE Accept(Owner  : SSWiS.ModuleHandles;
                     Window : SSWiS.WindowHandles;
                 VAR Report : SSWiS.EventReports);
    BEGIN
        CASE Report.Type OF 
            SSWiS.Notification :
                SSWiS.ExplicitRestore(Owner,Window,SSWiS.NeverClip)
        | SSWiS.Identification :
            SSWiS.Identify("Erwin - Test","===",
                           "P. Costanza",
                           "9/90 & 5/91”)
        | SSWiS.Keyboard : StopFlag := TRUE 
        ELSE END 
    END Accept;

    PROCEDURE DefWin;
    VAR p : SSWiS.ScreenPoints; P : SSWiS.Points;
    BEGIN
        (* Fensterelemente                  *)
        (* (Titelzeile, Scrollbalken, etc.) *)
        SSWiS.SetWindowElements(Module,0,
            SSWiS.SetOfWindowElements{SSWiS.Fuller..SSWiS.YScroller});

        (* Titelzeile *)
        SSWiS.SetWindowTitle(Module,0,"Erwin - Test");

        (* Position auf dem Bildschirm *) 
        p.X := 40; p.Y := 40;
        SSWiS.PositionWindow(Module,0,p);

        (* Minimale, mittlere und maximale  *)
        (* Größe des Fensters               *)
        p.X := 300; p.Y := 300;
        SSWiS.SizeWindowContent(Module,0,
            SSWiS.NullScreenPoint,p,SSWiS.ScreenSize);

        (* Abstand der linken oberen Ecke des *)
        (* Bildschirms zur linken oberen Ecke *)
        (* der Welt                           *)
        SSWiS.PositionWorld(Module,0,SSWiS.NullPoint);

        (* Größe der Welt *)
        P.X := 1000; P.Y := 1000;
        SSWiS.SizeWorld(Module,0,P);

        (* Sichtbarmachen des Fensters *)
        SSWiS.P1aceWindowOnTop(Module,0)
    END DefWin;

BEGIN
    SSWiS.Register(Module,"Erwin - Test",Accept);
    Erwin.CreateWindow(Module,0);
    DefWin;
    Erwin.Switch(Module,0);

    Basic.BOX(450,450,550,550);
    Basic.PBOX(100,100,200,200);
    Basic.PLOT(0,0);
    Basic.DRAWto(1000,1000);
    Basic.PLOT(0,1000);
    Basic.DRAWto(1000,0);
    Basic.DEFTEXT(1,17,0,32);
    Basic.TEXT(250,250,"Dies ist ein Test!");

    Erwin.UPDATE;

    StopFlag := FALSE;
    REPEAT SSWiS.PollEvents UNTIL StopFlag;

    Erwin.DeleteWindow (Module,0);
    SSWiS.Deregister(Module)
END Test.

Pascal Costanza
Aus: ST-Computer 03 / 1992, Seite 120

Links

Copyright-Bestimmungen: siehe Über diese Seite