A Complete GEM Application, Part 3
by CLAYTON WALNUM
This month we'll continue examining the MicroCheck ST source code by adding the functions we need to get the menu bar working. We'll also be looking at the code that controls the sliders and arrows in our window. But first....
A Wee Bug
The ST, for some reason, overlooks some runtime errors. For example, a divide-by-zero operation, clearly illegal, doesn't seem to bother the ST at all (at least it doesn't generate any bombs; it does, however, cause an exception #5). Unless you're running the program in question from within the Laser C shell or with a monitor like Templemon installed, you'd never know if you got one of these exceptions.
When I was working on this month's listing, I discovered that there are times when the function calc__vslid() will try to divide by zero. I didn't notice this before because virtually all of MicroCheck ST was written using the older Megamax C, and, although the code was converted to Laser C, for some reason I never ran the program from the shell.
Anyway, you can fix the bug by adding the following piece of code to the beginning of calc__vslid(), right above the wind__get() call:
if ( line-cnt == 0 ) line-cnt = l;
Listing 1 is this month's portion of the MicroCheck ST source code. You should load the source code you've typed so far, delete the do__menu(), do__wind__close(), do__arrow(), do__vslide(), do__hslide() and handle__button() stubs (those do-nothing functions that we added for the linker's sake), and add Listing 1. You should leave in the handle__keys() stub, and, of course, all the stubs in this month's portion.
When you run the program (after compiling it, of course), you'll notice two big changes: the Quit option of the File menu now works (hallelujah!) and the horizontal scroll bar on the window works.
In point of fact, the entire menu bar is now working, but since we haven't added the code necessary to perform the functions chosen from the menu (except Quit), most of the menu choices still do nothing. Likewise, all the window controls are now in working order, but since the window is displaying nothing, most of them seem to be nonfunctional.
Now let's step through this month's listing.
Here we take the MU__MESAG (a menu message) passed from GEM and interpret it, sending program execution to the appropriate function. As you may recall (see the C-manship in the June '87 ST-LOG), the object number of the menu title can be found in the third element of the message buffer, and the object number of the selected entry within the menu can be found in the fourth element of the message buffer (in our case, msg_buf and msg__buf).
To interpret the message, we use nested switch statements. The outer switch checks msg__buf to find out which of the menus was accessed. The inner switch statements (one for each of the menus) uses msg_buf to route the user's request to the right function.
If you look closely at do__menu( ), you'll see that every menu and every selection within each menu is represented here. Although the function is long, it is really quite simple. The only other thing of note here is the call, at the end of the function, to menu__normal(), which deselects (turns off the highlighting) the menu title chosen.
This function is called whenever the user clicks on the window's close box or selects the Close entry of the File menu. Because MicroCheck ST has three modes—edit, search and cancel—do__wind__close() has three sections, each of which handles one of the modes.
Since we'll be modifying the window, the first thing we must do is call wind_update() to lock the window from any other redraws. (At the end of the function, we unlock the window with the same function.)
If the user is in the search or cancel mode, we need to return to the edit mode. The first two sections of the if statement handle these situations. In both cases, the current mode is turned off (returning the program to edit mode), the window name is changed to show that the user is back in the edit mode, the window is redrawn and the menu entries are set appropriately. (Some of them are not available in every mode, so, according to the mode, some are enabled and some are disabled.)
If we're already in the edit mode when the user selects Close, we need to close the account. First, we bring up an alert box, asking the user if he's sure he wants to close the account. If he is, we save the account to disk, blank the window, reset the menu entries (almost everything will be disabled) and reset the window's title bar to show the user that no account is open.
Whenever the mouse button is clicked on MicroCheck's work area, this function is called. If the mouse pointer was over the check window, then the user either wants to edit a check or wishes to mark it as cancelled.
If the program is not in the search or cancel modes, we call edit() to bring up the check dialog for the check he has selected, otherwise we call canc__chk(), the function that places the program in the cancel mode and allows the user to cancel transactions.
Whenever the user clicks the mouse pointer on one of the window's arrows or slider tracks (not on the slider itself), GEM sends us a WM__ARROWED message (see the C-manship in the May '88 ST LOG). This message comes in eight different flavors (only six of which are of interest to MicroCheck). The actual type of arrow message is contained in the fourth element of the message buffer. So in the case of MicroCheck, we call do__arrow(), passing it the value of msg__buf. The user may be asking to move up or down a line, up or down a page, right or left a line (or character, actually) or right or left a page. Since MicroCheck allows the user to scroll the window right or left only by a full page, we don't need to worry about the WA__LFLINE and WA__RTLINE messages.
As with do__menu(), we use a switch statement to route the user's request to the appropriate function.
If the user clicked in the portion of the slider track above the slider, do__uppage() takes over. Here we simply find out how many lines will fit in the window, subtract that value from the index number for the check displayed at the top of the window to calculate a new cur__top and redraw the window.
If the user clicked in the portion of the slider track below the slider, he wants to move down a page. We call do__dnpage(), which works much like do__uppage(), except that we add lines__available (the number of lines that'll fit in the window) to cur__top rather than subtract it.
When the window's Up arrow is clicked, do__upline() springs into action, moving the window up one line. Moving up or down a single line is, if it's to be done elegantly, much more complex than moving an entire page. When we move up or down a full page, we have no choice but to redraw the entire window in the normal way, since none of the data we want to display is available anywhere on the screen.
However, when we move up or down a single line, all the data we need, except one line, is on the screen. If we want the scrolling action to be smooth, we can't just redraw the entire window in the conventional way. Instead, we raster (block move) the portion of the window containing data we can use up or down one line, then add the new data at the top or bottom, depending on which way we're moving. These block moves of screen memory are fast and help create the illusion of the window scrolling a line at a time.
If you've forgotten how the raster functions work, please refer to the C-manship in the March '87 ST-LOG. There isn't room here for a complete discussion.
So, in do__upline() we subtract one from cur__top (the index of the check shown at the top of the window), raster a portion of the window, starting with the top line and extending down to the next to the last line, down one position, then replace the top line with the new current top. Simple (well, almost) and elegant.
This function works almost exactly like do__upline(), except it moves the window down one line instead of up.
If the user chooses to use the vertical slider, do__vslide() will accommodate him. Handling the sliders is much more complicated than handling the arrows or slider tracks because the user can place the slider anywhere within the track. We need to calculate what portion of the data to display based on the slider's new position. (See the C-manship in the May '88 ST-LOG.)
The fourth element of the message buffer contains the new position of the slider. We use this value first to see if the slider has actually moved to a new position. If the slider has been moved, we calculate its position within our "document" (in MicroCheck's case, the list of checks), set cur__top to the appropriate check index (the one that'll now appear at the top of the window) and redraw the window, placing the slider in its new location.
The horizontal slider works in much the same fashion as its vertical counterpart. The main difference is that in MicroCheck we allow this slider to have only two positions: full right or full left. This makes the job much easier, since we don't have to do a lot of fancy calculations. Instead, we use the flag left to keep track of the horizontal slider's current position. When the user moves the slider, all we have to do is toggle our flag and change the position of the slider, redrawing the window as we do.
The only complication is that, since we are going to be displaying a different portion of the data, we need to change the labels in the window information line. This is easy to do with a quick call to wind__set().
When the user is ready to leave the program and return to the desktop, he'll select the Quit selection of the File menu (at least, he will if we wants a safe exit). When he does, do__quit() will ask if he's sure he wants to quit. If he is, his data will be saved and the flag all__done will be set to true. This flag will then break us out of the get__event() loop and return the program to the end of do__mcheck(), where all our windows and GEM resources will be deleted from memory.
Putting it in Order
Over the last few months, we've put together a large chunk of source code. Now you might want to rearrange some of the functions so that the higher-level functions are at the top of the program, and the lower-level functions are at the bottom. This "top down" organization will make the program easier to read and trace.
As for me, I'll see you next time.
Clayton Walnum is the executive editor of ST-LOG and ANALOG Computing, as well as the associate editor of VideoGames & Computer Entertainment.