Section 4 Home page Section 6

5. Updating a Window: Redraw Events

We would like to have a decent display in our window. Firstly, it should not show the background, but a nice clean surface. Secondly, it should keep showing the contents we want, even when we drag another window over it and away.

Although there are several steps involved in the following, the code is completely reusable in all your GEM programs. Once you have a working template, you can simply copy it and everything should work.

5.1. Drawing within an Area

Look again at our function draw_example: the call to v_gtext does not refer to our window, only the screen. Our drawing code could, in theory, write anywhere on the screen we want. However, this would break the illusion of our program providing a window onto some information. A better solution is to set up a clip area: this is a rectangle we define, so that if our VDI calls go outside that area, they will be clipped to only appear within the given rectangle.

We thus wrap our call to draw_example in a function draw_interior, which sets up a clip area and also clears the display of our window for us. The following draw_interior function does a lot of useful work for us. First, it hides the mouse, so we don’t draw over it. Second, it sets a clip area (set_clip is defined in "windows.c"), so that all VDI calls will only show within the given rectange. We then get the dimensions of the working area of our window. This is the same wind_get call we made before, except now we ask for the working area of our window, instead of the desktop. The working area will exclude the window boundaries, any sliders, etc. A call to clear the window and we can then call out to our application’s drawing code. Finally the clipping is turned off, and the mouse reshown.

/* Draw interior of window, within given clipping rectangle */
void draw_interior (struct win_data * wd, GRECT clip) {
        int pxy[4];
        int wrkx, wrky, wrkw, wrkh; /* some variables describing current working area */

        /* set up drawing, by hiding mouse and setting clipping on */
        graf_mouse (M_OFF, 0L);                                 // <1>
        set_clip (true, clip);
        wind_get (wd->handle, WF_WORKXYWH, &wrkx, &wrky, &wrkw, &wrkh);

        /* clears the display */
        vsf_color (app_handle, WHITE);                          // <2>
        pxy[0] = wrkx;
        pxy[1] = wrky;
        pxy[2] = wrkx + wrkw - 1;
        pxy[3] = wrky + wrkh - 1;
        vr_recfl (app_handle, pxy);

        /* draws our specific code */
        draw_example (app_handle, wd);                          // <3>

        /* tidies up */
        set_clip (false, clip);                                 // <4>
        graf_mouse (M_ON, 0L);
}
  1. Hide the mouse, set clipping on, and find the work area.

  2. Clear the work area.

  3. Call out to our drawing code.

  4. Set clipping off, and restore the mouse.

5.2. Updating a Display

One of the technically more complex aspects of handling windows in GEM is the concept of the rectangle list, and how to manage it. In essence, the idea is very simple. Assume our program’s window is obscured by several windows. One of these windows is closed. Our program will be told to redraw the area which was underneath the window which has now closed.

However, we cannot blindly fill that area with the contents of our window, because there may be other windows partially obscuring this area. Hence, we must take a look at every other window on the system, find those which are obscuring our window, and make sure we avoid drawing over them.

For example, consider the following two images. In the first image, we have three windows,all overlapping each other. We now close the top window: how should the back window, the one showing the list "Classic 1" etc, be updated?

images/redraw-1.png

The second image shows the scene with the top window closed. The highlighted rectangle indicates the area that needs to be redrawn, and only this area. If we redraw any other parts of the bottom window, we will overwrite the contents of the window now on top.

images/redraw-2.png

The process to ensure we only update the required areas is called "walking the rectangle list". Our application receives the total area that was revealed by closing the top window, and which needs updating. Our application compares that area to the other windows that are obscuring it, before locating the highlighted rectangle to draw.

Walking the rectangle list is controlled by two get operations:

  1. wind_get (wd→handle, WF_FIRSTXYWH, …) retrieves the first rectangle relevant to our window, storing the x, y, w, h values in the remaining reference parameters.

  2. wind_get (wd→handle, WF_NEXTXYWH, …) retrieves the next rectangle, again storing the x, y, w, h values in the remaining reference parameters.

This continues while the w and h values retrieved are non-zero values: when they are both zero, we have reached the end of the rectangle list.

All we check is whether the rectangles in the list intersect our rectangle to update and, if so, we draw the intersected area.

/* Called when application asked to redraw parts of its display.
   Walks the rectangle list, redrawing the relevant part of the window.
 */
void do_redraw (struct win_data * wd, GRECT * rec1) {
        GRECT rec2;

        wind_update (BEG_UPDATE);                       // <1>

        wind_get (wd->handle, WF_FIRSTXYWH,
                  &rec2.g_x, &rec2.g_y, &rec2.g_w, &rec2.g_h); // <2>
        while (rec2.g_w && rec2.g_h) {                  // <3>
                if (rc_intersect (rec1, &rec2)) {       // <4>
                        draw_interior (wd, rec2);       // <5>
                }
                wind_get (wd->handle, WF_NEXTXYWH,
                          &rec2.g_x, &rec2.g_y, &rec2.g_w, &rec2.g_h); // <6>
        }

        wind_update (END_UPDATE);                       // <7>
}
  1. Turn off all other window updates, while we update our window contents.

  2. Retrieve the first rectangle to update.

  3. We now loop, while there is a rectangle that needs updating.

  4. Check if the rectangle to update intersects the part of the display we have to update.

  5. Draw our window only in the area intersecting the two rectangles.

  6. Retrieve the next rectangle to update.

  7. Turn back on other window updates.

(Note that rc_intersect is provided within AHCC’s library.)

5.3. Responding to REDRAW Events

What does the AES do when it thinks our window needs updating? It sends us a REDRAW event. Along with the event, it tells us the handle of the window we need to update, and also a rectangle: this rectangle is the area of our window it wants us to update.

For a redraw event, we receive the following information in msg_buf:

  • msg_buf[0] = WM_REDRAW, the type of event.

  • msg_buf[3] = handle of window which must be redrawn.

  • msg_buf[4] = x coordinate of area to redraw.

  • msg_buf[5] = y coordinate of area to redraw.

  • msg_buf[6] = width of area to redraw, in pixels.

  • msg_buf[7] = height of area to redraw, in pixels.

do {
        evnt_mesag (msg_buf);

        switch (msg_buf[0]) {                                   // <1>
                case WM_REDRAW:                                 // <2>
                        do_redraw (wd, (GRECT *)&msg_buf[4]);   // <3>
                        break;
        }
} while (msg_buf[0] != WM_CLOSED);
  1. We use a switch statement to select the action to respond to, based on the type of event.

  2. The type for a redraw event.

  3. We simply call our do_redraw function. As we only have one window in this program, we can pass its window data directly. Notice how the x, y, w, h coordinates in msg_buf[4,5,6,7] are turned into the GRECT type.

This part of the program does a lot of hard work. To recap, every time our program must redraw part of its screen, it will receive a REDRAW event, containing the window handle and screen area to redraw. We pass these to do_redraw which walks the rectangle list, ensuring we draw on only those parts of the window which are not obscured by other windows in the operating system. do_redraw then calls, for each unobscured rectangle, draw_interior, which sets the clipping region to the unobscured rectangle and draws our application’s data into just that region.

The good news is two-fold. First, having written all this code, it will probably be pretty-much the same for any GEM program. The main part you need to change is draw_example, or its equivalent. Second, this sequence is called every time the computer thinks you need to update the screen, and so you don’t have to do anything extra to keep the window updated in many cases. In particular, your program will receive a REDRAW event when it starts up, which means we can delete the call to draw_example we had in start_program.

5.4. Sample Program: Version 2

This version includes the REDRAW events, walking the rectangle list, and the code to clear the display background. When you run the code, the display should now look a lot better. Try dragging another window over the top of your program: notice how the display updates itself, keeping your window’s display exactly as you want it.

However, it is now time to use some of the other possible window controls, and manage the events they trigger.