/*
 * Xailer source code:
 *
 * Browse.prg
 * Clase TBrowse()
 *
 * Copyright 2003, 2020 Ignacio Ortiz de Zúñiga
 * Copyright 2020 Xailer.com
 * All rights reserved
 *
 */

#include "Xailer.ch"
#include "CommCtrl.api"
#include "Inkey.ch"
#include "Dbstruct.ch"

#define ROW_EXTRAHEIGHT       6
#define ROW_MINHEIGHT        18

#define SEL_DELETEALL         0
#define SEL_ADDCURRENTKEY     1
#define SEL_SWAPCURRENTKEY    2
#define SEL_SETRANGE          3
#define SEL_SELECTALL         4

//--------------------------------------------------------------------------

CLASS XBrowse FROM TWinControl

PUBLISHED:
   PROPERTY nWidth      INIT 150
   PROPERTY nHeight     INIT 120

   PROPERTY aCols       INIT {} AS TBrwColumn[] EDITOR PE_StringList
   PROPERTY oImageList  WRITE METHOD SetImageList AS TImageList  EDITOR PE_ImageList
   PROPERTY oRecSel     AS TBrwRecSel EDITOR PE_BrwRecSel
   PROPERTY oSeek       EDITOR PE_Control AS TControl

   PROPERTY nBorderStyle INIT bvETCHED
   PROPERTY nBorderSides

   PROPERTY nRowDividerStyle INIT blNOLINES VALUES blNOLINES, blBLACK, blDARKGRAY, blFORECOLOR, blLIGHTGRAY, blINSET, blRAISED ;
									  WRITE METHOD SetRowDividerStyle
   PROPERTY nColDividerStyle INIT blNOLINES VALUES blNOLINES, blBLACK, blDARKGRAY, blFORECOLOR, blLIGHTGRAY, blINSET, blRAISED ;
									  WRITE METHOD SetColDividerStyle
   PROPERTY nMarqueeStyle    INIT bmSOLIDCELL VALUES bmNOMARQUEE, bmDOTEDCELL, bmSOLIDCELL, bmHIGHLCELL, bmHIGHLROWRC, bmHIGHLROW, bmHIGHLROWMS
   PROPERTY nDataLines       INIT 1 WRITE METHOD SetDataLines
   PROPERTY nFreeze          INIT 0
   PROPERTY nHeaderHeight    INIT 24 WRITE METHOD SetHeaderHeight EDITOR PE_Scale
   PROPERTY nFooterHeight    INIT 24 WRITE METHOD SetFooterHeight EDITOR PE_Scale
   PROPERTY nRowHeight       INIT 0  WRITE METHOD SetRowHeight    EDITOR PE_Scale
   PROPERTY nClrText         INIT clWindowText          EDITOR PE_Color WRITE METHOD SetClrText
   PROPERTY nClrPane         INIT clWindow              EDITOR PE_Color WRITE METHOD SetClrPane
   PROPERTY nClrAltPane      INIT clWindow              EDITOR PE_Color WRITE METHOD SetClrAltPane
   PROPERTY nClrSelText      INIT clBtnText             EDITOR PE_Color WRITE METHOD SetClrSelText
   PROPERTY nClrSelPane      INIT clBtnFace             EDITOR PE_Color WRITE METHOD SetClrSelPane
   PROPERTY nClrSelFocusText INIT clHighLightText       EDITOR PE_Color WRITE METHOD SetClrSelFocusText
   PROPERTY nClrSelFocusPane INIT clHighLight           EDITOR PE_Color WRITE METHOD SetClrSelFocusPane

   PROPERTY lHeader             INIT .T. WRITE METHOD SetHeader
   PROPERTY lFooter             INIT .F. WRITE METHOD SetFooter
   PROPERTY lAllowEdit          INIT .T.
   PROPERTY lAllowAppend        INIT .F.
   PROPERTY lAllowInsert        INIT .F.
   PROPERTY lAllowDelete        INIT .F.
   PROPERTY lAllowColSwapping   INIT .T.
   PROPERTY lAllowColSizing     INIT .T.
   PROPERTY lAllowColHiding     INIT .T.
   PROPERTY lAllowRowSizing     INIT .T.
   PROPERTY lColDividerComplete INIT .T.
   PROPERTY lRecordSelector     INIT .T. WRITE METHOD SetRecSel
   PROPERTY lFastEdit           INIT .F.
   PROPERTY lTabStop            INIT .T.
   PROPERTY lFilterBar          INIT .F. WRITE METHOD FilterMode
   PROPERTY cMsgNoFilter        INIT ""
   PROPERTY lAutoSave           INIT .F.

   PROPERTY lVScroll     INIT bsAUTO ;
                         WRITE INLINE ( ::FlVScroll := Value, IIf( Value != bsAUTO, ::SetVScroll( Value ), ) ) ;
								 VALUES bsAUTO, bsYES, bsNO

   PROPERTY lHScroll     INIT bsAUTO ;
                         WRITE INLINE ( ::FlHScroll := Value, IIf( Value != bsAUTO, ::SetHScroll( Value ), ) ) ;
								 VALUES bsAUTO, bsYES, bsNO

PUBLIC:
   PROPERTY cText
   PROPERTY lRedraw      INIT .T. WRITE METHOD SetRedraw
   PROPERTY nEditMode    INIT beREAD VALUES beREAD, beEDIT, beAPPEND, beINSERT READONLY
   DATA oHeader          AS THeader READONLY
   DATA oFooter          AS THeader READONLY
   DATA aSelected        INIT {}
   DATA nKeyNo           INIT 0 READONLY
   DATA nLen             INIT 0 READONLY
   DATA nRowSel, nColSel //READONLY
   DATA nDataRows        READONLY
   ACCESS lEditMode()    INLINE ( ::nEditMode != beREAD )

RESERVED:
   DATA nIntRowHeight    INIT 0
   DATA nPhantomRow      INIT 0
   DATA lAutoOrder       INIT .F.

PROTECTED:
   DATA nCtlStyle        INIT nOr( WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_TABSTOP, WS_VSCROLL, WS_HSCROLL )
   DATA nExStyle         INIT 0
   DATA nClickedCol      INIT 0
	DATA lOnPostEdit      INIT .F.

   PROPERTY lBorder      INIT .F.
   PROPERTY l3D          INIT .F.
   PROPERTY oBkGnd
   PROPERTY nBkgndMode
   PROPERTY nBkgndMarginX
   PROPERTY nBkgndMarginY
   PROPERTY nGradient
   PROPERTY nClrPaneEnd

   DATA hBtnShadowPen
   DATA hColPen
   DATA hRowPen
   DATA hClrSelText
   DATA hClrSelPane
   DATA hClrAltPane
   DATA hClrSelFocusText
   DATA hClrSelFocusPane
   DATA lCaptured        INIT .F.
   DATA lMultiEdit       INIT .F.
   DATA nFilterHeight

PUBLISHED:
   EVENT OnChange( oSender, lBookMarkChanged ) // --> Nil
   EVENT OnPastEof( oSender ) // --> Nil
   EVENT OnGoTop( oSender ) // --> Nil
   EVENT OnGoBottom( oSender ) // --> Nil
   EVENT OnFilterChange( oSender, nColumn ) // --> Nil
   EVENT OnDrawRow( oSender, nRowPos ) // --> Nil
   EVENT OnPostEdit( oSender, lCanceled, nKey ) // --> Nil
   EVENT OnChangeHeight( oSender, nNewHeight, nOldHeight ) // --> Nil
   EVENT OnNewItem( oSender, oItem ) // --> Nil
   EVENT OnEdit( oSender ) // --> Nil
   EVENT OnAppend( oSender ) // --> Nil
   EVENT OnInsert( oSender ) // --> Nil
   EVENT OnDelete( oSender ) // --> Nil
   EVENT OnClick( oSender, nKeys, nCol, nRow ) // --> Nil
   EVENT OnDblClick( oSender, nKeys, nCol, nRow ) // --> Nil
   EVENT OnRClick( oSender, nKeys, nCol, nRow ) // --> Nil

   // debe retornar filas desplazadas
   EVENT OnSkip( oSender, nSkip )    // --> nSkipped

   // debe retornar .T. si Bof
   EVENT OnBof( oSender )            // --> lResult

   // debe retornar .T. si Eof
   EVENT OnEof( oSender )            // --> lResult

   // Si nPos = Nil debe retornar actual posicion lógica (según orden), sino debe saltar a posición nPos
   EVENT OnKeyNo( oSender, nPos )    // --> Nil | nPosition

   // Debe retornar número de filas
   EVENT OnKeyCount( oSender )       // --> nCount

   // Si nPos = Nil debe retornar una referencia a la posición actual, sino debe salrtar a dicha referencia nPos
   EVENT OnBookMark( oSender, nPos ) // --> nId

   // Debe retornar .T. si encontrado
   EVENT OnSeek( oSender, cSeek )    // --> lResult

   // Cambio de seleccion cuando es multi-select

   EVENT OnChangeSelection( oSender, aSelected ) // -> Nil

PUBLIC:
   METHOD New( oParent ) CONSTRUCTOR
   METHOD Create( oParent ) CONSTRUCTOR
   METHOD Free()

   METHOD AddCol( oCol, lRefresh ) // --> oColumn
   METHOD InsCol( nIndex, oCol, lRefresh ) // --> oColumn
   METHOD DelCol( nIndex, lRefresh ) // --> lSuccess
   METHOD SwapCols( xCol1, xCol2, lRefresh ) // --> Nil
   METHOD MoveCol( xOrg, nDes, lRefresh ) // --> Nil

   METHOD Refresh( lComplete ) // --> lSuccess
   METHOD RefreshCurrent()  INLINE ::DrawLine( Nil, .T.) // --> Nil
   METHOD Refill()          INLINE ( ::GetDisplayCols(), ::Refresh( .T. ) ) // --> lSuccess

   METHOD GoUp() // --> nMoved
   METHOD GoDown( lKeyBoard ) // --> Nil
   METHOD GoPageUp( nLines ) // --> Nil
   METHOD GoPageDown( nLines ) // --> Nil
   METHOD GoTop() // --> Nil
   METHOD GoBottom() // --> Nil
   METHOD GoTo( nPos ) // --> Nil

   METHOD GoLeft( lOffset, lRefresh ) // --> Nil
   METHOD GoRight( lOffset, lRefresh ) // --> Nil
   METHOD GoLeftMost() // --> Nil
   METHOD GoRightMost() // --> Nil
   METHOD GoHPos( nPos ) // --> Nil

   METHOD Select( nOperation ) // --> Nil
                               // 0 -> Delete all
                               // 1 -> Add current key
                               // 2 -> Swap current key (Ctrl+lClick)
                               // 3 -> Tipical Shift with mouse
                               // 4 -> Select all
   METHOD SelectAll()  INLINE ::Select( SEL_SELECTALL ) // --> Nil
   METHOD SelectNone() INLINE ::Select( SEL_DELETEALL ) // --> Nil

   METHOD Seek( cSeek ) // --> Nil
   METHOD CancelEdit( lAbort, nKey ) // --> Nil
   METHOD SaveState() // --> cString
   METHOD RestoreState( cState ) // --> Nil
   METHOD Edit( lFromUser ) // --> Nil
   METHOD Append( lFromUser ) // --> lSuccess
   METHOD Insert( lFromUser ) // --> lSuccess
   METHOD Delete( lFromUser ) INLINE .F. // --> lSuccess
   METHOD Reset() // --> Nil

   METHOD ColWithHeader( cHeader ) // --> oColumn
   METHOD SelectCol( nCol ) // --> Nil
   METHOD ColAtIndex( nIndex ) // --> oColumn
   METHOD ColAtPos( nPos ) INLINE ::aCols[ ::aDisplay[ nPos ] ] // --> oColumn
   METHOD SelectedCol()    INLINE ::ColAtPos( ::nColSel ) // --> oColumn
   METHOD ColPos( oCol ) // --> nPosition
   METHOD SelectCellFromPoint( nPosX, nPosY ) // --> lResult
   METHOD Adjust( lForced ) // --> Nil
   METHOD AddImage( cImage, lMasked ) // --> nImage
   METHOD IsMultipleEdit() INLINE ::lMultiEdit // --> lResult
   METHOD IsEditable() // --> lResult
   METHOD IsEof()          INLINE .T. // --> .T.
   METHOD IsBof()          INLINE .T. // --> .T.
   METHOD BookMark( n )    INLINE  1 // --> nId
   METHOD KeyCount()       INLINE ( ::nLen   := IIf( ::EventAssigned( "OnKeyCount" ), ::OnKeyCount(), 0 ) ) // --> nCount
   METHOD KeyNo()          INLINE ( ::nKeyNo := IIf( ::EventAssigned( "OnKeyNo" ), ::OnKeyNo(), 1 ) ) // --> nPosition
   METHOD MouseRowPos( nRow ) // --> nPosition
   METHOD MouseColPos( nCol ) // --> nPosition
   METHOD AdjustCols()
   METHOD StretchCols()
   METHOD ToExcel()
   METHOD ClearAllFilters() INLINE ::oHeader:ClearAllFilters(), ::SetFilter( 0 )

RESERVED:
   METHOD MoveTop()        VIRTUAL
   METHOD MoveBottom()     VIRTUAL
   METHOD Skip()           VIRTUAL
   METHOD SetFilter( n )   VIRTUAL

   METHOD SetClrText( Value )
   METHOD SetClrPane( Value )
   METHOD SetClrSelText( Value )
   METHOD SetClrSelPane( Value )
   METHOD SetClrAltPane( Value )
   METHOD SetClrSelFocusText( Value )
   METHOD SetClrSelFocusPane( Value )
   METHOD SetHeader( Value )
   METHOD SetFooter( Value )
   METHOD SetDataLines( Value )
   METHOD CreateHeader()
   METHOD CreateFooter()
   METHOD SetRecSel( lRecSel, lRefresh )
   METHOD SetRowDividerStyle( nStyle )
   METHOD SetColDividerStyle( nStyle )
   METHOD AdjustColWidth( oSender, nCol ) VIRTUAL
   METHOD SetRedraw( Value )
   METHOD Click( nKeys, nX, nY )
   METHOD XAClick( nKeys, nX, nY )  INLINE ::Click( nKeys, nX, nY )
   METHOD OrderOff()                INLINE AEval( ::aCols, {|v| v:nSort := 0 })
   METHOD GetFreeze()
   METHOD RowEdit( lFromUser )
   METHOD RecCount() INLINE ::nDataRows

//(TBrwCol friend methods)

   DATA aDisplay     INIT {}
   DATA cSeek        INIT ""
   DATA nColOffset
   DATA nLastEditCol
   DATA hWhitePen

   METHOD ColDrag( oHeader, nCol, nOrder )
   METHOD ColTrack( oHeader, nCol, nWidth )
   METHOD ColBegTrack( oHeader, nCol, nWidth )
   METHOD WMEraseBkgnd() INLINE 1
   METHOD WMPaint()
   METHOD PaintVerticalLines( hDC )
   METHOD PaintHorizontalLines( hDC )
   METHOD PaintRecordSelector( hDC )
   METHOD DrawLine( hDC, lSelected, nRowSel )
   METHOD EraseData( nRow, hDC )
   METHOD WMSize( nWParam, nLParam )
   METHOD HasFocus()
   METHOD WMSetFocus( hCtl )  INLINE ( ::Super:WMSetFocus( hCtl ),;
                                          IIf( GetParent( hCtl ) != ::Handle .AND. ;
                                               IsWindowVisible( ::Handle ), ::RefreshCurrent(), ) )
   METHOD WMKillFocus( hCtl ) INLINE ( ::Super:WMKillFocus( hCtl ),;
                                          IIf( GetParent( hCtl ) != ::Handle .AND. ;
                                               IsWindowVisible( ::Handle ), ::RefreshCurrent(), ) )
   METHOD SetImageList( oImageList )


   METHOD SetVScroll( lVScroll )
   METHOD SetHScroll( lHScroll )
   METHOD GetVScroll()          INLINE lAnd( ::nCtlStyle, WS_VSCROLL )
   METHOD GetHScroll()          INLINE lAnd( ::nCtlStyle, WS_HSCROLL )
   METHOD SetVRange( min, max ) INLINE SetScrollRange( ::Handle, SB_VERT, Min, Max, ::lVScroll != bsAUTO, .T. ), SetScrollPage( ::Handle, SB_VERT, 1, ::lVScroll != bsAUTO, .T. )
   METHOD SetHRange( min, max ) INLINE SetScrollRange( ::Handle, SB_HORZ, Min, Max, ::lHScroll != bsAUTO, .T. ), SetScrollPage( ::Handle, SB_HORZ, 1, ::lHScroll != bsAUTO, .T. )
   METHOD GetVRange()           INLINE GetScrollRange( ::Handle, SB_VERT )
   METHOD GetHRange()           INLINE GetScrollRange( ::Handle, SB_HORZ )
   METHOD SetVPos( nPos )       INLINE ( IIf( nPos == bsAUTO, nPos := 1, ), SetScrollPos( ::Handle, SB_VERT, nPos, ::lVScroll != bsAUTO, .T. ) )
   METHOD SetHPos( nPos )       INLINE ( IIf( nPos == bsAUTO, nPos := 1, ), SetScrollPos( ::Handle, SB_HORZ, nPos, ::lHScroll != bsAUTO, .T. ) )
   METHOD GetVPos()             INLINE GetScrollPos( ::Handle, SB_VERT )
   METHOD GetHPos()             INLINE GetScrollPos( ::Handle, SB_HORZ )
   METHOD SetVPage( nPage )     INLINE SetScrollPage( ::Handle, SB_VERT, nPage, ::lVScroll != bsAUTO, .T. )
   METHOD SetHPage( nPage )     INLINE SetScrollPage( ::Handle, SB_HORZ, nPage, ::lHScroll != bsAUTO, .T. )
   METHOD WMVScroll()
   METHOD WMHScroll()
   METHOD WMLButtonDown( nWParam, nLParam, hWnd, nFlags, lEvent )
   METHOD WMLButtonUp( nWParam, nLParam )
   METHOD WMMouseMove( nWParam, nLParam )
   METHOD WMMouseWheel( nWParam, nLParam )
   METHOD WMLButtonDblClk( nWParam, nLParam )
   METHOD WMRButtonDown( nWParam, nLParam )
   METHOD WMRButtonUp( nWParam, nLParam )
   METHOD HorzLine( nRow, nOperation, nLine )

   METHOD WMKeyDown( nKey, nFlags, hWnd )
   METHOD WMChar( nKey, nFlags, hWnd )

   METHOD GetDisplayCols()
   METHOD UpdateDisplayCols()
   METHOD GetVisibleCols()
   METHOD GetDisplayColsWidth( aSizes )
   METHOD LastDisplayPos( lComplete )
   METHOD IsDisplayPosVisible( nPos, lComplete )
   METHOD IsColVisible( oCol, lComplete ) INLINE ::IsDisplayPosVisible( oCol:nPos, lComplete )
   METHOD AllColsVisible()
   METHOD LastColVisible()
   METHOD DataWidth()    INLINE ::BrwWidth()
   METHOD DataHeight()   INLINE ::BrwHeight() - ::HeaderHeight() - ::FooterHeight()
   METHOD BrwHeight()    INLINE ::nClientHeight
   METHOD BrwWidth()     INLINE ::nClientWidth
   METHOD HeaderHeight() INLINE IIf( ::lHeader, ::nHeaderHeight, 0 )
   METHOD FooterHeight() INLINE IIf( ::lFooter, ::nFooterHeight, 0 )
   METHOD RowCount()     INLINE Int( ::DataHeight() / Max( ::nIntRowHeight, 1 ) )
   METHOD FirstDataRow() INLINE ::HeaderHeight()
   METHOD LastDataRow()  INLINE ::BrwHeight() - ::FooterHeight()
   METHOD LastOrdRow()   INLINE ::LastDataRow() - ::nIntRowHeight + 1
   METHOD FirstCol()     INLINE IIf( ::lRecordSelector .and. ::oRecSel != Nil, ::oRecSel:nWidth, 0 )
   METHOD LastCol()      INLINE ::nClientWidth
   METHOD RowHeight()    INLINE ::nIntRowHeight - IIf( ::nRowDividerStyle > blNOLINES, 1, 0 ) - ;
                                IIf( ::nRowDividerStyle >= blINSET, 1, 0 )
   METHOD FilterMode( lValue )
   METHOD FilterEdit( oSender, nHeaderItem )

   METHOD UpdControls()
   METHOD SetHeaderHeight( Value )
   METHOD SetFooterHeight( Value )
   METHOD SetRowHeight( Value )
   METHOD DataScroll( nDirection )
   METHOD ChangeFocus( oFrom, lBack )
   METHOD UpdGridEdits()

ENDCLASS

//--------------------------------------------------------------------------

METHOD New( oParent ) CLASS XBrowse

   ::Super:New( oParent )

   ::nRowSel     := 1
   ::nColSel     := 1
   ::nColOffset  := 1
   ::nDataRows   := 0

   ::FoImageList := TImageList():Create( Self )

RETURN Self

//--------------------------------------------------------------------------

METHOD Create( oParent ) CLASS XBrowse

   LOCAL oCol, oHeadCol
   LOCAL nFor

   UPDATE ::oParent TO oParent

   ::SetRecSel( ::FlRecordSelector, .F. )

   ::Super:Create( oParent )

   IF ::lVScroll != bsAUTO
      ::SetVScroll( ::lVScroll )
   ENDIF

   IF ::lHScroll != bsAUTO
      ::SetHScroll( ::lHScroll )
   ENDIF

   IF ! Empty( ::oImageList )
      ::SendMsg( HDM_SETIMAGELIST, 0, ::oImageList:Handle )
   ENDIF

   AEval( ::aCols, {|v| v:Create( Self ) } )

   IF ::lHeader
      ::nHeaderHeight := MulDiv( ::nHeaderHeight, Application:nScale, 100 )
      ::CreateHeader()
   ENDIF

   IF ::lFooter
      ::nFooterHeight := MulDiv( ::nFooterHeight, Application:nScale, 100 )
      ::CreateFooter()
   ENDIF

   ::Adjust()
   ::UpdControls()

RETURN Self

//--------------------------------------------------------------------------

METHOD Free() CLASS XBrowse

   ::oHeader := Nil
   ::oFooter := Nil

   IF ! Empty( ::oImageList )
      IF ::oImageList:oParent == Self
         ::oImageList:End()
      ENDIF
      ::FoImageList := Nil
   ENDIF

   IF ::hClrSelText != Nil
      DeleteObject( ::hClrSelText )
      ::hClrSelText := Nil
   ENDIF

   IF ::hClrSelFocusText != Nil
      DeleteObject( ::hClrSelFocusText )
      ::hClrSelFocusText := Nil
   ENDIF

   IF ::hClrSelPane != Nil
      DeleteObject( ::hClrSelPane )
      ::hClrSelPane := Nil
   ENDIF

   IF ::hClrAltPane != Nil
      DeleteObject( ::hClrAltPane )
      ::hClrAltPane := Nil
   ENDIF

   IF ::hClrSelFocusPane != Nil
      DeleteObject( ::hClrSelFocusPane )
      ::hClrSelFocusPane := Nil
   ENDIF

   IF ::hBtnShadowPen != Nil
      DeleteObject( ::hBtnShadowPen )
      ::hBtnShadowPen := Nil
   ENDIF

   IF ::hWhitePen != Nil
      DeleteObject( ::hWhitePen )
      ::hWhitePen := Nil
   ENDIF

   IF ::hColPen != Nil
      DeleteObject( ::hColPen )
      ::hColPen := Nil
   ENDIF

   IF ::hRowPen != Nil
      DeleteObject( ::hRowPen )
      ::hRowPen := Nil
   ENDIF

   IF ::oRecSel != Nil
      ::oRecSel:End()
      ::oRecSel := Nil
   ENDIF

   ::oSeek := NIL

   AEval( ::aCols, {|v| v:End() } )

   ::aCols := {}

RETURN ::Super:Free()

//--------------------------------------------------------------------------

METHOD Adjust( lForced ) CLASS XBrowse

   LOCAL nFor, nLength, nHeight, nStyle

   nLength := Len( ::aCols )
   nHeight := 0

   IF ::lRecordSelector .AND. ::oRecSel != Nil
      ::oRecSel:Adjust( lForced )
   ENDIF

   FOR nFor := 1 TO nLength
      ::aCols[ nFor ]:Adjust( lForced )
   NEXT

   IF Empty( ::nRowHeight )
      nHeight := ::GetFont():GetTextHeight( "B", Self )
      FOR nFor := 1 TO nLength
         nHeight := Max( nHeight, ::aCols[ nFor ]:DataHeight() )
      NEXT
      WITH OBJECT ::oImageList
         IF :GetImageCount() > 0
            nHeight := Max( nHeight, :nHeight )
         ENDIF
      END WITH
      IF ::oRecSel != Nil
         nHeight := Max( ::oRecSel:nHeight, nHeight )
      ENDIF
      ::nIntRowHeight := ( nHeight * ::nDataLines ) + ROW_EXTRAHEIGHT // lines to give 3d look
      ::nIntRowheight := Max( ::nIntRowHeight, ROW_MINHEIGHT )
      IF ::nRowDividerStyle != blNOLINES
         ::nIntRowHeight ++
      ENDIF
      IF ::nRowDividerStyle >= blINSET
         ::nIntRowHeight ++
      ENDIF
   ELSE
      ::nIntRowHeight := Max( ::nRowHeight, ROW_MINHEIGHT )
   ENDIF

   DEFAULT ::hBtnShadowPen TO CreatePen( PS_SOLID, 1, clBtnShadow ),;
           ::hWhitePen     TO CreatePen( PS_SOLID, 1, clBtnHighlight )

   IF ::hColPen != Nil
      DeleteObject( ::hColPen )
      ::hColPen := Nil
   ENDIF

   nStyle := ::nColDividerStyle

   DO CASE
      CASE nStyle == blBLACK
         ::hColPen := CreatePen( PS_SOLID, 1, clBlack )
      CASE nStyle == blDARKGRAY
         ::hColPen := CreatePen( PS_SOLID, 1, clGray )
      CASE nStyle == blFORECOLOR
         ::hColPen := CreatePen( PS_SOLID, 1, ::nClrText )
      CASE nStyle == blLIGHTGRAY
         ::hColPen := CreatePen( PS_SOLID, 1, clLtGray )
      CASE nStyle == blRAISED .or. nStyle == blINSET
         ::hColPen := CreatePen( PS_SOLID, 1, RGB( 199, 197, 198 ) )
   END CASE

   IF ::hRowPen != Nil
      DeleteObject( ::hRowPen )
      ::hRowPen := Nil
   ENDIF

   nStyle := ::nRowDividerStyle

   DO CASE
      CASE nStyle == blBLACK
         ::hRowPen := CreatePen( PS_SOLID, 1, clBlack )
      CASE nStyle == blDARKGRAY
         ::hRowPen := CreatePen( PS_SOLID, 1, clGray )
      CASE nStyle == blFORECOLOR
         ::hRowPen := CreatePen( PS_SOLID, 1, ::nClrText )
      CASE nStyle == blLIGHTGRAY
         ::hRowPen := CreatePen( PS_SOLID, 1, clLtGray )
      CASE nStyle == blRAISED .or. nStyle == blINSET
         ::hRowPen := CreatePen( PS_SOLID, 1, RGB( 199, 197, 198 ) )
   END CASE

   IF ::hClrSelText == Nil
      ::hClrSelText := CreateSolidBrush( ::FnClrSelText )
   ENDIF

   IF ::hClrSelFocusText == Nil
      ::hClrSelFocusText := CreateSolidBrush( ::FnClrSelFocusText )
   ENDIF

   IF ::hClrSelPane == Nil
      ::hClrSelPane := CreateSolidBrush( ::FnClrSelPane )
   ENDIF

   IF ::hClrAltPane == Nil
      ::hClrAltPane := CreateSolidBrush( ::FnClrAltPane )
   ENDIF

   IF ::hClrSelFocusPane == Nil
      ::hClrSelFocusPane := CreateSolidBrush( ::FnClrSelFocusPane )
   ENDIF

   ::GetDisplayCols()

   ::KeyCount()

RETURN Nil

//--------------------------------------------------------------------------

METHOD EraseData( nRow, hDC ) CLASS XBrowse

   LOCAL oCol
   LOCAL nLast, nFor, nHeight, nCol
   LOCAL lCreateDC

   IF Empty( hDC )
      lCreateDC := .T.
      hDC       := GetDC( ::Handle )
   ELSE
      lCreateDC := .F.
   ENDIF

   nHeight := ::BrwHeight() - ::FooterHeight() - nRow

   IF Len( ::aDisplay ) == 0
      FillRect( hDC, { ::FirstCol(), nRow, ::LastCol(), nRow + nHeight }, ::oBrush:Handle )
   ELSE
      nLast := ::LastDisplayPos()

      FOR nFor := 1 TO nLast
         oCol := ::ColAtPos( nFor )
         oCol:EraseData( hDC, nRow, nHeight, .T., nFor )
      NEXT

      IF nLast == Len( ::aDisplay )
         nCol := oCol:nDisplayCol + oCol:nWidth
         IF ::nColDividerStyle < blINSET
            nCol --
         ENDIF
         IF nCol < ::LastCol()
            FillRect( hDC, { nCol, nRow, ::LastCol(), nRow + nHeight }, ::oBrush:Handle )
         ENDIF
      ENDIF
   ENDIF

   IF ::lRecordSelector
      ::oRecSel:Erase( hDC, nRow, nHeight, .T. )
   ENDIF

   IF lCreateDC
      ReleaseDC( ::Handle, hDC )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD Refresh( lComplete )  CLASS XBrowse

   LOCAL nLines, nAfter

   DEFAULT lComplete TO .F.

   ::KeyNo()
   ::KeyCount()

   IF ::IsEof()
      ::nKeyNo := ::nLen
   ENDIF

   IF lComplete
      ::nRowSel  := 1
   ELSE
      nLines  := ::RowCount()
      nAfter  := ::nLen - ::nKeyNo
      IF ::nKeyNo <= nLines
         ::nRowSel := Min( ::nKeyNo, ::nRowSel )
      ELSEIF nAfter < nLines
         ::nRowSel := nLines - nAfter
      ENDIF
      ::nRowSel := Min( ::nRowSel, ::nLen )
   ENDIF

   ::UpdControls()

   IF ! IsWindowVisible( ::Handle )
      RETURN Nil
   ENDIF

RETURN ::Super:Refresh( lComplete )

//--------------------------------------------------------------------------

METHOD AllColsVisible() CLASS XBrowse

   LOCAL nFor, nCol

   nCol := 0

	// No hay columnas

	IF Len( ::aCols ) == 0 .OR. Len( ::aDisplay ) == 0
      RETURN .T.
   ENDIF

   // Comprobamos que la primera es visible por completo

   nCol := Ascan( ::aCols, {|v| v:IsVisible() } )

   IF nCol == 0
      RETURN .T.
   ENDIF

   WITH OBJECT ::aCols[ nCol ]
      IF :nPos == 0 .OR. !::IsDisplayPosVisible( :nPos, .T. )
         RETURN .F.
      ENDIF
   END WITH

	// Comprobamos que no hay columnas ocultas por Freeze

	IF ::nFreeze > 0 .AND. Len( ::aCols ) > ::nFreeze .AND. ::aCols[ ::nFreeze + 1 ]:nPos == 0
		 RETURN .F.
	ENDIF

   // Comprobamos que la última es visible por completo

   FOR nFor := Len( ::aCols ) TO 1 step -1
      IF ::aCols[ nFor ]:IsVisible()
         nCol := nFor
         EXIT
      ENDIF
   NEXT

   WITH OBJECT ::aCols[ nCol ]
      IF Empty( :nPos ) .OR. ! ::IsDisplayPosVisible( :nPos, .T. )
         RETURN .F.
      ENDIF
   END WITH

RETURN .T.

//--------------------------------------------------------------------------

METHOD LastColVisible()  CLASS XBrowse

   LOCAL nFor, nCol

   FOR nFor := Len( ::aCols ) TO 1 step -1
      IF ::aCols[ nFor ]:IsVisible()
         nCol := nFor
         EXIT
      ENDIF
   NEXT

   IF Empty( nCol )
      RETURN .F.
   ENDIF

   WITH OBJECT ::aCols[ nCol ]
      IF Empty( :nPos ) .OR. ! ::IsDisplayPosVisible( :nPos, .T. )
         RETURN .F.
      ENDIF
   END WITH

RETURN .T.

//--------------------------------------------------------------------------

METHOD WMSize( nWParam, nLParam ) CLASS XBrowse

   LOCAL nType, nWidth, nHeight

   nType   := nwParam
   nWidth  := LoWord( nLParam )

   ::Super:WMSize( nWParam, nLParam )

   IF ! Empty( ::nIntRowHeight )
      SWITCH nType
         CASE SIZE_MAXIMIZED
            ::GetDisplayCols()
            EXIT
         CASE SIZE_RESTORED
            IF nWidth > ::GetDisplayColsWidth() .AND. ! ::LastColVisible()
               ::GetDisplayCols()
            ENDIF
            EXIT
      END
     ::UpdControls()
     ::UpdGridEdits()
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD UpdControls() CLASS XBrowse

   LOCAL nTop, nLeft, nWidth, nHeight, nKeyNo

   IF ::lHScroll == bsAUTO
      ::SetHScroll( !::AllColsVisible() )
   ELSE
      ::SetHScroll( ::lHScroll )
   ENDIF

   nKeyNo := ::nKeyNo

   IF nKeyNo == Nil
      nKeyNo := 1
   ENDIF

   IF ::lVScroll != bsNO
      SetScrollInfo( ::Handle, SB_VERT, 1, ::nLen, 1, nKeyNo, ::lVScroll == bsYES, .T. )
   ENDIF

   IF ::oHeader != Nil .AND. ! Empty( ::oHeader:Handle )
      nTop    := 0
      nLeft   := 0
      nWidth  := ::DataWidth()
      nHeight := ::nHeaderHeight
      ::oHeader:SetBounds( nLeft, nTop, nWidth, nHeight )
   ENDIF

   IF ::oFooter != Nil .AND. ! Empty( ::oFooter:Handle )
      nTop    := ::nClientHeight - ::nFooterHeight
      nLeft   := 0
      nWidth  := ::DataWidth()
      nHeight := ::nFooterHeight
      ::oFooter:SetBounds( nLeft, nTop, nWidth, nHeight )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD HasFocus() CLASS XBrowse

   LOCAL nHandle

   nHandle := GetFocus()

RETURN ( nHandle == ::Handle .OR. GetParent( nHandle ) == ::Handle )

//--------------------------------------------------------------------------

METHOD SetImageList( oValue ) CLASS XBrowse

   IF ::FoImageList != Nil .AND. ::FoImageList:oParent == Self
      ::FoImageList:Destroy()
   ENDIF

   ::FoImageList := oValue

   IF ::oHeader != Nil
      ::oHeader:SetImageList( oValue )
   ENDIF

   IF ::oFooter != Nil
      ::oFooter:SetImageList( oValue )
   ENDIF

RETURN oValue

//--------------------------------------------------------------------------

METHOD AddImage( cImage, lMasked )

   IF Empty( ::oImageList )
      ::oImageList := TImageList():Create( Self )
   ENDIF

RETURN ::oImageList:Add( cImage, lMasked )

//--------------------------------------------------------------------------

METHOD AddCol( oCol, lRefresh ) CLASS XBrowse

   LOCAL oItem

   DEFAULT lRefresh TO .F.

   IF oCol == Nil
      oCol := TBrwColumn():New( Self )
   ENDIF

   IF ! Empty( oCol:nIndex )
      RETURN oCol
   ENDIF

   oCol:nIndex := Len( ::aCols ) + 1

   IF ! oCol:lCreated
      oCol:Create()
   ENDIF

   AAdd( ::aCols, oCol )

   ::OnNewItem( oCol )

   IF ! Empty( ::Handle )
      oCol:Adjust()
      IF lRefresh
         ::GetDisplayCols()
         ::Refresh( .T. )
      ENDIF
   ENDIF

RETURN oCol

//--------------------------------------------------------------------------

METHOD InsCol( nIndex, oCol, lRefresh ) CLASS XBrowse

   LOCAL nFor

   DEFAULT lRefresh TO .T.

   IF nIndex <= 0
      RETURN Nil
   ENDIF

   IF nIndex > Len( ::aCols )
      RETURN ::AddCol( oCol )
   ENDIF

   IF oCol == Nil
      oCol := TBrwColumn():New( Self )
   ENDIF

   oCol:nIndex := nIndex

   IF ! oCol:lCreated
      oCol:Create()
   ENDIF

   AAdd( ::aCols, Nil )
   AIns( ::aCols, nIndex )
   ::aCols[ nIndex ] := oCol

   ::OnNewItem( oCol )

   FOR nFor := nIndex + 1 TO len( ::aCols )
      IF ! Empty( ::aCols[ nFor ]:nIndex )
         ::aCols[ nFor ]:nIndex++
      ENDIF
   NEXT

   IF ! Empty( ::Handle )
      oCol:Adjust()
      IF lRefresh
         ::GetDisplayCols()
         ::Refresh( .T. )
      ENDIF
   ENDIF

RETURN oCol

//--------------------------------------------------------------------------

METHOD DelCol( xCol, lRefresh ) CLASS XBrowse

   LOCAL nFor, nIndex

   DEFAULT lRefresh TO .T.

   IF Empty( xCol )
      RETURN .F.
   ENDIF

   IF Valtype( xCol ) == "O"
      nIndex := Ascan( ::aCols, {|v| v:nIndex == xCol:nIndex } )
   ELSE
      nIndex := xCol
   ENDIF

   IF nIndex <= 0 .OR. nIndex > Len( ::aCols )
      RETURN .F.
   ENDIF

//   IF Len( ::aCols ) <= 1 .AND. ! Empty( ::Handle )
//      MsgBeep()
//      RETURN .F.
//   ENDIF

   ADel( ::aCols, nIndex )
   ASize( ::aCols, Len( ::aCols ) - 1 )

   FOR nFor := nIndex TO len( ::aCols )
      ::aCols[ nFor ]:nIndex--
   NEXT

   IF ! Empty( ::Handle ) .AND. lRefresh
      ::GetDisplayCols()
      ::Refresh( .T. )
   ENDIF

RETURN .T.

//--------------------------------------------------------------------------

METHOD GetVisibleCols() CLASS XBrowse

   LOCAL oCol
   LOCAL aVisible
   LOCAL nLen, nCol

   aVisible := {}
   nLen     := Len( ::aCols )

   FOR nCol := 1 TO nLen
      oCol := ::aCols[ nCol ]
      IF oCol:IsVisible()
         Aadd( aVisible, oCol )
      ENDIF
   NEXT

RETURN aVisible

//--------------------------------------------------------------------------

METHOD GetDisplayColsWidth( aSizes ) CLASS XBrowse

   LOCAL nWidth, nPos, nLen

   aSizes := {}
   nPos   := 1
   nLen   := Len( ::aDisplay )

   IF nLen == 0
      RETURN 0
   ENDIF

   nWidth := ::FirstCol()

   FOR nPos := 1 TO nLen
      nWidth += ::ColAtPos( nPos ):nWidth
      AAdd( aSizes, ::ColAtPos( nPos ):nWidth )
   NEXT

RETURN nWidth

//--------------------------------------------------------------------------

METHOD IsDisplayPosVisible( nPos, lComplete ) CLASS XBrowse

   LOCAL nWidth, nFor

   DEFAULT lComplete TO .F.

   IF nPos < 1 .OR. nPos > Len( ::aDisplay )
      RETURN .F.
   ENDIF

   nWidth := ::FirstCol()

   FOR nFor := 1 TO nPos - 1
      nWidth += ::ColAtPos( nFor ):nWidth
   NEXT

   IF lcomplete
      nWidth += ::ColAtPos( nPos ):nWidth
   ENDIF

RETURN ( nWidth  <= ::DataWidth() )

//--------------------------------------------------------------------------

METHOD LastDisplayPos( lComplete ) CLASS XBrowse

   LOCAL nWidth, nMaxWidth, nPos, nLen

   DEFAULT lComplete TO .F.

   nPos      := 1
   nMaxWidth := ::DataWidth()
   nLen      := Len( ::aDisplay )

   nWidth := ::FirstCol()

   DO WHILE nPos <= nLen .AND. nWidth <= nMaxWidth
      nWidth += ::ColAtPos( nPos++ ):nWidth
   ENDDO

   nPos--

   IF lComplete .AND. nWidth > nMaxWidth
      nPos--
   ENDIF

   nPos := Max( 1, nPos )

RETURN nPos

//--------------------------------------------------------------------------

METHOD SelectCol( nCol ) CLASS XBrowse

   LOCAL nWidth, nPos, nMax, nOff

   IF ::nMarqueeStyle < bmHIGHLROW
      IF ::IsDisplayPosVisible( nCol - ::nColOffset + 1 )
         ::nColSel := nCol - ::nColOffset + 1
         ::RefreshCurrent()
      ELSE
         nWidth := 0
         nMax   := ::DataWidth()
         nPos   := 1
         nOff   := 1
         DO WHILE nPos <= nCol
            nWidth += ::aCols[ nPos++ ]:nWidth
            IF nWidth >= nMax
               nOff ++
               nPos := nOff
               nWidth :=0
            ENDIF
         ENDDO
         nOff := Min( nOff, nCol )
         ::nColOffset := nOff
         ::nColSel    := nPos - nOff
         ::GetDisplayCols()
         ::Super:Refresh( .F. )
      ENDIF
   ELSE
      ::nColOffset := nCol
      ::GetDisplayCols()
      ::Super:Refresh( .F. )
   ENDIF

   ::OnChange( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD ColPos( oCol )  CLASS XBrowse

   LOCAL nAt

   nAt := Ascan( ::aDisplay, {|v| ::ColAtPos( v ):nIndex == oCol:nIndex } )

RETURN nAt

//--------------------------------------------------------------------------

METHOD ColAtIndex( nIndex )  CLASS XBrowse

   LOCAL oCol
   LOCAL nAt

   nAt := Ascan( ::aCols, {|v|  v:nIndex == nIndex } )

   IF nAt > 0
      oCol := ::aCols[ nAt ]
   ENDIF

RETURN oCol

//--------------------------------------------------------------------------

METHOD ColWithHeader( cHeader )  CLASS XBrowse

   LOCAL oCol
   LOCAL nAt

   cHeader := Upper( cHeader )

   IF ( nAt := Ascan( ::aCols, {|v| Upper( v:cHeader ) == cHeader } ) ) > 0
      oCol := ::aCols[ nAt ]
   ENDIF

RETURN oCol

//--------------------------------------------------------------------------

METHOD SwapCols( xCol1, xCol2, lRefresh ) CLASS XBrowse

   LOCAL oCol
   LOCAL nAt1, nAt2, nPos

   DEFAULT lRefresh TO .T.

   IF Valtype( xCol1 ) == "O"
      nAt1 := Ascan( ::aCols, {|v| v:nIndex == xCol1:nIndex } )
      nAt2 := Ascan( ::aCols, {|v| v:nIndex == xCol2:nIndex } )
   ELSE
      nAt1 := xCol1
      nAt2 := xCol2
   ENDIF

   IF nAt1 > 0 .AND. nAt2 > 0 .AND. ::GetFreeze() < Min( nAt1, nAt2 )
      oCol := ::aCols[ nAt1 ]
      nPos := oCol:nPos
      ::aCols[ nAt1 ]:nPos := ::aCols[ nAt2 ]:nPos
      ::aCols[ nAt2 ]:nPos := nPos
      ::aCols[ nAt1 ] := ::aCols[ nAt2 ]
      ::aCols[ nAt2 ] := oCol
      IF lRefresh
         ::GetDisplayCols()
         ::Super:Refresh( .F. )
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD MoveCol( xCol, nDes, lRefresh ) CLASS XBrowse

   LOCAL oCol
   LOCAL nOrg, nScanOrg, nScanDes, nFor

   DEFAULT lRefresh TO .T.

   IF Valtype( xCol ) == "O"
      oCol := xCol
      nOrg := xCol:nPos
   ELSE
      nOrg := xCol
      oCol := ::ColAtPos( xCol )
   ENDIF

   IF nOrg > 0 .AND. nDes > 0 .AND. nOrg <> nDes .AND. ::GetFreeze() < Min( nOrg, nDes )

      // Buscamos en ::aCols la columna original y destino

      nScanOrg := AScan( ::aCols, {|v| v:nIndex == oCol:nIndex } )
      nScanDes := AScan( ::aCols, {|v| v:nPos == nDes } )

      // Eliminamos la original

      ADel( ::aCols, nScanOrg )

      // Insertamos en destino

      AIns( ::aCols, nScanDes )
      ::aCols[ nScanDes ] := oCol

      // Actualizamos las columnas

      IF lRefresh
         ::GetDisplayCols()
         ::Super:Refresh( .F. )
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD ColDrag( oHeader, nCol, nOrder ) CLASS XBrowse

   LOCAL oCol

   ::CancelEdit()

   oHeader:lCancelDrag := .T.

   IF nCol == Nil .OR. nOrder == Nil
      RETURN .F.
   ENDIF

   IF ::lRecordSelector
      nCol--
      nOrder--
   ENDIF

   IF nCol <= 0 .OR. nOrder <= 0
      RETURN .F.
   ENDIF

   oCol := ::ColAtPos( nCol )

   IF ! Empty( oCol )
      ::MoveCol( oCol, nOrder )
   ENDIF

RETURN .T.

//--------------------------------------------------------------------------

METHOD ColTrack( oHeader, nPos, nWidth ) CLASS XBrowse

   LOCAL oCol

   ::CancelEdit()

   IF ::lRecordSelector
      nPos--
   ENDIF

   IF nPos > 0
      oCol := ::ColAtPos( nPos )
      IF ! Empty( oCol )
         oCol:nWidth := nWidth
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD ColBegTrack( oHeader, nPos, nWidth ) CLASS XBrowse

   ::CancelEdit()

   oHeader:lCancelTrack := .F.

   IF ::lRecordSelector .AND. nPos <= 1
      oHeader:lCancelTrack := .T.
      RETURN Nil
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetVScroll( lVScroll ) CLASS XBrowse

   DEFAULT lVScroll TO .T.

   IF lVScroll
      ::nCtlStyle := nOr( ::nCtlStyle, WS_VSCROLL )
   ELSE
      ::nCtlStyle := nExclude( ::nCtlStyle, WS_VSCROLL )
   ENDIF

   IF ! Empty( ::Handle )
      ShowScrollBar( ::Handle, SB_VERT, lVScroll )
   ENDIF

RETURN lVscroll

//--------------------------------------------------------------------------

METHOD SetHScroll( lHScroll ) CLASS XBrowse

   LOCAL oCol, oCol2
   LOCAL aTmp
   LOCAL nFor, nCols, nWidth, nMaxWidth, nVisible, nTotal, nSelect, nFor2, nFreeze

   DEFAULT lHScroll TO .T.

   IF lHScroll
      ::nCtlStyle := nOr( ::nCtlStyle, WS_HSCROLL )
   ELSE
      ::nCtlStyle := nExclude( ::nCtlStyle, WS_HSCROLL )
   ENDIF

   IF ! Empty( ::Handle ) .AND. Len( ::aDisplay ) > 0
      ShowScrollBar( ::Handle, SB_HORZ, lHScroll )
      IF lHScroll
         nCols     := Len( ::aCols )
         nVisible  := 0
         nTotal    := 0
         nSelect   := 0
         nMaxWidth := ::nClientWidth - ::FirstCol()
         nFreeze   :=  ::GetFreeze()

         FOR nFor := 1 TO nCols
      	   oCol := ::aCols[ nFor ]
            IF oCol:IsVisible()
               nWidth := Min( oCol:nWidth,  nMaxWidth - nVisible )
               IF ::IsColVisible( oCol )
                  nVisible += nWidth
               ENDIF
               IF oCol == ::ColAtPos( 1 )
                  nSelect := nTotal
                  IF nFreeze > 0 .AND. nFreeze < nCols
                     FOR nFor2 := nFreeze + 1 TO ::nColOffset + 1
                        oCol2 := ::aCols[ nFor2 ]
                        IF oCol2:IsVisible()
                           nSelect += Min( oCol2:nWidth, nMaxWidth )
                        ENDIF
                     NEXT
                  ENDIF
               ENDIF
               nTotal += Min( oCol:nWidth, nMaxWidth )
            ENDIF
         NEXT

         SetScrollRange( ::Handle, SB_HORZ, 1, nTotal - 1, ::lHScroll != bsAUTO, .T. )
         SetScrollPage( ::Handle, SB_HORZ, nVisible-1 , ::lHScroll != bsAUTO, .T. )
         ::SetHPos( nSelect )
      ENDIF
   ENDIF

RETURN lHScroll

//--------------------------------------------------------------------------

METHOD GoLeft( lOffset, lRefresh )  CLASS XBrowse

   LOCAL oCol, oTar

   IF ::nMarqueeStyle == bmNOMARQUEE .OR. ::nMarqueeStyle >= bmHIGHLROW
      lOffset := .T.
   ENDIF

   DEFAULT lOffset  TO .F., ;
           lRefresh TO .T.

    oCol := ::ColAtPos( ::nColSel )
    oTar := ::ColAtIndex( oCol:nIndex - 1 )

   IF ( ! lOffset .and. oTar != NIL .and. oTar:nPos > 0 .and. ::IsDisplayPosVisible( ::nColSel - 1 ) ) .OR. ;
      ( ::nColOffset == 1 .AND. ::nColSel > 1 )
      ::nColSel--
      IF lRefresh
         ::DrawLine( Nil, .T. )
      ENDIF
   ELSEIF ::nColOffset > 1
      ::nColOffset--
      ::GetDisplayCols()
      IF lRefresh
         ::Super:Refresh( .F. )
      ENDIF
   ENDIF

   IF ::GetHScroll()
      ::SetHScroll( .T. )
   ENDIF

   ::OnChange( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoRight( lOffset, lRefresh ) CLASS XBrowse

   LOCAL oCol
   LOCAL nLen, nVis, nFreeze

   IF ::lHScroll == bsNO .AND. ::nMarqueeStyle >= bmHIGHLROW
      RETURN Nil
   ENDIF

   IF ::nMarqueeStyle == bmNOMARQUEE .OR. ::nMarqueeStyle >= bmHIGHLROW
      lOffset := .T.
   ENDIF

   DEFAULT lOffset  TO .F., ;
           lRefresh TO .T.

   nLen    := Len( ::aDisplay )
   nVis    := 0
   nFreeze := ::GetFreeze()

   AEval( ::aCols, {|v| IIf( v:IsVisible(), nVis++, ) } )

   IF ( ! lOffset .AND. ::nColSel < ::LastDisplayPos( nFreeze == 0 ) .AND. ::nColSel < nLen )
      IF ::nColSel == ::LastDisplayPos( .T. )// ::LastDisplayPos( nFreeze == 0 )
         IF ! ::LastColVisible()
            //::nColOffset := Min( ::nColOffset + 1, nVis - nFreeze )
            IF ::nColOffset >= ( nVis - nFreeze )
               ::nColSel ++
            ELSE
               ::nColOffset ++
            ENDIF
            ::GetDisplayCols()
            IF lRefresh
               ::Super:Refresh( .F. )
            ENDIF
         ENDIF
      ELSE
         ::nColSel++
         IF lRefresh
            ::DrawLine( Nil, .T. )
         ENDIF
      ENDIF
   ELSEIF ::nColOffset < ( nVis - nFreeze )
      IF ! ::LastColVisible()
         ::nColOffset := Min( ::nColOffset + 1, nVis - nFreeze )
         ::GetDisplayCols( .F. )
         oCol := ::ColAtPos( ::nColSel )
         DO WHILE ! ::IsColVisible( oCol, .T. ) .AND. ::nColOffset < ( nVis  - nFreeze ) .AND. oCol:nPos != 1
            ::nColOffset++
            ::nColSel--
            ::GetDisplayCols( .F. )
         ENDDO
         ::UpdateDisplayCols()
         IF lRefresh
            ::Super:Refresh( .F. )
         ENDIF
      ELSEIF ::nMarqueeStyle < bmHIGHLROW .AND. ::nColSel < ::LastDisplayPos( .F. )
         ::nColSel++
         IF lRefresh
            ::DrawLine( Nil, .T. )
         ENDIF
      ENDIF
   ENDIF

   IF ::GetHScroll()
      ::SetHScroll( .T. )
   ENDIF

   ::OnChange( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoLeftMost()  CLASS XBrowse

   ::nColSel    := 1
   ::nColOffset := 1
   ::GetDisplayCols()
   ::Super:Refresh( .F. )

   IF ::GetHScroll()
      ::SetHScroll( .T. )
   ENDIF

   ::OnChange( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoRightMost()  CLASS XBrowse

   DO WHILE ! ::LastColVisible() .AND. ::nColOffset < Len( ::aDisplay )
      ::nColOffset++
      ::GetDisplayCols( .F. )
   ENDDO

   ::nColSel := Len( ::aDisplay )
   ::UpdateDisplayCols()
   ::Super:Refresh( .F. )

   IF ::GetHScroll()
      ::SetHScroll( .T. )
   ENDIF

   ::OnChange( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoHPos( nPos ) CLASS XBrowse

   LOCAL oCol
   LOCAL nFor, nCols, nWidth, nVisible, nCol

   nCols    := Len( ::aCols )
   nVisible := 0

   IF ::nMarqueeStyle <= bmHIGHLROWRC .AND. nPos > GetScrollPos( ::Handle, SB_HORZ )
      nPos += GetScrollPage( ::Handle, SB_HORZ )
   ENDIF

   FOR nFor := 1 TO nCols
      oCol := ::aCols[ nFor ]
      IF oCol:IsVisible()
         nVisible += oCol:nWidth
         IF nVisible >= nPos
            RETURN ::SelectCol( nFor )
         ENDIF
      ENDIF
   NEXT

RETURN NIL

//--------------------------------------------------------------------------

METHOD GoUp() CLASS XBrowse

   LOCAL oCol
   LOCAL nHeight, nSkip, nCol

   nSkip := 0

   IF ::nLen == 0 .OR. ::IsBof()
      IF ::lFilterBar
         nCol := ::nColSel
         DO WHILE nCol <= Len( ::aDisplay ) .AND. !Empty( oCol := ::ColAtPos( nCol ) ) .AND. !oCol:lCanFilter
            nCol++
         END WHILE
         ::oHeader:EditFilter( nCol - IIF(::lRecordSelector, 0, 1 ) )
      ENDIF
      RETURN nSkip
   ENDIF

   IF ::nEditMode >= beAPPEND
      ::CancelEdit()
      RETURN nSkip
   ENDIF

   ::CancelEdit()
   ::DrawLine()
   ::Seek()

   IF ::Skip( -1 ) == -1
      nSkip := -1
      ::nKeyNo --
      IF ::nRowSel > 1
         ::nRowSel--
      ELSE
         ::DataScroll( -1 )
         nHeight := ::BrwHeight() - ::FooterHeight() - ::FirstDataRow()
         IF ::nDataRows < ::RowCount()
            ::nDataRows++
         ENDIF
      ENDIF
      IF ::GetVScroll()
         ::SetVPos( ::GetVPos() - 1 )
      ENDIF
      ::OnChange( .T. )
   ENDIF

   ::DrawLine( Nil, .T. )

RETURN nSkip

//--------------------------------------------------------------------------

METHOD GoDown( lKeyBoard ) CLASS XBrowse

   LOCAL nLines
   LOCAL lGoLast

   DEFAULT lKeyBoard TO .F.

   ::CancelEdit()
   ::Seek()

   IF ::nLen == 0 .OR. ::IsEof() .AND. lKeyBoard
      lGoLast := ::OnPastEof()
      IF Empty( lGoLast )
         ::Append( .T. )
      ENDIF
      RETURN Nil
   ENDIF

   nLines := ::RowCount()

   ::DrawLine()

   IF ::Skip( 1 ) == 1
      ::nKeyNo++
      IF ::nRowSel < nLines
         ::nRowSel++
      ELSE
         ::DataScroll( 1 )
      ENDIF
      ::OnChange( .T. )
      IF ::GetVScroll()
         ::SetVPos( ::GetVPos() + 1 )
      ENDIF
   ELSE
      IF ::GetVScroll()
         ::SetVPos( ::GetVRange[ 2 ] )
      ENDIF
      IF lKeyBoard
         lGoLast := ::OnPastEof()
         IF Empty( lGoLast ) .AND. ::Append( .T. )
            RETURN Nil
         ENDIF
      ENDIF
   ENDIF

   ::DrawLine( Nil, .T. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoPageUp( nLines ) CLASS XBrowse

   LOCAL nSkipped

   IF ::nLen < 1
      RETURN Nil
   ENDIF

   DEFAULT nLines TO ::RowCount()

   ::CancelEdit()
   ::Seek()
   ::DrawLine()

   nSkipped = ::Skip( -nLines )

   IF nSkipped = 0
      ::DrawLine( Nil, .T.)
      RETURN Nil
   ENDIF

   ::nKeyNo += nSkipped

   IF -nSkipped < nLines
      ::nRowSel = 1
      ::OnChange( .T. )
      ::Super:Refresh( .F. )
      IF ::GetVScroll()
         ::SetVPos( 1 )
      ENDIF
   ELSE
      ::OnChange( .T. )
      ::Super:Refresh( .F. )
      IF ::GetVScroll()
         ::SetVPos( ::GetVPos() + nSkipped )
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoPageDown( nLines ) CLASS XBrowse

   LOCAL nSkipped, nBook1, nBook2

   IF ::nLen < 1
      RETURN Nil
   ENDIF

   DEFAULT nLines TO ::RowCount()

   ::CancelEdit()
   ::Seek()
   ::DrawLine()

   nSkipped  = ::Skip( nLines )
   ::nKeyNo += nSkipped

   IF nSkipped < nLines .AND. nSkipped <= ( ::nDataRows - ::nRowSel )
      ::nRowSel = ::nDataRows
      ::OnChange( .T. )
      ::DrawLine( Nil, .T. )
      IF ::GetVScroll()
         ::SetVPos( ::GetVRange[ 2 ] )
      ENDIF
      RETURN Nil
   ENDIF

   ::OnChange( .T. )

   ::Super:Refresh( .F. )

   IF ::GetVScroll()
      ::SetVPos( ::GetVPos() + nSkipped )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoTop() CLASS XBrowse

   ::nKeyNo := 1

   IF ::IsBof() .OR. ::nLen < 1
      RETURN Nil
   ENDIF

   ::CancelEdit()
   ::Seek()
   ::DrawLine()

   ::MoveTop()

   IF ::GetVScroll()
      ::SetVPos( 1 )
   ENDIF

   ::nRowSel := 1

   ::OnChange( .T. )

   ::Super:Refresh( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoBottom() CLASS XBrowse

   LOCAL nLines, nRow, nBook
   LOCAL hDC

   ::CancelEdit()
   ::Seek()
   ::DrawLine()

   ::nKeyNo := ::nLen

   IF ::IsEof() .OR. ::nLen == 0
      ::DrawLine( Nil, .T. )
      RETURN Nil
   ENDIF

   nLines := ::RowCount()
   nRow   := nLines

   ::MoveBottom()

   nBook := ::BookMark()

   DO WHILE nRow-- > 0 .AND. ::Skip( -1 ) == -1
   ENDDO

   ::nRowSel := nLines - nRow

   ::BookMark( nBook )

   IF ::GetVScroll()
      ::SetVPos( ::GetVRange[ 2 ] )
   ENDIF

   ::OnChange( .T. )

   ::Super:Refresh( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD GoTo( nPos ) CLASS XBrowse

   LOCAL nRow, nBook

   IF ::nLen < 1
      RETURN Nil
   ENDIF

   ::CancelEdit()
   ::Seek()
   ::DrawLine()

   nRow  := ::nRowSel
   nBook := ::BookMark()
   CursorWait()
   ::KeyNo( nPos )

   IF nBook == ::BookMark() // no ha funcionado OnKeyNo( npos )
      ::Skip( nPos - ::KeyNo() )
   ENDIF

   ::nKeyNo := nPos

   CursorArrow()

   nBook := ::BookMark()

   DO WHILE nRow > 0 .AND. ::Skip( -1 ) == -1
      nRow--
   ENDDO

   ::nRowSel := ::nRowSel - nRow

   ::BookMark( nBook )

   ::OnChange( .T. )

   ::SetVPos( nPos )
   ::Super:Refresh( .F. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD MouseRowPos( nRow ) CLASS XBrowse

   LOCAL nRowPos, nTmp

   IF nRow <= ::FirstDataRow()
      RETURN 0
   ENDIF

   nTmp    := nRow - ::FirstDataRow()
   nRowPos := Int( nTmp / ::nIntRowHeight ) + 1

   IF nRowPos > ::nDataRows
      nRowPos := 0
   ENDIF

RETURN nRowPos

//--------------------------------------------------------------------------

METHOD MouseColPos( nCol ) CLASS XBrowse

   LOCAL nWidth, nColPos, nLen, nFor

   nColPos   := 0
   nLen      := ::LastDisplayPos()

   nWidth := ::FirstCol()

   IF nCol < nWidth .OR. Len( ::aCols ) == 0
      RETURN -1
   ENDIF

   IF nCol > nWidth
      FOR nFor := 1 TO nLen
         nWidth += ::ColAtPos( nFor ):nWidth
         IF nWidth > nCol
            nColPos := nFor
            EXIT
         ENDIF
      NEXT
   ENDIF

RETURN nColPos

//--------------------------------------------------------------------------

METHOD WMLButtonDown( nWParam, nLParam, hWnd, nWmFlags, lEvent ) CLASS XBrowse

   LOCAL oCol
   LOCAL nRowPos, nColPos, nLen, nFor, nPos, nKeys, nRow, nCol
   LOCAL xRet

   DEFAULT lEvent TO .T.

   nKeys := nWParam
   nRow  := HiWord( nLParam )
   nCol  := LoWord( nLParam )

   IF ::SetFocus()
      ::CancelEdit()
      ::Seek()
   ENDIF

   IF Len( ::aDisplay ) == 0
      ::nClickedCol := -2
      RETURN ::Super:WMLButtonDown( nWParam, nLParam, hWnd, nWmFlags, lEvent )
   ENDIF

   nRowPos := ::MouseRowPos( nRow )
   nColPos := ::MouseColPos( nCol )
   nLen    := ::nDataRows

   ::nClickedCol := nColPos

   // click en el recordselector y se permite cambio de tamaño de la fila

   IF nColPos == -1 .AND. ::lRecordSelector .AND. ::lAllowRowSizing .AND. ::nRowDividerStyle > 0
      FOR nFor := 1 TO nLen
         nPos := ( nFor * ::nIntRowHeight ) + ::FirstDataRow()
         IF nRow >= ( nPos - 1 ) .AND. nRow <= ( nPos + 1 )
            ::HorzLine( nRow, 1, nFor )
            RETURN 0
         ENDIF
      NEXT
      RETURN ::Super:WMLButtonDown( nWParam, nLParam, hWnd, nWmFlags, lEvent )
   ENDIF

   // click en zona sin datos y con marquee de celda, no de linea

   IF nColPos == 0 .AND. ::nMarqueeStyle < bmHIGHLROWRC
      RETURN ::Super:WMLButtonDown( nWParam, nLParam, hWnd, nWmFlags, lEvent )
   ENDIF

   // click en fila o columna con datos

   IF nRowPos > 0 .OR. nColPos > 0

      ::DrawLine()

      IF nRowPos > 0 .AND. ( nRowPos - ::nRowSel ) != 0
         ::nKeyNo += ::Skip( nRowPos - ::nRowSel )
         ::nRowSel := nRowPos
         IF lEvent
            ::OnChange( .T. )
         ENDIF
      ENDIF

      IF ::nMarqueeStyle == bmHIGHLROWMS
         IF ! lGetKeyState( VK_CONTROL ) .AND. ! lGetKeyState( VK_SHIFT ) .AND. ;
               AScan( ::aSelected, ::BookMark() ) == 0
            ::SelectNone()
         ENDIF
      ENDIF

      IF nColPos > 0 .AND. ::nMarqueeStyle < bmHIGHLROW .AND. ::nColSel != nColPos
         ::nColSel := nColPos
         ::OnChange( .F. )
      ENDIF

      IF ::nMarqueeStyle == bmHIGHLROWMS
         DO CASE
            CASE lGetKeyState( VK_CONTROL )
               ::Select( SEL_SWAPCURRENTKEY )
            CASE lGetKeyState( VK_SHIFT )
                ::Select( SEL_SETRANGE )
            OTHERWISE
               ::Select( SEL_ADDCURRENTKEY )
         ENDCASE
      ENDIF

      ::DrawLine( Nil, .T. )

      IF ::GetVScroll()
         ::SetVPos( ::nKeyNo )
      ENDIF

      IF ::GetHScroll()
         ::SetHScroll( .T. )
      ENDIF

   ENDIF

RETURN ::Super:WMLButtonDown( nWParam, nLParam, hWnd, nWmFlags, lEvent )

//--------------------------------------------------------------------------

METHOD WMLButtonUp( nWParam, nLParam ) CLASS XBrowse

   LOCAL nRow, nCol

   IF Len( ::aDisplay ) != 0
      nRow  := HiWord( nLParam )
      nCol  := LoWord( nLParam )

      IF ::lCaptured
         ReleaseCapture()
         ::lCaptured := .F.
         ::HorzLine( nRow, 2 )
         RETURN 0
      ENDIF
   ENDIF

RETURN ::Super:WMLButtonUp( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD Click( nFlags, nX, nY ) CLASS XBrowse

   LOCAL oCol
   LOCAL nRowPos, nColPos
   LOCAL xRet

   IF ::lEditMode
      RETURN Nil
   ENDIF

   nRowPos := ::MouseRowPos( nY )
   nColPos := ::MouseColPos( nX )

   IF ::nOnDrag == 0 .AND. nRowPos == ::nRowSel .AND. ;
      ::nMarqueeStyle == bmHIGHLROWMS .AND. ;
         ! lGetKeyState( VK_CONTROL ) .AND. ! lGetKeyState( VK_SHIFT )
      ::SelectNone()
      ::Select( SEL_ADDCURRENTKEY )
   ENDIF

   IF ::nClickedCol != nColPos .OR. nColPos == -2 // Se cambio de columna, hacemos click del browse
      xRet := ::OnClick( nFlags, nX, nY )
   ELSE
      IF nColPos == -1 // RecordSelector
         xRet := ::oRecSel:OnClick( nFlags, nX, nY )
      ELSEIF nColPos > 0
         oCol := ::ColAtPos( nColPos )
         IF oCol != Nil
            xRet := oCol:OnDataClick( nFlags, nX, nY )
         ENDIF
      ENDIF
      IF xRet == Nil
         xRet := ::OnClick( nFlags, nX, nY )
      ENDIF
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD WMLButtonDblClk( nWParam, nLParam ) CLASS XBrowse

   LOCAL oCol
   LOCAL nRowPos, nColPos, nKeys, nRow, nCol
   LOCAL xRet

   nKeys := nWParam
   nRow  := HiWord( nLParam )
   nCol  := LoWord( nLParam )

   IF ::SetFocus()
      ::CancelEdit()
      ::Seek()
   ENDIF

   IF Len( ::aDisplay ) == 0
      RETURN ::Super:WMLButtonDblClk( nWParam, nLParam )
   ENDIF

   nRowPos := ::MouseRowPos( nRow )
   nColPos := ::MouseColPos( nCol )

   IF nRowPos > 0 .AND. nColPos > 0
      oCol := ::ColAtPos( nColPos )
      IF ::lAllowEdit .AND. ::nMarqueeStyle > bmHIGHLROWRC //.AND. ::IsEditable()
         IF ::Edit( .T. )
            xRet := 0
         ENDIF
      ELSEIF ::lAllowEdit .AND. oCol:nEditType > beNONE //.AND. ::nMarqueeStyle <= bmHIGHLCELL
         oCol:Edit()
         xRet := 0
      ELSE
         xRet := oCol:OnDataDblClick( nKeys, nCol, nRow )
      ENDIF
   ELSEIF nRowPos > 0 .AND. nColPos == -1 .AND. ::lRecordSelector
      xRet := ::oRecSel:OnDblClick( nKeys, nCol, nRow )
   ENDIF

   IF xRet == Nil
      xRet := ::OnDblClick( nKeys, nCol, nRow )
   ENDIF

RETURN xRet //::Super:WMLButtonDblClk( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMRButtonDown( nWParam, nLParam ) CLASS XBrowse

   LOCAL nColPos

   IF ::SetFocus()
      ::CancelEdit()
      ::Seek()
   ENDIF

   nColPos := ::MouseColPos( LoWord( nLParam ) )

   IF Empty( nColPos )
      ::nClickedCol := nColPos
   ELSE
      ::nClickedCol := -2
   ENDIF

RETURN ::Super:WMRButtonDown( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMRButtonUp( nWParam, nLParam ) CLASS XBrowse

   LOCAL oCol
   LOCAL nRowPos, nColPos, nRow, nCol, nKeys
   LOCAL xRet

   IF Empty( ::nClickedCol )
      RETURN ::Super:WMRButtonUp( nWParam, nLParam )
   ENDIF

   nRow    := HiWord( nLParam )
   nCol    := LoWord( nLParam )
   nKeys   := nWParam
   nRowPos := ::MouseRowPos( nRow )
   nColPos := ::MouseColPos( nCol )

   IF nRowPos > 0 .AND. nColPos > 0
      oCol := ::ColAtPos( nColPos )
      xRet := oCol:OnDataRClick( nKeys, nCol, nRow )
   ELSEIF nRowPos != 0 .AND. nColPos ==  -1 .AND. ::lRecordSelector
      xRet := ::oRecSel:OnRClick( nKeys, nCol, nRow )
   ENDIF

   IF xRet == Nil
      xRet := ::OnRClick( nKeys, nCol, nRow )
   ENDIF

   ::nClickedCol := 0

RETURN ::Super:WMRButtonUp( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMMouseMove( nWParam, nLParam ) CLASS XBrowse

   LOCAL nRow, nCol, nLen, nFor, nPos

   IF Len( ::aDisplay ) == 0
      RETURN ::Super:WMMouseMove( nWParam, nLParam )
   ENDIF

   nRow := HiWord( nLParam )
   nCol := LoWord( nLParam )

   IF ::lCaptured
      ::HorzLine( nRow, 3 )
   ENDIF

   nLen := ::nDataRows

   IF ::lRecordSelector .and. ::lAllowRowSizing .and. ;
      ::nRowDividerStyle > 0 .and. ::MouseColPos( nCol ) == -1

      FOR nFor := 1 TO nLen
         nPos := ( nFor * ::nIntRowHeight ) + ::FirstDataRow()
         IF nRow >= ( nPos - 1 ) .AND. nRow <= ( nPos + 1 )
            CursorNS()
            RETURN 0
         ENDIF
      NEXT
   ENDIF

RETURN ::Super:WMMouseMove( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMMouseWheel( nWParam, nLParam ) CLASS XBrowse

   LOCAL nKeys, nDelta, nXPos, nYPos

   IF Len( ::aDisplay ) == 0
      RETURN Nil
   ENDIF

   ::CancelEdit()

   nKeys  := LoWord( nWParam )
   nDelta := HiWord( nWParam )
   nXPos  := LoWord( nLParam )
   nYPos  := HiWord( nLParam )

   IF lAnd( nKeys, MK_MBUTTON )
      IF nDelta > 0
         ::GoPageUp()
      ELSE
         ::GoPageDown()
      ENDIF
   ELSE
      IF nDelta > 0
         ::GoUp()
      ELSE
         ::GoDown()
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD HorzLine( nRow, nOperation, nLine ) CLASS XBrowse

   STATIC sLine := 0, sRow := 0

   LOCAL hDC, nTop, nNewHeight, nOldHeight
   LOCAL lAbort

   hDC := GetDC( ::Handle )

   IF nLine != Nil
      sLine := nLine
   ENDIF

   DO CASE
      CASE nOperation == 1
         ::lCaptured := .T.
         SetCapture( ::Handle )
         sRow := nRow
         InvertRect( hDC, { 0, nRow - 1, ::LastCol(), nRow + 1 } )
         CursorNS()

      CASE nOperation == 2
         nTop := ( sLine * ::nIntRowHeight ) + ::FirstDataRow()
         InvertRect( hDC, { 0, sRow - 1, ::LastCol(), sRow + 1 } )
         IF Abs( nRow - nTop ) > 2
            nTop := ( ( sLine - 1 ) * ::nIntRowHeight ) + ::FirstDataRow()
            nOldHeight := ::nIntRowHeight
            nNewHeight := Max( Min( Max( nRow - nTop, 20 ), ::BrwHeight() - nTop - 20 ), 20)
            IF nOldHeight != nNewHeight
               lAbort := ::OnChangeHeight( @nNewHeight, nOldHeight )
               IF !( Valtype( lAbort ) = "L" .AND. lAbort )
                  ::nRowHeight := nNewHeight
                  ::Super:Refresh( .F. )
               ENDIF
            ENDIF
         ENDIF

      CASE nOperation == 3
         nTop := ( ( sLine - 1 ) * ::nIntRowHeight ) + ::FirstDataRow() + 20
         CursorNS()
         IF nRow > nTop .AND. nRow < (::BrwHeight() - 20 )
            InvertRect( hDC, { 0, sRow - 1, ::LastCol(), sRow + 1 } )
            sRow := nRow
            InvertRect( hDC, { 0, sRow - 1, ::LastCol(), sRow + 1 } )
         ENDIF
   ENDCASE

   ReleaseDC( ::Handle, hDC )

RETURN Nil

//--------------------------------------------------------------------------

METHOD WMKeyDown( nKey, nFlags, hWnd ) CLASS XBrowse

   LOCAL oCol
   LOCAL nRecno

   IF Len( ::aDisplay ) == 0 .OR. hWnd == Nil .OR. ::lEditMode
      RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
   ENDIF

   DO CASE
      CASE nKey == VK_UP
         IF ::nMarqueeStyle != bmHIGHLROWMS
            ::SelectNone()
         ELSE
            IF !lGetKeyState( VK_SHIFT )
               ::SelectNone()
            ELSE
               nRecno := ::BookMark()
               IF ::Skip( -1 ) != 0
                  nRecno := ::BookMark()
                  ::Skip( 1 )
                  IF AScan( ::aSelected, nRecno ) > 0
                     ::Select( SEL_SWAPCURRENTKEY )
                  ENDIF
               ELSE
                  ::BookMark( nRecno )
               ENDIF
            ENDIF
         ENDIF
         IF ::GoUp() < 0
            ::Select( SEL_ADDCURRENTKEY )
         ENDIF

      CASE nKey == VK_DOWN
         IF ::nMarqueeStyle != bmHIGHLROWMS
            ::SelectNone()
         ELSE
            IF ! lGetKeyState( VK_SHIFT )
               ::SelectNone()
            ELSE
               nRecno := ::BookMark()
               IF ::Skip( 1 ) != 0
                  nRecno := ::BookMark()
                  ::Skip( -1 )
                  IF Ascan( ::aSelected, nRecno ) > 0
                     ::Select( SEL_SWAPCURRENTKEY )
                  ENDIF
               ELSE
                  ::BookMark( nRecno )
               ENDIF
            ENDIF
         ENDIF
         ::GoDown( .T. )
         ::Select( SEL_ADDCURRENTKEY )

      CASE nKey == VK_LEFT
         IF lGetKeyState( VK_CONTROL )
            ::GoLeftMost()
         ELSE
            ::GoLeft()
         ENDIF

      case nKey == VK_RIGHT
         IF lGetKeyState( VK_CONTROL )
            ::GoRightMost()
         ELSE
            ::GoRight()
         ENDIF

      CASE nKey == VK_HOME
         ::SelectNone()
         ::GoTop()
         ::Select( SEL_ADDCURRENTKEY )

      CASE nKey == VK_END
         ::SelectNone()
         ::GoBottom()
         ::Select( SEL_ADDCURRENTKEY )

      CASE nKey == VK_PRIOR
         ::SelectNone()
         IF lGetKeyState( VK_CONTROL )
            //::GoTop()
            RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
         ELSE
            ::GoPageUp()
         ENDIF
         ::Select( SEL_ADDCURRENTKEY )

      CASE nKey == VK_NEXT
         ::SelectNone()
         IF lGetKeyState( VK_CONTROL )
            RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
            //::GoBottom()
         ELSE
            ::GoPageDown()
         ENDIF
         ::Select( SEL_ADDCURRENTKEY )
      CASE nKey == VK_RETURN .AND. ::lAllowEdit .AND. !lGetKeyState( VK_CONTROL ) .AND. !lGetKeyState( VK_SHIFT )
         IF ::nMarqueeStyle <= bmHIGHLROWRC
            oCol := ::SelectedCol()
            IF oCol:nEditType > beNONE
               IF !oCol:oGridEdit:lVisible
                  IF !oCol:Edit()
                     RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
                  ENDIF
               ELSE
                  PostMessage(oCol:oGridEdit:Handle, WM_KEYDOWN, VK_RETURN)
               ENDIF
            ENDIF
         ELSE
            IF !::Edit( .T. )
               RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
            ENDIF
         ENDIF

    CASE nKey == VK_INSERT .AND. ! lGetKeyState( VK_CONTROL ) .AND. ! lGetKeyState( VK_SHIFT )
         IF ! ::Insert( .T. )
            RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
         ENDIF

      CASE nKey == VK_DELETE .AND. ! lGetKeyState( VK_CONTROL ) .AND. ! lGetKeyState( VK_SHIFT )
         IF ! ::Delete( .T. )
            RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
         ENDIF

      OTHERWISE
         RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )
   ENDCASE

   ::oForm:SendMsg( WM_CHANGEUISTATE, MakeLong( UIS_CLEAR, UISF_HIDEFOCUS ) )

RETURN 0

//--------------------------------------------------------------------------

METHOD WMChar( nKey, nFlags, hWnd ) CLASS XBrowse

   LOCAL oCol
   LOCAL cKey

   IF Len( ::aDisplay ) == 0 .OR. ::lEditMode .OR. hWnd != ::Handle
      RETURN ::Super:WMChar( nKey, nFlags, hWnd )
   ENDIF

   DO CASE
      CASE nKey == VK_ESCAPE

      CASE IsCharAlphaNumeric( nKey ) .OR. IsSpecialChar( nKey )
         cKey := Chr( nKey )
         IF ::lAllowEdit .AND. ::lFastEdit .AND. ::nMarqueeStyle <= bmHIGHLCELL
            oCol := ::SelectedCol()
            IF oCol:nEditType > beNONE
               oCol:Edit( nKey )
               RETURN 0
            ENDIF
         ENDIF
         IF nKey > 31 .AND. ::EventAssigned( "OnSeek" )
            ::Seek( ::cSeek + cKey )
         ELSE
            RETURN ::Super:WMChar( nKey, nFlags, hWnd )
         ENDIF

      CASE nKey == VK_BACK .AND. ! Empty( ::cSeek )
         ::Seek( Left( ::cSeek, Len( ::cSeek ) -1 ) )

      OTHERWISE
         RETURN ::Super:WMChar( nKey, nFlags, hWnd )
   ENDCASE

RETURN 0

//--------------------------------------------------------------------------

METHOD Select( nOperation ) CLASS XBrowse

   LOCAL uBook, uCurRow, uOldRow, uTemp
   LOCAL aTemp
   LOCAL nAt, nLen
   LOCAL lRefresh

   IF ::nMarqueeStyle != bmHIGHLROWMS
      RETURN Nil
   ENDIF

   DEFAULT nOperation TO 1

   DO CASE
      CASE nOperation == 0 // delete all
         IF Len( ::aSelected ) == 1 .AND. ::BookMark() == ::aSelected[ 1 ]
            lRefresh := .F.
         ELSE
            lRefresh := .T.
         ENDIF
         ::aSelected := {}

         IF lRefresh .AND. ! Empty( ::Handle )
            ::Redraw()
         ENDIF

      CASE nOperation == 1 .OR. ( Len( ::aSelected ) == 0 .AND. nOperation != 4 ) // Add current
         uBook := ::BookMark()
         nAt   := AScan( ::aSelected, uBook )
         IF nAt == 0
            AAdd( ::aSelected, uBook )
            lRefresh := .t.
            IF ! Empty( ::Handle )
               ::DrawLine( Nil, .T. )
            ENDIF
         ENDIF

      CASE nOperation == 2 // Swap key (Ctrl+LClick)
         uBook := ::BookMark()
         nAt   := AScan( ::aSelected, uBook )
         IF nAt > 0
            IF ! Empty( ::Handle )
               ::DrawLine( Nil, .F. )
            ENDIF
            ::aSelected[ nAt ] := ATail( ::aSelected )
            ASize( ::aSelected, Len( ::aSelected ) - 1 )
         ELSE
            AAdd( ::aSelected, ::BookMark() )
            IF ! Empty( ::Handle )
               ::DrawLine( Nil, .T. )
            ENDIF
         ENDIF

      CASE nOperation == 3 // Shift & lclick
         uBook   := ::BookMark()
         uCurRow := ::KeyNo()
         ::BookMark( ATail( ::aSelected ) )
         uOldRow := ::KeyNo()
         IF uOldRow != uCurRow
            ::aSelected := { ATail( ::aSelected ) }
            IF uCurRow > uOldRow
               CursorWait()
               DO WHILE ( uTemp := ::BookMark() ) != uBook .AND. !::IsEof()
                  IF AScan( ::aSelected, uTemp ) == 0
                     AAdd( ::aSelected, uTemp )
                  ENDIF
                  ::nKeyNo += ::Skip( 1 )
               ENDDO
               CursorArrow()
            ELSE
               CursorWait()
               DO WHILE ( uTemp := ::BookMark() ) != uBook .AND. ! ::IsBof()
                  IF Ascan( ::aSelected, uTemp ) == 0
                     Aadd( ::aSelected, uTemp )
                  ENDIF
                  ::nKeyNo += ::Skip( -1 )
               ENDDO
               CursorArrow()
            ENDIF
            AAdd( ::aSelected, uBook )
            ::BookMark( uBook )
            IF ! Empty( ::Handle )
               ::Refresh()
            ENDIF
         ELSE
            ::BookMark( uBook )
         ENDIF

      CASE nOperation == 4 // Select all
         uBook       := ::BookMark()
         ::aSelected := Array( ::KeyCount() )
         nAt         := 1
         nLen        := ::nLen
         aTemp       := ::aSelected
         CursorWait()
         ::GoTop()
         DO WHILE nAt <= nLen
            aTemp[ nAt++ ] := ::BookMark()
            ::nKeyNo += ::Skip( 1 )
         ENDDO
         ::BookMark( uBook )
         CursorArrow()
         IF ! Empty( ::Handle )
            ::Refresh()
         ENDIF
   ENDCASE

   ::OnChangeSelection( ::aSelected )

RETURN Nil

//--------------------------------------------------------------------------

METHOD Seek( cSeek ) CLASS XBrowse

   LOCAL uBook, uSeek
   LOCAL nFor, nRows
   LOCAL lRefresh

   IF ! ::EventAssigned( "OnSeek" )
      RETURN Nil
   ENDIF

   IF cSeek == Nil
      IF ! Empty( ::cSeek )
         ::cSeek := ""
         IF ::oSeek != Nil
            IF ::oSeek:IsKindOf( "XEdit" )
               ::oSeek:Value := ""
            ELSE
               ::oSeek:cText := ""
            ENDIF
         ENDIF
      ENDIF
      RETURN Nil
   ENDIF

   uBook := ::BookMark

   IF ! ::OnSeek( cSeek )
      ::BookMark( uBook )
      MsgBeep()
      RETURN Nil
   ENDIF

   ::cSeek  := cSeek
   uSeek    := ::BookMark()
   nRows    := ::nDataRows
   lRefresh := .T.

   IF ::oSeek != Nil
      IF ::oSeek:IsKindOf( "XEdit" )
         ::oSeek:Value := cSeek
      ELSE
         ::oSeek:cText := cSeek
      ENDIF
   ENDIF

   ::BookMark( uBook )
   ::Skip( 1 - ::nRowSel )

   FOR nFor := 1 TO nRows
      IF ::BookMark() == uSeek
         lRefresh := .F.
         EXIT
      ENDIF
      IF ::Skip( 1 ) == 0
         EXIT
      ENDIF
   NEXT

   IF lRefresh
      ::BookMark( uSeek )
      ::OnChange( .T. )
      ::Super:Refresh(.F.)
   ELSE
      ::BookMark( uBook )
      ::DrawLine( Nil, .F. )
      ::nRowSel := nFor
      ::BookMark( uSeek )
      ::OnChange( .T. )
      ::DrawLine( Nil, .T. )
   ENDIF

   ::KeyNo()

   IF ::GetVScroll()
      ::SetVPos( ::nKeyNo )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD CancelEdit( lAbort, nKey, lEdRes ) CLASS XBrowse

   LOCAL oCol

   DEFAULT lAbort TO .F.
   DEFAULT nKey   TO 0
   DEFAULT lEdRes TO .T.

   IF ! ::lEditMode .OR. ::lOnPostEdit
      RETURN Nil
   ENDIF

   FOR EACH oCol IN ::aCols
      WITH OBJECT oCol:oGridEdit
         IF :lVisible
            :lVisible := .F.
         ENDIF
      END WITH
   NEXT

   FOR EACH oCol IN ::aCols
      WITH OBJECT oCol
         IF :lEdited
            :PostEdit( @lAbort, nKey )
            IF lEdRes
               :lEdited := .F.
            ENDIF
         ENDIF
      END WITH
   NEXT

	::lOnPostEdit := .T.
	::OnPostEdit( @lAbort, nKey )
	::lOnPostEdit := .F.

   IF ::nPhantomRow != 0
      ::Refresh()
   ENDIF

   ::lMultiEdit  := .F.
   ::nPhantomRow := 0
   ::nEditMode   := beREAD

RETURN Nil

//--------------------------------------------------------------------------

METHOD SaveState() CLASS XBrowse

   LOCAL oCol
   LOCAL cState
   LOCAL nFor, nLen

   nLen   := Len( ::aCols )
   cState := Ltrim( Str( ::nRowHeight ) ) + ";"

   FOR nFor := 1 TO nLen
      oCol   := ::aCols[ nFor ]
      cState += Ltrim( Str( oCol:nIndex ) ) + ":" + ;
                Ltrim( Str( oCol:nWidth ) ) + ":" + ;
                IIf( oCol:IsVisible(), "S", "H")
      IF nFor < nLen
         cState += ";"
      ENDIF
   NEXT

RETURN cState

//--------------------------------------------------------------------------

METHOD RestoreState( cState ) CLASS XBrowse

   LOCAL oCol
   LOCAL aMoved
   LOCAL cCol
   LOCAL nLen, nOrder, nWidth, nFor, nAt, nHeight
   LOCAL lVisible

   IF Empty( cState )
      RETURN Nil
   ENDIF

   aMoved := {}
   nLen   := len( ::aCols )

   nHeight := Val( hb_TokenGet( cState, 1, ";" ) )

   IF nLen == 0 .OR. HB_TokenCount( cState, ";" ) != ( nLen + 1 )
      RETURN Nil
   ENDIF

   ::nRowHeight := nHeight

   FOR nFor := 1 TO nLen
      cCol     := hb_TokenGet( cState, nFor + 1, ";" )
      IF Empty( cCol )
         RETURN Nil
      ENDIF
      nOrder   := Val( hb_TokenGet( cCol, 1, ":" ) )
      nWidth   := Val( hb_TokenGet( cCol, 2, ":" ) )
      lVisible := ( hb_TokenGet( cCol, 3, ":" ) == "S" )
      nAt      := AScan( ::aCols, {|v| v:nIndex == nOrder } )
      IF nAt > 0
         oCol := ::aCols[ nAt ]
         oCol:lVisible := lVisible
         oCol:SetWidth( nWidth, .f. )
      ENDIF
      AAdd( aMoved, ::aCols[ nOrder ] )
   NEXT

   ::aCols := aMoved

   ::GetDisplayCols()
   ::Super:Refresh()

RETURN Nil

//--------------------------------------------------------------------------

METHOD FilterMode( lFilterBar ) CLASS XBrowse

   ::CancelEdit()

   IF ! ::lFilterBar
      ::nFilterHeight := ::nHeaderHeight
   ENDIF

   ::FlFilterBar := lFilterBar

   IF ::oHeader != Nil
      WITH OBJECT ::oHeader
         :lFilterBar     := lFilterBar
         IF ::lCreated
            :nHeight        := Round( ::nFilterHeight * IIf( lFilterBar, 1.5, 1 ), 0 )
            ::nHeaderHeight := :nHeight
         ENDIF
      END
      ::Refresh()
      ::SetFilter( 0 )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD FilterEdit( oSender, nHeaderItem ) CLASS XBrowse

   LOCAL oCol

   IF ::lRecordSelector
      nHeaderItem--
   ENDIF

   IF nHeaderItem <= 0
      RETURN .F.
   ENDIF

   IF !Empty( oCol := ::ColAtPos( nHeaderItem ) )
      IF ! oCol:lCanFilter
         IF Empty( ::cMsgNoFilter )
            MsgBeep()
         ELSE
            MsgInfo( ::cMsgNoFilter )
         ENDIF
         ::SetFocus()
         RETURN .F.
      ENDIF
   ENDIF

RETURN NIL

//--------------------------------------------------------------------------

METHOD SetClrText( Value )  CLASS XBrowse

   IF Value == ::FnClrText
      RETURN Nil
   ENDIF

   ::FnClrText := Value

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrPane( Value ) CLASS XBrowse

   IF Value == ::FnClrPane
      RETURN Nil
   ENDIF

   IF ::oBrush != Nil
      ::oBrush:End()
      ::oBrush := Nil
   ENDIF

   ::FnClrPane := Value

   IF ::FnClrPane != Nil
      ::oBrush := TBrush():Create( Nil, ::nClrPane )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrSelText( Value ) CLASS XBrowse

   IF ::hClrSelText != Nil
      DeleteObject( ::hClrSelText )
   ENDIF

   ::FnClrSelText := Value
   ::hClrSelText  := CreateSolidBrush( ::FnClrSelText )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrSelPane( Value ) CLASS XBrowse

   IF ::hClrSelPane != Nil
      DeleteObject( ::hClrSelPane )
   ENDIF

   ::FnClrSelPane := Value
   ::hClrSelPane  := CreateSolidBrush( ::FnClrSelPane )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrAltPane( Value ) CLASS XBrowse

   IF ::hClrAltPane != Nil
      DeleteObject( ::hClrAltPane )
   ENDIF

   ::FnClrAltPane := Value
   ::hClrAltPane  := CreateSolidBrush( ::FnClrAltPane )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrSelFocusText( Value ) CLASS XBrowse

   IF ::hClrSelFocusText != Nil
      DeleteObject( ::hClrSelFocusText )
   ENDIF

   ::FnClrSelFocusText := Value
   ::hClrSelFocusText  := CreateSolidBrush( ::FnClrSelFocusText )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetClrSelFocusPane( Value ) CLASS XBrowse

   IF ::hClrSelFocusPane != Nil
      DeleteObject( ::hClrSelFocusPane )
   ENDIF

   ::FnClrSelFocusPane := Value
   ::hClrSelFocusPane  := CreateSolidBrush( ::FnClrSelFocusPane )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetHeader( Value ) CLASS XBrowse

   IF ::oHeader != Nil
      ::oHeader:End()
      ::oHeader := Nil
   ENDIF

   ::FlHeader := Value

   IF ! Empty( ::Handle )
      IF Value
         ::CreateHeader()
         ::GetDisplayCols()
      ENDIF
      ::Refresh( .T. )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD CreateHeader() CLASS XBrowse

   WITH OBJECT ::oHeader := THeader():New( Self )
      :nHeight        := ::nHeaderHeight
      :nWidth         := ::nClientWidth
      :oImageList     := ::oImageList
      :lDragDrop      := ::lAllowColSwapping
      :lResize        := ::lAllowColSizing
      :lBorder        := .F.
      :lTabStop       := .F.
      ::nFilterHeight := ::nHeaderHeight
      :lFilterBar     := ::lFilterBar
      :nAlign         := alNONE
      ::nHeaderHeight := :nHeight
      :OnEndDrag      := "ColDrag"
      :OnBeginTrack   := "ColBegTrack"
      :OnEndTrack     := "ColTrack"
      :OnDividerDblClick := "AdjustColWidth"
      :OnFilterChange := {| o, i | IIf( o:oParent:lRecordSelector, i--, ), IIf( i > 0 .AND. ::oHeader != Nil, ::SetFilter( i ), ) }
      :OnFilterEdit   := "FilterEdit"
      :Create()
   END WITH

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetFooter( Value ) CLASS XBrowse

   IF ::oFooter != Nil
      ::oFooter:End()
      ::oFooter := Nil
   ENDIF

   ::FlFooter := Value

   IF !Empty( ::Handle )
      IF Value
         ::CreateFooter()
         ::GetDisplayCols()
      ENDIF
      ::Refresh( .T. )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD CreateFooter() CLASS XBrowse

   WITH OBJECT ::oFooter := THeader():New( Self )
      :nHeight    := ::nFooterHeight
      :nWidth     := ::nClientWidth
      :nTop       := ::nClientHeight - :nHeight
      :oImageList := ::oImageList
      :lDragDrop  := .F.
      :lResize    := ::lAllowColSizing
      :lBorder    := .F.
      :lTabStop   := .F.
      :nAlign     := alNONE
      :OnEndTrack := "ColTrack"
      :Create()
   END WITH

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetHeaderHeight( Value ) CLASS XBrowse

   UPDATE ::FnHeaderHeight TO Value

   IF ::oHeader != Nil .AND. ! Empty( ::oHeader:Handle )
      ::oHeader:nHeight := Value
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetFooterHeight( Value ) CLASS XBrowse

   UPDATE ::FnFooterHeight TO Value

   IF ::oFooter != Nil .AND. ! Empty( ::oFooter:Handle )
      ::oFooter:nHeight := Value
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetRowHeight( Value ) CLASS XBrowse

   LOCAL nHeight, nFor, nLength

   UPDATE ::FnRowHeight TO Value

   IF Value > 0
      ::nIntRowHeight := Value
   ELSE
      nHeight := ::GetFont():GetTextHeight( "B", Self )
      nLength := Len( ::aCols )
      FOR nFor := 1 TO nLength
         nHeight := Max( nHeight, ::aCols[ nFor ]:DataHeight() )
      NEXT
      IF ::oRecSel != Nil
         nHeight := Max( ::oRecSel:nHeight, nHeight )
      ENDIF
      ::nIntRowHeight := ( nHeight * ::nDataLines ) + ROW_EXTRAHEIGHT // lines to give 3d look
      ::nIntRowHeight := Max( ::nIntRowHeight, ROW_MINHEIGHT )
      IF ::nRowDividerStyle != blNOLINES
         ::nIntRowHeight++
      ENDIF
      IF ::nRowDividerStyle >= blINSET
         ::nIntRowHeight++
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD Edit( lFromUser ) CLASS XBrowse
RETURN ::RowEdit( lFromUser )

//--------------------------------------------------------------------------

METHOD RowEdit( lFromUser ) CLASS XBrowse

   LOCAL oCol, oGridEdit
   LOCAL nFor
   LOCAL xRet

   DEFAULT lFromUser TO .F.

   IF ::nEditMode == beREAD
      xRet := ::OnEdit()
      IF Valtype( xRet ) == "L" .AND. !xRet
         RETURN .F.
      ENDIF
   ENDIF

   IF ! ::lAllowEdit .AND. lFromUser
      RETURN .F.
   ENDIF

   IF ! ::IsEditable() .OR. ::nEditMode == beEDIT // ya estaba en edición
      RETURN .F.
   ENDIF

   IF ::nEditMode == beREAD
      ::nEditMode := beEDIT
   ENDIF

   ::nRowSel := Min( ::nRowSel, ::RowCount() )

   ::lMultiEdit := .T.

   FOR EACH oCol IN ::aCols
      IF oCol:IsVisible()
         IF oCol:Edit(, .T. )
            IF oGridEdit == Nil .AND. ::IsColVisible( oCol )
               oGridEdit := oCol:oGridEdit
            ENDIF
         ENDIF
      ENDIF
   NEXT

   IF oGridEdit != Nil
      oGridEdit:SetFocus()
   ELSE
      IF ! ::ChangeFocus( ::SelectedCol(), .F. )
         IF ! ::ChangeFocus( ::SelectedCol(), .T. )
            ::lMultiEdit := .F.
            ::nEditMode := beREAD
         ENDIF
      ENDIF
   ENDIF

RETURN .T.

//--------------------------------------------------------------------------

METHOD Append( lFromUser ) CLASS XBrowse

   LOCAL xRet

   DEFAULT lFromUser TO .F.

   IF ::lEditMode .OR. ! ::IsEditable() .OR. (! ::lAllowAppend .AND. lFromUser )
      RETURN .F.
   ENDIF

   IF Valtype( xRet := ::OnAppend() ) == "L" .AND. !xRet
      RETURN .F.
   ENDIF

   IF ::nDataRows < ::RowCount()
      ::DrawLine( Nil, .F. )
      ::Skip( ::nDataRows - ::nRowSel )
      ::nKeyNo += ( ::nDataRows - ::nRowSel )
      ::nRowSel := Max( 1, ::nDataRows  )
   ELSE
      ::GoBottom()
      ::DrawLine( Nil, .F. )
   ENDIF

   ::nEditMode := beAPPEND

   IF ::nRowSel >= ::RowCount()
      ::Redraw()
   ENDIF

   ::nPhantomRow := ::nRowSel + 1

   ::RowEdit( lFromUser )
   ::Refresh()

RETURN .T.

//--------------------------------------------------------------------------

METHOD Insert( lFromUser ) CLASS XBrowse

   LOCAL xRet

   DEFAULT lFromUser TO .F.

   xRet := ::OnInsert()

   IF Valtype( xRet ) == "L" .AND. !xRet
      RETURN .F.
   ENDIF

   IF ! ::lAllowInsert .AND. lFromUser
      RETURN .F.
   ENDIF

   IF ::lEditMode .OR. ! ::IsEditable()
      RETURN .F.
   ENDIF

   ::DrawLine( Nil, .F. )

   ::nEditMode := beINSERT

   ::nPhantomRow := ::nRowSel

   ::Refresh()
   ::Edit( lFromUser )

RETURN .T.

//--------------------------------------------------------------------------

METHOD IsEditable() CLASS XBrowse

   LOCAL oCol

   FOR EACH oCol IN ::aCols
      IF oCol:nPos > 0 .AND. oCol:nEditType != beNONE
         RETURN .T.
      ENDIF
   NEXT

RETURN .F.

//--------------------------------------------------------------------------

METHOD SetRecSel( lRecSel, lRefresh ) CLASS XBrowse

   DEFAULT lRefresh TO .T.

   ::FlRecordSelector := lRecSel

   IF lRecSel
      IF ::oRecSel == Nil
         ::oRecSel := TBrwRecSel():Create( Self )
         ::oRecSel:Adjust()
      ELSE
         ::oRecSel:oParent := Self
      ENDIF
   ELSEIF ::oRecSel != Nil
      ::oRecSel:End()
      ::oRecSel := Nil
   ENDIF

   IF lRefresh .AND. ! Empty( ::Handle )
      ::GetDisplayCols()
      ::Refresh( .T. )
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD Reset() CLASS XBrowse

   AEval( ::aCols, {|v| v:End() } )
   ::FaCols     := {}
   ::nRowSel    := 1
   ::nColSel    := 1
   ::nColOffset := 1
   ::nDataRows  := 0
   ::nKeyNo     := 0
   ::nLen       := 0
   ::GetDisplayCols()

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetRowDividerStyle( nStyle ) CLASS XBrowse

   ::FnRowDividerStyle := nStyle

   IF ::hRowPen != Nil
      DeleteObject( ::hRowPen )
      ::hRowPen := Nil
   ENDIF

   DO CASE
      CASE nStyle == blBLACK
         ::hRowPen := CreatePen( PS_SOLID, 1, clBlack )
      CASE nStyle == blDARKGRAY
         ::hRowPen := CreatePen( PS_SOLID, 1, clGray )
      CASE nStyle == blFORECOLOR
         ::hRowPen := CreatePen( PS_SOLID, 1, ::nClrText )
      CASE nStyle == blLIGHTGRAY
         ::hRowPen := CreatePen( PS_SOLID, 1, clLtGray )
      CASE nStyle == blRAISED .OR. nStyle == blINSET
         ::hRowPen := CreatePen( PS_SOLID, 1, RGB( 199, 197, 198 ) )
   END CASE

return nil

//--------------------------------------------------------------------------

METHOD SetColDividerStyle( nStyle ) CLASS XBrowse

   ::FnColDividerStyle := nStyle

   IF ::hColPen != Nil
      DeleteObject( ::hColPen )
      ::hColPen := Nil
   ENDIF

   DO CASE
      CASE nStyle == blBLACK
         ::hColPen := CreatePen( PS_SOLID, 1, clBlack )
      CASE nStyle == blDARKGRAY
         ::hColPen := CreatePen( PS_SOLID, 1, clGray )
      CASE nStyle == blFORECOLOR
         ::hColPen := CreatePen( PS_SOLID, 1, ::nClrText )
      CASE nStyle == blLIGHTGRAY
         ::hColPen := CreatePen( PS_SOLID, 1, clLtGray )
      CASE nStyle == blRAISED .OR. nStyle == blINSET
         ::hColPen := CreatePen( PS_SOLID, 1, RGB( 199, 197, 198 ) )
   END CASE

RETURN Nil

//--------------------------------------------------------------------------

METHOD SelectCellFromPoint( x, y ) CLASS XBrowse

   LOCAL nRowPos, nColPos
   LOCAL lChanged

   IF ::SetFocus()
      ::CancelEdit()
      ::Seek()
   ENDIF

   nRowPos  := ::MouseRowPos( y )
   nColPos  := ::MouseColPos( x )
   lChanged := .F.

   IF nRowPos > 0 .or. nColPos > 0
      ::DrawLine()

      IF nRowPos > 0 .AND. ( nRowPos - ::nRowSel ) != 0
         ::nKeyNo += ::Skip( nRowPos - ::nRowSel )
         ::nRowSel := nRowPos
         ::OnChange( .T. )
      ENDIF

      IF nColPos > 0 .AND. ::nMarqueeStyle < bmHIGHLROW .AND. ::nColSel != nColPos
         ::nColSel := nColPos
         ::OnChange( .F. )
      ENDIF

      ::DrawLine( Nil, .T. )

      IF ::GetVScroll()
         ::SetVPos( ::nKeyNo )
      ENDIF

      IF ::GetHScroll()
         ::SetHScroll( .T. )
      ENDIF

      RETURN ( nRowPos > 0 .AND. nColPos > 0 )
   ENDIF

RETURN .F.

//--------------------------------------------------------------------------

METHOD SetRedraw( lRedraw ) CLASS XBrowse

   IF lRedraw
      ::GetDisplayCols()
   ENDIF

RETURN ::Super:SetRedraw( lRedraw )

//--------------------------------------------------------------------------

METHOD SetDataLines( Value ) CLASS XBrowse

   ::FnDataLines := Value

   AEval( ::aCols, {|v| v:DefStyle() } )

   IF Empty( ::nRowHeight )
      ::SetRowHeight( 0 )
   ENDIF

   IF ! Empty( ::Handle ) .AND. ::lRedraw
      ::Refresh()
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD ChangeFocus( oFrom, lBack ) CLASS XBrowse

   LOCAL oCol, oFst, oLst
   LOCAL nPos, nLen, nLast, nFor, nTmp
   LOCAL lUpd

   nPos := ::aDisplay[ oFrom:nPos ]
   nLen := Len( ::aCols )
   lUpd := .F.

	// Primera columan editable
	FOR nFor := 1 TO nLen
		WITH OBJECT ::aCols[ nFor ]
			IF :IsVisible() .AND. :nEditType != beNONE .AND. :oGridEdit:lEnabled //.AND. :oGridEdit:lVisible
				oFst := :__WithObject()
				EXIT
			ENDIF
		END WITH
	NEXT

	//Ultima columna editable
	FOR nFor := nLen TO 1 STEP -1
		WITH OBJECT ::aCols[ nFor ]
			IF :IsVisible() .AND. :nEditType != beNONE .AND. :oGridEdit:lEnabled //.AND. :oGridEdit:lVisible
				oLst := :__WithObject()
				EXIT
			ENDIF
		END WITH
	NEXT

   IF ! lBack
      nPos ++
		// Buscamos siguiente columna editable
      DO WHILE nPos <= nLen
         WITH OBJECT ::aCols[ nPos ]
            IF :IsVisible() .AND. :nEditType != beNONE .AND. :oGridEdit:lEnabled //.AND. :oGridEdit:lVisible
               oCol := :__WithObject()
               EXIT
            ENDIF
         END WITH
         nPos++
      ENDDO

		// Sino se encontro ninguna columna ir a la primera editable
      IF oCol == Nil .AND. oFst != NIL
         ::GoLeftMost()
         oCol := oFst
         lUpd := .T.
      ENDIF

		// Forzar que la columna este completamente visible
      IF oCol != Nil
         DO WHILE oCol:nPos == 0
            ::GoRight( .T. )
            lUpd := .T.
         ENDDO
			nTmp := ::nColOffset
			DO WHILE ! ::IsColVisible( oCol, .T. ) .AND. ! ( oCol == oFst )
            ::GoRight( .T. )
         	IF nTmp == ::nColOffset
					EXIT
            ELSE
                nTmp := ::nColOffset
				ENDIF
            lUpd := .T.
         ENDDO
      ENDIF

		// Actualizar la posición de los GridEdits
      IF lUpd
         ::UpdGridEdits()
      ENDIF

		// Cambiar el foco
      IF oCol != Nil .AND. !( oCol == oFrom )
         oCol:oGridEdit:SetFocus()
         RETURN .T.
      ENDIF
   ELSE
      nPos--
		// Buscamos anterior columna editable
      DO WHILE nPos >= 1
         WITH OBJECT ::aCols[ nPos ]
            IF :IsVisible() .AND. :nEditType != beNONE .AND. :oGridEdit:lEnabled //.AND. :oGridEdit:lVisible
               oCol := :__WithObject()
               EXIT
            ENDIF
         END WITH
         nPos--
      ENDDO

		// Sino se encontro ninguna nos vamos a la ultima editable
      IF oCol == Nil
         oCol := oLst
         lUpd := .T.
			nTmp := ::nColOffset
         DO WHILE oCol != NIL .and. ! ::IsColVisible( oCol, .T. ).AND. ! ( oCol == oFst )
            ::GoRight( .T. )
         	IF nTmp == ::nColOffset
					EXIT
            ELSE
                nTmp := ::nColOffset
				ENDIF
         ENDDO
      ENDIF

		// Forzar que este completamente visible
      IF oCol != Nil
         DO WHILE oCol:nPos == 0 .AND. ::nColOffset > 1
            ::GoLeft( .T. )
            lUpd := .T.
         ENDDO
			nTmp := ::nColOffset
         DO WHILE ! ::IsColVisible( oCol, .T. ) .AND. ::nColOffset > 1
            ::GoLeft( .F. )
            lUpd := .T.
         	IF nTmp == ::nColOffset
					EXIT
            ELSE
                nTmp := ::nColOffset
				ENDIF
         ENDDO
         IF lUpd
            ::UpdGridEdits()
         ENDIF
         IF !( oCol == oFrom )
            oCol:oGridEdit:SetFocus()
            RETURN .T.
         ENDIF
      ENDIF
   ENDIF

RETURN .F.

//--------------------------------------------------------------------------

METHOD UpdGridEdits() CLASS XBrowse

   LOCAL oCol

   IF ::nEditMode != beREAD .AND. !::lOnPostEdit
      FOR EACH oCol IN ::aCols
         IF ::IsColVisible( oCol )
            oCol:SetGridEditBounds()
            IF oCol:nEditType != beNONE .AND. ! oCol:oGridEdit:lVisible
               oCol:oGridEdit:lVisible := .T.
               oCol:lEdited := .T.
            ENDIF
         ELSE
            IF oCol:oGridEdit:lVisible
               oCol:oGridEdit:lVisible := .F.
            ENDIF
         ENDIF
      NEXT
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD GetFreeze() CLASS XBrowse

   IF ::lEditMode
      RETURN 0
   ENDIF

RETURN ::FnFreeze

//--------------------------------------------------------------------------

METHOD AdjustCols() CLASS XBrowse

   Aeval( ::aCols, {|oCol| ::AdjustColWidth( Nil, oCol ) } )

RETURN Nil

//--------------------------------------------------------------------------

METHOD StretchCols() CLASS XBrowse

   DO WHILE Len( ::GetDisplayCols( .F. ) ) < Len( ::aCols ) .AND. !::LastColVisible()
      Aeval( ::aCols, {|oCol| oCol:nWidth *= 0.90 } )
   ENDDO

   ::UpdateDisplayCols()

RETURN Nil

//--------------------------------------------------------------------------

METHOD ToExcel( cFile, lXlsNumeric ) CLASS XBrowse

   LOCAL oExcel, oWorkBook, oSheet, oCol, oClp, oError
   LOCAL cHeader, cData,cCol
   LOCAL nBookMark, nAlign, nAt, nLine, nLines, nCol, nCols, nAdj
   LOCAL lState

   DEFAULT lXlsNumeric TO .T.

   IF Len( ::aCols ) == 0
      RETURN .F.
   ENDIF

   TRY
      oExcel := GetActiveObject( "Excel.Application" )
   CATCH
      TRY
         oExcel := CreateObject( "Excel.Application" )
      CATCH
         MsgAlert( "ERROR! Excel not available. [" + Ole2TxtError()+ "]" )
         RETURN .F.
      END
   END

   TRY
      oWorkBook := oExcel:Workbooks:Add()
      oSheet    := oWorkBook:WorkSheets( 1 )
   CATCH
      MsgAlert( "ERROR! Excel worksheet not available. [" + Ole2TxtError()+ "]" )
      oExcel := NIL
      ::oForm:Close()
      RETURN .F.
   END

   nCol  := 0
   nCols := 0
   cCol  := ""

   TRY

   // Print Column titles

   FOR EACH oCol IN ::aCols
      WITH OBJECT oCol
         IF oCol:lVisible
            nCol ++
            nAlign := :nAlignment
            cHeader := :cHeader
            WITH OBJECT oSheet:Cells( 1, nCol )
               :Value := cHeader
               IF nAlign == taRIGHT .OR. nAlign == taLEFTHEADERRIGHT .OR. nAlign == taCENTERHEADERRIGHT
                  :HorizontalAlignment := 4
               ELSEIF nAlign == taCENTER .OR. nAlign == taLEFTHEADERCENTER .OR. nAlign == taRIGHTHEADERCENTER
                  :HorizontalAlignment := 3
               ENDIF
            END WITH

            IF ( nAdj := Int( nCol / 27 ) ) > 0
               cCol := Chr( 64 + nAdj ) + Chr( 65 + ( nCol % 27 ) )
            ELSE
               cCol := Chr( 64 + nCol )
            ENDIF

            IF lXlsNumeric .AND. (nAlign == taRIGHT .OR. nAlign == taRIGHTHEADERCENTER .OR. nAlign == taRIGHTHEADERLEFT)
               WITH OBJECT oSheet:Columns( cCol )
                  :HorizontalAlignment := 4
                  IF ( nAt := At( ".", oCol:cPicture ) ) > 0
                     :NumberFormat := "#.##0," + Replicate( "0", Len( oCol:cPicture ) - nAt )
                  ENDIF
               END WITH
            ENDIF
         ENDIF
      END WITH

   NEXT

   nCols := nCol

   // Format column titles

   WITH OBJECT oSheet:Range( "A1:" + cCol + "1" )
      :Font:Bold := .T.
      :Font:Size := 8
      :Interior:Color := RGB( 192, 192, 192 )
   END WITH

   oClp := TClipboard():Create( ::oParent )

   lState := ::oParent:lEnabled

   ::oParent:lEnabled := .F.
   Application:lBusy := .T.
   nBookMark := ::BookMark()

   ::GoTop()

   cData  := ""
   nLine  := 2
   nLines := 0

   DO WHILE .T.
      nCol := 0
      FOR EACH oCol IN ::aCols
         IF oCol:lVisible
            nCol ++
            cData += XlsData( oCol, lXlsNumeric ) + IIF( nCol < nCols, Chr(9), "" )
         ENDIF
      NEXT
      cData += CRLF
      nLines ++

      IF Len( cData ) > 65000
         oClp:Empty()
         oClp:SetText( cData )
         PasteExcel( oSheet, "A" + lTrim( Str( nLine ) ) )
         nLine += nLines
         nLines := 0
         cData := ""
         oClp:Empty()
      ENDIF

      IF ::Skip() == 0
         EXIT
      ENDIF

   ENDDO

   IF !Empty( cData )
      oClp:Empty()
      oClp:SetText( cData )
      PasteExcel( oSheet, "A" + lTrim( Str( nLine ) ) )
   ENDIF

   // Last formatting

   oSheet:Columns( "A:" + Chr( 65 + nCols ) ):AutoFit()

   // Exit

   IF !Empty( cFile )
      oWorkBook:SaveAs( cFile )
      oWorkBook:Close()
   ELSE
      oExcel:Visible := .T.
   ENDIF

   CATCH oError
      MsgStop( "EXCEL error: " + oError:Description)
   END

   oSheet    := NIL
   oWorkBook := NIL
   oExcel    := NIL

   IF oClp != NIL
      oClp:End()
   ENDIF

   ::BookMark(nBookMark)

   ::oParent:lEnabled := .T.
   Application:lBusy := .F.

RETURN .T.

//------------------------------------------------------------------------------

STATIC PROCEDURE PasteExcel( oSheet, cRange )

   LOCAL lError := .T., nRetry := 0, oError

   WHILE lError .AND. nRetry++ < 5
      TRY
         WITH OBJECT oSheet
            :Paste( :Range( cRange ) )
         END WITH
         lError := .F.
      CATCH oError
         Sleep( 100 )
      END
   ENDDO
   IF lError
      Eval( ErrorBlock(), oError )
   ENDIF

RETURN


//--------------------------------------------------------------------------

STATIC FUNCTION XlsData( oCol, lXlsNumeric )

   LOCAL xValue
   LOCAL cText, cType

   xValue := oCol:GetData()
   cType  := Valtype( xValue )

   IF lXlsNumeric .AND. cType == "N"
      cText  := XlsStr( xValue, oCol:cPicture )
   ELSEIF cType == "H"
      cText := "<JSON>"
   ELSE
      IF cType == "C"
         xValue := StrTran( xValue, CRLF, "\r\n" )
         xValue := StrTran( xValue, Chr( 9 ), "\t" )
      ENDIF
      cText := Transform( xValue, oCol:cPicture )
   ENDIF

RETURN AllTrim( cText )

//--------------------------------------------------------------------------

STATIC FUNCTION XlsStr( nValue, cPicture )

   STATIC lComaDecSep

   LOCAL cValue, cChar
   LOCAL nLen, nDec
   LOCAL lDec

   IF lComaDecSep == NIL
      lComaDecSep := ( Application:DecimalSeparator() == "," )
   ENDIF

   nLen := 0
   nDec := 0
   lDec := .F.

   FOR EACH cChar IN cPicture
      SWITCH cChar
      CASE "9"
         IF lDec
            nDec ++
         ENDIF
         nLen ++
         EXIT
      CASE "."
         lDec := .T.
         nLen ++
      END
   NEXT

   cValue := Str( nValue, nLen, nDec )

   IF lComaDecSep .AND. lDec
      XA_SetCharAtPos( @cValue, nLen - nDec, "," )
   ENDIF

RETURN Ltrim( cValue )

//--------------------------------------------------------------------------

STATIC FUNCTION IsSpecialChar( nKey )

RETURN Chr( nKey ) $ ( " .:,;-_¿¡'?=)(/&%$!ºª*Çç¨{}\|@#[]+<>" + Chr( 32 ) )

//--------------------------------------------------------------------------

#pragma BEGINDUMP

#include <windows.h>
#include <commctrl.h>
#include <xailer.h>

//--------------------------------------------------------------------------

XA_FUNC( XA_SETCHARATPOS )
{
   PHB_ITEM oItem = hb_param( 1, HB_IT_ANY );
   char *cRet = (LPSTR) hb_itemGetCPtr( oItem );
   cRet += hb_parnl( 2 ) - 1;
   *cRet = *hb_parc( 3 );
   hb_ret();
}

#pragma ENDDUMP
