exodus@uop.UUCP (Freddy Kreuger) (10/17/87)
*PROFESSIONAL GEM* By Tim Oren Column #13 - A New Form Manager This is the 13th installment of ST PRO GEM, and the first devoted to explaining a large piece of code. This article is also the second in a series of three concerning GEM user interface techniques. The code is an alternate form (dialog) manager for GEM. It is stored as GMCL13.C in DL3 of PCS-58. You should go and download it now, or you will have no hope of following this discussion. What is unique about this version of the form manager? First, it implements all of the functions of the standard GEM form_do routine, as well as adding a "hot spots" feature which causes selectable objects to become mouse-sensitive, just like the entries in menu dropdowns. The second (and obvious) difference is that this form manager is provided in source code form. This gives you the freedom to examine it and change it to suit your own needs. I have several purposes in presenting this code. It is intended as an example of GEM program structure, and an application of some of the techniques presented in earlier columns. It is also relevant to the continuing thread discussing the necessity of feedback when constructing a user interface. Also, this issue represents the beginning of a fundamental change in direction for ST PRO GEM. Since this column began last August, Atari ST developers have increased not only in number, but in sophistication. A number of books, as well as back issues of ST PRO GEM, are now available to explain the basics of the ST and GEM. Quick answers to common questions are available here on Compuserve's PCS-57 from Atari itself, or from helpful members of the developer community. To reflect these changes, future columns will discuss more advanced topics in greater depth, with an accent on code which can be adapted by developers. The program presented in this issue will be a basis for a number of these discussions. There will be fewer "encyclopediac" treatments of AES and VDI function calls. Finally, to give me the time required to create this code or clean up tools from my "bag of tricks", ST PRO GEM will probably convert to a monthly format around the start of summer. ON WITH THE SHOW. Taking your listing in hand, you will quickly notice two things. First, this program uses the infamous portability macros, so that it may be used with Intel versions of GEM. Second, the routines are arranged "bottom up", with the main at the end, and subroutines going toward the beginning. (This is a carry-over from my days with ALGOL and PASCAL.) You should now turn to the form_do entry point near the end of the code. One change has been made in the standard calling sequence for form_do. The starting edit field is now a pointer to a value, rather than the value itself. The new form_do overwrites the initial value with the number of the object being edited when the dialog terminated. Using this information, your program can restore the situation when the dialog is next called. As before, if there is NO editable field, the initial value should be zero. There are several local variables which maintain vital state information during the dialog interaction. Edit_obj is the number of the editable object currently receiving keystrokes. Next_obj is set when the mouse is clicked over an object. If the object happens to be editable, next_obj becomes the new edit_obj. Three variables are associated with the "hot-spot" feature. If hot_mode is set to M1_ENTER, then the mouse is outside the area of the dialog. If it equals M1_EXIT, then the mouse is currently in the dialog. If it is in the dialog, hot_obj indicates whether there is an active "hot" object. If its value is NIL (-1), then there is no active object. Otherwise, it is equal to the number of the object which is currently "hot", that is, inverted on the screen. Finally, hot_rect is the current wait rectangle. If the mouse is outside of the window, then the wait rectangle equals the dialog's ROOT. If there is a current hot object, then hot_rect equals that object's screen rectangle. If the mouse is in the dialog, but not within a hot object, then the wait rectangle defines the area within which no further collision checks are necessary. This is arrived at through an algorithm explained below. Form_do's initialization code sets up the hot-spot variables to trigger if the mouse is within the dialog. It also sets starting values for edit_obj and next_obj which will cause the edit startup code to be activated. The main portion of form_do is a loop, exhibiting the type of event driven structure discussed in the last column. Before entering the evnt_multi wait, the status of next_obj and edit_obj are checked to see if a new object should be initialized for editing. If so, objc_edit is called with the EDINIT function code. NOTE: the objc_edit calling sequence used in this program differs from the one given in the AES manual, which is incorrect! You should check the bindings you are using to be sure they will work with this code, and modify as necessary. The evnt_multi is set up to wait for a mouse click (single or double), for a keyboard input, or for the mouse to make a "significant" movement, as discussed above. Notice that since form_do is used as a subroutine, it does not handle messages which are normally processed by the main loop of your application. Notice that this creates a mode, and that this routine as written handles modal dialogs. You could, however, use this code as the basis for a non-modal dialog handler by drawing the dialog within a window, and combining the main loop of form_do with the main loop of your application. (This possibility may be examined in future columns. In the meantime, it is left as an exercise for the reader.) The event bit vector is returned to the variable "which". Since events are not mutually exclusive, each possible event type must be examined in turn before returning to the evnt_multi call. The form manager's event handling routines are form_hot, for mouse rectangle event, form_keybd, for character input, and form_button, for mouse clicks. Form_keybd and form_button are allowed to terminate the dialog by returning a value of false to the loop control variable "cont". If termination is imminent, or the user has clicked on a new editable object, objc_edit is called with EDEND to remove the cursor from the old object. The normal flow of control then returns to edit setup and evnt_multi. A few cleanup actions are performed upon termination. If the terminating object (stored in next_obj) is not the same as the hot_obj, then a race condition has occured and the hot object must be cleared with objc_toggle before exiting. After this test, the final edit_obj value is passed back via the parameter, and the terminating object is returned as the function value. RELAXEN UND WATCHEN DAS BLINKENLICHTEN. Form_hot is responsible for maintaining on-screen hot-spots, and correctly updating the internal hot-spot variables. It is about halfway through the listing. The first action in form_hot is to determine if the mouse has just exited an object which is "hot. In this case, objc_toggle is called to unhighlight the object and reset the SELECTED flag. The current mouse position is passed to form_hot by form_do. It is checked against the root rectangle of the dialog to see if the mouse is inside the dialog. If not, the program must wait for it to re-enter, so form_hot sets the rectangle and waiting mode accordingly, and returns NIL as the new hot_obj. When the mouse is within the dialog, a regular objc_find call determines the object at which it is pointing. For an object to be mouse-sensitive, it must be SELECTABLE and not DISABLED. If the found object meets these tests, the mouse will "hover" over the object, waiting to leave its screen rectangle. Since the object might already be SELECTED (and hence drawn reversed), this is checked before objc_toggle is called to do the highlighting and selection of the object, which becomes the hot_obj. (If the object was already SELECTED, the hot_obj becomes NIL.) The toughest condition is when the mouse is within the dialog, but not over a mouse-sensitive object. The regular GEM event structure will not work, because it can only wait on two rectangles, and there may be many more selectable objects in a dialog tree. I have found a way around this limitation using a combination of the map_tree utility (introduced in ST PRO GEM #5) with the principle of visual hierarchy in object trees. In summary, the algorithm attempts to find the largest bounding rectangle around the current mouse position, within which there are no mouse-sensitive objects. It starts with a rectangle equal to the dialog root, and successively "breaks" it with the rectangle of each mouse-sensitive object. The next few paragraphs examine this method in detail. Since C lacks the dynamic scoping of LISP, from which map_tree was derived, it is necessary to set up some globals to be used during the rectangle break process. Br_rect is the GRECT of the current bounding rectangle. Br_mx and br_my hold the current mouse position. Br_togl is a switch which determines whether the next break will be attempted horizontally or vertically. After initializing these variables, form_hot uses map_tree to invoke the break_obj routine for every object in the dialog. Break_obj first intersects the rectangle of the object with the current bounding rectangle. If they are disjoint, then neither the object nor any of its offspring can possible affect the operation, so FALSE is returned, causing map_tree to ignore the subtree. The object is next checked to see if it is mouse-sensitive. As before, it must be SELECTABLE and not DISABLED, and it must not be hidden (this was checked automatically by objc_find before). If these conditions are met, then the object intrudes into the current bounding rectangle. To maintain the desired condition, part of the rectangle must be removed or "broken away". In many cases, the break operation can be done either horizontally or vertically. Since we have no prior information as to which way the mouse will move next, break_obj uses the br_togl flag to alternate which direction it will try first. This should yield the most nearly square rectangle. The break_x and break_y routines are very similar. In each case, the segment occupied by the breaking object is compared to the point occupied by the mouse. If the point is within the segment, there is no possible break in this dimension, and FALSE is returned. If the point lies outside the segment, then the rectangle may be successfully broken by reducing this dimension. This is done, and TRUE is returned to report success. The break_y routine also employs a look-ahead test to prevent a possible infinite loop. It is conceivable, though not likely, that someone might nest a non-SELECTABLE object completely within another SELECTABLE object(s). If the mouse point is within such an object, the algorithm will not be able to select a break dimension. In the current version, the mouse rectangle is simply forced to a single pixel for this case. (Note that is is NOT sufficent to simply wait on the non-selectable object's rectangle, since other SELECTABLE objects may overlap it and follow it in tree order.) Since map_tree examines all possible objects, br_rect will be the correct bounding rectangle at completion. Note that you can readily adapt this technique to other uses, such as hot-spotting while dragging objects. It is much less demanding of CPU resources than other methods, such as repetitive objc_finds. WHAT A CHARACTER! The form_keybd routine acts as a filter on character input. When it recognizes a control character, it processes it and zeroes the keyboard word. Other chararacters are passed on to objc_edit to be inserted in edit_obj. If there is no editing object, the character goes to the bit bucket. The form_keybd given implements the standard GEM functionality with two minor additions. First, a carriage return in a dialog with no DEFAULT exit object is taken as a tab. This allows <CR> to be used "naturally" in dialogs with several lines of text input. Second, tabs and backtabs "wrap around" from top to bottom of the dialog, and are done by "walking the tree", rather than relying on the LASTOB flag to signal the end of the dialog. This allows the new form manager to handle dialog trees which are not contiguous in memory. The code sets up several global variables for use by mapped functions. Fn_obj is the output from both find_tab and find_def. Fn_dir is an input to find_tab. Fn_last, fn_prev, and fn_last are used while searching for tab characters. A carriage return results in a search of the entire tree, using map_tree and find_def, for an object with its DEFAULT flag set. Its SELECTED flag is set and it is inverted on the screen to indicate the action taken. Form_keybd returns a FALSE to force termination of the main form_do loop. If no DEFAULT is found, control passes to the tab code. The tabbing procedure is somewhat complicated because the same code is used for forward and backward tabbing. The old value of edit_obj (the object being tabbed FROM) is placed into fn_last. Fn_dir is set to one for a forward tab, and zero for a backward tab. The general strategy is to scan the entire tree for EDITABLE objects, always saving the last one found in fn_prev. When tabbing forward fn_last is checked against fn_prev. A match indicates that the current object is the one desired. When tabbing backward the current object is checked against fn_last. If they match, fn_prev is the desired object. This procedure requires two passes when the tab "wraps around" the tree, that is, when the desired object as at the opposite end of the traverse from the old editing object. The result of the tab operation is written back into form_do's next_obj parameter, and becomes the new editing object at the beginning of the next loop. BUTTON DOWN. The form_button procedure is lengthy because it must recognize and handle mouse clicks on several types of objects: EDITABLE, SELECTABLE, and TOUCHEXIT. The first section of code rejects other objects, which cannot accept a click. The next piece of form_button makes a special check for a double click on a TOUCHEXIT object. This will cause the high bit of the returned object number to be set. (By the way, this also occurs in the standard form_do.) This flag allows user dialog code to perform special processing on the object. The largest piece of form_button handles the various cases in which the SELECTABLE flag may be set. Setting the RBUTTON (radio button) flag causes all of the object's siblings in the tree to be deselected at the time the object is clicked. The do_radio routine uses the get_parent utility to find the ancestor, and then performs the deselect/select operation. If the SELECTABLE object is not TOUCHEXIT, then graf_watchbox is used to make sure that the mouse button comes back up while it is within the object. Otherwise, the click is cancelled. Care is necessary here, since the hot-spot code may have already set the SELECTED flag for the object. (We cannot be sure of this, for a race condition may have occurred!) If the SELECTABLE object is TOUCHEXIT, then the application has requested that form_do exit without waiting for the button to go back up. In both this and regular form_do, TOUCHEXIT objects are used when you want to provide immediate response (animation) within the context of a dialog. The final parts of form_button do cleanup. If the clicked object was already hot-spotted, hot_obj must be reset to NIL, otherwise form_do will carefully unselect the object which has just been selected! If the EXIT or TOUCHEXIT flags are in force, form_button returns FALSE to force the completion of form_do. For EDITABLE objects, next_obj is left intact to replace edit_obj during the next loop. Otherwise, next_obj has done its job and is zeroed, and form_button returns TRUE for continuation. This concludes the tour of the alternate form_do. The best cure for any confusion in this explanation is to compile the code into an application and watch how it runs with different resources, or attack it with a debugger. OPERATORS ARE STANDING BY. I encourage you to modify this code to meet your particular needs and incorporate it into your application. I would like to request than anyone who comes up with significant improvements (or bug fixes) send them to me so they can be made generally available. You can do this via the ANTIC ONLINE Feedback, or by sending E-mail to 76703,202. Speaking of Feedback, I would also like comments on the proposed change of direction for the column, and more suggestions for future topics. The next installment will be a further discussion of interface design. Topics now queued for future articles include the file selector and DOS error handling, a new object editor, and customized drag box and rubber box routines. Discussions on VDI workstations and printer output are on hold pending release of the GDOS by Atari. If there are items which you want to appear here, you must let me know!