BY CLAYTON WALNUM
Certainly all of us have used GEM's various constructions such as windows and dialog boxes. When we use these things we don't pay much attention to what is going on. We just take it for granted that when we drag the lower right corner of a window with the mouse pointer the window will get larger, or when we grab its title bar we'll be able to move it about the screen.
What you may not realize is that these routines that control windows and dialog boxes and the other GEM constructions are actually "high-level" routines that call various other "lower-level" routines in the AES and VDI. We've touched on this subject before when we talked about the VDI and why those graphics routines were called "primitives" and when we learned that it's not a good idea to call VDI mouse functions if you're using AES functions that may also call the VDI mouse routines.
Specifically, what we'll be talking about this month are a few of the AES functions that are used by windows and dialog boxes. We're going to be "dropping down a level," as it were, from the very sophisticated window and dialog routines to some of the functions that these routines call to do their tricks.
The sample program
Listing 1 is this month's sample program. You should type it in and compile it before going any further. Note that the program was written using Megamax C. If you have a different compiler, you may have to make some slight changes to the program to get it to compile and run correctly.
When you run the program, a large rectangle will be drawn on the screen, and the program will then wait for input via the mouse. The large rectangle is a border within which all the program's activities will be contained.
Hold down the left mouse button and drag the mouse pointer down to the right. A box that expands and contracts with the movement of the mouse pointer will be drawn on the screen. When you release the mouse button, the final box will be filled in. In the lower right corner of the box will be a small button. By placing the mouse pointer over this button and holding down the left mouse button, you'll be able to change the size of the box by dragging on its corner. Note that, if you try to resize the box beyond the boundary we've set up, the program will ignore your request.
If you place the mouse pointer inside the box and hold down the left button, the mouse pointer will change into a hand that you can use to reposition the box anywhere on the screen, as long as the box stays within the border we've drawn. (As a matter of fact, the program won't allow you to drag the box outside of the border.)
To get out of the sample program and back to the Desktop, hold down the right mouse button.
I'm sure you recognize all of the effects we're using in our sample program. We spend a lot of time on our STs moving and sizing boxes. These routines are the foundations upon which the more advanced abilities of GEM are built. Fortunately, these routines are available to us as GEM programmers, so that we can construct our own specialized, GEM-like routines.
All of the functions we're using to simulate GEM in this program are part of the AES Graphics Manager library. We've used a couple of these functions, like graf__handle( ) and graf__mouse( ), in past C-manships. We've also used most of the Graphics Manager's functions indirectly. For instance, when we studied dialog boxes, we talked about a function called form_dial( ) that, among other things, allowed us to have an expandingor shrinking box displayed on the screen. The form__dial( ) function performed some of its tricks by calling the graf__growbox( ) and graf_jhrinkbox( ) functions found in the AES Graphics Manager. (Note that all the Graphics Manager functions start with graf.)
If we wish to draw an animated expanding box of our own, we just use the call:
graf_growbox ( start_x, start_y, start_w, start_h, end_x, end_y, end_w, end_h );
Here the first four arguments are the X coordinate, Y coordinate, width and height of the starting rectangle and the second four arguments are the X coordinate, Y coordinate, width and height of the final rectangle. This function will return a zero upon failure or a positive integer if successful.
Its complementary function, the one that draws an animated shrinking box, is called in the same way:
graf_shrinkbox ( start_x, start_y, start_w, start_h, end_x, end_y, end_w, end_h );
In this month's sample program, we haven't used graf__growbox() or graf__shrinkbox(), but we have used many of the other functions available in the Graphics Manager library.
Look at the function called do__box(). Near the top you'll see a nested while loop. The first while is set up to repeat until we receive acceptable coordinates for the box the user wants to draw. The inner while loop forces the program to wait for a press of the left mouse button.
We continually poll the mouse's state until we detect that the button has been pressed. The function call we use to check the mouse's state is:
graf_mkstate ( &mouse_x, &mouse_y, &mouse_but, &key_state );
Here &mouse__x, &mouse__y, &mouse__but and &key__state are the addresses of the integers that will receive the mouse's current X coordinate, the mouse's current Y coordinate, the mouse's current button state, and the state of the keyboard's Shift, Control and Alternate keys. The integer mouse__but will contain a 1 if the left button was pressed, a 2 if the right button was pressed and a 3 if both were pressed. The integer key__state will contain a 1 if the right Shift was pressed, a 2 if the left Shift was pressed, a 4 if the Control key was pressed, and an 8 if the Alternate key was pressed. For multiple key presses, we would just add the appropriate values together. (You remember how to handle bit settings, right?)
When we detect that the left mouse button has been pressed, we get the mouse's coordinates and use them as the coordinates for the upper left corner of the box. The next step, then, is to allow the user to drag the mouse to outline the box that he wants. We do this with the call:
graf_rubberbox ( box_x, box_y, min_w, min_h, &box_w, &box_h );
Here box___x and box___y are the coordinates of the box's upper left corner, min___w and min___h are the box's minimum allowable size in pixels, and &box___w and &box___h are the addresses of integers where the final selected width and height of the box will be stored.
After our call to graf___rubberbox(), we'll have all the information we need to draw the box the user selected. But before we actually draw the box, we must check to make sure that the box will fit inside our boundary. In the if statement that checks this condition, you'll notice that we're using the values stored in work___out and work___out[l]. The work___out[ ] array is one of our GEM global arrays, and its elements were filled in when we initialized the application program. Element 0 of this array contains the width of our device (in this case, the device is the screen, so it is measured in pixels), and element 1 contains the height of our device.
Once we've drawn the box, we enter another nested while loop. The outer loop checks for a program-exit condition, and the inner loop again polls the mouse. If the right mouse button is pressed, we exit the program. If the left mouse button is pressed, we have to check the location of the mouse, so that we know whether the user wants to move the box or size it.
If the user is holding down the mouse button while on the box's sizing button, we need to call graf___rubberbox() again to let the user choose the new size of the box. There are two complications. The first is something we've already handled before, and that is that we can't allow the size of the new box to exceed the boundary that we've set up.
The second problem arises when the user chooses to make the box smaller. In this case it isn't good enough to just redraw the box at its new size because we'll be leaving on the screen parts of the old box. To erase the leftovers, we first have to calculate their size and then redraw them in the background color. Of course, we could simplify matters by erasing the entire old box before we draw the new one, but that would make the program less pleasing to the eye. The entire resizing process is handled in our function size___box().
If the user has pressed the left mouse button while within the box (but not on the box's sizing button), we have to allow the user to move the box to a new location. Our function move___box() illustrates how to do this.
We can get the new location of a box with the call:
graf_dragbox ( box_w, box_h, &box_x, &box_y, bound_x, bound_y, bound_w, bound_h, &end_x, &end_y );
Where the integers box___w and box___h are the box's width and height; &box___x and &box___y are the addresses of integers where the box's new X and Y coordinates will be stored; the integers bound___x, bound___y, bound___w and bound___h are the coordinates, width and height of the boundary within which the box must remain; and &end___x and &end___y are the addresses of integers where the box's new X and Y coordinates will be stored.
Once we get the coordinates of the new box, all we have to do is erase the old box and draw the new one.
There are a couple of functions in the Graphics Manager library that we haven't looked at yet. One of them is a function that allows you to draw an animated box moving from one location to another. The call looks like this:
graf_movbox ( box_w, box_h, old_x, old_y, new_x, new_y );
Here, the integers box___w and box___h are the width and height of the box, the integers old___x and old___y are the current X and Y coordinates of the box, and the integers new___x and new___y are the final coordinates of the box. Note the spelling of the function call; it's different than the spelling in the Megamax manual. If you try to call the function using the manual's spelling, your program will not link properly because the linker won't be able to find the label. Anyway, this function is really of limited use; I can't think of any place where I've seen it in action.
Finally, the two remaining functions, graf___slidebox() and graf___watchbox() are used with object trees. The first is the function that allows sliders, such as those found in windows, to work; and the second allows us to track the mouse in and out of a particular rectangle while the mouse button is held down. These functions would be useful should we ever want to write our own custom dialog box (form___do()) routines.
This completes our tour of the AES Graphics Manager library (though we may get back to graf___slidebox() and graf___watchbox() at some time in the future). You should now have an even clearer idea of how some of GEM's routines work, and you should be able to construct many handy GEM-like routines with the information you've learned.