home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 5 / 05.iso / a / a012 / 1.ddi / APPENDIX.EXE / MAXIBROW.PRG < prev    next >
Encoding:
Text File  |  1991-05-01  |  45.7 KB  |  1,458 lines

  1. /*
  2.    File: MAXIBROW.PRG
  3.    Author: Craig Yellick
  4.    Excerpted from "Clipper 5: A Developer's Guide"
  5.    Copyright (c) 1991 M&T Books
  6.                       501 Galveston Drive
  7.                       Redwood City, CA 94063-4728
  8.                       (415) 366-3600
  9.  
  10.    This is a general purpose database browser which attempts to use ALL
  11.    of the TBrowse features in a single program. Yikes. The result is not
  12.    intended to be a real routine that you'd implement in your
  13.    applications. It goes to sometimes ridiculous lengths to incorporate
  14.    TBrowse functions. It's intended to be a TBrowse playground of sorts
  15.    where you can see various instance variables and methods in action
  16.    and in the context of a large application rather than in small,
  17.    isolated examples. Use it to experiment-- make code changes, add new
  18.    features, change constants, do whatever you find interesting. The
  19.    more time you spend playing with TBrowse the better you'll understand
  20.    it.
  21.  
  22.    The use of color and the formatting of the various status messages is
  23.    deliberately spartan. I didn't want colorful and graphical things to
  24.    get in the way of the underlying concepts. Plus, there's enough code
  25.    as it is! The screen is by design very blah until you start fiddling
  26.    around with highlighting and selection functions, or supply a
  27.    database with dates, negative numbers, logical values or memo fields.
  28.    Then you'll probably wish there weren't so many colors. I predict
  29.    the incredibly flexible TBrowse color scheme will be responsible for
  30.    a whole new generation of garish application screens. See the
  31.    following comments for more details.
  32.  
  33.    ------------------------------------------------------------------------
  34.  
  35.    All the features of this program are tied to various keystrokes. The
  36.    source code comments surrounding the features describe the techniques
  37.    being used. Briefly, here's what MaxiBrow can do:
  38.  
  39.  
  40.    Navigation Keys
  41.    ===============
  42.  
  43.    Up, Down, Left, Right: Move one cell in any direction, will scroll
  44.    rows up/down and pan columns left/right as needed.
  45.  
  46.    Home, End: Jump to first or last visible column.
  47.  
  48.    Control-Home, Control-End: Jump to very first or very last column.
  49.  
  50.    PageUp, PageDown: Scroll one screenful up or down.
  51.  
  52.    Control-PageUp, Control-PageDown: Jump to very top or very bottom of
  53.    database.
  54.  
  55.    Tab, Shift-Tab: Pan the screen left or right to see more columns.
  56.  
  57.  
  58.    Function Keys
  59.    =============
  60.  
  61.    F1   Display record numbers/columns visited during "help". Each time
  62.         you press F1 the current record number is added to a list. A
  63.         column-based counter is also incremented. Press F1 periodically
  64.         as you move through the database.  F1 also displays a brief
  65.         summary of what most the other keystrokes do.
  66.  
  67.    F2   Toggle between color and monochrome color schemes.
  68.  
  69.    F3   Insert a copy of current column.
  70.  
  71.    F4   Delete current column. Can't delete the last non-frozen column.
  72.  
  73.    F5   Move window. Up/Down/Left/Right work as expected. Press any
  74.         other key to finish the move. Built-in logic will prevent you
  75.         from moving the window completely off the screen. Press
  76.         backspace to restore coordinates to initial settings.
  77.  
  78.    F6   Resize window. Up/Left make window larger, Down/Right make it
  79.         smaller. Press any other key to finish the sizing. Built-in
  80.         logic will prevent you from making window too small to be
  81.         useful. Press backspace to restore coordinates to initial
  82.         settings.
  83.  
  84.    F7   Rotate column positions. Non-frozen columns are shifted to the
  85.         left, first column is moved to the far right. Can press F7
  86.         repeatedly to cycle through as many columns as you wish.  Press
  87.         Shift-F7 to rotate columns in the other direction.
  88.  
  89.    F8   Toggle drag-highlight navigation mode. After pressing F8 all
  90.         cursor movements will enlarge a highlight box on the screen. Due
  91.         to limitations inherent in TBrowse's colorRect() method the
  92.         highlighted box is for the visible screen, only.
  93.  
  94.    F9   Highlight current column. Due to aforementioned colorRect()
  95.         limitation the highlight extends only for the rows currently
  96.         visible.
  97.  
  98.    F10  Highlight the current row.  Entire row is highlighted, including
  99.         columns that are not currently visible.
  100.  
  101.    ESC  Finished browsing, you'll be asked to confirm that you want to
  102.         exit. Press Y to exit or any other key to remain in MaxiBrow.
  103.  
  104.  
  105.    Editing Keys
  106.    ============
  107.  
  108.    Alt-U           Toggle the global SET DELETED flag on/off.
  109.    Control-U       Toggle the individual record deletion flag on/off.
  110.  
  111.    Enter           Edit current cell (including memo fields).
  112.    Control-Enter   Clear contents of current cell then edit (but not memo).
  113.    !..chr(255)     Edit current cell, start with current keystroke.
  114.  
  115.  
  116.    Other Misc Keys
  117.    ===============
  118.  
  119.    Control-Left   Make current column more narrow.  Can't make column
  120.                   smaller than one character wide.
  121.  
  122.    Control-Right  Make current column more wide.  Can't make column
  123.                   wider than width of original field in database.
  124.  
  125.    Backspace      Clear all highlights and selections, reset all cargo
  126.                   instance variables, refresh entire screen. Used
  127.                   primarily to recover after making a mess.
  128.  
  129.    Spacebar       Toggle record selection check-mark on and off. Counter
  130.                   at bottom of screen indicates how many have been
  131.                   selected. Unlike the F8, F9 and F10 highlighting
  132.                   functions, the check-marks are not limited to the
  133.                   currently visible screen. Backspace clears all
  134.                   selections.
  135.  
  136.  
  137.    Other Automatic Things
  138.    ======================
  139.  
  140.    Other things will happen automatically depending on the contents of
  141.    the database being browsed.
  142.  
  143.    *  Negative numbers will be in red.
  144.  
  145.    *  Logically false values will also be in red.
  146.  
  147.    *  All date columns will be in magenta.
  148.  
  149.    *  "Thud" sounds will be heard when you attempt to scroll or pan
  150.       beyond the physical boundaries of the database.
  151.  
  152.    *  A relative position indicator, or "elevator", will be displayed on
  153.       the left side of the window if the database contains more records
  154.       than can fit in the window. Important note: The indicator is
  155.       maintained independent of the index, if one is being used. This is
  156.       implemented very elegantly thanks to the wonderful concept
  157.       of TBrowse "skipBlocks".
  158.  
  159.    ------------------------------------------------------------------------
  160.  
  161.    Compiling and Running
  162.    =====================
  163.  
  164.        Compile with: /n /w
  165.  
  166.        Usage (from DOS): maxibrow datafile [indexfile]
  167.  
  168.    ------------------------------------------------------------------------
  169.  
  170.    Comments
  171.    ========
  172.  
  173.    There's a tremendous amount of fancy browsing going on in here and to
  174.    help keep it all straight I've included lots of comments. Block-style
  175.    comments, like the ones you're reading here, are used to describe
  176.    large sections of code and also are used as headers to functions.
  177.    Single line comments are used when the comments are directed at the
  178.    next line (or small number of lines). Two blank lines separate
  179.    logical groups of source code, single blank lines separate small runs
  180.    of related source code within a major group.
  181.  
  182.    Other helpful conventions:  If a function name is in all lowercase
  183.    letters then it's part of the built-in Clipper functions. If a
  184.    function name is in "proper" case it's a user-defined function and
  185.    can be found in this source code file. Array names end with a
  186.    trailing underscore. Manifest constants are in all upper case.
  187.  
  188.    ------------------------------------------------------------------------
  189.  
  190.    File Contents
  191.    =============
  192.  
  193.    Main(cFilename [, cIndexname])
  194.      Main browsing program. From DOS, send database filename and option
  195.      index filename.
  196.  
  197.    Proper(cString)
  198.      Given string, returns "properized" string where first character is
  199.      made uppercase. Used in this program to make the database
  200.      fieldnames look better.
  201.  
  202.    YesNo(cMsg [, nSeconds])
  203.      Given a message to display and optional maximum number of seconds
  204.      to wait, return .t. if "Y" or "y" is pressed, .f. if not.
  205.  
  206.    HelpStat(oBrowse)
  207.      Given a browse object, use browse and current column cargo
  208.      variables to display stats about how often the F1-HELP key was
  209.      pressed and which records were visited. (Not a general purpose
  210.      help function, it's specific to this goofy program.)
  211.  
  212.    FitInBox(nTop, nLeft, nBottom, nRight, aMessage)
  213.      Draw a box at specified coordinates and display array of message
  214.      lines within the boundaries of the box, trim to fit if needed.
  215.  
  216.    RecPosition([cHow] [, nHowmany])
  217.      This is a single function used for all three TBrowse data
  218.      positioning blocks. Given the type of movement required (top,
  219.      bottom or skip) and how many records to skip, function performs all
  220.      the necessary database movements. If called without any parameters
  221.      the function returns the current record's position within the
  222.      database. The position is maintained independent of the index, if
  223.      any.
  224.  
  225.    RecDisplay(nRec, aList, lDeleted)
  226.      This function is used by the record-# column data retrieval block
  227.      to format the record number for display. It handles the check-mark.
  228.      The current record number is passed along with an array containing
  229.      record numbers that should be displayed with check-marks. Records
  230.      marked for deletion have an asterisk in the column.
  231.  
  232.    aCount(aX, bCountBlock [, nStart] [, nCount])
  233.      Given an array, use the supplied code block to count the number
  234.      of elements that match a condition. Optional starting element
  235.      number and number of elements to evaluate. Returns count.
  236.  
  237.    aInsert(aX, nPos [, xValue])
  238.      Increase size of array by inserting new value in specified
  239.      position. This function combines the effects of the ains() and
  240.      asize() functions.
  241.  
  242.    aDelete(aX, nPos)
  243.      Decrease size of array by deleting element in specified position.
  244.      This function combines the effects of the adel() and asize()
  245.      functions.
  246.  
  247.    aRotate(aX [,lDir])
  248.      Rotates elements in array passed as parameter. Elements are shifted
  249.      up one. First element is shifted to last element. Optional
  250.      direction to shift, default is .t. and implies shift up, .f. is
  251.      down. Returns nil.
  252.  
  253.    ColumnColor(xValue)
  254.      Given a value of any type, returns a color-selection array based on
  255.      the type and value. Used to install logic for displaying certain
  256.      data types or values in special colors. For example, negative
  257.      numbers in red. Any number of cases can be installed.
  258.  
  259.    Navigate(objBrowse, nKey)
  260.      Given a browse object and a potential navigation key, function
  261.      searches its internal list of keystrokes and associated browse
  262.      navigation methods. If key is found the method is sent to the
  263.      browse and the function returns .t., if not found, function
  264.      returns .f. and no action is taken.
  265.  
  266.    EditCell(objBrowse, cFieldName, cColor)
  267.      A general-purpose browse cell contents editor, works with all
  268.      database field types including memo fields. All editing, including
  269.      memo-edit, occurs within the browse window regardless of its
  270.      current size or position. On exit from cell the function passes
  271.      along browse navigation when appropriate.
  272.  
  273.    ------------------------------------------------------------------------
  274. */
  275.  
  276.  
  277. /*
  278.    Establish some helpful preprocessor directives.
  279. */
  280. #include "INKEY.CH"
  281. #include "DBSTRUCT.CH"
  282.  
  283. #define K_SPACE      32
  284. #define K_CTRL_ENTER 10
  285.  
  286. #define THUD     tone(60, 0.5)
  287. #define BADKEY   tone(480, 0.25); tone(240, 0.25)
  288. #define lstr(n)  ltrim(str(n))
  289.  
  290. #define INIT_R1   4
  291. #define INIT_R2  (maxrow() -4)
  292. #define INIT_C1  10
  293. #define INIT_C2  (maxcol() -10)
  294.  
  295. #define FREEZE_COL  1
  296.  
  297.  
  298. /*
  299.     Default color scheme for all columns.
  300.     (Used with instance variable browse:colorSpec.)
  301.  
  302.     1: Regular cell
  303.     2: Highlighted regular cell
  304.     3: Block-selection cell
  305.     4: Highlighted block-selection cell
  306.     5: Checked record-#
  307.     6: Highlighted, checked record-#
  308.     7: Regular negative numbers and .F. values
  309.     8: Highlighted negative numbers and .F. values
  310.     9: Regular dates
  311.    10: Highlighted dates
  312.  
  313.                     1    2     3    4      5    6     7     8      9    10
  314. */
  315. #define COL_COLOR "W/N, N/W, W+/B, B/W, W+/G, B+/G, R+/N, W+/R, RB+/N, W+/RB"
  316. #define COL_MONO  "W/N, N/W, N/W,  W*/N, W/N, W+/N, W+/N, N/W,  W+/N,  N/W"
  317.  
  318. /*
  319.    The following make it easier to use the browse:colorSpec.
  320.    They correspond to the color scheme defined above.
  321. */
  322. #define REGULAR_CELL  {1,2}
  323. #define BLOCKED_CELL  {3,4}
  324. #define CHECKED_CELL  {5,6}
  325. #define NEGVAL_CELL   {7,8}
  326. #define DATE_CELL     {9,10}
  327.  
  328. //  This next one is for the GET/READ feature,
  329. //  defined here for consistency with rest of browse.
  330. #define EDIT_COLOR    "W+/G"
  331.  
  332. /*-----------------------------------------------------------------------*/
  333.  
  334. function Main(filename, indexname)
  335. /*
  336.    Main browsing function.
  337. */
  338.  
  339. local r1, r2, c1, c2, scr, fileDescr
  340. local column, browse, key
  341. local stru_, recs_
  342. local s, n, w
  343. local hiRow, hiCol, hiRow2, hiCol2
  344. local dragMode := .f., delSwitch := .f.
  345. local temp, useColor, relPos
  346.  
  347.  
  348.   //  Check that command line parameters are kosher.
  349.   if filename = nil
  350.     ? "Must specify a database filename and optionally an index filename."
  351.     quit
  352.   elseif .not. (file(filename) .or. file(filename +".DBF"))
  353.     ? "Database file does not exist."
  354.     quit
  355.   endif
  356.  
  357.  
  358.   //  Get rid of the cursor and start with a clean slate.
  359.   setcursor(0)
  360.   @ 0,0 clear
  361.   set scoreboard off
  362.  
  363.  
  364.   //  Open the database and index.
  365.   use (filename) new
  366.   fileDescr := "File: " +upper(filename)
  367.   if (indexname <> nil) .and. (file(indexname) ;
  368.                           .or. file(indexname +".NTX"))
  369.     set index to (indexname)
  370.     fileDescr += ", Index: " +upper(indexname)
  371.   endif
  372.  
  373.  
  374.   //  Assign initial browse window coordinates.
  375.   r1 := INIT_R1
  376.   r2 := INIT_R2
  377.   c1 := INIT_C1
  378.   c2 := INIT_C2
  379.   @ r1 -2, c1 +((c2 -(c1 +len(fileDescr))) /2) say fileDescr
  380.   @ r1 -1, c1 -1, r2 +1, c2 +1 box "▄▄▄█▀▀▀█ "
  381.  
  382.  
  383.   //  Create a new browse object.
  384.   browse := TBrowseNew()
  385.  
  386.  
  387.   /*
  388.      Things that affect the entire browse.
  389.   */
  390.   //  Assign window coordinates.
  391.   browse:nTop := r1
  392.   browse:nBottom := r2
  393.   browse:nLeft := c1
  394.   browse:nRight := c2
  395.   //  Assign heading, footing and column separators.
  396.   browse:headSep := "═╤═"
  397.   browse:colSep  := " │ "
  398.   browse:footSep := "═╧═"
  399.   //  Cargo will be used later, associated with the F1 key.
  400.   browse:cargo := {}
  401.  
  402.  
  403.   //  Assign default color scheme according to adapter card.
  404.   useColor := iscolor()
  405.   browse:colorSpec := if(useColor, COL_COLOR, COL_MONO)
  406.  
  407.  
  408.   //  All three position blocks get routed through a single function.
  409.   //  This allows us to do some amazing things, later.
  410.   browse:goTopBlock    := { | | RecPosition("top")     }
  411.   browse:goBottomBlock := { | | RecPosition("bottom")  }
  412.   browse:skipBlock     := { |n| RecPosition("skip", n) }
  413.  
  414.  
  415.   /*
  416.      First column will be the record number.
  417.      We're going to do some tricky things with this column
  418.      so setting it up is more complex than normally necessary.
  419.   */
  420.   //  This array will keep track of the records visited each
  421.   //  time the F1-HELP key is pressed.
  422.   recs_ := {}
  423.  
  424.   //  Create a new column object.
  425.   column := TBColumnNew()
  426.  
  427.   //  The RecDisplay() function provides the check-mark toggle
  428.   //  that's associated with the spacebar key.
  429.   column:block := { || RecDisplay(recno(), recs_, deleted()) }
  430.  
  431.   //  The footing line will be used to display field type and width.
  432.   column:heading := " Rec-#"
  433.   column:footing := "  Type:; Col-#:"
  434.  
  435.   //  We want this column to have a different color when "checked".
  436.   column:colorBlock := { |r| if("√" $ r, CHECKED_CELL, REGULAR_CELL) }
  437.  
  438.   //  Column cargo is used later store a count of how many times
  439.   //  the F1-HELP key was pressed in each column.
  440.   column:cargo := 0
  441.  
  442.   //  Add the record-# column just defined to the main browse object.
  443.   browse:addColumn(column)
  444.  
  445.  
  446.   /*
  447.      The remainder of the columns in the browse will be comprised
  448.      of the fields in the current database.
  449.   */
  450.  
  451.   //  For each field in the database...
  452.   //    (See documentation for the dbstruct() function and
  453.   //    details about what the stru_ array contains.)
  454.   stru_ := dbstruct()
  455.   for n := 1 to len(stru_)
  456.  
  457.     //  Create a column object for each field.
  458.     column := TBColumnNew()
  459.  
  460.     //  Heading is the field name, footing is the type and width.
  461.     //  For example, a 12 character field would be "C:12".
  462.     //  Columns are numbered in a second line in the footing, (n).
  463.     column:heading := Proper(lower(stru_[n, DBS_NAME]))
  464.     column:footing := stru_[n, DBS_TYPE] +":" +lstr(stru_[n, DBS_LEN]) ;
  465.                       +";(" +lstr(n) +")"
  466.  
  467.     //  Date-type columns get special color scheme.
  468.     if stru_[n, DBS_TYPE] = "D"
  469.       column:defColor := DATE_CELL
  470.     else
  471.       //  Make some of the colors depend on cell value.
  472.       column:colorBlock := { |v| ColumnColor(v) }
  473.     endif
  474.  
  475.  
  476.     //  Data-retrieval blocks based simply on the field value.
  477.     //  Don't create a block for memo fields.
  478.     if stru_[n, DBS_TYPE] <> "M"
  479.       column:block := fieldblock(stru_[n, DBS_NAME])
  480.       column:width := stru_[n, DBS_LEN]
  481.     else
  482.       column:block := { || " memo " }
  483.       column:width := 6
  484.     endif
  485.  
  486.  
  487.     //  Initialize cargo, we'll be using it later.
  488.     column:cargo := 0
  489.  
  490.  
  491.     //  First column after frozen one (in this case, the
  492.     //  record-#) gets a different set of separators to
  493.     //  better divide "frozen" columns from the scrollable ones.
  494.     //
  495.     if n = FREEZE_COL
  496.       column:headSep := "═╦═"
  497.       column:colSep  := " ║ "
  498.       column:footSep := "═╩═"
  499.     endif
  500.  
  501.     //  Add the new column object to the main browse object.
  502.     browse:addColumn(column)
  503.   next n
  504.  
  505.  
  506.   //  Freeze the first column (the record-#).
  507.   browse:freeze := FREEZE_COL
  508.  
  509.  
  510.   //  Move cell pointer beyond frozen column(s).
  511.   browse:colPos := browse:freeze +1
  512.  
  513.  
  514.   //  We'll handle our own highlighting, thank you.
  515.   browse:autoLite := .f.
  516.  
  517.  
  518.   //  Used later to mark relative pointer position on left edge of window.
  519.   relPos := 1
  520.  
  521.  
  522.   /*
  523.      Finally!  We're done getting everything set up.
  524.      Allow user to play with the browse until exit is confirmed.
  525.   */
  526.   do while .t.
  527.  
  528.  
  529.     //  Can't move beyond last column.
  530.     //  This condition will be fixed up by stabilize(),
  531.     //  so we must check for it prior to stabilization.
  532.     if browse:colPos > browse:colCount
  533.       THUD
  534.     endif
  535.  
  536.  
  537.     //  Can't move into frozen column.
  538.     if browse:colPos <= browse:freeze
  539.       THUD
  540.       browse:colPos := browse:freeze +1
  541.     endif
  542.  
  543.  
  544.     //  Stabilize the display, if it needs to be. Use of the nextkey()
  545.     //  function allows us to exit the loop if a keystroke occurs, but
  546.     //  without disturbing the contents of the keyboard buffer.
  547.     if .not. browse:stable
  548.       @ 0,0 say "STABILIZING..."
  549.       do while .not. browse:stabilize()
  550.         if nextkey() <> 0
  551.           exit
  552.         endif
  553.       enddo
  554.       @ 0,0
  555.     endif
  556.  
  557.  
  558.     //  These get updated during the stabilize,
  559.     //  so they can't be checked until after stabilize finishes.
  560.     if browse:hitTop .or. browse:hitBottom
  561.       THUD
  562.     endif
  563.  
  564.  
  565.     //  If in "drag the highlight around" mode, update
  566.     //  the rectangle coordinates and display it.
  567.     if dragMode
  568.       hiRow  := min(hiRow,  browse:rowPos)
  569.       hiCol  := min(hiCol,  browse:colPos)
  570.       hiRow2 := max(hiRow2, browse:rowPos)
  571.       hiCol2 := max(hiCol2, browse:colPos)
  572.       browse:colorRect({hiRow, hiCol, hiRow2, hiCol2}, BLOCKED_CELL)
  573.     endif
  574.  
  575.  
  576.     //  Update relative position indicator, but only if
  577.     //  there are more records in database than can fit on the screen.
  578.     if lastrec() > browse:rowCount
  579.       @ browse:nTop +2 +relPos, browse:nLeft -1 say "█"
  580.       relPos := min((RecPosition()/lastrec()) *browse:rowCount, ;
  581.                      browse:rowCount -1)
  582.       @ browse:nTop +2 +relPos, browse:nLeft -1 say chr(18) color "I"
  583.     endif
  584.  
  585.     //  Update the "more columns left" and "more columns right" indicators.
  586.     //  Start by clearing existing indicator arrows, if any.
  587.     @ browse:nTop, browse:nLeft  -1 say " " color "I"
  588.     @ browse:nTop, browse:nRight +1 say " " color "I"
  589.     if browse:leftVisible > (browse:freeze +1)
  590.       @ browse:nTop, browse:nLeft  -1 say chr(27) color "I"
  591.     endif
  592.     if browse:rightVisible < browse:colCount
  593.       @ browse:nTop, browse:nRight +1 say chr(26) color "I"
  594.     endif
  595.  
  596.  
  597.     /*
  598.        The bottom three rows of the screen are used to display status
  599.        information about various pieces of the browse and column
  600.        objects. Watch these lines as you navigate in the database.
  601.     */
  602.  
  603.     //  Display info about the browse window.
  604.     @ maxrow() -2, 0
  605.     ?? "Browse: Row " +lstr(browse:rowPos)
  606.     ?? ", Col " +lstr(browse:colPos)
  607.  
  608.     @ maxrow() -1, 0
  609.     ?? "Absolute DBF position: " +lstr(RecPosition())
  610.     ?? "  (" +lstr( round((RecPosition()/lastrec()) *100, 0)) +"%)"
  611.  
  612.     @ maxrow(), 0
  613.     column := browse:getColumn(browse:colPos)
  614.     ?? "Record " +lstr(recno()) +": " +column:heading +" = "
  615.     //
  616.     //  Use of @..SAY will allow long strings to display off
  617.     //  the edge of the screen, rather than wrapping around.
  618.     //
  619.     @ row(), col() say eval(column:block)
  620.  
  621.  
  622.     s := "[ F1:HELP ]"
  623.     @ maxrow() -2, (maxcol() -len(s)) /2 say s
  624.  
  625.  
  626.     s := "Records √-Marked: " +lstr(aCount(recs_, { | e | (e <> nil) }) )
  627.     @ maxrow() -2, maxcol() -len(s) say s
  628.     s := "LastKey = " +lstr(lastkey())
  629.     @ maxrow() -1, maxcol() -len(s) say s
  630.     s := "NextKey = " +lstr(nextkey())
  631.     @ maxrow(), maxcol() -len(s) say s
  632.  
  633.  
  634.     //  Highlight cell pointer and wait for keystroke.
  635.     browse:hilite()
  636.     key := inkey(0)
  637.  
  638.     /*
  639.        Take action on the keystroke. Could be cursor navigation
  640.        or any of a large number of browse-modification features.
  641.     */
  642.     do case
  643.  
  644.  
  645.     //  If the general browse navigation function returns .t.
  646.     //  it means it handled the key for us.
  647.     //
  648.     case Navigate(browse, key)
  649.  
  650.  
  651.     case key = K_CTRL_LEFT  //  Decrease column width (if we can).
  652.       //
  653.       //  stru_[colPos -1] because first column is record number.
  654.       //
  655.       w := browse:getcolumn(browse:colPos):width
  656.       if w > 1
  657.         browse:getcolumn(browse:colPos):width--
  658.         //  Update the footing to reflect the new width.
  659.         browse:getcolumn(browse:colPos):footing ;
  660.          := stru_[browse:colPos -1, DBS_TYPE] +":" +lstr(--w) ;
  661.          +";(" +lstr(browse:colPos) +")"
  662.         browse:configure()
  663.       else
  664.         THUD
  665.       endif
  666.  
  667.  
  668.     case key = K_CTRL_RIGHT  //  Increase column width (if we can).
  669.       //
  670.       //  stru_[colPos -1] because first column is record number.
  671.       //
  672.       w := browse:getcolumn(browse:colPos):width
  673.       if w < stru_[browse:colPos -1, DBS_LEN]
  674.         browse:getcolumn(browse:colPos):width++
  675.         //  Update the footing to reflect the new width.
  676.         browse:getcolumn(browse:colPos):footing ;
  677.          := stru_[browse:colPos -1, DBS_TYPE] +":" +lstr(++w) ;
  678.          +";(" +lstr(browse:colPos) +")"
  679.         browse:configure()
  680.       else
  681.         THUD
  682.       endif
  683.  
  684.  
  685.     case key = K_F1          //  Display help/cargo status.
  686.       //
  687.       HelpStat(browse)
  688.  
  689.  
  690.     case key = K_F2          //  Toggle colorSpec between color and mono.
  691.       //
  692.       useColor := .not. useColor
  693.       browse:colorSpec := if(useColor, COL_COLOR, COL_MONO)
  694.       browse:configure()
  695.  
  696.  
  697.     case key = K_F3          //  Insert copy of current column.
  698.       //
  699.       //  stru_[colPos -1] because first column is record number.
  700.       //
  701.       //  Must adjust the stru_ array so it stays accurate.
  702.       //
  703.       aInsert(stru_, browse:colPos -1, stru_[browse:colPos -1])
  704.       browse:insColumn(browse:colPos, browse:getColumn(browse:colPos))
  705.  
  706.  
  707.     case key = K_F4          //  Delete current column.
  708.       //
  709.       //  stru_[colPos -1] because first column is record number.
  710.       //
  711.       //  Don't allow deletion of last non-frozen column.
  712.       //  Must adjust the stru_ array so it stays accurate.
  713.       //
  714.       if browse:colCount > (browse:freeze +1)
  715.         aDelete(stru_, browse:colPos -1)
  716.         browse:delColumn(browse:colPos)
  717.       else
  718.         THUD
  719.       endif
  720.  
  721.  
  722.     case key = K_F5          //  Move the window.
  723.       //
  724.       //  Don't allow window to be pushed completely off the screen,
  725.       //  force atleast a few rows and columns to say visible, TBrowse
  726.       //  is capable of hanging the computer under certain oddball
  727.       //  situations.
  728.       //
  729.       scr := savescreen(0,0,maxRow(),maxCol())
  730.       @ 0,0
  731.       @ 0,0 say "Move window: " +chr(18) +" " +chr(29)
  732.       do while .t.
  733.         @ r1 -1, c1 -1, r2 +1, c2 +1 box replicate("·", 8)
  734.         key := inkey(0)
  735.         restscreen(0,0,maxRow(),maxCol(), scr)
  736.         do case
  737.         case key = K_UP
  738.           if r2 > 4
  739.             r1--
  740.             r2--
  741.           else ; THUD; endif
  742.         case key = K_DOWN
  743.           if r1 < (maxRow() -4)
  744.             r1++
  745.             r2++
  746.           else ; THUD; endif
  747.         case key = K_LEFT
  748.           if c2 > 10
  749.             c1--
  750.             c2--
  751.           else ; THUD; endif
  752.         case key = K_RIGHT
  753.           if c1 < (maxCol() -10)
  754.             c1++
  755.             c2++
  756.           else ; THUD; endif
  757.         case key = K_BS  // Restore initial values
  758.           r1 := INIT_R1
  759.           r2 := INIT_R2
  760.           c1 := INIT_C1
  761.           c2 := INIT_C2
  762.         otherwise
  763.           exit
  764.         endcase
  765.       enddo
  766.       restscreen(0,0,maxRow(),maxCol(), scr)
  767.       @ browse:nTop -2, browse:nLeft -1 ;
  768.         clear to browse:nBottom +1, browse:nRight +1
  769.       @ r1 -2, c1 +((c2 -(c1 +len(fileDescr))) /2) say fileDescr
  770.       @ r1 -1, c1 -1, r2 +1, c2 +1 box "▄▄▄█▀▀▀█ "
  771.       browse:nTop := r1
  772.       browse:nBottom := r2
  773.       browse:nLeft := c1
  774.       browse:nRight := c2
  775.  
  776.  
  777.     case key = K_F6         //   Resize the window.
  778.       //
  779.       //  Don't allow resize unless entire window is visible,
  780.       //  TBrowse might hang the computer if things get too wierd.
  781.       //  Also, don't let size get too small or too large.
  782.       //
  783.       if (r1 < 0) .or. (c1 < 0) ;
  784.          .or. (r2 > maxRow()) .or. (c2 > maxCol())
  785.          BADKEY
  786.       else
  787.         scr := savescreen(0,0,maxRow(),maxCol())
  788.         @ 0,0
  789.         @ 0,0 say "Resize window: " +chr(18) +" " +chr(29)
  790.         do while .t.
  791.           @ r1 -1, c1 -1, r2 +1, c2 +1 box replicate("·", 8)
  792.           key := inkey(0)
  793.           restscreen(0,0,maxRow(),maxCol(), scr)
  794.           do case
  795.           case key = K_UP
  796.             if (r2 -r1) < (maxRow() -1)
  797.               r1--
  798.               r2++
  799.             else ; THUD; endif
  800.           case key = K_DOWN
  801.             if (r2 -r1) > 4
  802.               r1++
  803.               r2--
  804.             else ; THUD; endif
  805.           case key = K_LEFT
  806.             if (c2 -c1) < (maxCol() -3)
  807.               c1--
  808.               c2++
  809.             else ; THUD; endif
  810.           case key = K_RIGHT
  811.             if (c2 -c1) > 8
  812.               c1++
  813.               c2--
  814.             else ; THUD; endif
  815.           case key = K_BS  // Restore initial values
  816.             r1 := INIT_R1
  817.             r2 := INIT_R2
  818.             c1 := INIT_C1
  819.             c2 := INIT_C2
  820.           otherwise
  821.             exit
  822.           endcase
  823.         enddo
  824.         restscreen(0,0,maxRow(),maxCol(), scr)
  825.         @ browse:nTop -2, browse:nLeft -1 ;
  826.           clear to browse:nBottom +1, browse:nRight +1
  827.         @ r1 -2, c1 +((c2 -(c1 +len(fileDescr))) /2) say fileDescr
  828.         @ r1 -1, c1 -1, r2 +1, c2 +1 box "▄▄▄█▀▀▀█ "
  829.         browse:nTop := r1
  830.         browse:nBottom := r2
  831.         browse:nLeft := c1
  832.         browse:nRight := c2
  833.       endif
  834.  
  835.  
  836.     case key = K_F7 .or. ;   //  Rotate non-frozen column positions +/-.
  837.          key = K_SH_F7
  838.       //
  839.       @ 0,0 say "ROTATING COLUMNS..."
  840.       if key = K_F7
  841.         temp := browse:getColumn(browse:freeze +1)
  842.         for n := (browse:freeze +1) to (browse:colCount -1)
  843.           browse:setcolumn(n, browse:getColumn(n +1))
  844.         next n
  845.         browse:setcolumn(browse:colCount, temp)
  846.       else
  847.         temp := browse:getcolumn(browse:colCount)
  848.         for n := browse:colCount to (browse:freeze +2) step -1
  849.           browse:setcolumn(n, browse:getcolumn(n -1))
  850.         next n
  851.         browse:setcolumn(browse:freeze +1, temp)
  852.       endif
  853.       //
  854.       //  Also rotate database structure array so
  855.       //  anything that depends on it remains accurate.
  856.       //
  857.       aRotate(stru_, key == K_F7)
  858.       @ 0,0
  859.  
  860.  
  861.     case key = K_F8         //  Drag-highlight mode.
  862.       //
  863.       //  Initialize only if not already in drag-highlight mode.
  864.       //
  865.       if .not. dragMode
  866.         hiRow := hiRow2 := browse:rowPos
  867.         hiCol := hiCol2 := browse:colPos
  868.       endif
  869.       dragMode := .not. dragMode
  870.  
  871.  
  872.     case key = K_F9         //  Highlight current column.
  873.       //
  874.       browse:colorRect({1, browse:colPos, ;
  875.                         browse:rowCount, browse:colPos}, ;
  876.                         BLOCKED_CELL)
  877.  
  878.       //  Move over one column, a convenience feature.
  879.       if browse:colPos > browse:colCount
  880.         *  Wrap to first column?
  881.       else
  882.         browse:right()
  883.       endif
  884.  
  885.  
  886.     case key = K_F10        //  Highlight current row.
  887.       //
  888.       browse:colorRect({browse:rowPos, browse:freeze +1, ;
  889.                         browse:rowPos, browse:colCount}, ;
  890.                         {3,4})
  891.  
  892.       //  Move down one row, a convenience feature.
  893.       if browse:hitBottom
  894.         *  Wrap to top?
  895.       else
  896.         browse:down()
  897.       endif
  898.  
  899.  
  900.     case key = K_BS        //  Clear, zero and refresh everything in sight.
  901.       //
  902.       //  stru_[n -1] because first column is record number.
  903.       //
  904.       @ 0,0 say "CLEANING UP..."
  905.       dragMode := .f.
  906.       recs_ := {}
  907.       for n := (browse:freeze +1) to browse:colCount
  908.         browse:getcolumn(n):cargo := 0
  909.         browse:getcolumn(n):width := stru_[n -1, DBS_LEN]
  910.         browse:getcolumn(n):footing := stru_[n -1, DBS_TYPE] ;
  911.                             +":" +lstr(stru_[n -1, DBS_LEN]) ;
  912.                             +";(" +lstr(n) +")"
  913.       next n
  914.       browse:cargo := {}
  915.       browse:configure()
  916.       @ 0,0
  917.  
  918.  
  919.     case key = K_SPACE    //  Toggle record marker on/off.
  920.       //
  921.       n := ascan(recs_, recno())
  922.       if n = 0
  923.         n := ascan(recs_, nil)
  924.         if n = 0
  925.           aadd(recs_, recno())
  926.         else
  927.           recs_[n] := recno()
  928.         endif
  929.       else
  930.         adel(recs_, n)
  931.       endif
  932.  
  933.       //  Force this row to be refreshed. If user marked it
  934.       //  we want to be certain they're seeing the most up-to-date data.
  935.       browse:refreshCurrent()
  936.  
  937.       //  Move down to next row as a convenience for user.
  938.       browse:down()
  939.  
  940.  
  941.     case key = K_ALT_U    //  Toggle SET DELETED on/off.
  942.       //
  943.       if (delSwitch := .not. delSwitch)
  944.         set deleted on
  945.       else
  946.         set deleted off
  947.       endif
  948.       browse:refreshAll()
  949.  
  950.  
  951.     case key = K_CTRL_U   //  Toggle the record deletion flag.
  952.       //
  953.       if deleted()
  954.         recall
  955.       else
  956.         delete
  957.       endif
  958.       browse:refreshCurrent()
  959.  
  960.  
  961.     case (key = K_ENTER) ;       //  Open current cell for editing.
  962.     .or. (key = K_CTRL_ENTER) ;  //  Clear cell contents and edit.
  963.     .or. (key > K_SPACE)         //  Edit by starting to type.
  964.       //
  965.       EditCell(browse, ;
  966.                stru_[browse:colPos -1, DBS_NAME], ;  //  Field name
  967.                EDIT_COLOR)
  968.  
  969.  
  970.     case key = K_ESC      //  Done browsing.
  971.       //
  972.       //  Turn off hilite, user's attention should be at y/n prompt.
  973.       //
  974.       browse:deHilite()
  975.       if YesNo("Exit? Are you sure?")
  976.         exit
  977.       endif
  978.  
  979.  
  980.     //  Undefined key, be-boop to let user
  981.     //  know that we heard but can't obey.
  982.     //
  983.     otherwise
  984.       BADKEY
  985.     endcase
  986.  
  987.   enddo  //  While browsing.
  988.  
  989.   setcursor(1)
  990.   @ maxrow(), 0
  991.  
  992. return nil
  993.  
  994.  
  995. /*-----------------------------------------------------------------------*/
  996.  
  997.  
  998. function Proper(s)
  999. /*
  1000.    Return "properized" version of string, first letter made uppercase.
  1001.    Used in column headings to make the field names look more nice.
  1002. */
  1003. return upper(left(s, 1)) +substr(s, 2)
  1004.  
  1005.  
  1006. /*-----------------------------------------------------------------------*/
  1007.  
  1008.  
  1009. function YesNo(msg, time)
  1010. /*
  1011.     Display yes/no question message in box centered on screen, wait up
  1012.     to so many seconds before assuming "no". This function takes pains
  1013.     not to disturb the calling routine's screen/color/cursor settings.
  1014. */
  1015. local k, scr, curs, clr
  1016.   scr := savescreen(11,0,13,maxCol())
  1017.   msg := " " +msg +" "
  1018.   curs := setcursor(0)
  1019.   clr := setcolor( if(iscolor(), "GR+/R", "W+*/N") )
  1020.   @ 11, (maxCol()/2) -(len(msg)/2) -1 ;
  1021.     to 13, (maxCol()/2) +(len(msg) /2) double
  1022.   @ 12, (maxCol()/2) -(len(msg)/2) say msg
  1023.   k := inkey(if(time = nil, 0, time))
  1024.   restscreen(11,0,13,maxCol(), scr)
  1025.   setcolor(clr)
  1026.   setcursor(curs)
  1027. return (chr(k) $ "Yy")
  1028.  
  1029.  
  1030. /*-----------------------------------------------------------------------*/
  1031.  
  1032.  
  1033. function HelpStat(b)
  1034. /*
  1035.    Display help and status screen. You can do pretty well anything you
  1036.    want for "help". In this case we're displaying some interesting
  1037.    stats about where the cell pointer was sitting when help was pressed.
  1038. */
  1039. local clr, scr := savescreen(0, 0, maxrow(), maxcol())
  1040.  
  1041.   //  Look for current record number in the browse cargo,
  1042.   //  add it to list of records if not found.
  1043.   if ascan(b:cargo, recno()) = 0
  1044.     aadd(b:cargo, recno())
  1045.   endif
  1046.  
  1047.   @ 0, 0 clear
  1048.   @ 0, 0 to 4, maxCol()
  1049.   @ 1, 2 say "Browse and Column Cargo..."
  1050.  
  1051.   //  Display list of record numbers maintained in browse cargo.
  1052.   @ 2, 2 say "  Record-#s visited when HELP was pressed:"
  1053.   aeval(b:cargo, { |rec| qqout(" " +lstr(rec)) } )
  1054.  
  1055.   //  Display current column cargo count, then increment it.
  1056.   @ 3, 2 say "  Prior times HELP pressed in this column: " ;
  1057.              +lstr(b:getColumn(b:colPos):cargo++)
  1058.  
  1059.   FitInBox(5, 0, 16, 35, ;
  1060.            {"         Navigation Keys        ", "", ;
  1061.             "Up·Dn·Lt·Rt         Take a guess", ;
  1062.             "Home·End       First/last column", ;
  1063.             "^Home·^End   Very first/last col", ;
  1064.             "PgUp·PgDn            See up/down", ;
  1065.             "^PgUp·^PgDn    First/last record", ;
  1066.             "Tab·Shf-Tab  Pan cols left/right", "", ;
  1067.             "           ESC Exits            "})
  1068.  
  1069.   FitInBox(maxrow() -19, maxcol() -42, maxrow(), maxcol(), ;
  1070.            {"F2   Toggle between color/mono", ;
  1071.             "F3   Insert copy of current column", ;
  1072.             "F4   Delete current column", ;
  1073.             "F5   Move window (BS=reset)", ;
  1074.             "F6   Resize window (BS=reset)", ;
  1075.             "F7   Rotate column positions (Shift-F7)", ;
  1076.             "F8   Toggle drag-highlight on/off", ;
  1077.             "F9   Highlight current column", ;
  1078.             "F10  Highlight current row", "", ;
  1079.             "Alt-U      Toggle SET DELETED on/off", ;
  1080.             "^U         Toggle record delete on/off", ;
  1081.             "Enter      Edit current cell (incl memo)", ;
  1082.             "^Enter     Clear cell then edit", ;
  1083.             "^Left      Make column more narrow", ;
  1084.             "^Right     Make column more wide", ;
  1085.             "Spacebar   Toggle √-record", ;
  1086.             "Backspace  Clear and reset everything"})
  1087.  
  1088.  
  1089.   @ maxrow() -4, 0 say "See Detailed Comments in Source Code"
  1090.   clr := setcolor("I")
  1091.   @ maxrow() -3, 0 say replicate("▀", 36)
  1092.   @ maxrow() -2, 0 say "     MaxiBrow by Craig Yellick      "
  1093.   @ maxrow() -1, 0 say "     Ver 1.4a        20-Apr-91      "
  1094.   @ maxrow(),    0 say replicate("▄", 36)
  1095.   setcolor(clr)
  1096.  
  1097.   inkey(0)
  1098.   restscreen(0, 0, maxrow(), maxcol(), scr)
  1099.  
  1100. return nil
  1101.  
  1102.  
  1103. /*-----------------------------------------------------------------------*/
  1104.  
  1105. function FitInBox(r1, c1, r2, c2, msg_)
  1106. /*
  1107.    Draw a box of specified dimensions and display the contents
  1108.    of an array of message lines in it. Display only what will
  1109.    fit within the box boundaries.
  1110. */
  1111. local i
  1112.  
  1113.   @ r1, c1 clear to r2, c2
  1114.   @ r1, c1 to r2, c2 double
  1115.   for i := 1 to min(len(msg_), r2 -r1 -1)
  1116.     @ r1 +i, c1 +2 say left(msg_[i], c2 -c1 -1)
  1117.   next i
  1118.  
  1119. return nil
  1120.  
  1121. /*-----------------------------------------------------------------------*/
  1122.  
  1123.  
  1124. function RecPosition(how, howMany)
  1125. /*
  1126.    General-purpose record positioning function, called by TBrowse goTop,
  1127.    goBottom and skip blocks. Returns number of record actually moved if
  1128.    in "skip" mode.
  1129.  
  1130.    Also can be called with no parameters to get record position within
  1131.    database independent of presence of index.
  1132. */
  1133.  
  1134. //  Assume no movement was possible
  1135. local actual := 0
  1136.  
  1137. local i
  1138. static where := 1
  1139.  
  1140.   do case
  1141.   case how = "top"
  1142.     where := 1
  1143.     goto top
  1144.  
  1145.   case how = "bottom"
  1146.     where := lastrec()
  1147.     goto bottom
  1148.  
  1149.   case how = "skip"
  1150.     do case
  1151.     //  Moving backwards
  1152.     case howMany < 0
  1153.       do while (actual > howMany) .and. (.not. bof())
  1154.         skip -1
  1155.         if .not. bof()
  1156.           actual--
  1157.         endif
  1158.       enddo
  1159.  
  1160.     //  Moving forwards
  1161.     case howMany > 0
  1162.       do while (actual < howMany) .and. (.not. eof())
  1163.         skip +1
  1164.         if .not. eof()
  1165.           actual++
  1166.         endif
  1167.       enddo
  1168.       if eof()
  1169.         skip -1
  1170.       endif
  1171.  
  1172.     //  No movement requested, re-read current record
  1173.     otherwise
  1174.       skip 0
  1175.     endcase
  1176.  
  1177.   //  No parameters passed, return current position.
  1178.   otherwise
  1179.     return where
  1180.   endcase
  1181.  
  1182.   //  Update position tracker and prevent boundary wrap.
  1183.   where += actual
  1184.   where := min(max(where, 1), lastrec())
  1185.  
  1186. return actual
  1187.  
  1188.  
  1189. /*-----------------------------------------------------------------------*/
  1190.  
  1191.  
  1192. function RecDisplay(rec, list_, del)
  1193. /*
  1194.   Returns specified record number plus indicator if record has been
  1195.   placed in list_ array. Intended for use in TBColumn retrieval block.
  1196. */
  1197. return if(del, " *","  ") +str(rec,4) ;
  1198.       +if(ascan(list_, rec) = 0, "  ", " √")
  1199.  
  1200.  
  1201. /*-----------------------------------------------------------------------*/
  1202.  
  1203.  
  1204. function aCount(a_, countBlock, start, count)
  1205. /*
  1206.   Given array and code block, return number of elements that evaluate
  1207.   true.
  1208. */
  1209. local howMany := 0
  1210.   aeval(a_, ;
  1211.     { |elem| howMany += if(eval(countBlock, elem), 1, 0) }, ;
  1212.     start, count)
  1213. return howMany
  1214.  
  1215.  
  1216. /*-----------------------------------------------------------------------*/
  1217.  
  1218.  
  1219. function aInsert(a_, pos, value)
  1220. /*
  1221.    Increase size of array by inserting new value in specified position.
  1222. */
  1223.   asize(a_, len(a_) +1)
  1224.   ains(a_, pos)
  1225.   a_[pos] := value
  1226. return nil
  1227.  
  1228.  
  1229. /*-----------------------------------------------------------------------*/
  1230.  
  1231.  
  1232. function aDelete(a_, pos)
  1233. /*
  1234.    Decrease size of array by removing element at specified position.
  1235. */
  1236.   adel(a_, pos)
  1237.   asize(a_, len(a_) -1)
  1238. return nil
  1239.  
  1240.  
  1241. /*-----------------------------------------------------------------------*/
  1242.  
  1243.  
  1244. function aRotate(a_, up)
  1245. /*
  1246.    Rotate array elements such that first is last, last is first, and all
  1247.    others shift up one position. If UP is passed and is false, the shift
  1248.    direction is reversed.
  1249. */
  1250. local temp
  1251.   if (up = nil) .or. up
  1252.     temp := a_[1]
  1253.     aeval(a_, { |e,n| a_[n] := a_[n +1] }, 1, len(a_) -1)
  1254.     a_[len(a_)] := temp
  1255.   else
  1256.     //
  1257.     //  Yes, it's possible to traverse an array backwards with aeval()!
  1258.     //
  1259.     temp := a_[len(a_)]
  1260.     aeval(a_, { |e,n| a_[len(a_) -(n-1)] := a_[len(a_) -n] }, ;
  1261.               1, len(a_) -1)
  1262.     a_[1] := temp
  1263.   endif
  1264. return nil
  1265.  
  1266.  
  1267. /*-----------------------------------------------------------------------*/
  1268.  
  1269.  
  1270. function ColumnColor(value)
  1271. /*
  1272.    Color selection used in TBColumn colorBlock. Allows each data type to
  1273.    have it's own color scheme.
  1274. */
  1275. local type, clr
  1276.   type := valtype(value)
  1277.   do case
  1278.   case (type = "N") .and. (value < 0)
  1279.     clr := NEGVAL_CELL
  1280.   case (type = "L") .and. (.not. value)
  1281.     clr := NEGVAL_CELL
  1282.   otherwise
  1283.     clr := REGULAR_CELL
  1284.   endcase
  1285. return clr
  1286.  
  1287.  
  1288. /*-----------------------------------------------------------------------*/
  1289.  
  1290. function Navigate(b, k)
  1291. /*
  1292.    Establish array of navigation keystrokes and the cursor movement
  1293.    method to associate with each key. The array is comprised of
  1294.    two-element arrays containing the inkey() value of the key and a
  1295.    codeblock to execute when the key is pressed.
  1296.  
  1297.    This function gets passed a browse object and a potential
  1298.    navigation key. If the key is found in the array it's
  1299.    associated navigation message is sent to the browse.
  1300.    Function returns .t. if navigation was handled, .f. if not.
  1301. */
  1302. local n
  1303.  
  1304. //  Made static so it doesn't get re-initialized on every call.
  1305. //  Due to Clipper bug of some sort it's not possible to directly
  1306. //  assign this array on the static statement line.  Perhaps this
  1307. //  will be fixed by the time you read this, if so you can eliminate
  1308. //  the if..endif and assign the array directly on the static
  1309. //  statement line.
  1310. //
  1311. static keys_
  1312.   if keys_ = nil
  1313.     keys_ := { ;
  1314.       {K_UP,        {|| b:up()       } }, ;  //  Up one row
  1315.       {K_DOWN,      {|| b:down()     } }, ;  //  Down one row
  1316.       {K_LEFT,      {|| b:left()     } }, ;  //  Left one column
  1317.       {K_RIGHT,     {|| b:right()    } }, ;  //  Right one column
  1318.       {K_PGUP,      {|| b:pageUp()   } }, ;  //  Up on page
  1319.       {K_PGDN,      {|| b:pageDown() } }, ;  //  Down one page
  1320.       {K_CTRL_PGUP, {|| b:goTop()    } }, ;  //  Up to the first record
  1321.       {K_CTRL_PGDN, {|| b:goBottom() } }, ;  //  Down to the last record
  1322.       {K_HOME,      {|| b:home()     } }, ;  //  First visible column
  1323.       {K_END,       {|| b:end()      } }, ;  //  Last visible column
  1324.       {K_CTRL_HOME, {|| b:panHome()  } }, ;  //  First column
  1325.       {K_CTRL_END,  {|| b:panEnd()   } }, ;  //  Last column
  1326.       {K_TAB,       {|| b:panRight() } }, ;  //  Pan to the right
  1327.       {K_SH_TAB,    {|| b:panLeft()  } }  ;  //  Pan to the left
  1328.     }
  1329.   endif
  1330.  
  1331.   //  Search for the inkey() value in the cursor movement array.
  1332.   //  If one is found, evaluate the code block associated with it.
  1333.   //  Remember these are paired in arrays: {key, block}.
  1334.   //
  1335.   n := ascan(keys_, { | pair | k == pair[1] })
  1336.   if n <> 0
  1337.     eval(keys_[n, 2])
  1338.   endif
  1339.  
  1340. return (n <> 0)
  1341.  
  1342. /*-----------------------------------------------------------------------*/
  1343.  
  1344. function EditCell(b, fieldName, editColor)
  1345. /*
  1346.    General-purpose browse cell editing function, can handle all database
  1347.    field types including memo fields. If you want the edits to "stick"
  1348.    you must assign fieldblock()-style column:block instance variables.
  1349.    All editing, including memo-edit, is done within the boundaries of
  1350.    the browse window. On exit any appropriate browse cursor navagation
  1351.    messages are passed along.
  1352. */
  1353. local c, k, clr, crs, rex, block, cell
  1354.  
  1355.  
  1356.   //  Retrieve the column object for the current cell.
  1357.   c := b:getcolumn(b:colPos)
  1358.  
  1359.  
  1360.   //  Create a field block used to check for a memo field
  1361.   //  and later used to store the edited memo back. It's
  1362.   //  done this way so you can have the browse window display
  1363.   //  a notation like "memo" rather than displaying a small
  1364.   //  hunk of the real memo field.
  1365.   //
  1366.   block := fieldblock(fieldName)
  1367.  
  1368.  
  1369.   //  Can't just "get" a memo, need a memo-edit.
  1370.   if valtype(eval(block)) = "M"
  1371.  
  1372.     //  Tell the user what's going on.
  1373.     //
  1374.     @ b:nTop, b:nLeft clear to b:nBottom, b:nRight
  1375.  
  1376.     @ b:nTop, b:nLeft say ;
  1377.       padc("Memo Edit: Record " +lstr(recno()) ;
  1378.           +', "'+ c:heading +'" Field', b:nRight -b:nLeft)
  1379.  
  1380.     @ row() +1, b:nLeft say replicate("─", b:nRight -b:nLeft +1)
  1381.  
  1382.  
  1383.     //  Turn cursor on and perform the memo edit
  1384.     //  using the specified color.
  1385.     crs := setcursor(1)
  1386.     clr := setcolor(editColor)
  1387.     cell := memoedit(eval(block), b:nTop +2, b:nLeft, b:nBottom, b:nRight)
  1388.     setcursor(crs)
  1389.     setcolor(clr)
  1390.  
  1391.  
  1392.     //  If they didn't abandon the edit, save changes.
  1393.     //  When passed a parameter, fieldblock-style code
  1394.     //  blocks store the value back to the database.
  1395.     //  Handiest darn thing they ever stuck in this language.
  1396.     if lastkey() <> K_ESC
  1397.       eval(block, cell)
  1398.     endif
  1399.  
  1400.  
  1401.     //  We mussed up the entire window, tell TBrowse to clean it up.
  1402.     b:invalidate()
  1403.  
  1404.     //  Re-read from database, since we edited it.
  1405.     b:refreshCurrent()
  1406.  
  1407.  
  1408.   //  Regular data type, do a GET/READ.
  1409.   else
  1410.  
  1411.     //  Pass along any additional keystrokes.
  1412.     if lastkey() = K_CTRL_ENTER
  1413.       keyboard(chr(K_CTRL_Y))
  1414.     elseif (lastkey() > K_SPACE) .and. (lastkey() < 256)
  1415.       keyboard(chr(lastkey()))
  1416.     endif
  1417.  
  1418.  
  1419.     //  Create a get object for the field.
  1420.     cell := getnew(row(), col(), c:block, fieldName,, "W/N,"+editColor)
  1421.  
  1422.  
  1423.     //  Allow up/down to exit the read, and turn the cursor off.
  1424.     rex := readexit(.t.)
  1425.     crs := setcursor(1)
  1426.  
  1427.      //  Perform the read.
  1428.     readmodal({cell})
  1429.  
  1430.     //  Restore original cursor and read-exit states.
  1431.     setcursor(crs)
  1432.     readexit(rex)
  1433.  
  1434.  
  1435.     //  If user hit a navigation key to exit, do it.
  1436.     if Navigate(b, lastkey())
  1437.  
  1438.     //  If they pressed Enter, advance to next column.
  1439.     elseif lastkey() = K_ENTER
  1440.       if b:colPos < b:colCount
  1441.         b:right()
  1442.       else
  1443.         b:down()
  1444.         b:colPos := b:freeze +1
  1445.       endif
  1446.     endif
  1447.  
  1448.  
  1449.     //  We changed the field value and TBrowse doesn't know it.
  1450.     //  So we must force a re-read for the current row.
  1451.     b:refreshCurrent()
  1452.   endif
  1453.  
  1454. return nil
  1455.  
  1456. /*-----------------------------------------------------------------------*/
  1457. // eof MaxiBrow.Prg
  1458.