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.
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.
... 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.
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.
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;