home *** CD-ROM | disk | FTP | other *** search
/ Falcon 030 Power 2 / F030_POWER2.iso / ST_STE / MAGS / ICTARI05.ARJ / ictari.05 / C / GEM_TUT / GEM_005.TXT next >
Text File  |  1993-05-12  |  18KB  |  335 lines

  1.                         *PROFESSIONAL GEM*
  2.                 Part 5 -- Resource Tree Structures
  3.                            By Tim Oren
  4.  
  5.      This is the fifth issue of ST PROFESSIONAL GEM, concluding our
  6. trek through GEM dialogs and resources with a look at the internal
  7. structure of object trees.  Also, I'll answer a number of questions
  8. of general interest which have been received via the ANTIC ONLINE
  9. FEEDBACK.  As always, there is a download file associated with this
  10. column: GEMCL5.C, which you will find in DL3 of the new Atari 16-bit
  11. SIG (type GO PCS-58 or GO ATARI16).
  12.  
  13.      Even if you have no immediate use for this issue's code, be sure
  14. to take the download anyway; some of the routines will be used in
  15. later articles.
  16.  
  17.      In the last installment, we established that resources trees are
  18. pointed to by the tree index, and that they are composed of objects
  19. which contain pointers onward to other structures.  However, we
  20. passed over the issue of linkage among the objects within a tree.  It
  21. is now time to go back and cure this omission.
  22.  
  23.      The technical term for the linkage scheme of an object tree is a
  24. "right-threaded binary tree".   If you already know what this is, you
  25. can skim over the next few paragraphs.  If you happen to have access
  26. to a copy of the book "FUNDAMENTAL ALGORITHMS", which is part of the
  27. series THE ART OF COMPUTER PROGRAMMING by Donald E. Knuth, you might
  28. want to read his excellent discussion of binary trees beginning on
  29. page 332.
  30.  
  31.      For the following discussion, you should have a listing of the C
  32. image of a resource tree in front of you.  For those who do not have
  33. the listing from the last column, I have included a fragment at the
  34. beginning of the download.  Before we begin, I should warn you of one
  35. peculiarity of "computer trees":  They grow upside-down!  That is,
  36. when they are diagrammed or described, their root is at the top, and
  37. the  "leaves" grow downward.  You will see this both in the listing,
  38. and in the way the following discussion talks about moving through
  39. trees.
  40.  
  41.      Each GEM object tree begins at its ROOT object, numbered zero,
  42. which is the object pointed at by the tree index.  There are three
  43. link  fields at the beginning of each object.  They are called
  44. OB_NEXT, OB_HEAD,  and OB_TAIL, which is the order in which they
  45. appear.
  46.  
  47.      Each of the links is shown as an index relative to the root of
  48. the current tree.  This means that the link '0' would refer to the
  49. root of the tree, while '2' would indicate the object two lines below
  50. it. The special link -1 is called NIL, and means that there is no
  51. link in  the given direction.
  52.  
  53.      Each object, or node, in a tree may have "offspring" or nodes
  54. which are nested below it.  If it does, then its OB_HEAD will point
  55. to its first (or "leftmost") "child", while the OB_TAIL will point to
  56. the last ("rightmost") of its offspring.  The OB_NEXT pointer links
  57. the  children together, with the OB_NEXT of the first pointing to the
  58. second, and so on, until the OB_NEXT of the last finally points back
  59. to its parent, the object at which we started.
  60.  
  61.      Remember that each of these children may in turn have offspring
  62. of their own, so that the original "parent" may have a large and
  63. complex collection of "descendents".
  64.  
  65.      Let's look at the first tree in the download to see an example
  66. of this structure.  The very first object is the ROOT.  Note that its
  67. OB_NEXT is NIL, meaning that there are no more objects in the tree:
  68. the ROOT is both the beginning and the end of the tree.  In this
  69. case, the OB_HEAD is 1 and the OB_TAIL is 3, showing that there are
  70. at least two different children.
  71.  
  72.      Following OB_HEAD down to the next line, we can trace through
  73. the OB_NEXT links (2, 3, 0) as they lead through a total of three
  74. children and back to the ROOT.  You will notice that the first two
  75. children have NIL for the OB_HEAD and OB_TAILs, indicating that they
  76. have no further offspring.
  77.  
  78.      However, node three, the last child of the ROOT, does have the
  79. value 4 for both its OB_HEAD and OB_TAIL.  By this we can tell that
  80. it  has one, and only one, offspring.  Sure enough, when we look at
  81. node four, we see that its OB_NEXT leads immediately back to node
  82. three. Additionally, it has no further offspring because its OB_HEAD
  83. and OB_TAIL are NIL.
  84.  
  85.      You will find that object trees are always written out by the
  86. Resource Construction Set in "pre-order".  (Again, see Knuth if you
  87. have a copy.)  This means that the ROOT is always written first, then
  88. its offspring left to right.  This rule is applied recursively, that
  89. is, we go down to the next level and write out each of these nodes,
  90. then THEIR children left to right, and so on.
  91.  
  92.      For a further example, look at the next tree in rs_object in the
  93. download.  You will see that the ROOT has an OB_HEAD of 1 and an
  94. OB_TAIL of 6, but that it actually has only three offspring (nodes 1,
  95. 2 and 6).  We see that node 2 itself had children, and applying the
  96. rule given above, they were written out before continuing with the
  97. next child of the ROOT.
  98.  
  99.      Why was this seemingly complex structure chosen for GEM?  The
  100. reason has do with the tasks of drawing objects in their proper
  101. locations on the screen, and determining which object was "hit" when
  102. a mouse click is detected.
  103.  
  104.      To find out how this works, we must look at four more fields
  105. found in each object: OB_X, OB_Y, OB_WIDTH, and OB_HEIGHT.  These
  106. fields are the last four on each line in the sample trees.
  107.  
  108.      Each object in a tree "owns" a rectangle on the screen.  These
  109. fields define that rectangle.  When a resource is stored "outside"
  110. the  program the fields are in character units, so that an object
  111. with OB_WIDTH  of 10 and OB_HEIGHT of 2 (for instance) would define a
  112. screen area 10  characters wide and 2 high.
  113.  
  114.      When the resource is read into memory with an rsrc_load call,
  115. GEM multiplies the appropriate character dimension in pixels into
  116. each of these fields.  In this way portability is achieved: the same
  117. resource file works for any of the ST's three resolutions.  Knowing
  118. how rsrc_load works, your code should treat these fields as pixel
  119. coordinates.
  120.  
  121.      (I have committed one oversimplification above.  If an object is
  122. not created on a character boundary in the RCS, then the external
  123. storage method described will not work.  In this case, the lower byte
  124. of each rectangle field is used to store the nearest character
  125. position, while the upper byte stores the pixel remainder to be added
  126. after the character size is multiplied in.
  127.  
  128.      Non-character-boundary objects may only be created in the "FREE"
  129. tree mode of the Resource Construction Set (also called "PANEL" in
  130. RCS 2.0). You should use them only in programs which will run in a
  131. single ST screen  mode, because pixel coordinates are not portable
  132. between resolutions.)
  133.  
  134.      The first real secret of object rectangles is that each OB_X and
  135. OB_Y is specified RELATIVE to the X and Y coordinate of its parent
  136. object  within the tree.  This is the first property we have seen
  137. that is actually "inherited" from level to level within the tree.
  138.  
  139.      The second secret is more subtle:  Every object's rectangle must
  140. be entirely contained within the rectangle of its parent.  This
  141. principle goes by the names "bounding rectangles" or "visual
  142. hierarchy".  We'll see in a moment how useful it is when detecting
  143. mouse/object collisions.
  144.  
  145.                          HOW GEM DOES IT.
  146.  
  147.      Knowing these secrets, and the linkage structure  of object
  148. trees, we can deduce how a number of the GEM operations must work.
  149. For instance, consider objc_offset,  which returns the actual screen
  150. X and Y  of an object.  We can see now that simply loading the OB_X
  151. and OB_Y fields of  the object does not suffice: they only give the
  152. offset relative to the parent  object.  So, objc_offset must BEGIN
  153. with these values, and then work its way  back up to the ROOT of the
  154. tree, adding in the offsets found at each level.
  155.  
  156.      This can be done by following the OB_NEXT links from the chosen
  157. object.  Whenever OB_NEXT points to an object whose OB_TAIL points
  158. right back  to the same location, then the new node is another level,
  159. or "parent" in the  tree, and objc_offset adds its OB_X and OB_Y into
  160. the running totals.  When OB_NEXT becomes NIL, then the ROOT has been
  161. reached and the totals are the values to return.  (By the way,
  162. remember that the OB_X and OB_Y of the ROOT are undefined until
  163. form_center has been called for the tree.  They are shown as zeroes
  164. in the sample trees.)
  165.  
  166.      We can also figure out objc_draw.  It works its way DOWN the
  167. tree, drawing each object as it comes to it.  It, too, must keep a
  168. running X and Y variable, adding in object offsets as it descends
  169. tree levels (using OB_HEAD), and subtracting them again as it returns
  170. from each level.   Since the larger objects are nearer the ROOT, we
  171. can now see why they are  drawn first, with smaller objects drawn
  172. later or "on top of" them.
  173.  
  174.      (If you write an application which needs to move portions of a
  175. dialog or screen with respect to each other, you can take advantage
  176. of inheritance of screen position in objc_draw.  Simply by changing
  177. the OB_X and/or OB_Y of an object, you can move it and its entire
  178. sub-tree to a new location in the dialog.  For instance, changing the
  179. coordinates of the parent box of a set of radio buttons will cause
  180. all of the buttons to move along with it.)
  181.  
  182.      Objc_draw also gives us an example of the uses of visual
  183. hierarchy. Recall that a clipping rectangle is specified when calling
  184. objc_draw. At each level of the tree we know that all objects below
  185. are contained in the screen rectangle of the current object.  If the
  186. current rectangle falls completely outside the specified clipping
  187. rectangle, we know  immediately that we need not draw the object, or
  188. any of its descendents! This ability to ignore an entire subtree is
  189. called "trivial rejection".
  190.  
  191.      Now it's rather easy to figure out objc_find.  It starts out by
  192. setting its "object found" variable to NIL.  It begins a "walk"
  193. through the entire object tree, following OB_HEAD and OB_NEXT links,
  194. and keeping  a current X and Y, just like objc_draw.
  195.  
  196.      At each node visited, it simply checks to see if the "mouse" X,Y
  197. specified in the call are inside the current object's rectangle.  If
  198. they  are, that object becomes the found object, and the tree walk
  199. continues with the object's offspring, and then siblings.  Notice how
  200. this checking of offspring makes sure that a smaller object nested
  201. within, i.e., below, a larger object is found correctly.
  202.  
  203.      If the mouse X,Y position is not within the object being
  204. checked, then by visual hierarchy it cannot be within any of its
  205. offspring, either.   Trivial rejection wins again, and the entire
  206. sub-tree is skipped!  Objc_find  moves on to the OB_NEXT of the
  207. rejected object.
  208.  
  209.                        THOUGHT EXPERIMENTS.
  210.  
  211.      Thinking about the objc_find algorithm  reveals some information
  212. about its performance, and a few tricks we may  use in improving the
  213. appearance of dialogs and other object trees.
  214.  
  215.      First consider the problem of a dialog which contains many
  216. objects. If we lay them all out "side-by-side", then they will all be
  217. immediate offspring of the ROOT object.  In this situation, the
  218. trivial rejection method will gain nothing.  The time objc_find takes
  219. to complete will vary linearly with the total number of objects.
  220. This is called an "Order N" process.
  221.  
  222.      Suppose that instead we broke up the dialog into two areas with
  223. invisible boxes, then broke up each of these areas in a like fashion,
  224. and so on until we got down to the size of the individual selectable
  225. objects.  The number of bottom level objects in this scheme is a
  226. power  of two equal to the depth of the tree. Trivial rejection is
  227. used to its  fullest in this case.  It is called an "Order Log N"
  228. process, and is much  more efficient for large numbers of objects.
  229.  
  230.      In practice, the speed of the ST will allow you to ignore this
  231. distinction for most dialogs and other trees.  But if you get into a
  232. situation when speed is critical in searching a large tree, remember
  233. that nesting objects can improve performance dramatically.
  234.  
  235.      If you have been following closely, you may have also noticed a
  236. hole in the visual hierarchy rule.  It says that all of a node's
  237. children must lie within its rectangle, but it does NOT guarantee
  238. that the children's  rectangles will be disjoint, that is, not
  239. overlap one another.  This peculiarity is the basis of several useful
  240. tricks.
  241.  
  242.      First, remember that objc_find always tries to scan the entire
  243. tree.  That is, it doesn't quit when it finds the first object on the
  244. given coordinates.  As mentioned above, this normally guarantees that
  245. nested objects will be found.  Consider, however, what happens when
  246. the mouse  coordinates are on a point where two or more objects AT
  247. THE SAME LEVEL overlap: they will replace one another as the "found
  248. object" until  objc_find returns with the one which is "last", that
  249. is, rightmost in  the tree.
  250.  
  251.      This quirk can be used to advantage in a number of cases.
  252. Suppose that you have in a dialog an image and a string which you
  253. would  like to be selected together when either is clicked.  Nesting
  254. within a common parent achieves nothing in this case.  Instead,
  255. knowing that  form_do must use objc_find, you could use our trick.
  256.  
  257.      You have to know that the Resource Construction Set normally
  258. adds  objects in a tree left to right, in the order in which you
  259. inserted them.  You proceed to build the dialog in the following
  260. order: insert the image  first, the string next, then carefully add
  261. an invisible box which is not  nested within either, and size it to
  262. cover them both.  Set the SELECTABLE  attribute for the box, and the
  263. dialog manager will find it, and invert  the whole area, when either
  264. the image or string is clicked.
  265.  
  266.      By the way, remember that the SORT option in the RCS will change
  267. the order of an object's offspring.  If you are going to try this
  268. trick, don't use SORT!  It will undo all of your careful work.
  269.  
  270.      A TREEWALKER OF OUR OWN.
  271.  
  272.      Since the GEM system gets so much mileage  out of walking object
  273. trees, it seems reasonable that the same method should  be useful in
  274. application programs.  In the download you will find map_tree(). As
  275. many LISP veterans might guess from the name, this code will traverse
  276. all or part of an object tree, applying a function to each node.  It
  277. also allows the function to return a true/false value specifying
  278. whether  the sub-tree below a particular node should be ignored.
  279. Let's examine map_tree() in more detail as a final review of object
  280. tree structure.
  281.  
  282.      First, look at the parameters.  "tree" is the long address of
  283. the object tree of interest, as retrieved by rsrc_gaddr.  "this" is
  284. the node at which to begin the traverse, and "last" is the node at
  285. which to terminate.
  286.  
  287.      In most cases, the beginning node will be ROOT, and the final
  288. value will be NIL.  This will result in the entire tree being
  289. traversed. You may use other values, but be sure that you CAN get to
  290. "last" from "this" by following tree links!  Although map_tree()
  291. includes a safety  check to prevent "running off" the tree, you could
  292. get some very strange  results from incorrect parameters.
  293.  
  294.      The declaration for the final parameter, "routine", makes use of
  295. C construct which may be new to some.  It is a pointer to a
  296. subroutine which returns a WORD as a result.
  297.  
  298.      Map_tree() begins by initializing a temporary variable, tmp1,
  299. which is used to store the number of the last node visited.  Since no
  300. node will follow itself, setting tmp1 to the starting node is safe.
  301.  
  302.      The main loop of the routine simply repeats visiting a new node
  303. until the last value is reached, or the safety check for end of tree
  304. is satisfied.
  305.  
  306.      At any node visited, we can be in one of two conditions. Either
  307. we are at a node which is "new", that is, not previously visited, or
  308. else we are returning to a parent node which has already been
  309. processed. We can detect the latter condition by comparing the last
  310. node visited (tmp1) with the OB_TAIL pointer of the current node.  If
  311. the node is "old", it is not processed a second time, we simply
  312. update tmp1 and  continue.
  313.  
  314.      If the node is new, we call "routine" to process it, sending the
  315. tree address and object number as parameters.  If a FALSE is
  316. returned,  we will ignore any subtree below this node.  On a TRUE
  317. return, we load up  the OB_HEAD pointer and follow it if a subtree
  318. exists.  (If you don't care  about rejecting subtrees, simply remove
  319. the if condition.)  Finally, if  the new node had no subtree, or was
  320. rejected by "routine", we follow along  its OB_NEXT link to the next
  321. node.
  322.  
  323.      A simple application of our new tool shows its power.  From a
  324. previous column you may recall the tedium of deselecting every button
  325. inside a dialog after it was completed.  Using map_tree(), you can
  326. deselect EVERY OBJECT in the tree by using map_tree(tree, ROOT, NIL,
  327. desel_obj); You must use a slightly modified version of desel_obj()
  328. (included in the download) which always returns TRUE.  Be sure to
  329. define or declare desel_obj() in your code BEFORE making the map_tree
  330. call!
  331.  
  332.      In future columns, I will return to map_tree() and show how it
  333. can be used for advanced techniques such as animated dialogs.  In the
  334. meantime, experiment and enjoy!
  335.