Sprechen Sie GEM? Anwendungsprogrammierung mit GFA-Basic, Teil 2

Dass GFA-Basic inzwischen etwas in die Jahre gekommen ist, merkt man beim Blick auf modernere Programmiersprachen recht schnell. Solche Sprachen hat man inzwischen nämlich um ein wichtiges Sprachmittel erweitert: die Typbildung. Fachmännisch „Objektorientierung" genannt, verbirgt sich dahinter eine triviale Idee, die richtig umgesetzt viele neue Möglichkeiten bietet.

Im ersten Teil dieses Kurses (st-computer 08/2000) wurde ein Tetrisspiel entworfen und realisiert. Der Spielzustand wurde dabei in globalen Variablen abgelegt - und dies war nötig, denn der Zustand musste prozedurübergreifend erhalten bleiben. Dadurch wurde aber zunächst die Möglichkeit genommen, z.B. ein zweites - typgleiches - Tetrisfenster zu öffnen, das vom ersten Fenster unabhängig ist. Man müsste dazu einen zweiten Satz globaler Variablen anlegen, und damit auch einen zweiten Satz von diese benutzenden Prozeduren - nicht gerade eine vertrauenserweckende Vorgehensweise. Dass es mit den Mitteln der Objektorientierung besser geht, soll dieser Kursteil lehren.

Für ein Tetrisspiel muss man sich natürlich noch keine Gedanken über eine solche „Mehrfache-Problematik machen, denn ein zweites, unabhängiges Tetrisfenster macht aufgrund dessen, dass immer nur ein Spieler vor der Konsole sitzen kann, keinen Sinn. Dies ist aber eher die Ausnahme als die Regel, denn in den meisten Fällen muss ein Programmierer nicht nur gegen die Problematik ankämpfen, dass sein Programm mehrere Objekte eines bestimmten Typs (seien es die Textfenster eines Editors, die Tabellenzellen einer Tabellenkalkulation oder die Raumschiffe eines Weltraumwirtschaftsspiels) verwalten muss, sondern sich oft auch damit abfinden, dass er zur Zeit der Programmierung nicht weiß, wieviele dieser Objekte es zur Laufzeit tatsächlich geben mag.

Statisch zu dimensionieren ist hier im Allgemeinen kein gangbarer Weg. Man denke an den Texteditor, dem man nicht nur eine Beschränkung in der Anzahl der verwaltbaren Dokumente, sondern auch in der Anzahl der Zeilen je Dokument und sogar in der Anzahl der Zeichen je Zeile geben müsste. Gleichzeitig wäre der Speicherverbauch des Editors natürlich ebenfalls statisch - und würde ins unermeßliche reichen.

Dynamische Speicherverwaltung

Grundvoraussetzung zur Lösung dieses Problems ist demnach eine dynamische Speicherverwaltung, in diesem Punkt war Basic einst die führende Sprache - nirgends sonst konnte man so einfach mit Zeichenketten und Variablenfeldern hantieren. Zur Speicherung komplexer Strukturen stößt man aber auch damit sofort an seine Grenzen.

Wir werden nun die dynamische Speicherverwaltung, die die OT/OB-Lib von RUN! Software [1] bietet, nutzen, um letztendlich nicht nur ein Fenster auf den Bildschirm zu bringen, das beliebig viele Grafikobjekte (Linien, Rechtecke und Ellipsen) enthalten kann, sondern das Ganze zudem so gestalten, dass auch von diesen „DTP-Fenstern" beliebig viele geöffnet werden können.

Die Deklarationsphase

Im ersten Schritt muss man sich darüber klar werden, wieviel Speicher je Objekt - in unserem Falle also je DTP-Fenster - benötigt wird. Sicherlich ist der konkrete Verbrauch aufgrund der variablen Anzahl der Grafikobjekte vorher nicht festlegbar, dennoch aber läßt sich natürlich eine Struktur angeben, z.B.:

• Seitenbreite in Pixel (32 Bit)
• Seitenhöhe in Pixel (32 Bit)
• Hintergrundfarbe (16 Bit)
• Array mit Grafikobjekten (variabel)

Die OT/OB-Lib bietet nun in zweierlei Hinsicht einen besonderen Komfort, der sich durch die Deklarierbarkeit eigener Variablentypen ergibt: Beim Programmstart werden der Bibliothek nämlich durch Aufruf bestimmter Prozeduren - der ot_xxx-Prozeduren - mitgeteilt, wieviel Speicher für die Objekte der verschiedenen Typen jeweils benötigt wird. Die Bibliothek erinnert sich dann später beim Anfordern von Speicher selbst daran, wieviel Speicher nötig ist. Zum Zweiten wird der Speicher bei dieser Deklaration nicht konkret in der Summe angegeben, sondern es werden tatsächlich die einzelnen Komponenten aufgezählt. Dies ist von besonderer Bedeutung bei späteren Änderungen am Programm. Es können sehr einfach neue Komponenten hinzugefügt oder alte entfernt oder abgeändert werden, ohne dass man jedesmal Bits und Bytes zählen oder gar Variablenoffsets anpassen muss.

Listing 1 enthält zur Deklaration des DTP-Variablentyps die Prozedur dtp_typedef. Es wird darin zunächst über @ot_create eine Typkennung angefordert und in der globalen Variablen dtp_% gesichert. Dann werden die vier oben aufgezählten sog. „konkreten Attribute" (vgl. OT/OB-Lib-Doku) angemeldet, jeweils unter Angabe ihres Typs. Zurück erhält man dabei vier Attributkennungen, die in den globalen Variablen dtp_xxx_% hinterlegt werden.

Nun ist die Sprache zwischen dem Hauptprogramm und der OT/OB-Lib erweitert worden. Mit Angabe der neuen Kennungen kann man sich fortan auch über Variablen des Typs „DTP" unterhalten. Im Folgenden wird dabei von DTP-Objekten gesprochen.

Kursübersicht

Teil 1. Grundlagen, Fenster fester Grösse, Tastatureingaben, Echtzeitprogrammierung

Teil 2. Strukturierte Informationsspeicher mit Hilfe der OT/OB-Lib, größenveränderbare Fenster mit scrollbarem Inhalt

Teil 3. Mauseingaben, Selektion, nachscrollende Fenster

Teil 4. Menüs, Dialogfenster, Werkzeugleisten

Teil 5. Modulares Programmieren: Die Wrinkles

Listing 1

'
' OB: DTP
PROCEDURE dtp_typedef !call
    LET dtp_%=@ot_create
    LET dtp_w_%=@ot_attrib_create(dtp_%,long_%)
    LET dtp_h_%=@ot_attrib_create(dtp_%,long_%)
    LET dtp_bgcolor_%=@ot_attrib_create(dtp_%,word_%)
    LET dtp_objects_%=@ot_attrib_create(dtp_%,array_%)
RETURN

FUNCTION dtp(page_w%,page_h%,bgcolor&) !call
    LOCAL ob#
    LET ob#=@ob_create(dtp_%)
    @dtp_init(ob#,page_w%,page_h%,bgcolor&)
    RETURN ob#
ENDFUNC
PROCEDURE dtp_init(ob#,page_w%,page_h%,bgcolor&) !call
    @long_init(@ob_attrib(ob#,dtp_%,dtp_w_%))
    @long_init(@ob_attrib(ob#,dtp_%,dtp_h_%))
    @word_init{@ob_attrib(ob#,dtp_%,dtp_bgcolor_%))
    @array_init(@ob_attrib(ob#,dtp_%,dtp_objects_%),link_%,0)
    '
    @long_set(@ob_attrib(ob#,dtp_%,dtp_w_%),page_w%)
    @long_set(@ob_attrib(ob#,dtp_%,dtp_h_%),page_h%)
    @word_set(@ob_attrib(ob#,dtp_%,dtp_bgcolor_%),bgcolor&)
RETURN
PROCEDURE dtp_exit(ob#) !call
    LOCAL objects#,max%,i%
    LOCAL link#
    '
    @long_exit(@ob_attrib(ob#,dtp_%,dtp_w_%))
    @long_exit(@ob_attrib(ob#,dtp_%,dtp_h_%))
    @word_exit(@ob_attrib(ob#,dtp_%,dtp_bgcolor_%))
    '
    LET objects#=@dtp_objects(ob#)
    LET max%=@array_max(objects#)
    '
    FOR i%=0 TO max%
        LET link#=@array_get(objects#,i%)
        @gr_free_sub(@link_get(link#))
        @link_exit(@array_get(objects#,i%))
    NEXT i%
    '
    @array_exit(objects#)
RETURN
PROCEDURE dtp_free(ob#) !call
    @dtp_exit(ob#)
    @ob_free(ob#,dtp_%)
RETURN
PROCEDURE dtp_gr_ob_add(ob#,gr_ob#) !call
    LOCAL objects#
    LOCAL new_size%,new_link#
    '
    LET objects#=@dtp_objects(ob#)
    LET new_size%=@array_size(objects#)+1 
    @array_size_set(objects#,new_size%)
    LET new_link#=@array_get(objects#,new_size%-1)
    '
    @link_init(new_link#)
    @link_set(new_link#,gr_ob#)
RETURN
PROCEDURE dtp_gr_ob_delete(ob#,nr%) !call
    LOCAL objects#
    LOCAL link#
    '
    LET objects#=@dtp_objects(ob#)
    LET link#=@array_get(objects#,nr%)
    '
    @gr_free_sub(@link_get(link#))
    @link_exit(link#)
    @array_delete(objects#,nr%,1)
RETURN
FUNCTION dtp_w(ob#) !call
    $F%
    RETURN @long_get(@ob_attrib(ob#,dtp_%,dtp_w_%))
ENDFUNC
FUNCTION dtp_h(ob#) !call
    $F%
    RETURN @long_get(@ob_attrib(ob#,dtp_%,dtp_h_%))
ENDFUNC
FUNCTION dtp_bgcolor(ob#) !call
    $F%
    RETURN @word_get(@ob_attrib(ob#,dtp_%,dtp_bgcolor_%))
ENDFUNC
PROCEDURE dtp_draw(ob#,output#,off_x%,off_y%,cx&,cy&,cw&,ch&) !call 
    LOCAL objects#,max%
    LOCAL i%,link#,gr_ob#
    LOCAL ox&,oy&,ow&,oh&
    '
    @output_color_set(output#,@dtp_bgcolor(ob#))
    @output_fillstyle_set(output#,fillstyle_100_#)
    @output_boundary(output#,0)
    @output_draw_rectangle(output#,off_x%(off_y%,off_x%+@dtp_w(ob#)-1,off_y%+@dtp_h(ob#)-1)
    '
    LET objects#=@dtp_objects(ob#)
    LET max%=@array_max(objects#)
    '
    FOR i%=0 TO max%
        LET link#=@array_get(objects#,i%)
        LET gr_ob#=@link_get(link#)
        '
        LET ox&=ADD(@gr_x(gr_ob#),off_x%)
        LET oy&=ADD(@gr„y(gr_ob#),off_y%)
        LET ow&=@gr_w(gr_ob#)
        LET oh&=@gr_h(gr_ob#)
        '
        IF RC INTERSECT(cx&,cy&,cw&,ch&,ox&,oy&,ow&,oh&)
            @gr_draw(gr_ob#,output#,off_x%,off_y%)
        ENDIF 
    NEXT i%
RETURN
FUNCTION dtp_objects(ob#)
    RETURN @ob_attrib(ob#,dtp_%,dtp_objects_%)
ENDFUNC
Abbildung 1: Einstellungen im Hauptdialog.

Standardtypen und erweiterte Typen

Über einen Sachverhalt in der Deklarationsphase wurde noch nicht gesprochen. Es geht um die Frage, von welchen Typen die Komponenten sein können, die man bei einem neuen Typ anmeldet. Hierzu bietet die OT/OB-Lib einige Standardtypen (etwa BYTE, WORD, LONG, STRING, ARRAY usw.). Hat man aber aufbauend auf den Standardtypen einen neuen (sog. erweiterten) Typ deklariert, so kann ab diesem Zeitpunkt auch dieser wieder in neuen Typen als Komponente eingesetzt werden. Wir werden hier z.B für die im Array zu sichernden Grafikobjekte (also Linien, Rechtecke und Ellipsen) auf den Objekttyp gr_% (bzw. gr_ line_% und gr_filled_%) zurückgreifen. Dieser Typ ist der OT/OB-Lib als Beispielsource GRAPHIC.LST beigefügt.

Die Operationsphase

In der auf die Deklarationsphase folgenden Operationsphase kann nun mit Objekten der deklarierten Typen gearbeitet werden. Dazu können über bestimmte Prozeduren - die ob_xxx-Prozeduren - beliebig viele Objekte angelegt, es kann auf den Inhalt bestehender Objekte zugegriffen und es können bestehende Objekte wieder freigegeben werden.

Beim Anlegen eines Objektes - auf diesen Vorgang wird gleich noch etwas genauer eingegangen - erhält man von der Funktion ob_create eine Objektkennung in Form einer Fließkommazahl. Zum Zugriff auf den Objektinhalt bieten die Standard-Objekttypen Prozeduren an, denen man diese Objektkennung wieder übergibt. Die verfügbaren Prozeduren sind natürlich vom Typ abhängig - so gibt es für die Typen BYTE, CARD, WORD, LONG und FLOAT jeweils Routinen zum Setzen und Auslesen des Wertes (byte_set, byte_get usw.), für Arrays aber Routinen zum Verkleinern und Vergrössern des Arrays und zum Ermitteln der Objektkennungen der im Array gesicherten Objekte (array_size_set, array_get usw.).

Zum Zugriff auf Objekte der erweiterten Typen, die ja aus Komponenten bestehen, dient die Funktion ob_attrib.

Sie ermittelt die Objektkennung einer bestimmten Komponente (dazu wird die Kennung der Komponente, z.B. dtp_w_%, angegeben), und nun kann über diese Objektkennung auf die Komponente Zugriffen werden, als sei sie ein eigenständiges Objekt. In Listing 1 kann man dies z.B. in den Funktionen dtp_w, dtp_h oder dtp_bgcolor sehen, wo einfach nur die in den Komponenten dtp_w_%, dtp_h_% und dtp_bgcolor_% gesicherten Werte (Seitenbreite, Seitenhöhe und Hintergrundfarbe) ermittelt und zurückgegeben werden.

Listing 2

FUNCTION page1_open 
    $F%
    '
    ' Grafikobjekte definieren: 
    '
    page1:
    DATA LINE,10,120,536,1,1,1,H 
    DATA ELLI,10,130,536,300,1 
    DATA BOX,10,440,400,200,3 
    DATA BOX,30,460,400,200,4 
    DATA LINE,10,10,200,100,2,3,H 
    DATA LINE,10,10,200,100,2,3,V 
    DATA LINE,10,10,200,100,2,3,LORU 
    DATA LINE,10,10,200,100,2,3,LURO 
    DATA LINE,346,10,200,100,2,3,H 
    DATA LINE,346,10,200,100,2,3,V 
    DATA LINE,346,10,200,100,2,3,LORU 
    DATA LINE,346,10,200,100,2,3,LURO 
    DATA "END"
    '
    ' Fenster öffnen
    '
    RESTORE page1
    RETURN @page_open(8*72,11*72,14) ! A4 bei 72dpi, Farbe: gelb
    ' HINWEIS: Bei Schwarzweiß-Monitoren Hintergrundfarbe weiß 
    ' benutzen!!
ENDFUNC

Initialisierung des Objektspeichers

Beim Anlegen eines Objektes ist es normalerweise mit der Zuteilung eines Speicherbereiches nicht getan. Dieser Speicherbereich sollte sogleich nämlich auf einen gültigen Wert gebracht werden, ehe mit dem Objekt tatsächlich operiert wird. Oft gehören zu einem Objekt neben dem Objektspeicher auch noch weitere Resourcen, die ebenfalls an dieser Stelle angefordert werden müssen.

Zur Initialisierung eines Objektspeichers gibt es in der OT/OB-Lib zu jedem Objekttyp der Standardtypen eine Prozedur namens xxx_init. Für erweiterte Objekttypen - also auch für den Typ dtp_ % aus Listing 1 - muss diese Prozedur ebenfalls eingeführt werden. Innerhalb dieser Prozedur werden dann zunächst die einzelnen Komponenten selbst initialisiert. In dtp_init in Listing 1 sind dies die ersten vier Zeilen: ein Aufruf für jede der vier Komponenten. Erst anschließend können die Komponenten benutzt werden und sie werden sodann mit Werten belegt, um das sie beinhaltende, also das eigentlich zu initialisierende Objekt korrekt einzurichten. In dtp_init in Listing 1 sind dies die letzten drei Zeilen. Wie man hier sieht, können einer xxx_init-Prozedur auch Werte übergeben werden, um die Initialisierung zu parametrisieren. Da das Anlegen eines neuen Objektes stets aus der Kombination eines Aufrufs von ob_create (Speicher anfordern) und xxx_init (Speicher initialisieren) besteht, wird für jeden Objekttyp auch eine Funktion eingeführt, die beide Dinge auf einmal erledigt (vgl. die Funktion dtp aus Listing 1).

Tabelle 1: Schnittstelle und Interna der DTP-Objekte

Objekttyp

   
dtp_% Dient dem Speichern eine Seite fester Größe und fester Hintergrundfarbe, die eine beliebige Anzahl von Objekten des Typs gr_ % enthalten kann; es ist ferner eine Prozedur vorhanden, mit der die Seite gezeichnet werden kann.

Anwendbare Unterprogramme

Standard-Unterprogramme

   
dtp_typedef Typdeklaration
FLOAT @dtp(w%,h%,bgcol&) Legt ein DTP-Objekt mit der Größe w%, h% (Angaben in Pixeln) und der Hintergrundfarbe bgcol& an und gibt die Objektkennung zurück.
dtp_init(ob#,w%,h%,bgcol&) Einrichten eines DTP-Objektes; Parameter analog zu @dtp().
dtp_exit(ob#) Aufräumen eines DTP-Objektes
dtp_free(ob#) Freigeben eines DTP-Objektes

Abfragefunktionen

   
LONG @dtp_w(ob#) Ermittelt die Seitenbreite (in Pixel) eines DTP-Objektes.
LONG @dtp_h(ob#) Ermittelt die Seitenhöhe (in Pixel) eines DTP-Objektes.
WORD @dtp_bgcolor(ob#) Ermittelt die Hintergrundfarbe eines DTP-Objektes.

Operationen

   
dtp_gr_ob_add(ob#,gr_ob#) Fügt in ein DTP-Objekt ein neues Grafikobjekt ein.
dtp_gr_ob_delete(ob#,nr%) Löscht das Grafikobjekt mit der Nummer nr% aus einem DTP-Objekt; das erste Objekt hat die Nummer 0.
dtp_draw(ob#,output#,off_x%, Zeichnet das DTP-Objekt über das Ausgabeobjekt output#; die Ausgabe wird um den Offset off_x%, off_y% verschoben. Es
off_y%,cx&,cy&,cw&,ch&) werden nur Objekte gezeichnet, die nach der Verschiebung noch das Clippingrechteck cx&,cy&,cw&,ch& schneiden.

Interne Unterprogramme

Abfragefunktionen

   
FLOAT @dtp_objects(ob#) Ermittelt die Objektkennung des Arrays mit den Grafikobjekten (vom Typ gr_%) eines DTP-Objektes.

Interne Variablen (konkrete Attribute)

   
dtp_w_% long_%-Objekt mit der Seitenbreite in Pixeln
dtp_h_% long_%-Objekt mit der Seitenhöhe in Pixeln
dtp_bgcolor_% word_%-Objekt mit der Hintergrundfarbe
dtp_objects_% Array mit link_%-Objekten; enthält die Verweise auf die Grafikobjekte (vom Typ gr_%).

Listing 3

PROCEDURE page_init
    LET page_window_&=1 ! Userhandle
RETURN
FUNCTION page_open(w%,h%,bgcolors)
    LOCAL handle&,index& 
    LOCAL page#
    '
    LET handle&=@win_open(" DTP Fenster ","",dtp_window%,-1,w%,h%,8,8,page_window_&,20,40,300,300,-1)
    '
    IF handle&=0 
        LET index&=-1 
    ELSE
        LET index&=@win_get_index(handle&)
        '
        LET page#=@dtp(w%,h%,bgcolor&)
        @page_read(page#)
        @page_store(index&,page#)
        '
    ENDIF
    '
    RETURN index& 
ENDFUNC
PROCEDURE page_close(index&)
    LOCAL page#
    '
    @page_load(index&,page#)
    @dtp_free(page#)
RETURN
PROCEDURE page_close_all 
    LOCAL index&
    FOR index&=0 TO PRED(max_number_windows&)
        IF window_array&(index&,0)<>0 ! Fenster benutzt?
            IF window_array&(index&,1)=0 ! Userwindow?
                IF window_array&(index&,8)=page_window_& ! Page-Fenster?
                    @page_close(index&)
                ENDIF 
            ENDIF 
        ENDIF 
    NEXT index& 
RETURN
PROCEDURE page_read(page#)
    LOCAL typ$,ob#,x%,y%,w%,h%,cS,form$,thickness&
    '
    DO
        READ typ$
        EXIT IF typ$="END"
        '
        SELECT typ$
        CASE "LINE"
            LET ob#=@gr_line 
        DEFAULT
            LET ob#=@gr_filled 
        ENDSELECT
        '
        READ x%,y%,w%,h%,c& 
        @gr_x_set(ob#,x%)
        @gr_y_set(ob#,y%)
        @gr_w_set(ob#,w%)
        @gr_h_set(ob#,h%)
        @gr_color_set(ob#,c&)
        '
        SELECT typ$
        CASE "LINE"
            READ thickness&,form$
            @gr_line_thickness_set(ob#,thickness&)
            SELECT form$
            CASE "H"
                @gr_line_form_set(ob#,gr_line_form_horizontal_&)
            CASE "V"
                @gr_line_form_set(ob#,gr_line_form_vertikal_&)
            CASE "LORU"
                @gr_line_form_set(ob#,gr_line_form_loru_&)
            CASE "LURO"
                @gr_line_£orm_set(ob#,gr_line_form_luro_&)
            ENDSELECT 
        CASE "BOX"
            @gr_filled_form_set(ob#,gr_filled_form_rectangle_&)
        CASE "ELLI"
            @gr_filled_form_set(ob#,gr_filled_form_ellipse_&)
        ENDSELECT
        '
        @dtp_gr_ob_add(page#,ob#)
        '
    LOOP
RETURN
PROCEDURE page_draw(index&,off_x%,off_y%,cx&,cy&,cw&,ch&)
    LOCAL page#
    LOCAL wx&,wy&,ww&,wh&
    '
    @win_get_workarea(index&,wx&,wy&,ww&,wh&)
    '
    @page_load(index&,page#)
    @dtp_draw(page#,screen#,-off_x%,-off_y%,SUB(cx&,wx&),SUB(cy&,wy&),cw&,ch&) 
    '
RETURN
PROCEDURE page_store(index&,page#)
    LET window_tree%(index&,6)={V:page#}
    LET window_tree%(index&,7)={ADD(V:page#,4)}
RETURN
PROCEDURE page_load(index&,VAR page#)
    {V:page#}=window_tree%(index&,6)
    {ADD(V:page#,4)}=window_tree%{index&,7)
RETURN

Verweisobjekte

Solange ein Objekt Bestandteil eines anderen ist, kommt man an seine Kennung wie erwähnt durch Aufruf der Funktion ob_attrib heran. Oftmals muss man aber eine Objektkennung eines Objektes auch an anderer Stelle zur Verfügung haben. Man denke an den Fall eines Adressdatenbankprogramms, das die Datensätze gleichzeitig nach verschiedenen Kriterien sortiert verfügbar haben muss - vielleicht einmal nach Name, und einmal nach Wohnort. Man wird nicht die kompletten Datenbank zweimal anlegen, sondern üblicherweise nur einmal. Dazu ergänzt man dann zwei sortierte Listen, die nicht die Datensätze selbst, sondern nur Verweise (sog. Links) auf die Datensätze beinhalten.

Bei der OT/OB-Lib ist jede Objektkennung ein solcher Verweis, denn die Daten sind natürlich nicht in der Kennung enthalten, sondern werden nur über die Kennung adressiert. Um sich einen Verweis auf ein Objekt zu merken, genügt es also, sich diese Kennung - also eine Fließkommazahl - zu merken. Die OT/OB-Lib bietet aber mit gleichem Speicherverbrauch wie float_% den speziellen Typ link_% an, der zusätzlich noch den Wert „ungültiger Verweis" annehmen kann.

In Listing 1 wird für das DTP-Objekt ebenfalls der Typ link_% benötigt. In dem Array, in dem die einzelnen Grafikobjekte abgelegt werden, werden dort nämlich „nur" Verweise auf die Grafikobjekte abgelegt. Dies kommt hier nicht daher, dass die Grafikobjekte semantisch nicht als Bestandteil des DTP-Objektes zu werten wären, sondern weil die Grafikobjekte je nach Untertyp (Linie, Ellipse, Rechteck...) einen unterschiedlichen Speicherverbrauch haben. Dies kommt durch den Einsatz der sog. Einfachvererbung zustande (vgl. OT/OB-Lib-Doku ot_inherit). Solche Objekte dürfen laut OT/OB-Lib-Doku nicht als Komponente oder in Arrays eingesetzt werden.

Damit ist nun erklärt, wieso in dtp_init das Objekt-Array nicht mit Objekten des Typs gr_%, sondern mit Objekten des Typs link_% initialisiert wird.

Aufräumen des Objektspeichers

Wenn beim Anlegen eines Objektes oder im Betrieb neben dem Objektspeicher auch andere Resourcen angefordert wurden, so müssen diese beim Freigeben eines Objektes natürlich ebenfalls mit freigegeben werden. Deshalb gibt es in der OT/OB-Lib analog zu den xxx_init-Prozeduren für jeden Objekttyp auch xxx_exit-Prozeduren. Im Falle der erweiterten Typen werden hier nach der Freigabe solcher Resourcen (in Listing 1 ist mangels Anforderung auch keine Freigabe erforderlich) entsprechend die xxx_exit-Prozeduren der einzelnen Komponenten aufgerufen (vgl. dtp_exit).

Bei Arrays muss man darauf achten, dass auch für alle noch bestehenden Arrayelemente die xxx_exit-Prozedur aufgerufen wird, ehe das Array selbst mit array_exit bedient wird. In dtp_exit in Listing 1 findet man dazu eine Schleife mit link_exit-Aufrufen für das Grafikobjekte-Array. Da hier zudem die Grafikobjekte (trotz Implementation mit Hilfe der Verweise) wie vereinbart als Bestandteil des freizugebenden DTP-Objektes interpretiert wurden, werden nicht nur die Verweise „aufgeräumt", sondern zuvor in der gleichen Schleife auch die durch sie vertretenen Grafikobjekte explizit freigegeben (Aufruf gr_free_sub). Nach den Aufräumarbeiten kann der Objektspeicher dann tatsächlich über die Prozedur ob_free freigegeben werden. Genau wie beim Anlegen eines Objektes gibt es auch zum Freigeben aus praktischen Gründen für jeden Objekttyp eine vorgefertigte Prozedur xxx_free die genau diese beiden Arbeiten übernimmt. Eine Prozedur dtp_free war somit auch für die DTP-Objekte zu implementieren. Somit sind die fünf grundlegenden Unterprogramme für OT/OB-Lib-Typen erklärt:

• PROCEDURE xxx_typedef
• FUNCTION xxx
• PROCEDURE xxx_init
• PROCEDURE xxx_exit
• PROCEDURE xxx_free

Typabhängige Operationen

Je nach Typ gibt es wie bereits erwähnt dann eine Sammlung typabhängiger Funktionen und Prozeduren. Für die DTP-Objekte sind in Listing 1 sechs solcher Unterprogramme vorhanden, die in Tabelle 1 dokumentiert wurden.

Man erkennt in den Prozeduren dtp_gr_ob_add und dtp_gr_ob_delete wieder eine Besonderheit: Auch im Zusammenhang mit der Veränderung der Arraygröße gilt, dass nach dem Vergrößern die neuen Objekte initialisiert, und vor dem Verkleinern die Objekte „aufgeräumt" werden müssen. Dies geschieht ganz genauso wie in dtp_init und dtp_exit.

Die Prozedur dtp_draw zeichnet ein DTP-Objekt. Dabei wird zuerst der Hintergrund in der Hintergrundfarbe gemalt, anschließend werden die Objekte in geordneter Reihenfolge gezeichnet, sofern sie das Clippingrechteck schneiden.

Abbildung 2: Einstellungen für den Userfenstertyp „dtp_window“

Scrollbare Fenster

Nun geht es darum, das implementierte DTP-Objekt zu nutzen. Dazu soll exemplarisch ein solches Objekt angelegt, mit Grafikobjekten gefüllt und mit Hilfe von faceVALUE wie eingangs besprochen in ein scrollbares Fenster eingeblendet werden.

Es genügt hier ebenfalls, wie im letzten Kursteil wieder eine minimale RSC-Datei, die in faceVALUE eingeladen wird. Den anzulegenden Fenstertyp nennen wir dieses Mal „dtp_window". Nun wollen wir bei den Window-Flags aber auch die Fensterelemente zum Scrollen des Fensterinhaltes und zum Ändern der Fenstergröße aktivieren (s. Abb. 2). Gleichzeitig werden einige Optionen auf der linken Seite aktiviert, deren Bedeutung Sie der Dokumentation von faceVALUE entnehmen können.

Im faceVALUE-Hauptdialog (s. Abb. 1) sind zwei Einstellungen zu tätigen: Zum einen ist eine Extraroutine anzuwählen, die sich „Keyb. scroll Userw." nennt. Mit ihr können später bequem die Cursortasten zum Scrollen des Fensterinhalts per Tastatur ausgewertet werden. Zum anderen soll das Array window_tree%() um zwei Einträge erweitert werden. Diese acht Bytes werden wir nutzen, um die Objektkennung des im DTP-Fenster einzublendenden DTP-Objektes abzulegen. Diese Kennung hat als Fließkommazahl genau eine Größe von acht Bytes.

Lassen Sie von faceVALUE eine LST-Datei erstellen und laden Sie diese in den GFA-Interpeter. Anschließend können Sie an die vorgesehene Stelle im Listing (vgl. Kursteil 1) die OT/OB-Lib, die Objekte gr_%, fillstyle_% und output_% aus der Datei GRAPHIC.LST sowie das Listing 1 hinzufügen. Vergessen Sie nicht, entsprechend in der Prozedur typedef die Aufrufe @fillstyle_typedef, @gr_typedef, @output_typedef und @dtp_typedef hinzuzufügen.

Programmierung des Fensters

Im Gegensatz zum Tetrisfenster aus dem letzten Kurs soll das DTP-Fenster nun so implementiert werden, dass beliebig viele DTP-Fenster geöffnet werden können. Listing 3 enthält die dazu nötigen Routinen. Das Öffnen eines Fenster soll über die Prozedur page_open geschehen, so wie es in Listing 2 exemplarisch gezeigt ist. Nach dem Öffnen soll die Verwaltung der Fenster komplett automatisch funktionieren. Um dies zu erreichen ist es notwendig, dass beim Eintreten eines Fensterereignisses vom betroffenen Fenster auf seinen Typ und seinen konkreten Inhalt rückgeschlossen werden kann. Dann nämlich können die Routinen, die ja die Ereignisse behandeln, selbst entscheiden, wie das Ereignis bezüglich dieses Fensters zu behandeln ist. Dieses Ziel wird wie folgt erreicht:

Erstens: Typunterscheidung durch Userhandle

Zunächst wird je nach Fenstertyp (z.B. Editorfenster, Pixelgrafikfenster ober auch DTP-Fenster) das Ereignis an eine Prozedur weitergereicht, die sich mit diesem Fenstertyp auskennt. Es stellt sich aber die Frage, woran denn dieser „Weiterreicher" den Fenstertyp erkennen könnte. Userfenstern kann man beim Öffnen dazu ein sog. Userhandle übergeben. Dies ist eine vom Programmierer wählbare Word-Zahl (userhandle&; vgl. win_open), die bei Fensterereignissen stets wieder von der faceVALUE-Engine angegeben wird (vgl. user_window_content).

Für DTP-Fenster wählen wir das Userhandle „1" (Listing 3, Prozedur page_init). Für andere Fenstertypen würde man nun eine andere Zahl wählen. In Listing 4, welches die Eintragungen in die user-Prozeduren zeigt, sieht man bei user_window_content und user_win_close_ok, wie aufgrund des Userhandles die zum DTP-Fenster passenden Routinen aus Listing 3 aufgerufen werden. Bei einem anderen Userhandle würde man entsprechend in die passenden Routinen eines anderen „Fensterfachmanns" weiterleiten.

Zweitens: konkrete Unterscheidung durch Fenster-Arrays

Diese Routinen aus Listing 3 wissen nun, dass es sich um ein DTP-Fenster handelt. Sie wissen aber noch nicht, um welches der möglicherweise mehreren. Hier kommt wie angesprochen das Fensterarray window_tree%() zum Einsatz, wo für jedes Fenster individuelle Daten abgelegt werden können. Die beiden im faceVALUE-Hauptprogramm angeforderten LONG-Variablen haben dort an den Indizes 6 und 7 (allg. 6ff) Platz gefunden. Über die Routinen page_store und page_load kann nun eine Objektkennung (8 Bytes) in diese beiden 4-Byte-Plätze abgelegt und wieder herausgelesen werden. Wird beim Öffnen des Fensters wie in page_open die Objektkennung des DTP-Objektes vermerkt, kann diese von page_draw, page_close und page_close_all wieder ermittelt werden. Auf diese Weise werden nun DTP-Fenster nach dem Öffnen stets korrekt verwaltet. Das Öffnen des Beispielfensters aus Listing 2 wurde in user_on_ open eingebaut. Es ist ohne weiteres möglich, aus Listing 2 weitere Prozeduren page2_open, page3_open usw. abzuleiten und so mehrere, unabhängige Fenster zu öffnen. In vielen Fällen wird man auch das Öffnen verallgemeinern, und die Daten nicht aus einem DATA-Feld, sondern entweder aus einer Datei nehmen (Menüpunkt „Dokument öffnen"), oder eine leere Seite anlegen (Menüpunkt „Neues Dokument").

Es sollte Ihnen aufgefallen sein, dass hier jedes DTP-Fenster genau ein wirklich eigenes DTP-Objekt besitzt. Denn dieses DTP-Objekt wird beim Öffnen des Fensters angelegt und beim Schließen wieder freigegeben.

Mitscrollender Fensterinhalt

Es bleibt noch zu erwähnen, wie realisiert wurde, dass der Fensterinhalt mit den Scrollbalken gescrollt werden kann. Dies ist aber sehr einfach erklärt: Die faceVALUE-Engine übergibt an user_window_content die beiden Parameter off_x% und off_y%, die die aktuelle Verschiebung in Pixeln beinhalten. Es ergibt sich folgende Regel: Wenn etwas in das Fenster gezeichnet werden soll, das sich entsprechend dem Scrollbalken mitbewegt, so ist beim Zeichnen von allen Koordinaten off_x% bzw. off_y% abzuziehen. Soll sich etwas nicht mit den Scrollbalken mitbewegen, so ist nichts abzuziehen.

Ausblick

Im nächsten Kursteil werden zum ersten Mal Mauseingaben behandelt. Die Objekte des DTP-Fensters sollen selektierbar und verschiebbar werden. Dabei soll das Fenster an den Rändern automatisch nachscrollen.

[1] RUN! Software, - friendly applications Vorgartenstraße 9, D-66424 Homburg, info@run-software.de, http://www.run-software.de

Listing 4

PROCEDURE user_rsc_var_init 
    @libs_init 
    @typedef 
    @page_init 
RETURN
PROCEDURE user_on_open 
    IF @page1_open=-1
        LET exit_program!=TRUE 
    ENDIF
    LET screen#=@output 
RETURN
PROCEDURE user_window_content(index&,userhandle&,off_x%,off_y%,cx&,cy&,cw&,ch&) 
    ~GRAF_MOUSE(256,0)
    SELECT userhandle&
    CASE page_window_&
        @page_draw(index&,off_x%,off_y%,cx&,cy&,cw&,ch&)
    ENDSELECT 
    ~GRAF_MOUSE(257,0)
RETURN
PROCEDURE user_keyb(handle&,userhandle&,index&,ks&,key&)
    IF index&>=0
        @win_keyb_scroll(index&,handle&,ks&,key&)
    ENDIF 
RETURN
FUNCTION user_win_close_ok (index&, userhandle&)
    $F%
    SELECT userhandle&
    CASE page_window_&
        @page_close(index&)
        LET exit_program!=TRUE 
        RETURN TRUE 
    ENDSELECT 
ENDFUNC
PROCEDURE user_win_close_all 
    @page_close_all 
    LET exit_program!=TRUE 
RETURN
PROCEDURE user_on_exit 
@output_free(screen#)
    @libs_exit
RETURN

Holger Herzog
Aus: ST-Computer 09 / 2000, Seite 28

Links

Copyright-Bestimmungen: siehe Über diese Seite