Wodan: Grüße aus Walhalla, Teil 4

Die Fertigstellung von Wodan steht bevor, heute kommen die letzten Module. An der Reihe sind die Implementationsmodule zur Kommunikation mit anderen Programmen und zur Behandlung allgemeiner GEM-Aufgaben. Ferner wird an einem einfachen Beispiel vorgeführt, wie mit einem fremden Programm die Parameter von Wodan manipuliert werden können.

Beginnen wir mit dem einfachen Modul GEMUtility. Dieses Modul stellt einige Routinen zur Maus oder besser zum Mauszeiger zur Verfügung. Dazu gehört, daß der Mauszeiger an- und ausgeschaltet und in seiner Form verändert werden kann. Statt der unter ST-GEM-Programmen üblichen Mausform "Biene" wird die unter anderen GEM-Programmen übliche Form einer Sanduhr benutzt. Dazu ist es nötig, eine frei definierte Mausform zu wählen.

Die dazugehörige Struktur wurde in dem Typ MouseImageStruct zusammengefaßt. Dazu benötigt man den Hot-Spot, den Punkt relativ zur linken oberen Ecke der 16x16 Bit-Struktur, der als Mausposition zählt, die Nummer der Farbebene sowie der Vorder- und der Hintergrundfarbe. Die Daten der Maske und der eigentlichen Mausform werden in jeweils 16 Word-Werten abgelegt. Um die Mausform automatisch zu setzen, wird die Modula-2-typische Möglichkeit der Initialisierung innerhalb der Implementationsmodule benutzt, damit der Programmierer/die Programmiererin damit keine Arbeit mehr hat.

Ferner gehören zu diesem Modul Prozeduren. die die Größe und Position von Objekten ermitteln sowie deren Status verändern.

Mit getTextString und setTextString werden Zeichenketten aus einer TEDINFO-Struktur, die zu den Objekttypen G_TEXT, G_BOXTEXT, G_FTEXT bzw. G_FBOXTEXT gehört, abgefragt bzw. gesetzt. Die Prozedur setTextChar setzt einen Buchstaben ein. der zu dem Objekttyp G_BOXCHAR gehört.

Ein Dialog wird mit prepareBox vorbereitet und mit releaseBox nachbereitet. Zur Vorbereitung gehören die Sperrung der Fenstererneuerung, die Zentrierung der Dialogbox, die Reservierung des Hintergrunds und die Ausgabe der Dialogbox. Zur Nachbereitung gehört nur noch die Freigabe des Hintergrunds und die Freigabe der Fenstererneuerung.

Sollte Ihnen die Fenstererneuerung über FormDial zu langsam sein, so kann diese durch eine eigenständige ersetzt werden. Dazu reserviert man sich temporär soviel Speicherplatz, wie die Dialogbox von der Fläche her gesehen belegt, kopiert den Hintergrund mit Hilfe der VDI-Rasterfunktionen in den reservierten Speicher und kopiert diesen nach Beendigung des Dialogs wieder zurück. Der Nachteil ist, daß zusätzlich Speicherplatz in der Größe der Dialogbox benötigt wird. Der Vorteil jedoch ist, daß die Hintergrundrestaurierung wesentlich schneller vollzogen wird. Soweit zu GEMUtility.

Die Kommunikation von Wodan mit anderen Programmen ermöglicht individuelle Tastaturbelegungen für diverse Programme. Jedes Programm, das ernsthaft die Tastaturbelegung ändert, sollte auch dafür Sorge tragen, daß die alte Belegung vor dem Verlassen des Programms wiederhergestellt wird. Das Beispielprogramm WO_CALL macht es jedoch nicht, da sonst der Effekt der Manipulation der Zeichenketten nicht sichtbar würde.

Eine Nachricht erhält Wodan über einen Message-Event. Dazu werden neue Nachrichten eingeführt, die nur Wodan und ein aufrufendes Programm verstehen können. Alle anderen Programme reagieren auf derartige Nachrichten nicht. Wodan selbst antwortet auf den Anruf durch ein fremdes Programm, indem sofort eine Nachricht zurückgeschickt wird.

Eine Nachricht besteht in der Regel aus einem 8 Word ( = 16 Byte) großen Feld, wobei Word 0 die Nachrichtennummer beinhaltet. Word 1 die Kennung des sendenden Prozesses und Word 2 die Überlänge der Nachricht. Wodan setzt also in Word 0 seine Nachrichtennummer, in Word 1 seine Applications-Identity und in Word 2 eine 0. Die restlichen 5 Words stehen zur freien Verfügung. Also sendet Wodan mit Word 3 und 4 die Adresse der Parameterstruktur und mit Word 5 eine Kennung, die dem aufrufenden Programm erlaubt, Wodan eindeutig zu identifizieren. Die übrigen 2 Words bleiben unbenutzt. Die so aufgebaute Nachricht wird via appl_write (AES 12) an den aufrufenden Prozeß (das fremde Programm) geschickt. Mehr macht Wodan schon nicht mehr.

Das fremde Programm - in unserem Beispiel WO_CALL - muß nicht in Modula-2 geschrieben sein, da Betriebssystem- und nicht Modula-2-Funktionen benutzt werden. Es ist also zur Abwechslung mit Turbo C geschrieben worden, läuft aber auch mit Megamax Laser C.

WO_CALL wird initialisiert, fragt die Identity von Wodan via appl_find (AES 13) ab und baut dann die Nachricht auf, die an Wodan geschickt wird. Anschließend wird auf die Antwort gewartet. Da auch ein anderes Programm den Namen WODAN tragen könnte, muß die Wartezeit begrenzt werden. Antwortet Wodan also nicht binnen einer Sekunde, bricht WO_CALL seine Arbeit ab und teilt dies mit. Sollte Wodan antworten, werden die ersten beiden Zeichenketten verändert. So einfach ist das!

So gibt Wodan seine Strings weiter

Nun können Sie beginnen, die restlichen Module einzugeben und das Accessory zu testen. Erweiterungsmöglichkeiten ergeben sieh noch reichlich. Wie wäre es zum Beispiel mit einem aktuellen Datum, das automatisch dann eingesetzt wird, wenn in einer Zeichenkette ein [%d] auftaucht.

Ich wünsche Ihnen viel Spaß mit Wodan und hoffe, daß Ihnen das Programm auch Anregungen für die zukünftige Programmierung bietet.

Literatur:

[1] Atari ST Profibuch, H.-D. Jankowski/J. F. Reschke/D. Rabich, 6. Auflage, Sybex-Verlag 1988/89, S467ff, 495ff

[2] GEM Programmier-Handbuch, P. Balma/W. Fitler, Sybex-Verlag 1988, S.35ff, 350f

(**************************************************************)
(* Modulname            : CommunicationWodan <IMPLEMENTATION> *)
(* by D. Rabich         : (c) MAXON Computer GmbH             *)
(* Datum                : 10. Juni 1989                       *)
(* letztes Edier-Datum  : 1. September 1989                   *)
(* Version              : 1.00a                               *)
(* Entwicklungssystem   : Megamax Modula-2                    *)
(**************************************************************)

IMPLEMENTATION MODULE CommunicationWodan;
(*$Q+,M-,N-,V+,P-,R-,S-*)

(* GEM-Routinen *)
FROM GEMEnv         IMPORT ApplicationID;
FROM AESEvents      IMPORT MessageBuffer;
FROM AESMisc        IMPORT WriteToAppl;

(* SYSTEM-Routinen *)
FROM SYSTEM         IMPORT ADR,ADDRESS;

(* Typen *)
TYPE ForeignBuffer  = RECORD (* spezieller Message-Puffer *) 
                        msgType : CARDINAL; 
                        msgID   : CARDINAL; 
                        msgOver : CARDINAL; 
                        msgSAdr : ADDRESS; 
                        msgSpID : CARDINAL; 
                        msgFree : LONGCARD 
                      END;

    PtrForeignBuffer = POINTER TO ForeignBuffer;

(* sendet Nachricht an aufrufende Applikation *) 
PROCEDURE SendToAppl (msgBuffer : MessageBuffer;
                      lParasAdr : ADDRESS);

    VAR LokalBuffer       : MessageBuffer;
        PtrForeignMBuffer : PtrForeignBuffer;
        Sender            : CARDINAL;

    BEGIN
        LokalBuffer       := msgBuffer;
        PtrForeignMBuffer := ADR(LokalBuffer);
        Sender            := PtrForeignMBuffer^.msgID;

        WITH PtrForeignMBuffer^ (* Nachricht zusammenstellen *)
        DO
            msgType ;= ForeignAnswer;   (* Art der Nachricht *)
            msgID   := ApplicationID(); (* Eigene ID *)
            msgOver := 0;               (* keine Überlänge *)
            msgSAdr := lParasAdr;       (* Adresse der Parameter *) 
            msgSpID := 10089;           (* magic*)
            msgFree := 0L               (* nicht benutzt +)
        END;

        WriteToAppl(Sender,LokalBuffer,0) (*senden*) 
    END SendToAppl;

END CommunicationWodan.

Listing 1

/**************************************************
/* Programmname        : wo_call                 */
/* by D. Rabich          (c) MAXON Computer GmbH */
/* Datum               : 10. Juni 1989           */
/* letztes Edier-Datum : 9. Oktober 1989         */
/* Version             : 1.00a                   */
/* Entwicklungssystem  : Turbo C 1.1             */
/**************************************************

/* Include-Dateien */
/* für Turbo C */
# ifdef __TURBOC__
# include <aes.h>

/* für Megamax Laser C */ 
# else
# include <gembind.h>
# define void
# endif

# include <string.h>

# define FOREIGN_CALL   0x4001 /* Aufruf von Mercur */
# define FOREIGN_ANSWER 0x4002 /* Antwort von Mercur */

/* Alert-Strings */
char time_out[]     = "[0][Time - Out!][ OK ]"; 
char antwort[]      = "[0][Wodan|antwortet!][ OK ] ";
char no_wodan[]     = "[0][Wodan antwortet|nicht korrekt.)( OK )"; 
char not_loadad[]   = "[0][Wodan ist|nicht präsent.][ OK ]";

/* Name von Wodan */ 
char wodan[] = "WODAN   ";

/* Struktur der Wodan-Parameter */ 
typedef struct
{
    int     aktiv; 
    int     position; 
    void*   strings;
} L_PARA;

/* spezielle Message zur Kommunikation mit Wodan */

typedef struct
{
    unsigned int msg_type; 
    unsigned int msg_id; 
    unsigned int msg_over;
    L_PARA*      msg_sadr;
    unsigned int msg_sp_id; 
    long int msg_free;
} MERCUR_MSG;

void main (void)
{
    int         apl_id,     /* Programm-ID      */
                wodan_id,   /* Wodan-ID         */
                mx, my,     /* für evnt_multi...*/
                mbutton, 
                mstate, 
                keycode, 
                mclicks, 
                event;
    MERCUR_MSG  message;    /* Message */
    L_PARA*     parameter;  /* Parameter */
    long        alt_strings; /* AdresseDerStrings*/

    apl_id = appl_init();   /* eigene ID */
    if (apl_id != -1)       /* alles OK? */
    {
        wodan_id = appl_find (wodan); /*ID v Wodan */

        if (wodan_id <!= -1) /* Wodan gefunden? */
        {
            message.msg_type = FOREIGN_CALL; /* Nachricht aufbauen */ 
            message.msg_id   = apl_id;
            message.msg_over = 0;

            appl_write (wodan_id, 16, &message); /* senden */

            event = evnt_multi (MU_MESAG | MU_TIMER,    /* Antwort abwarten */
                                1, 1, 1,
                                0, 0, 0, 0, 0,
                                0, 0, 0, 0, 0,
                                (int*) &message,
                                1000, 0,
                                &mx, &my, &mbutton, &mstate,
                                &keycode, &mclicks);

            if (event ( MU_TIMER) /* keine Antwort? */ 
                form_alert (1, time_out);

            else if (event & MU_MESAG) /* Antwort! */
            {
                if (message.msg_sp_id == 10089) /* spezielle ID OK? */
                {
                    parameter = message.msg_sadr; /* Adresse der Strings */ 
                    alt_strings = (long) parameter->strings; 
                    strcpy ( (char*) alt_strings,"Hallo");
                    strcpy ( (char*) (alt_strings + 80L) , "Welt!");

                    form_alert (1, antwort);
                }
                else
                    form_alert (1, no_wodan);
            }
        }
        else
            form_alert (1, not_loaded);

    appl_exit (); /* Auf Wiedersehen) */
    }
}

Listing 2

(*****************************************************)
(* Modulname           : GEMUtility (IMPLEMENTATION) *)
(* by D. Rabich          (C) MAXON Computer GmbH     *)
(* Datum               : 3. Juni 1989                *)
(* letztes Edier-Datum : 11. Oktober 1989            *)
(* Version             : 1.00b                       *)
(* Entwicklungssystem  : Megamax Modula-2            *)
(*****************************************************)

IMPLEMENTATION MODULE GEMUtility;
(*$Q+,M-,N-,V+,P-,R-,S-*)

(* GEM-Routinen *)
FROM GEMGlobals     IMPORT PtrObjTree, Root, ObjState, OStateSet, MaxDepth,
                           MButtonSet, SpecialKeySet ;
FROM AESForms       IMPORT FormDial, FormDialMode, FormCenter;
FROM AESGraphics    IMPORT MouseForm, GrafMouse, MouseKeyState;
FROM AESObjects     IMPORT ChangeObjState, DrawObject, ObjectOffset;
FROM AESWindows     IMPORT UpdateWindow;

FROM ObjHandler     IMPORT CurrObjTree, ObjectState, ObjectSpace, BorderThickness,
                           SetCurrObjTree, AssignTextStrings, SetPtrChoice,
                           GetTextStrings, ObjTreeError;

(* Graphik-Routinen *)
FROM GrafBase       IMPORT Rect, Rectangle, TransRect, Point;

(* Strings-Routine *)
FROM Strings        IMPORT String;

(* System-Routine *)
FROM SYSTEM         IMPORT ADR;

(* Typ *)
TYPE MouseImageStruct = RECORD
                            MfXhot    : CARDINAL;
                            MfYhot    : CARDINAL;
                            MfNPlanes : CARDINAL;
                            MfFg      : CARDINAL;
                            MfBg      : CARDINAL;
                            MfMask    : ARRAY[0..15) OF CARDINAL; 
                            MfData    : ARRAY[0..15] OF CARDINAL
                        END;

(* Variablen *)
VAR MouseImage : MouseImageStruct; (*Maus-Form*) 
    MousePos   : Point;

{* Maus "beschäftigt" *)
PROCEDURE ShowBusy;

    BEGIN
        GrafMouse (userCursor, ADR(MouseImage))
    END ShowBusy;


(* Maus als Pfail *)
PROCEDURE ShowArrow;

    BEGIN
        GrafMouse (arrow, NIL)
    END ShowArrow;


(* Maus sichtbar *)
PROCEDURE ShowMouse;

    BEGIN
        GrafMouse (mouseOn, NIL)
    END ShowMouse;


(* Maus unsichtbar *)
PROCEDURE HideMouse;

    BEGIN
        GrafMouse (mouseOff, NIL)
    END HideMouse;


(* Platzbedarf ermitteln *)
PROCEDURE objectSpace (obj : CARDINAL) : Rectangle;

    VAR space : Rectangle;

    BEGIN
        space := ObjectSpace (obj);
        IF shadowObj IN ObjectState (obj)  (* Schattierung beachten *)
        THEN
            space.w := space.w + 2 * ABS(BorderThickness(obj)); 
            space.h := space.h + 2 * ABS(BorderThickness(obj))
        END;
        RETURN space 
    END objectSpace;

(* Platzbadarf bzgl. Offset *)
PROCEDURE objOffsetSpace (obj : CARDINAL) : Rectangle;

    BEGIN
        RETURN TransRect (objectSpace(obj),ObjectOffset(CurrObjTree(), obj))
    END objOffsetSpace;


(* Status entfernen *)
PROCEDURE clearObjState (obj : CARDINAL; which : ObjState; redraw : BOOLEAN);

    VAR state : OStateSet;

    BEGIN
        state := ObjectState (obj);
        EXCL (state, which);
        ChangeObjState (CurrObjTree(), obj, objOffsetSpace(obj), state, redraw)
    END clearObjState;


(* Status setzen *)
PROCEDURE setObjState (obj : CARDINAL; which : ObjState; redraw : BOOLEAN);

    VAR state OStateSet;

    BEGIN
        state := ObjectState (obj);
        INCL (state, which);
        ChangeObjState (CurrObjTree(), obj, objOffsetSpace(obj), state, redraw)
    END setObjState;


(* Zeichenkette holen *)
PROCEDURE getTextString (tree    : PtrObjTree;
                         obj     : CARDINAL;
                         VAR str : ARRAY OF CHAR);

    VAR voidStr : String;

    BEGIN
        SetCurrObjTree (tree, FALSE);
        GetTextStrings (obj, str, voidStr, voidStr); 
        IF ObjTreeError ()
        THEN
            str[0]:=0C 
        END
    END getTextString;


(* Zeichenkette setzen *)
PROCEDURE setTextString (tree    : PtrObjTree;
                         obj     : CARDINAL;
                         VAR str : ARRAY OF CHAR);

    BEGIN
        SetCurrObjTree (tree, FALSE); 
        AssignTextStrings (obj, setOnly, str, noChange,'', noChange, '')
    END setTextString;


(* Character setzen *)
PROCEDURE setTextChar (tree     : PtrObjTree;
                       obj      : CARDINAL;
                       char     : CHAR);

    BEGIN
        SetCurrObjTree (tree, FALSE); 
        tree^[obj].spec.letter := char 
    END setTextChar;


(* Dialog vorbereiten *)
PROCEDURE prepareBox (tree : PtrObjTree) : Rectangle;

    VAR space   : Rectangle;
        Keys    : SpecialKeySet;
        Buts    : MButtonSet;

    BEGIN
        ShowArrow;           (* Maus als Pfeil *) 
        UpdateWindow (TRUE); (* Keine Fenstererneuerung *)
        SetCurrObjTree (tree, FALSE); 
        space := FormCenter (tree); (* zentrieren *)
        space := objOffsetSpace (Root); (* Schattierung beachten *)
        MouseKeyState (MousePos, Buts, Keys); 
        FormDial (reserveForm, Rect (MousePos.x, MousePos.y, 20, 20), space);
        (* Hintergrund reservieren *)
        FormDial (growForm, Rect (MousePos.x, MousePos.y, 20, 20), space);
        (* Rechtecke *)
        DrawObject (tree, Root, MaxDepth, space); (* Dialogbox ausgeben*)
        RETURN space 
    END prepareBox;


(* Dialog nachbereiten *)
PROCEDURE releaseBox (tree : PtrObjTree; space : Rectangle);

    BEGIN
        FormDial (freeForm, Rect (MousePos.x, MousePos.y, 20, 20), space);
        (* Hintergrund freigeben *)
        FormDial (shrinkForm, Rect (MousePos.x, MousePos.y, 20, 20), space);
        (* Rechtecke *)
        UpdateWindow (FALSE) (* Fenster erneuern *) 
    END releaseBox;


BEGIN

    (* Mausform setzen *)
    WITH MouseImage DO
        MfXhot      := $0;      MfYhot     := $0;   (* Hot-Spot                 *)
        MfNPlanes   := $1;                          (* Farbebenen               *)
        MfFg        := $0;      MfBg       := $1;   (* Vorder- und Hintergrund  *)
        MfMask[ 0]  := $3FFC;   MfMask[ 1] := $3FFC;(* Maske                    *)
        MfMask[ 2]  := $3FFC;   MfMask[ 3] := $1FF8;
        MfMask[ 4]  := $1FF8;   MfMask[ 5] := $1FF8;
        MfMask[ 6]  := $1FF8;   MfMask[ 7] := $0FF0;
        MfMask[ 8]  := $0FF0;   MfMask[ 9] := $1FF8;
        MfMask(10]  := $1FF8;   MfMask[11] := $1FF8;
        MfMask[12]  := $1FF8;   MfMask[13] := $3FFC;
        MfMask[14]  := $3FFC;   MfMask[15] := $3FFC;
        MfData[ 0]  := $0000;   MfData[ 1] := $1FF8;(* Daten                    *)
        MfData[ 2]  := $0810;   MfData[ 3] := $0C30;
        MfData[ 4]  := $0E30;   MfData[ 5] := $0FF0;
        MfData[ 6]  := $07E0;   MfData[ 7] := $0340;
        MfData[ 8]  := $0340;   MfData[ 9] := $0420;
        MfData[10]  := $0910;   MfData[11] := $0890;
        MfData[12]  := $0BD0;   MfData[13] := $0FF0;
        MfData[14]  := $1FF8;   MfData[15] := $0000
    END

END GEMUtility.

Dietmar Rabich
Aus: ST-Computer 04 / 1990, Seite 108

Links

Copyright-Bestimmungen: siehe Über diese Seite