Section 11 Home page Section 13

12. Events

This is an appropriate time to consider event handling in more detail, so we can extend the range of events we can respond to. For example, in the last section we considered menus. Important menu items should contain keyboard shortcuts, for users who prefer not to take their hands from the keyboard. Keyboard events are sent to our program just like other events, but we must listen for them. To do so, we must change our event loop to handle multiple event types.

Any GEM program may produce one or more of a range of different event types. These include:

  • MU_MESAG: AES messages for the window and menu events (as we have handled so far)

  • Mouse events: information on the mouse, there are three of these

    • MU_M1: When mouse enters or leaves region 1

    • MU_M2: When mouse enters or leaves region 2

    • MU_BUTTON: Information on button clicks

  • MU_TIMER: Timer events, an event triggered at regular time intervals

  • MU_KEYBD: Keyboard events, triggered when the user types on the keyboard

Although GEM provides separate event listeners for these different types, in any real GEM program multiple event types will be needed. For example, any GEM program will need to respond to AES messages about the windows and menus, mouse events and the keyboard, at a minimum; to do this, we need to use a multiple-event listener.

There are two ways to handle multiple event types. There is the traditional way, which uses a call to evnt_multi. This requires setting up several internal variables to store any required return values. AHCC offers an alternative way, which uses a call to EvntMulti, using a convenient struct to hold all input and return values. The advantage of evnt_multi is conformance to previous practice. The advantage of EvntMulti is a simpler calling routine; the AHCC description also claims improved performance. We shall describe both ways.

12.1. Traditional evnt_multi

The traditional evnt_multi function is an extension of the event_mesag we have been using so far. It offers the advantage of being documented in the Atari literature, and will be familiar to most programmers. However, it does require care in defining and presenting different reference variables to hold various return values.

The call to evnt_multi looks as follows:

int evnt_multi( int ev_mflags, int ev_mbclicks, int ev_mbmask,
                int ev_mbstate, int ev_mm1flags, int ev_mm1x,
                int ev_mm1y, int ev_mm1width, int ev_mm1height,
                int ev_mm2flags, int ev_mm2x, int ev_mm2y,
                int ev_mm2width, int ev_mm2height,
                int *ev_mmgpbuff, int ev_mtlocount,
                int ev_mthicount, int *ev_mmox, int *ev_mmoy,
                int *ev_mmbutton, int *ev_mmokstate,
                int *ev_mkreturn, int *ev_mbreturn );

Many of these variables define values describing the kinds of events to listen for. ev_mflags indicates the event types, such as mouse and keyboard. ev_mbclicks describe the number of clicks that will trigger an event, etc.

The reference variables (pointers to ints) are used for return values. For example, ev_mkreturn will hold the code of a pressed key. ev_mmgpbuff is a pointer to the message buffer for AES messages.

The following is a complete list:

  • ev_mflags: flags indicating which events to listen for

  • ev_mbclicks: number of mouse clicks to listen for

  • ev_mbmask: button mask (for which button)

  • ev_mbstate: button state (0 for up, 1 for down)

  • ev_mm1flags: 1 for leave, 0 for enter region 1

  • ev_mm1x: x coordinate of region 1

  • ev_mm1y: y coordinate of region 1

  • ev_mm1width: width of region 1

  • ev_mm1height: height of region 1

  • ev_mm2flags: 1 for leave, 0 for enter region 2

  • ev_mm2x: x coordinate of region 2

  • ev_mm2y: y coordinate of region 2

  • ev_mm2width: width of region 2

  • ev_mm2height: height of region 2

  • ev_mtlocount: low count for timer event

  • ev_mthicount: high count for timer event (pair is a long value for time in ms)

  • ev_mmox: mouse x coordinate

  • ev_mmoy: mouse y coordinate

  • ev_mmbutton: button state

  • ev_mmokstate: key state (bit 0 right-shift, 1 left-shift, 2 ctrl, 3 alt)

  • ev_mkreturn: holds the code of the pressed key

  • ev_mbreturn: number of clicks

  • ev_mmgpbuf: this is an array of 8 ints, which we have called msg_buf

The return value from evnt_multi gives the actual event type that occurred. For example, if we wish to listen for an AES message, mouse click or keyboard event, we would call evnt_multi with the flags:

result = evnt_multi (MU_MESAG | MU_KEYBD | MU_BUTTON, ...);

result would then hold one of the flag values, depending on which event type occurred.

The following excerpt from version 7 of our sample program illustrates how evnt_multi is used. In this version we listen for the standard AES messages regarding the menus and window events, but additionally look out for a keyboard event:

void event_loop (OBJECT * menu_addr, struct win_data * wd) {
        int msg_buf[8];
        int dum, key, event_type;                                             // <1>

        do {
                event_type = evnt_multi (MU_MESAG | MU_KEYBD, 0,0,0,0,0,
                                         0,0,0,0,0,0,0,0,msg_buf,0,0,
                                         &dum, &dum, &dum, &dum, &key, &dum); // <2>

                /* -- check for and handle keyboard events */
                if (event_type & MU_KEYBD) {                                  // <3>
                        if (key == 0x1011) { /* code for ctrl-Q                  <4> */
                                break; /* exit the do-while loop */
                        }
                }

                /* -- check for and handle menu events */
                if (event_type & MU_MESAG) {
                        switch (msg_buf[0]) {

                                case MN_SELECTED: /* menu selection */
                                        do_menu (menu_addr, wd, msg_buf[4]);
                                        /* return menu to normal */
                                        menu_tnormal (menu_addr, msg_buf[3], true);
                                        break;

                                // REMAINING CASE STATEMENTS

                        }
                }
        } while ((MN_SELECTED != msg_buf[0]) || (MAIN_MENU_QUIT != msg_buf[4]));
}
  1. Create some variables to hold the values created in evnt_multi. dum is used for those slots we do not need.

  2. Call evnt_multi with flags for the events we are listening for and references to the variables to hold results.

  3. Check event_type for the actual event that occurred.

  4. Use the variables to locate results relevant to each event type.

12.2. AHCC EvntMulti

AHCC’s custom EvntMulti offers the advantage of a simpler calling routine. Instead of defining separate variables for the possible return values, we simply define one instance of EVENT, and pass its address to EvntMulti. The return values and the msgbuf are all then stored within our instance of EVENT. (The only small downside is that the slot names are not very intuitive.)

The struct EVENT has a slot for each of the arguments to evnt_multi, as discussed above (except that ev_mbmask is called ev_bmask and ev_mmbutton is called ev_mmobutton). Instead of passing the values by position in the function call, input values are set and output values are stored in the relevant slots.

We use EvntMulti just like evnt_multi. First we set the input data for the events we want to listen for, then we call EvntMulti, and use its return value to decide which kind of event has occurred. Information about the event is stored in the relevant output slots of EVENT.

The following excerpt is from our example program, version 7. It does the same as was done using the evnt_multi call above:

void event_loop (OBJECT * menu_addr, struct win_data * wd) {
        EVENT ev;                                       // <1>

        ev.ev_mflags = MU_MESAG | MU_KEYBD;             // <2>

        do {
                int event_type = EvntMulti (&ev);       // <3>

                /* -- check for and handle keyboard events */
                if (event_type & MU_KEYBD) {
                        if (ev.ev_mkreturn == 0x1011) { /* code for ctrl-Q */
                                break; /* exit the do-while loop */
                        }
                }

                /* -- check for and handle menu events */
                if (event_type & MU_MESAG) {            // <4>
                        switch (ev.ev_mmgpbuf[0]) {     // <5>

                                case MN_SELECTED: /* menu selection */
                                        do_menu (menu_addr, wd, ev.ev_mmgpbuf[4]);
                                        /* return menu to normal */
                                        menu_tnormal (menu_addr, ev.ev_mmgpbuf[3], true);
                                        break;

                                // REMAINING CASES
                        }
                }
        } while ((MN_SELECTED != ev.ev_mmgpbuf[0]) ||
                 (MAIN_MENU_QUIT != ev.ev_mmgpbuf[4]));
}
  1. Create an instance of the EVENT struct.

  2. Set the flags for the event types to listen for.

  3. Make the call to EvntMulti.

  4. Test the event type, just as with evnt_multi.

  5. Slots in ev store the return values, like the variables used in evnt_multi.

12.3. Sample Program: Binary Clock

In the clock folder of the source code which accompanies this guide is a simple clock program. To be a little different, this displays clock times in binary. (This program looks best in colour.)

images/binary.jpg

The program illustrates the use of the timer event, along with the usual close/top/move/redraw messages for a GEM window. The program simply redraws its display after each second.

void event_loop (struct win_data * wd) {
        EVENT ev;

        /* listen for AES events and timer events */
        ev.ev_mflags = MU_MESAG | MU_TIMER;             // <1>

        /* timer should fire every second                  <2> */
        ev.ev_mtlocount = 1000;
        ev.ev_mthicount = 0;

        do {
                int event_type = EvntMulti (&ev);

                if (event_type & MU_TIMER) {            // <3>
                        /* when the timer event occurs, we need to update our display */
                        GRECT rec;

                        wind_get (wd->handle, WF_WORKXYWH, &rec.g_x, &rec.g_y, &rec.g_w, &rec.g_h);
                        do_redraw (wd, &rec);
                }

                if (event_type & MU_MESAG) {

                        // USUAL TOP/MOVE/REDRAW events

                }
        } while (ev.ev_mmgpbuf[0] != WM_CLOSED);
}
  1. Set up the listener for AES menu/window events and timer events.

  2. Set up the timer count to fire every second.

  3. After receiving an event, check if it is a timer event, and redraw the display if so.

12.4. Sample Program: Version 7

In version 7 of the program, we include a menu. The menu provides an about dialog, a way to quit the program, and a menu to select which poem to show.

The menu was created in Resource Master, and saved into the RSC file. The C header was exported, creating the .rsh file. The .rsh file is needed for compiling, and the RSC file for running the program.

The Quit option may be triggered via the menu or by keyboard, so this version uses multiple events in the event loop; two event loops are provided, one for the traditional approach, and one using AHCC’s custom one. The About option displays a simple dialog box: this uses the built in form_alert call to construct a dialog. I explain more about this and dialogs in general in the next section.

Finally, this version allows us to open and close poems as we wish. So the window creation code has been changed to allow for a more dynamic set of windows, and the window close code has been changed, to only close the selected window, not exit the program. The program is only exited with the QUIT option.