Section 3 Home page Section 5

4. Getting Started: Opening a Window

In this first section, we see how to start up a GEM program, and display the most simple of windows. This is the "hello world" of GEM programming.

I find it convenient to separate the code into three sets of source code:

  1. main.c contains the main function, and handles all the setup code before calling your program.

  2. windows.c contains all the standard GEM stuff, and functions to respond to GEM events.

  3. eg_draw.c contains the specific drawing code for this application.

The header file "windows.h" is also important. Here we define some variables which AES requires, and also our own window data structure (see below).

4.1. Starting a GEM Application

We must begin and end our GEM application with some startup and teardown code. These register our application with the operating system, and provide a unique reference to identify our application.

I place this code in main, with a call out to a function to run our own application (for a complete listing, including header files and variable definitions, see the source code which accompanies this guide):

void main (int argc, char ** argv) {
        appl_init ();                   // <1>
        open_vwork ();                  // <2>
        start_program ();               // <3>
        rsrc_free ();                   // <4>
        v_clsvwk (app_handle);          // <5>
        appl_exit ();                   // <6>
}
  1. This is a built-in AES function, and initialises our application. It returns a unique reference for our application, but we won’t need it just yet.

  2. This function we must provide, and is used to create and open a virtual work station for our program, and store the application handle.

  3. This function we provide, and is where our program runs.

  4. After our program has finished, resources are freed.

  5. Using the application handle from (2), the work station is closed.

  6. And finally our application exits.

Functions (2) and (3) must be provided by ourselves. (2) is a standard process to open a virtual workstation (to obtain access to the screen), as shown below. An important part of this is to create a reference to the screen, through the call to graf_handle; this reference is stored in the variable app_handle.

void open_vwork (void) {
        int i;
        int dum;

        app_handle = graf_handle (&dum, &dum, &dum, &dum);      // <1>
        work_in[0] = 2 + Getrez ();                             // <2>
        for (i = 1; i < 10; work_in[i++] = 1);
        work_in[10] = 2;
        v_opnvwk (work_in, &app_handle, work_out);              // <3>
}
  1. Set up the screen output, and save the reference. We don’t need the values of the other parameters (which relate to the size of text in the screen).

  2. Set up the values for declaring a virtual workstation.

  3. Finally create the virtual workstation, for our application.

4.2. Opening a window

Each window in our application will display some information to the user. A window may respond to many events: it can be moved around the screen, be made smaller or larger, have its sliders moved, and must always keep its contents up to date. There are, therefore, many pieces of information which a window must be aware of. For this reason, I use a struct to store all data relevant to a single window. My basic win_data is as follows:

struct win_data {
        int handle;     /* identifying handle of the window    <1> */
        char * text;    /* text to display in window           <2> */
};
  1. Every window has a unique handle to identify it.

  2. Application-specific data, used to control what is shown in window.

As we proceed through this guide, the contents of win_data will expand. In addition, your application will have various data that are relevant to the window. This should also be stored or referred to in win_data: for now, we use text as our example application’s specific data.

Creating a window requires setting up an instance of win_data with the relevant data, creating a window, and then showing it. The following code appears in "windows.c", in the start_program function.

struct win_data wd;
int fullx, fully, fullw, fullh;

/* 1. set up and open our window */
wind_get (0, WF_WORKXYWH, &fullx, &fully, &fullw, &fullh);              // <1>
wd.handle = wind_create (NAME|CLOSER, fullx, fully, fullw, fullh);      // <2>
wind_set (wd.handle, WF_NAME, "Example: Version 1", 0, 0);              // <3>
wind_open (wd.handle, fullx, fully, 300, 200);                          // <4>
wd.text = "Hello";                                                      // <5>
  1. This line finds the current size of the desktop. The first parameter, 0, refers to the desktop. The next, WF_WORKXYWH, tells the function which data to get, and the remaining addresses are locations to store the result.

  2. This line creates the window. The first parameter defines which parts of the window to include. Notice we save the handle in our wd variable. Also, we give the maximum dimensions for the window - in this case, the size of the desktop.

  3. Sets the value of WF_NAME in our window: this is the title of the window. The title text must be stored in your program somewhere, as the AES does not make a copy.

  4. Finally, open the window on the screen. The last four parameters define the x, y position and w, h size of the window. These do not have to be the same as the maximum size of the window.

  5. Set up some application-specific data for our window: in this case, the text to display in the window.

Tip
On, for example, an Atari ST, it is possible for GEM to run out of windows. If this happens, the handle retrieved from wind_create will be negative. You should, between (2) and (3), check if wd.handle is negative, and warn the user if so. We won’t worry about this in our example programs.

The functions wind_set and wind_get will be met frequently as you work with windows. They both take a window handle as their first parameter, and then an identifier for some component/information of that window. Handle 0 is used to refer to the desktop.

The identifier refers to different parts of the window. Some of the values we will use include:

  • WF_NAME : the text in the title of the window

  • WF_INFO : the text in the information line of the window

  • WF_WORKXYWH : the current working area of the window, on which you can draw

  • WF_CURRXYWH : the current size of the window, including its widgets

  • WF_PREVXYWH : the previous size of the window, including its widgets

  • WF_FULLXYWH : the maximum size of the window, including its widgets

  • WF_HSLIDE : the position of the horizontal slider

  • WF_VSLIDE : the position of the vertical slider

  • WF_HSLSIZE : the size of the horizontal slider

  • WF_VSLSIZE : the size of the vertical slider

Notice in wind_create we include the flags NAME|CLOSER. These tell our window to include space for a title, and for a close box. We set the title through WF_NAME, as above. The close box is used to exit the program, and we discuss how this works when we cover the event loop, below. Windows can contain many parts, and to use them we include them in the list of flags. Some common flags are:

  • NAME : for the title of a window

  • CLOSER : adds a close box

  • FULLER : adds option to make window its maximum size, and restore

  • MOVER : allows window to be moved

  • INFO : an internal information line, for the window

  • SIZER : allows window to be resized

  • UPARROW : shows the up arrow on the vertical slider

  • DNARROW : shows the down arrow on the vertical slider

  • VSLIDE : includes a vertical slider

  • LFARROW : shows the left arrow on the horizontal slider

  • RTARROW : shows the right arrow on the horizontal slider

  • HSLIDE : includes a horizontal slider

The function start_program continues as follows:

draw_example (app_handle, &wd);                 // <1>

/* 2. process events for our window */
event_loop (&wd);                               // <2>

/* 3. close and remove our window */
wind_close (wd.handle);                         // <3>
wind_delete (wd.handle);                        // <4>
  1. Draws the content of our window. (This is only here for version 1.)

  2. Waits for the user to interact with our program. Returns when the user closes the window.

  3. To tidy up, we first close our window, to remove it from the screen.

  4. Finally, we delete the window, releasing its handle to be used again.

4.3. Displaying some content

A function draw_example is used to draw the window contents on the screen. Our example simply displays the given text in the window.

void draw_example (int app_handle, struct win_data * wd) {

        v_gtext (app_handle, 10, 60, wd->text); // <1>

}
  1. Displays text at the given coordinates on the screen. Note we use app_handle, referring to the screen, in calling the VDI functions.

4.4. Event Loop

All GEM programs work in an "event driven" manner. This means that the program waits for the user or operating system to send it an event: something to do. The program then does what it wants in response to that event, before returning to the loop. For example, clicking on the close icon at the top-left of a window will send a message to our program. Our program must then do the appropriate thing, in this case, we must close the window and end the program.

For the example we will use the function evnt_mesag to obtain events for our program. In any real GEM program, you will use its big brother evnt_multi (or AHCC’s EvntMulti), which lets you spot events from the mouse, keyboard or other sources as well as those relating to your window; we discuss other events in a later section. For now, to keep things simple, we will only need to respond to window events, so we use this call.

void event_loop (struct win_data * wd) {
        int msg_buf[8];                         // <1>

        do {
                evnt_mesag (msg_buf);           // <2>

        } while (msg_buf[0] != WM_CLOSED);      // <3>
}
  1. Set up some storage for the event message.

  2. Retrieve the next event.

  3. Loop until we receive a WM_CLOSED message, indicating that the user clicked on close.

In later sections, we will respond to more kinds of events within the loop. We will then discuss the other values that can be used in msg_buf.

4.5. Sample Program: Version 1

At this point, we have a window which displays some text inside it. However, if you drag a window over the top of it, the contents will disappear. Also, none of the widgets (except for close) do anything: we cannot move the window, bring it to the top when we click on another window, etc. Also, the background shows through in our window, particularly a problem if you are running a multi-tasking OS, such as MINT.