home *** CD-ROM | disk | FTP | other *** search
/ BUG 4 / BUGCD1997_05.BIN / aplic / clip4win / clip4win.exe / C4W30E.HUF / SOURCE / CONTRIB / WBROWS.ZIP / WBROWSE.PRG
Text File  |  1993-05-26  |  43KB  |  1,091 lines

  1. // WBrowse() - A "generic" tbrowse() routine for use with Clip4Win.
  2. // By Hugh A. Lokey, 05/26/93
  3. //
  4. // With WBrowse() you can browse indexed files, files without indexes, and
  5. // sub-sets of files. From within WBrowse() a user can Add a record, Delete
  6. // a record, Edit a record, Find/Search for a record, and Print reports/
  7. // lists.  WBrowse() is "modeless" so a user can do anything you allow them
  8. // to do while browsing.  You can have as many browse windows open as
  9. // memory allows.  If you have multiple browse windows open at one time
  10. // WBrowse() will update each window automatically each time Windows
  11. // tells it to (fun to watch!).
  12. //
  13. // Calling conventions/param list:
  14. //
  15. // WBrowse(cPrompt,;     String you want to appear on the statusbar
  16. //         bSearch,;     Codeblock for searching
  17. //         cKey,;        Search key for browsing sub-sets of a database
  18. //         bKeyBlk,;     Codeblock to do searching for sub-sets
  19. //         aBrowFields,; Array containing the fields for the browse,
  20. //                       has two dimensions, the first contains the field
  21. //                       number or a codeblock.  The second contains a
  22. //                       string to use for the column header.
  23. //         bEDTUDF,;     Codeblock to use when the user selects Edit.
  24. //         bDELUDF,;     Codeblock to use when the user selects Delete.
  25. //         bADDUDF,;     Codeblock to use when the user selects Add.
  26. //         bSELUDF,;     Codeblock to use when the user selects View.
  27. //         bPRNUDF,;     Codeblock to use when the user selects Print.
  28. //         cAlias,;      Alias of database being browsed.
  29. //         cTitle,;      Title for browse window.
  30. //         bDestroy)     Optional codeblock to use when browse terminated.
  31.  
  32. // NOTES:
  33. //
  34. // 1. bSearch - If you are going to let the user do searchs you need
  35. // to include a codeblock else just pass .F..  For an indexed database
  36. // you use something like {|cSKey|DBSEEK(cSKey)}.  For a database that
  37. // is not indexed or you are going to browse in natural order you
  38. // would use something like {|cStr|FindIt(cStr)} where FindIt(cStr)
  39. // looks something like this:
  40.  
  41. // STATIC FUNCTION FindIt(cStr)
  42. //
  43. //        LOCATE FOR cStr $ UPPER(SYSLOG->TEXT)
  44. // RETURN(NIL)
  45.  
  46. // 2. cKey/bKeyBlk - This is an expression or database field that will be used
  47. // to limit the scope of the browse to a sub-set of a database.  The following
  48. // example shows how cKey and cKeyBlk are used to limit the browse to
  49. // a sub-set:
  50.  
  51. //STATIC FUNCTION HistViewer
  52. //       LOCAL bSELUDF := {||BViewHist()},;
  53. //             bEDTUDF := "",;
  54. //             bADDUDF := "",;
  55. //             bDELUDF := "",;
  56. //             bPRNUDF := {||BPrntStat()}
  57. //
  58. //       LOCAL bKeyBlk :={||ACCTHIST->ACCT_NO}
  59. //       LOCAL aCols   := {{{||DTOC(ACCTHIST->DATE)},"DATE"},;
  60. //                         {{||Ptype(ACCTHIST->TYPE)},"TYPE"},;
  61. //                         {{||STR(ACCTHIST->AMOUNT,7,2)},"AMOUNT"},;
  62. //                         {{||LEFT(ACCTHIST->TXCODE,40)},"FOR"},;
  63. //                         {{||PADL(ALLTRIM(TRANSFORM(ACCTHIST->BALANCE,'@B( 99999.99')),9)},"BALANCE"}}
  64. //
  65. //
  66. //       SELECT ACCTHIST
  67. //       WBrowse("ACCOUNT HISTORY FOR: "+PARENTS->(MakeName()),;
  68. //               .F.,;
  69. //               PARENTS->SSN,;
  70. //               bKeyBlk,;
  71. //               aCols,;
  72. //               bEDTUDF,;
  73. //               bDELUDF,;
  74. //               bADDUDF,;
  75. //               bSELUDF,;
  76. //               bPRNUDF,;
  77. //               "ACCTHIST",;
  78. //               "Review Account History",;
  79. //               .F.)
  80. //
  81. //RETURN(NIL)
  82. //
  83. // since we are already browsing a sub-set of a database then we do not
  84. // have the luxury of allowing the user to perform searchs since we are
  85. // moving the record pointer with seeks already.
  86.  
  87. // 3.  aBrowFields - In the above example we use codeblocks in all of
  88. // the browse field definitions.  You can use either a field number or
  89. // codeblock in the first position.  The second position requires a
  90. // string to use as the column header.  If you are lazy and want to
  91. // include all fields with their field names as column headers you
  92. // use LdBrowFlds() as this param.  LdBrowFlds() returns an array
  93. // consisting of the field number and field name that WBrowse() can
  94. // use. You can mix codeblocks and field numbers.  The following
  95. // example uses a relation in the browse and uses a mix of codeblocks
  96. // and field numbers:
  97.  
  98. //STATIC FUNCTION BrowChld
  99. //       LOCAL bSELUDF := {||BViewChild()},;
  100. //             bEDTUDF := {||BEditChild(),;
  101. //             bDELUDF := {||BDelChild()},;
  102. //             bADDUDF := {||BAddChild()},;
  103. //             bPRNUDF := {||BPrnChild()}
  104. //
  105. //       LOCAL aCols   := {{{||MakeName()},"NAME"},;
  106. //                         {  1,"ID#"},;
  107. //                         { 16,"STATUS"},;
  108. //                         { 19,"ROOM"},;
  109. //                         { {||RATES->RATE_CODE+' '+RATES->DESCRIPT},"BILLING RATE"}}
  110. //
  111. //       SELECT CHILD
  112. //       SET ORDER TO 2
  113. //       GO TOP
  114. //       SET RELATION TO RATE_CODE INTO RATES
  115. //       WBrowse("SEARCH KEY:  LASTNAME",;
  116. //               {|cSkey|DBSEEK(cSkey)},;
  117. //               .F.,;
  118. //               .F.,;
  119. //               aCols,;
  120. //               bEDTUDF,;
  121. //               bDELUDF,;
  122. //               bADDUDF,;
  123. //               bSELUDF,;
  124. //               bPRNUDF,;
  125. //               "CHILD",;
  126. //               "Browse - Children",;
  127. //               {||MenuOnOff(.F.)})
  128. //RETURN(NIL)
  129. //
  130. // MakeName() is a function that takes fields containing the lastname,
  131. // firstname, MI, and turns them into a proper name.  MenuOnOff() is
  132. // a function that turns the menu items on/off on the menu from where
  133. // Browse was selected.  Called when the browse is terminated so the
  134. // menu selections are re-enabled/turned back on.
  135.  
  136. // 4.  bEDTUDF, bADDUDF, bDELUDF, bSELUDF, bPRNUDF - These are all
  137. // optional codeblocks that are called to Edit the highlighted record,
  138. // add a new record, delete the highlighted record, view the highlighted
  139. // record, and to print reports/lists.  If you pass something other than
  140. // a codeblock ("", .F., etc.) then WBrowse() will automatically disable
  141. // the button/key used to select that function.
  142.  
  143. // 5.  bDestroy - This is an optional codeblock that is used by the
  144. // DelHandler() routine I use.  It is called when the browse is
  145. // terminated.  You will have to modifiy YOUR AddHandler() and DelHandler()
  146. // routines to use it.  My primary use is to turn off the menu selection
  147. // that invoked the browse then turn it back on when the browse is
  148. // finished.  I don't want the user to have two instances of the same
  149. // browse going at one time!  I also turn off any other menu items
  150. // that could cause potential havoc if envoked from within the browse.
  151.  
  152. // 6. General - I take the approach that in order for a user to edit
  153. // an existing record, delete a record or view a record that they
  154. // first must be provided with a way of selecting the record they want
  155. // to work with. Why have the code overhead of providing a routine to
  156. // just locate the record they want when you are going to provide a
  157. // browse routine anyway? In both my DOS and Windows apps I always
  158. // have a menu item labeled "Browse/Delete/Edit".  This menu selection
  159. // calls WBrowse() to do it all!  Window size, colors, screen location,
  160. // etc. are hard coded so that the browse window appears in the center
  161. // of the window where the menu selection to invoke it is.  The window
  162. // size is NOT changeable by the user but they can minimize it or move
  163. // it out of the way. Since I use valid clauses in most of my input
  164. // fields (to prevent "garbage in garbage out"), I don't allow the user
  165. // to edit one field/column in WBrowse() but provide a full screen
  166. // input routine for both editing and adding a new record.
  167.  
  168. // Those functions that ARE NOT declared as being STATIC are used by
  169. // other routines in my generic Windows Lib.  If you don't make use
  170. // of them elsewhere you can make them STATIC to save some table
  171. // space.
  172.  
  173. // I am not real happy with the statusbar and horizontal scroll bits
  174. // at this time.  If someone out there comes up with something better
  175. // please post it so all of us can take a look see.
  176.  
  177. // Any questions, comments, slurs, etc. will be appreciated.....
  178.  
  179. ****************************************************************
  180. //#include "caresw.ch"
  181. //Modified version of include file included with C4W listed below:
  182. //#define FORCE_FOCUS   6
  183. //#define WIN_WANT_CLIPBOARD
  184. //#define WIN_WANT_LBS
  185. //#define WIN_WANT_HELP
  186. //#define WIN_WANT_ALL
  187. //#include "dbstruct.ch"
  188. //#include "directry.ch"
  189. //#include "error.ch"
  190. //#include "getexit.ch"
  191. //#include "inkey.ch"
  192. //#include "windows.ch"
  193. //#include "setcaret.ch"
  194. //#include "font.ch"
  195. //#include "commdlg.ch"
  196. //#define C_RED        RGB(255,0,0)
  197. //#define C_BLUE       RGB(0,0,128)
  198. //#define C_GREEN      RGB(0,255,0)
  199. //#define C_MAGENTA    RGB(255,0,255)
  200. //#define C_BLACK      RGB(0,0,0)
  201. //#define APP_NAME     "CARES For Windows"
  202.  
  203. FUNCTION WBrowse(cPrompt,;               // Displayed on status bar
  204.                  bSearch,;               // code block to use for searching
  205.                  cKey,;                  // key value for sub-sets
  206.                  bKeyBlk,;               // code block to evaluate key
  207.                  aBrowFields,;           // fields/codeblocks for browse
  208.                  bEDTUDF,;               // codeblock for editing
  209.                  bDELUDF,;               // codeblock for deleting
  210.                  bADDUDF,;               // codeblock for adding
  211.                  bSELUDF,;               // codeblock for viewing
  212.                  bPRNUDF,;               // codeblock for printing
  213.                  cAlias,;                // name of database/alias
  214.                  cTitle,;                // title for browse window
  215.                  bDestroy)               // codeblock for DelHandler() to use
  216.          LOCAL oB, nCtr, column, nNoFields, nId, hInst, hWnd, hVSb, hHSb
  217.          LOCAL aButtons[9][9], hOldWnd
  218.  
  219.          hOldWnd := SelectWindow()
  220.          hWnd := WinNew(cTitle,             ;
  221.                         19,                 ;       // x co-ordinate
  222.                         80,                 ;       // y co-ordinate
  223.                         602,                ;       // width
  224.                         327,                ;
  225.                         WS_BORDER+WS_CAPTION+WS_MINIMIZEBOX+WS_CLIPCHILDREN)
  226.  
  227.          DBSELECTAREA(cAlias)
  228.          oB := TBrowseDB(1, 2, 17, 70)
  229.          oB:headSep   := "┬─"
  230.          oB:colSep    := "│"
  231.          oB:colorSpec := "N/W,W+/N,W+/N,W+/N"
  232.          FOR nCtr = 1 TO LEN(aBrowFields)
  233.              IF VALTYPE(aBrowFields[nCtr][1]) == 'N'
  234.                 column := TBColumnNew(aBrowFields[nCtr][2], FieldBlock(FieldName(aBrowFields[nCtr][1])))
  235.              ELSE
  236.                 column := TBColumnNew(aBrowFields[nCtr][2], aBrowFields[nCtr][1])
  237.              ENDIF
  238.              column:defColor := {1,2}
  239.              oB:addColumn(column)
  240.          NEXT
  241.          IF VALTYPE(bKeyBlk) = 'B'
  242.             oB:goBottomblock := {|| FindLast(cKey,bKeyBlk)}
  243.             oB:goTopblock    := {|| FindFirst(cKey)}
  244.             oB:Skipblock     := {|nSkip| SkipFor(nSkip,cKey,bKeyBlk)}
  245.             IF FindFirst(cKey) == .F.
  246.                DestroyWindow(hWnd)
  247.                SelectWindow(hOldWnd)
  248.                SetFocus(hOldWnd)
  249.                IF VALTYPE(bDestroy) == 'B'
  250.                   EVAL(bDestroy)
  251.                ENDIF
  252.                RETURN(.F.)
  253.             ENDIF
  254.          ELSE
  255.             GO TOP
  256.             IF EOF()
  257.                EmptyErr()
  258.                DestroyWindow(hWnd)
  259.                SelectWindow(hOldWnd)
  260.                SetFocus(hOldWnd)
  261.                IF VALTYPE(bDestroy) == 'B'
  262.                   EVAL(bDestroy)
  263.                ENDIF
  264.                RETURN(.F.)
  265.             ENDIF
  266.          ENDIF
  267.          hInst := _GetInstance()
  268.          WinBox(hWnd, 0,  0,600,360,1,2,0)
  269.          WinBox(hWnd,12,  8,588,236,1,2,2)
  270.          WinBox(hWnd,12,239,588,272,2,2,2)
  271.          Message(hWnd,"")
  272.          IF VALTYPE(bKeyBlk) != 'B'
  273.             WinBox(hWnd, 08,283,438,303,1,2,1,0,4)
  274.             WinBox(hWnd,438,283,518,303,1,2,1,0,6)
  275.             WinBox(hWnd,516,283,594,303,1,2,1,0,6)
  276.             UpdateStatusBar(hWnd,cPrompt,RECCOUNT())
  277.          ELSE
  278.             UpDateStatusBar(hWnd,cPrompt,0)
  279.          ENDIF
  280.          aButtons[1][1] := CreateWindow("BUTTON", "Top"   ,WS_CHILD + WS_VISIBLE+BS_PUSHBUTTON,  22, 244, 60, 24,hWnd,1,hInst)
  281.          aButtons[2][1] := CreateWindow("BUTTON", "Bottom",WS_CHILD + WS_VISIBLE+BS_PUSHBUTTON,  84, 244, 60, 24,hWnd,2,hInst)
  282.          aButtons[3][1] := CreateWindow("BUTTON", "Add"   ,WS_CHILD + IIF(VALTYPE(bADDUDF)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 146, 244, 60, 24,hWnd,3,hInst)
  283.          aButtons[4][1] := CreateWindow("BUTTON", "Delete",WS_CHILD + IIF(VALTYPE(bDELUDF)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 208, 244, 60, 24,hWnd,4,hInst)
  284.          aButtons[5][1] := CreateWindow("BUTTON", "Edit"  ,WS_CHILD + IIF(VALTYPE(bEDTUDF)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 270, 244, 60, 24,hWnd,5,hInst)
  285.          aButtons[6][1] := CreateWindow("BUTTON", "Find"  ,WS_CHILD + IIF(VALTYPE(bSearch)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 332, 244, 60, 24,hWnd,6,hInst)
  286.          aButtons[7][1] := CreateWindow("BUTTON", "Print" ,WS_CHILD + IIF(VALTYPE(bPRNUDF)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 394, 244, 60, 24,hWnd,7,hInst)
  287.          aButtons[8][1] := CreateWindow("BUTTON", "View"  ,WS_CHILD + IIF(VALTYPE(bSELUDF)=='B',0,WS_DISABLED)+WS_VISIBLE+BS_PUSHBUTTON, 456, 244, 60, 24,hWnd,8,hInst)
  288.          aButtons[9][1] := CreateWindow("BUTTON", "Quit"  ,WS_CHILD + WS_VISIBLE+BS_PUSHBUTTON, 518, 244, 60, 24,hWnd,9,hInst)
  289.          hVSb           := CreateWindow("SCROLLBAR",""    ,SBS_VERT + WS_CHILD + WS_VISIBLE,    569, 11, 17, 206,hWnd,-1,hInst)
  290.          hHSb           := CreateWindow("SCROLLBAR",""    ,SBS_HORZ + WS_CHILD + WS_VISIBLE,     15,216,571,  18,hWnd,-1,hInst)
  291.          SetScrollRange(hVSb,SB_CTL,0,100,.F.)
  292.          SetScrollRange(hHSb,SB_CTL,0,100,.F.)
  293.          IF oB:rightVisible == oB:colCount
  294.             EnableWindow(hHSB,.F.)
  295.          ENDIF
  296.          aButtons[1][2] := .T.
  297.          aButtons[2][2] := .T.
  298.          aButtons[3][2] := (VALTYPE(bADDUDF)=='B')
  299.          aButtons[4][2] := (VALTYPE(bDELUDF)=='B')
  300.          aButtons[5][2] := (VALTYPE(bEDTUDF)=='B')
  301.          aButtons[6][2] := (VALTYPE(bSearch)=='B')
  302.          aButtons[7][2] := (VALTYPE(bPRNUDF)=='B')
  303.          aButtons[8][2] := (VALTYPE(bSELUDF)=='B')
  304.          aButtons[9][2] := .T.
  305.          oB:cargo := {.T.,INDEXORD(),RECNO()}
  306.          nId := AddHandler(hWnd, {|nEvent| BrowseEvent(nEvent, hWnd, oB,;
  307.                            cPrompt,;
  308.                            bSearch,;
  309.                            cKey,;
  310.                            bKeyBlk,;
  311.                            bEDTUDF,;
  312.                            bDELUDF,;
  313.                            bADDUDF,;
  314.                            bSELUDF,;
  315.                            bPRNUDF,;
  316.                            nId,aButtons,hVSb,hHSb,cAlias)},bDestroy)
  317. RETURN(NIL)
  318.  
  319. STATIC FUNCTION BrowseEvent(nEvent,hWnd,oB,;
  320.                             cPrompt,;
  321.                             bSearch,;
  322.                             cKey,;
  323.                             bKeyBlk,;
  324.                             bEDTUDF,;
  325.                             bDELUDF,;
  326.                             bADDUDF,;
  327.                             bSELUDF,;
  328.                             bPRNUDF,;
  329.                             nId,aButtons,hVSb,hHSb,cAlias)
  330.        LOCAL Foo, nCrec, nKey, nButton, nScrollCmd, hOldWnd
  331.        LOCAL nMaxRecs, nSkipCnt, nCur, nOrder, cOldAlias
  332.  
  333.        cOldAlias := ALIAS()
  334.        DBSELECTAREA(cAlias)
  335.        nMaxRecs  := RECCOUNT()
  336.        nOrder    := INDEXORD()
  337.        nOldWnd   := SelectWindow(hWnd)
  338.        nCur      := oB:rightVisible
  339.        SET ORDER TO oB:cargo[2]
  340.  
  341.        DO CASE
  342.           CASE nEvent == EVENT_KILLFOCUS
  343.                SET ORDER TO nOrder
  344.                oB:cargo[3] := RECNO()
  345.                SELECT (cOldAlias)
  346.  
  347.           CASE nEvent == EVENT_REDRAW
  348.                IF oB:cargo[1] != .T.
  349.                   WinBox(hWnd, 0,  0,600,360,1,2,0)
  350.                   WinBox(hWnd,12,  8,588,236,1,2,2)
  351.                   WinBox(hWnd,12,239,588,272,2,2,2)
  352.                   Message(hWnd,"")
  353.                   IF VALTYPE(bKeyBlk) != 'B'
  354.                      WinBox(hWnd, 08,283,438,303,1,2,1,0,4)
  355.                      WinBox(hWnd,438,283,518,303,1,2,1,0,6)
  356.                      WinBox(hWnd,516,283,594,303,1,2,1,0,6)
  357.                   ENDIF
  358.                   GOTO oB:cargo[3]
  359.                   oB:refreshAll()
  360.                ELSE
  361.                   oB:cargo[1] := .F.
  362.                ENDIF
  363.                IF oB:rightVisible == oB:colCount .AND.;
  364.                   oB:leftVisible  == 1
  365.                   EnableWindow(hHSb,.F.)
  366.                ENDIF
  367.  
  368.           CASE nEvent == EVENT_KEY
  369.                oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  370.                nKey := 0
  371.                DO WHILE nKey == 0
  372.                   nKey := inkey()
  373.                   IF nKey != 0
  374.                      EXIT
  375.                   ENDIF
  376.                ENDDO
  377.                DO CASE
  378.                   CASE ( nKey == 43 )  // + key
  379.                          IF !EMPTY(bADDUDF)
  380.                             ButtonsOff(aButtons)
  381.                             Foo := EVAL(bADDUDF)
  382.                             SelectWindow(hWnd)
  383.                             ButtonsOn(aButtons)
  384.                             oB:goTop()
  385.                          ENDIF
  386.  
  387.                   CASE ( nKey == K_RETURN )
  388.                          IF !EMPTY(bSELUDF)
  389.                             ButtonsOff(aButtons)
  390.                             Foo := EVAL(bSELUDF)
  391.                             SelectWindow(hWnd)
  392.                             ButtonsOn(aButtons)
  393.                          ENDIF
  394.  
  395.                   CASE ( nKey == K_INS )
  396.                        IF !EMPTY(bEDTUDF)
  397.                           ButtonsOff(aButtons)
  398.                           Foo := EVAL(bEDTUDF)
  399.                           SelectWindow(hWnd)
  400.                           ButtonsOn(aButtons)
  401.                           IF VALTYPE(bKeyBlk) = 'B'
  402.                              FindFirst(cKey)
  403.                           ENDIF
  404.                         ENDIF
  405.  
  406.                   CASE ( nKey == K_DEL )
  407.                          IF !EMPTY(bDELUDF)
  408.                             Beep()
  409.                             IF NRecLock()
  410.                                IF ErrorMsg("Delete This Record?"+CHR(13)+&(INDEXKEY()),"W") == IDOK
  411.                                   DELETE
  412.                                   Foo := EVAL(bDELUDF)
  413.                                   UNLOCK
  414.                                   oB:goTop()
  415.                                   IF EOF()
  416.                                      EmptyErr()
  417.                                      DelHandler(nId)
  418.                                      DestroyWindow(hWnd)
  419.                                      RETURN(NIL)
  420.                                   ENDIF
  421.                                ENDIF
  422.                             ENDIF
  423.                             SelectWindow(hWnd)
  424.                         ENDIF
  425.  
  426.                   CASE ( nKey == K_RIGHT)
  427.                          IF oB:colCount > oB:rightVisible
  428.                             oB:panRight()
  429.                          ELSE
  430.                             Beep()
  431.                          ENDIF
  432.  
  433.                   CASE ( nKey == K_LEFT)
  434.                          IF oB:leftVisible > 1
  435.                             oB:panLeft()
  436.                          ELSE
  437.                             Beep()
  438.                          ENDIF
  439.  
  440.                   CASE ( nKey == K_CTRL_RIGHT)
  441.                          oB:panEnd()
  442.  
  443.                   CASE ( nKey == K_CTRL_LEFT)
  444.                          oB:panHome()
  445.  
  446.                   CASE ( nKey == K_CTRL_P )
  447.                          IF VALTYPE(bPRNUDF) == 'B'
  448.                             ButtonsOff(aButtons)
  449.                             Foo := EVAL(bPRNUDF)
  450.                             SelectWindow(hWnd)
  451.                             BringWindowToTop(hWnd)
  452.                             ButtonsOn(aButtons)
  453.                          ENDIF
  454.  
  455.                   CASE ( nKey == K_F1 )
  456.                        // help goes here when it is finished!
  457.  
  458.                   CASE ( nKey == K_DOWN )
  459.                          oB:down()
  460.  
  461.                   CASE ( nKey == K_UP )
  462.                          oB:up()
  463.  
  464.                   CASE ( nKey == K_PGDN )
  465.                          oB:pageDown()
  466.  
  467.                   CASE ( nKey == K_PGUP )
  468.                          oB:pageUp()
  469.  
  470.                   CASE ( nKey == K_HOME )
  471.                          oB:goTop()
  472.  
  473.                   CASE ( nKey == K_END )
  474.                          oB:goBottom()
  475.  
  476.                   CASE ( nKey == K_ESC )
  477.                          DelHandler(nId)
  478.                          DestroyWindow(hWnd)
  479.                          SelectWindow(hOldWnd)
  480.                          RETURN(NIL)
  481.  
  482.                   CASE ( nKey == K_F2 )
  483.                        IF !EMPTY(bSearch)
  484.                            QuickS(_GetInstance(),hWnd,bSearch)
  485.                            SelectWindow(hWnd)
  486.                            oB:refreshAll()
  487.                        ENDIF
  488.  
  489.                   OTHERWISE
  490.                       IF UPPER(CHR(nKey)) $ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
  491.                          nCrec := RECNO()
  492.                          SEEK UPPER(CHR(nKey))
  493.                          IF !FOUND()
  494.                             GOTO nCrec
  495.                          ENDIF
  496.                          oB:refreshAll()
  497.                       ENDIF
  498.                 ENDCASE
  499.  
  500.        CASE nEvent == EVENT_CONTROL
  501.             nButton := _lastwParam()
  502.             SelectWindow(hWnd)
  503.             SetFocus(hWnd)
  504.             oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  505.             DO CASE
  506.                CASE nButton == 1
  507.                     oB:GoTop()
  508.  
  509.                CASE nButton == 2
  510.                     oB:GoBottom()
  511.  
  512.                CASE nButton == 3
  513.                     IF !EMPTY(bADDUDF)
  514.                        ButtonsOff(aButtons)
  515.                        Foo := EVAL(bADDUDF)
  516.                        SelectWindow(hWnd)
  517.                        ButtonsOn(aButtons)
  518.                        oB:goTop()
  519.                     ENDIF
  520.  
  521.                CASE nButton == 4
  522.                     IF !EMPTY(bDELUDF)
  523.                        Beep()
  524.                        IF NRecLock()
  525.                           IF ErrorMsg("Delete This Record?"+CHR(13)+&(INDEXKEY()),"W") == IDOK
  526.                              SelectWindow(hWnd)
  527.                              DELETE
  528.                              Foo := EVAL(bDELUDF)
  529.                              UNLOCK
  530.                              oB:goTop()
  531.                              IF EOF()
  532.                                 EmptyErr()
  533.                                 DelHandler(nId)
  534.                                 DestroyWindow(hWnd)
  535.                                 SelectWindow(hOldWnd)
  536.                                 RETURN(NIL)
  537.                              ENDIF
  538.                           ENDIF
  539.                        ENDIF
  540.                        SelectWindow(hWnd)
  541.                     ENDIF
  542.  
  543.                CASE nButton == 5
  544.                     IF !EMPTY(bEDTUDF)
  545.                        ButtonsOff(aButtons)
  546.                        Foo := EVAL(bEDTUDF)
  547.                        SelectWindow(hWnd)
  548.                        IF VALTYPE(bKeyBlk) = 'B'
  549.                           FindFirst(cKey)
  550.                        ENDIF
  551.                        ButtonsOn(aButtons)
  552.                     ENDIF
  553.  
  554.                CASE nButton == 6
  555.                     IF !EMPTY(bSearch)
  556.                         QuickS(_GetInstance(),hWnd,bSearch)
  557.                         SelectWindow(hWnd)
  558.                         oB:refreshAll()
  559.                     ENDIF
  560.  
  561.                CASE nButton == 7
  562.                     IF VALTYPE(bPRNUDF) == 'B'
  563.                        ButtonsOff(aButtons)
  564.                        Foo := EVAL(bPRNUDF)
  565.                        SelectWindow(hWnd)
  566.                        ButtonsOn(aButtons)
  567.                     ENDIF
  568.  
  569.                CASE nButton == 8
  570.                     IF !EMPTY(bSELUDF)
  571.                        ButtonsOff(aButtons)
  572.                        Foo := EVAL(bSELUDF)
  573.                        SelectWindow(hWnd)
  574.                        ButtonsOn(aButtons)
  575.                     ENDIF
  576.  
  577.                CASE nButton == 9
  578.                     DelHandler(nId)
  579.                     DestroyWindow(hWnd)
  580.                     SelectWindow(hOldWnd)
  581.                     RETURN(NIL)
  582.             ENDCASE
  583.  
  584.        CASE nEvent == EVENT_LCLICK
  585.             IF (MouseRow() > oB:nTop) .AND. (MouseRow() < oB:nBottom);
  586.                .AND. (MouseCol() >= oB:nLeft) .AND. (MouseCol() <= oB:nRight)
  587.                 oB:deHilite()
  588.                 oB:colorRect({oB:RowPos,1,oB:RowPos,oB:colCount},{1,2})
  589.                 oB:rowPos := MouseRow() - 2
  590.                 oB:colorRect({oB:RowPos,1,oB:RowPos,oB:colCount},{2,2})
  591.                 oB:Hilite()
  592.             ENDIF
  593.  
  594.        CASE nEvent == EVENT_VSCROLL
  595.             nScrollCmd := _lastwParam()
  596.             nCur := -1
  597.             DO CASE
  598.                CASE nScrollCmd == SB_LINEDOWN
  599.                     oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  600.                     oB:down()
  601.                CASE nScrollCmd == SB_PAGEDOWN
  602.                     oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  603.                     oB:PageDown()
  604.                CASE nScrollCmd == SB_LINEUP
  605.                     oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  606.                     oB:Up()
  607.                CASE nScrollCmd == SB_PAGEUP
  608.                     oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  609.                     oB:PageUp()
  610.                CASE nScrollCmd == SB_THUMBPOSITION .or. nScrollCmd == SB_THUMBTRACK
  611.                     nCur := _lastlolParam()     // from Windows
  612.             ENDCASE
  613.             IF nScrollCmd <> SB_THUMBTRACK
  614.                IF nCur != -1
  615.                   oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{1,2})
  616.                   GO TOP
  617.                   nSkipCnt := SkipWhat(nCur,nMaxRecs)
  618.                   DO CASE
  619.                      CASE nSkipCnt == -1
  620.                           oB:goBottom()
  621.                      CASE nSkipCnt == 0
  622.                           oB:goTop()
  623.                      OTHERWISE
  624.                          SKIP nSkipCnt
  625.                   ENDCASE
  626.                ENDIF
  627.             ENDIF
  628.             nCur := oB:rightVisible
  629.  
  630.        CASE nEvent == EVENT_HSCROLL
  631.             nScrollCmd := _lastwParam()
  632.             nCur       := oB:rightVisible
  633.             DO CASE
  634.                 CASE nScrollCmd == SB_LINELEFT .OR.;
  635.                      nScrollCmd == SB_PAGELEFT
  636.                      IF oB:leftVisible > 1
  637.                         oB:panLeft()
  638.                      ELSE
  639.                         Beep()
  640.                      ENDIF
  641.                 CASE nScrollCmd == SB_LINERIGHT .OR.;
  642.                      nScrollCmd == SB_PAGERIGHT
  643.                      IF nCur < oB:colCount
  644.                         oB:panRight()
  645.                      ELSE
  646.                         Beep()
  647.                      ENDIF
  648.  
  649.                 CASE nScrollCmd == SB_THUMBPOSITION
  650.                      nCur := _lastlolParam()
  651.                      nCur := INT((oB:colCount*nCur)/100)
  652.                      IF nCur > oB:rightVisible
  653.                         DO WHILE oB:rightVisible < nCur
  654.                            oB:panRight()
  655.                         ENDDO
  656.                         nCur := oB:rightVisible
  657.                      ELSE
  658.                         IF oB:leftVisible > nCur
  659.                            DO WHILE oB:leftVisible > nCur .AND.;
  660.                               oB:leftVisible > 1
  661.                               oB:panLeft()
  662.                            ENDDO
  663.                          ENDIF
  664.                          nCur := oB:leftVisible
  665.                      ENDIF
  666.             ENDCASE
  667.        ENDCASE
  668.  
  669.        DO WHILE ( !oB:stabilize() )
  670.        ENDDO
  671.        IF oB:hitTop() .OR. oB:hitBottom()
  672.           Beep()
  673. *         MessageBeep()
  674. *         IF oB:hitTop
  675. *            MessageBox( , "You are at the top of the list!", ;
  676. *                          "Message", MB_ICONHAND + MB_OK)
  677. *         ELSE
  678. *            MessageBox( , "You are at the bottom of the list!",;
  679. *                          "Message", MB_ICONHAND + MB_OK)
  680. *         ENDIF
  681. *         oB:refreshAll()
  682. *         DO WHILE ( !oB:stabilize() )
  683. *         ENDDO
  684.        ENDIF
  685.        oB:colorRect({ob:rowPos,1,ob:rowPos,ob:colCount},{2,2})
  686.        UpdtVScrollbar(hVSb,nMaxRecs)
  687.        UpDtHScrollbar(hHSb,oB,nCur)
  688.        UpdateStatusBar(hWnd,cPrompt,IIF(VALTYPE(bKeyBlk) != 'B',nMaxRecs,0))
  689.        SelectWindow(hOldWnd)
  690. RETURN(NIL)
  691.  
  692. STATIC FUNCTION UpdateStatusBar(hWnd,cPrompt,nMaxRecs)
  693.        LOCAL hDC        := GetDC(hWnd)
  694.        LOCAL nOldBkClor := SetBkColor(hDC,RGB(192,192,192))
  695.        LOCAL nCur
  696.  
  697.        DrawText(hDC,cPrompt,;
  698.                {22,285,400,300},DT_LEFT)
  699.        IF nMaxRecs > 0
  700.           IF INDEXORD() != 0
  701.              nCur := NtxPos(INDEXORD(),RECNO())
  702.           ELSE
  703.              nCur := RECNO()
  704.           ENDIF
  705.           DrawText(hDC,STRZERO(nCur,7),    {451,285,524,301},DT_LEFT)
  706.           DrawText(hDC,STRZERO(nMaxRecs,7),{527,285,600,301},DT_LEFT)
  707.        ENDIF
  708.        SetBkColor(hDC,nOldBkClor)
  709.        ReleaseDC(hWnd,hDC)
  710. RETURN(NIL)
  711.  
  712. FUNCTION UpdtVScrollbar(hWnd,nMax)
  713.          LOCAL nNewpos, nCur
  714.  
  715.          IF INDEXORD() != 0
  716.             nCur := NtxPos(INDEXORD(),RECNO())
  717.          ELSE
  718.             nCur := RECNO()
  719.          ENDIF
  720.          DO CASE
  721.             CASE nCur == nMax
  722.                  nNewPos := 100
  723.             CASE nCur == 1
  724.                  nNewPos := 0
  725.             OTHERWISE
  726.                  nNewPos := INT((nCur/nMax)*100)
  727.          ENDCASE
  728.          SetScrollPos(hWnd, SB_CTL, nNewpos, .T.)
  729. RETURN(NIL)
  730.  
  731. STATIC FUNCTION UpdtHScrollbar(hWnd,oB,nCur)
  732.        LOCAL nNewPos
  733.  
  734.        DO CASE
  735.           CASE oB:leftVisible == 1
  736.                nNewPos := 0
  737.           CASE oB:rightVisible == oB:colCount
  738.                nNewPos := 100
  739.           OTHERWISE
  740.                nNewPos := nCur
  741.        ENDCASE
  742.        SetScrollPos(hWnd, SB_CTL, nNewPos,.T.)
  743. RETURN(NIL)
  744.  
  745. FUNCTION SkipWhat(nCur,nMax)
  746.          LOCAL nSkipCnt
  747.  
  748.          DO CASE
  749.             CASE nCur == 100
  750.                  RETURN(-1)
  751.             CASE nCur == 0
  752.                  RETURN(0)
  753.             OTHERWISE
  754.                  IF nCur > nMax
  755.                     nSkipCnt := INT((nMax*nCur)*100)
  756.                  ELSE
  757.                     nSkipCnt := INT((nMax/100)*nCur)
  758.                  ENDIF
  759.          ENDCASE
  760.          IF nSkipCnt >= nMax
  761.             RETURN(-1)
  762.          ENDIF
  763. RETURN(nSkipCnt)
  764.  
  765. FUNCTION QuickS(hInst,hWnd,bSearch)
  766.          STATIC cSearchStr
  767.          LOCAL aDlg, nItem, nCrec := RECNO()
  768.          LOCAL cMsel, nOrder := INDEXORD()
  769.  
  770.          IF nOrder < 1
  771.             aDlg := CreateDialog("Search Type?",;
  772.                                  DS_MODALFRAME+WS_POPUP+WS_CAPTION+WS_SYSMENU ,;
  773.                                  80, 28, 142, 72)
  774.             aDlg  := AppendDialog(aDlg,'new', DLG_BUTTON,BS_AUTORADIOBUTTON+WS_CHILD+WS_VISIBLE+WS_TABSTOP,    24, 05, 97, 12,"Start New Search", )
  775.             aDlg  := AppendDialog(aDlg,'old', DLG_BUTTON,BS_AUTORADIOBUTTON+WS_CHILD+WS_VISIBLE+WS_TABSTOP,    24, 17, 97, 12,"Continue Previous Search",)
  776.             aDlg  := AppendDialog(aDlg,'stat',DLG_STATIC,WS_VISIBLE+WS_CHILD,                                  24, 29, 97, 8,"Current Search Value")
  777.             aDlg  := AppendDialog(aDlg,'edit',DLG_EDIT,  ES_UPPERCASE+WS_CHILD+WS_VISIBLE+WS_TABSTOP+WS_BORDER,24, 38, 95, 12, cSearchStr)
  778.             aDlg  := AppendDialog(aDlg,"ok",  DLG_BUTTON,WS_CHILD+WS_VISIBLE+WS_TABSTOP,                       24, 55, 40, 12,"&Ok")
  779.             aDlg  := AppendDialog(aDlg,"can", DLG_BUTTON,WS_CHILD+WS_VISIBLE+WS_TABSTOP,                       80, 55, 40, 12,"&Cancel")
  780.             CheckDlgButton(aDlg,'new',1)
  781.             nItem := ModalDialog(aDlg,_GetInstance(),hWnd)
  782.          ELSE
  783.             cSearchStr := ""
  784.             aDlg  := CreateDialog("Search For?",WS_POPUP + WS_CAPTION + WS_SYSMENU+WS_VISIBLE+128,;
  785.                      100, 52, 113, 52 )
  786.             aDlg  := AppendDialog(aDlg,"edit",DLG_EDIT,ES_UPPERCASE+;
  787.                      WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP, 9, 19, 96, 12,cSearchStr)
  788.             aDlg  := AppendDialog(aDlg,"text",DLG_STATIC, WS_CHILD + WS_VISIBLE + WS_TABSTOP,8, 6, 107, 12,"Enter item to find")
  789.  
  790.             aDlg  := AppendDialog(aDlg,"ok",DLG_BUTTON,  WS_CHILD + WS_VISIBLE + WS_TABSTOP, 9, 35, 40, 12,"&Ok")
  791.             aDlg  := AppendDialog(aDlg,"can",DLG_BUTTON, WS_CHILD + WS_VISIBLE + WS_TABSTOP, 65, 35, 40, 12,"&Cancel")
  792.             nItem := ModalDialog(aDlg,hInst,hWnd)
  793.          ENDIF
  794.          IF ( nItem != 0 .AND. GetDialogResult(aDlg,"can") != .T. )
  795.             cSearchStr := GetDialogResult(aDlg,"edit")
  796.             IF !EMPTY(cSearchStr)
  797.                cSearchStr := ALLTRIM(cSearchStr)
  798.             ENDIF
  799.          ELSE
  800.             RETURN(NIL)
  801.          ENDIF
  802.          IF nOrder != 0
  803.             SEEK cSearchStr
  804.          ELSE
  805.             IF GetDialogResult(aDlg,'new') == 1
  806.                EVAL(bSearch,cSearchStr)
  807.             ELSE
  808.                CONTINUE
  809.             ENDIF
  810.          ENDIF
  811.          IF !FOUND()
  812.             ErrorMsg("NO MATCHING RECORD FOUND!",'E')
  813.             GOTO nCrec
  814.          ENDIF
  815. RETURN(NIL)
  816.  
  817. // LdBrowFlds() - Returns an array for use by WBrowse() that contains
  818. //                ALL fields in a database
  819.  
  820. FUNCTION LdBrowFlds
  821.          LOCAL nCtr
  822.          LOCAL aFldList := {}
  823.          LOCAL aStruct  := DBSTRUCT()
  824.  
  825.          FOR nCtr := 1 TO LEN(aStruct)
  826.              AADD(aFldList,{nCtr,aStruct[nCtr][1]})
  827.          NEXT
  828. RETURN(aFldList)
  829.  
  830. // Turn off the WBrowse() buttons - don't want the user doing anything
  831. // silly while we are doing something else from within this browse...
  832.  
  833. STATIC FUNCTION ButtonsOff(aButtons)
  834.        LOCAL nCtr
  835.  
  836.        FOR nCtr := 1 TO 9
  837.            EnableWindow(aButtons[nCtr][1],.F.)
  838.        NEXT
  839. RETURN(NIL)
  840.  
  841. // Turn the buttons back on when we return to the WBrowse()
  842.  
  843. STATIC FUNCTION ButtonsOn(aButtons)
  844.        LOCAL nCtr
  845.  
  846.        FOR nCtr := 1 TO 9
  847.            EnableWindow(aButtons[nCtr][1],aButtons[nCtr][2])
  848.        NEXT
  849. RETURN(NIL)
  850.  
  851. // Called by Message() to draw a sunken bar at the bottom of a window
  852.  
  853. FUNCTION MsgBar(hWnd)
  854.          LOCAL nLeft, nTop, nRight, nBottom, aCRect, hOldPen
  855.          LOCAL hDC, hColor, hBlackPen, hGreyPen, hWhitePen
  856.  
  857.          hDc     := GetDC(hWnd)
  858.          aCRect  := GetClientRect(hWnd)
  859.          nLeft   := 0
  860.          nTop    := aCRect[4]-26
  861.          nRight  := aCRect[3]
  862.          nBottom := nTop+26
  863.  
  864.          hBlackPen := CreatePen(PS_SOLID,1,RGB(0,0,0))
  865.          hGreyPen  := CreatePen(PS_SOLID,1,RGB(128,128,128))
  866.          hWhitePen := CreatePen(PS_SOLID,1,RGB(255,255,255))
  867.  
  868.          hOldPen   := SelectObject(hDC,hBlackPen)
  869.          Rectangle(hDC,nLeft,nTop,nRight,nBottom)
  870.          hColor := CreateSolidBrush(RGB(192,192,192))
  871.          FillRect(hDC,nLeft,nTop+1,nRight,nBottom,hColor)
  872.  
  873.          SelectObject(hDC,hGreyPen)
  874.          MoveTo(hDC,nLeft+10,nBottom-4)
  875.          LineTo(hDC,nLeft+10,nTop+4)
  876.          LineTo(hDC,nRight-10,nTop+4)
  877.  
  878.          SelectObject(hDC,hWhitePen)
  879.          LineTo(hDC,nRight-10,nBottom-4)
  880.          LineTo(hDC,nLeft+10,nBottom-4)
  881.  
  882.          SelectObject(hDC,hOldPen)
  883.          ReleaseDC(hWnd,hDC)
  884.          DeleteObject(hColor)
  885.          DeleteObject(hBlackPen)
  886.          DeleteObject(hGreyPen)
  887.          DeleteObject(hWhitePen)
  888. RETURN(NIL)
  889.  
  890. // Display a message in a sunken message bar at the bottom of a window
  891.  
  892. FUNCTION Message(hWnd,cText)
  893.          LOCAL nLeft, nTop, nRight, nBottom, aCRect, hDC
  894.          LOCAL nOldBkClor
  895.  
  896.          aCRect  := GetClientRect(hWnd)
  897.          nLeft   := 0
  898.          nTop    := aCRect[4]-26
  899.          nRight  := aCRect[3]
  900.          nBottom := nTop+28
  901.  
  902.          hDC        := GetDC(hWnd)
  903.          nOldBkClor := SetBkColor(hDC,RGB(192,192,192))
  904.          MsgBar(hWnd)
  905.          DrawText(hDC,cText,;
  906.                      {nLeft+10,nTop+5,nRight-11,nBottom-5},DT_CENTER)
  907.          SetBkColor(hDC,nOldBkClor)
  908.          ReleaseDC(hWnd,hDC)
  909. RETURN(NIL)
  910.  
  911. // WinBox() - Draws sunken/raised boxes
  912. //            See WBrowse() for examples of usage
  913.  
  914. FUNCTION WinBox(hWnd,nX,nY,nCX,nCY,nStyle,nBorder,nPixHD,nTBbord,nLRBord)
  915.          LOCAL nLeft, nTop, nRight, nBottom, hOldPen, hTopPen, hBotPen
  916.          LOCAL hDC, hFillPen, hBlackPen, hGreyPen, hWhitePen, nCtr
  917.  
  918.          hDc       := GetDC(hWnd)
  919.          nLeft     := nX
  920.          nTop      := nY
  921.          nRight    := nCX
  922.          nBottom   := nCY
  923.          nStyle    := IIF(nStyle  == NIL, 1 , nStyle)
  924.          nBorder   := IIF(nBorder == NIL, 1 , nBorder)
  925.          nPixHD    := IIF(nPixHD  == NIL, 1 , nPixHD)
  926.  
  927.          hBlackPen := CreatePen(PS_SOLID,1,RGB(0,0,0))
  928.          hGreyPen  := CreatePen(PS_SOLID,1,RGB(128,128,128))
  929.          hWhitePen := CreatePen(PS_SOLID,1,RGB(255,255,255))
  930.          hFillPen  := CreateSolidBrush(RGB(192,192,192))
  931.          hOldPen   := SelectObject(hDC,hBlackPen)
  932.  
  933.          Rectangle(hDC,nLeft,nTop,nRight,nBottom)
  934.          IF nBorder == 1
  935.             FillRect(hDC,nLeft+1,nTop+1,nRight-1,nBottom-1,hFillPen)
  936.             nLeft   += 1
  937.             nTop    += 1
  938.             nRight  -= 2
  939.             nBottom -= 2
  940.          ELSE
  941.             FillRect(hDC,nLeft,nTop,nRight,nBottom,hFillPen)
  942.             ++nLeft
  943.             ++nTop
  944.             --nRight
  945.             --nBottom
  946.          ENDIF
  947.          nLeft   := IIF(nLRBord == NIL,nLeft,nLeft+nLRBord)
  948.          nRight  := IIF(nLRBord == NIL,nRight,nRight-nLRBord)
  949.          nTop    := IIF(nTBBord == NIL,nTop,nTop+nTBBord)
  950.          nBottom := IIF(nTBBord == NIL,nBottom,nBottom-nTBBord)
  951.          IF nStyle == 1
  952.             hTopPen := hGreyPen
  953.             hBotPen := hWhitePen
  954.          ELSE
  955.             hTopPen := hWhitePen
  956.             hBotPen := hGreyPen
  957.          ENDIF
  958.          FOR nCtr := 1 TO nPixHD
  959.              DrawWBox(hDC,hTopPen,hBotPen,nTop,nLeft,nBottom,nRight)
  960.              ++nLeft
  961.              ++nTop
  962.              --nRight
  963.              --nBottom
  964.          NEXT
  965.          SelectObject(hDC,hOldPen)
  966.          ReleaseDC(hWnd,hDC)
  967.          DeleteObject(hFillPen)
  968.          DeleteObject(hBlackPen)
  969.          DeleteObject(hGreyPen)
  970.          DeleteObject(hWhitePen)
  971. RETURN(NIL)
  972.  
  973. STATIC FUNCTION DrawWBox(hDC,hTopPen,hBotPen,nTop,nLeft,nBottom,nRight)
  974.  
  975.        SelectObject(hDC,hTopPen)
  976.        MoveTo(hDC,nLeft,nBottom)
  977.        LineTo(hDC,nLeft,nTop)
  978.        LineTo(hDC,nRight,nTop)
  979.        SelectObject(hDC,hBotPen)
  980.        LineTo(hDC,nRight,nBottom)
  981.        LineTo(hDC,nLeft,nBottom)
  982. RETURN(NIL)
  983.  
  984. // SkipFor(), FindFirst(), FindLast() - Used when browsing a sub-set of
  985. // a database
  986.  
  987. FUNCTION SkipFor(nSkip, xStart,i_co_bk)
  988.          LOCAL nMoved
  989.  
  990.          nMoved := 0
  991.          IF ( LASTREC() == 0 )
  992.             RETURN (nMoved)
  993.          ENDIF
  994.  
  995.          DO CASE
  996.             CASE ( nSkip == 0 )
  997.                  SKIP 0
  998.  
  999.             CASE ( nSkip > 0 )
  1000.                  // Because of non-unique index keys, we must actually move past end
  1001.                  // and then come back to make sure we're on the last record that
  1002.                  // is in the range.
  1003.                  DO WHILE ( nMoved <= nSkip .AND. EVAL(i_co_bk)=xStart ) .AND. !EOF()
  1004.                     SKIP 1
  1005.                     nMoved++
  1006.                  ENDDO
  1007.  
  1008.                  // Move back to last record that is in the range
  1009.                 SKIP -1
  1010.                 nMoved--
  1011.  
  1012.          CASE ( nSkip < 0 )
  1013.               // Because of non-unique index keys, we must actually move past end
  1014.               // and then come back to make sure we're on the last record that
  1015.               // is in the range.
  1016.               DO WHILE ( nMoved > nSkip .AND. EVAL(i_co_bk) >= xStart )
  1017.                  SKIP -1
  1018.                  IF BOF()
  1019.                     EXIT
  1020.                  ENDIF
  1021.                  nMoved--
  1022.               ENDDO
  1023.  
  1024.               IF (EVAL(i_co_bk) < xStart)
  1025.                  SKIP
  1026.                  nMoved++
  1027.               ENDIF
  1028.          ENDCASE
  1029. RETURN(nMoved)
  1030.  
  1031. FUNCTION FindFirst( xValue )
  1032.          LOCAL lSeek
  1033.  
  1034.          lSeek := SET(_SET_SOFTSEEK,.T.)
  1035.  
  1036.          SEEK xValue
  1037.          IF !FOUND()
  1038.             EmptyErr()
  1039.             RETURN(.F.)
  1040.          ENDIF
  1041.          SET(_SET_SOFTSEEK, lSeek)
  1042. RETURN(.T.)
  1043.  
  1044. FUNCTION FindLast( xValue,i_co_bk )
  1045.          LOCAL lSeek
  1046.          lSeek := SET(_SET_SOFTSEEK,.T.)
  1047.  
  1048.          SEEK xValue
  1049.          IF !FOUND()
  1050.             EmptyErr()
  1051.             RETURN(.F.)
  1052.          ENDIF
  1053.          // Move through all matching index keys
  1054.          DO WHILE EVAL(i_co_bk) <= xValue  .AND. ! (EOF())
  1055.             SKIP
  1056.          ENDDO
  1057.          // and come back to last record in range
  1058.          SKIP -1
  1059.          SET(_SET_SOFTSEEK, lSeek)
  1060. RETURN(NIL)
  1061.  
  1062. // Generic message display function.  'W' = Warning, 'E' = Error
  1063. // anything else is a prompt or just information for the user
  1064.  
  1065. FUNCTION ErrorMsg(cMarr,cType)
  1066.          LOCAL nResponse
  1067.  
  1068.          DO CASE
  1069.             CASE cType = 'W'
  1070.                  MessageBeep(MB_ICONEXCLAMATION)
  1071.                  nResponse := MessageBox(GetFocus(),cMarr,"Caution!",;
  1072.                               MB_OKCANCEL+MB_ICONEXCLAMATION)
  1073.             CASE cType = 'E'
  1074.                  MessageBeep(MB_ICONHAND)
  1075.                  nResponse := MessageBox(GetFocus(),cMarr,"Problem!",;
  1076.                               MB_OK+MB_ICONHAND)
  1077.             OTHERWISE
  1078.                  MessageBeep(MB_OK)
  1079.                  nResponse := MessageBox(GetFocus(),cMarr,"Please!",;
  1080.                               MB_OKCANCEL+MB_ICONASTERISK)
  1081.          ENDCASE
  1082. RETURN(nResponse)
  1083.  
  1084. // Called when you try to browse an empty database or the user
  1085. // deletes all records from within a browse
  1086.  
  1087. FUNCTION EmptyErr
  1088.  
  1089.          ErrorMsg("FILE/LIST IS EMPTY",'E')
  1090. RETURN(NIL)
  1091.