Global Variables application example

This is a smallish program which allows you to investigate the value of all the Global Variables which have been defined, and to change, add or delete global variables.

Global Variables are an operating system extension which is introduced in ProWesS. It is a system which is similar to (but not the same as) environment variables on Unix systems. It allows you to assign a value (a string) to a name. This value can be queried by everybody and can also be changed by anybody. It is mainly used to ease the installation process for programs. It is for example used when loading ProWesS. The device and directory where ProWesS is loaded from is stored in a global variable (PWSDIR). The value of this variable is then used to find files.

The libraries (syslib) support the use of global variables when a file is opened. For example, when a file if opened with the name "$PWDIR_doc_loader_html", then the "$PWDIR" is automatically replaced by its value (e.g. "win1_pws").

This program is also a very good example for the ease with which interactions between several parts of the window can be programmed.

The window contains a few items. The two items labeled "constant" and "value", contains the name and value of a constants. These items can be edited at wish. When the name of a Global Variable is indicated in the menu at the bottom of the window, then that name and the current value will be displayed in the items just above the menu. A Global Variable can be (re)set by indicating the "Set constant" item, or deleted by indicating "Delete constant".

The source code

Here is a run through of the source code. Obviously, it starts by including the header files, and some constants which are used to access the Global Variables thing. Also a special macro is defined which helps to catch errors when they occur (hence the name).

#include "str.h"
#include "thing.h"
#include "ProWesS.h"

#define catch(x) if (err=(x)) return err; else

#define GLOBAL_NAME "Global Variables"
#define GLOBAL_GET  0x47455420  /* "GET " */
#define GLOBAL_SET  0x53455420  /* "SET " */
#define GLOBAL_DELE 0x44454c45  /* "DELE" */
#define GLOBAL_FRST 0x46525354  /* "FRST" */
#define GLOBAL_NEXT 0x4e455854  /* "NEXT" */

The text which is displayed inside the items and as labels is defined separately. This makes it easier to change them (for example to produce a copy of the program in a different language). In fact, they could just as well be made configurable.

#define LabelConstant   "constant : "
#define LabelValue      "value : "
#define ItemSet         "Set constant"
#define ItemDelete      "Delete constant"

The maximum length of the strings which can be edited (the name and value of the constant) are defined here. These length of the value is not limited by the Global Variables thing, but limiting it makes them easier to handle. In fact, the "edline" object (which allows you to edit a string) always works with a fixed length string. This length can be defined when the object is created. Otherwise a default length is used (this default length is configurable).

#define MAX_NAME    64
#define MAX_VALUE   256

Because we think it useful to make programs re-entrant, you should not use global variables, as their value will be shared between all the copies of the program (especially when the program is loaded as an executable thing). Therefore a global structure is needed which is used to pass the parameters so that they are accessible in all the functions. The base of this structure can be stored in the ProWesS system.

typedef struct {
    PWObject menu;
    PWObject constant, value;
} Global;

/* forward declarations */
Error readall(PWObject object);
Error set(PWObject object);
Error delete(PWObject object);
Error select(PWObject object, char *item);

The program starts by creating the outline for the window. This outline includes the title (which is the default, the program name), a wake, quit and a sleep item. The quit item is also activated when is pressed. The action which has to be called for the wake action is defined (re-read which global variables are defined). To allow the event handlers to find the globally used variables, the Global structure is stored in the global auxiliary.

Error init()
{
    Global g;
    Error err;
    PWObject win, box;

    catch( PWCreate(NULL, &win, PW_TYPE_OUTLINE,
                PW_OUTLINE_SLEEP,
                PW_OUTLINE_QUIT,
                PW_OUTLINE_QUIT_KEYPRESS, 27,
                PW_OUTLINE_ACTION_WAKE, readall,
                PW_GLOBAL_AUXILIARY, &g,
                NULL) );

Inside the outline, there are many items. The items are normally all below each other at the first level of nesting inside the outline, so a box is created to change the direction as I want the items to be side by side. Inside this box, there are the two loose items to set and delete a constant. The event handlers which have to be called when the items are indicated are defined. It is also specified that the status of the items should not be changed when they are indicated.

    catch( PWCreate(win, &box, PW_TYPE_DIRECTION, NULL) );
    catch( PWCreate(box, NULL, PW_TYPE_LOOSE_ITEM,
                PW_LOOSE_TEXT, ItemSet,
                PW_LOOSE_CHANGE_STATUS, FALSE,
                PW_LOOSE_ACTION_HIT, set,
                NULL) );
    catch( PWCreate(box, NULL, PW_TYPE_LOOSE_ITEM,
                PW_LOOSE_TEXT, ItemDelete,
                PW_LOOSE_CHANGE_STATUS, FALSE,
                PW_LOOSE_ACTION_HIT, delete,
                NULL) );

Below these items, there are the two objects to edit the strings with the name and value of the constant. The maximum length of these strings is given. Then the two edline objects are connected with each other to make sure that the user can move the cursor between the two items. This is done using the up and down keys. Also after editing the string in the first edline, the user can automatically modify the value for that constant.

    catch( PWCreate(win, NULL, PW_TYPE_SEPARATOR, NULL) );
    catch( PWCreate(win, &g.constant, PW_TYPE_EDLINE,
                PW_EDLINE_MAXLENGTH, MAX_NAME,
                NULL) );
    catch( PWCreate(win, &g.value, PW_TYPE_EDLINE,
                PW_EDLINE_MAXLENGTH, MAX_VALUE,
                NULL) );
    catch( PWChange(g.constant,
                PW_EDLINE_EDLINE_AFTER, g.value,
                PW_EDLINE_EDLINE_DOWN, g.value,
                NULL) );
    catch( PWChange(g.value,
                PW_EDLINE_EDLINE_UP, g.constant,
                NULL) );

Of course, we also need a menu which will contain all the constants which are defined at a given moment. This menu is separated from the rest of the window with a separator line. At least six lines are always visible in the menu. All the items inside the menu are always sorted, using a case independant compare (compare routine is given). The event handler which has to handle the selection of an item is specified, but no item can appear to be selected.

    catch( PWCreate(win, NULL, PW_TYPE_SEPARATOR, NULL) );
    catch( PWCreate(win, &g.menu, PW_TYPE_MENU,
                PW_MENU_VISIBLE_LINES, 6,
                PW_MENU_SORT_COMPARE, STRCompareCI,
                PW_MENU_ACTION_SELECT, select,
                PW_MENU_NONE_SELECTED,
                NULL) );

The edline objects which were defined a bit higher are not yet labeled. Therefore, the labels are added to the left of the items. To make sure the edlines are as large as possible when the window is scaled, we make sure that the label itself is not scaled.

The labels are added here because otherwise the default ordering of the objects in the window could no longer be used. The alternative solution for this is used when defining the loose items above. These are also positioned side by side, but because they are positioned inside a direction box, the default positioning rule is not hampered (as this is defined to be a structuring object).

    catch( PWCreate(win, NULL, PW_TYPE_LABEL,
                PW_POSITION_LEFT_OF, g.constant,
                PW_LABEL_TEXT, LabelConstant,
                PW_SCALE_FACTOR, 0,
                NULL) );
    catch( PWCreate(win, NULL, PW_TYPE_LABEL,
                PW_POSITION_LEFT_OF, g.value,
                PW_LABEL_TEXT, LabelValue,
                PW_SCALE_FACTOR, 0,
                NULL) );

Before we can start, we have to fill the menu with all the constants which are defined at the moment. So we call the event handler which will also handle the wake event. Then the window is activated.

    readall(win);

    return PWActivate(win);
}

To read all the definition constants, an iterator which loops over all the Global Variables has to be used. To start we have to extract the global structure from the ProWesS system (the global auxiliary). The menu is then cleared to remove the old contents. A little loop is then started which iterates over all the constants which are defined. The Global Variables system is accessed using the thing system. Each constant of which the name is thus obtained, is then adde in the menu. The menu object will automatically make sure that its contents remains sorted.

Error readall(PWObject object)
{
    Error err;
    Global *g;
    char name[MAX_NAME], value[MAX_VALUE];

    PWQuery(object,PW_GLOBAL_AUXILIARY,&g);

    PWChange(g->menu,PW_MENU_CLEAR,NULL);

    err=THINGCall(GLOBAL_NAME,GLOBAL_FRST,3,name,value,MAX_VALUE);
    while (!err)
    {
        catch( PWChange(g->menu, PW_MENU_ADD_COPY, name, NULL) );
        err=THINGCall(GLOBAL_NAME,GLOBAL_NEXT,3,name,value,MAX_VALUE);
    }
    return ERR_OK;
}

When a constant in the menu is indicated, the name and value of that constant have to be displayed in the edline objects. So to start, we have to retrieve the object identifiers for the edline objects. These are stored in the Global structure which is referenced in the global auxiliary for the window. The value for the constant then has to be queried by calling the Global Variables thing. The strings with the name and value of the constant then have to be passed to the edline objects.

Error select(PWObject object, char *item)
{
    Error err;
    Global *g;
    char value[MAX_VALUE];

    PWQuery(object,PW_GLOBAL_AUXILIARY,&g);

    err=THINGCall(GLOBAL_NAME,GLOBAL_GET,3,item,value,MAX_VALUE);
    if (err) item="", value[0]='\0';

    PWChange(g->constant, PW_EDLINE_SET, item, NULL);
    PWChange(g->value, PW_EDLINE_SET, value, NULL);

    return ERR_OK;
}

Setting a Global Variable is approximately the reverse of the select routine above. After querying the global auxiliary, the strings which are stored in the edline objects have to be obtained. Then a Global Variable is defined with the given name and value. To make sure that the menu stays synchronized with the existing variables, the contents of the menu is rebuilt.

Error set(PWObject object)
{
    Error err;
    Global *g;
    char name[MAX_NAME], value[MAX_VALUE];

    PWQuery(object,PW_GLOBAL_AUXILIARY,&g);

    PWChange(g->constant,PW_EDLINE_GET,MAX_NAME,name,NULL);
    PWChange(g->value,PW_EDLINE_GET,MAX_VALUE,value,NULL);

    THINGCall(GLOBAL_NAME,GLOBAL_SET,2,name,value);

    return readall(object);
}

Deleting a constants is also quite similar with setting one. In this case, the value for the name is irrelevant, but it is adviseable that the edlines are cleared after the constant was deleted. Again, the menu is also rebuilt to stay up to date.

Error delete(PWObject object)
{
    Error err;
    Global *g;
    char name[MAX_NAME];

    PWQuery(object,PW_GLOBAL_AUXILIARY,&g);

    PWChange(g->constant,PW_EDLINE_GET,MAX_NAME,name,NULL);

    THINGCall(GLOBAL_NAME,GLOBAL_DELE,1,name);

    PWChange(g->constant, PW_EDLINE_SET, "", NULL);
    PWChange(g->value, PW_EDLINE_SET, "", NULL);

    return readall(object);
}

The makefile

The makefile for this program is quite straightforward. In fact, most of the makefile is standard, as it originates from a simple template makefile. The most import lines are the line which starts with "OBJ =". The parameter is a list of all the object files for the application. In this case, the entire program is in one file.

Another important line starts with "all :". This lists all the targets in this directory which have to be created. All dependencies are automatically checked and everything is rebuilt when necessary.

The line starting with "global" lists first the dependencies, and then the programs which have to be called to build the file. This starts by calling the linker, with all the object files. The output file (-o) is called "global", and the map and symbol table are produced (-ms). All the necessary libraries are included (-lpw -lpf -lsms). Because the program which is built will be an executable, the proper startup file has to be used. This is done with the -sexec parameter.

After the linking stage, some post processing has to be done to make the dataspace of the output file correct and add the program name. This is done with the "mkexec" program which has the file and the program name as parameters. Optionally, an extra parameter with the requested extra amount of dataspace can be passed (the default is 4kB). The program name is enclosed in quotes (a quote has to be preceded by a backslash or the "make" program will discard it). The yen symbol is used to separate the actual program name from an extra comment which will be part of the file.

# makefile for ProWesS application software
# possible flags - none define just yet
DEFINES =
# specify compiler etc
CC = cc
CFLAGS = -c -O
LD = ld
MAC = qmac

OBJ = global_o

all : global

global : ${OBJ}
        ${LD} -ms -oglobal \
        ${OBJ} \
        -lpw -lpf -lsms -sexec
        mkexec global \"globalžv1.00, manipulate \"\"Global Variables\"\", from PROGS, Belgium\"

_c_o : ; ${CC} ${CFLAGS} ${DEFINES} $<

_s_o : ; ${CC} -c $<

_asm_rel : ; ${MAC} $<

PROGS, Professional & Graphical Software
last edited June 28, 1996
originally published in QL Today May/June 1996