home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 5 / 05.iso / a / a093 / 3.ddi / GENMENU.PR_ / GENMENU.bin
Encoding:
Text File  |  1993-01-17  |  48.7 KB  |  1,709 lines

  1. *
  2. * GENMENU - Menu code generator.
  3. *
  4. * Copyright (c) 1990 - 1993 Microsoft Corp.
  5. * 1 Microsoft Way
  6. * Redmond, WA 98052
  7. *
  8. * Description:
  9. * This program generates menu code which was designed in the
  10. * FoxPro 2.5 MENU BUILDER.
  11. *
  12. * Notes:
  13. * In this program, for clarity/readability reasons, we use variable
  14. * names that are longer than 10 characters.  Note, however, that only
  15. * the first 10 characters are significant.
  16. *
  17. * Modification History:
  18. * December 13, 1990        JAC        Program Created
  19. *
  20. * Modifed for FoxPro 2.5 by WJK.
  21. *
  22. PARAMETER m.projdbf, m.recno
  23. PRIVATE ALL
  24. IF SET("TALK") = "ON"
  25.     SET TALK OFF
  26.     m.talkstate = "ON"
  27. ELSE
  28.     m.talkstate = "OFF"
  29. ENDIF
  30. m.escape = SET("ESCAPE")
  31. SET ESCAPE OFF
  32. m.trbetween = SET("TRBET")
  33. SET TRBET OFF
  34. m.comp = SET("COMPATIBLE")
  35. SET COMPATIBLE OFF
  36. mdevice = SET("DEVICE")
  37. SET DEVICE TO SCREEN
  38.  
  39. *
  40. * Declare Constants
  41. *
  42. #DEFINE c_esc    CHR(27)
  43. #DEFINE c_null    CHR(0)
  44.  
  45. *
  46. * Possible values of Objtype field in SCX database.
  47. *
  48. #DEFINE c_menu        1
  49. #DEFINE c_submenu    2
  50. #DEFINE c_item        3
  51.  
  52. *
  53. * Some of the values of Objcode field in SCX database.
  54. *
  55. #DEFINE    c_global    1
  56. #DEFINE c_proc        80
  57.  
  58. #DEFINE c_maxsnippets    25
  59. #DEFINE c_maxpads        25
  60. #DEFINE c_pjx20flds        33
  61. #DEFINE c_pjxflds        31
  62. #DEFINE c_mnxflds        23
  63. #DEFINE c_20mnxflds        22
  64.  
  65. #DEFINE c_authorlen        45
  66. #DEFINE c_complen        45
  67. #DEFINE c_addrlen        45
  68. #DEFINE c_citylen        20
  69. #DEFINE c_statlen        5
  70. #DEFINE c_ziplen        10
  71. #DEFINE c_countrylen 40
  72.  
  73. #DEFINE c_error_1        "Minor"
  74. #DEFINE c_error_2        "Serious"
  75. #DEFINE c_error_3        "Fatal"
  76.  
  77. #DEFINE c_dlgface        "MS Sans Serif"
  78. #DEFINE c_dlgsize        8.000
  79. #DEFINE c_dlgstyle        "B"
  80.  
  81. #DEFINE c_replace        0
  82. #DEFINE c_append        1
  83. #DEFINE c_before        2
  84. #DEFINE c_after            3
  85.  
  86. *
  87. * Declare Variables
  88. *
  89. STORE "" TO m.cursor, m.consol, m.bell, m.onerror, ;
  90.     m.exact, m.print, m.fixed, m.delimiters, m.mpoint
  91. STORE 0 TO m.deci, m.memowidth
  92.  
  93. m.g_error      = .F.
  94. m.g_errlog     = ""
  95. m.g_homedir    = ""
  96. m.g_location   = 0
  97. m.g_menucolor  = 0
  98. m.g_menumark   = ""
  99. m.g_nohandle   = .T.
  100. m.g_nsnippets  = 0
  101. m.g_outfile    = ""
  102. m.g_padloca    = ""
  103. m.g_projalias  = ""
  104. m.g_projdbf    = m.projdbf
  105. m.g_projpath   = ""
  106. m.g_status     = 0
  107. m.g_snippcnt   = 0
  108. m.g_thermwidth = 0
  109. m.g_workarea   = 0
  110. m.g_graphic    = .F.            
  111. m.g_20mnx       = .F.            
  112.  
  113. m.g_devauthor  = PADR("Author's Name",45," ")
  114. m.g_devcompany = PADR("Company Name",45, " ")
  115. m.g_devaddress = PADR("Address",45," ")
  116. m.g_devcity    = PADR("City",20," ")
  117. m.g_devstate   = "  "
  118. m.g_devzip     = PADR("Zip",10," ")
  119. m.g_devctry    = PADR("Country",40," ")
  120.  
  121. m.g_boxstrg = ['─','─','│','│','┌','┐','└','┘','─','─','│','│','┌','┐','└','┘']
  122.  
  123. STORE "" TO m.g_corn1, m.g_corn2, m.g_corn3, m.g_corn4, m.g_corn5, ;
  124.     m.g_corn6, m.g_verti2
  125. STORE "*" TO  m.g_horiz, m.g_verti1
  126.  
  127. *
  128. * Array Declarations
  129. *
  130. * g_mnxfile [1] - Normalized path + name
  131. * g_mnxfile [2] - Basename
  132. * g_mnxfile [3] - Opened originally?
  133. * g_mnxfile [4] - Alias
  134. *
  135. DIMENSION g_mnxfile[4]
  136. g_mnxfile[1] = ""
  137. g_mnxfile[2] = ""
  138. g_mnxfile[3] = .F.
  139. g_mnxfile[4] = ""
  140.  
  141. *
  142. * g_pads - names of generated menu pads
  143. *
  144. DIMENSION g_pads(c_maxpads)
  145.  
  146. *
  147. * g_snippets [*,1] - generated snippet procedure name
  148. * g_snippets [*,2] - recno()
  149. *
  150.  
  151. DIMENSION g_snippets (c_maxsnippets,2)
  152. g_snippets = ""
  153.  
  154. IF AT("WINDOWS", UPPER(VERSION())) <> 0 OR ;
  155.         AT("MAC", UPPER(VERSION())) <> 0
  156.     m.g_graphic = .T.
  157. ELSE
  158.     m.g_graphic = .F.
  159. ENDIF
  160.  
  161. *
  162. * Main program
  163. *
  164. m.onerror = ON("ERROR")
  165. ON ERROR DO errorhandler WITH MESSAGE(), LINENO(), c_error_3
  166.  
  167. IF PARAMETERS()=2
  168.     DO setup
  169.     IF validparams()
  170.         ON ESCAPE DO eschandler
  171.         SET ESCAPE ON
  172.         DO refreshprefs
  173.         DO BUILD
  174.     ENDIF
  175.     DO cleanup
  176. ELSE
  177.     DO errorhandler WITH "Invalid number of parameters passed to"+;
  178.         " the generator",LINENO(),c_error_3
  179. ENDIF
  180. ON ERROR &onerror
  181.  
  182. RETURN m.g_status
  183.  
  184. **
  185. ** Setup, Cleanup, Validparams, and Refreshprefs of Main Program
  186. **
  187.  
  188. *
  189. * STARTUP - Create program's environment.
  190. *
  191. * Description:
  192. * Save the user's environment so that we can set it back when
  193. * we are done, then issue various SET commands. The only state
  194. * we cannot conveniently save is SET TALK, because storing the
  195. * state involves an assignment statement, and assignments
  196. * generate unwanted output if TALK is set ON.
  197. *
  198. * Side Effects:
  199. * Creates a temporary file which is deleted in the Cleanup
  200. * procedure executed at the end of MENUGEN.
  201. *
  202. PROCEDURE setup
  203.     CLEAR PROGRAM
  204.     CLEAR GETS
  205.     m.g_workarea = SELECT()
  206.     m.delimiters = SET('TEXTMERGE',1)
  207.     SET TEXTMERGE DELIMITERS TO
  208.     SET UDFPARMS TO VALUE
  209.     
  210.     m.bell = SET("BELL")
  211.     SET BELL OFF
  212.     m.consol = SET("CONSOLE")
  213.     SET CONSOLE OFF
  214.     m.cursor = SET("CURSOR")
  215.     SET CURSOR OFF
  216.     m.deci = SET("DECIMALS")
  217.     SET DECIMALS TO 0
  218.     mdevice = SET("DEVICE")
  219.     SET DEVICE TO SCREEN
  220.     m.memowidth = SET("MEMOWIDTH")
  221.     SET MEMOWIDTH TO 256
  222.     m.exact = SET("EXACT")
  223.     SET EXACT ON
  224.     m.print = SET("PRINT")
  225.     SET PRINT OFF
  226.     m.fixed = SET("FIXED")
  227.     SET FIXED ON
  228.     mpoint = SET("POINT")
  229.     SET POINT TO "."
  230.     
  231. *
  232. * CLEANUP - restore environment to pre-execution state.
  233. *
  234. * Description:
  235. * Close all databases opened in the course of the execution of MENUGEN.
  236. * Restore the environment to the pre-execution of MENUGEN.  Delete
  237. * the VIEW file since there is no further use for it.
  238. *
  239. * Side Effects:
  240. * Closes databases.
  241. * Deletes the temporary view file.
  242. *
  243. PROCEDURE cleanup
  244.     PRIVATE m.delilen, m.ldelimi, m.rdelimi
  245.     IF EMPTY(m.g_projalias)
  246.         RETURN
  247.     ENDIF
  248.     SELECT (m.g_projalias)
  249.     USE
  250.     IF NOT EMPTY(g_mnxfile[3])
  251.         IF USED(g_mnxfile[4])
  252.             SELECT (g_mnxfile[4])
  253.             USE
  254.         ENDIF
  255.     ENDIF
  256.     SELECT (m.g_workarea)
  257.     
  258.     m.delilen = LEN(m.delimiters)
  259.     m.ldelimi = SUBSTR(m.delimiters,1,;
  260.         IIF(MOD(m.delilen,2)=0,m.delilen/2,CEILING(m.delilen/2)))
  261.     m.rdelimi = SUBSTR(m.delimiters,;
  262.         IIF(MOD(m.delilen,2)=0,m.delilen/2+1,CEILING(m.delilen/2)+1))
  263.     SET TEXTMERGE DELIMITERS TO m.ldelimi, m.rdelimi
  264.     
  265.     IF m.bell = "ON"
  266.         SET BELL ON
  267.     ENDIF
  268.     IF m.cursor = "ON"
  269.         SET CURSOR ON
  270.     ELSE
  271.         SET CURSOR OFF
  272.     ENDIF
  273.     IF m.consol = "ON"
  274.         SET CONSOLE ON
  275.     ENDIF
  276.     IF m.escape = "ON"
  277.         SET ESCAPE ON
  278.     ELSE
  279.         SET ESCAPE OFF
  280.     ENDIF
  281.     IF m.print = "ON"
  282.         SET PRINT ON
  283.     ENDIF
  284.     IF m.exact = "OFF"
  285.         SET EXACT OFF
  286.     ENDIF
  287.     IF m.fixed = "OFF"
  288.         SET FIXED OFF
  289.     ENDIF
  290.     SET DECIMALS TO m.deci
  291.     SET MEMOWIDTH TO m.memowidth
  292.     SET DEVICE TO &mdevice
  293.     IF m.trbetween = "ON"
  294.         SET TRBET ON
  295.     ENDIF
  296.     IF m.comp = "ON"
  297.         SET COMPATIBLE ON
  298.     ENDIF
  299.     IF m.talkstate = "ON"
  300.         SET TALK ON
  301.     ENDIF
  302.     SET POINT TO "&mpoint"
  303.     SET MESSAGE TO
  304.     
  305.     ON ERROR &onerror
  306.    
  307.     
  308. *
  309. * VALIDPARAMS - Validate generator parameters.
  310. *
  311. * Description:
  312. * Attempt to open the project database.  If error encountered then
  313. * on error routine takes over and issues 'CANCEL'.  The output file
  314. * cannot be erased, name not known.
  315. *
  316. FUNCTION validparams
  317.     SELECT 0
  318.     m.g_projalias = IIF(USED("projdbf"),"P"+;
  319.         SUBSTR(LOWER(SYS(3)),2,8),"projdbf")
  320.     USE (m.projdbf) ALIAS (m.g_projalias)
  321.     m.g_errlog = stripext(m.projdbf)
  322.     m.g_projpath = SUBSTR(m.projdbf,1,RAT("\",m.projdbf))
  323.     
  324.     IF FCOUNT() <> c_pjxflds
  325.         DO errorhandler WITH "Generator out of date.",;
  326.             LINENO(), c_error_2
  327.         RETURN .F.
  328.     ENDIF
  329.     
  330.     GOTO RECORD m.recno
  331.     
  332.     m.g_outfile = ALLTRIM(SUBSTR(outfile,1,AT(c_null,outfile)-1))
  333.     m.g_outfile = FULLPATH(m.g_outfile, m.g_projpath)
  334.     g_mnxfile[1] = FULLPATH(ALLTRIM(name), m.g_projpath)
  335.     g_mnxfile[2] = basename(g_mnxfile[1])
  336.     
  337. *
  338. * REFRESHPREFS - Refresh comment style and developer preferences.
  339. *
  340. * Description:
  341. * Get the newest preferences for documentation style and developer
  342. * data from the project database.
  343. *
  344. PROCEDURE refreshprefs
  345.     PRIVATE m.start, m.savrecno
  346.     m.savrecno = RECNO()
  347.     LOCATE FOR TYPE = "H"
  348.     IF NOT FOUND ()
  349.         DO errorhandler WITH "Missing header record in "+m.g_projdbf,;
  350.             LINENO(), c_error_2
  351.         GOTO RECORD m.savrecno
  352.         RETURN
  353.     ENDIF
  354.     
  355.     m.g_homedir = ALLTRIM(SUBSTR(homedir,1,AT(c_null,homedir)-1))
  356.     
  357.     m.start = 1
  358.     m.g_devauthor = subdevinfo(m.start,c_authorlen,m.g_devauthor)
  359.     
  360.     m.start = m.start + c_authorlen + 1
  361.     m.g_devcompany = subdevinfo(m.start,c_complen,m.g_devcompany)
  362.     
  363.     m.start = m.start + c_complen + 1
  364.     m.g_devaddress = subdevinfo(m.start,c_addrlen,m.g_devaddress)
  365.     
  366.     m.start = m.start + c_addrlen + 1
  367.     m.g_devcity = subdevinfo(m.start,c_citylen,m.g_devcity)
  368.     
  369.     m.start = m.start + c_citylen + 1
  370.     m.g_devstate = subdevinfo(m.start,c_statlen,m.g_devstate)
  371.     
  372.     m.start = m.start + c_statlen + 1
  373.     m.g_devzip = subdevinfo(m.start,c_ziplen,m.g_devzip)
  374.  
  375.     m.start = m.start + c_ziplen + 1
  376.     m.g_devctry = subdevinfo(m.start,c_countrylen,m.g_devctry)
  377.     
  378.     IF cmntstyle = 0
  379.         m.g_corn1 = "╓"
  380.         m.g_corn2 = "╖"
  381.         m.g_corn3 = "╙"
  382.         m.g_corn4 = "╜"
  383.         m.g_corn5 = "╟"
  384.         m.g_corn6    = "╢"
  385.         m.g_horiz = "─"
  386.         m.g_verti1 = "║"
  387.         m.g_verti2 = "║"
  388.     ENDIF
  389.     GOTO RECORD m.savrecno
  390.     
  391. *
  392. * SUBDEVINFO - Substring the DEVINFO memo filed.
  393. *
  394. FUNCTION subdevinfo
  395.     PARAMETER m.start, m.stop, m.default
  396.     PRIVATE m.string
  397.     m.string = SUBSTR(devinfo, m.start, m.stop+1)
  398.     m.string = SUBSTR(m.string, 1, AT(c_null,m.string)-1)
  399.     RETURN IIF(EMPTY(m.string), m.default, m.string)
  400.     
  401. **
  402. ** Menu Code Generator's Main Module.
  403. **
  404.  
  405. *
  406. * BUILD - Generate code for a menu.
  407. *
  408. * Description:
  409. * Call BUILDENABLE to open .MNX database specified by the user.
  410. * If the above is successfully accomplished, then proceed to generate
  411. * the menu code.  After the menu code is generated, call BUILDDISABLE
  412. * to disable code generation between SET TEXTMERGE ON and
  413. * SET TEXTMERGE OFF.
  414. *
  415. PROCEDURE BUILD
  416.     IF NOT buildenable()
  417.         RETURN
  418.     ENDIF
  419.     DO acttherm WITH "Generating Menu Code..."
  420.     DO updtherm WITH 10
  421.     
  422.     DO HEADER
  423.     DO gensetupcleanup WITH "setup"
  424.     DO definemenu
  425.     DO definepopups
  426.     DO globaldefaults
  427.     DO gensetupcleanup WITH "cleanup"
  428.     DO genprocedures
  429.     
  430.     IF m.g_graphic
  431.         SET MESSAGE TO 'Generation Complete'
  432.     ENDIF
  433.     DO builddisable
  434.     DO updtherm WITH 100
  435.     DO deactthermo
  436.     
  437. *
  438. * BUILDENABLE - Enable code generation.
  439. *
  440. * Description:
  441. * Call opendb to open .MNX database.
  442. * Call openfile to open file to hold the generated program.
  443. * If error(s) encountered in opendb or openfile then don't do
  444. * anything and exit, otherwise enable code generation with the
  445. * SET TEXTMERGE ON command.
  446. *
  447. * Returns:
  448. * .T. on success; .F. on failure
  449. *
  450. FUNCTION buildenable
  451.     PRIVATE m.stat
  452.     m.stat = opendb(g_mnxfile[1]) AND openfile()
  453.     IF m.stat
  454.         SET TEXTMERGE ON
  455.     ENDIF
  456.     RETURN m.stat
  457.     
  458. *
  459. * BUILDDISABLE - Disable code generation.
  460. *
  461. * Description:
  462. * Issue the command SET TEXTMERGE OFF.
  463. * Close the generated menu code output file.
  464. * If anything goes wrong display appropriate message to the user.
  465. *
  466. PROCEDURE builddisable
  467.     SET ESCAPE OFF
  468.     ON ESCAPE
  469.     SET TEXTMERGE OFF
  470.     IF NOT FCLOSE(_TEXT)
  471.         DO errorhandler WITH "Unable to Close the Application File",;
  472.             LINENO(), c_error_2
  473.     ENDIF
  474.     
  475. *
  476. * OPENDB - Prepare database for processing.
  477. *
  478. * Description:
  479. * Attempt to USE a database.  If attempt fails and error is reported
  480. * call ERRORHANDLER routine to display a friendly message.  Return
  481. * with a status of .F..  If attempt succeeds, return with status of .T.
  482. *
  483. * Returns:
  484. * .T. on success; .F. on failure
  485. *
  486. FUNCTION opendb
  487.     PARAMETER m.dbname
  488.     PRIVATE m.dbalias
  489.     ON ERROR DO errorhandler WITH MESSAGE(), LINENO(), c_error_2
  490.     
  491.     m.dbalias = basename(m.dbname)
  492.     IF USED (m.dbalias)
  493.         SELECT (m.dbalias)
  494.         IF RAT(".MNX",DBF())<>0
  495.             g_mnxfile[3] = .F.
  496.             g_mnxfile[4] = m.dbalias
  497.         ELSE
  498.             g_mnxfile[4] = "M"+SUBSTR(LOWER(SYS(3)),2,8)
  499.             SELECT 0
  500.             USE (m.dbname) AGAIN ALIAS (g_mnxfile[4])
  501.             g_mnxfile[3] = .T.
  502.         ENDIF
  503.     ELSE
  504.         IF illegalname(m.dbalias)
  505.             g_mnxfile[4] = "M"+SUBSTR(LOWER(SYS(3)),2,8)
  506.         ELSE
  507.             g_mnxfile[4] = m.dbalias
  508.         ENDIF
  509.         SELECT 0
  510.         USE (m.dbname) AGAIN ALIAS (g_mnxfile[4])
  511.         g_mnxfile[3] = .T.
  512.     ENDIF
  513.     
  514.     IF FCOUNT() <> c_mnxflds
  515.         IF FCOUNT() = c_20mnxflds
  516.             m.g_20mnx = .T.
  517.         ELSE
  518.             DO errorhandler WITH "Menu "+m.dbalias+" is invalid",LINENO(),;
  519.                 c_error_2
  520.             RETURN .F.
  521.         ENDIF
  522.     ELSE
  523.         m.g_20mnx = .F.
  524.     ENDIF
  525.     
  526.     ON ERROR DO errorhandler WITH MESSAGE(), LINENO(), c_error_3
  527.     IF m.g_error = .T.
  528.         RETURN .F.
  529.     ENDIF
  530.     
  531. *
  532. * ILLEGALNAME - Check if default alias will be used when this
  533. *               database is USEd. (i.e., 1st letter is not A-Z,
  534. *                a-z or '_', or any one of ramaining letters is not
  535. *                alphanumeric.)
  536. *
  537. FUNCTION illegalname
  538.     PARAMETER m.menuname
  539.     PRIVATE m.start, m.aschar, m.length
  540.     m.length = LEN(m.menuname)
  541.     m.start  = 0
  542.     IF m.length = 1
  543.         *
  544.         * If length 1, then check if default alias can be used,
  545.         * i.e., name is different than A-J and a-j.
  546.         *
  547.         m.aschar = ASC(m.menuname)
  548.         IF (m.aschar >= 65 AND m.aschar <= 74) OR ;
  549.                 (m.aschar >= 97 AND m.aschar <= 106)
  550.             RETURN .T.
  551.         ENDIF
  552.     ENDIF
  553.     DO WHILE m.start < m.length
  554.         m.start  = m.start + 1
  555.         m.aschar = ASC(SUBSTR(m.menuname, m.start, 1))
  556.         IF m.start<>1 AND (m.aschar >= 48 AND m.aschar <= 57)
  557.             LOOP
  558.         ENDIF
  559.         IF NOT ((m.aschar >= 65 AND m.aschar <= 90) OR ;
  560.                 (m.aschar >= 97 AND m.aschar <= 122) OR m.aschar = 95)
  561.             RETURN .T.
  562.         ENDIF
  563.     ENDDO
  564.     RETURN .F.
  565.         
  566. *
  567. * OPENFILE - Create and open the application output file.
  568. *
  569. * Description:
  570. * Create a file that will hold the generated menu code.
  571. * Open the newly created file.  If error(s) encountered
  572. * at any time issue an error message and return .F.
  573. *
  574. * Returns:
  575. * .T. on success; .F. on failure
  576. *
  577. FUNCTION openfile
  578.     PRIVATE m.msg
  579.     _TEXT = FCREATE(m.g_outfile)
  580.     IF (_TEXT = -1)
  581.         m.msg = "Cannot open file "+m.g_outfile
  582.         DO errorhandler WITH m.msg, LINENO(), c_error_3
  583.         m.g_nohandle = .T.
  584.         RETURN .F.
  585.     ENDIF
  586.     m.g_nohandle = .F.
  587.     
  588. *
  589. * DEFINEMENU - Define main menu and its pads.
  590. *
  591. * Description:
  592. * Issue DEFINE MENU ... command.
  593. * Call a procedure to define all menu pads.
  594. * Call a procedure to generate ON PAD statements when appropriate.
  595. *
  596. PROCEDURE definemenu
  597.     
  598.     IF m.g_graphic
  599.         SET MESSAGE TO 'Generating menu definitions...'
  600.     ENDIF
  601.     DO commentblock WITH "menu"
  602.     SELECT (g_mnxfile[4])
  603.     LOCATE FOR objtype = c_menu
  604.     m.g_location = location
  605.     m.g_padloca  = ALLTRIM(name)
  606.     
  607.     LOCATE FOR objtype = c_submenu AND objcode = c_global
  608.     
  609.     m.g_menucolor = SCHEME
  610.     m.g_menumark  = MARK
  611.     IF m.g_location = c_replace
  612.         \SET SYSMENU TO
  613.         \
  614.     ENDIF
  615.     \SET SYSMENU AUTOMATIC
  616.     \
  617.     
  618.     DO defmenupads
  619.     DO defonpad
  620.     \
  621.     DO updtherm WITH 25
  622.     
  623. *
  624. * DEFMENUPADS - Define all pads for the menu bar.
  625. *
  626. * Description:
  627. * Scan the menu database for all objects of the type item which
  628. * have the levelname=_MSYSMENU.
  629. * For each such item, generate a statement DEFINE PAD... where
  630. * the name of the pad is the contents of NAME field or (if Name
  631. * field is empty) an automatically generated name.
  632. * Call procedures addkey, addskipfor, and mark to generate
  633. * KEY, SKIPFOR, or MARK clauses when appropriate.
  634. *
  635. PROCEDURE defmenupads
  636.     PRIVATE m.padname, m.prompt
  637.     SCAN FOR objtype=c_item AND UPPER(levelname)="_MSYSMENU"
  638.         IF NOT EMPTY(ALLTRIM(name))
  639.             g_pads[VAL(Itemnum)] = name
  640.         ELSE
  641.             g_pads[VAL(Itemnum)] = LOWER(SYS(2015))
  642.         ENDIF
  643.         \DEFINE PAD <<g_pads[VAL(Itemnum)]>> OF _MSYSMENU
  644.         
  645.         IF MOD(VAL(itemnum),25)=0
  646.             DIMENSION g_pads[VAL(Itemnum)+25]
  647.         ENDIF
  648.         m.prompt = SUBSTR(PROMPT,1,LEN(PROMPT))
  649.         \\ PROMPT "<<m.prompt>>"
  650.         \\ COLOR SCHEME <<m.g_menucolor>>
  651.         
  652.         IF m.g_menumark<>c_null AND m.g_menumark<>""
  653.             \\ ;
  654.             \    MARK "<<m.g_menumark>>"
  655.         ENDIF
  656.         
  657.         DO CASE
  658.             CASE m.g_location = c_before
  659.                 \\ ;
  660.                 \    BEFORE <<m.g_padloca>>
  661.             CASE m.g_location = c_after
  662.                 \\ ;
  663.                 \    AFTER
  664.                 IF VAL(itemnum) = 1
  665.                     \\ <<m.g_padloca>>
  666.                 ELSE
  667.                     \\ <<g_pads[VAL(Itemnum)-1]>>
  668.                 ENDIF
  669.         ENDCASE
  670.         
  671.         DO addkey
  672.         DO addskipfor
  673.         DO addmessage
  674.         
  675.     ENDSCAN
  676.     DO updtherm WITH 35
  677.     
  678. *
  679. * DEFONPAD - Generate ON PAD... statements.
  680. *
  681. * Description:
  682. * Generate ON PAD statements for each pad off of the main menu which
  683. * has a submenu associated with it.
  684. * For pads which have no submenus, but there is a command associated
  685. * with them, issue ON SELECTION PAD... statements.  If the code
  686. * associated with a pad is a snippet, then issue a call to the
  687. * generated procedure and place the snippet code in it.
  688. *
  689. PROCEDURE defonpad
  690.     PRIVATE m.padname
  691.     SCAN FOR objtype=c_item AND UPPER(levelname)="_MSYSMENU"
  692.         IF NOT EMPTY(ALLTRIM(name))
  693.             m.padname = name
  694.         ELSE
  695.             m.padname = g_pads[VAL(Itemnum)]
  696.         ENDIF
  697.         SKIP
  698.         IF objtype=c_submenu AND numitems<>0
  699.             \ON PAD <<m.padname>> OF _MSYSMENU
  700.             \\ ACTIVATE POPUP <<LOWER(Name)>>
  701.             SKIP -1
  702.         ELSE
  703.             SKIP -1
  704.             DO onselection WITH "pad", m.padname, '_MSYSMENU'
  705.         ENDIF
  706.     ENDSCAN
  707.     DO updtherm WITH 45
  708.     
  709. *
  710. * DEFINEPOPUPS - Define popups and their bars.
  711. *
  712. * Description:
  713. * Scan the Menu database to find all objecttypes = submenu.
  714. * They all correspond to popups.  For each such object found, issue
  715. * command DEFINE POPUP....  Add MARK, KEY, and SKIP FOR clauses
  716. * if appropriate by calling procedures to handle these tasks.  Call
  717. * procedure Defbars to define all bars of each popup.
  718. *
  719. PROCEDURE definepopups
  720.     PRIVATE m.savrecno, m.popname, m.sch
  721.     IF m.g_graphic
  722.         SET MESSAGE TO 'Generating popup definitions...'
  723.     ENDIF
  724.     SCAN FOR objtype=c_submenu AND UPPER(levelname)<>"_MSYSMENU" ;
  725.             AND numitems <> 0
  726.         
  727.         m.savrecno = RECNO()
  728.         m.popname  = ALLTRIM(LOWER(levelname))
  729.         m.sch      = SCHEME
  730.         
  731.         \DEFINE POPUP <<LOWER(Name)>> MARGIN RELATIVE SHADOW
  732.         \\ COLOR SCHEME <<m.sch>>
  733.         
  734.         DO addmark
  735.         DO addkey
  736.         DO defbars WITH m.popname, numitems
  737.         DO defonbar WITH m.popname
  738.         \
  739.         GOTO RECORD m.savrecno
  740.     ENDSCAN
  741.     DO updtherm WITH 65
  742.     
  743. *
  744. * DEFBARS - Define bars for each popup.
  745. *
  746. * Description:
  747. * Scan the menu database for all objects of the type item whose
  748. * name equals to the current popup name.
  749. * For each such item, generate a statement DEFINE BAR....
  750. * Call procedures addkey, addskipfor, and addmark to generate
  751. * KEY, SKIPFOR, or MARK clauses when appropriate.
  752. *
  753. PROCEDURE defbars
  754.     PARAMETER m.popname, m.howmany, m.name
  755.     PRIVATE m.itemno, m.prompt
  756.     SCAN FOR objtype=c_item AND LOWER(levelname)=m.popname
  757.         m.itemno = ALLTRIM(itemnum)
  758.         
  759.         IF NOT EMPTY(ALLTRIM(name))
  760.             m.name = name
  761.             \DEFINE BAR <<m.name>> OF <<LOWER(m.popname)>>
  762.         ELSE
  763.             \DEFINE BAR <<m.itemno>> OF <<LOWER(m.popname)>>
  764.         ENDIF
  765.         m.prompt = SUBSTR(PROMPT, 1,LEN(PROMPT))
  766.         \\ PROMPT "<<m.prompt>>"
  767.         
  768.         DO addmark
  769.         DO addkey
  770.         DO addskipfor
  771.         DO addmessage
  772.         
  773.         IF VAL(m.itemno)=m.howmany
  774.             DO updtherm WITH 75
  775.             RETURN
  776.         ENDIF
  777.     ENDSCAN
  778.     
  779. *
  780. * DEFONBAR - Generate ON BAR... statements.
  781. *
  782. * Description:
  783. * Generate ON BAR statements for each popup.
  784. * For bars which have no submenus, but there is a command associated
  785. * with them, issue ON SELECTION BAR... statements.  If a snippet is
  786. * associated with the code then generate a call statement to the
  787. * generated procedure containing the snippet code.
  788. *
  789. PROCEDURE defonbar
  790.     PARAMETER m.popname
  791.     PRIVATE m.itemno
  792.     SCAN FOR objtype=c_item AND LOWER(levelname)=m.popname
  793.         IF EMPTY(ALLTRIM(name))
  794.             m.itemno = ALLTRIM(itemnum)
  795.         ELSE
  796.             m.itemno = name
  797.         ENDIF
  798.         SKIP
  799.         IF objtype=c_submenu AND numitems<>0
  800.             \ON BAR <<m.itemno>> OF <<LOWER(m.popname)>>
  801.             \\ ACTIVATE POPUP <<LOWER(Name)>>
  802.             SKIP -1
  803.         ELSE
  804.             SKIP -1
  805.             DO onselection WITH "BAR", m.itemno, m.popname
  806.         ENDIF
  807.     ENDSCAN
  808.     DO updtherm WITH 85
  809.     
  810. *
  811. * GLOBALDEFAULTS - Generate global default statements
  812. *
  813. * Description:
  814. * Search the menu database for information needed to generate any of
  815. * the following commands:
  816. * ON SELECTION MENU <name> DO <action>
  817. * ON SELECTION POPUP ALL DO <action>
  818. * ON SELECTION POPUP <name> DO <action>
  819. * It is possible that none of the above mentioned statements will be
  820. * generated.  It is also possible that the action is a snippet of
  821. * code and a call to the generated procedure containing the snippet
  822. * will be generated.
  823. *
  824. * First try to generate ON SELECTION MENU...
  825. * Then try to generate ON POPUP ALL...
  826. * Lastly, try to generate ON SELECTION POPUP...
  827. *
  828. PROCEDURE globaldefaults
  829.     LOCATE FOR objtype = c_menu
  830.     m.mrk = MARK
  831.     IF FOUND() AND MARK <> ""
  832.         IF MARK = c_null
  833.             \SET MARK OF MENU _MSYSMENU TO " "
  834.         ELSE
  835.             \SET MARK OF MENU _MSYSMENU TO "<<Mark>>"
  836.         ENDIF
  837.     ENDIF
  838.     IF FOUND() AND NOT EMPTY(PROCEDURE)
  839.         \ON SELECTION MENU _MSYSMENU
  840.         DO genproccall
  841.     ENDIF
  842.     LOCATE FOR objtype = c_submenu AND objcode = c_global
  843.     IF FOUND() AND NOT EMPTY(PROCEDURE)
  844.         \ON SELECTION POPUP ALL
  845.         DO genproccall
  846.     ENDIF
  847.     SCAN FOR (objtype=c_submenu AND UPPER(levelname)<>"_MSYSMENU";
  848.             AND NOT EMPTY(PROCEDURE))
  849.         \ON SELECTION POPUP <<ALLTRIM(LOWER(Levelname))>>
  850.         DO genproccall
  851.     ENDSCAN
  852.     DO updtherm WITH 95
  853.     
  854. **
  855. ** Subroutines for processing menu clause options.
  856. **
  857.  
  858. *
  859. * ADDMARK - Generate a MARK clause whenever appropriate.
  860. *
  861. * Description:
  862. * Add a MARK clause to the current PAD or BAR definition.
  863. * If a field named Mark is not empty, then add the continuation
  864. * character, ";", to the previous line, and then add the MARK... clause.
  865. *
  866. PROCEDURE addmark
  867.     IF MARK<>c_null AND MARK<>""
  868.         \\ ;
  869.             \    MARK "<<Mark>>"
  870.     ENDIF
  871.     
  872. *
  873. * ADDKEY - Generate KEY... clause whenever appropriate.
  874. *
  875. * Description:
  876. * Add a KEY clause to the current PAD or BAR definition.
  877. * If a field named Keyname is not empty, then add the continuation
  878. * character, ";", to the previous line, and then add the KEY... clause.
  879. *
  880. PROCEDURE addkey
  881.     IF NOT EMPTY(keyname)
  882.         \\ ;
  883.         \    KEY <<Keyname>>, "<<Keylabel>>"
  884.     ENDIF
  885.     
  886. *
  887. * ADDSKIPFOR - Generate SKIP FOR... clause whenever appropriate.
  888. *
  889. * Description:
  890. * Add a ADDSKIPFOR clause to the current PAD or BAR definition.
  891. * If a field named Addskipfor is not empty, then add the continuation
  892. * character, ";", to the previous line, and then add the SKIP FOR...
  893. * clause.
  894. *
  895. PROCEDURE addskipfor
  896.     PRIVATE m.skip
  897.     m.skip = skipfor
  898.     IF NOT EMPTY(skipfor)
  899.         \\ ;
  900.         \    SKIP FOR <<m.skip>>
  901.     ENDIF
  902.     
  903. *
  904. * ADDMESSAGE - Generate MESSAGE clause whenever appropriate.
  905. *
  906. * Description:
  907. * Add a MESSAGE clause to the current PAD or BAR definition.
  908. * If a field named MESSAGE is not empty and it is not a 2.0 menu,
  909. * then add the continuation character, ";", to the previous line,
  910. * and then add the MESSAGE clause.
  911. *
  912. PROCEDURE addmessage
  913.     
  914.     IF !m.g_20mnx AND NOT EMPTY(MESSAGE)
  915.         \\ ;
  916.         \    MESSAGE <<Message>>
  917.     ENDIF
  918.         
  919. *
  920. * HEADER - Generate generated program's header.
  921. *
  922. * Description:
  923. * As a part of the automatically generated program's header generate
  924. * program name, name of the author of the program, copyright notice,
  925. * company name and address, and the word 'Description:' which will be
  926. * followed with a short description of the generated code.
  927. *
  928. PROCEDURE HEADER
  929.     \\*       <<m.g_corn1>><<REPLICATE(m.g_horiz,57)>><<m.g_corn2>>
  930.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  931.     \*       <<m.g_verti1>> <<DATE()>>
  932.     \\<<PADC(UPPER(ALLTRIM(strippath(m.g_outfile))),IIF(SET("CENTURY")="ON",35,37))," ")>>
  933.     \\ <<TIME()>>  <<m.g_verti2>>
  934.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  935.     \*       <<m.g_corn5>><<REPLICATE(m.g_horiz,57)>><<m.g_corn6>>
  936.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  937.     \*       <<m.g_verti1>> <<m.g_devauthor>>
  938.     \\<<REPLICATE(" ",56-LEN(m.g_devauthor))>><<m.g_verti2>>
  939.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  940.     \*       <<m.g_verti1>>
  941.     \\ Copyright (c) <<YEAR(DATE())>>
  942.     IF LEN(ALLTRIM(m.g_devcompany)) <= 36
  943.         \\ <<ALLTRIM(m.g_devcompany)>>
  944.         \\<<REPLICATE(" ",37-LEN(ALLTRIM(m.g_devcompany)))>>
  945.         \\<<m.g_verti2>>
  946.     ELSE
  947.         \\ <<REPLICATE(" ",37)>><<m.g_verti2>>
  948.         \*       <<m.g_verti1>> <<m.g_devcompany>>
  949.         \\<<REPLICATE(" ",56-LEN(m.g_devcompany))>><<m.g_verti2>>
  950.     ENDIF
  951.     
  952.     \*       <<m.g_verti1>> <<m.g_devaddress>>
  953.     \\<<REPLICATE(" ",56-LEN(m.g_devaddress))>><<m.g_verti2>>
  954.     
  955.     \*       <<m.g_verti1>> <<ALLTRIM(m.g_devcity)>>, <<m.g_devstate>>
  956.     \\  <<ALLTRIM(m.g_devzip)>>
  957.     \\<<REPLICATE(" ",50-(LEN(ALLTRIM(m.g_devcity)+ALLTRIM(m.g_devzip))))>>
  958.     \\<<m.g_verti2>>
  959.     
  960.     IF !INLIST(ALLTRIM(UPPER(m.g_devctry)),"USA","COUNTRY") AND !EMPTY(m.g_devctry)
  961.        \*       <<m.g_verti1>> <<ALLTRIM(m.g_devctry)>>
  962.        \\<<REPLICATE(" ",50-(LEN(ALLTRIM(m.g_devctry))))>>
  963.        \\<<m.g_verti2>>
  964.     ENDIF
  965.         
  966.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  967.     \*       <<m.g_verti1>> Description:
  968.     \\                                            <<m.g_verti2>>
  969.     \*       <<m.g_verti1>>
  970.     \\ This program was automatically generated by GENMENU.
  971.     \\    <<m.g_verti2>>
  972.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  973.     \*       <<m.g_corn3>><<REPLICATE(m.g_horiz,57)>><<m.g_corn4>>
  974.     \
  975.     
  976. *
  977. * GENFUNCHEADER - Generate Comment for Function/Procedure.
  978. *
  979. PROCEDURE genfuncheader
  980.     PARAMETER m.procname
  981.     PRIVATE m.place, m.prompt
  982.     m.g_snippcnt = m.g_snippcnt + 1
  983.     DO CASE
  984.         CASE objtype = c_menu
  985.             m.place = "ON SELECTION MENU _MSYSMENU"
  986.         CASE objtype = c_submenu AND objcode = c_global
  987.             m.place = "ON SELECTION POPUP ALL"
  988.         CASE objtype = c_submenu AND objcode <> c_global
  989.             m.place = "ON SELECTION POPUP "+LOWER(ALLTRIM(name))
  990.         CASE objtype = c_item AND UPPER(levelname) = "_MSYSMENU"
  991.             m.place = "ON SELECTION PAD "
  992.         CASE objtype = c_item AND UPPER(levelname) <> "_MSYSMENU"
  993.             m.place = "ON SELECTION BAR "+ALLTRIM(itemnum)+;
  994.                 +" OF POPUP "+LOWER(ALLTRIM(levelname))
  995.         OTHERWISE
  996.             m.place = ""
  997.     ENDCASE
  998.     \
  999.     \*       <<m.g_corn1>><<REPLICATE(m.g_horiz,57)>><<m.g_corn2>>
  1000.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1001.     \*       <<m.g_verti1>> <<UPPER(PADR(m.procname,10))>>  <<m.place>>
  1002.     \\<<REPLICATE(" ",44-LEN(m.place))>><<m.g_verti2>>
  1003.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1004.     \*       <<m.g_verti1>> Procedure Origin:
  1005.     \\<<REPLICATE(" ",39)>><<m.g_verti2>>
  1006.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1007.     \*       <<m.g_verti1>> From Menu:
  1008.     \\  <<ALLTRIM(strippath(m.g_outfile))>>
  1009.     \\,            Record:  <<STR(RECNO(),3)>>
  1010.     \\<<REPLICATE(" ",22-LEN(ALLTRIM(strippath(m.g_outfile))+STR(RECNO(),3))))>>
  1011.     \\<<m.g_verti2>>
  1012.     \*       <<m.g_verti1>> Called By:  <<m.place>>
  1013.     \\<<REPLICATE(" ",44-LEN(m.place))>><<m.g_verti2>>
  1014.     IF NOT EMPTY(PROMPT)
  1015.         m.prompt = removemeta()
  1016.         \*       <<m.g_verti1>> Prompt:     <<ALLTRIM(m.prompt)>>
  1017.         \\<<REPLICATE(" ",44-LEN(ALLTRIM(m.prompt)))>><<m.g_verti2>>
  1018.     ENDIF
  1019.     \*       <<m.g_verti1>> Snippet:
  1020.     \\    <<ALLTRIM(STR(m.g_snippcnt,2))>>
  1021.     \\<<REPLICATE(" ",44-LEN(ALLTRIM(STR(m.g_snippcnt,2))))>><<m.g_verti2>>
  1022.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1023.     \*       <<m.g_corn3>><<REPLICATE(m.g_horiz,57)>><<m.g_corn4>>
  1024.     \*
  1025.     
  1026. *
  1027. * REMOVEMETA - Remove meta characters for documentation.
  1028. *
  1029. FUNCTION removemeta
  1030.     PRIVATE m.prompt, m.hotkey
  1031.     m.prompt = PROMPT
  1032.     m.hotkey = AT("\<",m.prompt)
  1033.     
  1034.     IF m.hotkey <> 0
  1035.         m.prompt = STUFF(m.prompt,m.hotkey,2,"")
  1036.     ENDIF
  1037.     
  1038.     m.disabl = AT("\",m.prompt)
  1039.     IF m.disabl <> 0
  1040.         m.prompt = STUFF(m.prompt,m.disabl,1,"")
  1041.     ENDIF
  1042.     RETURN m.prompt
  1043.     
  1044. *
  1045. * COMMENTBLOCK - Generate a comment block.
  1046. *
  1047. PROCEDURE commentblock
  1048.     PARAMETER m.snippet
  1049.     \
  1050.     \*       <<m.g_corn1>><<REPLICATE(m.g_horiz,57)>><<m.g_corn2>>
  1051.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1052.     DO CASE
  1053.         CASE m.snippet == "setup"
  1054.             \*       <<m.g_verti1>>
  1055.             \\ <<PADC(" Setup Code",56," ")>>
  1056.         CASE m.snippet == "cleanup"
  1057.             \*       <<m.g_verti1>>
  1058.             \\ <<PADC(" Cleanup Code & Procedures",56," ")>>
  1059.         CASE m.snippet == "init"
  1060.             \*       <<m.g_verti1>>
  1061.             \\ <<PADC(" Initializing Code",56," ")>>
  1062.         CASE m.snippet == "menu"
  1063.             \*       <<m.g_verti1>>
  1064.             \\ <<PADC(" Menu Definition",56," ")>>
  1065.     ENDCASE
  1066.     \\<<m.g_verti2>>
  1067.     \*       <<m.g_verti1>><<REPLICATE(" ",57)>><<m.g_verti2>>
  1068.     \*       <<m.g_corn3>><<REPLICATE(m.g_horiz,57)>><<m.g_corn4>>
  1069.     \*
  1070.     \
  1071.     
  1072. **
  1073. ** Supporting routines
  1074. **
  1075.  
  1076. *
  1077. * ONSELECTION - Generate ON SELECTION... statements for menu items.
  1078. *
  1079. * Description:
  1080. * For pads and bars which have no submenu associated with them but
  1081. * instead have a non-empty Command field in the database, issue
  1082. * the ON SELECTION <command> statements.  If a snippet is associated
  1083. * with a pad then issue a call statement to the generated procedure
  1084. * containing the snippet.  Generated snippet procedure will be
  1085. * appended to the end of the output file.
  1086. *
  1087. PROCEDURE onselection
  1088.     PARAMETER m.which, m.name, m.ofname, m.commd
  1089.     PRIVATE m.trimname, m.basename
  1090.     IF EMPTY(PROCEDURE) AND EMPTY(COMMAND)
  1091.         RETURN
  1092.     ENDIF
  1093.     DO CASE
  1094.         CASE m.which == "pad"
  1095.             \ON SELECTION PAD <<m.name>>
  1096.         CASE m.which == "BAR"
  1097.             \ON SELECTION <<m.which+" "+m.name>>
  1098.     ENDCASE
  1099.     \\ OF <<m.ofname>>
  1100.     IF objcode = c_proc
  1101.         DO gensnippname
  1102.         m.trimname = SYS(2014,m.g_outfile,m.g_homedir)
  1103.         m.trimname = stripext(m.trimname)
  1104.         m.basename = basename(m.trimname)
  1105.         \\ ;
  1106.         \    DO <<g_snippets[g_nsnippets,1]>> ;
  1107.         \    IN LOCFILE("<<m.trimname>>"
  1108.         \\ ,"MPX;MPR|FXP;PRG"
  1109.         \\ ,"Where is <<m.basename>>?")
  1110.     ELSE
  1111.         m.commd = COMMAND
  1112.         \\ <<m.commd>>
  1113.     ENDIF
  1114.     
  1115. *
  1116. * GENSNIPPNAME - Generate a unique name for snippet procedure.
  1117. *
  1118. * Description:
  1119. * Lookup the #NAME name of this snippet, or alternatively 
  1120. * provide a unique name for a snippet of code associated with the
  1121. * generated menu.  Save this name in an array g_snippets.
  1122. *
  1123. PROCEDURE gensnippname
  1124.     g_nsnippets = g_nsnippets + 1
  1125.     g_snippets[g_nsnippets,1] = getcname(procedure)
  1126.     g_snippets[g_nsnippets,2] = RECNO()
  1127.     
  1128.     IF MOD(g_nsnippets,25) = 0
  1129.         DIMENSION g_snippets [g_nsnippets+25,2]
  1130.     ENDIF
  1131.     
  1132. *
  1133. * GENPROCCALL - Generate a call statement to snippet procedure.
  1134. *
  1135. * Description:
  1136. * Generate a call to the snippet procedure in the menu definition
  1137. * code.
  1138. *
  1139. PROCEDURE genproccall
  1140.     PRIVATE m.trimname, m.basename, m.proc
  1141.     IF singleline()
  1142.         m.proc = PROCEDURE
  1143.         \\ <<MLINE(m.proc,1)>>
  1144.     ELSE
  1145.         DO gensnippname
  1146.         m.trimname = SYS(2014,m.g_outfile,m.g_homedir)
  1147.         m.trimname = stripext(m.trimname)
  1148.         m.basename = basename(m.trimname)
  1149.         \\ ;
  1150.         \    DO <<g_snippets[m.g_nsnippets,1]>> ;
  1151.         \    IN LOCFILE("<<m.trimname>>"
  1152.         \\ ,"MPX;MPR|FXP;PRG"
  1153.         \\ ,"Where is <<m.basename>>?")
  1154.     ENDIF
  1155.     
  1156. *
  1157. * SINGLELINE - Determine if Memo contains only one line.
  1158. *
  1159. * Description:
  1160. * This procedure is used to decide if an ON SELECTION... statement
  1161. * and a snippet procedure will be needed (i.e., if more than one
  1162. * line of snippet code then its a snippet, otherwise its a command)
  1163. *
  1164. FUNCTION singleline
  1165.     PRIVATE m.size, m.i
  1166.     m.size = MEMLINES(PROCEDURE)
  1167.     IF m.size = 1
  1168.         RETURN .T.
  1169.     ENDIF
  1170.     m.i = m.size
  1171.     DO WHILE m.i > 1
  1172.         m.line = MLINE(PROCEDURE, m.i)
  1173.         IF NOT EMPTY(m.line)
  1174.             RETURN .F.
  1175.         ENDIF
  1176.         m.i = m.i - 1
  1177.     ENDDO
  1178.     
  1179. *
  1180. * GENPROCEDURES - Generate procedure/snippet code.
  1181. *
  1182. * Description:
  1183. * Generate 'PROCEDURE procedurename' statement and its body.
  1184. *
  1185. PROCEDURE genprocedures
  1186.     PRIVATE m.i
  1187.     IF m.g_graphic
  1188.         SET MESSAGE TO 'Generating procedures...'
  1189.     ENDIF
  1190.     FOR m.i = 1 TO m.g_nsnippets
  1191.         GOTO RECORD (g_snippets[m.i,2])
  1192.         DO genfuncheader WITH g_snippets[m.i,1]
  1193.         \PROCEDURE <<g_snippets[m.i,1]>>
  1194.         DO writecode WITH procedure
  1195.         \
  1196.     ENDFOR
  1197.     
  1198. *
  1199. * WRITECODE - Write contents of a memo to a low level file.
  1200. *
  1201. * Description:
  1202. * Receive a memo field as a parameter and write its contents out
  1203. * to the currently opened low level file whose handle is stored
  1204. * in the system memory variable _TEXT.  Contents of the system
  1205. * memory variable _pretext will affect the positioning of the
  1206. * generated text.
  1207. *
  1208. PROCEDURE writecode
  1209.     PARAMETER m.memo
  1210.     PRIVATE m.lines, m.i, m.thisline
  1211.     m.lines = MEMLINES(m.memo)
  1212.     _MLINE = 0
  1213.     FOR m.i = 1 TO m.lines
  1214.         m.thisline = MLINE(m.memo, 1, _MLINE)
  1215.         IF LEFT(UPPER(LTRIM(m.thisline)),5) == "#INSE"   && #INSERT
  1216.            DO GenInsertCode WITH m.thisline
  1217.         ELSE
  1218.            IF LEFT(UPPER(LTRIM(m.thisline)),5) <> "#NAME"
  1219.               \<<m.thisline>>
  1220.            ENDIF
  1221.         ENDIF
  1222.     ENDFOR
  1223.     
  1224. *
  1225. * GENSETUPCLEANUP - Generate setup/cleanup code.
  1226. *
  1227. PROCEDURE gensetupcleanup
  1228.     PARAMETER m.choice
  1229.     LOCATE FOR objtype = c_menu
  1230.     DO CASE
  1231.         CASE m.choice == "setup"
  1232.             IF EMPTY(setup)
  1233.                 RETURN
  1234.             ENDIF
  1235.             IF m.g_graphic
  1236.                 SET MESSAGE TO 'Generating Menu Setup Code...'
  1237.             ENDIF
  1238.             DO commentblock WITH m.choice
  1239.             DO writecode WITH setup
  1240.         CASE m.choice == "cleanup"
  1241.             IF EMPTY(cleanup)
  1242.                 RETURN
  1243.             ENDIF
  1244.             IF m.g_graphic
  1245.                 SET MESSAGE TO 'Generating Menu Cleanup Code...'
  1246.             ENDIF
  1247.             DO commentblock WITH m.choice
  1248.             DO writecode WITH cleanup
  1249.     ENDCASE
  1250.     
  1251. *
  1252. * STRIPEXT - Strip the extension from a file name.
  1253. *
  1254. * Description:
  1255. * Use the algorithm employed by FoxPRO itself to strip a
  1256. * file of an extension (if any): Find the rightmost dot in
  1257. * the filename.  If this dot occurs to the right of a "\"
  1258. * or ":", then treat everything from the dot rightward
  1259. * as an extension.  Of course, if we found no dot,
  1260. * we just hand back the filename unchanged.
  1261. *
  1262. * Parameters:
  1263. * filename - character string representing a file name
  1264. *
  1265. * Return value:
  1266. * The string "filename" with any extension removed
  1267. *
  1268. FUNCTION stripext
  1269.     PARAMETER m.filename
  1270.     PRIVATE m.dotpos, m.terminator
  1271.     m.dotpos = RAT(".", m.filename)
  1272.     m.terminator = MAX(RAT("\", m.filename), RAT(":", m.filename))
  1273.     IF m.dotpos > m.terminator
  1274.         m.filename = LEFT(m.filename, m.dotpos-1)
  1275.     ENDIF
  1276.     RETURN m.filename
  1277.     
  1278. *
  1279. * STRIPPATH - Strip the path from a file name.
  1280. *
  1281. * Description:
  1282. * Find positions of backslash in the name of the file.  If there is one
  1283. * take everything to the right of its position and make it the new file
  1284. * name.  If there is no slash look for colon.  Again if found, take
  1285. * everything to the right of it as the new name.  If neither slash
  1286. * nor colon are found then return the name unchanged.
  1287. *
  1288. * Parameters:
  1289. * filename - character string representing a file name
  1290. *
  1291. * Return value:
  1292. * The string "filename" with any path removed
  1293. *
  1294. FUNCTION strippath
  1295.     PARAMETER m.filename
  1296.     PRIVATE m.slashpos, m.namelen, m.colonpos
  1297.     m.slashpos = RAT("\", m.filename)
  1298.     IF m.slashpos > 0
  1299.         m.namelen  = LEN(m.filename) - m.slashpos
  1300.         m.filename = RIGHT(m.filename, m.namelen)
  1301.     ELSE
  1302.         m.colonpos = RAT(":", m.filename)
  1303.         IF m.colonpos > 0
  1304.             m.namelen  = LEN(m.filename) - m.colonpos
  1305.             m.filename = RIGHT(m.filename, m.namelen)
  1306.         ENDIF
  1307.     ENDIF
  1308.     RETURN m.filename
  1309.     
  1310. *
  1311. * BASENAME - returns strippath(stripext(filespec))
  1312. *
  1313. FUNCTION basename
  1314.     PARAMETER m.filespec
  1315.     RETURN strippath(stripext(m.filespec))
  1316.  
  1317. *
  1318. * GENINSERTCODE - Emit code from the #insert file, if any
  1319. PROCEDURE GenInsertCode
  1320. PARAMETER strg
  1321. PRIVATE m.word1, m.filname, m.ins_fp, m.buffer
  1322.  
  1323. IF UPPER(LEFT(LTRIM(m.strg),5)) == "#INSE"
  1324.    m.word1 = wordnum(m.strg,1)
  1325.    m.filname = SUBSTR(m.strg,LEN(m.word1)+1)
  1326.    m.filname = ALLTRIM(CHRTRAN(m.filname,CHR(9),""))
  1327.    
  1328.    * Bail out if we can't find the file either explicitly or on the DOS path
  1329.    IF !FILE(m.filname)
  1330.       filname = FULLPATH(m.filname,1)
  1331.       IF !FILE(m.filname)
  1332.          \*Insert file <<m.filname>> could not be found
  1333.          RETURN
  1334.       ENDIF
  1335.    ENDIF
  1336.    
  1337.    ins_fp = FOPEN(m.filname)
  1338.    IF ins_fp > 0
  1339.       \* Inserted from <<strippath(m.filname)>>
  1340.       DO WHILE !feof(ins_fp)
  1341.          m.buffer = fgets(ins_fp)
  1342.          \<<m.buffer>>
  1343.       ENDDO
  1344.       =fclose(m.ins_fp)
  1345.       \* End of inserted lines
  1346.    ENDIF
  1347. ENDIF
  1348.     
  1349. **
  1350. ** Code Associated with the Thermometer
  1351. **
  1352.  
  1353. *
  1354. * ACTTHERM(<text>) - Activate thermometer.
  1355. *
  1356. * Description:
  1357. * Activates thermometer.  Update the thermometer with UPDTHERM().
  1358. * Thermometer window is named "thermometer."  Be sure to RELEASE
  1359. * this window when done with thermometer.  Creates the global
  1360. * m.g_thermwidth.
  1361. *
  1362. PROCEDURE acttherm
  1363.     PARAMETER m.text
  1364.     PRIVATE m.prompt
  1365.     
  1366.     IF m.g_graphic
  1367.         m.prompt = LOWER(m.g_outfile)
  1368.         IF txtwidth(m.prompt, c_dlgface, c_dlgsize, c_dlgstyle) > 43
  1369.             DO WHILE txtwidth(m.prompt+"...", c_dlgface, c_dlgsize, c_dlgstyle) > 43
  1370.                 m.prompt = LEFT(m.prompt, LEN(m.prompt)-1)
  1371.             ENDDO
  1372.             m.prompt = m.prompt + "..."
  1373.         ENDIF
  1374.         
  1375.         DEFINE WINDOW thermomete ;
  1376.             AT  INT((SROW() - (( 5.615 * ;
  1377.             fontmetric(1, c_dlgface, c_dlgsize, c_dlgstyle )) / ;
  1378.             fontmetric(1, wfont(1,""), wfont(2,""), wfont(3,"")))) / 2), ;
  1379.             INT((SCOL() - (( 63.833 * ;
  1380.             fontmetric(6, c_dlgface, c_dlgsize, c_dlgstyle )) / ;
  1381.             fontmetric(6, wfont(1,""), wfont(2,""), wfont(3,"")))) / 2) ;
  1382.             SIZE 5.615,63.833 ;
  1383.             font c_dlgface, c_dlgsize ;
  1384.             STYLE c_dlgstyle ;
  1385.             NOFLOAT ;
  1386.             NOCLOSE ;
  1387.             NONE ;
  1388.             COLOR rgb(0, 0, 0, 192, 192, 192)
  1389.         
  1390.         ACTIVATE WINDOW thermomete NOSHOW
  1391.         
  1392.         @ 0.5,3 SAY m.text font c_dlgface, c_dlgsize STYLE c_dlgstyle
  1393.         @ 1.5,3 SAY m.prompt font c_dlgface, c_dlgsize STYLE c_dlgstyle
  1394.         @ 0.000,0.000 TO 0.000,63.833 ;
  1395.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1396.         @ 0.000,0.000 TO 5.615,0.000 ;
  1397.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1398.         @ 0.385,0.667 TO 5.231,0.667 ;
  1399.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1400.         @ 0.308,0.667 TO 0.308,63.167 ;
  1401.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1402.         @ 0.385,63.000 TO 5.308,63.000 ;
  1403.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1404.         @ 5.231,0.667 TO 5.231,63.167 ;
  1405.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1406.         @ 5.538,0.000 TO 5.538,63.833 ;
  1407.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1408.         @ 0.000,63.667 TO 5.615,63.667 ;
  1409.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1410.         @ 3.000,3.333 TO 4.231,3.333 ;
  1411.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1412.         @ 3.000,60.333 TO 4.308,60.333 ;
  1413.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1414.         @ 3.000,3.333 TO 3.000,60.333 ;
  1415.             COLOR rgb(128, 128, 128, 128, 128, 128)
  1416.         @ 4.231,3.333 TO 4.231,60.500 ;
  1417.             COLOR rgb(255, 255, 255, 255, 255, 255)
  1418.         m.g_thermwidth = 56.269
  1419.         
  1420.         SHOW WINDOW thermomete TOP
  1421.     ELSE
  1422.         m.prompt = SUBSTR(SYS(2014,m.g_outfile),1,48)+;
  1423.             IIF(LEN(m.g_outfile)>48,"...","")
  1424.         
  1425.         DEFINE WINDOW thermomete;
  1426.             FROM INT((SROW()-6)/2), INT((SCOL()-57)/2) ;
  1427.             TO INT((SROW()-6)/2) + 6, INT((SCOL()-57)/2) + 57;
  1428.             DOUBLE COLOR SCHEME 5
  1429.         
  1430.         ACTIVATE WINDOW thermomete NOSHOW
  1431.         
  1432.         m.g_thermwidth = 50
  1433.         @ 0,3 SAY m.text
  1434.         @ 1,3 SAY UPPER(m.prompt)
  1435.         @ 2,1 TO 4,m.g_thermwidth+4 &g_boxstrg
  1436.         
  1437.         SHOW WINDOW thermomete TOP
  1438.     ENDIF
  1439.     
  1440. *
  1441. * UPDTHERM(<percent>) - Update thermometer.
  1442. *
  1443. PROCEDURE updtherm
  1444.     PARAMETER m.percent
  1445.     PRIVATE m.nblocks, m.percent
  1446.     ACTIVATE WINDOW thermomete
  1447.     m.nblocks = (m.percent/100) * (m.g_thermwidth)
  1448.     IF m.g_graphic
  1449.         @ 3.000,3.333 TO 4.231,m.nblocks + 3.333 ;
  1450.             pattern 1 COLOR rgb(128, 128, 128, 128, 128, 128)
  1451.     ELSE
  1452.         @ 3,3 SAY REPLICATE("█",m.nblocks)
  1453.     ENDIF
  1454.     
  1455. *
  1456. * DEACTTHERMO - Deactivate and Release thermometer window.
  1457. *
  1458. PROCEDURE deactthermo
  1459.     RELEASE WINDOW thermomete
  1460.     
  1461. **
  1462. ** Error Handling Code
  1463. **
  1464.  
  1465. *
  1466. * ERRORHANDLER - Error Processing Center.
  1467. *
  1468. PROCEDURE errorhandler
  1469.     PARAMETERS m.messg, m.lineno, m.code
  1470.     IF ERROR() = 22
  1471.         ON ERROR &onerror
  1472.         DO cleanup
  1473.         CANCEL
  1474.     ENDIF
  1475.     
  1476.     DO CASE
  1477.         CASE m.code == "Minor"
  1478.             DO errlog WITH m.messg, m.lineno
  1479.             m.g_status = 1
  1480.         CASE m.code == "Serious"
  1481.             DO errlog  WITH m.messg, m.lineno
  1482.             DO errshow WITH m.messg, m.lineno
  1483.             m.g_error = .T.
  1484.             m.g_status = 2
  1485.             ON ERROR
  1486.         CASE m.code == "Fatal"
  1487.             IF NOT m.g_nohandle
  1488.                 DO errlog  WITH m.messg, m.lineno
  1489.             ENDIF
  1490.             DO errshow WITH m.messg, m.lineno
  1491.             IF WEXIST("Thermomete") AND WVISIBLE("Thermomete")
  1492.                 RELEASE WINDOW thermometer
  1493.             ENDIF
  1494.             ON ERROR
  1495.             DO cleanup
  1496.             CANCEL
  1497.     ENDCASE
  1498.     
  1499. *
  1500. * ESCHANDLER - Escape handler.
  1501. *
  1502. PROCEDURE eschandler
  1503.     ON ERROR
  1504.     WAIT WINDOW "Generation process stopped." NOWAIT
  1505.     DO builddisable
  1506.     IF m.g_status > 0
  1507.         ERASE (m.g_outfile)
  1508.     ENDIF
  1509.     IF WEXIST("Thermomete") AND WVISIBLE("Thermomete")
  1510.         RELEASE WINDOW thermometer
  1511.     ENDIF
  1512.     DO cleanup
  1513.     CANCEL
  1514.     
  1515. *
  1516. * ERRLOG - Insert error message into the error log.
  1517. *
  1518. PROCEDURE errlog
  1519.     PARAMETER m.messg, m.lineno
  1520.     PRIVATE m.savehandle
  1521.     m.savehandle = _TEXT
  1522.     DO openerrfile
  1523.     SET CONSOLE OFF
  1524.     
  1525.     \\GENERATOR: <<ALLTRIM(m.messg)>>
  1526.     IF NOT EMPTY(m.lineno)
  1527.         \\ LINE NUMBER: <<m.lineno>>
  1528.     ENDIF
  1529.     \
  1530.     = FCLOSE(_TEXT)
  1531.     _TEXT = m.savehandle
  1532.     
  1533. *
  1534. * ERRSHOW - Display error message in the alert box.
  1535. *
  1536. PROCEDURE errshow
  1537.     PARAMETER m.msg, m.lineno
  1538.     PRIVATE m.curcursor
  1539.     
  1540.     IF m.g_graphic
  1541.         DEFINE WINDOW alert ;
  1542.             AT  INT((SROW() - (( 5.615 * ;
  1543.             fontmetric(1, c_dlgface, c_dlgsize, c_dlgstyle )) / ;
  1544.             fontmetric(1, wfont(1,""), wfont(2,""), wfont(3,"")))) / 2), ;
  1545.             INT((SCOL() - (( 63.833 * ;
  1546.             fontmetric(6, c_dlgface, c_dlgsize, c_dlgstyle )) / ;
  1547.             fontmetric(6, wfont(1,""), wfont(2,""), wfont(3,"")))) / 2) ;
  1548.             SIZE 5.615,63.833 ;
  1549.             font c_dlgface, c_dlgsize ;
  1550.             STYLE c_dlgstyle ;
  1551.             NOCLOSE ;
  1552.             DOUBLE ;
  1553.             TITLE "Genmenu Error" ;
  1554.             COLOR rgb(0, 0, 0, 255, 255, 255)
  1555.         
  1556.         ACTIVATE WINDOW alert NOSHOW
  1557.         
  1558.         m.msg = SUBSTR(m.msg,1,44)+IIF(LEN(m.msg)>44,"...","")
  1559.         @ 1,(WCOLS()-txtwidth( m.msg ))/2 SAY m.msg
  1560.         
  1561.         m.msg = "Line Number: "+STR(m.lineno, 4)
  1562.         @ 2,(WCOLS()-txtwidth( m.msg ))/2 SAY m.msg
  1563.         
  1564.         m.msg = "Press any key to cleanup and exit..."
  1565.         @ 3,(WCOLS()-txtwidth( m.msg ))/2 SAY m.msg
  1566.         
  1567.         SHOW WINDOW alert
  1568.     ELSE
  1569.         DEFINE WINDOW alert;
  1570.             FROM INT((SROW()-6)/2), INT((SCOL()-50)/2) TO INT((SROW()-6)/2) + 6, INT((SCOL()-50)/2) + 50 ;
  1571.             FLOAT NOGROW NOCLOSE NOZOOM    SHADOW DOUBLE;
  1572.             COLOR SCHEME 7
  1573.         
  1574.         ACTIVATE WINDOW alert
  1575.         
  1576.         @ 0,0 CLEAR
  1577.         @ 1,0 SAY PADC(SUBSTR(m.msg,1,44)+;
  1578.             IIF(LEN(m.msg)>44,"...",""), WCOLS())
  1579.         @ 2,0 SAY PADC("Line Number: "+STR(m.lineno, 4), WCOLS())
  1580.         @ 3,0 SAY PADC("Press any key to cleanup and exit...", WCOLS())
  1581.     ENDIF
  1582.     
  1583.     m.curcursor = SET( "CURSOR" )
  1584.     SET CURSOR OFF
  1585.     
  1586.     WAIT ""
  1587.     
  1588.     RELEASE WINDOW alert
  1589.     SET CURSOR &curcursor
  1590.     
  1591.     RELEASE WINDOW alert
  1592.     
  1593. *
  1594. * OPENERRFILE - Open error file.
  1595. *
  1596. PROCEDURE openerrfile
  1597.     PRIVATE m.errfile, m.errhandle
  1598.     m.errfile   = m.g_errlog+".ERR"
  1599.     m.errhandle = FOPEN(m.errfile,2)
  1600.     IF m.errhandle < 0
  1601.         m.errhandle = FCREATE(m.errfile)
  1602.         IF m.errhandle < 0
  1603.             DO errshow WITH ".ERR could not be opened...", LINENO()
  1604.             m.g_status = 2
  1605.             IF WEXIST("Thermomete") AND WVISIBLE("Thermomete")
  1606.                 RELEASE WINDOW thermometer
  1607.             ENDIF
  1608.             ON ERROR
  1609.             RETURN TO MASTER
  1610.         ENDIF
  1611.     ELSE
  1612.         = FSEEK(m.errhandle,0,2)
  1613.     ENDIF
  1614.     IF SET("TEXTMERGE") = "OFF"
  1615.         SET TEXTMERGE ON
  1616.     ENDIF
  1617.     _TEXT = m.errhandle
  1618.  
  1619. *
  1620. * GETCNAME - Manufacture a procedure name, unless there is a #NAME directive
  1621. *
  1622. FUNCTION getcname
  1623. PARAMETERS snippet
  1624. PRIVATE ALL
  1625. IF proctype = 1
  1626.    numlines = MEMLINES(snippet)
  1627.    IF m.numlines > 0
  1628.       _MLINE = 0
  1629.       m.i = 1
  1630.       DO WHILE m.i <= m.numlines
  1631.          m.thisline = UPPER(ALLTRIM(MLINE(snippet,1, _MLINE)))
  1632.          DO CASE
  1633.          CASE LEFT(m.thisline,5) == "#NAME"
  1634.             RETURN ALLTRIM(SUBSTR(m.thisline,6))
  1635.          CASE EMPTY(m.thisline) OR iscomment(m.thisline)
  1636.             * Do nothing.  Get next line.
  1637.          OTHERWISE
  1638.             EXIT 
  1639.          ENDCASE
  1640.          m.i = m.i + 1
  1641.       ENDDO
  1642.    ENDIF
  1643. ENDIF
  1644. RETURN LOWER(SYS(2015))
  1645.  
  1646. *
  1647. * ISCOMMENT - Determine if textline is a comment line.
  1648. *
  1649. FUNCTION IsComment
  1650. PARAMETER m.textline
  1651. PRIVATE m.asterisk, m.isnote, m.ampersand, m.statement
  1652. IF EMPTY(m.textline)
  1653.    RETURN .F.
  1654. ENDIF
  1655. m.statement = UPPER(ALLTRIM(m.textline))
  1656.  
  1657. m.asterisk  = AT("*", LEFT(m.statement,1))
  1658. m.ampersand = AT(CHR(38)+CHR(38), LEFT(m.statement,2))
  1659. m.isnote    = AT("NOTE", LEFT(m.statement,4))
  1660.  
  1661. DO CASE
  1662. CASE (m.asterisk = 1 OR m.ampersand = 1)
  1663.    RETURN .T.
  1664. CASE (m.isnote = 1 ;
  1665.         AND (LEN(m.statement) <= 4 OR SUBSTR(m.statement,5,1) = ' '))
  1666.    * Don't be fooled by something like "notebook = 7"
  1667.    RETURN .T.
  1668. ENDCASE
  1669. RETURN .F.
  1670. *
  1671. * WORDNUM - Returns w_num-th word from string strg
  1672. *
  1673. FUNCTION wordnum
  1674. PARAMETERS strg,w_num
  1675. PRIVATE strg,s1,w_num,ret_str
  1676.  
  1677. m.s1 = ALLTRIM(m.strg)
  1678.  
  1679. * Replace tabs with spaces
  1680. m.s1 = CHRTRAN(m.s1,CHR(9)," ")
  1681.  
  1682. * Reduce multiple spaces to a single space
  1683. DO WHILE AT('  ',m.s1) > 0
  1684.    m.s1 = STRTRAN(m.s1,'  ',' ')
  1685. ENDDO
  1686.  
  1687. ret_str = ""
  1688. DO CASE
  1689. CASE m.w_num > 1
  1690.    DO CASE
  1691.    CASE AT(" ",m.s1,m.w_num-1) = 0   && No word w_num.  Past end of string.
  1692.       m.ret_str = ""
  1693.    CASE AT(" ",m.s1,m.w_num) = 0     && Word w_num is last word in string.
  1694.       m.ret_str = SUBSTR(m.s1,AT(" ",m.s1,m.w_num-1)+1,255)
  1695.    OTHERWISE                         && Word w_num is in the middle.
  1696.       m.strt_pos = AT(" ",m.s1,m.w_num-1)
  1697.       m.ret_str  = SUBSTR(m.s1,strt_pos,AT(" ",m.s1,m.w_num)+1 - strt_pos)
  1698.    ENDCASE
  1699. CASE m.w_num = 1
  1700.    IF AT(" ",m.s1) > 0               && Get first word.
  1701.       m.ret_str = SUBSTR(m.s1,1,AT(" ",m.s1)-1)
  1702.    ELSE                              && There is only one word.  Get it.
  1703.       m.ret_str = m.s1
  1704.    ENDIF
  1705. ENDCASE
  1706. RETURN ALLTRIM(m.ret_str)
  1707.  
  1708.