Pascal-Toolbox Nr. 1
Mausunterstützung (DOS)
Als erstes Projekt dieser Ausgabe habe ich mir eine Unit zur
Mausunterstützung überlegt.
Zuerst legen wir eine Datenstruktur mit Maus-Informationen an:
TYPE TMaus=RECORD
Func,Taste,X,Y:Word;
TextModus:Boolean;
END;
VAR Maus:Tmaus;
"Taste" gibt an, welche Taste zuletzt gedrückt wurde, "X" und
"Y" sind die letzten Koordinaten des Mauszeigers und "Textmodus" gibt an, ob der Textmodus aktiv
ist oder nicht (Wenn ja, müssen X und Y entsprechen verändert werden, da der Maustreiber
die Koordinaten im Textmodus nur in 8er-Schritten ausgibt)
Als nächstes brauchen wir eine Routine, mit der die elementaren
Mausfunktionen (Initialisierung, Zeiger an, Zeiger aus) schnell ausgeführt werden
können:
PROCEDURE MausDa(Nr:Word);
INLINE($58/$cd/$33);
Wir verwenden hierzu die INLINE-Anweisung, die den Compiler dazu
bringt, den Code der Funktion bei jedem Aufruf der Funktion direkt in den Programmcode
einzufügen (statt eine Sprunganweisung zur Funktion zu schreiben). Dies hat zur Folge, das der
Programmablauf schneller wird, sich andererseits aber auch der Programmcode immens aufblähen
kann, da jeder Funktionsaufruf durch den kompletten Funktionscode ersetzt wird.
Die Folge $58/$cd/$33 steht für die
Assembler-Anweisungen POP AX; INT $33; und hat zur Folge, daß der
Funktionsparameter vom Stack geholt und in dem Register AX gesichert wird und dann der
Mausinterrupt $33 aufgerufen wird. Zulässige Werte für "Nr" sind 0 (Initialisierung), 1
(Zeiger an),2 (Zeiger aus).
Um die Bedienung zu Vereinfachen, führen wir noch eine Funktion
ein, die einfach nur zum An- bzw. Ausschalten des Mauszeigers dient:
PROCEDURE ShowMouse(Show:Boolean);
BEGIN
IF Show=True THEN MausDa(1)
ELSE MausDa(2);
END;
Je nach Wert von "Show" wird der Mauszeiger an-(Show=True) oder
ausgeschaltet(Show=False).
Im Prinzip würde "MausDa" schon reichen, um die Maus zu
initialisieren, aber es fehlt noch eine Prüfung, ob der Maustreiber überhaupt installiert
ist. Die folgende Funktion löst dieses Problem recht elegant:
FUNCTION MausReset(TextModus:Boolean):Boolean;
BEGIN
IF Mem[MemW[0:$CC+2]:MemW[0:$CC]]<>$CF THEN BEGIN
MausDa(0);
MausDa(1);
MausReset:=True;
END
ELSE BEGIN
MausReset:=False;
END;
Maus.TextModus:=TextModus;
END;
Zuerst wird geprüft, ob an der Stelle, auf die der
Interrupt-Vektor von $33 zeigt, wirklich ein Maustreiber installiert ist oder ob dort ein $CF
(=nicht installiert) steht. Hierzu muß erst herausgefunden werden, auf welche Stelle der
Interrupt-Vektor zeigt; dazu wird zuerst der Speicherplatz des Interuptvektors berechnet:
51(=$33)*4(Byte pro Int.-Vektor)=204. An 0:$CC und 0:$CC+1 steht das Offset der Speicherstelle, an
0:$CC+2 und 0:$CC+3 das Segment; um immer beide Bytes zu sichern, benutzt man MEMW.
Nun muß nur noch per MEM auf die Adresse (Segment:Offset) zugegriffen
werden.
Ist der Maustreiber installiert, wird die Maus initialisiert und
dann der Mauszeiger sichtbar gemacht. Der Rückgabewert der Funktion wird auf TRUE gesetzt, was
einen erfolgreichen Abschluß signalisieren soll. So kann der Anwender gleich prüfen, ob
ihm überhaupt eine Maus zur Verfügung steht und gegebenenfalls das Programm beenden. Ist
kein Treiber installiert, wird der Rückgabewert auf FALSE gesetzt.
Weiterhin wird der Funktion noch der Parameter "TextModus"
übergeben, der angibt, ob das Programm im Textmodus ausgeführt wird oder nicht;
entsprechend wird der Parameter "Maus.TextModus" gesetzt.
Für die weiteren Anwendungen benötigen wir eine erweiterte
Version der Funktion MausDa, die etwas flexibler (aber auch langsamer) ist:
PROCEDURE MausInt; ASSEMBLER;
ASM
MOV AX,Maus.Func;
MOV BX,Maus.Taste;
MOV CX,Maus.X;
MOV DX,Maus.Y;
INT $33;
MOV Maus.Func,AX;
MOV Maus.Taste,BX;
MOV Maus.X,CX;
MOV Maus.Y,DX;
END;
Zuerst werden die Register mit den (zuvor belegten) Mausvariablen
gefüttert, dann der Mausinterrupt ausgeführt und dann die Register wieder ausgelesen. Die
Mausvariablen übernehmen also praktisch die Funktion von Parametern der Funktion.
Nun brauchen wir nur noch eine Funktion, die die Mausvariablen neu
belegt:
PROCEDURE MausData;
BEGIN
Maus.Func:=3;
MausInt;
IF Maus.TextModus THEN BEGIN
Maus.X:=Maus.X DIV 8;
Maus.Y:=Maus.Y DIV 8;
END;
Maus.Y:=Maus.Y+1;
Maus.X:=Maus.X+1;
END;
Zuerst wird in "Maus.Func" die auszuführende Mausfunktion(3)
geschrieben und dann MausInt aufgerufen. MausInt füllt dann die Mausvariablen mit den
aktuellen Mausdaten. Danach wird geprüft, ob der Textmodus aktiv ist; ist dies der Fall,
werden die Mauskoordinaten durch Acht dividiert, da der Maustreiber im Textmodus die Daten nur in
8er-Schritten liefert. Dann werden die Koordinaten noch jeweils um Eins erhöht, damit der
Koordinatenursprung bei (1,1) beginnt (sonst (0,0)).
Die Prozedur muß ständig aufgerufen
werden, um ständig aktuelle Daten zu haben! (Beispiel s.u.)
Da es manchmal auch nötig ist, ein Mausfenster zu setzen, also
einen bestimmten Bereich des Bildschirms, in dem sich die Maus nur bewegen kann, führen wir
noch folgende Funktion ein:
PROCEDURE MausWindow(X1,Y1,X2,Y2:Integer);
BEGIN
Dec(X1); Dec(X2); Dec(Y1); Dec(Y2);
IF Maus.Textmodus=True THEN BEGIN
X1:=X1*8; X2:=X2*8; Y1:=Y1*8; Y2:=Y2*8;
END;
MausDa(2);
Maus.Func:=7;
Maus.X:=X1;
Maus.Y:=X2;
MausInt;
Maus.Func:=8;
Maus.X:=Y1;
Maus.Y:=Y2;
MausInt;
MausDa(1);
END;
Zuerst werden die vier Variablen (X1,Y1=linke,obere Ecke;
X2,Y2=rechte,untere Ecke) jeweils um Eins verkleinert, um die Koordinaten an das
Maustreiberkoordinatensystem (Ursprung(0,0)) anzupassen. Dann werden, falls der Textmodus gesetzt
ist, noch alle Parameter mit Acht multipliziert, da der Maustreiber auch hier in 8er-Schritten
zählt.
Dann wird der Mauszeiger abgeschaltet. Im Folgenden werden nun der
horizontale (Maus.Func=7) und der vertikale (Maus.Func=8) Bereich festgelegt, wobei jedesmal
mittels der Variablen Maus.X und Maus.Y die Start- und Endposition übergeben werden. Nach der
Belegung wird beide Male MausInt aufgerufen, um den Interrupt auszuführen. Zum Schluß
wird der Mauszeiger wieder angeschaltet.
So, das wars schon!
Ein rudimentäres Beispielprogramm könnte etwa so
aussehen:
USES CRT;
[...]
VAR Flag:Boolean;
[...]
BEGIN
IF MausReset(True)=False THEN HALT(1);
REPEAT
MausData {!!!}
GotoXY (20,20);
Write ('X:',Maus.X,' Y:',Maus.Y,'Taste:',Maus.Taste,' ');
UNTIL KEYPRESSED;
ReadKey;
ShowMouse(False); {WICHTIG!!!}
END.
Wichtig ist, daß, wenn Daten vom Maustreiber gebraucht werden,
die Prozedur "MausData" ständig (z.B. mittels einer Schleife) aufgerufen wird, um die
Variablen entsprechend zu belegen, und auch, daß der Mauszeiger vor dem Beenden des
Programmes ausgeschaltet wird.
Die Mausfunktionen funktionieren sowohl im Text- als auch in den
Standard-Grafikmodi von Pascal (16 Farben).
Wer weitere Informationen zum Thema sucht, dem empfehle ich das Buch
"Turbo Pascal 7.0 - Das Kompendium", erschienen im Markt&Technik-Verlag.
Tabelle 19 bietet eine
Übersicht über die Funktionen des Maus-Interrupts $33.
Die fertige Unit (ca. 1,5 KB) könnt Ihr hier downloaden.
Alle Angaben ohne Gewähr. |