C-manship: A Complete GEM Application, Part 2
By Clayton Walnum
This month, as promised, we'll take a look at the second part of the source code for MicroCheck ST. But before we get into that, those of you who don't receive the disk version of ST-LOG should turn your attention to Listing 1. In order to run Micro-Check ST, you need to have, in the same directory as the main program, the file MICROCHK.RSC. As you should know, this file contains the data GEM needs to set up the menu bar and the various dialog boxes the program will access. Listing 1 is an ST BASIC program that will create the MICROCHK.RSC file for you. Type it in, then check your typing using ST Check, found elsewhere in this issue. Once you're sure the program has been typed correctly, load it into ST BASIC and run it. MicroCheck's resource file will be written to the disk in Drive A.
Back to the task at hand
In the last installment of C-manship, we looked at the portion of the MicroCheck ST source code that declared all of the global variables used in the program and initialized both GEM and the program itself. Essentially, you could say that the first part of the source code did all the startup housekeeping. With all the housekeeping (or most of it) out of the way, we are ready to explore the program itself.
Recall that a GEM application is controlled by "events." Whenever the user attempts any form of input, whether it be accessing the menu bar, typing a key, clicking the mouse or fiddling with one of a window's controls, GEM sends our application a message that describes what the user wants to do. It's up to us as programmers to decide how to handle and interpret these messages. We can even ignore them if we want to.
The source code we looked at last time included a function called get__event(), which used a call to the function evnt__multi() to retrieve events from GEM and route them to the appropriate parts of the program. In MicroCheck ST we are interested in only three types of events: keyboard events, mouse-button events or GEM messages. These three events are handled in MicroCheck ST by the functions handle__keys(), handle__button() and handle__messages(), respectively.
Last time, we represented these functions in our source code as "stubs," do-nothing functions that simply fulfill the linker's need for a label. This allowed us to compile and link a small portion of the program. Without the stubs, the linker would have complained about the missing functions and not allowed us to get a running program. This month we'll be replacing the handle__messages() stub with the real function, adding to MicroCheck () the ability to handle GEM message events.
At this point you should load the portion of MicroCheck ST we looked at last time and delete the handle__messages() stub You should then add the code shown in Listing 2. Once Listing 2 has been merged with the code from last time, you will be able to compile and link it. When you run the resulting program, rather than ending up with a mostly empty screen as we did in the previous program segment, the addition of the handle_messages() function and some of its subordinate functions allows the window to be filled in and the buttons at the bottom of the screen to be drawn. We'll see why soon.
Unfortunately, we still haven't added a quit function to the program, so once you have run the program, the only way to get back to the desktop is to turn off your computer.
As we've discussed before (see the June '87 C-manship), when GEM sends a message, it stores it in an array that we must provide. In MicroCheck ST, this array is named msg_buf. The array element msg_buf will contain the type of message we've received.
Now take a look at the top of Listing 2, where you'll find the function handle_messages(). This function simply takes the message type that was stored in msg_buf and uses it in a switch statement to route the message to the appropriate part of the program. The switch is followed by seven case statements that handle menu messages, redraw messages, window-fulled messages, arrow messages, vertical-slider messages, horizontal-slider messages and window-closed messages, respectively.
This month we will be dealing only with redraw messages and window-fulled messages. The functions do_redraw() and do__full() handle those. The other functions needed for handle_messages()—do_menu(), do_arrow(), do__vslide(), do_hslide() and do_wind_close()—are represented in this month's code segment by stubs.
Notice that in the call to do_redraw(), we are passing the address of msg_bng cast into a pointer to GRECT (a structure that is defined in the header file OB-DEFS.H and that contains the coordinates for a GEM rectangle). The array elements msg_buf through msg__buf contain the coordinates for the work area of the window that needs to be redrawn. (See the September '87 C-manship.)
This function steps through the rectangle list, redrawing all portions of the windows that need to be refreshed. This includes the "invisible" window I mentioned in the last C-manship. The information buttons on the bottom of the screen are drawn as a result of a redraw message for this window.
The function do_redraw(), after locking the windows (to prevent corruption by such occurrences as a menu bar dropping down), steps through the rectangle list for each window, calculating the rectangles that need to be redrawn. (See the September '87 and April '88 C-manships.) The redraw itself is handled by the function draw_interior(), which we'll get to in a moment. After the rectangle list is empty, we unlock the window and control returns back to get_event(), where we wait for another event.
This function handles the actual redraw operations. The structure clip, passed to this function from do_redraw(), contains the coordinates, of the rectangle to be redrawn. First we turn off the mouse and set the drawing mode to "replace." Then, if the redraw message is for window #1, the invisible window, we calculate the coordinates, based on the current resolution, of the portion of the window to be redrawn, and redraw both that rectangle and the boxes on the bottom of the screen. (The only rectangle that'll ever be redrawn in the invisible window is the section of the screen below the visible window, the part of the screen that contains the buttons. If the visible window, the one that displays the check data, is fully opened, we'll never get a redraw message for the invisible window since it is completely covered.)
If the redraw message isn't for window #1, it has to be for window #2, the visible window. First we check the status of the flag full_draw. If its value is FALSE, we are not going to be updating the entire window, so we set a clipping rectangle to the portion of the window's work area we are going to redraw. A call to draw_rec() redraws the window rectangle.
The check data for MicroCheck ST is stored in an array of structures called checks. The first check is in element 0 of the array, the second in element 1, and so on. The position within the array at which a check is located is its index.
We find out how many lines of check data will fit in the window (lines_avail) based on the flag full, which indicates whether the window has been fully opened. Next we find out how many lines we need to display (lines_shown) by subtracting from the total number of checks(cur_count) the index of the check data shown at the top of the window (cur_top). (This doesn't have to be the first record in the checksf] array; if the user has used the vertical scroll bar or down arrow, the index for the check data at the top of the window could be almost anything, up to the maximum minus the number of checks that'll fit in the window.)
If lines_avail is larger than lines_shown, we have more space in the window than we have checks to fill it (when starting with cur_top), so we calculate a new cur_top in an attempt to show as many checks as possible without any blank space at the bottom. If cur_top ends up less than 0, we don't have enough check data in the array to fill the window. In this case, we just set cur__top to the first check in the array.
We then set the vertical and horizontal sliders, call updte__chk__wind() to print the check data that should appear in the window, turn off the full__draw flag, turn off clipping (otherwise we won't be able to print anything to the screen unless its coordinates are within the clipping rectangle) and turn the mouse back on.
This function does nothing more than draw a filled rectangle at the coordinates passed in the structure rec. The interior style, fill style and color of the rectangle are also passed to the function, although in most cases when you're redrawing a window, you'll want a solid white rectangle. Passing the extra information makes the function a little more flexible. Notice also that, as usual, whenever we draw anything on the screen, we shut off the mouse. This is important if you want to keep your screen clean. (See the January '87 C-manship.)
Here we set the clipping rectangle, the portion of the screen in which we want to restrict our drawing, to the coordinates passed in the structure rec. The integer flag is a Boolean variable that tells the function whether we're turning the clipping on or off. (TRUE turns it on.)
This function simply prints the necessary check data to the newly updated window (with a call to prnt__chk__wnd()). We get the coordinates of the window's work area and use those values to calculate, based on the character size and the check data index, the position within the window in which to print the data. We need to use the character size because high-resolution characters are a different height (16 bits) from medium-resolution characters (eight bits). Remember that the integer cur__top holds the index for the check data that should be displayed at the top of the window.
It's here that we actually print the check data to the screen. The index into the check array and the row at which the data should be printed are passed to this function from updte__chk__wind(). This function uses basic C text handling, so you should be able to understand it easily. Of special note, however, is the structure cur__chk__strc and the use of the flag left.
Because MicroCheck uses two different structures for holding check data—one for the regular monthly file and one for data obtained with the search function—we use the pointer cur__chk__strc to pointto the currently active structure. This simplifies handling the two structures, there-by eliminating our need to know whether the program is in the edit or search mode.
The flag left indicates the position of the window's horizontal slider. If the slider is all the way to the left (left is TRUE), we display all the check data except the date, which is in the right-hand unseen portion of the window. If the slider is to the right, we display the date, but not the check's cancel indicator or number, which are now in the left-hand unseen portion of the window.
Because the date for a check is stored in the format mmddyy, it must be converted to the more typical mm/dd/yy format before it is printed in the check window. The function format__date() takes care of this chore for us. The pointer d1 is the address of a character array where the final formatted date will be stored, and the pointer d2 is the address of the date in its unformatted form.
Across the bottom of the MicroCheck screen there is a series of six boxes that contain various information about the currently opened account. The function draw__buttons(), along with a lot of help from its subordinate functions, prints the information in the boxes.
Most of the data to be displayed in the boxes is in integer or long-integer format, so we must first, using sprintf(), convert it to strings. Note that the strings to be displayed in the boxes are stored in character arrays whose names end with __but.
The raw data to be converted is stored in four global variables: The long integer balance is the account's balance, and the integers num__trans, num__chks and num__deps are the total number of transactions, the number of checks and the number of deposits in the currently opened month, respectively.
This function does the actual drawing of the boxes. The pointer strl is the address of the box's label, the pointer str2 is the address of the information to print, and x1 is the X coordinate of the box being updated. The function first calculates the position in which the boxes should be drawn. It then, with a little help from center__butstring(), draws the boxes. (If the VDI functions used in this piece of code are confusing, you should review the C-manship in Issue 9 of ST-LOG, which was bound into the December '86 issue of ANALOG Computing.)
Function button ()
This function centers and prints the text in the boxes. A pointer to the text to be printed, along with the X and Y coordinates for the box, is passed to the function from button().
The check-display window in MicroCheck can be opened to only two sizes. The smaller size holds 16 checks and allows the user to view the information boxes on the bottom of the screen. The full-size window can hold up to 20 checks but covers the information boxes.
We keep track of the window's current size with the flag full. Whenever we get a window-fulled message from GEM (this happens when the user clicks on the full box in the upper right-hand corner of the window), we check the full flag and set the window's size accordingly. We are actually concerned only with the window's height, and because a high-resolution screen is twice the vertical resolution of a medium-resolution screen, we use the integer variable res, which holds the current screen resolution, to calculate the window's height.
Time for another break
Next time, yet more of MicroCheck ST. (1 bet you can hardly wait.)
Clayton Walnum is the executive editor of ST-LOG and ANALOG Gomputing, as well as the associate editor of Videogames & Computer Entertainment.