Manipulationen des RSC-Files Teil 2

In dieser Folge wird die Frage des Einbindens von eigenen Grafiken in ein Menü beantwortet. Ein wenig Mühe lohnt sich doch.

Ein Menü ist nur eine Sonderform eines Dialoges. Es wird mit den gleichen Programmierwerkzeugen aufgebaut. Nur die Reihenfolge der einzelnen Objekte unterliegt starren Vorschriften.

Bevor wir uns ins Detail begeben, muß ich die Datenstrukturen, wie sie hier verwendet werden, erläutern. Es könnte ja sein, daß Sie dieses Progrämmchen in eine andere Sprache, z.B. Modula oder C, übertragen wollen.

Jeder Menüeintrag ist ein „Object“. Ein Objekt ist hier wie folgt beschrieben:

OBJECT = RECORD
    next,head,tail  : INTEGER;
    typ             : INTEGER;
    flags           : OFlagSet;
    state           : OStateSet;
    spec            : ObjSpec;
    space           : Rectangle;
END;

Space ist wiederum ein Verbund mit den Komponenten x,y,w,h:INTEGER. Er beschreibt also die Lage und Größe eines Rechteckes. X und y bestimmen den Ort des oberen linken Punktes, w und h die Breite bzw. Höhe.

Spec ist auch ein Verbund mit folgendem Aussehen:

ObjSpec = RECORD 
    CASE BOOLEAN OF
        FALSE: (lettThick :INTEGER;
                color :INTEGER);
        TRUE ; (more :POINTER);
    END;

Dieser Record wirkt, zugestanden, sehr verwirrend. Er kann zum einen den Buchstaben und die Rahmendicke, kombiniert in lettThick, und die Farbe eines Objektes beschreiben, zum anderen eine Adresse auf eine weitere Struktur, wie z.B. auf einen BitBlock, wie wir ihn noch benötigen werden, enthalten.

Wie weiß denn das Programm, was nun gemeint ist? Was bedeutet das BOOLEAN?, Was ist denn nun TRUE oder FALSE? Zum Verständnis muß ich ein wenig ausholen. Trifft der Compiler auf einen Verbund, so legt er für jede Komponente Variablenplatz an. Betrachten wir noch einmal den TYPE Rectangle. Er lautet:

Rectangle = RECORD 
    x,y,w,h : INTEGER;
END;

Für ein Programm ist es ega1, wie Sie diesen Verbund beschreiben, nicht jedoch, wenn Sie Speichermanipulationen vornehmen wollen. Im obigen Fall wird zuerst die Variable x, dann y usf. angelegt, also von links nach rechts. Es gibt aber auch Compiler, die beginnen mit der Ablage bei der Typbezeichnung. Dort würde demnach die Komponente h zuerst abgelegt. Zur ersten Gruppe gehört MAXON Pasca1, zur zweiten Gruppe z.B. Megamax-Modula 2. Bei dieser Modulaversion müßte der Verbund so lauten:

Rechteck = Record
        x:INTEGER; 
        y:INTEGER; 
        usf.

Jeder Verbundkomponente muß also unmittelbar der Typ folgen.

In unserer ObjSpec-Definition legt der Compiler 4 Bytes an, 2 Bytes für lettThick und 2 Bytes für color. Für more vom Typ POINTER wird kein weiterer Speicher reserviert. Es werden die gleichen 4 Bytes benutzt. Bitte merken Sie sich diese Aussage gut. Man kann nun diesen Umstand für Tricks benutzen. Ein ähnlicher Verbund könnte z.B. aus zwei Lang Wörtern (zusammen 64 Bits) und einer REAL-Variablen (z.B. auch 64 Bits) bestehen. Weisen Sie der REAL-Variablen einen Wert zu und lesen Sie ihn mit den beiden Langwörtern wieder aus. Wenn Sie diese Lang Wörter dann bitweise darstellen, können Sie sehr schön die interne Struktur einer Fließkommavariablen erkennen.

Abb. 1: Das Ausgangsmenü

Nun zu dem BOOLEAN: Das muß da ganz einfach stehen, um den Compiler zu befriedigen. Es handelt sich hier um einen Varianten Record, der wohl erfunden wurde, als Speicherplatz noch teuer war. Die Varianten Teile brauchen übrigens nicht gleich groß zu sein, der Compiler berücksichtigt bei der Speicherplatzvergabe den größeren Teil. Was da hinter CASE steht, ist ziemlich gleichgültig. Es müssen nur die Typen stimmen. DerREAL-Langwort-verbund könnte auch so aussehen:

Tricki = RECORD
    CASE : INTEGER OF
        237 ; (lang1,lang2 : LONGINT);
        499 : (fliess : REAL);
    END;

Sie können also unserem ObjSpec entweder eine Adresse (more) oder die anderen Werte zuweisen. Wir werden hier die Adresse des Hundebildchens eintragen.

Doch bis dahin ist noch ein wenig Aufmerksamkeit und Mühe notwendig.

Aus Bild wird Text...

... und zwar mit Hilfe des Programmes RscPict. Jenes bewirkt, daß ein im Screen-format (32000 Bytes) auf Disk abgelegtes Bild eingelesen wird. Als Beispiel wurde hier Bild 3 verwendet, das wohl vielen dank STAD bekannt ist. Mit der Maus wird ein Rahmen um den gewünschten Ausschnitt gelegt. Dieser Ausschnitt wird in einen Puffer kopiert und auf Wortbreite gebracht. Von dort wird er als ASCII-Datei abgespeichert. Das Aussehen ist aus dem Kommentar im Listing 3 zu ersehen, von dem nur ein Teil zur Anschauung abgedruckt ist.

Diese Datei kann dann später per INCLUDE-Anweisung in das Testprogramm Listing 2. eingebunden werden. Diese Prozedur muß einmal gestartet werden, um die Anfangsadresse der Bilddaten zu erfahren. Die reine Abfrage der Prozedurstartadresse führt beim MAXON Pascal zu einer Falschinformation, da im Prozedurkopf immer einige Bytes für die Assembler-Anweisung LINK A6,#0 eingesetzt werden. Es werden immer die beiden Konstanten imgBreite (in Bytes) und img-Hoehe (in Pixeln) ausgegeben. Die Prozedur trägt immer den Namen BitData. Ändern Sie diese Namen ggfs, entsprechend Ihren Wünschen um.

Aus Menüitem wird ein Bild

Erzeugen Sie nun bitte mit dem RSC-Editor ein einfaches Menü wie in Bild 1.

Den drei Strings unter dem Titel Datei geben Sie die Namen, wie sie in diesem Bild auch als Text erscheinen. Erzeugen Sie mit dem RSC-Maker (Folge 1) eine Unit. Binden Sie die Konstanten aus der I-Datei (RSC-Editor-Ausgabe auf Pascal stellen!) unmittelbar in das Programm (Listing 2) MenuBeispiel ein. Übernehmen Sie aus der Unit, die der RSC-Maker geliefert hat, die Variable TreeAddr.

Abb. 2: Das Programmziel

Sie können natürlich auch die AES-Funk-tionen für RSC benutzen, falls Sie den RSC-Maker nicht anwenden wollen. Statt menu := TreeAddr^[..] müssen dann RSCLoad(name) und menu := GetAddr(..) benutzt werden. Vergessen Sie am Schluß dann nicht das RSCFree.

Die Aufgabe besteht nun im Verbiegen des zweiten Eintrages unter „Datei“. Statt Eintrag2 soll das Hundebild erscheinen. Dieses ist sicher größer als der einfache Text „Eintrag2“. Ist der Hund breiter, müssen auch „Eintrag 1“ und „Eintrag3“ verbreitert werden. Zusätzlich muß „Ein-trag3“ noch nach unten verschoben werden, da der Hund sicherlich auch höher wird als der Text „Eintrag2“.

Alle drei String-Objekte werden von einer Box umschlossen. Auch diese muß vergrößert werden. Diese Box hat immer (!) den Index „oberer String -1“, hier also „Eintrag1 -1“.

Das mittlere String-Objekt erhält den Typ 23 für ein Bitimage. Bei diesen Typen zeigt spec aus der Objektstruktur auf die Struktur ObjSpec. Die Komponente more verweist auf einen Verbund namens Bit-Block mit folgendem Aussehen:

BitBlock = RECORD
    data        : POINTER;
    bytes,h,x,y : INTEGER; 
    color       : INTEGER;
END;

Eine Variable von diesem Typ muß erzeugt und gefüllt werden. Data übergeben wir die Adresse der Bit-Daten, die vorher durch den Aufruf der Prozedur BitData erfragt wurde. Die 4 folgenden Werte werden berechnet, color wird mit $1100 angenommen.

Ist dies geschehen, kann spec.more auf diesen Block zeigen. Nach der Korrektur der Lage und Größe ist aus unserem einfachen Stringobjekt ein Bitimage-Objekt geworden. Testen Sie Ihr Programm.

In der nächsten Folge wird dieses Hündchen um den Dialog aus Folge 1 herumlaufen. Da wird es noch einmal etwas haarig.

Bruno Volkmer

Literatur:

Handbuch Maxon-Pascal
Handbuch Megamax Modula 2, ASH
Aumiller, Luda, Möllmann: GEM-Programmierung in C, Verlag Markt&Technik
Diverse Artikel in ST-Computer

(************************************************
*                                               *
*                      Listing 1                *
*                                               *
*              BitBilder aus Screendumps        *
*                                               *
* Es wird ein Quelltext erzeugt, der zuerst     *
* die beiden Konstanten imgBreite (in Bytes)    *
* und imgHoehe (in Pixelzeilen) ausgibt.        *
* Diese werden für den BitBlock benötigt.       *
* Danach wird d. PROCEDURE BitData gener.       *
* I.BitBlock-Parameter 'data' ist d. Adresse    *
* der Daten aus dieser Prozedur anzugeben.      *
* Der Text kann direkt in den Programmtext      *
* eingefügt o. per INCLUDE-Option eingelesen    *
* werden.                                       *
*                                               *
* B.Volkmer                                     *
* (c) 1991 MAXON Computer                       *
*                                               *
************************************************)

PROGRAM RscPict;
(*$F+*)

USES Gern, VDI, AES, DOS, GrafBase, NumConv;

TYPE
        PA = ARRAY[0..16000] OF INTEGER;
        PicPointer = ^PA;
VAR
        BSize           : LONGINT;
        handle          : INTEGER;
        ok              : BOOLEAN;
        SerAdr          : POINTER;
        lese            : POINTER;
        rezol           : INTEGER;
        inpfad,outpfad  : PathStr;
        indirekt,outdirekt : DirStr;
        inname,outname  : NameStr;
        inext,outext    : ExtStr;
        inFile,outFile,
        UrPfad, 
        egal,flabel,
        FileIn,FileOut  : STRING[60];
        fhandle         : FILE;
        f               : Text;
        gelesen         : INTEGER;
        rahmen          : Rectangle;
        SScreen, SPuffer : PtrMemFormDef;
        Screen ,Puffer  : MemFormDef;
        PiPoi           : PicPointer;
        hxs             : STRING;

(**********************************************
* 32000 Byte großen Screendump von Disk laden *
***********************************************)

FUNCTION BildLaden:BOOLEAN; (* v.Disk einlesen *) 
BEGIN
    inPfad := '\*.*';       (* Pfadvorgabe *)
    inFile := #0;           (* Vorgabe Filename *)
    flabel := 'Bild einlesen';
    ExtSelectFile(inPfad,inFile,flabel,ok);
    IF Ok THEN BEGIN
        HideCursor(handle);
        ClearWorkstation(handle);

        FSplit(inPfad,indirekt,inname,inext);
        FileIn := indirekt + inFile;
        (*$I-*)
        Reset(fhandle,FileIn);
        (*$I+*)
        IF (IoResult = 0) then BEGIN
            BlockRead(fhandle,ScrAdr^,BSize, gelesen);
            Close(fhandle);
            BildLaden := TRUE 
        END
    ELSE BildLaden := FALSE;

    ShowCursor(handle,TRUE);
    END 
    ELSE Bildladen := FALSE;

END; (* BildLaden *)

(********************************************
* Mauserechteck aufziehen.                  *
********************************************)

FUNCTION MausEcke(VAR eck : Rectangle):BOOLEAN; 
VAR buts : MButtonSet;
    keys : SpecialKeySet; 
    pu   : Point; 
    endp : Rectangle; 
    istok: BOOLEAN; 
    r    : Rectangle;
BEGIN
    REPEAT

        REPEAT
            GrafMouse(pointHand,NIL);   (* Mausdruck? *)
            MouseKeyState(pu,buts,keys);
        UNTIL ((msBut1 IN buts) OR (msBut2 IN buts )) & (pu.y > 19);
        GrafMouse(arrow,NIL);

        IF msBut1 IN buts THEN BEGIN
            (* wenn links, Rahmen ziehen *)
            GrafMouse(thinCross,NIL);
            Reet(pu.x,pu.y,16,8,r);
            RubberBox(r, endp); 
            eck := endp;
            GrafMouse( arrow,NIL); 
            istok := TRUE 
        END 
        ELSE
            istok := FALSE;
    UNTIL (eck.y > 19) & ((eck.w >= 32) & (eck.h >= 16));
    MausEcke := istok;
END; (* MausEcke *)

(**********************************************
* den Rahmen in einen Buffer bringen          *
***********************************************) 
VAR PicSize : INTEGER;
PROCEDURE BildSichern(bR : Rectangle);
VAR
    dR          : Rectangle;
BEGIN
    GetPhysMemForm(Screen);(* MemForm erfragen *) 
    GetMem(SScreen,SIZEOF(MemFormDef)); (* Schirm *)
    GetMem(SPuffer,SIZEOF(MemFormDef)); (* Ziel *)
    SScreen^:= Screen;
    IF (bR.w MOD 16) <> 0 THEN INC(bR.w,16); (* auf Wortlänge! *) 
    bR.w := (bR.w DIV 16) * 16;
    PicSize := (bR.w DIV 8) * bR.h; (* Bytes * Höhe *)
    GetMem(PiPoi,SIZEOF(PicSize)); (* Pufferplatz *)

    WITH SPuffer^ DO BEGIN (* MemForm vom Ziel *)
        start := PiPoi; (* der Puffer *)
        w := bR.w; (* Breite in Pixel *)
        h := bR.h; (* Hoehe in Pixel *)
        words:= w DIV 16; (* Breite in Worten *)
        standardForm := 1;
        CASE rezol OF 
            2: planes := 1; (* hohe Auflösung, 1 Ebene *)
            1: planes := 2; (* mittlere Auflösung, 2 Ebenen *)
            0: planes := 4; (* niedrig. Auflösung, 4 Ebenen *)
        END;
    END;

    dR := bR;
    WITH dR DO BEGIN x := 0; y := 0; END;

    (* Kopiere vom Screen das Rechteck bR in das 
    links oben im Ziel SPuffer liegende Rechteck dR *)

    CopyOpaque(handle,SScreen,SPuffer, bR,dR, OnlyS);
END (* BildSichern *);

(***********************************************
* und eine Prozedur aus den Bilddaten erzeugen,*
* die wie folgt aussehen wird:                 *
*   PROCEDURE BitData;                         *
*   BEGIN                                      *
*       ASM                                    *
*           DC.W $1234,.....                   *
*           .                                  *
*       END;                                   *
*   END BitData;                               *
***********************************************)

FUNCTION Aufbereiten:BOOLEAN;
VAR i,j,l : INTEGER;
    w : INTEGER;
    code : INTEGER;

BEGIN
    outPfad := '\*.INC';
    outFile := '       .INC';
    flabel  := 'Hexdatei abspeichern';
    ExtSelectFile(outPfad,outFile,flabel,ok);
    IF ok THEN BEGIN
        FSplit(outPfad,outDirekt,outname,outext);
        FileOut := Concat (outDirekt,outFile);

        ReWrite (f,FileOut);
        Write(f,'CONST imgBreite = ');
        Str(SPuffer^.words * 2:10,hxs);
        WriteLn (f,hxs,';');
        Write(f,'        imgHoehe = ');
        str(SPuffer^.h:10,hxs);
        WriteLn(f,hxs,';');
        WriteLn(f,' VAR BitStart : POINTER;');
        WriteLn(f);
        WriteLn(f,'PROCEDURE BitData; ');
        WriteLn(f,'ASSEMBLER;');
        WriteLn(f,'  ASM ’);
        WriteLn(f,'  LEA @start,A0 '); 
        writeln(f,'  MOVE.L A0,BitStart ');
        WriteLn(f,'  BRA @fertig 1);
        WriteLn(f,'  @start:');
        l := SPuffer^.words * SPuffer^.h;
        FOR i := 0 TO 1 DO BEGIN 
            w := PiPoi^[i]; 
            hxs := IntToHex(w);
            IF i MOD 8 = 0 THEN
                hxs := '  DC.W '+ hxs 
            ELSE
                hxs := ',' + hxs;
            Write(f,hxs);
            IF (i+1) MOD 8 =0 THEN WriteLn(f);
        END;
        WriteLn(f);
        WriteLn(f,'  @fertig:1);
        WriteLn(f,'  END;');
        Close (£);
        AufBereiten := TRUE;
    END (* wenn ok *)
    ELSE Aufbereiten := FALSE;
END (* Aufbereiten *);

VAR abbruch : BOOLEAN; 
    but : INTEGER;
    fs : STRING;

BEGIN (************* Hauptprogramm *************) 
    InitGem(2,handle,ok);
    IF ok THEN BEGIN 
        IF ~GemError THEN BEGIN
            GetScreen(ScrAdr,ScrAdr,rezol);
            BSize := 32000; 
            abbruch := FALSE;
            REPEAT 
                fs := '[1][ Bild mit FSel.Box|einlesen ][Verstanden]' ;
                formalert(1,fs,but);
                IF BildLaden THEN BEGIN
                    FormAlert(1,'[1][ Rahmen mit Maus | aufziehen ][Verstanden]',but);
                    IF MausEcke(rahmen) THEN BEGIN 
                        BildSichern(rahmen); 
                        fs :='[1][Text mit FSel.Box|abspeichern][Verstanden]';
                        formalert(1,fs,but);
                        IF Aufbereiten = FALSE THEN 
                            FormAlert(1,'[1][Fehler beim Speichern][schade]', but) ;
                            FreeMem(PiPoi,SizeOf(PicSize));
                    END;
                END; 
                fs :='[2][ Nochmal das Ganze ][ Nein | ja ]'; 
                formalert(1,fs,but);
                IF but = 1 THEN abbruch := TRUE;
            UNTIL abbruch;
        END;
        ExitGem(handle) ;
    END;
END (* RscPict *).

(*************************************************
*                                                *
*                    Listing 2                   *
*                                                *
*   kleines Beispielprogramm zum Einbinden von   *
*   Bildern in Dialoge und Menus                 *
*   B: Volkmer                                   *
*   (C) 1991 MAXON Computer                      *
*                                                *
*************************************************) 

Program Menubeisp;

Uses AES, VDI, GEM, GrafBase, Menubild;

(* Die Konstantendatei des RSC-Editor wird hier der Übersichtlichkeit wegen gleich eingefügt *)

CONST
    Hundmenu = 0;  (* Menuebaum *)
    Hundinfo = 7;  (* STRING in Baum HUNDMENU *)
    Eintrag1 = 16; (* STRING in Baum HUNDMENU *)
    Eintrag2 = 17; (* STRING in Baum HUNDMENU *)
    Eintrags = 18; (* STRING in Baum HUNDMENU *)

VAR
    handle      : INTEGER;
    ok,abbruch  : BOOLEAN; 
    menu        : PtrObjTree;
    HundEck     : Rectangle;
    Button,i    : INTEGER;
    msg         : MessageBuffer;
    HundBlock   : BitBlock;

(*$I F:\PASCAL\FOLGE.2\BEIPACK\HUND.INC *)
(* ASCII-Datei des Hundebildchens, Pfad *)
(* selbst anpassen                      *)

PROCEDURE MachHundeEintrag;
BEGIN
    BitData; (* Hundeadresse holen; Bitdata dazu ausführen *)
    WITH HundBlock DO BEGIN (* Eintrag2 wird BitBlock *) 
        data  := BitStart; 
        x     := 0;
        y     := 0;
        h     := ImgHoehe;
        bytes := ImgBreite; 
        color := $1100; (* angenommen *)
    END;

(* Eintrag2 liegt zwischen Eintrag 1 und   *) 
(* Eintrag 3. Die Breiten beider müssen    *) 
(* eventuell vergrößert werden.            *)
(* Alle drei Items umschließt eine GBox,   *) 
(* die natürlich angepaßt werden muß.      *)
(* Ferner wird Item "Eintrags*1 nach unten *) 
(* gerückt, da sich ein vergrößertes Item  *) 
(* "Eintrag2" m.d. Hund dazwischenschiebt  *) 
(* Die GBox hat den Index "Eintragi" -1!   *)

(* 1. wie groß ist mein Bildchen?          *)

    WITH HundEck DO BEGIN 
        x := 0; y := 0; 
        w := ImgBreite * 8; 
        h := ImgHoehe;
    END;

    (* 2. "Eintrag1" eventuell verbreitern *)

    WITH menu^[Eintrag1] DO
        IF space.w < HundEck.w THEN 
            space.w := HundEck.w;

    (* 3. "Eintrag3" anpassen und n. unten verschieben *)

    WITH menu^[Eintrag3] DO BEGIN 
        IF space.w < HundEck.w THEN 
            space.w := HundEck.w;

        (* Differenz der Höhen von Eintrag2 zu y addieren *) 
        space.y := space.y +
            (HundEck.h - menu^[Eintrag2].space.h);
    END;

    (* 4. jetzt die umschließende GBox korrigieren*)

    WITH menu^[Eintrag1-1] DO BEGIN 
        IF space.w < HundEck.w THEN 
            space.w := HundEck.w;

        (* und die Höhe korrigieren *) 
        space.h := space.h + (HundEck.h - menu^[Eintrag2].space.h);
    END;

    (* 5. jetzt den Typ von Eintrag2 ändern *)

    WITH menu^[Eintrag2] DO BEGIN 
        typ       := 23; (* ein BitImage *)
        spec.more := ADDR(HundBlock); (* der BitBlock*) 
        space.w   := HundEck.w; (* und die Maße *) 
        space.h   := HundEck.h;
    END
END (*MachHundeEintrag*);

BEGIN   (********* Hauptprogramm ************)

    InitGem(RC,handle,ok);
    IF Ok THEN BEGIN
        IF GemError = 0 THEN BEGIN 
            abbruch := FALSE; 
            menu := TreeAddr^[Hundmenu];

    (* jetzt Eintrag "Eintrag2" verändern und *) 
    (* Rest unter "Datei" anpassen            *)

            MachHundeEintrag;

            MenuBar(menu,TRUE);

            REPEAT
                MessageEvent(msg);
                NormalTitle(menu,msg.selTitle,TRUE); 
                CASE msg,selItem OF
                    Eintrag1: Write(CHR(7));
                    Eintrag2: FOR i := 1 TO 50 DO Write(CHR(7)); 
                    Eintrag3: abbruch := TRUE;
                END;
            UNTIL abbruch;
            MenuBar(menu,FALSE);
        END;
        ExitGem(handle) ;
    END;
END (*MenuBeispiel*).

(***********************************************
*                                              *
*           Listing 3                          *
*                                              *
* Diese Includedatei wird vom Program RscPic   *
* erzeugt.                                     *
* Sie wird später direkt in das Hauptprogramm  *
* eingefügt o. per INCLUDE-Option eingebunden  *
*                                              *
***********************************************)

(* Kommentare manuell hinzugefügt ! *)

CONST   imgBreite = 18;
        imgHoehe  = 76;

VAR BitStart : POINTER;

(*  Die Prozedur Bitdata muß vom einbindenden 
    Programm einmal gestartet werden, um die 
    Startadresse in die Variable BitStart zu 
    schreiben. Die Abfrage der Adresse der 
    Prozedur selbst ist falsch, da in MAXON-Pascal 
    grundsätzlich die Anweisung LINK A6,#0 
    vorgesetzt wird. *)

PROCEDURE BitData;
ASSEMBLER;
    ASM
    LEA @start,A0 
    MOVE.L A0,BitStart 
    BRA @fertig 
    @tart:
    DC.W $0000,$0000,$0000,$0000,$0000,$0000......
    .
    (* und viele weitere Zeilen gleiche Art *)
    .
    DC.W $0000,$0000,$0000,$0000,$0000 
    @fertig:
END;


Aus: ST-Computer 01 / 1992, Seite 138

Links

Copyright-Bestimmungen: siehe Über diese Seite