home *** CD-ROM | disk | FTP | other *** search
- ****
- * Tbdemo.prg
- * Illustration of TBROWSE and GET objects.
- * Copyright (c) 1990-1991 Nantucket Corp. All rights reserved.
- *
- * Compile: CLIPPER Tbdemo /m/n/w
- * Link: RTLINK FILE Tbdemo
- * Execute: Tbdemo <dbf> [<ntx>]
- *
-
- #include "Inkey.ch"
- #include "Setcurs.ch"
- #include "Error.ch"
-
-
- // These #defines use the browse's "cargo" slot to hold the
- // "append mode" flag for the browse. The #defines make it
- // easy to change this later (e.g. if you need to keep
- // several items in the cargo slot).
- #define TURN_ON_APPEND_MODE(b) (b:cargo := .T.)
- #define TURN_OFF_APPEND_MODE(b) (b:cargo := .F.)
- #define IS_APPEND_MODE(b) (b:cargo)
-
-
- // Separator strings for the browse display
- #define MY_HEADSEP "═╤═"
- #define MY_COLSEP " │ "
-
-
-
- ***
- * Tbdemo <dbf> [<index>]
- *
-
- PROCEDURE Tbdemo( dbf, index )
-
- LOCAL bSaveHandler, error
- LOCAL cScreen
-
-
- // Lazy man's error checking
- bSaveHandler := ERRORBLOCK( {|x| BREAK(x)} )
-
- BEGIN SEQUENCE
- USE (dbf) INDEX (index)
-
- RECOVER USING error
- IF error:genCode == EG_OPEN
- ?? "Error opening file(s)"
-
- ELSE
- // Assume it was a problem with the params
- ?? "Usage: Tbdemo <dbf> [<index>]"
-
- ENDIF
-
- QUIT
- END
-
- // Restore the default error handler
- ERRORBLOCK(bSaveHandler)
-
-
- // Save screen, set color, etc.
- cScreen := SAVESCREEN()
- SETCOLOR("N/BG")
- CLEAR SCREEN
-
- MyBrowse(3, 6, MAXROW() - 2, MAXCOL() - 6)
-
- // Put things back
- SET COLOR TO
- @ MAXROW(), 0
- RESTSCREEN(,,,,cScreen)
-
- QUIT
-
-
-
- ***
- * MyBrowse()
- * Create a Tbrowse object and browse with it.
- *
-
- STATIC PROCEDURE MyBrowse(nTop, nLeft, nBottom, nRight)
-
- LOCAL browse // The TBrowse object
- LOCAL cColorSave, nCursSave // State preservers
- LOCAL nKey // Keystroke
- LOCAL lMore // Loop control
-
-
- // Make a "stock" Tbrowse object for the current workarea
- browse := StockBrowseNew(nTop, nLeft, nBottom, nRight)
-
- // This demo uses the browse's "cargo" slot to hold a logical
- // value of true (.T.) when the browse is in "append mode",
- // otherwise false (.F.) (see #defines at top).
- TURN_OFF_APPEND_MODE(browse)
-
- // Use a custom 'skipper' to handle append mode (see below)
- browse:skipBlock := { |x| Skipper(x, browse) }
-
-
- // Change the heading and column separators
- browse:headSep := MY_HEADSEP
- browse:colSep := MY_COLSEP
-
- // Play with the colors
- FancyColors(browse)
-
- // Insert a column at the left for "Rec #" and freeze it
- AddRecno(browse)
-
-
- // Draw a window shadow
- cColorSave := SetColor("N/N")
- @ nTop+1, nLeft+1 CLEAR TO nBottom+1, nRight+1
- SETCOLOR("W/W")
- @ nTop, nLeft CLEAR TO nBottom, nRight
- SETCOLOR(cColorSave)
-
- // Save cursor shape, turn the cursor off while browsing
- nCursSave := SetCursor(SC_NONE)
-
-
- // Main loop
- lMore := .T.
- DO WHILE lMore
-
- // Don't let the cursor move into frozen columns
- IF browse:colPos <= browse:freeze
- browse:colPos := browse:freeze + 1
- ENDIF
-
- // Stabilize the display until it's stable or a key is pressed
- nKey := 0
- DO WHILE nKey == 0 .AND. .NOT. browse:stable
-
- browse:stabilize()
- nKey := InKey()
-
- ENDDO
-
-
- IF browse:stable
-
- IF browse:hitBottom .AND. .NOT. IS_APPEND_MODE(browse)
- // Banged against EOF; go into append mode
- TURN_ON_APPEND_MODE(browse)
- nKey := K_DOWN
-
- ELSE
- IF browse:hitTop .OR. browse:hitBottom
- TONE(125, 0)
- ENDIF
-
- // Make sure that the current record is showing
- // up-to-date data in case we are on a network.
- browse:refreshCurrent()
- ForceStable(browse)
-
- // Everything's done -- just wait for a key
- nKey := InKey(0)
-
- ENDIF
-
- ENDIF
-
-
- IF nKey == K_ESC
- // Esc means leave
- lMore := .F.
-
- ELSE
- // Apply the key to the browse
- ApplyKey(browse, nKey)
-
- ENDIF
-
-
- ENDDO
-
-
- SETCURSOR(nCursSave)
-
- RETURN
-
-
-
- ****
- * Skipper()
- * Handle record movement requests from the Tbrowse object.
- *
- * This is a special "skipper" that handles append mode. It
- * takes two parameters instead of the usual one. The second
- * parameter is a reference to the Tbrowse object itself. The
- * Tbrowse's "cargo" variable contains information on whether
- * append mode is turned on.
- *
- * NOTE: uses the cargo #defines shown at the top of Tbdemo.prg
- *
-
- STATIC FUNCTION Skipper(n, browse)
-
- LOCAL lAppend
- LOCAL i
-
-
- lAppend := IS_APPEND_MODE(browse) // see #defines at top
- i := 0
-
- IF n == 0 .OR. LASTREC() == 0
-
- // Skip 0 (significant on a network)
- SKIP 0
-
- ELSEIF n > 0 .and. RECNO() != LASTREC() + 1
-
- // Skip forward
- DO WHILE i < n
- SKIP 1
- IF ( EOF() )
- IF ( lAppend )
- i++
- ELSE
- SKIP -1
- ENDIF
-
- EXIT
- ENDIF
-
- i++
- ENDDO
-
- ELSEIF n < 0
-
- // Skip backward
- DO WHILE i > n
- SKIP -1
- IF ( BOF() )
- EXIT
- ENDIF
-
- i--
- ENDDO
-
- ENDIF
-
-
- RETURN i
-
-
-
- ***
- * ApplyKey()
- * Apply one keystroke to the browse.
- *
- * NOTE: uses the cargo #defines shown at the top of Tbdemo.prg
- *
-
- STATIC PROCEDURE ApplyKey(browse, nKey)
-
- DO CASE
- CASE nKey == K_DOWN
- browse:down()
-
- CASE nKey == K_PGDN
- browse:pageDown()
-
- CASE nKey == K_CTRL_PGDN
- browse:goBottom()
- TURN_OFF_APPEND_MODE(browse)
-
- CASE nKey == K_UP
- browse:up()
-
- IF IS_APPEND_MODE(browse)
- TURN_OFF_APPEND_MODE(browse)
- browse:refreshAll()
- ENDIF
-
- CASE nKey == K_PGUP
- browse:pageUp()
-
- IF IS_APPEND_MODE(browse)
- TURN_OFF_APPEND_MODE(browse)
- browse:refreshAll()
- ENDIF
-
- CASE nKey == K_CTRL_PGUP
- browse:goTop()
- TURN_OFF_APPEND_MODE(browse)
-
- CASE nKey == K_RIGHT
- browse:right()
-
- CASE nKey == K_LEFT
- browse:left()
-
- CASE nKey == K_HOME
- browse:home()
-
- CASE nKey == K_END
- browse:end()
-
- CASE nKey == K_CTRL_LEFT
- browse:panLeft()
-
- CASE nKey == K_CTRL_RIGHT
- browse:panRight()
-
- CASE nKey == K_CTRL_HOME
- browse:panHome()
-
- CASE nKey == K_CTRL_END
- browse:panEnd()
-
- CASE nKey == K_RETURN
- DoGet(browse)
-
- OTHERWISE
-
- KEYBOARD CHR(nKey)
- DoGet(browse)
-
- ENDCASE
-
-
- RETURN
-
-
-
- ***
- * DoGet()
- * Do a GET for the current column in the browse.
- *
- * NOTE: uses the cargo #defines shown at the top of Tbdemo.prg
- *
-
- STATIC PROCEDURE DoGet(browse)
-
- LOCAL bIns, lScore, lExit
- LOCAL col, get, nKey
- LOCAL lAppend, xOldKey, xNewKey
-
-
- // Make sure screen is fully updated, dbf position is correct, etc.
- ForceStable(browse)
-
- // If confirming a new record, do the physical append
- lAppend := IS_APPEND_MODE(browse)
- IF lAppend .AND. RECNO() == LASTREC() + 1
- APPEND BLANK
- ENDIF
-
-
- // Save the current record's key value (or NIL)
- // (for an explanation, refer to the rambling note below)
- xOldKey := IF( EMPTY(INDEXKEY()), NIL, &(INDEXKEY()) )
-
-
- // Save global state
- lScore := Set(_SET_SCOREBOARD, .F.)
- lExit := Set(_SET_EXIT, .T.)
- bIns := SetKey(K_INS)
-
- // Set insert key to toggle insert mode and cursor shape
- SetKey( K_INS, {|| InsToggle()} )
-
- // Set initial cursor shape
- SetCursor( IF(ReadInsert(), SC_INSERT, SC_NORMAL) )
-
-
- // Get the current column object from the browse
- col := browse:getColumn(browse:colPos)
-
- // Create a corresponding GET
- get := GetNew(Row(), Col(), col:block, col:heading,, browse:colorSpec)
-
- // Read it using the standard reader
- // NOTE: for a shared database, an RLOCK() is required here
- ReadModal( {get} )
-
-
- // Restore state
- SetCursor(0)
- Set(_SET_SCOREBOARD, lScore)
- Set(_SET_EXIT, lExit)
- SetKey(K_INS, bIns)
-
-
- // What this next piece of code does:
- //
- // When a TBrowse stabilizes, it always tries to leave the
- // same "cell" highlighted as was previously highlighted. That
- // is, it always tries to keep the highlight at the same position
- // within the browse window unless it is explicitly moved via an
- // "up" or "down" message. The TBrowse positions the data source
- // in a corresponding fashion. If there aren't enough rows left
- // in the data source (i.e. EOF is encountered while trying to
- // adjust the database to match the window), the TBrowse will
- // relent and move the cursor upward, leaving it on the correct
- // record but with part of the window unfilled.
- //
- // That works OK for logical EOF, but a problem can occur when
- // a GET on a key field causes the current record to move so
- // close to logical BOF that it is impossible to highlight the
- // correct record while leaving the highlight at its previous
- // position within the window. In this case, TBrowse opts to
- // leave the highlight in the same position within the window,
- // even though that position no longer corresponds with the same
- // record as before. That is, it repositions the database as far
- // as it will go, then leaves the highlight where it was. The
- // result is that you end up with the highlight on a different
- // record than the one you just edited.
- //
- // The following piece of code addresses this by checking to see
- // if the current record's key value changed during the GET. If
- // so (or if the record is a new record, just appended), the code
- // below forces a complete refresh and a full stabilization. It
- // then checks to see if this caused the TBrowse to position the
- // database to a different record than before. If so, the old
- // record is assumed to be somewhere "above" the current record,
- // and a series of "up" messages are issued to the browse to get
- // the highlight to move up to the proper position.
-
- // Get the record's key value (or NIL) after the GET
- xNewKey := IF( EMPTY(INDEXKEY()), NIL, &(INDEXKEY()) )
-
- // If the key has changed (or if this is a new record)
- IF .NOT. (xNewKey == xOldKey) .OR. (lAppend .AND. xNewKey != NIL)
-
- // Do a complete refresh
- browse:refreshAll()
- ForceStable(browse)
-
- // Make sure we're still on the right record after stabilizing
- DO WHILE &(INDEXKEY()) > xNewKey .AND. .NOT. browse:hitTop()
- browse:up()
- ForceStable(browse)
- ENDDO
-
- ENDIF
-
-
- // For this demo, we turn append mode off after each new record
- TURN_OFF_APPEND_MODE(browse)
-
- // Check exit key from get
- nKey := LASTKEY()
- IF nKey == K_UP .OR. nKey == K_DOWN .OR. ;
- nKey == K_PGUP .OR. nKey == K_PGDN
-
- // Ugh
- KEYBOARD( CHR(nKey) )
-
- ENDIF
-
-
- RETURN
-
-
-
- ***
- * ForceStable()
- * Force a complete stabilization of a TBrowse.
- *
-
- STATIC PROCEDURE ForceStable(browse)
-
- DO WHILE .NOT. browse:stabilize()
- ENDDO
-
- RETURN
-
-
-
- ***
- * InsToggle()
- * Toggle the global insert mode and the cursor shape.
- *
-
- STATIC PROCEDURE InsToggle()
-
- IF READINSERT()
- READINSERT(.F.)
- SETCURSOR(SC_NORMAL)
-
- ELSE
- READINSERT(.T.)
- SETCURSOR(SC_INSERT)
-
- ENDIF
-
- RETURN
-
-
-
- ***
- * StockBrowseNew()
- * Create a "stock" Tbrowse object for the current workarea.
- *
-
- STATIC FUNCTION StockBrowseNew(nTop, nLeft, nBottom, nRight)
-
- LOCAL browse
- LOCAL n, column, cType
-
-
- // Start with a new browse object from TBrowseDB()
- browse := TBrowseDB(nTop, nLeft, nBottom, nRight)
-
- // Add a column for each field in the current workarea
- FOR n := 1 TO FCount()
-
- // Make a new column
- column := TBColumnNew( ;
- Field(n), ;
- FieldWBlock(Field(n), Select()) ;
- )
-
- // Add the column to the browse
- browse:addColumn(column)
-
- NEXT
-
-
- RETURN browse
-
-
-
- ***
- * FancyColors()
- * Set up some colors for the browse.
- *
-
- STATIC PROCEDURE FancyColors(browse)
-
- LOCAL n, column
- LOCAL xValue
-
-
- // Set up a list of colors for the browse to use
- browse:colorSpec := "N/W, N/BG, B/W, B/BG, B/W, B/BG, R/W, B/R"
-
- // Loop through the columns, choose some colors for each
- FOR n := 1 TO browse:colCount
-
- // Get (a reference to) the column
- column := browse:getColumn(n)
-
- // Get a sample of the underlying data by evaluating the codeblock
- xValue := EVAL(column:block)
-
- IF VALTYPE(xValue) != "N"
- // For non-numeric, just use colors 3 and 4 ("B/W" and "B/BG")
- column:defColor := {3, 4}
-
- ELSE
- // For numbers, use a color block to highlight negative values
- column:colorBlock := {|x| if( x < 0, {7, 8}, {5, 6} )}
-
- // Set default colors also (controls the heading color)
- column:defColor := {7, 8}
-
- ENDIF
-
- NEXT
-
-
- RETURN
-
-
-
- ***
- * AddRecno()
- * Insert a frozen column at the left that shows current record number
- *
-
- STATIC PROCEDURE AddRecno(browse)
-
- LOCAL column
-
- // Create the column object
- column := TBColumnNew( " Rec #", {|| RECNO()} )
-
- // Insert it as the leftmost column
- browse:insColumn(1, column)
-
- // Freeze it at the left
- browse:freeze := 1
-
- RETURN
-