/*
 * Xailer source code:
 *
 * DbfBrowse.prg
 * Clase TDbfBrowse()
 *
 * Copyright 2003, 2007 Ignacio Ortiz de Zúñiga
 * Copyright 2003, 2007 Xailer.com
 * All rights reserved
 *
 */

#include "Xailer.ch"
#include "dbstruct.ch"
#include "error.ch"

#define COMPILAR(cExpr) &("{||" + cExpr + "}")


//--------------------------------------------------------------------------

CLASS XDbfBrowse FROM TBrowse

PUBLISHED:
   PROPERTY aCols AS TDbfBrwColumn[]

PUBLIC:
   DATA cAlias INIT "" READONLY

PROTECTED:
   DATA cOrgFilter
   DATA lOnFilter  INIT .F.

   METHOD SlowFilter()

PUBLIC:
   METHOD Create( oParent ) CONSTRUCTOR
   METHOD SetDbf( cAlias, aHeaders )
   METHOD Free()
   METHOD AddCol( oCol, lRefresh ) // --> oColumn
   METHOD InsCol( nIndex, oCol, lRefresh ) // --> oColumn
   METHOD IsEof() // --> lResult
   METHOD IsBof() // --> lResult
   METHOD BookMark( nPos ) // --> nId
   METHOD KeyNo( nPos ) // --> Nil | nPosition
   METHOD KeyCount( nPos ) // --> nCount

RESERVED:
   METHOD MoveTop()
   METHOD MoveBottom()
   METHOD Skip( nSkip )
   METHOD AdjustColWidth( oSender, xCol )
   METHOD SetFilter( nTrigger )
   METHOD CancelEdit( lAbort, nKey )
   METHOD Delete( lFromUser )

   METHOD FilterEdit( oSender, nHeaderItem )

ENDCLASS

//--------------------------------------------------------------------------

METHOD Create( oParent ) CLASS XDbfBrowse

   ::Super:Create( oParent )

   IF ! Empty( ::cAlias ) .OR. Len( ::aCols ) > 0
      ::SetDbf( ::cAlias )
   ENDIF

RETURN Self

//--------------------------------------------------------------------------

METHOD Free() CLASS XDbfBrowse

   IF ! Empty( ::cAlias )
      IF ! Empty( ::cOrgFilter )
         IF ::cOrgFilter != ( ::cAlias )->( DbFilter() )
            ( ::cAlias )->( DbSetFilter( COMPILAR( ::cOrgFilter ), ::cOrgFilter ) )
         ENDIF
      ELSEIF ! Empty( ( ::cAlias )->( DbFilter() ) )
         ( ::cAlias )->( DbClearFilter() )
      ENDIF
   ENDIF

RETURN ::Super:Free()

//--------------------------------------------------------------------------

METHOD AddCol( oCol, lRefresh ) CLASS XDbfBrowse

   IF oCol == Nil
      oCol := TDbfBrwColumn():New( Self )
   ENDIF

RETURN ::Super:AddCol( oCol, lRefresh )

//--------------------------------------------------------------------------

METHOD InsCol( nIndex, oCol, lRefresh ) CLASS XDbfBrowse

   IF oCol == Nil
      oCol := TDbfBrwColumn():New( Self )
   ENDIF

RETURN ::Super:InsCol( nIndex, oCol, lRefresh )

//--------------------------------------------------------------------------

METHOD SetFilter( nTrigger ) CLASS XDbfBrowse

   LOCAL xFilterEval
   LOCAL oCol
   LOCAL cSetFilter, cFilter, cType
   LOCAL nFor, nLen
   LOCAL lCodeBlocks, lRefresh
   LOCAL bFilter

   IF ::cOrgFilter == Nil
      ::cOrgFilter := ( ::cAlias )->( DbFilter() )
   ENDIF

   cSetFilter  := ::cOrgFilter
   lCodeBlocks := .F.
   lRefresh    := .F.

   IF Empty( ::cAlias ) .OR. ::lOnFilter
      RETURN Nil
   ENDIF

   IF ! ::lFilterBar
      IF Empty( ::cOrgFilter )
         ( ::cAlias )->( DbClearFilter() )
      ELSE
         ( ::cAlias )->( DbSetFilter( COMPILAR( ::cOrgFilter ), ::cOrgFilter ) )
      ENDIF
      ::OnFilterChange( nTrigger )
      ::OnChange( .T. )
      ::Refresh( .T. )
      RETURN Nil
   ENDIF

   ::lOnFilter := .T.

   IF ! Empty( nTrigger )
      oCol := ::ColAtPos( nTrigger )
   ENDIF

   IF oCol != Nil
      cType := oCol:cFieldType
      IF Empty( oCol:FilterEval )
         WITH OBJECT oCol
            DO CASE
               CASE cType == "C" .OR. cType == "M"
                  :FilterEval := "Upper( '%' ) $ Upper( " + :cFieldName + " )"
               CASE cType == "N"
                  :FilterEval := :cFieldName + " =  %"
               CASE cType == "D"
                  :FilterEval := :cFieldName + " =  CtoD( '%' )"
               CASE cType == "L"
                  :FilterEval := :cFieldName + " == ( Left( Upper( '%' ), 1 ) != 'N' )"
            ENDCASE
         END WITH
      ENDIF
   ENDIF

   IF oCol != Nil
      WITH OBJECT oCol
         cType := :cFieldType
         IF cType == "N" .AND. ! IsStringNumeric( :cFilter )
            MsgBeep()
            oCol:ClearFilter()
         ENDIF
      END WITH
   ENDIF

   nLen := len( ::aCols )

   FOR nFor := 1 TO nLen
      oCol        := ::aCols[ nFor ]
      cFilter     := oCol:cFilter
      xFilterEval := oCol:FilterEval
      IF ! Empty( cFilter ) .AND. xFilterEval != Nil
         IF ValType( xFilterEval ) == "B"
            lCodeBlocks := .T.
            EXIT
         ELSE
            xFilterEval := StrTran( xFilterEval, "%", cFilter )
            cSetFilter  += IIf( !Empty( cSetFilter ), " .AND. ", "" ) + xFilterEval
         ENDIF
      ENDIF
   NEXT

   IF lCodeBlocks
      bFilter := &("{||" + ::cOrgFilter + "}")
      ( ::cAlias )->( DbSetFilter( {|| Eval( bFilter ) .AND. ::SlowFilter() }, "::SlowFilter()" ) )
      lRefresh := .T.
   ELSE
      IF !Empty( cSetFilter )
         ( ::cAlias )->( DbSetFilter( &( " {|| " + cSetFilter + " }" ), cSetFilter ) )
         lRefresh := .T.
      ELSE
         lRefresh := ! Empty( ( ::cAlias )->( DbFilter() ) )
         IF Empty( ::cOrgFilter )
            ( ::cAlias )->( DbClearFilter() )
         ELSE
            ( ::cAlias )->( DbSetFilter( COMPILAR( ::cOrgFilter ), ::cOrgFilter ) )
         ENDIF
      ENDIF
   ENDIF

   ::OnFilterChange( nTrigger )
   ::OnChange( .T. )

   IF lRefresh
      ( ::cAlias )->( DbGotop() )
      ::KeyCount()
      ::Refresh( .T. )
   ENDIF

   ::lOnFilter := .F.

RETURN Nil

//--------------------------------------------------------------------------

METHOD SlowFilter() CLASS XDbfBrowse

   LOCAL oCol
   LOCAL bFilter
   LOCAL cFilter
   LOCAL nLen, nFor

   nLen := Len( ::aCols )

   FOR nFor := 1 TO nLen
      oCol    := ::aCols[ nFor ]
      cFilter := oCol:cFilter
      bFilter := oCol:FilterEval
      IF ! Empty( cFilter ) .AND. ! Empty( bFilter ) .AND. ! Eval( bFilter, Self, cFilter )
         RETURN .F.
      ENDIF
   NEXT

RETURN .T.

//--------------------------------------------------------------------------

METHOD MoveTop() CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   xRet := ::OnGoTop()

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         ( cAlias )->( DbGoTop() )
      ELSE
         ::Super:MoveTop()
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD MoveBottom() CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   xRet := ::OnGoBottom()

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         ( cAlias )->( DbGoBottom() )
      ELSE
         ::Super:MoveBottom()
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD Skip( nSkip ) CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   DEFAULT nSkip TO 1

   xRet := ::OnSkip( nSkip )

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         xRet := ( cAlias )->( __DbSkipper( nSkip ) )
      ELSE
         xRet := ::Super:Skip( nSkip )
      ENDIF
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD IsEof() CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   xRet := ::OnEof()

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         xRet := ( cAlias )->( Eof() )
      ELSE
         xRet := ::Super:IsEof()
      ENDIF
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD IsBof() CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   xRet := ::OnBof()

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         xRet := ( cAlias )->( Bof() )
      ELSE
         xRet := ::Super:IsBof()
      ENDIF
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD BookMark( nPos ) CLASS XDbfBrowse

   LOCAL xRet
   LOCAL cAlias

   xRet := ::OnBookMark( nPos )

   IF xRet == Nil
      cAlias := ::cAlias
      IF ! Empty( cAlias )
         xRet := ( cAlias )->( RecNo() )
         IF nPos != Nil
            ( cAlias )->( DbGoto( nPos ) )
         ENDIF
      ELSE
         xRet := ::Super:BookMark()
         IF nPos != Nil
            ::Super:BookMark( nPos )
         ENDIF
      ENDIF
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD KeyNo( nPos ) CLASS XDbfBrowse

   LOCAL xRet

   xRet := ::OnKeyNo( nPos )

   IF xRet == Nil
      IF ! Empty( ::cAlias )
         IF nPos != Nil
            ( ::cAlias )->( OrdKeyGoto( nPos ) )
         ELSE
            xRet := ( ::cAlias )->( OrdKeyNo() )
         ENDIF
      ELSE
         xRet := ::Super:KeyNo( nPos )
      ENDIF
   ENDIF

   IF nPos != Nil
      ::nKeyNo := nPos
   ELSE
      ::nKeyNo := xRet
   ENDIF

RETURN xRet

//--------------------------------------------------------------------------

METHOD KeyCount() CLASS XDbfBrowse

   LOCAL xRet

   xRet := ::OnKeyCount()

   IF xRet == Nil
      IF ! Empty( ::cAlias )
         xRet := ( ::cAlias )->( OrdKeyCount() )
      ELSE
         xRet := ::Super:KeyCount()
      ENDIF
   ENDIF

   ::nLen := xRet

RETURN xRet

//--------------------------------------------------------------------------

METHOD SetDbf( cAlias, aHeaders ) CLASS XDbfBrowse

   LOCAL oCol, oError
   LOCAL aStruc
   LOCAL cField
   LOCAL nFields, nFor, nAt

   IF cAlias != Nil
      ::cAlias := cAlias
   ENDIF

   IF Empty( ::cAlias )
      RETURN Nil
   ENDIF

   aStruc       := (::cAlias)->(DbStruct())
   ::cOrgFilter := (::cAlias)->( DbFilter() )

   IF Len( ::aCols ) == 0
      nFields := IIf( aHeaders == Nil, Len( aStruc ), Len( aHeaders ) )
      FOR nFor := 1 TO nFields
         IF aHeaders == Nil .OR. ValType( aHeaders[ nFor ] ) == "C"
            IF ( nAt := IIf( aHeaders == Nil, nFor, AScan( aStruc, {| aField | aField[ DBS_NAME ] == Upper( aHeaders[ nFor ] ) } ) ) ) > 0
               WITH OBJECT oCol := TDbfBrwColumn():New( Self )
                  :cHeader    := aStruc[ nAt, DBS_NAME ]
                  :nAlignment := IIf( aStruc[ nAt, DBS_TYPE ] == "N", taRIGHT, taLEFT )
                  :nField     := nAt
                  :cFieldType := aStruc[ nAt, 2 ]
                  :nFieldLen  := aStruc[ nAt, 3 ]
                  :nFieldDec  := aStruc[ nAt, 4 ]
                  :Create()
               END WITH
            ENDIF
         ELSE
            oCol := TDbfBrwColumn():New( Self )
            oCol:nAlignment := IIf( ValType( Eval( aHeaders[ nFor ] ) ) == "N", taRIGHT, taLEFT )
            oCol:Create()
         ENDIF
      NEXT
   ELSE
      FOR nFor := 1 TO Len( ::aCols )
         WITH OBJECT ::aCols[ nFor ]
            IF ! :IsKindOf( "TDbfBrwColumn" )
               oError             := ErrorNew()
               oError:Subsystem   := "XAILER:SetDbf"
               oError:Severity    := ES_ERROR
               oError:Description := "Incorrect Browse Column type, should be KindOf('TDbfBrwColumn') "
               oError:Operation   := "Column: " + Ltrim( Str( nFor ) )
               Eval( ErrorBlock(), oError )
            ENDIF
            IF ! Empty( :cFieldName )
               cField := Upper( :cFieldName )
               nAt := Ascan( aStruc, {|v| v[ 1 ] == cField } )
               IF nAt > 0
                  :nField     := nAt
                  :cFieldType := aStruc[ nAt, 2 ]
                  :nFieldLen  := aStruc[ nAt, 3 ]
                  :nFieldDec  := aStruc[ nAt, 4 ]
               ENDIF
            ENDIF
         END WITH
      NEXT
   ENDIF

   ::GetDisplayCols()

   IF ! Empty( ::Handle )
      ::Refresh( .T. )
   ENDIF

RETURN Self

//--------------------------------------------------------------------------

METHOD AdjustColWidth( oSender, xCol ) CLASS XDbfBrowse

   LOCAL oCol
   LOCAL nWidth, nMaxRows, nRowPos, nTemp

   IF !::lAllowColSizing
      RETURN Nil
   ENDIF

   IF Valtype( xCol ) == "N"
      IF ::lRecordSelector
         xCol--
      ENDIF

      IF Empty( xCol )
         RETURN Nil
      ENDIF

      oCol := ::ColAtPos( xCol )
   ELSE
      oCol := xCol
   ENDIF

   IF oCol == Nil
      RETURN Nil
   ENDIF

   nWidth := 10

   IF ::oHeader != Nil
      nWidth := oCol:GetWidth( .t. )
      nWidth := ::oHeader:oFont:GetTextWidth( oCol:cHeader, ::oHeader ) + 10
   ENDIF

   nMaxRows := ::RowCount()
   nRowPos  := 1
   nTemp    := 1 - Min( ::nRowSel, nMaxRows )

   IF nTemp != 0
      ::Skip( 1 - Min( ::nRowSel, nMaxRows ) )
   ENDIF

   DO WHILE nRowPos <= nMaxRows
      nWidth := Max( nWidth, oCol:DataWidth() )
      IF ::Skip() == 0
         EXIT
      ENDIF
      nRowPos++
   ENDDO

   ::Skip( ::nRowSel - nRowPos )

   nWidth += 10

   oCol:nWidth := nWidth

RETURN Nil

//--------------------------------------------------------------------------

METHOD CancelEdit( lAbort, nKey ) CLASS XDbfBrowse

   LOCAL oCol
   LOCAL nEditMode := ::nEditMode

   IF ! ::lEditMode
      RETURN Nil
   ENDIF

   ::Super:CancelEdit( @lAbort, nKey, .F. )

   IF ! lAbort .AND. ::lAutoSave
      IF nEditMode >= beAPPEND
         ( ::cAlias )->( DbAppend() )
         IF NetErr()
            lAbort := .T.
         ENDIF
      ELSE
         lAbort := !( ::cAlias )->( DbRLock() )
      ENDIF
   ENDIF

   FOR EACH oCol IN ::aCols
      WITH OBJECT oCol
         IF :lEdited
            IF !lAbort .AND. ::lAutoSave
               ( ::cAlias )->( FieldPut( :nField, :oGridEdit:Value ) )
            ENDIF
            :lEdited := .F.
         ENDIF
      END WITH
   NEXT

   IF ! lAbort .AND. ::lAutoSave
      ( ::cAlias )->( DbrUnlock() )
   ENDIF

   IF nEditMode = beAPPEND .AND. !lAbort
      ::nRowSel++
      ::nLen++
      //::Skip()
      ::OnChange( .T. )
   ENDIF

   ::Redraw()

RETURN Nil

//--------------------------------------------------------------------------

METHOD Delete( lFromUser ) CLASS XDbfBrowse

   LOCAL xRet

   DEFAULT lFromUser TO .F.

   IF ! ::lAllowDelete .AND. lFromUser
      RETURN .F.
   ENDIF

   xRet := ::OnDelete()

   IF Valtype( xRet ) == "L" .AND. !xRet
      RETURN .F.
   ENDIF

   IF !( ::cAlias )->( DbRLock() )
      RETURN .F.
   ENDIF

   ( ::cAlias )->( DbDelete() )
   ( ::cAlias )->( DbrUnlock() )

   DO WHILE ( ::cAlias )->( Deleted() ) .AND. Set( _SET_DELETED )
      ( ::cAlias )->( DbSkip() )
   ENDDO

   IF ::IsEof()
      ::GoBottom()
   ENDIF

   ::OnChange( .T. )
   ::Refresh()

RETURN .T.

//--------------------------------------------------------------------------

METHOD FilterEdit( oSender, nHeaderItem ) CLASS XDbfBrowse

   LOCAL oCol

   IF ::lRecordSelector
      nHeaderItem--
   ENDIF

   IF nHeaderItem <= 0
      RETURN .F.
   ENDIF

   IF !Empty( oCol := ::ColAtPos( nHeaderItem ) )
      WITH OBJECT oCol
         IF ! :lCanFilter .OR. ( Empty( :FilterEval) .AND. ( Empty( :cFieldName ) .OR. Empty( :cFieldType ) ) )
            IF Empty( ::cMsgNoFilter )
               MsgBeep()
            ELSE
               MsgInfo( ::cMsgNoFilter )
            ENDIF
            ::SetFocus()
            RETURN .F.
         ENDIF
      END
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------
