Wie oft begegnet einem diese Situation: Man klickt auf dem Bildschirm etwas an und muß in die Menüleiste, um die Option zu spezifizieren, damit sie ausgelöst werden kann.
Beim KUMA-Resource-Editor ist das ganz anders. Sobald man hier etwas anklickt, erscheint bei der Maus ein kleines Flipmenü, um einem den Weg in die Menüleiste zu ersparen. Die hier vorgestellte Routine leistet eben dieses: Sie kann ein Flipmenü an jedem Bildschirmpunkt (in der Cursor-Matrix 80x25) plazieren.
Diese Menüs werden nicht mit umständlichen GEM-Einträgen gestartet, sondern sind universell einsetzbar, sie initialisieren sich bei jedem Aufruf von selbst. Zur Einführung einige Worte zur...
Die Funktion bekommt beim Aufruf die Texte der Menüpunkte sowie die gewünschte Bildschirmposition des Menüs übergeben. Nach der Ausgabe auf dem Bildschirm wird nun ständig die Position der Maus und der Status der Maustasten abgefragt. Jenachdem wo sich die Maus befindet, wird der unter ihr liegende Menüpunkt invertiert. Befindet sich die Maus außerhalb des Menüs, wird nichts invertiert.
Werden die Maustasten aktiviert (rechts, links oder beide, das spielt keine Rolle), wird der invertierte Menüpunkt angewählt und an die aufrufende Prozedur die entspr. Eintragsnummer weitergegeben. Außerdem wird gemeldet, ob der Eintrag mit der rechten, linken oder gar mit beiden Maustasten angewählt wurde.
Von einer Funktion dieser Art verlangt man, daß nach deren Aufruf der Bildschirm wieder im urspr. Zustand vorliegt. Dies bedeutete für mich, der Einfachheit halber den gesamten Bildschirm zwischenzuspeichern. Entsprechendes leisten die Prozeduren Save_Screen und Restore_Screen (Z. 64-77), die wiederum bestimmte Typen und Variablen sowie die XBIOS-Funktion LogBase (XBIOS(2)) benötigen.
Um die Mausposition dauernd feststellen zu können, bediene ich mich der AES-Funktion 79: Graf_MKState. Auch sie benötigt spezielle Variablen und Typen (im Listing als solche gekennzeichnet).
Die Prozeduren write_xy und hiwrite_xy (Z. 78-86) dienen ebenso zum Darstellen des Menüs auf dem Bildschirm wie die Prozedur Rahmen (Z.88-100). All diese Prozeduren setzen ihre benötigten x-/y-Koordinaten als Koordinaten aus der Snap-Matrix (also 80x25) voraus.
Alle o.g. Prozeduren sind schon als solche brauchbar und nützlich in jedem Programm. Die eigentliche Funktion “FLIP” (Z. 105-188) setzt sie als vereinbart voraus. Aus diesem Grund sind sie auch mit aufgelistet.
Die Funktionsvereinbarung lautet:
FUNCTION Flip(x,y : INTEGER; was : Str255; VAR M_Keys : INTEGER)
: INTEGER;
Die Variablen x und y legen die obere linke Ecke des Menüs in der 80x25-Bildschirm-matrix fest. Im String “Was” finden sich die Menüeinträge, die jeweils durch einen Underscore getrennt sind.
Ein Beispiel: Man will ein Menü mit folgendem Aussehen erzeugen:
Dann muß der String so lauten: "Mami_Papi_Ich_und_Du".
Weitere Beispiele finden sich im Hauptprogramm des Listings. Jeder Menüpunkt kann eine Länge von max. 40 Zeichen haben, der gesamte Definitionsstring darf max. 255 Zeichen beinhalten. Die letztgenannte Grenze ist durch das Pascal gesetzt. In der Variablen M_Keys schließlich wird der Status der Maustasten zurückgegeben. Hierbei bedeutet 1, daß die linke Maustaste gedrückt wurde; 2 ist es bei der rechten, und bei beiden Tasten gleichzeitig ist es 3.
Zur Funktion selbst ist eigentlich nichts zu erwähnen. Die verschiedenen Unterprozeduren sind erstens sehr einfach und zweitens im Listing näher erläutert. So bleibt zum Schluß nur noch ein...
Die hier vorgestellte Flipmenü-Routine ist keineswegs vollständig. Sie stellt vielmehr eine Minimalversion dar. Denkbar wären z.B. DISABLE-te Menüeinträge die z.B. durch ein anderes Trennzeichen als dem Underscore, z.B. den Klammeraffen) kenntlich gemacht werden könnten. In diesem Fall müßten die Prozeduren write_xy und hiwrite_xy durch entspr. Draw_String-Anweisungen ersetzt werden (was jedoch mit einem Geschwindigkeitsverlust einhergeht).
Ein weiterer Vorschlag wäre, das Menü gleichzeitig auch über die Tastatur steuern zu können, um z.B. bestimmte Einträge mit den Funktionstasten anwählen zu können. All diese Möglichkeiten sind auf der Basis des gelieferten “Rohlistings” relativ einfach “nachzurüsten”. Solche Versionen der Funktion sind bei mir mittlerweile erfolgreich im Einsatz. Sie haben allerdings den Nachteil, den Rahmen dieser Programmierecke zu sprengen, weshalb ich mich auf die einfachste Version, die aber in den meisten Fällen durchaus ausreichend sein wird, beschränkt habe.
Das hier vorliegende Programm wurde mit ST-Pascal Plus 1.20 erstellt. Wer das Glück hatte, bereits mit einem Update versorgt zu werden, wird mit Sicherheit die eine oder andere Routine deutlich verkürzen oder sogar wegfallen lassen können.
Beispiel für eine verschobene Menüleiste
{$D-, S10} { Fehlermeldungen ausschalten }
PROGRAM Flipmenue;
{ Mausgesteuertes Flipmenue an beliebiger Bildschirmposition.
Die aufgefuehrten Variablen und Prozeduren gehoeren zur
benoetigten Umgebung der Routine. -
Autor: Udo Hilwerling,
Zumlohstr. 21 4410 Warendorf 1 }
CONST
{$1 GEMCONST.PAS }
TYPE
{$1 GEMTYPE.PAS }
{ Typen fuer AES-Aufruf }
AES_Pointer = ACHAR;
Int_In_Parms = ARRAY[0..15] OF INTEGER;
Int_Out_Parms = ARRAY[0..45] OF INTEGER;
Äddr__In_Parms = ARRAY[0. .1] OF AES_Pointer;
Addr__Out_Parms = ARRAY[0..0] OF AES_Pointer;
{ Typen zum Zwischenspeichern des Bildschirms }
Screen = PACKED ARRAY[1..32000] OF BYTE;
ScreenPointer =^Screen;
VAR
{ Variablen fuer AES-Aufruf }
int_in : Int_In_Parms;
int_out : Int_Out_Parms;
addr_in : Addr_In_Parms;
addr_out : Addr_Out_Parms;
ScreenBase : ScreenPointer; { Zeiger auf Bildschirmspeicher }
Schirm : Screen; { Hier wird der Bildschirm gepuffert }
test, mouse : INTEGER; { Testvariablen }
hilf : Str255;
{$I GEMSUBS.PAS }
{ AES-Prozeduren
------------------ }
PROCEDURE AES_Call(op : INTEGER;
VAR int_in Int_In_Parms;
VAR int_out : Int_Out_Parms;
VAR addr_in : Addr_In_Parms;
VAR addr_out : Addr_Out_Parms); EXTERNAL;
PROCEDURE Graf_MKState(VAR x, y, pr, key : INTEGER);
BEGIN
AES_Call(79, int_in, int_out, addr_in, addr_out);
x:=int_out[1]; y:=int_out[2];
pr:=int_out[3];
key:=int_out[4]
END;
{ ----------------------- }
FUNCTION LogBase : ScreenPointer;
XBIOS(2);
PROCEDURE Save_Screen; { Bildschirm Zwischenspeichern }
BEGIN
Hide_Mouse;
{$P-} Schirm:=ScreenBase^; {$P=}
Show_Mouse;
END;
PROCEDURE Restore_Screen; { Bildschirm wiederherstellen }
BEGIN
Hide_Mouse;
{$P-} ScreenBase^:=Schirm; {$P=}
Show_Mouse;
END;
PROCEDURE write_xy (wx,wy ; INTEGER; was : Str255);
BEGIN
write (#27,'Y',chr(wy+32), chr(wx+32) , was) ;
END;
PROCEDURE hiwrite_xy(wx, wy : INTEGER; was : Str255);
BEGIN
write(#27,'p'); write_xy(wx,wy,was); write(#27,'q')
END;
PROCEDURE Rahmen (x1, y1, x2, y2 : INTEGER);
{ Doppelrahmen zeichnen und weiss ausmalen }
VAR i : INTEGER;
BEGIN
Hide_Mouse;
x2:=x2+2;
x1:=(x1-1)*8+1; x2:=(x2-1)*8;
y1:= (y1-1)*16+6; y2: = (y2-1)*16+12;
Paint_Color(White); Paint_Rect(x1,y1,x2-x1,y2-y1);
Line_Color (Black) ;
Frame_Rect(x1,y1,x2-x1+1,y2-y1+1);
FOR i:=3 TO 4 DO
Frame_Rect(x1+i, y1+i, x2-xl-2*i+l, y2-y1-2*i+l) ;
Show_Mouse
END;
{ Die eigentliche Funktion, die jedoch obige Umgebung benoetigt.
-------------------------------- }
FUNCTION flip(x, y : INTEGER; { Bildschirmkoordinaten }
was : str255; { Menue-Eintraege }
VAR M_Keys : INTEGER) { Mausinfo }
: INTEGER;
TYPE Str40 = STRING[40];
VAR Feld : ARRAY[1..23] OF str40;
brstr : Str40;
Breite, anzahl,
akt, last,
xmin, xmax,
ymin, ymax,
xpos, ypos,
p, i : INTEGER;
FUNCTION wx (x : INTEGER) : INTEGER;
{ Rechnet Bildschirmkoordinaten um in
Pixelkoordinaten }
BEGIN wx:=x*8-7 END;
FUNCTION wy(y : INTEGER) : INTEGER;
{ s. wx }
BEGIN wy:=y*16-1 END;
PROCEDURE Aufspalten;
{ Menue-Eintraege auseinanderdividieren }
VAR l: INTEGER; h : Str255;
BEGIN
Breite:=0; l:=0; Anzahl:=1;
WHILE Pos ('_', was) <>0 DO BEGIN
{ '_' als Trennungzeichen }
l:=Pos('_',was)-1;
IF l>Breite THEN Breite:=1;
Feld[anzahl]:=Copy(was,1,1);
Delete(was,1,1+1);
anzahl:=anzahl+l
END;
Feld[anzahl]:=was;
IF length(was)>Breite THEN Breite:=length(was);
brstr :=Copy (' ',1, Breite);
{ ----------- 40 Spaces oben ------------- }
FOR l:=1 TO anzahl DO BEGIN
{ Felder gleichlang machen )
h:=Concat(' ',Feld[1],brstr) ;
Feld[l]:=Copy(h,l,Breite+2)
END;
Breite:=Breite+2
END;
PROCEDURE Fuellen; { Flipmenue fuellen }
VAR i : INTEGER;
BEGIN
Hide_Mouse;
FOR i:=1 TO anzahl DO write_xy(x,y+i-1,Feld[ij);
Show_Mouse;
END;
PROCEDURE Update; { Flipmenue aktualisieren }
BEGIN
Hide_Mouse;
IF last<>0 THEN write_xy (x, y+last-1, Feld [last]) ; { normal }
IF akt<>0 THEN hiwrite_xy (x, y+akt-1, Feld [akt]); { invers }
Show_Mouse
END;
BEGIN
akt:=0; last:-0; ScreenBase:=LogBase;
Aufspalten;
xmin:=wx(x+1); xmax:=wx(x+Breite+1);
ymin:=wy(y); ymax:=wy(y+anzahl);
Save_Screen;
Rahmen(x, y, x+Breite, y+Anzahl+1);
Fuellen;
REPEAT
Graf_MKState(xpos, ypos, M_Keys, i);
last:=akt;
IF ( (xpos>=xmin) AND (xpos<=xmax) ) AND
((ypos>=ymin) AND (ypos<=ymax))
THEN akt:=(ypos-2-ymin) DIV 16+1
ELSE akt:=0;
IF lastoakt THEN Update;
IF (M_Keys<>0) AND (akt=0) THEN write(#7)
UNTIL (M_Keys<>0) AND (akt<>0);
Restore_Screen;
flip:=akt
END;
{ ---------------------- }
{ Beispiel - Hauptprogramm }
BEGIN
IF Init_GEM>=0 THEN BEGIN
Init_Mouse;
hilf: =Concat ( { Hilfsstring fuer
Menue-Eintraege: }
' Datei laden__', { Eintraege werden durch den Underscore }
' ‘Datei speichern_', { (_) getrennt. }
' Datei neu erzeugen_',
' File umbenennen_',
' File loeschen_',
' Diskette pruefen_',
' Diskette formatieren _',
' Freier Speicherplatz');
test:=flip(15,3, hilf, mouse);
test:=flip(38,1,
' 1_2_3_4_5_6_7_3_9_10_11_12_13_14_15_16_17_18_19_20
_21_22_23', mouse);
writeln; writeln;
write('Eintrag ',test,' gewaehlt mit ');
IF mouse=1 THEN writeln('linker Maustaste.');
IF mouse=2 THEN writeln('rechter Maustaste.');
IF mouse=3 THEN writeln('beiden Maustasten.');
writeln('<RETURN>'); readln;
Exit_GEM
END
END.