/*
 * $Id$
 */

/*
 * Harbour Project source code:
 * TBrowse Class
 *
 * Copyright 1999 Antonio Linares <alinares@fivetech.com>
 * www - http://www.harbour-project.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
 *
 * As a special exception, the Harbour Project gives permission for
 * additional uses of the text contained in its release of Harbour.
 *
 * The exception is that, if you link the Harbour libraries with other
 * files to produce an executable, this does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * Your use of that executable is in no way restricted on account of
 * linking the Harbour library code into it.
 *
 * This exception does not however invalidate any other reasons why
 * the executable file might be covered by the GNU General Public License.
 *
 * This exception applies only to the code released by the Harbour
 * Project under the name Harbour.  If you copy code from other
 * Harbour Project or Free Software Foundation releases into a copy of
 * Harbour, as the General Public License permits, the exception does
 * not apply to the code that you add in this way.  To avoid misleading
 * anyone as to the status of such modified files, you must delete
 * this exception notice from them.
 *
 * If you write modifications of your own for Harbour, it is your choice
 * whether to permit this exception to apply to your modifications.
 * If you do not wish that, delete this exception notice.
 *
 */

 /*
 * The following parts are Copyright of the individual authors.
 * www - http://www.harbour-project.org
 *
 * Copyright 2000-2002 Maurilio Longo <maurilio.longo@libero.it>
 * Cursor movement handling, stabilization loop, multi-line headers and footers support
 * ::PageUp(), ::PageDown(), ::Down(), ::Up(), ::GoBottom(), ::GoTop(), ::Stabilize()
 * ::GotoXY(), ::DispCell(), ::WriteMLineText(), ::RedrawHeaders(),
 * ::freeze(), ::SetColumnWidth()
 *
 * Copyright 2001 Manu Exposito <maex14@dipusevilla.es>
 * Activate data PICTURE DispCell( nColumn, nColor )
 *
 * Copyright 2007 Viktor Szakats <harbour.01 syenar.hu>
 *    tbr_CookColor(), tbr_GetColor()
 *
 * See doc/license.txt for licensing terms.
 *
 */

/* NOTE: Don't use SAY in this module, use DispOut(), DispOutAt() instead,
         otherwise it will not be CA-Cl*pper compatible.
         ADDITION: Same goes for DevPos(), always use SetPos() instead.
         [vszakats] */

/* TODO: :firstScrCol() --> nScreenCol
         Determines screen column where the first table column is displayed.
         Xbase++ compatible method */

/* TODO: :viewArea() --> aViewArea
         Determines the coordinates for the data area of a TBrowse object.
         Xbase++ compatible method */

/* NOTE: These TBColumn properties are _not_ cached inside TBrowse:
         :picture, :block, :colorBlock */

#include "hbclass.ch"

#include "button.ch"
#include "color.ch"
#include "common.ch"
#include "error.ch"
#include "inkey.ch"
#include "setcurs.ch"
#include "tbrowse.ch"

/* TBColumn info constants */
#define TBCI_OBJ                1    // Object TBColumn
#define TBCI_WIDTH              2    // Column Width
#define TBCI_WIDTHCELL          3    // Width of the Cell
#define TBCI_HEADING            4    // Column Headings
#define TBCI_FOOTING            5    // Column Footings
#define TBCI_COLSEP             6    // Column Seperator
#define TBCI_SEPWIDTH           7    // Width of the Separator
#define TBCI_DEFCOLOR           8    // Array with index of color
#define TBCI_SETWIDTH           9    // If True, only SetFrozen can change TBCI_WIDTH
#define TBCI_LCOLSEP            10   // Should column separator be drawn
#define TBCI_SCRCOLPOS          11   // Temporary column position on screen

//#define TBCI_COLOBJECT       1   // column object
//#define TBCI_CELLWIDTH       2   // width of the cell
//#define TBCI_COLWIDTH        3   // width of the column
//#define TBCI_SCRCELLPOS      4   // cell position on screen
//#define TBCI_SCRCOLPOS       5   // column position on screen
//#define TBCI_SEPWIDTH        6   // width of the separator

#define TBC_CLR_STANDARD        1    // first index value to set unselected data color.
#define TBC_CLR_ENHANCED        2    // second index value to set selected data color.
#ifdef HB_COMPAT_C53
#define TBC_CLR_HEADING         3    // third index value to set heading color.
#define TBC_CLR_FOOTING         4    // fourth index value to set footing color.
#define TBC_CLR_MAX_            4
#else
#define TBC_CLR_HEADING         TBC_CLR_STANDARD
#define TBC_CLR_FOOTING         TBC_CLR_STANDARD
#define TBC_CLR_MAX_            2
#endif

/* NOTE: In CA-Cl*pper TBROWSE class does not inherit from any other classes
         and there is no public class function like TBrowse(). There is 
         in XPP though. */ 

#if defined(HB_C52_STRICT) && !defined(HB_COMPAT_XPP)
CREATE CLASS TBrowse STATIC
#else
CREATE CLASS TBrowse
#endif

   EXPORT:

   VAR cargo                                                // User-definable variable
   VAR leftVisible      INIT 0 READONLY                     // Indicates position of leftmost unfrozen column in display
   VAR rightVisible     INIT 0 READONLY                     // Indicates position of rightmost unfrozen column in display
   VAR rowCount                READONLY                     // Number of visible data rows in the TBrowse display

   METHOD down()                                            // Moves the cursor down one row
   METHOD end()                                             // Moves the cursor to the rightmost visible data column
   METHOD goBottom()                                        // Repositions the data source to the bottom of file
   METHOD goTop()                                           // Repositions the data source to the top of file
   METHOD home()                                            // Moves the cursor to the leftmost visible data column
   METHOD left()                                            // Moves the cursor left one column
   METHOD pageDown()                                        // Repositions the data source downward
   METHOD pageUp()                                          // Repositions the data source upward
   METHOD panEnd()                                          // Moves the cursor to the rightmost data column
   METHOD panHome()                                         // Moves the cursor to the leftmost visible data column
   METHOD panLeft()                                         // Pans left without changing the cursor position
   METHOD panRight()                                        // Pans right without changing the cursor position
   METHOD right()                                           // Moves the cursor right one column
   METHOD up()                                              // Moves the cursor up one row

   METHOD addColumn( oCol )
   METHOD delColumn( nPos )                                 // Delete a column object from a browse
   METHOD insColumn( nPos, oCol )                           // Insert a column object in a browse
   METHOD getColumn( nColumn )                              // Gets a specific TBColumn object
   METHOD setColumn( nColumn, oCol )                        // Replaces one TBColumn object with another
   METHOD colWidth( nColumn )                               // Returns the display width of a particular column
   METHOD colCount()
   METHOD colorRect()                                       // Alters the color of a rectangular group of cells
   /* NOTE: nMode is an undocumented Harbour parameter. Should not be used by app code. */
   METHOD configure( nMode )                                // Reconfigures the internal settings of the TBrowse object
   METHOD deHilite()                                        // Dehighlights the current cell
   METHOD forceStable()                                     // Performs a full stabilization
   METHOD hilite()                                          // Highlights the current cell
   METHOD invalidate()                                      // Forces entire redraw during next stabilization
   METHOD refreshAll()                                      // Causes all data to be recalculated during the next stabilize
   METHOD refreshCurrent()                                  // Causes the current row to be refilled and repainted on next stabilize
   METHOD stabilize()                                       // Performs incremental stabilization
#ifdef HB_COMPAT_C53
   METHOD setKey( nKey, bBlock )
   METHOD applyKey( nKey )
   METHOD hitTest( nMRow, nMCol )
   METHOD setStyle( nStyle, lNewValue )
#endif
#ifdef HB_COMPAT_XPP
   MESSAGE _Left() METHOD Left()
   MESSAGE _Right() METHOD Right()
   MESSAGE _End() METHOD End()
#endif

   METHOD autoLite( lAutoLite ) SETGET
   METHOD nBottom( nBottom ) SETGET
   METHOD nLeft( nLeft ) SETGET
   METHOD nRight( nRight ) SETGET
   METHOD nTop( nTop ) SETGET
   METHOD colorSpec( cColorSpec ) SETGET
   METHOD colSep( cColSep ) SETGET
   METHOD footSep( cFootSep ) SETGET
   METHOD headSep( cHeadSep ) SETGET
   METHOD colPos( nColPos ) SETGET
   METHOD goBottomBlock( bBlock ) SETGET
   METHOD goTopBlock( bBlock ) SETGET
   METHOD hitBottom( lHitBottom ) SETGET
   METHOD hitTop( lHitTop ) SETGET
   METHOD rowPos( nRowPos ) SETGET
   METHOD stable( lStable ) SETGET
   METHOD freeze( nFrozenCols ) SETGET
   METHOD skipBlock( bSkipBlock ) SETGET
#ifdef HB_COMPAT_C53                                   
   METHOD border( cBorder ) SETGET
   METHOD nRow( nRow ) SETGET                               /* NOTE: Undocumented CA-Cl*pper 5.3 method. */
   METHOD nCol( nCol ) SETGET                               /* NOTE: Undocumented CA-Cl*pper 5.3 method. */
   METHOD mRowPos( nMRowPos ) SETGET
   METHOD mColPos( nMColPos ) SETGET
   METHOD message( cMessage ) SETGET
#endif

   METHOD New( nTop, nLeft, nBottom, nRight )               /* NOTE: This method is a Harbour extension [vszakats] */

   PROTECTED:

   VAR lAutoLite        INIT .T.                            // Logical value to control highlighting
   VAR n_Top            INIT 0                              // Top row number for the TBrowse display
   VAR n_Left           INIT 0                              // Leftmost column for the TBrowse display
   VAR n_Bottom         INIT 0                              // Bottom row number for the TBrowse display
   VAR n_Right          INIT 0                              // Rightmost column for the TBrowse display
   VAR cBorder                                              // Character value defining characters drawn around object (C5.3)
   VAR cColorSpec                                           // Color table for the TBrowse display
   VAR aColorSpec                                           // Color table for the TBrowse display (preprocessed)
   VAR cColSep          INIT " "                            // Column separator character
   VAR cFootSep         INIT ""                             // Footing separator character
   VAR cHeadSep         INIT ""                             // Heading separator character
   VAR nColPos          INIT 1                              // Current cursor column position
   VAR bGoBottomBlock   INIT {|| NIL }                      // Code block executed by TBrowse:goBottom()
   VAR bGoTopBlock      INIT {|| NIL }                      // Code block executed by TBrowse:goTop()
   VAR lHitBottom       INIT .F.                            // Indicates the end of available data
   VAR lHitTop          INIT .F.                            // Indicates the beginning of available data
   VAR nRowPos          INIT 1                              // Current cursor row position
   VAR lStable          INIT .F.                            // Indicates if the TBrowse object is stable
   VAR aRect            INIT {}                             // The rectangle specified with ColorRect()
   VAR aRectColor       INIT {}                             // The color positions to use in the rectangle specified with ColorRect()
   VAR aRedraw          INIT {}                             // Array of logical items indicating, is appropriate row need to be redraw
   VAR aColumns         INIT {}                             // Array to hold all browse columns
   VAR aColsWidth       INIT {}                             // Array with width of TBrowse's columns
   VAR aColsPos         INIT {}                             // Array with position of TBrowse's columns
   VAR aColsInfo        INIT {}                             // Array with column data
   VAR lHeaders         INIT .F.                            // Internal variable which indicates whether there are column footers to paint
   VAR lFooters         INIT .F.                            // Internal variable which indicates whether there are column footers to paint
   VAR lRedrawFrame     INIT .T.                            // True if I need to redraw Headers/Footers
   VAR nColsWidth       INIT 0                              // Total width of visible columns plus ColSep
   VAR nColsVisible     INIT 0                              // Number of columns that fit on the browse width
   VAR lHitTop          INIT .F.                            // Internal Top reached flag
   VAR lHitBottom       INIT .F.                            // Internal Bottom reached flag
   VAR nRecsToSkip      INIT 0                              // Recs to skip on next Stabilize()
   VAR nNewRowPos       INIT 1                              // Next position of data source (after first phase of stabilization)
   VAR nLastRetrieved   INIT 1                              // Position, relative to first row, of last retrieved row (with an Eval(::bSkipBlock, n))
   VAR nHeaderHeight    INIT 1                              // How many lines is highest Header/Footer and so how many lines of
   VAR nFooterHeight    INIT 1                              // screen space I have to reserve
   VAR nFrozenWidth     INIT 0                              // How many screen column are not available on the left side of TBrowse display. > 0 only when there are frozen columns
   VAR bSkipBlock       INIT {|| NIL }                      // Code block used to reposition data source
   VAR nFrozenCols      INIT 0                              // Number of frozen columns on left side of TBrowse
   VAR nColumns         INIT 0                              // Number of columns added to TBrowse
   VAR lNeverDisplayed  INIT .T.                            // .T. if TBrowse has never been stabilized()
   VAR lHiLited         INIT .F.
#ifdef HB_COMPAT_C53                                        
   VAR n_Row            INIT 0                              // Row number for the actual cell
   VAR n_Col            INIT 0                              // Col number for the actual cell
   VAR nMRowPos         INIT 0
   VAR nMColPos         INIT 0
   VAR cMessage         INIT ""
   VAR aKeys
   VAR rect
   VAR aVisibleCols     INIT {}
   VAR aSetStyle        INIT { .F., .F., .F., .F., .F. }    /* TBR_APPEND, TBR_APPENDING, TBR_MODIFY, TBR_MOVE, TBR_SIZE */
#endif

   METHOD InitColumn( oCol, lAddColumn )
   METHOD PosCursor()                                       // Positions the cursor to the beginning of the call, used only when autolite==.F.
   METHOD LeftDetermine()                                   // Determine leftmost unfrozen column in display
   METHOD DispCell( nRow, nCol, nMode )                     // Displays a single cell and returns cell type as a single letter like Valtype()
   METHOD HowManyCol()                                      // Counts how many cols can be displayed
   METHOD RedrawHeaders()                                   // Repaints TBrowse Headers
   METHOD Moved()                                           // Every time a movement key is issued I need to reset certain properties of TBrowse, I do these settings inside this method
   METHOD WriteMLineText( cStr, nPadLen, lHeader, cColor )  // Writes a multi-line text where ";" is a line break, lHeader is .T. if it is a header and not a footer
   METHOD SetColumnWidth( oCol )                            // Calcs width of given column
   METHOD MGotoYX( nRow, nCol )                             // Given screen coordinates nRow, nCol sets TBrowse cursor on underlaying cell
                                                            // _M_GotoXY because this method will mostly be called to handle mouse requests
ENDCLASS

/* -------------------------------------------- */

METHOD invalidate() CLASS TBrowse

   AFill( ::aRedraw, .T. )
   ::lStable := .F.
   ::lRedrawFrame := .T.

   return Self

METHOD refreshAll() CLASS TBrowse

   AFill( ::aRedraw, .T. )
   ::lStable := .F.

   return Self

METHOD refreshCurrent() CLASS TBrowse

   if ! Empty( ::aRedraw ) .and. ::nRowPos > 0
      ::aRedraw[ ::nRowPos ] := .T.
   endif

   ::lStable := .F.

   return Self

METHOD configure( nMode ) CLASS TBrowse

   local n
   local nHeight
#ifdef HB_COMPAT_C53
   local nLeft
   local nRight
#endif

   // ; Fill the column info array
/*
   local xVal

   if nMode == 2

      for n := 1 to ::nColumns
      
         xVal := Eval( ::aColumns[ n ]:block )
      
         aCol[ TBCI_HEADING   ] := ::aColumns[ n ]:heading
         aCol[ TBCI_FOOTING   ] := ::aColumns[ n ]:footing
         aCol[ TBCI_WIDTH     ] := ::SetColumnWidth( ::aColumns[ n ] )
         aCol[ TBCI_WIDTHCELL ] := Min( aCol[ TBCI_WIDTH ], tbr_CalcWidth( xVal, ValType( xValue ), ::aColumns[ n ]:picture ) )
         aCol[ TBCI_COLSEP    ] := iif( aCol[ TBCI_OBJ ]:ColSep != NIL, aCol[ TBCI_OBJ ]:ColSep, ::ColSep )
         aCol[ TBCI_DEFCOLOR  ] := tbr_DefColor( ::aColumns[ n ]:defColor, ::aColorSpec )
         aCol[ TBCI_SEPWIDTH  ] := Len( aCol[ TBCI_COLSEP ] )
         aCol[ TBCI_LCOLSEP   ] := aCol[ TBCI_WIDTH ] > 0
         aCol[ TBCI_COLSEP    ] := iif( aCol[ TBCI_OBJ ]:ColSep != NIL, aCol[ TBCI_OBJ ]:ColSep, ::ColSep )
      next
   endif
*/
   // ;

   ::lHeaders := .F.
   ::lFooters := .F.
   ::lRedrawFrame := .T.

   if nMode == 2 .AND. ::nColumns == 1
      ::leftVisible := 1
   endif
   if ::nColumns < ::nFrozenCols
      ::nFrozenCols := 0
   endif

   // Are there column headers to paint ?
   for n := 1 to ::nColumns
      if ! Empty( ::aColumns[ n ]:Heading )
         ::lHeaders := .T.
         exit
      endif
   next

   // Are there column footers to paint ?
   for n := 1 to ::nColumns
      if ! Empty( ::aColumns[ n ]:Footing )
         ::lFooters := .T.
         exit
      endif
   next

   ::nHeaderHeight := 1
   ::nFooterHeight := 1

   // Find out highest header and footer
   for n := 1 to ::nColumns

      // ...

      if ::lHeaders .and. ! Empty( ::aColumns[ n ]:Heading )
         nHeight := Len( ::aColumns[ n ]:Heading ) - Len( StrTran( ::aColumns[ n ]:Heading, ";" ) ) + 1

         if nHeight > ::nHeaderHeight
            ::nHeaderHeight := nHeight
         endif

      endif

      if ::lFooters .and. ! Empty( ::aColumns[ n ]:Footing )
         nHeight := Len( ::aColumns[ n ]:Footing ) - Len( StrTran( ::aColumns[ n ]:Footing, ";" ) ) + 1

         if nHeight > ::nFooterHeight
            ::nFooterHeight := nHeight
         endif

      endif
   next

   if nMode == NIL
      for n := 1 to ::nColumns
         ::aColsWidth[ n ] := ::SetColumnWidth( ::aColumns[ n ] )
      next
   endif

   // 20/nov/2000 - maurilio.longo@libero.it
   // If I add (or remove) header or footer (separator) I have to change number
   // of available rows
   ::RowCount := ::n_Bottom - ::n_Top + 1 - ;
                  iif( ::lHeaders, ::nHeaderHeight + iif( Empty( ::cHeadSep ), 0, 1 ), 0 ) - ;
                  iif( ::lFooters, ::nFooterHeight + iif( Empty( ::cFootSep ), 0, 1 ), 0 )

   if Len( ::aRedraw ) != ::RowCount
      ASize( ::aRedraw, ::RowCount )
   endif

   ::Invalidate()

   // Force re-evaluation of space occupied by frozen columns
   if ::nFrozenCols > 0
      ::freeze := ::nFrozenCols
   endif

   if nMode == 2
      ::HowManyCol()
   endif

   #ifdef HB_COMPAT_C53

      nLeft := ::n_Left
      nRight := ::n_Right

      ::rect := { ::n_Top    + iif( ::lHeaders, ::nHeaderHeight + iif( Empty( ::cHeadSep ), 0, 1 ), 0 ),;
                  ::n_Left,;
                  ::n_Bottom - iif( ::lFooters, ::nFooterHeight - iif( Empty( ::cFootSep ), 0, 1 ), 0 ),;
                  ::n_Right }

      for n := nLeft To nRight
         AAdd( ::aVisibleCols, n )
      next

   #endif

   return Self

// Adds a TBColumn object to the TBrowse object
METHOD addColumn( oCol ) CLASS TBrowse

   /* NOTE: CA-Cl*pper does no checks at all on the parameters. */

   ::Moved() /* TOFIX: This logic should go inside ::configure() */

   ::nColumns++

   AAdd( ::aColumns, oCol )
   AAdd( ::aColsWidth, ::SetColumnWidth( oCol ) )
   AAdd( ::aColsPos, 0 )
   AAdd( ::aColsInfo, ::InitColumn( oCol, .T. ) )

   ::Configure( 2 )

   return Self

// Insert a column object in a browse
METHOD insColumn( nPos, oCol ) CLASS TBrowse

   /* NOTE: CA-Cl*pper does no checks at all on the parameters. */

#ifndef HB_C52_STRICT
   if nPos >= 1
   
      if nPos > ::nColumns
#endif      

         ::Moved() /* TOFIX: This logic should go inside ::configure() */

         /* NOTE: CA-Cl*pper doesn't do this, but crashes instead. */

         ::nColumns++

         AAdd( ::aColumns, oCol )
         AAdd( ::aColsWidth, ::SetColumnWidth( oCol ) )
         AAdd( ::aColsPos, 0 )
         AAdd( ::aColsInfo, ::InitColumn( oCol, .F. ) )

#ifndef HB_C52_STRICT
      else

         ::Moved() /* TOFIX: This logic should go inside ::configure() */

         ::nColumns++
         
         ASize( ::aColumns, ::nColumns )
         AIns( ::aColumns, nPos )
         ASize( ::aColsWidth, ::nColumns )
         AIns( ::aColsWidth, nPos )
         ASize( ::aColsPos, ::nColumns )
         AIns( ::aColsPos, nPos )
         ASize( ::aColsInfo, ::nColumns )
         AIns( ::aColsInfo, ::InitColumn( oCol, .F. ) )

         ::aColumns[ nPos ] := oCol
         ::aColsWidth[ nPos ] := ::SetColumnWidth( oCol )
         ::aColsPos[ nPos ] := 0
         ::aColsInfo[ nPos ] := ::InitColumn( oCol, .F. )

      endif
#endif
   
      ::Configure( 2 )

#ifndef HB_C52_STRICT
   endif
#endif

   return oCol

// Replaces one TBColumn object with another
METHOD setColumn( nPos, oCol ) CLASS TBrowse

   LOCAL oOldCol

   if nPos != NIL .AND. oCol != NIL

      nPos := _eInstVar( Self, "COLUMN", nPos, "N", 1001 )
      oCol := _eInstVar( Self, "COLUMN", oCol, "O", 1001 )

      /* NOTE: CA-Cl*pper doesn't check nPos range (and type in C5.3 - I didn't implement this behaviour), 
               but crashes instead. */

#ifndef HB_C52_STRICT
      if nPos >= 1 .AND. nPos <= ::nColumns
#endif      

         ::Moved() /* TOFIX: This logic should go inside ::configure() */
      
         oOldCol := ::aColumns[ nPos ]
      
         ::aColumns[ nPos ] := oCol
         ::aColsWidth[ nPos ] := ::SetColumnWidth( oCol )
         ::aColsPos[ nPos ] := 0
         ::aColsInfo[ nPos ] := ::InitColumn( oCol, .F. )
      
         ::Configure( 2 )

#ifndef HB_C52_STRICT
      endif
#endif      
   endif

   /* NOTE: CA-Cl*pper 5.2 NG says this will return the previously set 
            column, but it's returning Self instead. In C5.3 this bug 
            was fixed and it works as expected (except when wrong
            parameter is passed, when it returns NIL). [vszakats] */
#ifdef HB_C52_STRICT
   return Self
#else
   return oOldCol
#endif

METHOD delColumn( nPos ) CLASS TBrowse

   local oCol := ::aColumns[ nPos ] /* NOTE: To keep CA-Cl*pper compatible runtime error generation. [vszakats] */

   ::Moved() /* TOFIX: This logic should go inside ::configure() */

   /* Need to adjust variables in case last column is deleted. */

   /* TOFIX: This logic should go inside ::configure() */

   if nPos == ::nColPos .or. ;
      nPos == ::nColumns .or.;
      ::nColPos == ::nColumns .or. ;
      ::rightVisible == ::nColumns

      if ::leftVisible == ::rightVisible .and. ::leftVisible > 1
         ::leftVisible--
      endif

      ::rightVisible--

      if ::nColPos == ::nColumns
         ::nColpos--
      endif

   endif

   ::nColumns--

   ADel( ::aColumns, nPos )
   ASize( ::aColumns, ::nColumns )
   ADel( ::aColsWidth, nPos )
   ASize( ::aColsWidth, ::nColumns )
   ADel( ::aColsPos, nPos )
   ASize( ::aColsPos, ::nColumns )
   ADel( ::aColsInfo, nPos )
   ASize( ::aColsInfo, ::nColumns )

   ::Configure( 2 )

   return oCol

// Gets a specific TBColumn object
METHOD getColumn( nColumn ) CLASS TBrowse
#ifdef HB_C52_STRICT
   return ::aColumns[ nColumn ]
#else
   return iif( nColumn > 0 .and. nColumn <= ::nColumns, ::aColumns[ nColumn ], NIL )
#endif   

// Returns the display width of a particular column
METHOD colWidth( nColumn ) CLASS TBrowse
   return iif( nColumn > 0 .and. nColumn <= ::nColumns, ::aColsWidth[ nColumn ], 0 )

METHOD colCount() CLASS TBrowse
   return Len( ::aColumns )

METHOD freeze( nFrozenCols ) CLASS TBrowse

   local nCol
   local nWidth

   if ISNUMBER( nFrozenCols )
   
      if nFrozenCols >= 0 .and. nFrozenCols <= ::nColumns

         nWidth := ::n_Right - ::n_Left + 1    // Visible width of the browse
         
         ::nFrozenCols := nFrozenCols
         // Space inside TBrowse window reserved for frozen columns
         ::nFrozenWidth := 0
         
         // If I've never displayed this TBrowse before I cannot calc occupied space since
         // columns:width is not yet set, ::Stabilize() will call me later
         if ! ::lNeverDisplayed
         
            if nFrozenCols > 0
               for nCol := 1 to nFrozenCols
                  ::nFrozenWidth += ::aColsWidth[ nCol ]
                  if nCol < ::nColumns
                     ::nFrozenWidth += iif( ::aColumns[ nCol + 1 ]:ColSep != NIL,;
                                            Len( ::aColumns[ nCol + 1 ]:ColSep ),;
                                            Len( ::cColSep ) )
                  endif
               next
            endif
         
            for nCol := 1 to ::nColumns
               if nFrozenCols > 0
                  // If there are columns which are larger than TBrowse display width minus
                  // frozen columns reserved space, shrihnk them to fit
                  if ::nFrozenWidth + ::aColsWidth[ nCol ] > nWidth
                     ::aColsWidth[ nCol ] := nWidth - ::nFrozenWidth
                  endif
         
               else
                  // Reset column widths
                  ::aColsWidth[ nCol ] := ::SetColumnWidth( ::aColumns[ nCol ] )
               endif
            next
         endif
      else
         return nFrozenCols
      endif
   endif

   return ::nFrozenCols

METHOD down() CLASS TBrowse

   ::Moved()
   ::nRecsToSkip++

   return Self

METHOD up() CLASS TBrowse

   ::Moved()
   ::nRecsToSkip--

   return Self

METHOD end() CLASS TBrowse

   ::Moved()

   if ::nColPos < ::rightVisible
      ::nColPos := ::rightVisible
      // !
      ::lRedrawFrame := .T.
      ::refreshCurrent()
   endif

   return Self

METHOD goBottom() CLASS TBrowse

   local nToTop

   ::Moved() // !

   Eval( ::bGoBottomBlock )

   // Skip back from last record as many records as TBrowse can hold
   nToTop := Abs( Eval( ::bSkipBlock, - ( ::RowCount - 1 ) ) )

// ::nRecsToSkip := ::RowCount - 1

   // From top of TBrowse new row position is nToTop + 1 records away
   ::nNewRowPos := nToTop + 1

   // Last read record is first record inside TBrowse
   ::nLastRetrieved := 1
// ::nRowPos := 1
   ::refreshAll()

   return Self

METHOD goTop() CLASS TBrowse

   ::Moved() // !

   Eval( ::bGoTopBlock )
   ::nLastRetrieved := 1
   ::nNewRowPos := 1
   ::refreshAll()

   return Self

METHOD home() CLASS TBrowse

   ::Moved()

   if ::nColPos != ::leftVisible
      ::nColPos := ::leftVisible
      ::invalidate()
//    ::lRedrawFrame := .T.
//    ::refreshCurrent()
   endif

   return Self

METHOD right() CLASS TBrowse

   ::Moved()

   if ::nColPos < ::rightVisible
      ::nColPos++
   else
      if ::nColPos < ::nColumns
         ::rightVisible++
         ::leftVisible := ::leftDetermine()
         ::nColPos++
         ::invalidate()
//       ::lRedrawFrame := .T.
//       ::refreshAll()
      else
         /* 09/08/2004 - <maurilio.longo@libero.it>
                         In a ! ::lStable state clipper moves ::nColPos past ::ColCount or
                         before column 1, so here and on _Left(), Home(), End(), PanEnd()
                         PanHome() methods I let it go "out of bounds",
                         PerformStabilization() gives ::nColPos a correct value */
         ::nColPos++
      endif
   endif

   return Self

METHOD left() CLASS TBrowse

   local leftVis

   ::Moved()

   if ::nColPos > ::leftVisible .or.;
      ( ::nColPos <= ::nFrozenCols + 1 .and. ::nColPos > 1 )
      ::nColPos--
   else
      if ::nColPos <= Max( ::leftVisible, ::nFrozenCols ) .and. ::nColPos > 1
         leftVis := ::leftVisible
         do while leftVis == ::leftVisible
            ::rightVisible--
            ::leftVisible := ::LeftDetermine()
         enddo
         ::nColPos--
         ::Invalidate()
//       ::lRedrawFrame := .T.
//       ::RefreshAll()
      else
         ::nColPos-- // Can go "out of bounds", here we behave like clipper.
      endif
   endif

   return Self

METHOD leftDetermine() CLASS TBrowse

   local nWidthMax := ::n_Right - ::n_Left + 1  // Visible width of the browse
   local nWidth := ::nFrozenWidth
   local nCol

   nCol := ::rightVisible
   do while nWidth < nWidthMax .and. nCol > ::nFrozenCols

      nWidth += ::aColsWidth[ nCol ] +;
                iif( ::aColumns[ nCol ]:ColSep != NIL,;
                     Len( ::aColumns[ nCol ]:ColSep ),;
                     Len( ::cColSep ) )

      if nWidth < nWidthMax
         nCol--
      endif
   enddo

   return Min( nCol + 1, ::nColumns )

METHOD pageDown() CLASS TBrowse

   ::Moved()
   ::nRecsToSkip := ( ::RowCount - ::nRowPos ) + ::RowCount

   return Self

METHOD pageUp() CLASS TBrowse

   ::Moved()
   ::nRecsToSkip := - ( ( ::nRowPos - 1 ) + ::RowCount )

   return Self

METHOD panEnd() CLASS TBrowse

   ::Moved()

   if ::nColPos < ::nColumns
      if ::rightVisible < ::nColumns
         ::rightVisible := ::nColumns
         ::leftVisible := ::LeftDetermine()
         ::nColPos := ::rightVisible
         ::Invalidate()
//       ::lRedrawFrame := .T.
//       ::RefreshAll()
      else
         ::nColPos := ::rightVisible
         /* 18/10/2005 - <maurilio.longo@libero.it>
                         This is wrong, should not reload datasource. But ::Invalidate()
                         forces a full repaint, which is overkill. Maybe just a ::aRedraw[ x ] := .T. */
         ::RefreshCurrent()
      endif
   else
      ::nColPos := ::nColumns // Can go "out of bounds", here we behave like clipper
   endif

   return Self

METHOD panHome() CLASS TBrowse

   ::Moved()

   if ::nColPos > 1
      if ::leftVisible > ::nFrozenCols + 1
         ::leftVisible := ::nFrozenCols + 1
         ::nColPos := 1
         ::Invalidate()
//       ::RefreshAll()
//       ::lRedrawFrame := .T.
      else
         ::nColPos := 1
         ::RefreshCurrent()
      endif
   else
      ::nColPos := 1 // Can go "out of bounds", here we behave like clipper
   endif

   return Self

METHOD panLeft() CLASS TBrowse

   local leftVis

   ::Moved()

   if ::leftVisible > ::nFrozenCols + 1
      leftVis := ::leftVisible
      /* While space left available by columns exiting to the right side of tbrowse
         is not enough to contain a new column to the left (::leftVisible doesn't change) */
      do while leftVis == ::leftVisible
         ::rightVisible--
         ::leftVisible := ::LeftDetermine()
      enddo

      /* Since panel "shifts" to the right, ::nColPos could end up "out of" the
         right side of tbrowse, so, change it to ::rightvisible if this happens */
      ::nColPos := Min( ::nColPos, ::rightVisible )
      ::Invalidate()
//    ::lRedrawFrame := .T.
//    ::RefreshAll()
   endif

   return Self

METHOD panRight() CLASS TBrowse

   local leftVis

   ::Moved()

   if ::rightVisible < ::nColumns
      leftVis := ::leftVisible
      do while leftVis == ::leftVisible
         ::rightVisible++
         ::leftVisible := ::LeftDetermine()
      enddo
      ::nColPos := Min( ::nColPos, ::rightVisible )
      ::Invalidate()
//    ::lRedrawFrame := .T.
//    ::RefreshAll()
   endif

   return Self

METHOD forceStable() CLASS TBrowse

   DO WHILE !::Stabilize()
   ENDDO

   RETURN Self

METHOD deHilite() CLASS TBrowse

   local nRow
   LOCAL nCol

   IF ::rowPos < 1 .OR. ::rowPos > ::rowCount
      ::rowPos := 0
   ELSEIF ::nColPos > 0 .AND. ::nColPos <= Len( ::aColumns )

      nRow := ::n_Top +; // TOFIX
              ::nRowPos +;
              iif( ::lHeaders, ::nHeaderHeight, 0 ) +;
              iif( Empty( ::cHeadSep ) .or. ! ::lHeaders, 0, 1 ) - 1
      nCol := ::aColsPos[ ::nColPos ] // TOFIX
      
      SetPos( nRow, nCol ) // TOFIX
      nCol += ::DispCell( ::nRowPos, ::nColPos, TBC_CLR_STANDARD )
      SetPos( nRow, nCol )
   ENDIF

   ::lHiLited := .F.

   RETURN Self

METHOD hilite() CLASS TBrowse

   LOCAL nRow
   LOCAL nCol

   IF ::rowPos < 1 .OR. ::rowPos > ::rowCount
      ::rowPos := 0
   ELSEIF ::nColPos >= 1 .AND. ::nColPos <= Len( ::aColumns )

      nRow := ::n_Top +; // TOFIX
              ::nRowPos +;
              iif( ::lHeaders, ::nHeaderHeight, 0 ) +;
              iif( Empty( ::cHeadSep ) .or. ! ::lHeaders, 0, 1 ) - 1
      nCol := ::aColsPos[ ::nColPos ] // TOFIX
      
      SetPos( nRow, nCol )
      nCol += ::DispCell( ::nRowPos, ::nColPos, TBC_CLR_ENHANCED )
      SetPos( nRow, nCol )

      ::lHiLited := .T.
   ENDIF
      
   #ifdef HB_COMPAT_C53
      ::n_Row := nRow
      ::n_Col := nCol
   #endif

   RETURN Self

METHOD stabilize() CLASS TBrowse

   local nRow, n
   local nWidth := ::n_Right - ::n_Left + 1 // Visible width of the browse
   local lDisplay                      // Is there something to show inside current cell?
   local nRecsSkipped                  // How many records do I really skipped?
   local nFirstRow                     // Where is on screen first row of TBrowse?
   local nOldCursor                    // Current shape of cursor (which I remove before stabilization)

   local oStartCol, oEndCol
   local nStartCol, nEndCol

   /* First, since ::nColPos can go "out of bounds" we need
      to put 1 <= ::nColpos <= ::nColumns
      And we need to do this before calling ::Configure() which
      needs a ::nColPos "inside bounds" */
   ::nColPos := Max( Min( ::nColPos, ::nColumns ), 1 )

   // I need to set columns width If TBrowse was never displayed before
   if ::lNeverDisplayed

      if !Empty( ::cBorder )
         /* NOTE: Intentionally the external version of coordinate messages. */
         DispBox( ::nTop, ::nLeft, ::nBottom, ::nRight, ::cBorder, ::cColorSpec[ 1 ] )
      endif

      ::Configure( 0 )
      //AEval( ::aColumns, {| oCol | ::SetColumnWidth( oCol ) } )

      // NOTE: It must be before call to ::freeze assigment since this call
      //       tests this iVar value, and I set it to .F. since I'm going to display TBrowse
      //       for first time
      ::lNeverDisplayed := .F.

      // Force re-evaluation of frozen space since I could not calc it before
      // being columns width not set
      if ::nFrozenCols > 0
         ::freeze := ::nFrozenCols
      endif
   endif

   nOldCursor := SetCursor( SC_NONE )

   if ::lRedrawFrame
      // How may columns fit on TBrowse width?
      ::HowManyCol()
      ::RedrawHeaders()

      // Now that browser frame has been redrawn we don't need to redraw it unless
      // displayed columns change
      ::lRedrawFrame := .F.

   else
      oStartCol := ::aColumns[ iif( ::rightVisible != 0, ::rightVisible, 1 ) ]
      nStartCol := ::aColsPos[ iif( ::rightVisible != 0, ::rightVisible, 1 ) ]
      oEndCol := ::aColumns[ iif( ::nFrozenCols > 0, 1, ::leftVisible ) ]
      nEndCol := ::aColsPos[ iif( ::nFrozenCols > 0, 1, ::leftVisible ) ]
      ::nColsWidth := iif( oStartCol != NIL, nStartCol, 0 ) + ;
                      iif( oStartCol != NIL, ::aColsWidth[ iif( ::rightVisible != 0, ::rightVisible, 1 ) ], 0 ) - nEndCol
   endif

   // From this point there is stabilization of rows which is made up of three phases
   // 1st repositioning of data source
   // 2nd redrawing of rows, after each row we exit stabilization loop with .F.
   // 3rd if all rows have been redrawn we set ::lStable state to .T.
   if !::lStable

      // NOTE: I can enter here because of a movement key or a ::RefreshAll():ForceStable() call

      // If I have a requested movement still to handle
      if ::nRecsToSkip != 0

         // If I'm not under cursor (maybe I've interrupted an ongoing stabilization) I have to set data source to cursor position
         if ::nLastRetrieved != ::nNewRowPos
            Eval( ::bSkipBlock, ::nNewRowPos - ::nLastRetrieved )
            ::nLastRetrieved := ::nNewRowPos
         endif

         nRecsSkipped := Eval( ::bSkipBlock, ::nRecsToSkip )

         // I've tried to move past top or bottom margin
         if nRecsSkipped == 0

            if ::nRecsToSkip > 0
               ::lHitBottom := .T.

            elseif ::nRecsToSkip < 0
               ::lHitTop := .T.

            // else ::nRecsToSkip == 0
               //
            endif

         elseif nRecsSkipped == ::nRecsToSkip

            // If after movement I'm still inside present TBrowse
            if ( ::nNewRowPos + nRecsSkipped >= 1 ) .and. ( ::nNewRowPos + nRecsSkipped <= ::RowCount )
               ::nNewRowPos += nRecsSkipped
               ::nLastRetrieved := ::nNewRowPos
               // This is needed since present TBrowse has no cache, so I need to repaint current row
               // rereading it from data source and to force rereading from data source I have to mark
               // row as invalid
               ::aRedraw[ ::nNewRowPos ] := .T.

            else
               // It was K_PGDN or K_PGUP
               if Abs( nRecsSkipped ) >= ::RowCount

                  // K_PGDN
                  if nRecsSkipped > 0
                     ::nLastRetrieved := ::RowCount

                  else // K_PGUP
                     ::nLastRetrieved := 1

                  endif
                  ::RefreshAll()

               else // K_DN or K_UP

                  // Where does really start first TBrowse row?
                  nFirstRow := ::n_Top + iif( ::lHeaders, ::nHeaderHeight, 0 ) + iif( Empty( ::cHeadSep ) .or. !::lHeaders, 0, 1 )

                  // I'm at top or bottom of TBrowse so I can scroll
                  if ::nNewRowPos == ::RowCount
                     Scroll( nFirstRow + nRecsSkipped - 1, ::n_Left, nFirstRow + ::RowCount - 1, ::n_Right, nRecsSkipped )
                     ::nLastRetrieved := ::RowCount

                  else
                     Scroll( nFirstRow, ::n_Left, nFirstRow + ::RowCount + nRecsSkipped, ::n_Right, nRecsSkipped )
                     ::nLastRetrieved := 1

                  endif

                  // I've scrolled on screen rows, now I need to scroll ::aRedraw array as well!
                  if nRecsSkipped > 0
                     ADel( ::aRedraw, 1 )
                  else
                     AIns( ::aRedraw, 1 )
                  endif

                  ::aRedraw[ ::nNewRowPos ] := .T.
               endif
            endif

         else // I couldn't move as far as requested

            // I need to refresh all rows if I go past current top or bottom row
            if ( ::nNewRowPos + nRecsSkipped < 1 ) .or. ( ::nNewRowPos + nRecsSkipped > ::RowCount )
               // don't go past boundaries
               ::nNewRowPos := iif( nRecsSkipped > 0, ::RowCount, 1 )
               ::RefreshAll()

            else
               ::nNewRowPos += nRecsSkipped
               ::aRedraw[ ::nNewRowPos ] := .T.

            endif

            ::nLastRetrieved := ::nNewRowPos

         endif

         // Data source moved, so next time I won't enter this stage of stabilization
         ::nRecsToSkip := 0

         // Exit first stage of stabilization
         SetCursor( nOldCursor )
         return .F.

      endif

      // Data source is alredy at correct record number, now we need
      // to repaint browser accordingly.
      for nRow := 1 to ::RowCount

         // if there is a row to repaint
         if ::aRedraw[ nRow ]

            DispOutAt( ::n_Top + nRow + iif( ::lHeaders, ::nHeaderHeight, 0 ) + iif( Empty( ::cHeadSep ) .or. !::lHeaders, 0, 1 ) - 1, ::n_Left,;
                      Space( ( nWidth - ::nColsWidth ) / 2 ), ::aColorSpec[ 1 ] )

            for n := iif( ::nFrozenCols > 0, 1, ::leftVisible ) to ::rightVisible

               if ::nFrozenCols > 0 .and. n == ::nFrozenCols + 1
                  n := ::leftVisible
               endif

               if nRow == 1
                  ::aColsPos[ n ] := Col()
               endif

               // NOTE: If my TBrowse has 20 rows but I have only 3 recs, clipper clears
               //       remaining 17 rows in a single operation, I will, instead, try to skip
               //       17 times. Should be made more clever.
               if nRow != ::nLastRetrieved
                  if lDisplay := Eval( ::bSkipBlock, nRow - ::nLastRetrieved ) == ( nRow - ::nLastRetrieved )
                     ::nLastRetrieved := nRow
                  endif
               else
                  lDisplay := .T.
               endif

               if lDisplay
                  ::DispCell( nRow, n, TBC_CLR_STANDARD )
               else
                  // Clear cell
                  DispOut( Space( ::aColsWidth[ n ] ), tbr_GetColor( ::aColorSpec, ::aColumns[ n ]:defColor, TBC_CLR_STANDARD ) )
               endif

               if n < ::rightVisible
                  if ::aColumns[ n + 1 ]:ColSep != NIL
                     DispOut( ::aColumns[ n + 1 ]:ColSep, ::aColorSpec[ 1 ] )

                  elseif ::cColSep != NIL
                     DispOut( ::cColSep, ::aColorSpec[ 1 ] )

                  endif
               endif
            next

            DispOut( Space( Int( Round( ( nWidth - ::nColsWidth ) / 2, 0 ) ) ), ::aColorSpec[ 1 ] )

            // doesn't need to be redrawn
            ::aRedraw[ nRow ] := .F.

            // Exit incremental row stabilization
            SetCursor( nOldCursor )
            return .F.
         endif

      next

      // If I reach this point I've repainted all rows so I can set ::lStable state
      if nRow > ::RowCount

         // If I have fewer records than available TBrowse rows, cursor cannot be lower than
         // last record (note ::lHitBottom is set only during a movement)
         if ::nLastRetrieved < ::nNewRowPos
            ::nNewRowPos := ::nLastRetrieved
         endif

         // If I'm not already under cursor I have to set data source to cursor position
         if ::nLastRetrieved != ::nNewRowPos
            Eval( ::bSkipBlock, ::nNewRowPos - ::nLastRetrieved )
            ::nLastRetrieved := ::nNewRowPos
         endif

         // new cursor position
         ::nRowPos := ::nNewRowPos

//       ::HitTop := ::lHitTop
//       ::HitBottom := ::lHitBottom

         if ::lAutoLite
            ::Hilite()
         else
            ::PosCursor()
         endif
         SetCursor( nOldCursor )
         ::lStable := .T.

         return .T.
      endif

   else
      /* NOTE: DBU relies upon current cell being reHilited() even if already stable */
      if ::lAutoLite
         ::Hilite()
      else
         ::PosCursor()
      endif
      SetCursor( nOldCursor )
      return .T.

   endif

   return .F.

/* NOTE: Incompatibility: In C5x this function will refresh the
         screen if a non-empty aRect was passed. It does this
         without skipping/reloading all the records, so I suspect
         some internal buffering. For now I left out this feature,
         so caller has to refresh manually. [vszakats] */

METHOD colorRect( aRect, aRectColor ) CLASS TBrowse

   if ISARRAY( aRect ) .and. ISARRAY( aRectColor )
      ::aRect       := aRect
      ::aRectColor  := aRectColor
   endif

   return Self

/* -------------------------------------------- */

METHOD InitColumn( oCol, lAddColumn ) CLASS TBrowse

   IF !lAddColumn .AND. ISOBJECT( oCol ) .AND. ISBLOCK( oCol:block )
      RETURN {;
         oCol                          ,; // TBCI_OBJ      
         ::SetColumnWidth( oCol )      ,; // TBCI_WIDTH    
         0                             ,; // TBCI_WIDTHCELL
         ""                            ,; // TBCI_HEADING  
         ""                            ,; // TBCI_FOOTING  
         ""                            ,; // TBCI_COLSEP   
         0                             ,; // TBCI_SEPWIDTH 
         oCol:defColor                 ,; // TBCI_DEFCOLOR
         .F.                           ,; // TBCI_SETWIDTH 
         .T.                           ,; // TBCI_LCOLSEP
         0                             }  // TBCI_SCRCOLPOS
   ENDIF

   RETURN {;
      oCol                          ,; // TBCI_OBJ      
      0                             ,; // TBCI_WIDTH    
      0                             ,; // TBCI_WIDTHCELL
      ""                            ,; // TBCI_HEADING  
      ""                            ,; // TBCI_FOOTING  
      ""                            ,; // TBCI_COLSEP   
      0                             ,; // TBCI_SEPWIDTH 
      {}                            ,; // TBCI_DEFCOLOR
      .F.                           ,; // TBCI_SETWIDTH 
      .T.                           ,; // TBCI_LCOLSEP  
      0                             }  // TBCI_SCRCOLPOS

METHOD PosCursor() CLASS TBrowse

   local nRow := ::n_Top + ::nRowPos + iif( ::lHeaders, ::nHeaderHeight, 0 ) + iif( Empty( ::cHeadSep ) .or. ! ::lHeaders, 0, 1 ) - 1
   local nCol
   local cType := ValType( Eval( ::aColumns[ ::nColPos ]:block ) )

   nCol := ::aColsPos[ ::nColPos ] + iif( cType == "L", ::aColsWidth[ ::nColPos ] / 2, 0 )

   // Put cursor on first char of cell value
   SetPos( nRow, nCol )

   #ifdef HB_COMPAT_C53
      ::n_Row := nRow
      ::n_Col := nCol
   #endif

   return Self

// Calculate how many columns fit on the browse width including ColSeps
METHOD HowManyCol() CLASS TBrowse

   local nToAdd
   local nWidth := ::n_Right - ::n_Left + 1

   local nColsVisible
   local nColsWidth

#ifdef COMMENT

   local saveColsWidth
   local tryLeftVisible
   local nLeftCol
   local oErr
   
   nColsWidth   := 0
   nColsVisible := 0

   if ::nFrozenCols > 0
      nColsVisible := 0
      do while nColsVisible < ::nFrozenCols .and. nColsVisible < ::nColumns
         nToAdd := ::aColsWidth[ nColsVisible + 1 ]

         if nColsVisible >= 1 .and. nColsVisible < ::nColumns .and. ::aColsWidth[ nColsVisible ] > 0
            nToAdd += iif( ::aColumns[ nColsVisible + 1 ]:ColSep != NIL, Len( ::aColumns[ nColsVisible + 1 ]:ColSep ), Len( ::cColSep ) )
         endif

         if nColsWidth + nToAdd > nWidth
            exit
         endif

         nColsWidth += nToAdd
         nColsVisible++
      enddo

      if nColsWidth + nToAdd > nWidth .and. nColsVisible < ::nFrozenCols
         /* NOTE: Why do I change frozen columns here? */
         ::freeze       := 0
         ::nColsWidth   := 0
         ::rightVisible := nColsVisible
         ::nColsVisible := nColsVisible
         return Self
      endif

      if ::leftVisible <= ::nFrozenCols
         ::leftVisible := ::nFrozenCols + 1
      endif

   endif

   // ;

   // BDj notes:
   // Cannot assume that ::leftVisible is correct
   // (eg. if ::nColPos was assigned ::rightVisible+1)
   // Must do the following in a loop repeatedly until:
   // (0) ::nColPos <= ::nFrozenCols (assume ::nColPos > 0)
   // or
   // (1) ::leftVisible <= ::nColPos <= ::rightVisible
   // or
   // (2) the above conditions are impossible (runtime error)

   saveColsWidth  := nColsWidth
   tryLeftVisible := ::leftVisible

   // ::nColPos is to the left of leftVisible
   if ::nFrozenCols == 0 .and. tryLeftVisible > ::nColPos
      tryLeftVisible := ::nColPos
   endif

   do while .T.

      nColsVisible := Max( 0, tryLeftVisible - 1 )

      do while nColsVisible < ::nColumns
         // which column is displayed to the left of next col?
         if ::nFrozenCols > 0 .and. nColsVisible + 1 == tryLeftVisible
            nLeftCol := ::nFrozenCols
         else
            nLeftCol := nColsVisible
         endif

         nToAdd := ::aColsWidth[ nColsVisible + 1 ]

         // next, we must check against nLeftCol, not nColsVisible:
         if ( nColsVisible >= tryLeftVisible .or. ::nFrozenCols > 0 ) .and.;
            ( nLeftCol > 0 ) .and.;
            ::aColsWidth[ nLeftCol ] > 0

            nToAdd += iif( ::aColumns[ nColsVisible + 1 ]:ColSep != NIL, Len( ::aColumns[ nColsVisible + 1 ]:ColSep ), Len( ::cColSep ) )
         endif

         if nColsWidth + nToAdd > nWidth
            exit
         endif

         nColsWidth += nToAdd
         nColsVisible++
      enddo

      // check: is ::nColPos fit within these calculated cols?
      if ::nColPos <= ::nFrozenCols .or.;
         ( tryLeftVisible <= ::nColPos .and. ::nColPos <= nColsVisible )
         exit
      endif

      // not ok. can retry?
      if tryLeftVisible == ::nColumns
         // cannot fit ::nColPos into display, generate Error TBROWSE
         oErr := ErrorNew()
         oErr:severity    := ES_ERROR
         oErr:genCode     := EG_LIMIT
         oErr:subSystem   := "TBROWSE"
         oErr:subCode     := 0
         oErr:description := "Width limit exceeded"
         oErr:canRetry    := .F.
         oErr:canDefault  := .F.
         oErr:fileName    := ""
         oErr:osCode      := 0
         Eval( ErrorBlock(), oErr )
      endif

      // retry until ::nColPos fit into display
      tryLeftVisible++
      nColsWidth := saveColsWidth

   enddo

   ::leftVisible  := Max( 1, tryLeftVisible )
   ::rightVisible := Max( 1, nColsVisible )
   ::nColsVisible := Max( 1, nColsVisible )
   ::nColsWidth   := nColsWidth

#endif

   nColsWidth := 0
   nColsVisible := 0
   
   if ::nFrozenCols > 0

      if ::leftVisible <= ::nFrozenCols
         ::leftVisible := ::nFrozenCols + 1
      endif

      do while nColsVisible < ::nFrozenCols

         nToAdd := ::aColsWidth[ nColsVisible + 1 ]

         if nColsVisible >= 1 .and. nColsVisible < ::nColumns
            nToAdd += iif( ::aColumns[ nColsVisible + 1 ]:ColSep != NIL,;
                           Len( ::aColumns[ nColsVisible + 1 ]:ColSep ),;
                           Len( ::cColSep ) )
         endif

         if nColsWidth + nToAdd > nWidth
            exit
         endif

         nColsWidth += nToAdd
         nColsVisible++
      enddo

      if nColsWidth > nWidth
         // NOTE: Why do I change frozen columns here?
         ::freeze := 0
         nColsWidth := 0
      endif
   endif

   nColsVisible := Max( ::leftVisible - 1, 0 )

   do while nColsVisible < ::nColumns

      nToAdd := ::aColsWidth[ nColsVisible + 1 ]

      if nColsVisible >= ::leftVisible .or. ::nFrozenCols > 0
         nToAdd += iif( ::aColumns[ nColsVisible + 1 ]:ColSep != NIL, Len( ::aColumns[ nColsVisible + 1 ]:ColSep ), Len( ::cColSep ) )
      endif

      if nColsWidth + nToAdd > nWidth
         exit
      endif

      nColsWidth += nToAdd
      nColsVisible++
   enddo

   ::nColsVisible := Max( 1, nColsVisible )
   ::nColsWidth   := nColsWidth
   ::rightVisible := ::nColsVisible

   return Self

// Movement keys cause TBrowse to become unstable.
METHOD Moved() CLASS TBrowse

   // Internal flags used to set ::lHitTop/::lHitBottom during next stabilization
   ::lHitTop         := .F.
   ::lHitBottom      := .F.

   // No need to DeHilite() current cell more than once
   if ::lStable
      if ::lAutoLite
         ::DeHilite()
      else
         ::PosCursor()
      endif
      ::lStable := .F.
   endif

   return Self

METHOD DispCell( nRow, nCol, nMode ) CLASS TBrowse

   LOCAL oCol     := ::aColumns[ nCol ]    // TOFIX
   LOCAL nWidth   := ::aColsWidth[ nCol ]  // TOFIX
   LOCAL ftmp     := Eval( oCol:block )
   LOCAL cType    := ValType( ftmp )
   LOCAL cPicture := oCol:Picture
   LOCAL nSkip    := 0
   LOCAL aDefColor
   LOCAL cColor

   IF !ISCHARACTER( cPicture )
      cPicture := ""
   ENDIF

   IF ! Empty( ::aRect ) .AND. ; // TOFIX: aRect validation ?
      nCol >= ::aRect[ 2 ] .AND. ;
      nCol <= ::aRect[ 4 ] .AND. ;
      nRow >= ::aRect[ 1 ] .AND. ;
      nRow <= ::aRect[ 3 ] .AND. ;
      ! Empty( ::aRectColor ) // TOFIX: ISEMPTY ?
      cColor := tbr_GetColor( ::aColorSpec, ::aRectColor, nMode )
   ELSE
      /* NOTE: Not very optimal that we're evaluating this block all the time. 
               But CA-Cl*pper always has a block here, and there is no other way 
               to tell if the code in it is NIL (the default) or something valuable. 
               [vszakats] */
      aDefColor := Eval( oCol:colorBlock, ftmp )
      cColor := tbr_GetColor( ::aColorSpec, iif( ISARRAY( aDefColor ), aDefColor, oCol:defColor ), nMode ) // TOFIX: ISARRAY ?
   ENDIF

   SWITCH cType
   CASE "C"
   CASE "M"
      DispOut( PadR( Transform( ftmp, cPicture ), nWidth ), cColor )
      EXIT

   CASE "N"
      DispOut( PadL( Transform( ftmp, cPicture ), nWidth ), cColor )
      EXIT

   CASE "D"
      DispOut( PadR( Transform( ftmp, iif( cPicture == "", "@D", cPicture ) ), nWidth ), cColor )
      EXIT

   CASE "L"
      ftmp := PadC( iif( ftmp, "T", "F" ), nWidth )
      nSkip := nWidth - Len( LTrim( ftmp ) ) - 1
      DispOut( Space( Len( ftmp ) - Len( LTrim( ftmp ) ) ), ::aColorSpec[ 1 ] )
      DispOut( ftmp, cColor )
      DispOut( Space( Len( ftmp ) - Len( RTrim( ftmp ) ) ), ::aColorSpec[ 1 ] )
      EXIT

   OTHERWISE
      DispOut( Space( nWidth ), cColor )

   ENDSWITCH

   RETURN nSkip

METHOD WriteMLineText( cStr, nPadLen, lHeader, cColor ) CLASS TBrowse

   local n
   local nCol := Col()
   local nRow := Row()

   // Do I have to write an header or a footer?
   if lHeader

      // Simple case, write header as usual
      if ::nHeaderHeight == 1
         DispOut( PadR( cStr, nPadLen ), cColor )
      else
         // Headers are aligned to bottom
         cStr := Replicate( ";", ::nHeaderHeight - hb_TokenCount( cStr, ";" ) ) + cStr

         for n := ::nHeaderHeight to 1 step -1
            SetPos( nRow + n - 1, nCol )
            DispOut( PadR( hb_TokenGet( @cStr, n, ";" ), nPadLen ), cColor )
         next

         SetPos( nRow, nCol + nPadLen )
      endif

   // footer
   else

      // Simple case, write footer as usual
      if ::nFooterHeight == 1
         DispOut( PadR( cStr, nPadLen ), cColor )
      else
         for n := 0 to ::nFooterHeight - 1
            SetPos( nRow - n, nCol )
            DispOut( PadR( hb_TokenGet( @cStr, ::nFooterHeight - n, ";" ), nPadLen ), cColor )
         next

         SetPos( nRow, nCol + nPadLen )
      endif

   endif

   return Self

METHOD SetColumnWidth( oCol ) CLASS TBrowse

   local nWidth
   local xValue
   local cString
   local nTokenPos
   local nLen

   // if oCol has :Width property set I use it
   if oCol:width != NIL

      nWidth := oCol:width

   elseif ISBLOCK( oCol:block )

      xValue := Eval( oCol:block )
      nWidth := tbr_CalcWidth( xValue, ValType( xValue ), oCol:picture )

      cString := oCol:heading
      do while ( nLen := Len( hb_TokenPtr( @cString, @nTokenPos, ";" ) ) ) > 0
         nWidth := Max( nLen, nWidth )
      enddo

      cString := oCol:footing
      do while ( nLen := Len( hb_TokenPtr( @cString, @nTokenPos, ";" ) ) ) > 0
         nWidth := Max( nLen, nWidth )
      enddo

   else

      nWidth := 0

   endif

   return Min( nWidth, ::n_Right - ::n_Left + 1 )

// Gets TBrowse width and width of displayed columns plus colsep
METHOD redrawHeaders() CLASS TBrowse

   local n, nTPos, nBPos
   local cBlankBox := Space(9)
   local nScreenRowT
   local nScreenRowB
   local nLCS             // Len( ColSep )
   local nWidth := ::n_Right - ::n_Left + 1
   local nColor

   if ::lHeaders // Drawing headers

      // Clear area of screen occupied by headers
      DispBox( ::n_Top, ::n_Left, ::n_Top + ::nHeaderHeight - 1, ::n_Right, cBlankBox, ::aColorSpec[ 1 ] )

      if Empty( ::cHeadSep ) // Draw horizontal heading separator line
         nScreenRowT := NIL
         /* ; NOTE: This is a bug in CA-Cl*pper 5.3. [vszakats] */
         nColor := TBC_CLR_STANDARD
      else
         DispOutAt( ( nScreenRowT := ::n_Top + ::nHeaderHeight ), ::n_Left,;
                   Replicate( Right( ::cHeadSep, 1 ), nWidth ), ::aColorSpec[ 1 ] )
         nColor := TBC_CLR_HEADING
      endif

      // Set cursor at first field start of description
      SetPos( ::n_Top, ::n_Left + ( ( nWidth - ::nColsWidth ) / 2 ) )

      for n := iif( ::nFrozenCols > 0, 1, ::leftVisible ) to ::rightVisible
         if ::nFrozenCols > 0 .and. n == ::nFrozenCols + 1
            n := ::leftVisible
         endif

         ::WriteMLineText( ::aColumns[ n ]:Heading, ::aColsWidth[ n ], .T., tbr_GetColor( ::aColorSpec, ::aColumns[ n ]:defColor, nColor ) )

         if n < ::rightVisible
            // Set cursor at start of next field description
            SetPos( Row(), Col() + iif( ::aColumns[ n + 1 ]:ColSep != NIL, Len( ::aColumns[ n + 1 ]:ColSep ), Len( ::cColSep ) ) )
         endif
      next
   endif

   if ::lFooters // Drawing footers

      // Clear area of screen occupied by footers
      DispBox( ::n_Bottom - ::nFooterHeight + 1, ::n_Left, ::n_Bottom, ::n_Right, cBlankBox, ::aColorSpec[ 1 ] )

      if Empty( ::cFootSep ) // Draw horizontal footing separator line
         nScreenRowB := NIL
         /* ; NOTE: This is a bug in CA-Cl*pper 5.3. [vszakats] */
         nColor := TBC_CLR_STANDARD
      else
         DispOutAt( ( nScreenRowB := ::n_Bottom - ::nFooterHeight ), ::n_Left,;
                   Replicate( Right( ::cFootSep, 1 ), nWidth ), ::aColorSpec[ 1 ] )
         nColor := TBC_CLR_FOOTING
      endif

      // Set cursor at first field start of description
      SetPos( ::n_Bottom, ::n_Left + ( ( nWidth - ::nColsWidth ) / 2 ) )

      for n := iif( ::nFrozenCols > 0, 1, ::leftVisible ) to ::rightVisible
         if ::nFrozenCols > 0 .and. n == ::nFrozenCols + 1
            n := ::leftVisible
         endif

         ::WriteMLineText( ::aColumns[ n ]:Footing, ::aColsWidth[ n ], .F., tbr_GetColor( ::aColorSpec, ::aColumns[ n ]:defColor, nColor ) )

         if n < ::rightVisible
            // Set cursor at start of next field description
            SetPos( Row(), Col() + iif( ::aColumns[ n + 1 ]:ColSep != NIL, Len( ::aColumns[ n + 1 ]:ColSep ), Len( ::cColSep ) ) )
         endif
      next
   endif

   nTPos := nBPos := ::n_Left + ( ( nWidth - ::nColsWidth ) / 2 )

   // Draw headin/footing column separator
   for n := iif( ::nFrozenCols > 0, 1, ::leftVisible ) to ::rightVisible

      if ::nFrozenCols > 0 .and. n == ::nFrozenCols + 1
         n := ::leftVisible
      endif

      if n < ::rightVisible

         nLCS := iif( ::aColumns[ n + 1 ]:ColSep != NIL, Len( ::aColumns[ n + 1 ]:ColSep ), Len( ::cColSep ) )

         if nScreenRowT != NIL
            DispOutAt( nScreenRowT, ( nTPos += ::aColsWidth[ n ] ), Left( ::cHeadSep, nLCS ), ::aColorSpec[ 1 ] )
            nTPos += nLCS
         endif

         if nScreenRowB != NIL
            DispOutAt( nScreenRowB, ( nBPos += ::aColsWidth[ n ] ), Left( ::cFootSep, nLCS ), ::aColorSpec[ 1 ] )
            nBPos += nLCS
         endif

      endif
   next

   return Self

// NOTE: Not tested, could be broken
METHOD MGotoYX( nRow, nCol ) CLASS TBrowse

   local nColsLen
   local nI
   local nNewRow

   // Am I inside TBrowse display area ?
   if nRow > ::n_Top .and. nRow < ::n_Bottom .and. ;
      nCol > ::n_Left .and. nCol < ::n_Right

      // if not stable force repositioning of data source; maybe this is not first Stabilize() call after
      // TBrowse became unstable, but we need to call Stabilize() al least one time before moving again to be sure
      // data source is under cursor position
      if ::lStable
         ::Moved()
      else
         ::stabilize()
      endif

      // Set new row position
      nNewRow := nRow - ::n_Top + iif( ::lHeaders, ::nHeaderHeight, 0 ) + iif( Empty( ::cHeadSep ) .or. ! ::lHeaders, 0, 1 ) - 1
      ::nRecsToSkip := nNewRow - ::nNewRowPos

      // move data source accordingly
      ::Stabilize()

      // Now move to column under nCol
      nColsLen := 0
      // NOTE: I don't think it is correct, have to look up docs
      nI := iif( ::nFrozenCols > 0, ::nFrozenCols, ::leftVisible )

      do while nColsLen < nCol .and. nI < ::rightVisible

         nColsLen += ::aColsWidth[ nI ]
         if nI >= 1 .and. nI < ::nColumns
            nColsLen += iif( ::aColumns[ nI ]:ColSep != NIL, Len( ::aColumns[ nI ]:ColSep ), Len( ::cColSep ) )
         endif

         nI++

      enddo

      ::nColPos := nI

      // Force redraw of current row with new cell position
      ::RefreshCurrent()

   endif

   return Self

/* -------------------------------------------- */

METHOD autoLite( lAutoLite ) CLASS TBrowse

   if ISLOGICAL( lAutoLite )
      ::lAutoLite := lAutoLite
   endif

   return ::lAutoLite

METHOD nTop( nTop ) CLASS TBrowse

   IF nTop != NIL
      #ifdef HB_COMPAT_C53
         ::n_Top := _eInstVar( Self, "NTOP", nTop, "N", 1001 )
         IF !Empty( ::cBorder )
            ::n_Top++
         ENDIF
      #else
         ::n_Top := _eInstVar( Self, "NTOP", nTop, "N", 1001, {| o, x | HB_SYMBOL_UNUSED( o ), x >= 0 } )
      #endif
      ::Configure( 2 )
   ENDIF

   #ifdef HB_COMPAT_C53
      IF !Empty( ::cBorder )
         RETURN ::n_Top - 1
      ENDIF
   #endif

   RETURN ::n_Top

METHOD nLeft( nLeft ) CLASS TBrowse

   IF nLeft != NIL
      #ifdef HB_COMPAT_C53
         ::n_Left := _eInstVar( Self, "NLEFT", nLeft, "N", 1001 )
         IF !Empty( ::cBorder )
            ::n_Left++
         ENDIF
      #else
         ::n_Left := _eInstVar( Self, "NLEFT", nLeft, "N", 1001, {| o, x | HB_SYMBOL_UNUSED( o ), x >= 0 } )
      #endif
      ::Configure( 2 )
   ENDIF

   #ifdef HB_COMPAT_C53
      IF !Empty( ::cBorder )
         RETURN ::n_Left - 1
      ENDIF
   #endif

   RETURN ::n_Left

METHOD nBottom( nBottom ) CLASS TBrowse

   IF nBottom != NIL
      ::n_Bottom := _eInstVar( Self, "NBOTTOM", nBottom, "N", 1001, {| o, x | x >= o:nTop } )
      #ifdef HB_COMPAT_C53
         IF !Empty( ::cBorder )
            ::n_Bottom--
         ENDIF
      #endif
      ::Configure( 2 )
   ENDIF

   #ifdef HB_COMPAT_C53
      IF !Empty( ::cBorder )
         RETURN ::n_Bottom + 1
      ENDIF
   #endif

   RETURN ::n_Bottom

METHOD nRight( nRight ) CLASS TBrowse

   IF nRight != NIL
      ::n_Right := _eInstVar( Self, "NRIGHT", nRight, "N", 1001, {| o, x | x >= o:nLeft } )
      #ifdef HB_COMPAT_C53
         IF !Empty( ::cBorder )
            ::n_Right--
         ENDIF
      #endif
      ::Configure( 2 )
   ENDIF

   #ifdef HB_COMPAT_C53
      IF !Empty( ::cBorder )
         RETURN ::n_Right + 1
      ENDIF
   #endif

   RETURN ::n_Right

METHOD colorSpec( cColorSpec ) CLASS TBrowse

   if cColorSpec != NIL
      ::cColorSpec := _eInstVar( Self, "COLORSPEC", cColorSpec, "C", 1001 )
      ::aColorSpec := tbr_CookColor( ::cColorSpec )
      ::Configure( 1 )
   endif

   return ::cColorSpec

METHOD colSep( cColSep ) CLASS TBrowse

   if cColSep != NIL
      ::cColSep := _eInstVar( Self, "COLSEP", cColSep, "C", 1001 )
   endif

   return ::cColSep

METHOD footSep( cFootSep ) CLASS TBrowse

   if cFootSep != NIL
      ::cFootSep := _eInstVar( Self, "FOOTSEP", cFootSep, "C", 1001 )
   endif

   return ::cFootSep

METHOD headSep( cHeadSep ) CLASS TBrowse

   if cHeadSep != NIL
      ::cHeadSep := _eInstVar( Self, "HEADSEP", cHeadSep, "C", 1001 )
   endif

   return ::cHeadSep

/* NOTE: CA-Cl*pper has a bug where negative nColPos value will be translated to 16bit unsigned int, 
         so the behaviour will be different in this case. [vszakats] */

METHOD colPos( nColPos ) CLASS TBrowse

   if PCount() > 0
      if ISNUMBER( nColPos )
         ::nColPos := nColPos
      else
         ::nColPos := 0
      endif
   endif

   return ::nColPos

/* NOTE: CA-Cl*pper has a bug where negative nRowPos value will be translated to 16bit unsigned int, 
         so the behaviour will be different in this case. [vszakats] */

METHOD rowPos( nRowPos ) CLASS TBrowse

   if PCount() > 0
      if ISNUMBER( nRowPos )
         ::nRowPos := iif( nRowPos < 1 .or. nRowPos > ::RowCount, ::RowCount, nRowPos )
         return nRowPos
      else
         ::nRowPos := ::RowCount
         return 0
      endif
   endif

   return ::nRowPos

METHOD goBottomBlock( bBlock ) CLASS TBrowse

   if bBlock != NIL
      /* NOTE: In CA-Cl*pper the string is: "GOBOTTOMBL" */
      ::bGoBottomBlock := _eInstVar( Self, "GOBOTTOMBLOCK", bBlock, "B", 1001 )
   endif

   return ::bGoBottomBlock

METHOD goTopBlock( bBlock ) CLASS TBrowse

   if bBlock != NIL
      ::bGoTopBlock := _eInstVar( Self, "GOTOPBLOCK", bBlock, "B", 1001 )
   endif

   return ::bGoTopBlock

METHOD hitBottom( lHitBottom ) CLASS TBrowse

   if PCount() > 0
      if ISLOGICAL( lHitBottom )
         ::lHitBottom := lHitBottom
      else
         return .T.
      endif
   endif

   return ::lHitBottom

METHOD hitTop( lHitTop ) CLASS TBrowse

   if PCount() > 0
      if ISLOGICAL( lHitTop )
         ::lHitTop := lHitTop
      else
         return .T.
      endif
   endif

   return ::lHitTop

METHOD stable( lStable ) CLASS TBrowse

   if PCount() > 0
      if ISLOGICAL( lStable )
         ::lStable := lStable
      else
         return .T.
      endif
   endif

   return ::lStable

METHOD skipBlock( bSkipBlock ) CLASS TBrowse

   if bSkipBlock != NIL
      ::bSkipBlock := _eInstVar( Self, "SKIPBLOCK", bSkipBlock, "B", 1001 )
   endif

   return ::bSkipBlock

#ifdef HB_COMPAT_C53

#define _TBC_SETKEY_KEY         1
#define _TBC_SETKEY_BLOCK       2

METHOD setKey( nKey, bBlock ) CLASS TBrowse

   LOCAL bReturn
   LOCAL nPos

   /* NOTE: Assigned codeblock receives two parameters:
            {| oTBrowse, nKey | <action> } */

   IF ::aKeys == NIL
      ::aKeys := { { K_DOWN       , {| o | o:Down()    , TBR_CONTINUE   } },;
                   { K_END        , {| o | o:End()     , TBR_CONTINUE   } },;
                   { K_CTRL_PGDN  , {| o | o:GoBottom(), TBR_CONTINUE   } },;
                   { K_CTRL_PGUP  , {| o | o:GoTop()   , TBR_CONTINUE   } },;
                   { K_HOME       , {| o | o:Home()    , TBR_CONTINUE   } },;
                   { K_LEFT       , {| o | o:Left()    , TBR_CONTINUE   } },;
                   { K_PGDN       , {| o | o:PageDown(), TBR_CONTINUE   } },;
                   { K_PGUP       , {| o | o:PageUp()  , TBR_CONTINUE   } },;
                   { K_CTRL_END   , {| o | o:PanEnd()  , TBR_CONTINUE   } },;
                   { K_CTRL_HOME  , {| o | o:PanHome() , TBR_CONTINUE   } },;
                   { K_CTRL_LEFT  , {| o | o:PanLeft() , TBR_CONTINUE   } },;
                   { K_CTRL_RIGHT , {| o | o:PanRight(), TBR_CONTINUE   } },;
                   { K_RIGHT      , {| o | o:Right()   , TBR_CONTINUE   } },;
                   { K_UP         , {| o | o:Up()      , TBR_CONTINUE   } },;
                   { K_ESC        , {|   |               TBR_EXIT       } },;
                   { K_LBUTTONDOWN, {| o | TBMouse( o, MRow(), MCol() ) } } }

      #ifdef HB_EXTENSION
         AAdd( ::aKeys, { K_MWFORWARD  , {| o | o:Up()      , TBR_CONTINUE   } } )
         AAdd( ::aKeys, { K_MWBACKWARD , {| o | o:Down()    , TBR_CONTINUE   } } )
      #endif
   ENDIF

   IF ( nPos := AScan( ::aKeys, {| x | x[ _TBC_SETKEY_KEY ] == nKey } ) ) == 0
      IF ISBLOCK( bBlock )
         AAdd( ::aKeys, { nKey, bBlock } )
      ENDIF
      bReturn := bBlock
   ELSEIF ISBLOCK( bBlock )
      ::aKeys[ nPos ][ _TBC_SETKEY_BLOCK ] := bBlock
      bReturn := bBlock
   ELSEIF PCount() == 1
      bReturn := ::aKeys[ nPos ][ _TBC_SETKEY_BLOCK ]
   ELSE
      bReturn := ::aKeys[ nPos ][ _TBC_SETKEY_BLOCK ]
      IF PCount() == 2 .AND. bBlock == NIL .AND. nKey != 0
         ADel( ::aKeys, nPos )
         ASize( ::aKeys, Len( ::aKeys ) - 1 )
      ENDIF
   ENDIF

   RETURN bReturn

METHOD applyKey( nKey ) CLASS TBrowse

   LOCAL bBlock := ::setKey( nKey )

   DEFAULT bBlock TO ::setKey( 0 )

   IF bBlock == NIL
      RETURN TBR_EXCEPTION
   ENDIF

   RETURN Eval( bBlock, Self, nKey )

METHOD hitTest( nMRow, nMCol ) CLASS TBrowse
   local i

   ::nmRowPos := ::nRowPos
   ::nmColPos := ::nColPos
  
   if nMRow < ::rect[ 1 ] .or. nMRow > ::rect[ 3 ]
      return HTNOWHERE
   endif
  
   if nMCol < ::rect[ 2 ] .or. nMCol > ::rect[ 4 ]
      return HTNOWHERE
   endif
  
   ::nmRowPos := nMRow - ::rect[ 1 ] + 1

   for i := 1 to Len( ::aVisibleCols )
      if nMCol < ::aVisibleCols[ i ]
         exit
      endif
   next
  
   ::mColPos := ::aVisibleCols[ i ]

   return HTCELL

METHOD border( cBorder ) CLASS TBrowse

   IF PCount() > 0

      cBorder := _eInstVar( Self, "BORDER", cBorder, "C", 1001 )

      IF Len( cBorder ) == 0 .OR. ;
         Len( cBorder ) == 8
      
         IF Empty( ::cBorder ) .AND. !Empty( cBorder )
            ::n_Top++
            ::n_Left++
            ::n_Bottom--
            ::n_Right--
            ::configure( 2 )
         ELSEIF !Empty( ::cBorder ) .AND. Empty( cBorder )
            ::n_Top--
            ::n_Left--
            ::n_Bottom++
            ::n_Right++
            ::configure( 2 )
         ENDIF

         ::cBorder := cBorder
      ENDIF
   ENDIF

   RETURN ::cBorder

METHOD nRow() CLASS TBrowse
   return ::n_Row

METHOD nCol() CLASS TBrowse
   return ::n_Col

METHOD mRowPos() CLASS TBrowse
   return ::nmRowPos

METHOD mColPos() CLASS TBrowse
   return ::nmColPos

METHOD message( cMessage ) CLASS TBrowse

   IF cMessage != NIL
      ::cMessage := _eInstVar( Self, "MESSAGE", cMessage, "C", 1001 )
   ENDIF

   RETURN ::cMessage

METHOD setStyle( nStyle, lNewValue ) CLASS TBrowse

   /* NOTE: CA-Cl*pper 5.3 does no checks on the value of nStyle, so in case 
            it is zero or non-numeric, a regular RTE will happen. [vszakats] */
  
   IF nStyle > Len( ::aSetStyle ) .AND. nStyle <= 4096 /* Some reasonable limit for maximum number of styles */
      ASize( ::aSetStyle, nStyle )
   ENDIF
  
   IF ISLOGICAL( lNewValue )
      ::aSetStyle[ nStyle ] := lNewValue
   ENDIF

   RETURN ::aSetStyle[ nStyle ]

#endif

/* -------------------------------------------- */

METHOD New( nTop, nLeft, nBottom, nRight ) CLASS TBrowse

   DEFAULT nTop    TO 0
   DEFAULT nLeft   TO 0
   DEFAULT nBottom TO MaxRow()
   DEFAULT nRight  TO MaxCol()

   ::nTop    := nTop
   ::nLeft   := nLeft
   ::nBottom := nBottom
   ::nRight  := nRight

   ::cColorSpec := SetColor()
   ::aColorSpec := tbr_CookColor( ::cColorSpec )

   #ifdef HB_COMPAT_C53
      ::rect := { ::n_Top, ::n_Left, ::n_Bottom, ::n_Right }
   #endif

   return Self

FUNCTION TBrowseNew( nTop, nLeft, nBottom, nRight )
   return TBrowse():New( nTop, nLeft, nBottom, nRight )

/* -------------------------------------------- */

/* NOTE: Preprocess user-supplied colorstring for internal usage. This is 
         needed to keep full C5.x compatibility while maintaining performace.
         C5.x would always have at least two items, defaulted to the 
         current SetColor() values, the rest of the items are defaulted 
         to "N/N". [vszakats] */
STATIC FUNCTION tbr_CookColor( cColorSpec )

   LOCAL nCount := Max( hb_TokenCount( cColorSpec, "," ), 2 )
   LOCAL aColorSpec := Array( nCount )
   LOCAL cColor
   LOCAL nPos

   FOR nPos := 1 TO nCount
      cColor := hb_TokenGet( @cColorSpec, nPos, "," )
      IF nPos <= 2
         aColorSpec[ nPos ] := iif( hb_ColorToN( cColor ) == 0 .AND. !( Upper( StrTran( cColor, " ", "" ) ) == "N/N" ), hb_ColorIndex( "W/N,N/W", nPos - 1 ), cColor )
      ELSE
         aColorSpec[ nPos ] := iif( hb_ColorToN( cColor ) == 0, "N/N", cColor )
      ENDIF
   NEXT

   RETURN aColorSpec

/* NOTE: Preprocess defColor so that it can be used "blindly" afterwards. */
STATIC FUNCTION tbr_DefColor( aDefColor, aColorSpec )

   IF !ISARRAY( aDefColor )
      aDefColor := {}
   ENDIF

   ASize( aDefColor, TBC_CLR_MAX_ )

   IF !ISNUMBER( aDefColor[ TBC_CLR_STANDARD ] ) .OR. aDefColor[ TBC_CLR_STANDARD ] > Len( aColorSpec )
      aDefColor[ TBC_CLR_STANDARD ] := 1
   ENDIF
   IF !ISNUMBER( aDefColor[ TBC_CLR_ENHANCED ] ) .OR. aDefColor[ TBC_CLR_ENHANCED ] > Len( aColorSpec )
      aDefColor[ TBC_CLR_ENHANCED ] := 2
   ENDIF
#ifdef HB_COMPAT_C53
   /* NOTE: To be backwards compatible, C5.3 will fall back to C5.2 colors 
            if the extra HEADING/FOOTING positions are not specified. [vszakats] */
   IF !ISNUMBER( aDefColor[ TBC_CLR_HEADING ] ) .OR. aDefColor[ TBC_CLR_HEADING ] > Len( aColorSpec )
      aDefColor[ TBC_CLR_HEADING ] := aDefColor[ TBC_CLR_STANDARD ]
   ENDIF
   IF !ISNUMBER( aDefColor[ TBC_CLR_FOOTING ] ) .OR. aDefColor[ TBC_CLR_FOOTING ] > Len( aColorSpec )
      aDefColor[ TBC_CLR_FOOTING ] := aDefColor[ TBC_CLR_STANDARD ]
   ENDIF
#endif

   RETURN aDefColor

/* NOTE: Strict sanity check for a color array. We need to use this 
         for the array returned by a :colorBlock. */
STATIC FUNCTION tbr_GetColor( aColorSpec, aDefColor, nMode )

   IF !ISARRAY( aDefColor )
      /* NOTE: This fits both C5.2 and C5.3. In C5.2 nMode is 1 or 2. [vszakats] */
      RETURN aColorSpec[ { 1, 2, 1, 1 }[ nMode ] ]
   ELSEIF nMode > Len( aDefColor )
      /* NOTE: C5.3 and C5.2 compatible method. To be backwards compatible, 
               C5.3 will fall back to C5.2 colors if the extra HEADING/FOOTING 
               positions are not specified. [vszakats] */
      SWITCH NMODE
      CASE TBC_CLR_STANDARD ; RETURN aColorSpec[ 1 ]
      CASE TBC_CLR_ENHANCED ; RETURN aColorSpec[ 2 ]
      CASE TBC_CLR_HEADING  ; RETURN aColorSpec[ iif( Len( aDefColor ) >= TBC_CLR_STANDARD .AND. ISNUMBER( aDefColor[ TBC_CLR_STANDARD ] ) .AND. aDefColor[ 1 ] <= Len( aColorSpec ), aDefColor[ TBC_CLR_STANDARD ], 1 ) ]
      CASE TBC_CLR_FOOTING  ; RETURN aColorSpec[ iif( Len( aDefColor ) >= TBC_CLR_STANDARD .AND. ISNUMBER( aDefColor[ TBC_CLR_STANDARD ] ) .AND. aDefColor[ 1 ] <= Len( aColorSpec ), aDefColor[ TBC_CLR_STANDARD ], 1 ) ]
      ENDSWITCH
   ENDIF

   RETURN aColorSpec[ iif( ISNUMBER( aDefColor[ nMode ] ) .AND. aDefColor[ nMode ] <= Len( aColorSpec ), aDefColor[ nMode ], { 1, 2, 1, 1 }[ nMode ] ) ]

STATIC FUNCTION tbr_CalcWidth( xValue, cType, cPicture )

   IF !ISCHARACTER( cPicture )
      cPicture := ""
   ENDIF

   SWITCH cType
   CASE "M"
   CASE "C" ; RETURN Len( iif( Empty( cPicture ), xValue        , Transform( xValue, cPicture ) ) )
   CASE "N" ; RETURN Len( iif( Empty( cPicture ), Str( xValue ) , Transform( xValue, cPicture ) ) )
   CASE "D" ; RETURN Len( iif( Empty( cPicture ), DToC( xValue ), Transform( xValue, cPicture ) ) )
   CASE "L" ; RETURN 1
   ENDSWITCH

   RETURN 0

/* -------------------------------------------- */

FUNCTION TBMouse( oBrowse, nMRow, nMCol )

   LOCAL n

   IF oBrowse:hitTest( nMRow, nMCol ) == HTCELL

      n := oBrowse:mRowPos - oBrowse:rowPos
      DO WHILE n < 0
         n++
         oBrowse:up()
      ENDDO
      DO WHILE n > 0
         n--
         oBrowse:down()
      ENDDO

      n := oBrowse:mColPos - oBrowse:colPos
      DO WHILE n < 0
         n++
         oBrowse:left()
      ENDDO
      DO WHILE n > 0
         n--
         oBrowse:right()
      ENDDO

      RETURN TBR_CONTINUE
   ENDIF

   RETURN TBR_EXCEPTION
