Entwickeln mit ACS, Teil 3

Dies ist der dritte und vorläufig letzte Teil dieser Serie. Es wird gezeigt, wie die restlichen ACS-Editoren funktionieren, wie man mit Listenfenstern umgeht und wie Ziehoperationen programmiert werden. Selbstverständlich liegt auch dieser Folge ein Beispielprogramm bei, das die Benutzeroberfläche für ein grafisches Make (Projektverwaltung) implementiert.

Wir haben in den letzten beiden Folgen gesehen, wie man mit den wichtigsten Editoren von ACS umgeht. Für diese Folge blieben noch der Alarmbox- und der Pop-Up-Editor übrig, denn Icon-Editoren sind nicht das Thema einer Programmierpraxis.

Der Alarmbox-Editor

Im Generelles-Fenster findet man den Alarmbox-Editor unter dem Namen Alertboxen. Ein Klick öffnet eine Fensterliste, in die Sie wieder das Neu-Icon schieben müssen. Nach der Eingabe eines Namens, der hier viel wichtiger als zum Beispiel bei den Menüs ist, weil er aus dem Programm aus angegeben werden muß, öffnet sich der Editor. Sie sehen dort (Bild 1) eine Alarmbox, in der Sie Textzeilen und Knöpfe editieren können. Nur Einträge, in denen wirklich etwas steht, führen zu einer neuen Zeile bzw. einem Knopf. Wenn Sie auf das Icon der Alarmbox klicken, können Sie ein anderes auswählen. Da das Ergebnis oft von der Vorstellung ab weicht, kann man mit dem Button Probe die fertige Alarmbox sehen und sie gegebenenfalls wieder korrigieren. Lassen Sie sich von dem Default-Button nicht verwirren; der ist bei der Probe immer an erster Stelle. Im Programm können Sie dann selber bestimmen, welcher der Buttons als default gelten soll. Ein Aufruf sieht bei einer Alarmbox namens ERROR folgendermaßen aus:

 form_alert(2, ERROR);

wobei der zweite Button default ist. Es bietet sich oftmals an (zum Beispiel bei einer Vielzahl von Fehlermeldungen), eine ACS-Datei nur für Alarmboxen zu erstellen. Sie sollten dann alle Fehlertexte in ein Array packen und eine Funktion anbieten, die über den Index den passenden Fehler ausgibt. In diesem Zusammenhang wäre es schön, wenn man noch variable Werte mit in die Box ausgeben könnte. Wenn Sie an einer Stelle in der Alarmbox %s angeben, wird die ACS-Funktion alert_str dort einen String einfügen:

alert_str(COULDNT_OPEN, input_file_name);

Der erste Knopf wird automatisch default. Eine bequemere Methode bietet die Funktion form_value (Listing 1). Man kann den Default-Button angeben, und die Parameterzahl und die Parametertypen sind variabel (entspricht printf):

form_value(2,"[1][Datei: ‘%s’|Fehler in Zeile: %d][Abbruch|Weiter]", fname, lineno);

Selbstverständlich hätte dieser Text auch im Alarmbox-Editor erstellt werden können.

Bild 1: Leichtes Editieren einer Alarmbox

Der Pop-Up-Editor

Pop-Up-Menüs (Bild 2) sind eine sinnvolle Einrichtung, um Entscheidungen ‘vor Ort’ zu treffen. Das bedeutet, daß man nicht immer mit der Maus zur Menüleiste fahren muß, um Optionen auszuwählen. Der Editor gestaltet sich ähnlich wie ein Menüleisten-Editor, nur hat man bei Pop-Ups vielfältigere Möglichkeiten zur Gestaltung. Prinzipiell ist ein Pop-Up-Menü ein Vaterobjekt, dessen Kinder zur Auswahl stehen. Wie diese angeordnet oder welchen Typs sie sind, ist völlig gleichgültig. Nur ein Typ hat besondere Bedeutung: Ist ein Eintrag ein Titel (Title), wird eine weitere Ebene in der Hierarchie geöffnet, ein Submenü. Es bietet wieder die Möglichkeit, einen Titel einzuführen. Diese hierarchischen Menüs sollten sparsam verwendet und die Titel am rechten Rand durch einen Pfeil gekennzeichnet werden (Bild 2).

Es gibt zwei Formen der Pop-Up-Menüs: die einen werden aufgerufen, wenn man zum Beispiel über einem ‘großen’ Objekt einen Klick ausübt, dabei erscheint ein Pop-Up an den aktuellen Mauskoordinaten, oder aber, es gibt in einer Dialogbox ein Objekt das schattiert ist, was aussagen soll, daß dieses Objekt mehrere Optionen anbietet, unter denen man aus wählen kann. Bei einem Klick auf diesen Button erscheint ein Pop-Up, das diese Optionen anbietet, und zwar möglichst so, daß der vorher gewählte Eintrag an der Mausposition erscheint. Dies verhindert, daß bei zufälligem Anklicken eine ungewollte Änderung eintritt. Der im Augenblick aktivierte Eintrag sollte im Pop-Up mit einem Haken versehen werden, wie dies bei normalen Menüs üblich ist. Dazu ist es nur nötig, das Flag checked zu setzen. Ein Aufruf eines Pop-Ups sieht wie folgt aus:

Ame_popup(window, &POPUP, x, y);

Das erste Argument spezifiziert das Fenster, zu dem das Pop-Up-Menü gehört, das zweite ein vom Editor erstelltes Pop-Up-Objekt. X und Y geben die Koordinaten an, an denen das Pop-Up-Menü gezeichnet werden soll. Sind diese -1, erscheint es auf aktuellen Mauskoordinaten. Das Ergebnis ist die Objektnummer des angewählten Eintrags. Diese braucht aber nicht ausgewertet zu werden, denn die Aktionen werden meistens über die Click-Routinen ausgeführt.

Tastendruckauswertung

Kommen wir zurück zur Programmierung, wo uns die Tastendruckauswertung beschäftigen wird. Ein Tastendruck wird unter ACS etwas komplizierter verwaltet, als das bisher unter GEM üblich war. Das geschieht in festgelegter Ordnung, die man jedoch für besondere Zwecke außer Kraft setzen kann. Höchste Priorität hat die Menüleiste des getoppten Fensters. Dann kommt dessen Arbeitsbereich, also zum Beispiel ein Button, der auf Tastendrücke reagiert. Erst, wenn diese beiden die Taste nicht konsumiert (ausgewertet) haben, kommt das Root-Fenster (Desktop) an die Reihe. Auch dort gibt es eine Menüleiste und einen Arbeitsbereich. Bevor der Tastendruck ganz ignoriert wird, bekommt ihn noch die Keys-Routine des getoppten Fensters. Die ist unter anderem für Edit-Felder zuständig. Indem man diese Routine umschreibt, könnte man bessere Bedienbarkeit der Edit-Felder erreichen.

Bild 2: Erstellen von Submenüs

Oft möchte man Sonderzeichen in Edit-Felder eingeben. Beim Atari funktioniert das zum Beispiel über Tastenkombinationen, wobei die Taste Control mit im Spiel ist. Wenn man zum Beispiel das Sonderzeichen CTRL-Q eingibt, bekommt das Root-Fenster diese Tastenkombination vor der Keys-Routine und beendet die Applikation. Um dies zu verhindern und das Sonderzeichen direkt an die Keys-Routine zu schicken, muß davor eine weitere Tastenkombination (ALT-SHIFT-Q) betätigt werden. Dieses Vorgehen nennt man Quoten (LISP läßt grüßen); es bewirkt, daß die nachfolgende Tastenkombination nicht ausgewertet wird.

# Mögliche Tasten

Alle Buchstaben, Ziffern, Zeichen und folgende Abkürzungen

BS (Backspace), DEL (Delete), DOWN. UP, LEFT RIGHT (Pfeiltasten), ENTER (Ziffernblock Enter), ESC, HELP HOME INS (Insert), RET oder RETURN, TAB, UNDO und Funktionstasten F1...F20

**Abb. 1: Standardsteuertastencodes**

Tastencodes

Da die Maus manchmal doch nicht das ideale Eingabemittel ist, sollten Programme die Steuerung der Funktionen über die Tastatur anbieten. ACS bietet dem Programmierer eine einfache Methode der Tastaturunterstützung: Es scannt auf Wunsch die Strings Ihrer anklickbaren Objekte durch und trägt automatisch die Tastencodes ein, falls diese am Ende der Einträge im passenden Format angegeben wurden. Leider schweigt sich das Handbuch über das Format aus. Deshalb sollen hier die genauen Angaben folgen.

Unter Refs finden Sie einen Eintrag, der Tasten heißt. Hier können Sie manuell die Tastenkombination eintragen, die dem Anklicken entsprechen soll. Der Benutzer würde dann aber nicht wissen, welche Tasten er betätigen muß. Deshalb hat man sich auf eine standardisierte, an den Macintosh angelehnte Darstellungsweise geeinigt. Diese vor allem in Menüleisten übliche, bei ACS aber auch bei Buttons mögliche Codierung verlangt, daß hinter dem Namen eines Menüeintrags erst eine beliebige Anzahl an Leerzeichen folgt, dann kommt der Tastencode und dann das String-Ende. Ein Leerzeichen ist am Ende nicht erlaubt, kann (soll) aber im Menü doch zu sehen sein (Bild 3).

Wie sehen die Codes aus? Der Taste selbst (Abbildung 1) können noch Steuertastencodes vorangestellt werden. Es gibt drei Steuertasten, die in beliebiger Kombination auftreten dürfen: Control wird durch das Potenzierungszeichen dargestellt, Shift durch den Pfeil nach oben und Alternate durch das Füller-Zeichen. Es fragt sich nur noch, wie man diese Sonderzeichen in die Einträge bekommt. Das Potenzierungszeichen ist leicht über die Tastatur zu erreichen, der Pfeil nach oben entspricht CTRL-A und das Füller-Zeichen CTRL-G.

Das Ziehen

Diese etwas seltsam anmutende Überschrift stellt einen wichtigen Bereich der ACS-Operationen dar. Objekte, die ziehbar sind, können zum Beispiel gelöscht, kopiert oderauch nur bewegt werden. Ziehbare (draggable) Objekte werden komplett vom ACS verwaltet, d.h. sie werden selektiert, wenn man sie anklickt oder einen Bereich aufzieht, in dem sie sich befinden; ihre Rahmen werden mitverschoben, wenn man die Maus mit gedrückter Taste bewegt, und schnappen zurück, falls die Ziehoperation unsinnig war. Andere Objekte wiederum können ziehbare akzeptieren. Ob ein Objekt ein anderes aufnehmen kann, sieht man daran, daß es invertiert wird, wenn der Benutzer sich mit der Maus darüber befindet. Objekte dieses Typs heißen Accept und ihre Verwandten, die dieselbe Funktionalität besitzen, aber dies nicht durch das Invertieren zeigen, Silent.

Eng verknüpft mit akzeptierenden Objekten ist die Drag-Routine. Sie wird ausgeführt, wenn über einem Objekt solchen Typs eine Auswahl, also eine beliebige Anzahl, von ziehbaren Objekten losgelassen wird. Hier tritt der Programmierer wieder in Aktion: Er muß in der Drag-Routine zuerst überprüfen, ob er mit den gezogenen Objekten etwas anfangen kann, denn eine Auswahl kann Objekte völlig verschiedenen Typs enthalten. Er kann dann mit den Objekten, die er kennt, tun, was er möchte. Das Problem besteht also darin, Objekte zu erkennen. Unter Refs findet man einen Eintrag Type, in dem jedes Objekt eine Nummer bekommt. Diese muß selbstverständlich einzigartig sein und kann bequem mit einem #define in ein allgemein verständliches Wort umgewandelt werden. Eine andere Möglichkeit der Auswertung besteht darin, dem Fenster, dem die Objekte gehören, eine Nachricht zu senden, damit es dann selbst die geeignete Operation ausführen kann. Diese indirekte Art der Auswertung ermöglicht es Fenstern, die überhaupt nichts von gezogenen Objekten wissen, eine Operation durchzuführen. Ein Beispiel dieser generischen Accept-Objekte ist der Papierkorb des Desktops: Er kümmert sich nicht um die Objekte selbst, sondern sendet die Nachricht AS_DELETE an die zuständige Applikation (Fenster).

Bild 3: Leerzeichen am Menüende

Die Information, welche ziehbaren Objekte selektiert sind und welchem Fenster sie gehören, erhält man mit Hilfe einiger ACS-Funktionen aus einer globalen Variablen mit dem Namen Aselect. Abbildung 2 zeigt die Struktur dieser Variablen. Für uns wichtig sind jedoch höchstens drei dieser Struktureinträge: window enthält einen Zeiger auf das Fenster, dem die Objekte gehören, actlen deren Anzahl und next das nächste auszuwertende Objekt.

Bei der Auswertung dieser Ziehliste müssen wir zuerst next initialisieren. Da wir alle Objekte auswerten möchten, setzen wir next auf Null. Wir bekommen den Index des ersten Objekts, indem wir die Routine Adr_next aufrufen. Diese erhöht next automatisch um Eins, damit beim nächsten Aufruf auch wirklich der nächste Index geliefert wird. Ist der Rückgabewert von Adr_next -1, sind wir am Ende der Liste angelangt. Die Indizes beziehen sich natürlich auf das Work-Objekt des betreffenden Fensters. Ziehbare Objekte besitzen alle den erweiterten Typ AOBJECT, auf den, wie in der letzten Folge geschildert wurde, zugegriffen werden kann, indem man den Index auf OBJECT um Eins erhöht. Über das AOBJECT kommt man an den Typ des Objekts; unbekannte Objekte werden einfach übersprungen. Mit den restlichen kann die korrekte Operation durchgeführt werden. Damit dem ACS mitgeteilt wird, daß ein Objekt ausgeführt worden ist, löschen wir es mit Hilfe von Adr_del aus der Liste. Nichtgelöschte Objekte schnappen dann, im Normalfall, zurück. Die praktische Auswertung der Auswahlliste sehen Sie weiter unten, wenn das Listing erklärt wird.

Das Modulkonzept

Eine der am weitesten unterschätzten Möglichkeiten von ACS ist die Modularisierung. Der ACS-Editor bietet die Möglichkeit, im Verhalten-Editor die Ausgabe der Deskriptorstruktur zu unterbinden. Dies ist die einzige Variable, die bei der Ausgabe global angelegt wird. Sie enthält wichtige Daten zum Start des ACS-Betriebssystems [es ist wirklich ein vollständiges, pseudomultitaskingfähiges Betriebssystem, das die Kommunikation unter den Applikationen (Fenstern) sehr vereinfacht]. Indem man den Deskriptor nicht mit ausgibt, hat man nur noch lokale Daten, so daß Kollisionen mit anderen eventuell vorhandenen ACS-Ausgaben nicht passieren können (einmal muß man die Deskriptorstruktur aber doch ausgeben).

Teile von Applikationen, die wiederverwendbar und leicht in eine Einheit zu verschließen sind, gehören in eigene Module, die über eine festdefinierte Schnittstelle zu bedienen sind. Damit erleichtert der Programmierer nicht nur sich selbst die Arbeit, er kann seine Werke auch über PD-Disketten oder gar kommerziell vertreiben und damit anderen helfen, die sich nicht unbedingt die Arbeit machen wollen, das Rad neu zu erfinden. Ein schöner, simpler Texteditor zum Beispiel gehört in fast jede ausgewachsene Applikation. Warum muß jede dieser Anwendungen ihren eigenen implementieren, wenn man ihn doch für geringe Kosten erwerben könnte? Da sich ACS immer größerer Beliebtheit erfreut, wird in absehbarer Zeit hier sicherlich ein Markt entstehen, der dem ST bisher verschlossen war.

Was die Schnittstelle eines Moduls zur Hauptapplikation angeht, muß der Programmierer eine Menge von Routinen anbieten, die die gesamte Funktionalität bereitstellen. Besonders Schlaue bedienen sich hier jedoch nicht des statischen, sondern des dynamischen Linkens. Im Klartext heißt das, daß keine Routinen bereitgestellt, sondern über das Nachrichtensystem Nachrichten gesandt werden, die erst intern mit den eigentlichen Funktionen verbunden werden. Der Performance-Verlust wird durch die viel größere Flexibilität mehr als ausgeglichen [1]. Da Nachrichten aber Nummern sind, sollten sie beim Autor vom ACS registriert werden, damit es zu keinen Konflikten unter einzelnen Modulen kommt.

Bild 4: Die drei Alarmboxen
typedef struct {
    Awindow *window;
    int maxlen;     /* MaxAnzahl der Einträge */
    int action;     /* Anzahl der Einträge */
    int next;       /* nächster Eintrag (Adr next) */
    int dragback;   /* graf_mbox Flag */
    int x, y;       /* Click-Koordinaten */
    int rx, ry;     /* losgelassen bei (Deltas) */
    int *array;     /* privat */
} Asel;

Bild 2: Die Aselect-Struktur

Eigenes Desktop?

Bei Gesprächen mit ACS-Anwendern ist es mir aufgefallen, daß das Default-Desktop das bei weitem größte Ärgernis der Programmierer darstellt. Sie hätten gerne individuelle Programme, die nach dem bisher üblichen Prinzip arbeiten. Dies ist durchaus zu verstehen; es bedarf etwas mehr Hintergrundinformation, um den Wert dieses Desktops zu verstehen.

ACS ist, wie schon gesagt, ein vollständiges Betriebssystem, das die Verwaltung der Fenster, die es für Applikationen hält, übernimmt. Es stellt eine gewisse Funktionalität bereit, die dazu notwendig ist. Indem man ein eigenes Desktop schreibt, erschwert man sich die Möglichkeit der Nutzbarmachung neuer Funktionen. ACS ist ein System, das sich ständig in Bewegung befindet (im Gegensatz zu TOS oder GEM). Ein neuer Eintrag in der Default-Menüleiste einer neuen Version (die neueste Version ist 1.05) ist durch erneute Übersetzung sofort in allen Programmen zugänglich, die das Default-Desktop benutzen. Programmierer eigener Desktops müßten diese neue Funktionalität selbst implementieren, wenn sie sie benutzen möchten.

Demjenigen, der es dennoch nicht lassen kann, empfehle ich, nur die Menüleiste auszutauschen, denn das Desktop an sich ist ja ganz schick und übernimmt die Icon-Verwaltung, die zu schreiben einfach lästig wäre. Dazu ersetzt man in der globalen Struktur DESKTOP (Typ Awindow), in der sich das Root-Fenster befindet, menu durch die Adresse des eigenen Menübaumes. Das muß man in der Routine ACSinit(), deren Existenz ich bisher verschwiegen hatte, erledigen. Zu diesem Zeitpunkt ist GEM bereits vollständig initialisiert, das Root-Fenster jedoch noch nicht. Indem diese Routine global geschrieben wird, ersetzt sie die Default-Routine, die keinen Zweck erfüllt. Ich werde hier aber kein Beispiel zeigen, damit solche Unternehmungen, die eigentlich illegal sind, nicht gang und gäbe werden.

Bild 5: Die Fensterdefinitionen
Bild 6: Die Aboutme-Dialogbox
Bild 7: Die fertige Oberfläche

MakeUp

So, und nun zum Beispiel dieses Monats. Eines der großen Ärgernisse von Pure-und Turbo-C ist die überaus schwache Make-Funktion, die eigentlich nur für Spielereien geeignet ist. Im Vergleich zu Unix-Make fehlen viele für größere Projekte notwendige Funktionen wie Variablen, bedingte Compilierung oder der Aufruf externer Programme. Die einzige Möglichkeit war der Schritt zurück zu den Command-Line-Shells. Die hier vorgestellte Oberfläche bietet jedem die Möglichkeit, sich ein eigenes Make zu implementieren. Klar, daß aus Platzgründen hier nur der grobe Rahmen der vollständigen Applikation gezeigt werden kann.

Die fertige Oberfläche bietet folgende Funktionalität: Zum Makefile, das durch ein Container-Fenster repräsentiert wird, können einzelne Files hinzugefügt werden (Bild 7). Dort können sie mit den üblichen Ziehoperationen manipuliert werden. Die Routinen zum Öffnen und Sichern eines Makefiles müssen Sie selbst implementieren. Zum Trost funktioniert das Exportieren ins Pure- und Turbo-C-Format.

Unter einem Container-Fenster versteht man ein Fenster, das eine Anzahl von Objekten enthalten kann. Prädestiniert für diese Anwendung sind die Listenfenster des ACS. Sie haben keine feste Größe und passen sich selbst an, damit Objekte so hineinpassen, daß kein horizontales Scrolling notwendig ist. Diese Fenster finden beispielsweise in Gemini Verwendung. Den Abstand der einzelnen Objekte untereinander definiert der Offset des ersten hinzugefügten Objekts zum Work-Objekt.

Das Aussehen der Menüleiste finden Sie in Bild 3. Tragen Sie wie üblich die Click-Routinen ein (do_quit,...). Die drei Alarmboxen sind in Bild 4 zu sehen. Ihre Namen befinden sich in der Infozeile des jeweiligen Fensters. Und die Fensterdefinitionen befinden sich im Bild 5. Tragen Sie unter den Fensterroutinen noch make_create und make_service ein. Erstellen Sie noch ein Icon (es könnte ebenso gut ein String sein), das für die Darstellung der einzelnen Source-Dateien zuständig ist. Der Name muß CICON lauten.

Das Programm an sich ist in drei Listings aufgeteilt: Listing 1 enthält die oben besprochene form_value-Routine, Listing 2 ist die eigentliche Oberfläche, und Listing 3 enthält einige Hilfsfunktionen für das Arbeiten mit Listen, die hier keine Erwähnung finden. Die Listings 4 und 5 enthalten wichtige Daten zum Includen, und Listing 6 zeigt die Projektdatei des Beispiels.

Auf der CeBIT hat jemand argumentiert, man müsse, wolle man den Aboutme-Eintrag im Desk-Menü ändern, eine ganze Befehlssequenz dafür schreiben. Das sei unverschämt. Hier nun die Gegendarstellung: Die Funktion ACSaboutme (Listing 2) enthält sage und schreibe 1 (in Worten: ‘einen’) Befehl. Das ist anwenderfreundlich. Meine Dialogbox sehen Sie übrigens im Bild 6. Der schwarze Kasten ist ein Userdef und bietet die Möglichkeit einer kleinen Animation.

In den Routinen ACSinit und make_create geschieht nicht viel Neues, außer daß das Desk-Icon den Namen des Makefiles bekommt. Dies muß unbedingt vor dem Erzeugen des Fensters durch Awi_create geschehen. In der Service-Routine jedoch werden die Messages AS_OPEN, AS_INFO und AS_DELETE abgefangen. Die erste dieser Nachrichten löscht einfach die Auswahlliste. AS_INFO gibt eine kurze Info über die ausgewählten Dateien aus und AS_DELETE löscht diese. Schauen Sie sich die zugehörigen Funktionen an. Wie Sie sehen können, wird keine Typüberprüfung durchgeführt, da in unserem Falle nur ein Objekttyp Vorkommen kann. An die Daten der einzelnen Objekte kommt man normalerweise über die Userpointer; hier war es jedoch günstiger, eine externe Struktur FILE_LINK zu benutzen.

Sehr wichtig ist auch die Funktion build_work. Sie legt das gesamte Work-Objekt neu an. Dies ist immer dann nötig, wenn Objekte hinzugefügt oder gelöscht werden. Um das Objekt in seinen neuen Ausmaßen darzustellen, wird window->sized aufgerufen.

Es bleibt Ihnen überlassen, das MakeUp so zu verbessern, daß es ähnlich gestaltet wird wie einige Modula-Shells. Das war es auch schon, was diese dreiteilige Folge über ACS betrifft. Sie sind jetzt an der Reihe, qualitativ hochwertige Programme mit entsprechenden Benutzeroberflächen zu erstellen. Schicken Sie Ihre Erfahrungen an die Redaktion oder direkt an mich, in der Maus Stuttgart. Bis bald, wenn es heißt: Komprimieren besser als LHarc.

Literatur:

[1] Brad, J. Cox: Object Oriented Programming, Addison-Wesley 1986

    #include <stdio.h> 

    int
    form value(int def button, char *string, ...) 
    {
        va_list arguments; 
        char buffer[256];

        va_start(arguments, char *); 
        vsprintf(buffer, string, arguments); 
        va_end(arguments);
        return(form_alert(def_button, buffer));
    }

Listing 1

/* (c)1992 by MAXON-Computer */
/* Autor: Grischa Ekart      */

#include    <tos.h>
#include    <stdio.h>
#include    <string.h>
#include    "g:\acs\acs.h"
#include    "struct.h"
#include    "file.h"

/*      external prototypes                 */

int form_value(int def_button, char *string, ...);

/*      internal prototypes                 */

static Awindow *make_create(void *not_used); 
static int make_service(Awindow *window, int task, void *in_out); 
static void term(Awindow *window); 
static void info_select(Awindow *window); 
static void deiete_select(Awindow *window); 
static char *file_select(char *path, char *ext, char *label);

static void do_add(void); 
static void do_import(void); 
static void do_export(void); 
static void do_quit(void);
static int cdecl draw_my_pict(PARMBLK *pb);
static int add_files(void);
static void build_work(Awindow *window);

#include "makeup.h"
#include "makeup.ah"

static char fname[FILENAME_MAX];

static OBJECT parent = {-1, -1, -1, G_BOX, NONE, AOS_FIXED, 0x00001101L, 0, 0, 100, 100);
static AOBJECT Aparent = { A_dummy, A_dummy, AEO, 0, NULL, NULL, 0, 0};

static OBJECT file_icon = {-1, -1, -1, G_ICON, NONE | AO_DRAGABLE, NORMAL, (long)&CICON, 0, 0, AREA_WIDTH, AREA_HEIGHT};
static AOBJECT Afile_icon = { A_dummy, A_dummy, AEO, 0, NULL, NULL, 0, 0};

void
ACSaboutme(void)
{
    A_dialog(&INFOBOX);
}

int
ACSinit(void)
{
    Awindow *window;

    window = Awi_root();        /* root window */
    if(window == NULL)
        return(FAIL);           /* lege NEU Icon an */

    window->service(window, AS_NEWCALL, &MAKEWINDOW.create); 
    fname[0] = 'A' + Dgetdrv(); 
    fname[1] = ':';
    Dgetpath(fname + 2, 0); 
    if(fname[2] == 0)
    {
        fname[2] = '\\'; 
        fname[3] = '\0';
    }
    else
    {
        int length = (int)strlen(fname);

        fname[length] = '\\'; 
        fname[length + 1] = '\0';
    }
    return(OK);
}

static Awindow 
*make_create(void *not_used)
{
    MAKEFILE    *makefile;
    Awindow     *window;
    int         length;
    char        *p;

    if(file_select(fname, "MAK", "Öffne Makedatei") == NULL) 
        return(NULL);

    length = (int)strlen(fname); 
    if(fname[length - 1] == '\\' ) /* fname ist ein Pfad */ 
        return(NULL);

    makefile = Ax_malloc(sizeof(MAKEFILE)); 
    if(makefile == NULL) 
        return(NULL);

    p = strrchr(fname, '\\');
    strcpy(MAKEWINDOW.iconblk->ib_ptext, p + 1); 
    window = Awi_create(&MAKEWINDOW); 
    if(window == NULL)
    {
        Ax_free(makefile); 
        return(NULL);
    }
    makefile->file_number = 0; 
    makefile->files.name = "Head_Of_List"; 
    makefile->files.next = NULL; 
    makefile->path[0] = 'A' + Dgetdrv(); 
    makefile->path[1] = Dgetpath(makefile->path + 2, 0); 
    if(makefile->path[2] == 0)
    {
        makefile->path[2] = '\\'; 
        makefile->path[3] = '\0';
    }
    else
    {
        int length = (int)strlen(fname);

        makefile->path[length] = '\\'; 
        makefile->path[length + 1] = '\0';
    }
    strncpy(makefile->name, fname, FILENAME_MAX -1);
    Ast_delete (window->name);
    window->name = Ast_create(fname);
    window->user = makefile;
    window->open(window);
    return(window);
}

static int
make_service(Awindow *window, int task, void *in_out)
{
    switch(task)
    {
        case AS_TERM:
            term(window); 
            break;

        case AS_OPEN:
            Adr_unselect(); 
            break;

        case AS_INFO:
            info_select(window); 
            break;

        case AS_DELETE:
            delete_select(window); 
            break;
    }
    return(FAIL);
}

static void 
term(Awindow *window)
{
    MAKEFILE *makefile;

    makefile = window->user; 
    free_link(makefile->files.next);
    Ax_free(makefile);
    Awi_delete(window);
}

static void
info_select(Awindow *window)
{
    MAKEFILE    *makefile;
    FILE_LINK   *file;
    AOBJECT     *object;
    char        *p;
    int         index;

    makefile = window->user; 
    if(Aselect.actlen == 0)
        ACSaboutme();
    else
    {
        Aselect.next = 0;
        while((index = Adr_next()) >= 0)
        {
            object = (AOBJECT *)&Aselect.window->work[index + 1]; 
            if(object->ob_flags & AEO)
            {
                file = get_file(&makefile->files, index / 2); 
                p = strrchr(file->name, '\\'); 
                form_value(1, INFO_ALERT, index / 2, P + 1);
                Adr_del(Aselect.window, index);
            }
        }
    }
}

static void
delete_select (Awindow *window)
{
    MAKEFILE    *makefile;
    AOBJECT     *object;
    int         index;

    makefile = window->user;
    Aselect.next = 0;
    while ((index = Adr_next()) >= 0)
    {
        object = (AOBJECT *)&Aselect.window->work[index +1]; 
        if (object->ob_flags & AEO)
        {
            Adr_del(Aselect.window, index); 
            disable_file(&makefile->files, index/ 2); /*ist korrekt */ 
            makefile->file_number--;
        }
    }
    delete_files(&makefile->files); 
    build_work(window);
}

static void 
do_add(void)
{
    if(add_files())
        build_work(ev_window);
}

static void 
do_import(void)
{
    FILE    *input;
    int     length;

again:
    if(file_select(fname, "PRJ", "Importiere Projektdatei") == NULL) 
        return;

    length = (int)strlen(fname);
    if(fname[length - 1] == '\\') /* fname ist ein Pfad */
    {
        if(form_alert(2, ERRORPATH) == 2) 
            goto again;
        else
            return;
    }
    if((input = fopen(fname, "r")) == NULL)
    {
        if(form_alert(2, ERROROPEN) == 2) 
            goto again;
        else
            return;
    }
    /* hier folgt der eigentliche Code */
    /* ... */
}

static void 
do_export(void)
{
    MAKEFILE    *makefile;
    FILE_LINK   *file;
    FILE        *output;
    int         length, count;
    char        *p;
    char        prg[13];

    makefile = ev_window->user; 
again:
    if(file_select(fname, "PRJ", "Exportiere Projektdatei") == NULL) 
        return;

    length = (int)strlen(fname);
    if(fname[length - 1] == '\\') /* fname ist ein Pfad */
    {
        if(form_alert(2, ERRORPATH) == 2) 
            goto again;
        else
            return;
    }
    if((output = fopen(fname, "w")) == NULL)
    {
        if(form_alert(2, ERROROPEN) == 2) 
            goto again;
        else
            return;
    }
    p = strrchr(fname, '\\'); 
    strcpy(prg, p + 1); 
    p = strrchr(prg, '.'); 
    strcpy(p + 1, "PRG");
    fprintf(output, ";\t\tProjektdatei erzeugt mit Makeup V1.0\n\n"); 
    fprintf(output, "%s\t;Name des Programs\n=\n", prg);
    for(count = 0; count < makefile->file_number; count++)
    {
        file = get_file(&makefile->files, count +1); 
        /* p = strrchr(file->name, '\\') + 1; */ 
        fprintf(output, "%s\n", file->name); /* immer mit Pfad */
    }
    fprintf(output, "\n;\t\tEnde\n"); 
    fclose(output);
}

static void 
do_quit(void)
{
    term(ev_window);
}

static int 
add_files(void)
{
    MAKEFILE    *makefile;
    FILE_LINK   *file;
    char        *p;
    int         retval = FALSE;

    makefile = ev_window->user;

again:
    if(file_select(makefile->path, "*", "Datei hinzufügen") == NULL) 
        return(retval);

    p = strrchr(makefile->path, '\\'); 
    if(p[1] == '\0')
    {
        if(form_alert(2, ERRORPATH) == 2) 
            goto again;
        else
            return (retval);
    }
    chain_on(&makefile->files, makefile->path);
    makefile->file_number++;
    retval = TRUE;
    goto again; /* Nochmal */
}

static void
build_work(Awindow *window)
{
    MAKEFILE    *makefile;
    OBJECT      *work;
    ICONBLK     *iconblk;
    FILE_LINK   *file;
    int         i, count;

    makefile = window->user;
    work = Ax_malloc((makefile->file_number + 1) * sizeof(OBJECT) * 2);
    if(work == NULL) 
        return;

    if(window->work != NULL)
        Aob_delete(window->work);

    memcpy(work, &parent, sizeof(OBJECT)); 
    memcpy(work + 1, &Aparent, sizeof(AOBJECT)); 
    window->work = work; 
    work->ob_head = 2; 
    work += 2; 
    i = 4;
    for(count = 0; count < makefile->file_number; count++)
    {
        char    *p;

        file = get_file(&makefile->files, count +1); 
        memcpy(work, &file_icon, sizeof(OBJECT)); 
        memcpy(work + 1, &Afile_icon, sizeof(AOBJECT)); 
        iconblk = Aic_create (file_icon.ob_spec.iconblk); 
        p = strrchr(file->name, '\\'); 
        strcpy(iconblk->ib_ptext, p + 1); 
        work->ob_spec.iconblk = iconblk; 
        work->ob_next = i; 
        i += 2; 
        work += 2;
    }
    work[-1].ob_flags |= LASTOB; 
    if(makefile->file_number == 0)
    {
        window->work->ob_head = -1; 
        window->work->ob_tail = -1;
        window->work->ob_next = -1;
    }
    else
    {
        work[-2].ob_next = 0; 
        window->work->ob_tail = i - 4;
    }
    (window->sized) (window, &window->wi_act);
}

static char
*file_select(char *path, char *ext, char *label)
{
    char    *p;
    int     button;
    char    file[16];

    p = strrchr (path, '\\');
    p[1] = '\0'; 
    strcat(path, "*.");
    strcat(path, ext); 
    file[0] = '\0';
    Aev_unhidepointer ();
    if(fsel_exinput(path, file, Sbutton, label) == 0 || button == 0) 
        return(NULL);

    if(file[0] == '\0')
    {
        p = strrchr(path, '\\'); 
        p[1] = '\0'; 
        return(path);
    }
    if(strlen(file) == 9) 
        if(file[8] == '.')
            strcat(file, ext);

    if(strchr(file, '.') == NULL)
    {
        strcat(file, "."); 
        strcat(file, ext);
    }
    p = strrchr(path, '\\'); 
    strcpy(p + 1, file); 
    return(path);
}

static int cdecl
draw_my_pict(PARMBLK *pb)
{
    return(pb->pb_currstate);
}

Listing 2

#include <stdio.h>
#include "g:\acs\acs.h"
#include "struct.h"

void
disable_file(FILE_LINK *file_link, int which)
{
    int     i;

    for(i = 0; i < which; i++)
        file_link = file_link->next;

    Ast_delete(file_link->name); 
    file_link->name = NULL;
}

void
delete_files(FILE_LINK *file_link)
{
    FILe_LINK   *fi1e;

    file = file_link->next; 
    if(file == NULL) 
        return;

    delete_files(file); 
    if (file->name == NULL)
    {
        file_link->next = file_link->next->next; 
        Ax_free(file);
    }
}

FILE_LINK
*get_file(FILE_LINK *file_link, int which)
{
    int     i;

    for(i = 0; i < which; i++)
        file_link = file_link->next;

    return(file_link);
}

FILE_LINK
*chain_on(FILE_LINK *file_link, char *string)
{
    FILE_LINK   *file;

    file = Ax_malloc(sizeof(FILE_LINK)); 
    if(file == NULL) 
        return(NULL);

    file->next = NULL; 
    file->name = Ast_create(string); 
    while(file_link->next != NULL) 
        file_link = file_link->next;

    file_link->next = file; 
    return(file);
}

void
free_link(FILE_LINK *file_link)
{
    if (file_link != NULL)
    {
        free_link(file_link->next); 
        Ast_delete(file_link->name);
        Ax_free(file_link);
    }
}

Listing 3

/* FILE_LINK ist der Eintrag für eine Datei aus dem Projekt */

typedef struct file_link { 
    char    *name; 
    struct  file_link *next;
} FILE_LINK;

/* MAKEFILE ist die interne Repräsentation der Makedatei */

typedef struct {
    char        name[FILENAME_MAX];
    char        path[PATH_MAX];
    int         file_number;
    FILE_LINK   files;
} MAKEFILE;

Listing 4

void        disable_file(FILE_LINK *file_link, int which);
void        delete_files(FILE_LINK *file_link);
FILE_LTNK   *get_file(FILE_LINK *file_link, int which);
FILE_LINK   *chain_on(FILE_LINK *file_link, char *string);
void        free_link(FILE_LINK *file_link);

Listing 5

MAKEUP.PRG      ; name of executable program
=               ; list of modules follows...
PCSTART.O       ; startup code

MAKEUP.C        (MAKEUP.AH, FILE.H, STRUCT.H)
FILE.C          (STRUCT.H)
FORM.C

G:\ACS\ACS.LIB  ; ACS Library
PCSTDLIB.LIB    ; standard library
PCTOSLIB.LIB    ; extended library
PCGEMLIB.LIB    ; AES and VDI library

Listing 6


Grischa Ekart
Aus: ST-Computer 06 / 1992, Seite 132

Links

Copyright-Bestimmungen: siehe Über diese Seite