Those mysterious rectangles.
by Clayton Walnum
Those of you who read the last installment of C-manship (that's all of you, right?) are no doubt a little—or, more likely, a lot—perplexed about all this rectangle business. Don't feel bad. Not only is it a complex topic, but it's also virtually impossible to find complete documentation on it anywhere. Most of the books I've seen merely gloss over the subject, as if the reader were born with an intimate knowledge of GEM's rectangle list.
Well, friends and neighbors, I, for one, was not born with that knowledge. I've spent the last couple of months in research, trying to dig out all the facts I could about rectangle lists, not only because I wanted to clarify the issue for myself, but because I wanted to put together a decent tutorial to help you, the reader, understand this mysterious process.
The typing part.
This month's demo program can be found in Listing 1. You should now type it in and compile it, since all the following discussion will be based on it. The functions main(), open__vwork(), do__move() and set__clip() are identical to the functions of the same name in issue 16's listing. If you have that listing, you don't need to retype the above functions; just copy them in from the old listing. Note that the program was developed using the Megamax C compiler. If you have a different compiler, you may need to make some modifications.
You can also find the listing on this month's disk version and in the Atari SIG on Delphi.
I recently spoke with Frank Cohen of Regent Software, who told me that one method he used to sort out this rectangle nonsense was to update each rectangle returned from the redraw message with a different fill pattern. I told him I thought that was sheer genius (well, slightly clever, anyway) and as soon as I hung up the phone, I set about stealing his idea.
Steal it, I did (with his blessings, I hope). The demo program uses this method to graphically illustrate the process of walking the rectangle list. The figures on the following pages take you step by step through the tutorial here. These screens were taken from a monochrome monitor, so if you have a color system, you may get slightly different results.
When you run the program, you'll first see the screen shown in Figure 1. Three windows have been opened, and since the windows need to have their work areas drawn, GEM has sent the program three redraw messages—one for each window. Because I inserted a call to Cconin() in the rectangle list processing loop, nothing further will happen until you press RETURN.
But before we let GEM do its thing, take a look at the top line of the screen. Here, you'll see the handle of the window for which the redraw message was sent and the number of the rectangle from the rectangle list we're currently working on. In Figure 1, we see that window 1 is waiting for a redraw and that we're about to process rectangle 1 from the list.
We can't blindly go ahead and fill in window 1's workspace, because part of that space is covered by the other two windows; we don't want to erase them. For this reason, when GEM created the redraw message for window 1, it took that window's workspace (only those areas not covered by the other two windows) and divided it into a series of non-overlapping rectangles. It then loaded the coordinates and sizes of those rectangles into the rectangle list, where we can get at them for processing.
Now press RETURN once. The first rectangle from the list will be processed, the screen will be updated, and the program once again waits for a key press. But before we give it that key press, let's take a closer look at what happened with that first rectangle.
If you think back to the last C-manship, you'll remember that, when processing the rectangle list, we're always dealing with two rectangles: one returned in the redraw message and one retrieved from the list. In the case of the rectangle we just processed, the rectangle received from the redraw message was window 1's complete work area. The rectangle returned from the list was the one you just saw filled in (the light gray rectangle at the top of Figure 2). The first thing we had to do when we got these rectangles was check if they overlapped, with this call:
rc_intersect ( rec1, rec2 );
What I didn't mention last time was that, after the call, the rectangle found in rec2 may not be the same one we started with; it'll actually be a rectangle representing the intersection of the two original rectangles.
Now, do our first two rectangles intersect? Sure enough! We know we've found an area that must be updated, and we send the rectangle found in rec2 to draw_interior(), our actual drawing routine. Note that, in this case, the rectangle returned in rec2 was the same rectangle we started out with, because its entire area is in intersection with the one returned in the redraw message.
Look at the information at the top of the screen. We're now ready to process rectangle 2 from window 1's list. Press RETURN, and this area will be updated. Keep pressing RETURN, filling in each of window 1's rectangles, until you hear the computer's bell ring, indicating you've reached the end of the rectangle list and completed the processing of the first redraw message.
Now, the information at the top of the screen will show that we're about to process a redraw message for window 2. Before you press RETURN, see if you can figure out how many rectangles it'll take to do the job. (Hint: nothing should be drawn in window 3's workspace.) Figured it out? Everyone who guessed that we need to update two rectangles may look into a mirror and tell himself how clever he is. Press the RETURN key twice to update window 2. The bell should ring after the second press, leaving us ready to process the redraw message for window 3.
How many rectangles for window 3? One. Window 3 is the topmost window, so has nothing covering it; we need only fill in the entire work area.
Now your screen should look similar to Figure 2. Grab window 3 by the mover bar, and drag it into the upper right corner of the screen, as shown in Figure 3. Notice that even though the image of the old window 3 is still on the screen, we didn't have to redraw the window in its new position. Since its complete image was on the screen, GEM did the job for us, blitting the image to its new location.
After blitting the window image, GEM then redrew window 2, not including the workspace. Why not the workspace, too? Before we moved window 3, window 2 was partially hidden, so after the move, GEM didn't know what we wanted to put in the uncovered area. It happily dropped the whole mess—including a rectangle list—into our laps. Our status line at the top of the screen now tells us that we've received a redraw message for window 2, and we're waiting to process rectangle 1.
Now think for a minute (aw, come on, it won't hurt much). What portion of window 2 is going to be redrawn when we press the RETURN key? Any guesses? The entire work area, you say? Wrong! Why take the time to update the entire work area, when only a small section—the area covered by window 3—needs it? Because it's easy? Well, yes, but it's just as simple to do it right, because, after our call to rc__intersect, we have the area of intersection—the exact rectangle we need—in rec2. Press RETURN to see this rectangle get its due.
Since GEM wants to get rid of the rest of window 3's old image, we now have a redraw message for window 1. Press RETURN. Hmmm. Nothing happened. If you look at the rectangle number in our status display, though, you'll see that something did happen, because we're now on rectangle 2. So what happened to rectangle 1? We processed it just like all the others, but because it didn't intersect the dirty area (the rectangle returned in the redraw message), we skipped over it. If you look at the screen, you can see that rectangle didn't need to be redrawn.
Continue pressing RETURN until the bell rings, watching to see which rectangles are redrawn and which are skipped. Your screen should end up resembling Figure 4.
As you may recall, another way to generate a redraw message is to increase the size of a window. Grab window 2's sizer button (gently, we don't want any bruising) and enlarge it as shown in Figure 5. Predictably, a redraw message for window 2 is sent. How many rectangles this time? Only one.
Sorry, but I guess it was a trick question. Go ahead and press RETURN. The entire work area of window 2 is redrawn, leaving your screen looking like Figure 6. Strange, considering I just said it was a waste of time to redraw portions of a window that didn't need it. That top left-hand corner didn't need to be redrawn, did it? The fact is, whenever you enlarge a window or make it uppermost, the rectangle returned in rec2 will be the entire work area. I never promised GEM was consistent.
Next step in our experimentation: move window 2 to the lower right corner of the screen, below window 3 (not overlapping), then press RETURN until the bell rings, watching as window 1 is updated. Figure 7 shows the results. Now click the mouse pointer on window 1's workspace, making it uppermost. Press RETURN and your screen will look like Figure 8. Using window 1's sizer button, reduce the window to its smallest size, as shown in Figure 9.
Note that, when we reduced window 1, GEM cleaned up the desktop on its own, leaving only the work areas of windows 2 and 3 for us to worry about. As they stand now, both windows 2 and 3 contain information left behind by window 1. Press RETURN twice to update both windows.
Move window 1 around the desktop, without overlapping any of the other windows or going off screen. We get no redraw messages. GEM is delighted to blit window 1 from one place to another with no help from us.
Now place window 1 in the center of window 2, as shown in Figure 10. Still no redraw messages. GEM blitted the window to its new location then erased the old image, just like before. Windows 2 and 3 don't get redraw messages because none of their visible data has been corrupted. Sure, we covered some of it up, but that's not a problem until we move window 1 out of the way again. Do so now, as shown in Figure 11. Whoops! GEM did the blit all right, but it left a mess behind. Press RETURN to conclude this fascinating journey through GEM's rectangle lists, leaving the screen shown in Figure 12.
Out of the fog.
I hope the above experiments have taken some of the mystery out of GEM's rectangles and how the system works. Play around with the program all you want, moving and changing windows, all the while watching to see how GEM sets up its rectangle list and how the program processes it. There are an infinite number of possibilities. It'll be a long time before you exhaust them.
If you want the program to run without waiting for a key press, remove the call to Cconin() found in the function do__redraw(). The information printed at the top of the screen won't do you much good if you do, though. You'll never be able to read it as fast as our program can update those rectangles.
Before we close up shop for this month, there are a couple of things in Listing 1 we ought to go over. This is the first time we've handled the WM_TOPPED message in a program.
We get this message from GEM whenever the user clicks the mouse over an inactive window. All it takes to "top" the window, make it uppermost and active, is the call:
wind_set (Msg_buf , WF_TOP, 0, 0};
Here, msg__buf is, as usual, the window's handle; WF__ TOP is the function we want wind__set() to perform, defined in GEMDEFS.H as 10; and the two zeros are dummy arguments.
Another nuance worthy of note is the way we're using arrays to cut down the window handling code in the program. Wherever we perform the same function concurrently on all windows, we can use a FOR loop, the control variable of which becomes an index into an array (one element for each window).
If you look at the function open__window() in Listing 1, you'll see the loop that opens the windows and places their handles into the array w__h. We need only one set of wind__create() and wind__set() calls. However, when we actually open the windows, we use a separate wind__open() call for each window. Why? Because each window is opened at its own set of coordinates, and for the sake of clarity, I decided to "hard code" those coordinates into the function calls, rather than use arrays.
Another day, another dollar.
Next month our subject will be…Yes, you guessed it! Windows, again. Maybe we'll figure out how to use those sliders and arrows to change the contents of a window's workspace. Sounds like a good idea to me. How about you?