home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 5 / 05.iso / a / a012 / 1.ddi / BOXGET.TXT < prev    next >
Encoding:
Text File  |  1991-05-01  |  8.3 KB  |  215 lines

  1.                   5 FOCUS: BOXGET(), a Generic GET Function
  2.                                  By Greg Lief
  3.  
  4.  
  5. Introduction
  6.  
  7. History has proven that I respond very well to challenges.  This
  8. function resulted from such a challenge.  Matthew Amis posted a public
  9. message on the Aquarium BBS regarding a function that he was working
  10. on.  His function would draw a box, display a prompt, and GET a
  11. variable.  He was coming up against some kind of obstacle, and
  12. was soliciting opinions (for which the Aquarium Message Center is
  13. perfectly suited).
  14.  
  15. Silly me... I volunteered to write it for him.  Guess what, folks?  It
  16. is a bit trickier than it sounds!  I'll show you why after we cover
  17. the basic syntax.
  18.  
  19.  
  20. Syntax
  21.  
  22. BOXGET <var> PROMPT <prompt> [ PICTURE <pict> ] [ VALID <valid> ] ;
  23.          [ BOXCOLOR <boxcolor> ]  [ COLOR <color> ] [ ROW <row> ] ;
  24.          [ COLUMN <column> ] [NORESTORE] [RESTOREALL] [DOUBLE]
  25.  
  26.  
  27. Required Parameters
  28.  
  29. <var> is the name of the variable to be gotten.
  30.  
  31. <prompt> is a character expression representing the prompt to be
  32. displayed.
  33.  
  34.  
  35. Optional Parameters
  36.  
  37. <pict> is a character expression representing the PICTURE clause to use
  38. for the GET variable.
  39.  
  40. <valid> is the VALID clause to use for the GET variable.
  41.  
  42. <boxcolor> is a character expression representing the color in which to
  43. draw the box. If you do not specify this, the current color will be used.
  44.  
  45. <color> is a character expression representing the GET color. You must
  46. specify this in the format "<standard>,<enhanced>", where <enhanced>
  47. will be used when the GET is highlighted. If you do not specify this,
  48. the current standard and enhanced color settings will be used.
  49.  
  50. <row> and <column> are the top row and left column at which to display
  51. the box. If you do not specify these parameters, BOXGET() will
  52. automatically center the box horizontally and/or vertically on the
  53. screen.
  54.  
  55. <title> is a character expression. If you specify this, it will be
  56. centered on the top row of the box.
  57.  
  58. The NORESTORE clause directs BOXGET() not to automatically restore
  59. the underlying screen upon exit. This can be used primarily to leave
  60. a series of GETs on the screen. If you specify this clause, the contents
  61. of the underlying screen will be added to a stack for later restoration
  62. with the RESTOREALL clause.
  63.  
  64. The RESTOREALL clause will restore all screen fragments stored in the
  65. aforementioned window stack.
  66.  
  67. The DOUBLE clause will cause a double-line box to be drawn.  By default,
  68. the box is single-line.
  69.  
  70.  
  71. Return Value
  72.  
  73. BOXGET() does not return a value, because it manipulates <var> directly.
  74.  
  75.  
  76. Methodology
  77.  
  78. The first thing you may wonder is how a function such as BOXGET() can
  79. operate on a variable that is declared local in a higher-level module
  80. (especially when you consider that BOXGET() returns no value).  That's
  81. a good question, and here's a great answer: It works through the magic
  82. of code blocks.
  83.  
  84. Look carefully at the user-defined command syntax in the adjoining
  85. source code file. A GET object is created with the GETNEW() function.
  86. GETNEW() requires a retrieval code block for the GET variable in
  87. question.  Following is a sample retrieval block for the variable
  88. MNAME:
  89.  
  90.    { | x | if(pcount() = 0, mname, mname := x }
  91.  
  92. This retrieval code block accepts one optional argument. If the
  93. argument is passed to the code block, the value of the argument is
  94. assigned to the GET variable. That is precisely what happens after you
  95. complete a GET.
  96.  
  97. The important thing to remember is that in 5.0, GET variables are
  98. never manipulated directly -- rather, they are only manipulated
  99. through their corresponding retrieval code block.
  100.  
  101.  
  102. Housekeeping
  103.  
  104. BOXGET() handles all necessary cleanup, including cursor size and
  105. position, screen contents, color, and SCOREBOARD status (which I
  106. thoroughly despise).  If you specify the NORESTORE clause, the screen
  107. contents under each box are saved to a dynamically resizable array
  108. with the following statement:
  109.  
  110.    aadd(boxstack_, { TOP, LEFT, BOTTOM, RIGHT, ;
  111.                      savescreen(TOP, LEFT, BOTTOM, RIGHT) } )
  112.  
  113. This stack is then dumped when you call BOXGET with the RESTOREALL
  114. clause:
  115.  
  116.    for x = len(boxstack_) to 1 step -1
  117.       restscreen(boxstack_[x, 1], boxstack_[x, 2], boxstack_[x, 3], ;
  118.                  boxstack_[x, 4], boxstack_[x, 5])
  119.    next
  120.    asize(boxstack_, 0)        // clean it out
  121.  
  122.  
  123.  
  124. VALID Clause
  125.  
  126. Unlike the internal _GET_() function, GETNEW() does not allow you to
  127. pass a VALID clause to be attached to the GET object. This caused me a
  128. bit of consternation.  One solution would have been to use a temporary
  129. variable to hold the GET object in the user-defined command, then
  130. assign its postBlock instance variable.  This did not feel right to
  131. me, so I ripped out some of my hair until I found a better solution.
  132.  
  133. Finally, I decided to have the preprocessor convert the VALID clause
  134. to a code block with the "blockify" result-marker ("<{valid}>").  This
  135. code block would then be passed to BOXGET(). Once inside the function,
  136. I could then safely assign this code block to the postBlock instance
  137. variable for the GET object.  It works beautifully!
  138.  
  139.  
  140. Screen Position
  141.  
  142. If you look closely at the call to GETNEW(), something odd will leap
  143. out at you.  The first two parameters are the row and column at which
  144. the GET should appear.  "But why in Heaven's name do I use MAXROW()+1
  145. and MAXCOL()+1?" you ask.  "Those would be off the screen, right?"
  146.  
  147. Quite right, but I had my reasons for doing this.  In order to
  148. calculate the proper coordinates for the box, it is necessary to
  149. determine the length of the GET variable. This could be done by
  150. checking the length of its PICTURE clause, but suppose that no PICTURE
  151. clause was specified.  Moreover, we cannot always trust the PICTURE
  152. clause, for it may contain a deceptive template (e.g., "@!", "@R").
  153.  
  154. Therefore, I felt it best to verify the length of the GET buffer,
  155. But the GET must be activated with the setFocus() method before you
  156. can check the buffer, because the g:buffer instance variable only
  157. exists when the GET is active.  The following statements activate the
  158. GET, grab the length of the buffer, and de-activate it.
  159.  
  160.    oget:setFocus()
  161.    getlength = len(oget:buffer)
  162.    oget:killFocus()
  163.  
  164. It must be de-activated because the box has not yet been drawn.
  165. Remember... we need to know the length of the buffer before we can
  166. think about drawing the box!
  167.  
  168. Now do you see why I used MAXROW()+1 and MAXCOL()+1 as the row and
  169. column position for the GET?  Of course... because it must be
  170. activated "before the fact".  When we activate it to test the length
  171. of the buffer, it will be invisible to the user!
  172.  
  173. Afterwards, it is a simple matter to re-assign the g:row and g:col
  174. instance variables to the proper position, but that can only be done
  175. after we know where the box is going to appear.
  176.  
  177.  
  178. Delimiters
  179.  
  180. If you use delimiters in your programs, you will be happy to know that
  181. BOXGET() properly obeys them.  However, it required some legwork. If
  182. you issue a GET using the @..GET command, the DELIMITERS setting is
  183. automatically taken into account.  But when you create a GET object
  184. using GETNEW() (as I do with the BOXGET() function), delimiters are
  185. ignored.
  186.  
  187. Therefore, I had to check for whether delimiters were on or off.  If
  188. they were on, the function had to account for two additional
  189. characters, so the box would be drawn accordingly.  The delimiters
  190. themselves had to be drawn manually using this code:
  191.  
  192.    dispout(' ' + left(set(_SET_DELIMCHARS), 1) + ;
  193.            space(getlength) + right(set(_SET_DELIMCHARS), 1))
  194.  
  195. It seems like a lot of trouble to go through, but perfection is worth
  196. any price.  (This is not to say that BOXGET() is perfect, because as
  197. we all know, software is inherently imperfect!)
  198.  
  199.  
  200. Summary
  201.  
  202. Not only will you get good mileage out of this very useful function, but
  203. I hope that it gives you additional insight into GET objects and 5.0
  204. in general.  This also shows you how you can use the preprocessor to
  205. make it easier to use functions that have many parameters.  By
  206. creating a user-defined command, you don't have to remember the order
  207. of the parameters.
  208.  
  209.  
  210. About The Author
  211.  
  212. Greg Lief is the author of the Grumpfish Library.  He is presenting
  213. Clipper 5.0 training seminars throughout North America this Spring.
  214. Contact Grumpfish, Inc. if you are interested in attending.
  215.