home *** CD-ROM | disk | FTP | other *** search
/ Crawly Crypt Collection 1 / crawlyvol1.bin / program / books / progem / forms.13 < prev    next >
Text File  |  1986-11-01  |  43KB  |  890 lines

  1.       Permission to reprint or excerpt is granted only if the following
  2.       line appears at the top of the article:
  3.  
  4.        ANTIC PUBLISHING INC., COPYRIGHT 1986.  REPRINTED BY PERMISSION.
  5.  
  6.  
  7.  
  8.       PROFESSIONAL GEM  by Tim Oren
  9.       Column #13 - A New Form Manager
  10.  
  11.  
  12.            This  is  the 13th installment of ST PRO GEM,  and the  first
  13.       devoted to explaining a large piece of code.  This article is also
  14.       the  second  in a series of three concerning  GEM  user  interface
  15.       techniques.   The  code is an alternate form (dialog) manager  for
  16.       GEM.   It  is stored as GMCL13.C in DL3 of PCS-58.   You should go
  17.       and  download it now,  or you will have no hope of following  this
  18.       discussion.
  19.  
  20.            What  is  unique  about this version  of  the  form  manager?
  21.       First,  it  implements  all of the functions of the  standard  GEM
  22.       form_do  routine,  as  well as adding a "hot spots" feature  which
  23.       causes  selectable  objects to become mouse-sensitive,  just  like
  24.       the   entries  in  menu  dropdowns.    The  second  (and  obvious)
  25.       difference  is that this form manager is provided in  source  code
  26.       form.   This  gives you the freedom to examine it and change it to
  27.       suit your own needs.
  28.  
  29.            I  have  several  purposes in presenting this  code.   It  is
  30.       intended   as  an  example  of  GEM  program  structure,   and  an
  31.       application  of  some  of  the  techniques  presented  in  earlier
  32.       columns.   It is also relevant to the continuing thread discussing
  33.       the necessity of feedback when constructing a user interface.
  34.  
  35.            Also,  this  issue represents the beginning of a  fundamental
  36.       change in direction for ST PRO GEM.   Since this column began last
  37.       August, Atari ST developers have increased not only in number, but
  38.       in sophistication.   A number of books,  as well as back issues of
  39.       ST PRO GEM,  are now available to explain the basics of the ST and
  40.       GEM.   Quick  answers  to common questions are available  here  on
  41.       Compuserve's PCS-57 from Atari itself,  or from helpful members of
  42.       the developer community.
  43.  
  44.            To  reflect these changes,  future columns will discuss  more
  45.       advanced topics in greater depth, with an accent on code which can
  46.       be  adapted  by developers.   The program presented in this  issue
  47.       will be a basis for a number of these discussions.   There will be
  48.       fewer  "encyclopediac" treatments of AES and VDI  function  calls.
  49.       Finally, to give me the time required to create this code or clean
  50.       up tools from my "bag of tricks", ST PRO GEM will probably convert
  51.       to a monthly format around the start of summer.
  52.  
  53.            ON  WITH  THE SHOW.   Taking your listing in hand,  you  will
  54.       quickly notice two things.   First, this program uses the infamous
  55.       portability macros,  so that it may be used with Intel versions of
  56.       GEM.  Second, the routines are arranged "bottom up", with the main
  57.       at the end,  and subroutines going toward the beginning.  (This is
  58.       a carry-over from my days with ALGOL and PASCAL.)  You should  now
  59.       turn to the form_do entry point near the end of the code.
  60.  
  61.            One change has been made in the standard calling sequence for
  62.       form_do.   The  starting  edit field is now a pointer to a  value,
  63.       rather  than  the value itself.   The new form_do  overwrites  the
  64.       initial value with the number of the object being edited when  the
  65.       dialog  terminated.   Using  this  information,  your program  can
  66.       restore the situation when the dialog is next called.   As before,
  67.       if there is NO editable field, the initial value should be zero.
  68.  
  69.            There are several local variables which maintain vital  state
  70.       information during the dialog interaction.  Edit_obj is the number
  71.       of  the editable object currently receiving keystrokes.   Next_obj
  72.       is  set when the mouse is clicked over an object.   If the  object
  73.       happens to be editable,  next_obj becomes the new edit_obj.
  74.  
  75.            Three  variables are associated with the "hot-spot"  feature.
  76.       If hot_mode is set to M1_ENTER, then the mouse is outside the area
  77.       of the dialog.   If it equals M1_EXIT, then the mouse is currently
  78.       in the dialog.   If it is in the dialog, hot_obj indicates whether
  79.       there is an active "hot" object.   If its value is NIL (-1),  then
  80.       there is no active object.   Otherwise,  it is equal to the number
  81.       of the object which is currently "hot",  that is,  inverted on the
  82.       screen.   Finally, hot_rect is the current wait rectangle.  If the
  83.       mouse is outside of the window, then the wait rectangle equals the
  84.       dialog's  ROOT.   If there is a current hot object,  then hot_rect
  85.       equals  that  object's screen rectangle.   If the mouse is in  the
  86.       dialog,  but  not  within  a hot object,  then the wait  rectangle
  87.       defines  the  area within which no further  collision  checks  are
  88.       necessary.   This  is  arrived at through an  algorithm  explained
  89.       below.
  90.  
  91.            Form_do's initialization code sets up the hot-spot  variables
  92.       to  trigger  if  the mouse is within the  dialog.   It  also  sets
  93.       starting  values  for edit_obj and next_obj which will  cause  the
  94.       edit startup code to be activated.
  95.  
  96.            The main portion of form_do is a loop, exhibiting the type of
  97.       event  driven  structure  discussed in the  last  column.   Before
  98.       entering the evnt_multi wait,  the status of next_obj and edit_obj
  99.       are  checked  to  see if a new object should  be  initialized  for
  100.       editing.   If  so,  objc_edit  is called with the EDINIT  function
  101.       code.   NOTE:  the objc_edit calling sequence used in this program
  102.       differs from the one given in the AES manual,  which is incorrect!
  103.       You  should check the bindings you are using to be sure they  will
  104.       work with this code, and modify as necessary.
  105.  
  106.            The evnt_multi is set up to wait for a mouse click (single or
  107.       double),  for  a  keyboard  input,  or  for the mouse  to  make  a
  108.       "significant"  movement,  as  discussed above.   Notice that since
  109.       form_do is used as a subroutine, it does not handle messages which
  110.       are  normally  processed  by the main loop  of  your  application.
  111.       Notice that this creates a mode,  and that this routine as written
  112.       handles modal dialogs.   You could,  however, use this code as the
  113.       basis for a non-modal dialog handler by drawing the dialog  within
  114.       a  window,  and  combining the main loop of form_do with the  main
  115.       loop  of your application.   (This possibility may be examined  in
  116.       future  columns.   In the meantime,  it is left as an exercise for
  117.       the reader.)
  118.  
  119.            The  event  bit vector is returned to the  variable  "which".
  120.       Since events are not mutually exclusive,  each possible event type
  121.       must be examined in turn before returning to the evnt_multi  call.
  122.       The form manager's event handling routines are form_hot, for mouse
  123.       rectangle event, form_keybd, for character input, and form_button,
  124.       for  mouse  clicks.   Form_keybd  and form_button are  allowed  to
  125.       terminate  the  dialog by returning a value of false to  the  loop
  126.       control variable "cont".   If termination is imminent, or the user
  127.       has  clicked  on a new editable object,  objc_edit is called  with
  128.       EDEND  to remove the cursor from the old object.   The normal flow
  129.       of control then returns to edit setup and evnt_multi.
  130.  
  131.            A few cleanup actions are performed upon termination.  If the
  132.       terminating  object  (stored in next_obj) is not the same  as  the
  133.       hot_obj, then a race condition has occured and the hot object must
  134.       be cleared with objc_toggle before exiting.   After this test, the
  135.       final  edit_obj  value is passed back via the parameter,  and  the
  136.       terminating object is returned as the function value.
  137.  
  138.            RELAXEN   UND  WATCHEN  DAS  BLINKENLICHTEN.    Form_hot   is
  139.       responsible  for  maintaining on-screen hot-spots,  and  correctly
  140.       updating  the  internal hot-spot variables.   It is about  halfway
  141.       through the listing.
  142.  
  143.            The first action in form_hot is to determine if the mouse has
  144.       just exited an object which is "hot". In this case, objc_toggle is
  145.       called to unhighlight the object and reset the SELECTED flag.
  146.  
  147.            The current mouse position is passed to form_hot by  form_do.
  148.       It  is checked against the root rectangle of the dialog to see  if
  149.       the mouse is inside the dialog.  If not, the program must wait for
  150.       it  to re-enter,  so form_hot sets the rectangle and waiting  mode
  151.       accordingly, and returns NIL as the new hot_obj.
  152.  
  153.            When the mouse is within the dialog, a regular objc_find call
  154.       determines  the object at which it is pointing.   For an object to
  155.       be  mouse-sensitive,  it must be SELECTABLE and not DISABLED.   If
  156.       the  found object meets these tests,  the mouse will "hover"  over
  157.       the  object,  waiting  to leave its screen rectangle.   Since  the
  158.       object might already be SELECTED (and hence drawn reversed),  this
  159.       is checked before objc_toggle is called to do the highlighting and
  160.       selection  of  the  object,  which becomes the hot_obj.   (If  the
  161.       object was already SELECTED, the hot_obj becomes NIL.)
  162.  
  163.            The  toughest  condition  is when the  mouse  is  within  the
  164.       dialog,  but  not over a mouse-sensitive object.   The regular GEM
  165.       event  structure  will not work,  because it can only wait on  two
  166.       rectangles,  and  there  may be many more selectable objects in  a
  167.       dialog  tree.   I have found a way around this limitation using  a
  168.       combination of the map_tree utility (introduced in ST PRO GEM  #5)
  169.       with the principle of visual hierarchy in object trees.
  170.  
  171.            In  summary,  the  algorithm  attempts to  find  the  largest
  172.       bounding rectangle around the current mouse position, within which
  173.       there are no mouse-sensitive objects.   It starts with a rectangle
  174.       equal  to the dialog root,  and successively "breaks" it with  the
  175.       rectangle of each mouse-sensitive object.  The next few paragraphs
  176.       examine this method in detail.
  177.  
  178.            Since  C  lacks  the  dynamic scoping  of  LISP,  from  which
  179.       map_tree was derived, it is necessary to set up some globals to be
  180.       used during the rectangle break process.   Br_rect is the GRECT of
  181.       the current bounding rectangle.   Br_mx and br_my hold the current
  182.       mouse position.   Br_togl is a switch which determines whether the
  183.       next  break will be attempted horizontally or  vertically.   After
  184.       initializing these variables, form_hot uses map_tree to invoke the
  185.       break_obj routine for every object in the dialog.
  186.  
  187.            Break_obj  first intersects the rectangle of the object  with
  188.       the  current  bounding  rectangle.   If they  are  disjoint,  then
  189.       neither  the object nor any of its offspring can  possible  affect
  190.       the  operation,  so FALSE is returned,  causing map_tree to ignore
  191.       the subtree.
  192.  
  193.            The  object is next checked to see if it is  mouse-sensitive.
  194.       As before, it must be SELECTABLE and not DISABLED, and it must not
  195.       be  hidden (this was checked automatically by  objc_find  before).
  196.       If  these  conditions are met,  then the object intrudes into  the
  197.       current  bounding rectangle.   To maintain the desired  condition,
  198.       part of the rectangle must be removed or "broken away".
  199.  
  200.            In  many  cases,  the  break  operation can  be  done  either
  201.       horizontally or vertically.  Since we have no prior information as
  202.       to which way the mouse will move next,  break_obj uses the br_togl
  203.       flag to alternate which direction it will try first.   This should
  204.       yield the most nearly square rectangle.
  205.  
  206.            The  break_x and break_y routines are very similar.   In each
  207.       case,  the  segment occupied by the breaking object is compared to
  208.       the  point  occupied  by the mouse.   If the point is  within  the
  209.       segment,  there is no possible break in this dimension,  and FALSE
  210.       is  returned.   If  the point lies outside the segment,  then  the
  211.       rectangle  may be successfully broken by reducing this  dimension.
  212.       This is done, and TRUE is returned to report success.
  213.  
  214.            The break_y routine also employs a look-ahead test to prevent
  215.       a possible infinite loop.   It is conceivable,  though not likely,
  216.       that someone might nest a non-SELECTABLE object completely  within
  217.       another  SELECTABLE object(s).   If the mouse point is within such
  218.       an  object,  the  algorithm  will not be able to  select  a  break
  219.       dimension.   In the current version, the mouse rectangle is simply
  220.       forced  to  a single pixel for this case.   (Note that is  is  NOT
  221.       sufficent to simply wait on the non-selectable object's rectangle,
  222.       since  other  SELECTABLE objects may overlap it and follow  it  in
  223.       tree order.)
  224.  
  225.            Since map_tree examines all possible objects, br_rect will be
  226.       the  correct bounding rectangle at completion.   Note that you can
  227.       readily  adapt this technique to other uses,  such as hot-spotting
  228.       while  dragging  objects.   It  is  much  less  demanding  of  CPU
  229.       resources than other methods, such as repetitive objc_finds.
  230.  
  231.            WHAT A CHARACTER!  The form_keybd routine acts as a filter on
  232.       character  input.   When  it  recognizes a control  character,  it
  233.       processes it and zeroes the keyboard word.  Other chararacters are
  234.       passed on to objc_edit to be inserted in edit_obj.  If there is no
  235.       editing object, the character goes to the bit bucket.
  236.  
  237.            The   form_keybd   given   implements   the   standard    GEM
  238.       functionality with two minor additions.   First, a carriage return
  239.       in  a dialog with no DEFAULT exit object is taken as a tab.   This
  240.       allows  <CR> to be used "naturally" in dialogs with several  lines
  241.       of text input.   Second,  tabs and backtabs "wrap around" from top
  242.       to  bottom  of  the dialog,  and are done by "walking  the  tree",
  243.       rather  than relying on the LASTOB flag to signal the end  of  the
  244.       dialog.   This  allows the new form manager to handle dialog trees
  245.       which are not contiguous in memory.
  246.  
  247.            The  code sets up several global variables for use by  mapped
  248.       functions.   Fn_obj is the output from both find_tab and find_def.
  249.       Fn_dir is an input to find_tab.  Fn_last, fn_prev, and fn_last are
  250.       used while searching for tab characters.
  251.  
  252.            A  carriage  return results in a search of the  entire  tree,
  253.       using  map_tree and find_def,  for an object with its DEFAULT flag
  254.       set.  Its SELECTED flag is set and it is inverted on the screen to
  255.       indicate  the action taken.   Form_keybd returns a FALSE to  force
  256.       termination  of  the main form_do loop.   If no DEFAULT is  found,
  257.       control passes to the tab code.
  258.  
  259.            The  tabbing  procedure is somewhat complicated  because  the
  260.       same code is used for forward and backward tabbing.  The old value
  261.       of edit_obj (the object being tabbed FROM) is placed into fn_last.
  262.       Fn_dir  is set to one for a forward tab,  and zero for a  backward
  263.       tab.
  264.  
  265.            The general strategy is to scan the entire tree for  EDITABLE
  266.       objects,  always  saving  the  last one found  in  fn_prev.   When
  267.       tabbing  forward  fn_last  is checked against  fn_prev.   A  match
  268.       indicates  that  the  current object is  the  one  desired.   When
  269.       tabbing  backward the current object is checked  against  fn_last.
  270.       If  they  match,  fn_prev is the desired object.   This  procedure
  271.       requires two passes when the tab "wraps around" the tree, that is,
  272.       when  the  desired object as at the opposite end of  the  traverse
  273.       from the old editing object.
  274.  
  275.            The  result  of  the  tab  operation  is  written  back  into
  276.       form_do's  next_obj parameter,  and becomes the new editing object
  277.       at the beginning of the next loop.
  278.  
  279.            BUTTON DOWN.  The form_button procedure is lengthy because it
  280.       must  recognize  and  handle  mouse clicks  on  several  types  of
  281.       objects:  EDITABLE,  SELECTABLE, and TOUCHEXIT.  The first section
  282.       of code rejects other objects, which cannot accept a click.
  283.  
  284.            The  next  piece of form_button makes a special check  for  a
  285.       double click on a TOUCHEXIT object.   This will cause the high bit
  286.       of the returned object number to be set.   (By the way,  this also
  287.       occurs  in  the standard form_do.)  This flag allows  user  dialog
  288.       code to perform special processing on the object.
  289.  
  290.            The largest piece of form_button handles the various cases in
  291.       which the SELECTABLE flag may be set.   Setting the RBUTTON (radio
  292.       button) flag causes all of the object's siblings in the tree to be
  293.       deselected  at  the  time the object  is  clicked.   The  do_radio
  294.       routine uses the get_parent utility to find the ancestor, and then
  295.       performs the deselect/select operation.
  296.  
  297.            If the SELECTABLE object is not TOUCHEXIT, then graf_watchbox
  298.       is used to make sure that the mouse button comes back up while  it
  299.       is within the object.  Otherwise, the click is cancelled.  Care is
  300.       necessary  here,  since the hot-spot code may have already set the
  301.       SELECTED flag for the object.   (We cannot be sure of this,  for a
  302.       race condition may have occurred!)
  303.  
  304.            If  the SELECTABLE object is TOUCHEXIT,  then the application
  305.       has requested that form_do exit without waiting for the button  to
  306.       go back up.   In both this and regular form_do,  TOUCHEXIT objects
  307.       are  used when you want to provide immediate response  (animation)
  308.       within the context of a dialog.
  309.  
  310.            The  final parts of form_button do cleanup.   If the  clicked
  311.       object  was  already hot-spotted,  hot_obj must be reset  to  NIL,
  312.       otherwise  form_do  will carefully unselect the object  which  has
  313.       just been selected!
  314.  
  315.            If  the  EXIT or TOUCHEXIT flags are  in  force,  form_button
  316.       returns  FALSE to force the completion of form_do.   For  EDITABLE
  317.       objects,  next_obj  is left intact to replace edit_obj during  the
  318.       next  loop.   Otherwise,  next_obj has done its job and is zeroed,
  319.       and form_button returns TRUE for continuation.
  320.  
  321.            This  concludes the tour of the alternate form_do.   The best
  322.       cure for any confusion in this explanation is to compile the  code
  323.       into  an  application  and  watch  how  it  runs  with   different
  324.       resources,  or attack it with a debugger.
  325.  
  326.            OPERATORS  ARE STANDING BY.   I encourage you to modify  this
  327.       code  to meet your particular needs and incorporate it  into  your
  328.       application.   I  would  like to request than anyone who comes  up
  329.       with  significant improvements (or bug fixes) send them to  me  so
  330.       they  can  be made generally available.   You can do this via  the
  331.       ANTIC ONLINE Feedback, or by sending E-mail to 76703,202.
  332.  
  333.            Speaking  of  Feedback,  I  would also like comments  on  the
  334.       proposed change of direction for the column,  and more suggestions
  335.       for  future  topics.   The  next  installment will  be  a  further
  336.       discussion  of  interface design.   Topics now queued  for  future
  337.       articles  include the file selector and DOS error handling,  a new
  338.       object  editor,  and customized drag box and rubber box  routines.
  339.       Discussions  on  VDI workstations and printer output are  on  hold
  340.       pending  release of the GDOS by Atari.   If there are items  which
  341.       you want to appear here, you must let me know!
  342.  
  343.  
  344.  
  345. /*********************** Forms manager code *******************************/
  346.  
  347. #include "portab.h"                             /* portable coding conv */
  348. #include "machine.h"                            /* machine depndnt conv */
  349. #include "obdefs.h"                             /* object definitions   */
  350. #include "gembind.h"                            /* gem binding structs  */
  351. #include "taddr.h"
  352.  
  353. #define M1_ENTER        0x0000
  354. #define M1_EXIT         0x0001
  355.  
  356. #define BS      0x0008
  357. #define TAB     0x0009
  358. #define CR      0x000D
  359. #define ESC     0x001B
  360. #define BTAB    0x0f00
  361. #define UP      0x4800
  362. #define DOWN    0x5000
  363. #define DEL     0x5300
  364.                                         /* Global variables used by */
  365.                                         /* 'mapped' functions       */
  366. MLOCAL  GRECT   br_rect;                /* Current break rectangle  */
  367. MLOCAL  WORD    br_mx, br_my, br_togl;  /* Break mouse posn & flag  */ 
  368. MLOCAL  WORD    fn_obj;                 /* Found tabable object     */
  369. MLOCAL  WORD    fn_last;                /* Object tabbing from      */
  370. MLOCAL  WORD    fn_prev;                /* Last EDITABLE obj seen   */
  371. MLOCAL  WORD    fn_dir;                 /* 1 = TAB, 0 = BACKTAB     */
  372.  
  373.  
  374. /************* Utility routines for new forms manager ***************/
  375.  
  376.         VOID
  377. objc_toggle(tree, obj)                  /* Reverse the SELECT state */
  378.         LONG    tree;                   /* of an object, and redraw */
  379.         WORD    obj;                    /* it immediately.          */
  380.         {
  381.         WORD    state, newstate;
  382.         GRECT   root, ob_rect;
  383.  
  384.         objc_xywh(tree, ROOT, &root);
  385.         state = LWGET(OB_STATE(obj));
  386.         newstate = state ^ SELECTED;
  387.         objc_change(tree, obj, 0, root.g_x, root.g_y, 
  388.                 root.g_w, root.g_h, newstate, 1);
  389.         }
  390.  
  391.         VOID                            /* If the object is not already */
  392. objc_sel(tree, obj)                     /* SELECTED, make it so.        */
  393.         LONG    tree;
  394.         WORD    obj;
  395.         {
  396.         if ( !(LWGET(OB_STATE(obj)) & SELECTED) )
  397.                 objc_toggle(tree, obj);
  398.         }
  399.  
  400.         VOID                            /* If the object is SELECTED,   */
  401. objc_dsel(tree, obj)                    /* deselect it.                 */
  402.         LONG    tree;
  403.         WORD    obj;
  404.         {
  405.         if (LWGET(OB_STATE(obj)) & SELECTED)
  406.                 objc_toggle(tree, obj);
  407.         }
  408.  
  409.         VOID                            /* Return the object's GRECT    */
  410. objc_xywh(tree, obj, p)                 /* through 'p'                  */
  411.         LONG    tree;
  412.         WORD    obj;
  413.         GRECT   *p;
  414.         {
  415.         objc_offset(tree, obj, &p->g_x, &p->g_y);
  416.         p->g_w = LWGET(OB_WIDTH(obj));
  417.         p->g_h = LWGET(OB_HEIGHT(obj));
  418.         }
  419.  
  420.         VOID                            /* Non-cursive traverse of an   */
  421. map_tree(tree, this, last, routine)     /* object tree.  This routine   */
  422.         LONG            tree;           /* is described in PRO GEM #5.  */
  423.         WORD            this, last;
  424.         WORD            (*routine)();
  425.         {
  426.         WORD            tmp1;
  427.  
  428.         tmp1 = this;            /* Initialize to impossible value: */
  429.                                 /* TAIL won't point to self!       */
  430.                                 /* Look until final node, or off   */
  431.                                 /* the end of tree                 */ 
  432.         while (this != last && this != NIL)
  433.                                 /* Did we 'pop' into this node     */
  434.                                 /* for the second time?            */
  435.                 if (LWGET(OB_TAIL(this)) != tmp1)
  436.                         {
  437.                         tmp1 = this;    /* This is a new node       */
  438.                         this = NIL;
  439.                                         /* Apply operation, testing  */
  440.                                         /* for rejection of sub-tree */
  441.                         if ((*routine)(tree, tmp1))
  442.                                 this = LWGET(OB_HEAD(tmp1));
  443.                                         /* Subtree path not taken,   */
  444.                                         /* so traverse right         */ 
  445.                         if (this == NIL)
  446.                                 this = LWGET(OB_NEXT(tmp1));
  447.                         }
  448.                 else                    /* Revisiting parent:        */
  449.                                         /* No operation, move right  */
  450.                         {
  451.                         tmp1 = this;
  452.                         this = LWGET(OB_NEXT(tmp1));
  453.                         }
  454.         }
  455.  
  456.         WORD                            /* Find the parent object of    */
  457. get_parent(tree, obj)                   /* by traversing right until    */
  458.         LONG            tree;           /* we find nodes whose NEXT     */
  459.         WORD            obj;            /* and TAIL links point to      */
  460.         {                               /* each other.                  */
  461.         WORD            pobj;
  462.  
  463.         if (obj == NIL)
  464.                 return (NIL);
  465.         pobj = LWGET(OB_NEXT(obj));
  466.         if (pobj != NIL)
  467.         {
  468.           while( LWGET(OB_TAIL(pobj)) != obj ) 
  469.           {
  470.             obj = pobj;
  471.             pobj = LWGET(OB_NEXT(obj));
  472.           }
  473.         }
  474.         return(pobj);
  475.         } 
  476.  
  477.         WORD
  478. inside(x, y, pt)                /* determine if x,y is in rectangle     */
  479.         WORD            x, y;
  480.         GRECT           *pt;
  481.         {
  482.         if ( (x >= pt->g_x) && (y >= pt->g_y) &&
  483.             (x < pt->g_x + pt->g_w) && (y < pt->g_y + pt->g_h) )
  484.                 return(TRUE);
  485.         else
  486.                 return(FALSE);
  487.         } 
  488.  
  489.         WORD
  490. rc_intersect(p1, p2)            /* compute intersection of two GRECTs   */
  491.         GRECT           *p1, *p2;
  492.         {
  493.         WORD            tx, ty, tw, th;
  494.  
  495.         tw = min(p2->g_x + p2->g_w, p1->g_x + p1->g_w);
  496.         th = min(p2->g_y + p2->g_h, p1->g_y + p1->g_h);
  497.         tx = max(p2->g_x, p1->g_x);
  498.         ty = max(p2->g_y, p1->g_y);
  499.         p2->g_x = tx;
  500.         p2->g_y = ty;
  501.         p2->g_w = tw - tx;
  502.         p2->g_h = th - ty;
  503.         return( (tw > tx) && (th > ty) );
  504.         }
  505.  
  506.         VOID
  507. rc_copy(psbox, pdbox)           /* copy source to destination rectangle */
  508.         GRECT   *psbox;
  509.         GRECT   *pdbox;
  510.         {
  511.         pdbox->g_x = psbox->g_x;
  512.         pdbox->g_y = psbox->g_y;
  513.         pdbox->g_w = psbox->g_w;
  514.         pdbox->g_h = psbox->g_h;
  515.         }
  516.  
  517.  
  518. /************* "Hot-spot" manager and subroutines  ***************/
  519.  
  520.         WORD
  521. break_x(pxy)
  522.         WORD    *pxy;
  523.         {                               /* Breaking object is right of  */
  524.         if (br_mx < pxy[0])             /* mouse.  Reduce width of      */
  525.                 {                       /* bounding rectangle.          */
  526.                 br_rect.g_w = pxy[0] - br_rect.g_x;
  527.                 return (TRUE);
  528.                 }
  529.         if (br_mx > pxy[2])             /* Object to left.  Reduce width*/
  530.                 {                       /* and move rect. to right      */
  531.                 br_rect.g_w += br_rect.g_x - pxy[2] - 1;
  532.                 br_rect.g_x = pxy[2] + 1;
  533.                 return (TRUE);
  534.                 }
  535.         return (FALSE);                 /* Mouse within object segment. */
  536.         }                               /* Break attempt fails.         */
  537.  
  538.         WORD
  539. break_y(pxy)
  540.         WORD    *pxy;
  541.         {
  542.         if (br_my < pxy[1])             /* Object below mouse.  Reduce  */
  543.                 {                       /* height of bounding rect.     */
  544.                 br_rect.g_h = pxy[1] - br_rect.g_y;
  545.                 return (TRUE);
  546.                 }
  547.         if (br_my > pxy[3])             /* Object above mouse.  Reduce  */
  548.                 {                       /* height and shift downward.   */
  549.                 br_rect.g_h += br_rect.g_y - pxy[3] - 1;
  550.                 br_rect.g_y = pxy[3] + 1;
  551.                 return (TRUE); 
  552.                 }
  553.         /* Emergency escape test! Protection vs. turkeys who nest */
  554.         /* non-selectable objects inside of selectables.          */
  555.         if (br_mx >= pxy[0] && br_mx <= pxy[1])
  556.                 {                               /* Will X break fail?     */
  557.                 br_rect.g_x = br_mx;            /* If so, punt!           */
  558.                 br_rect.g_y = br_my;
  559.                 br_rect.g_w = br_rect.g_h = 1;
  560.                 return (TRUE);
  561.                 }
  562.         return (FALSE);
  563.         }
  564.  
  565.         WORD
  566. break_obj(tree, obj)                    /* Called once per object to    */
  567.         LONG    tree;                   /* check if the bounding rect.  */
  568.         WORD    obj;                    /* needs to be modified.        */
  569.         {
  570.         GRECT   s;
  571.         WORD    flags, broken, pxy[4];
  572.  
  573.         objc_xywh(tree, obj, &s);
  574.         grect_to_array(&s, pxy);
  575.         if (!rc_intersect(&br_rect, &s))
  576.                 return (FALSE);         /* Trivial rejection case       */
  577.  
  578.         flags = LWGET(OB_FLAGS(obj));   /* Is this object a potential   */
  579.         if (flags & HIDETREE)           /* hot-spot?                    */
  580.                 return (FALSE);
  581.         if ( !(flags & SELECTABLE) )
  582.                 return (TRUE);
  583.         if (LWGET(OB_STATE(obj)) & DISABLED)
  584.                 return (TRUE);
  585.  
  586.         for (broken = FALSE; !broken; ) /* This could take two passes   */
  587.                 {                       /* if the first break fails.    */
  588.                 if (br_togl)
  589.                         broken = break_x(pxy);
  590.                 else
  591.                         broken = break_y(pxy);
  592.                 br_togl = !br_togl;
  593.                 }
  594.         return (TRUE);
  595.         }
  596.  
  597.         WORD                            /* Manages mouse rectangle events */
  598. form_hot(tree, hot_obj, mx, my, rect, mode)
  599.         LONG    tree;
  600.         WORD    hot_obj, mx, my, *mode;
  601.         GRECT   *rect;
  602.         {
  603.         GRECT   root;
  604.         WORD    state;
  605.  
  606.         objc_xywh(tree, ROOT, &root);   /* If there is already a hot-spot */
  607.         if (hot_obj != NIL)             /* turn it off.                   */
  608.                 objc_toggle(tree, hot_obj);
  609.  
  610.         if (!(inside(mx, my, &root)) )  /* Mouse has moved outside of     */
  611.                 {                       /* the dialog.  Wait for return.  */
  612.                 *mode = M1_ENTER;
  613.                 rc_copy(&root, rect);
  614.                 return (NIL);
  615.                 }
  616.                                         /* What object is mouse over?     */
  617.                                         /* (Hit is guaranteed.)           */
  618.         hot_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my);
  619.                                         /* Is this object a hot-spot?     */
  620.         state = LWGET(OB_STATE(hot_obj));
  621.         if (LWGET(OB_FLAGS(hot_obj)) & SELECTABLE)
  622.         if ( !(state & DISABLED) )
  623.                 {                       /* Yes!  Set up wait state.       */
  624.                 *mode = M1_EXIT;
  625.                 objc_xywh(tree, hot_obj, rect);
  626.                 if (state & SELECTED)   /* But only toggle if it's not    */
  627.                         return (NIL);   /* already SELECTED!              */
  628.                 else
  629.                         {
  630.                         objc_toggle(tree, hot_obj);
  631.                         return (hot_obj);
  632.                         }
  633.                 }
  634.  
  635.         rc_copy(&root, &br_rect);       /* No hot object, so compute    */
  636.         br_mx = mx;                     /* mouse bounding rectangle.    */
  637.         br_my = my;
  638.         br_togl = 0;
  639.         map_tree(tree, ROOT, NIL, break_obj);
  640.         rc_copy(&br_rect, rect);        /* Then return to wait state.   */
  641.         *mode = M1_EXIT;
  642.         return (NIL);
  643.         }
  644.  
  645.  
  646. /************* Keyboard manager and subroutines ***************/
  647.  
  648.         WORD
  649. find_def(tree, obj)             /* Check if the object is DEFAULT       */
  650.         LONG    tree;
  651.         WORD    obj;
  652.         {                       /* Is sub-tree hidden?                  */
  653.         if (HIDETREE & LWGET(OB_FLAGS(obj)))
  654.                 return (FALSE);
  655.                                 /* Must be DEFAULT and not DISABLED     */
  656.         if (DEFAULT & LWGET(OB_FLAGS(obj)))
  657.         if ( !(DISABLED & LWGET(OB_STATE(obj))) )
  658.                 fn_obj = obj;   /* Record object number                 */
  659.         return (TRUE);
  660.         }
  661.  
  662.         WORD
  663. find_tab(tree, obj)             /* Look for target of TAB operation.    */
  664.         LONG    tree;
  665.         WORD    obj;
  666.         {                       /* Check for hiddens subtree.           */
  667.         if (HIDETREE & LWGET(OB_FLAGS(obj)))
  668.                 return (FALSE);
  669.                                 /* If not EDITABLE, who cares?          */
  670.         if ( !(EDITABLE & LWGET(OB_FLAGS(obj))) )
  671.                 return (TRUE);
  672.                                 /* Check for forward tab match          */
  673.         if (fn_dir && fn_prev == fn_last)
  674.                 fn_obj = obj;
  675.                                 /* Check for backward tab match         */
  676.         if (!fn_dir && obj == fn_last)
  677.                 fn_obj = fn_prev;
  678.         fn_prev = obj;          /* Record object for next call.         */
  679.         return (TRUE);
  680.         }       
  681.  
  682.         WORD
  683. form_keybd(tree, edit_obj, next_obj, kr, out_obj, okr)
  684.         LONG    tree;
  685.         WORD    edit_obj, next_obj, kr, *out_obj, *okr;
  686.         {
  687.         if (LLOBT(kr))          /* If lower byte valid, mask out        */
  688.                 kr &= 0xff;     /* extended code byte.                  */
  689.         fn_dir = 0;             /* Default tab direction if backward.   */
  690.         switch (kr) {
  691.                 case CR:        /* Zap character.                       */
  692.                         *okr = 0;
  693.                                 /* Look for a DEFAULT object.           */
  694.                         fn_obj = NIL;
  695.                         map_tree(tree, ROOT, NIL, find_def);
  696.                                 /* If found, SELECT and force exit.     */
  697.                         if (fn_obj != NIL)
  698.                                 {
  699.                                 objc_sel(tree, fn_obj);
  700.                                 *out_obj = fn_obj;
  701.                                 return (FALSE);
  702.                                 }               /* Falls through to     */ 
  703.                 case TAB:                       /* tab if no default    */
  704.                 case DOWN:      
  705.                         fn_dir = 1;             /* Set fwd direction    */
  706.                 case BTAB:
  707.                 case UP:
  708.                         *okr = 0;               /* Zap character        */
  709.                         fn_last = edit_obj;
  710.                         fn_prev = fn_obj = NIL; /* Look for TAB object  */
  711.                         map_tree(tree, ROOT, NIL, find_tab);
  712.                         if (fn_obj == NIL)      /* try to wrap around   */
  713.                                 map_tree(tree, ROOT, NIL, find_tab);
  714.                         if (fn_obj != NIL)
  715.                                 *out_obj = fn_obj;
  716.                         break;
  717.                 default:                        /* Pass other chars     */
  718.                         return (TRUE);
  719.                 }
  720.         return (TRUE);
  721.         }
  722.  
  723.  
  724. /************* Mouse button manager and subroutines ***************/
  725.  
  726.         WORD
  727. do_radio(tree, obj)
  728.         LONG    tree;
  729.         WORD    obj;
  730.         {
  731.         GRECT   root;
  732.         WORD    pobj, sobj, state;
  733.  
  734.         objc_xywh(tree, ROOT, &root);
  735.         pobj = get_parent(tree, obj);           /* Get the object's parent */
  736.  
  737.         for (sobj = LWGET(OB_HEAD(pobj)); sobj != pobj;
  738.                 sobj = LWGET(OB_NEXT(sobj)) )
  739.                 {                               /* Deselect all but...     */
  740.                 if (sobj != obj)
  741.                         objc_dsel(tree, sobj);
  742.                 }
  743.         objc_sel(tree, obj);                    /* the one being SELECTED  */
  744.         }
  745.  
  746.         WORD                                    /* Mouse button handler    */
  747. form_button(tree, obj, clicks, next_obj, hot_obj)
  748.         LONG    tree;
  749.         WORD    obj, clicks, *next_obj, *hot_obj;
  750.         {
  751.         WORD    flags, state, hibit, texit, sble, dsbld, edit;
  752.         WORD    in_out, in_state;
  753.  
  754.         flags = LWGET(OB_FLAGS(obj));           /* Get flags and states   */
  755.         state = LWGET(OB_STATE(obj));
  756.         texit = flags & TOUCHEXIT;
  757.         sble = flags & SELECTABLE;
  758.         dsbld = state & DISABLED;
  759.         edit = flags & EDITABLE;
  760.  
  761.         if (!texit && (!sble || dsbld) && !edit) /* This is not an      */
  762.                 {                                /* interesting object  */
  763.                 *next_obj = 0;
  764.                 return (TRUE);
  765.                 }
  766.  
  767.         if (texit && clicks == 2)               /* Preset special flag  */
  768.                 hibit = 0x8000;
  769.         else
  770.                 hibit = 0x0;
  771.  
  772.         if (sble && !dsbld)                     /* Hot stuff!           */
  773.                 {
  774.                 if (flags & RBUTTON)            /* Process radio buttons*/
  775.                         do_radio(tree, obj);    /* immediately!         */ 
  776.                 else if (!texit)
  777.                         {
  778.                         in_state = (obj == *hot_obj)?   /* Already toggled ? */
  779.                                 state: state ^ SELECTED;        
  780.                         if (!graf_watchbox(tree, obj, in_state, 
  781.                                 in_state ^ SELECTED))
  782.                                 {                       /* He gave up...  */
  783.                                 *next_obj = 0;
  784.                                 *hot_obj = NIL;
  785.                                 return (TRUE);
  786.                                 }
  787.                         }
  788.                 else /* if (texit) */
  789.                         if (obj != *hot_obj)    /* Force SELECTED       */
  790.                                 objc_toggle(tree, obj);
  791.                 }
  792.  
  793.         if (obj == *hot_obj)            /* We're gonna do it! So don't  */
  794.                 *hot_obj = NIL;         /* turn it off later.           */
  795.  
  796.         if (texit || (flags & EXIT) )   /* Exit conditions.             */
  797.                 {
  798.                 *next_obj = obj | hibit;
  799.                 return (FALSE);         /* Time to leave!               */
  800.                 }
  801.         else if (!edit)                 /* Clear object unless tabbing  */
  802.                 *next_obj = 0;
  803.  
  804.         return (TRUE);
  805.         }
  806.  
  807.  
  808. /************* New forms manager: Entry point and main loop *************/
  809.  
  810.         WORD
  811. form_do(tree, start_fld)
  812.         REG LONG        tree;
  813.         WORD            *start_fld;
  814.         {
  815.         REG WORD        edit_obj;
  816.         WORD            next_obj, hot_obj, hot_mode;
  817.         WORD            which, cont;
  818.         WORD            idx;
  819.         WORD            mx, my, mb, ks, kr, br;
  820.         GRECT           hot_rect;
  821.         WORD            (*valid)();
  822.                                                 /* Init. editing        */
  823.         next_obj = *start_fld;
  824.         edit_obj = 0;
  825.                                                 /* Initial hotspot cndx */
  826.         hot_obj = NIL; hot_mode = M1_ENTER;
  827.         objc_xywh(tree, ROOT, &hot_rect);
  828.                                                 /* Main event loop      */
  829.         cont = TRUE;
  830.         while (cont)
  831.           {
  832.                                                 /* position cursor on   */
  833.                                                 /*   the selected       */
  834.                                                 /*   editting field     */
  835.           if (edit_obj != next_obj)
  836.           if (next_obj != 0)
  837.                 {
  838.                 edit_obj = next_obj;
  839.                 next_obj = 0;
  840.                 objc_edit(tree, edit_obj, 0, &idx, EDINIT);
  841.                 }
  842.                                                 /* wait for button or   */
  843.                                                 /* key or rectangle     */
  844.           which = evnt_multi(MU_KEYBD | MU_BUTTON | MU_M1, 
  845.                         0x02, 0x01, 0x01,
  846.                         hot_mode, hot_rect.g_x, hot_rect.g_y, 
  847.                                 hot_rect.g_w, hot_rect.g_h, 
  848.                         0, 0, 0, 0, 0,
  849.                         0x0L,
  850.                         0, 0,
  851.                         &mx, &my, &mb, &ks, &kr, &br);
  852.  
  853.           if (which & MU_M1)                    /* handle rect. event   */
  854.                 hot_obj = form_hot(tree, hot_obj, mx, my, &hot_rect, &hot_mode);
  855.                                                 /* handle keyboard event*/
  856.           if (which & MU_KEYBD)
  857.                 {                               /* Control char filter  */
  858.                 cont = form_keybd(tree, edit_obj, next_obj, kr, &next_obj, &kr);
  859.                 if (kr && edit_obj)             /* Add others to object */
  860.                         objc_edit(tree, edit_obj, kr, &idx, EDCHAR);
  861.                 }
  862.                                                 /* handle button event  */
  863.           if (which & MU_BUTTON)
  864.                 {                               /* Which object hit?    */
  865.                 next_obj = objc_find(tree, ROOT, MAX_DEPTH, mx, my);
  866.                 if (next_obj == NIL)
  867.                         next_obj = 0;
  868.                 else                            /* Process a click      */
  869.                         cont = form_button(tree, next_obj, br, 
  870.                                 &next_obj, &hot_obj);
  871.                 }
  872.                                                 /* handle end of field  */
  873.                                                 /*   clean up           */
  874.           if (!cont || (next_obj != edit_obj && next_obj != 0))
  875.           if (edit_obj != 0) 
  876.                 objc_edit(tree, edit_obj, 0, &idx, EDEND);
  877.           }
  878.                                                 /* If defaulted, may    */
  879.                                                 /* need to clear hotspot*/
  880.         if (hot_obj != (next_obj & 0x7fff))
  881.         if (hot_obj != NIL)
  882.                 objc_toggle(tree, hot_obj);
  883.                                                 /* return exit object   */
  884.                                                 /*   hi bit may be set  */
  885.                                                 /*   if exit obj. was   */
  886.                                                 /*   double-clicked     */
  887.         *start_fld = edit_obj;
  888.         return(next_obj);
  889.         }
  890.