/*
 * CardBox.prg
 * Clase TCardBox
 *
 * Copyright 2020 Ignacio Ortiz de Zúñiga
 * Copyright 2020 https://Xailer.com
 * All rights reserved
 */

#include "Xailer.ch"
#include "CommCtrl.api"

#define MODE_DELETE     0
#define MODE_SWITCH     1
#define MODE_RANGE      2

//------------------------------------------------------------------------------

CLASS XCardBox FROM TGestureControl

PUBLISHED:
   PROPERTY oImageList     AS TImageList EDITOR PE_ImageList WRITE METHOD SetImageList
   PROPERTY aItems         INIT {} AS TCardItem[] EDITOR PE_StringList
   PROPERTY nWidth         INIT 200
   PROPERTY nHeight        INIT 200
   PROPERTY nAlign         INIT alNONE
   PROPERTY nClrPane       INIT clWindow
   PROPERTY nCardGap       INIT 5;
                           WRITE INLINE ( ::FnCardGap := Value, ::CalcItemsRect( .T. ), Value )
   PROPERTY nCardMargin    INIT 5;
                           WRITE INLINE ( ::FnCardMargin := Value, ::CalcItemsRect( .T. ), Value )
   PROPERTY nCardInnerGap  INIT 0;
                           WRITE INLINE ( ::FnCardInnerGap := Value, ::CalcItemsRect( .T. ), Value )
   PROPERTY nCardClrPane   INIT clWindow EDITOR PE_Color ;
                           WRITE INLINE ( ::FnCardClrPane := Value, ::Refresh(), Value )
   PROPERTY nCardClrHot    INIT clHotLight EDITOR PE_Color ;
                           WRITE INLINE ( ::FnCardClrHot := Value, ::Refresh(), Value )
   PROPERTY nCardClrAct    INIT NIL EDITOR PE_ColorOrNil ;
                           WRITE INLINE ( ::FnCardClrAct := Value, ::Refresh(), Value )
   PROPERTY nCardClrSel    INIT clActiveCaption EDITOR PE_Color ;
                           WRITE INLINE ( ::FnCardClrSel := Value, ::Refresh(), Value )
   PROPERTY nCardClrLink   INIT clBlue EDITOR PE_Color;
                           WRITE INLINE ( ::FnCardClrLink := Value, ::Refresh(), Value )
   PROPERTY nCardSize      INIT 70;
                           WRITE INLINE ( ::FnCardSize := Value, ::CalcItemsRect( .T. ), Value )
   PROPERTY lCardShadow    INIT .F. WRITE METHOD SetCardShadow
   PROPERTY lHotTrack      INIT .F.;
                           WRITE INLINE ( ::FlHotTrack := Value, ::nHotPos := 0, ::Refresh(), Value )
   PROPERTY lGapFirstCard  INIT .T.;
                           WRITE INLINE ( ::FlGapFirstCard := Value, ::CalcItemsRect( .T. ), Value )
   PROPERTY lCardBorder    INIT .T.;
                           WRITE INLINE ( ::FlCardBorder := Value, ::Refresh(), Value )
   PROPERTY lTransparent   INIT .F.
   PROPERTY nOpacity       INIT 0
   PROPERTY lTabStop       INIT .T.
   PROPERTY lMultipleSel   INIT .F. WRITE SetMultipleSel

   PROPERTY nBorderStyle   INIT bvNONE
   PROPERTY nBorderSides
   PROPERTY lHideScrollBars   INIT .F. // Must be set before Create().

   EVENT OnCardPaint( oSender, oItem, BYREF Value, BYREF nClrText, BYREF nClrPane, nPos, lActive, hDC, aRect ) // --> Empty()
   EVENT OnCardClick( oSender, oItem, nPos, nX, nY ) // --> Empty()
   EVENT OnCardDblClick( oSender, oItem, nPos, nX, nY ) // --> Empty()
   EVENT OnCardChange( oSender, nNewPos, nOldPos ) // NIL
   EVENT OnCardSelection( oSender, aSelected ) // --> NIL
   EVENT OnCardGapPaint( oSender, hDC, aRect ) // --> NIL

PUBLIC:
   PROPERTY aData          INIT {} WRITE METHOD SetData
   PROPERTY aSelected      INIT {} WRITE INLINE ( ::FaSelected := Value, ::Refresh() )
   PROPERTY nIndex         INIT 1  WRITE SetIndex

   DATA oLabelEx    AS TLabelEx
   DATA nHotPos     INIT 0 READONLY
   DATA nHotItem    INIT 0 READONLY
   DATA nClickPos   INIT 0 READONLY
   DATA nClickItem  INIT 0 READONLY

   METHOD New( oParent )    CONSTRUCTOR
   METHOD Create( oParent ) CONSTRUCTOR
   METHOD Free() // --> NIL
   METHOD AddItem() INLINE ::InsertItem( Nil ) // --> oCardItem
   METHOD InsertItem( nItem )  // --> oCardItem
   METHOD DeleteItem( nItem ) // --> Nil
   METHOD DeleteItems() // --> NIL
   METHOD RecCount()            INLINE Len( ::aData )
   METHOD HitTest( nX, nY )

RESERVED:
   METHOD CalcItemsRect( lRefresh ) // --> NIL
   METHOD CalcHeight( lRefresh )
   METHOD GetItemRect( nItem )  INLINE ::aItems[ nItem ]:GetRect() // --> aRect
   METHOD ItemIndex( nItem )    INLINE ( nItem == ::nIndex )
   METHOD ItemSelected( nItem )
   METHOD Paint( hDC )  // --> NIL

   METHOD WMPaint( nWParam, nLParam, hWnd )  // --> NIL
   METHOD WMPrintClient()  EXTERN XCardBox_WMPaint()
   METHOD WMEraseBkgnd( nWParam, nLParam, hWnd )   INLINE    1
   METHOD WMLButtonDown( nWParam, nLParam, hWnd )
   METHOD WMLButtonUp( nWParam, nLParam, hWnd )
   METHOD WMLButtonDblClk( nWParam, nLParam, hWnd )
   METHOD WMMouseMove( nWParam, nLParam, hWnd )
   METHOD WMMouseLeave( wParam, lParam, hWnd )
   METHOD WMKeyDown( nKey, nFlags, hWnd )
   METHOD WMSize( nWParam, nLParam )
   METHOD WMSetCursor( nWParam, nLParam )
   METHOD WMMouseWheel( nWParam, nLParam )

   EVENT OnBeforeCardPaint( oSender, nPos ) // used by DBCardBox

PROTECTED:
   PROPERTY lBorder      INIT .F.

   DATA nLastSel     INIT 0
   DATA nLastCount   INIT 0
   DATA nCardShadow  INIT 0
   DATA nLastWidth   INIT 0
   DATA nLastHeight  INIT 0
   DATA hBorderBrush

   PROPERTY cText

   METHOD SetImageList( Value )
   METHOD SetData( aData )
   METHOD SetIndex( Value )
   METHOD FirstVisible()
   METHOD LastVisible()
   METHOD IsCardVisible( nIndex, lFull )
   METHOD CardSpace() INLINE ( ::nCardSize + ::nCardGap )
   METHOD FirstGap()  INLINE IIF( ::lGapFirstCard, ::nCardGap, 0 )
   METHOD SetMultipleSel( Value )
   METHOD SelectItem( nItem, nMode )
   METHOD SetCardShadow( lValue )
   METHOD CreateItem( nItem ) INLINE TCardItem():Create( Self, nItem )

ENDCLASS


//------------------------------------------------------------------------------

METHOD New( oParent ) CLASS XCardBox

   ::Super:New( oParent )
   ::oImageList   := TImageList():Create( Self )
   ::hBorderBrush := CreateSolidBrush( cl3DLight )

RETURN Self

//------------------------------------------------------------------------------

METHOD Create( oParent ) CLASS XCardBox

   ::Super:Create( oParent )
   ::CalcItemsRect()
   ::CalcHeight()

   WITH OBJECT ::oLabelEx := TLabelEx():New( Self )
      :SetBounds(0, 0, 0, 0 )
      :lTransparent := .f.
      :lAutoScroll  := .t.
      :lVisible     := .f.
      :lHideScrollBars := ::lHideScrollBars
      :Create()
   END WITH

RETURN Self

//--------------------------------------------------------------------------

METHOD Free() CLASS XCardBox

   AEval( ::aItems, {|v| v:Free() } )

   IF ::oImageList:oParent == Self
      ::oImageList:End()
      ::oImageList := NIL
   ENDIF

   DeleteObject( ::hBorderBrush )

   IF ::oLabelEx != NIL
      ::oLabelEx:Free()
      ::oLabelEx := NIL
   ENDIF

RETURN ::Super:Free()

//--------------------------------------------------------------------------

METHOD SetImageList( oImageList ) CLASS XCardBox

   IF ::FoImageList != Nil
      ::FoImageList:End()
   ENDIF

   ::FoImageList := oImageList

   IF ::FoImageList != NIL
      ::SendMsg( TB_SETIMAGELIST, 0, ::FoImageList:Handle )
   ENDIF

RETURN oImageList

//--------------------------------------------------------------------------

METHOD SetData( aData ) CLASS XCardBox

   ::FaData      := aData
   ::FnIndex     := 1
   ::FnClientTop := 0
   ::CalcHeight( .T. )

RETURN aData

//------------------------------------------------------------------------------

METHOD InsertItem( nItem ) CLASS XCardBox

   LOCAL oItem, oPrev

   oItem := ::CreateItem( nItem )

   IF oItem:nItem > 1
      oPrev := ::aItems[ oItem:nItem - 1 ]
      oItem:nAlign := oPrev:nAlign
      oItem:nAlignWeight := oPrev:nAlignWeight
      oItem:nSiblingWeight := oPrev:nSiblingWeight
   ENDIF

RETURN oItem

//--------------------------------------------------------------------------

METHOD DeleteItem( nItem ) CLASS XCardBox

   LOCAL nLen
   LOCAL lOk

   nLen := Len( ::aItems )
   lOk  := .F.

   IF nItem > 0 .AND. nItem <= nLen
      ::aItems[ nItem ]:End()
      hb_ADel( ::aItems, nItem, .t. )
      nLen := Len( ::aItems )
      WHILE nItem <= nLen
         ::aItems[ nItem ]:nColumn--
         ::aItems[ nItem ]:nItem--
         nItem++
      ENDDO
      ::CalcItemsRect( .T. )
      lOk := .T.
   ENDIF

RETURN lOk

//--------------------------------------------------------------------------

METHOD DeleteItems() CLASS XCardBox

   AEval( ::aItems, {| oItem | oItem:End() } )
   ::FaItems := {}
   ::CalcItemsRect( .T. )

RETURN Nil

//--------------------------------------------------------------------------

METHOD WMSize( nWParam, nLParam ) CLASS XCardBox

   LOCAL nWidth, nHeight

   nWidth  := LoInt( nLParam )
   nHeight := HiInt( nLParam )

   IF ::oLabelEx != NIL .AND. ::oLabelEx:lVisible
      ::oLabelEx:lVisible := .F.
   ENDIF

   IF nHeight != ::nLastHeight
      ::CalcHeight( .F. )
   ENDIF

   IF nWidth != ::nLastWidth
      ::CalcItemsRect( .F. )
   ENDIF

   ::nLastWidth := nWidth
   ::nLastHeight := nHeight

RETURN ::Super:WMSize( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD CalcHeight( lRefresh ) CLASS XCardBox

   DEFAULT lRefresh TO .F.

   ::nVirtualHeight := Max( ::RecCount() * ( ::CardSpace() ) + ::FirstGap(), ::nClientHeight )

   IF lRefresh .AND. ::Handle != 0
      ::Refresh( .t. )
   ENDIF

RETURN NIL

//------------------------------------------------------------------------------

METHOD CalcItemsRect( lRefresh ) CLASS XCardBox

   LOCAL oItem AS CLASS TCardItem
   LOCAL aRect, aLast
   LOCAL nPos, nLast, nSize
   LOCAL lExit, lOnSibling

   DEFAULT lRefresh TO .F.

   aRect := { 0, 0, ::nClientWidth - ( ::nCardMargin * 2 ), ::nCardSize }
   aLast := {}
   nLast := alNONE
   nSize := 0

   IF RectWidth( aRect ) <= 0 .OR. RectHeight( aRect ) <= 0
      FOR EACH oItem IN ::aItems
         oItem:SetRect( {0,0,0,0} )
      NEXT
      RETURN nil
   ENDIF

   IF ::lCardBorder
      InflateRect( aRect, -1, -1 )
   ENDIF

   lOnSibling := .F.

   FOR EACH oItem IN ::aItems
      WITH OBJECT oItem

         IF :IsVisible()
            IF lOnSibling .AND. nLast == :nAlign .AND. :nSiblingWeight != 0 .AND. :nAlignWeight == 0
               LOOP
            ENDIF

            lOnSibling := ( :nSiblingWeight != 0 )

            IF :nAlignWeight > 0
               IF Len( aLast ) == 0
                  AAdd( aLast, oItem )
                  IF :nAlign == alTOP .OR. :nAlign == alBOTTOM
                     nSize := RectHeight( aRect )
                  ELSE
                     nSize := RectWidth( aRect )
                  ENDIF
               ELSEIF :nAlign == nLast
                  AAdd( aLast, oItem )
                  LOOP
               ELSE  // hay pesos pero cambia la alineación
                  FitItems( aLast, nLast, ::nCardInnerGap, 1 )
                  aLast := {}
                  AAdd( aLast, oItem )
                  IF :nAlign == alTOP .OR. :nAlign == alBOTTOM
                     nSize := RectHeight( aRect )
                  ELSE
                     nSize := RectWidth( aRect )
                  ENDIF
               ENDIF
            ELSE
               nSize := :nSize
            ENDIF

            IF nSize <= 0
               LOOP
            ENDIF

            nLast := :nAlign

            SWITCH :nAlign
            CASE alLEFT
               nPos := Min( aRect[ rtLEFT ] + nSize, aRect[ rtRIGHT ] )
               :SetRect( { aRect[ rtLEFT ], aRect[ rtTOP ], nPos, aRect[ rtBOTTOM ] } )
               aRect[ rtLEFT ] := nPos + ::nCardInnerGap
               EXIT
            CASE alRIGHT
               nPos := Max( aRect[ rtRIGHT ] - nSize, aRect[ rtLEFT ] )
               :SetRect( { nPos, aRect[ rtTOP ], aRect[ rtRIGHT ], aRect[ rtBOTTOM ] } )
               aRect[ rtRIGHT ] := nPos - ::nCardInnerGap
               EXIT
            CASE alTOP
               nPos := Min( aRect[ rtTOP ] + nSize, aRect[ rtBOTTOM] )
               :SetRect( { aRect[ rtLEFT ], aRect[ rtTOP ], aRect[ rtRIGHT ], nPos } )
               aRect[ rtTOP ] := nPos + ::nCardInnerGap
               EXIT
            CASE alBOTTOM
               nPos := Max( aRect[ rtBOTTOM ] - nSize, aRect[ rtTOP ] )
               :SetRect( { aRect[ rtLEFT ], nPos, aRect[ rtRIGHT ], aRect[ rtBOTTOM ] } )
               aRect[ rtBOTTOM ] := nPos - ::nCardInnerGap
               EXIT
            CASE alCLIENT
               :SetRect( AClone( aRect ) )
               EXIT
            END SWITCH
         ENDIF
      END WITH
   NEXT

   IF Len( aLast ) > 0
      FitItems( aLast, nLast, ::nCardInnerGap, 1 )
   ENDIF

   aRect := { 0, 0, ::nClientWidth - ( ::nCardMargin * 2 ), ::nCardSize }
   aLast := {}
   nLast := alNONE

   FOR EACH oItem IN ::aItems
      WITH OBJECT oItem
         IF :IsVisible()
            IF :nSiblingWeight > 0
               IF Len( aLast ) == 0
                  AAdd( aLast, oItem )
                  nLast := :nAlign
               ELSEIF :nAlign == nLast .AND. :nAlignWeight == 0
                  AAdd( aLast, oItem )
                  LOOP
               ELSE  // hay pesos pero cambia la alineación
                  FitItems( aLast, nLast, ::nCardInnerGap, 2 )
                  aLast := {}
                  AAdd( aLast, oItem )
                  nLast := :nAlign
               ENDIF
            ELSEIF Len( aLast ) > 0
               FitItems( aLast, nLast, ::nCardInnerGap, 2 )
               nLast := alNONE
               aLast := {}
            ENDIF

            SWITCH :nAlign
            CASE alLEFT
               nPos := Min( aRect[ rtLEFT ] + :nSize, aRect[ rtRIGHT ] )
               aRect[ rtLEFT ] := nPos + ::nCardInnerGap
               EXIT
            CASE alRIGHT
               nPos := Max( aRect[ rtRIGHT ] - :nSize, aRect[ rtLEFT ] )
               aRect[ rtRIGHT ] := nPos - ::nCardInnerGap
               EXIT
            CASE alTOP
               nPos := Min( aRect[ rtTOP ] + :nSize, aRect[ rtBOTTOM] )
               aRect[ rtTOP ] := nPos + ::nCardInnerGap
               EXIT
            CASE alBOTTOM
               nPos := Max( aRect[ rtBOTTOM ] - :nSize, aRect[ rtTOP ] )
               aRect[ rtBOTTOM ] := nPos - ::nCardInnerGap
               EXIT
            END SWITCH
         ENDIF
      END WITH
   NEXT

   IF Len( aLast ) > 0
      FitItems( aLast, nLast, ::nCardInnerGap, 2 )
   ENDIF

   IF lRefresh .AND. ::Handle != 0
      ::Refresh( .t. )
   ENDIF

RETURN NIL

//------------------------------------------------------------------------------
// Esta función encaja todos los items según su peso en el primer rectángulo
// No se permite alineación alCLIENT

STATIC FUNCTION FitItems( aItems, nAlign, nSpace, nWeightType )

   LOCAL oItem AS CLASS TCarditem
   LOCAL aRect, aPer
   LOCAL nWidth, nHeight, nLen, nTot, nPos, nSiz

   aRect   := aItems[ 1 ]:GetRect()
   nWidth  := aRect[ rtRIGHT ] - aRect[ rtLEFT ]
   nHeight := aRect[ rtBOTTOM ] - aRect[ rtTOP ]
   nLen    := Len( aItems )
   aPer    := Array( nLen )
   nTot    := 0

   IF nWeightType == 1
      AEval( aItems, {|v| nTot += Abs(v:nAlignWeight) } )
      AEval( aItems, {|v,e| aPer[ e ] := Abs(v:nAlignWeight) / nTot } )

      IF nAlign == alLEFT .OR. nAlign == alRIGHT
         nPos  := aRect[ rtLEFT ]
         nTot  := nWidth + nPos
         FOR EACH oItem IN aItems
            nSiz := Int( nWidth * aPer[ oItem:__EnumIndex() ] ) + nPos
            oItem:SetRect( { nPos, aRect[ rtTOP ], nSiz, aRect[ rtBOTTOM ] } )
            nPos  := nSiz + nSpace
         NEXT
         IF nPos != nTot
            aRect := ATail( aItems ):GetRect()
            aRect[ rtRIGHT ] := nTot
            ATail( aItems ):SetRect( aRect )
         ENDIF
      ELSE
         nPos := aRect[ rtTOP ]
         nTot := nHeight + nPos
         FOR EACH oItem IN aItems
            nSiz := Int( nHeight * aPer[ oItem:__EnumIndex() ] ) + nPos
            oItem:SetRect( { aRect[ rtLEFT ], nPos, aRect[ rtRIGHT ], nSiz } )
            nPos  := nSiz + nSpace
         NEXT
         IF nPos != nTot
            aRect := ATail( aItems ):GetRect()
            aRect[ rtBOTTOM ] := nTot
            ATail( aItems ):SetRect( aRect )
         ENDIF
      ENDIF
   ELSE
      AEval( aItems, {|v| nTot += Abs(v:nSiblingWeight) } )
      AEval( aItems, {|v,e| aPer[ e ] := Abs(v:nSiblingWeight) / nTot } )

      IF nAlign == alLEFT .OR. nAlign == alRIGHT
         nPos := aRect[ rtTOP ]
         nTot := nHeight + nPos
         FOR EACH oItem IN aItems
            nSiz := Int( nHeight * aPer[ oItem:__EnumIndex() ] ) + nPos
            oItem:SetRect( { aRect[ rtLEFT ], nPos, aRect[ rtRIGHT ], nSiz } )
            nPos  := nSiz + nSpace
         NEXT
         IF nPos != nTot
            aRect := ATail( aItems ):GetRect()
            aRect[ rtBOTTOM ] := nTot
            ATail( aItems ):SetRect( aRect )
         ENDIF
      ELSE
         nPos  := aRect[ rtLEFT ]
         nTot  := nWidth + nPos
         FOR EACH oItem IN aItems
            nSiz := Int( nWidth * aPer[ oItem:__EnumIndex() ] ) + nPos
            oItem:SetRect( { nPos, aRect[ rtTOP ], nSiz, aRect[ rtBOTTOM ] } )
            nPos  := nSiz + nSpace
         NEXT
         IF nPos != nTot
            aRect := ATail( aItems ):GetRect()
            aRect[ rtRIGHT ] := nTot
            ATail( aItems ):SetRect( aRect )
         ENDIF
      ENDIF
   ENDIF


RETURN NIL

//------------------------------------------------------------------------------

METHOD SetMultipleSel( Value ) CLASS XCardBox

   ::FlMultipleSel := Value

   IF !Value
      ::aSelected := {}
      ::Refresh()
      ::OnCardSelection( ::aSelected )
   ENDIF

RETURN Value

//------------------------------------------------------------------------------

METHOD SelectItem( nItem, nMode ) CLASS XCardBox

   LOCAL nAt, nFrom, nTo

   SWITCH nMode
   CASE MODE_DELETE
      ::nLastSel  := nItem
      ::aSelected := {}
      EXIT
   CASE MODE_SWITCH
      IF ( nAt := AScan( ::aSelected, nItem ) ) == 0
         AAdd( ::aSelected, nItem )
      ELSE
         HB_ADel( ::aSelected, nAt, .T. )
      ENDIF
      ::nLastSel := nItem
      EXIT
   CASE MODE_RANGE
      nTo := Max( ::nLastSel, 1 )
      IF nItem <= nTo
         FOR nFrom := nItem TO nTo
            IF AScan( ::aSelected, nFrom ) == 0
               AAdd( ::aSelected, nFrom )
            ENDIF
         NEXT
      ELSE
         FOR nFrom := nItem TO nTo STEP -1
            IF AScan( ::aSelected, nFrom ) == 0
               AAdd( ::aSelected, nFrom )
            ENDIF
         NEXT
      ENDIF
      ::nLastSel := nItem
      EXIT

   END SWITCH

   ::Refresh()

   ::OnCardSelection( ::aSelected )

RETURN NIL

//------------------------------------------------------------------------------

METHOD ItemSelected( nItem ) CLASS XCardBox

RETURN ::lMultipleSel .AND. Ascan( ::aSelected, nItem ) > 0

//------------------------------------------------------------------------------

METHOD SetCardShadow( lValue ) CLASS XCardBox

   ::FlCardShadow := lValue

   IF lValue
      ::nCardShadow := Min( 6, Min( ::nCardGap, ::nCardMargin ) )
   ENDIF

   ::Refresh()

RETURN lValue

//------------------------------------------------------------------------------

METHOD HitTest( nX, nY, BYREF nGap ) CLASS XCardBox

   LOCAL nSize, nItem, nMrg

   nSize := ::CardSpace()
   nItem := Int( -::nClientTop / nSize ) + 1
   nGap  := ::nClientTop + ( ( nItem - 1 ) * nSize ) + ::nCardSize + ::FirstGap()
   nMrg  := ::nCardMargin

   IF ( nItem == 1 .AND. nY < ::FirstGap() ) .OR. ( nX < nMrg ) .OR. ( nX > ( ::nClientWidth -nMrg ) )
      RETURN 0
   ENDIF

   DO WHILE nY > nGap
      IF nY <= ( nGap + ::nCardGap )
         nItem := 0
         EXIT
      ELSE
         nItem ++
         nGap += nSize
      ENDIF
   ENDDO

   IF nItem > ::RecCount()
      nItem := 0
   ENDIF

   nGap -= nSize - ::nCardGap

RETURN nItem

//------------------------------------------------------------------------------

METHOD FirstVisible()

RETURN Int( -::nClientTop / ::CardSpace() ) + 1

//------------------------------------------------------------------------------

METHOD LastVisible()

RETURN Min( ::RecCount(), ::FirstVisible() + Int( ::nClientHeight / ::CardSpace() ) )

//------------------------------------------------------------------------------

METHOD IsCardVisible( nIndex, lFull )

   LOCAL nFirst, nLast
   LOCAL lValue

   DEFAULT lFull TO .T.

   nFirst := ::FirstVisible()
   nLast  := ::LastVisible()

   lValue := nIndex > nFirst .AND. nIndex < nLast

   IF nIndex == nFirst
      lValue := ( ::nClientTop >= 0 )
   ELSEIF nIndex == nLast
      lValue := ( -::nClientTop >= ( ::nVirtualHeight - ::nClientHeight ) )
   ENDIF

RETURN lValue

//------------------------------------------------------------------------------

METHOD SetIndex( Value ) CLASS XCardBox

   LOCAL nPos

   IF Value > 0 .AND. Value <= ::RecCount() .AND. Value != ::FnIndex
      ::OnCardChange( Value, ::FnIndex )
      ::FnIndex := Value
      ::CalcHeight( .t. )
      // Asegurarse de que el nuevo item esta visible en pantalla - [JFG]
      nPos := ::CardSpace() * ( Value - 1 )
      IF nPos < -::nClientTop
         ::nClientTop := -nPos
      ELSEIF nPos + ::CardSpace() > ::nClientHeight - ::nClientTop
         ::nClientTop := ::nClientHeight - ::CardSpace() - nPos
      ENDIF
   ENDIF

RETURN ::FnIndex

//------------------------------------------------------------------------------

METHOD Paint( hDC ) CLASS XCardBox

   LOCAL oItem
   LOCAL aRect
   LOCAL nFirst, nLast, nSize, nFor, nX, nY
   LOCAL lActive, lHot, lSel, lBkDark

   nSize  := ::CardSpace()
   nFirst := ::FirstVisible()
   nLast  := Min( ::LastVisible() + 1, ::RecCount() ) // una de regalo
   nX     := ::nCardMargin
   nY     := ::nClientTop + ( ( nFirst - 1 ) * nSize ) + ::FirstGap()

   IF nFirst == 1 .AND. ::lGapFirstCard
      ::OnCardGapPaint( hDC, { 0, 0, ::nClientWidth, ::nCardGap } )
   ENDIF

   FOR nFor := nFirst TO nLast
      lActive := ( ::nIndex == nFor )
      lHot    := ::lHotTrack .AND. ( ::nHotPos == nFor )
      lSel    := ::ItemSelected( nFor )
      aRect   := { nX, nY, ::nClientWidth - nX, nY + ::nCardSize }
      ::OnBeforeCardPaint( nFor )
      IF ::lCardBorder
         FrameRect( hDC, aRect, ::hBorderBrush )
      ENDIF
      FOR EACH oItem IN ::aItems
         oItem:Paint( hDC, nFor, nX, nY, lActive, lHot, lSel )
      NEXT
      IF ::lCardShadow
         CutreShadow( hDC, aRect, ::nCardShadow )
      ENDIF
      IF lActive .AND. ::nCardClrAct == NIL
         lBkDark := XA_Luminance( ::nClrPane ) < 127
         XA_FillRectAlpha( hDC, aRect , IIF( lBkDark, clWhite, clBlack ), IIF( GetFocus() = ::Handle, 60, 10 ) )
      ENDIF
      nY += nSize
      ::OnCardGapPaint( hDC, { 0, nY - ::nCardGap, ::nClientWidth, nY } )
   NEXT

RETURN NIL

//------------------------------------------------------------------------------

METHOD WMLButtonDown( nWParam, nLParam, hWnd ) CLASS XCardBox

   LOCAL oItem1, oItem2
   LOCAL nPos, nX, nY, nGap

   nX   := LoWord( nLParam )
   nY   := HiWord( nLParam )
   nPos := ::HitTest( nX, nY, @nGap )

   IF ::oLabelEx:lVisible
      ::oLabelEx:lVisible := .F.
   ENDIF

   IF ::lTabStop .AND. GetFocus() != ::Handle
      SetFocus( ::Handle )
   ENDIF

   IF nPos != 0
      FOR EACH oItem1 IN ::aItems
         IF oItem1:PointInRect( ::nCardMargin, nGap, { nX, nY } )
            ::nClickPos  := nPos
            ::nClickItem := oItem1:nItem
            oItem2 := oItem1
            EXIT
         ENDIF
      NEXT
      IF ::nIndex != nPos
         ::nIndex := nPos
      ELSE
         IF oItem2 != NIL
            oItem2:CheckScroll( nPos, ::nCardMargin, nGap )
         ENDIF
      ENDIF
      ::Refresh()
   ENDIF

RETURN ::Super:WMLButtonDown( nWParam, nLParam, hWnd )

//------------------------------------------------------------------------------

METHOD WMLButtonUp( nWParam, nLParam, hWnd ) CLASS XCardBox

   LOCAL oItem
   LOCAL nPos, nX, nY, nGap

   IF ::nClickItem > 0
      nX   := LoWord( nLParam )
      nY   := HiWord( nLParam )
      nPos := ::HitTest( nX, nY, @nGap )

      IF nPos == ::nClickPos
         FOR EACH oItem IN ::aItems
            IF oItem:PointInRect( ::nCardMargin, nGap, { nX, nY } )
               IF oItem:nItem == ::nClickItem .AND. !Empty( ::OnCardClick( oItem, nPos, nX, nY ) )
                  nPos := 0
               ENDIF
               EXIT
            ENDIF
         NEXT
      ELSE
         nPos := 0
      ENDIF
      IF nPos > 0
         ::nIndex := nPos
         IF ::lMultipleSel
            IF lGetKeyState( VK_SHIFT )
               ::SelectItem( nPos, MODE_RANGE )
            ELSEIF lGetKeyState( VK_CONTROL )
               ::SelectItem( nPos, MODE_SWITCH )
            ELSE
               ::SelectItem( nPos, MODE_DELETE )
            ENDIF
         ENDIF
      ENDIF
      ::nClickPos  := 0
      ::nClickItem := 0
      ::Refresh()
   ENDIF

RETURN ::Super:WMLButtonUp( nWParam, nLParam, hWnd )

//------------------------------------------------------------------------------

METHOD WMLButtonDblClk( nWParam, nLParam, hWnd ) CLASS XCardBox

   LOCAL oItem
   LOCAL nPos, nX, nY, nGap

   nX   := LoWord( nLParam )
   nY   := HiWord( nLParam )
   nPos := ::HitTest( nX, nY, @nGap )

   FOR EACH oItem IN ::aItems
      IF oItem:PointInRect( ::nCardMargin, nGap, { nX, nY } )
         ::OnCardDblClick( oItem, nPos, nX, nY )
         RETURN 0
      ENDIF
   NEXT

RETURN ::Super:WMLButtonDblClk( nWParam, nLParam, hWnd )

//------------------------------------------------------------------------------

METHOD WMMouseMove( nWParam, nLParam, hWnd ) CLASS XCardBox

   LOCAL oItem
   LOCAL nPos, nItem, nX, nY, nGap

   nX   := LoWord( nLParam )
   nY   := HiWord( nLParam )
   nPos := ::HitTest( nX, nY, @nGap )

   IF ::lHotTrack
      IF nPos != ::nHotPos
         ::Refresh()
      ENDIF
      ::nHotPos := nPos
      IF nPos > 0
         TrackMouseEvent( ::Handle, TME_LEAVE )
      ENDIF
   ELSEIF nPos == 0
      ::nHotPos := 0
      IF ::nHotItem > 0
         ::nHotItem := 0
         ::Refresh()
      ENDIF
   ELSE
      nItem := 0 //::nHotItem
      FOR EACH oItem IN ::aItems
         IF oItem:lHotTrack .AND. oItem:PointInRect( ::nCardMargin, nGap, { nX, nY } )
            nItem := oItem:nItem
            EXIT
         ENDIF
      NEXT
      IF nPos != ::nHotPos .OR. nItem != ::nHotItem
         ::Refresh()
      ENDIF
      ::nHotPos  := nPos
      ::nHotItem := nItem
      IF nPos > 0 .AND. nItem > 0
         TrackMouseEvent( ::Handle, TME_LEAVE )
      ENDIF
   ENDIF

RETURN ::Super:WMMouseMove( nWParam, nLParam, hWnd )

//--------------------------------------------------------------------------

METHOD WMSetCursor( nWParam, nLParam ) CLASS XCardbox

   IF nWParam == ::Handle
      IF Application:lBusy
         CursorWait()
         RETURN .T.
      ELSEIF LoWord( nLParam ) == HTCLIENT
         IF ::nHotItem > 0 .AND. ::aItems[ ::nHotItem ]:oCursor != NIL
            SetCursor( ::aItems[ ::nHotItem ]:oCursor:Handle )
            RETURN .T.
         ELSEIF ::oCursor != Nil
            SetCursor( ::oCursor:Handle )
            RETURN .T.
         ENDIF
      ENDIF
   ENDIF

RETURN Nil

//------------------------------------------------------------------------------

METHOD WMMouseLeave( nWParam, nLParam, hWnd ) CLASS XCardBox

   IF ::nHotPos > 0 .OR. ::nHotItem > 0
      ::nHotPos  := 0
      ::nHotItem := 0
      ::Refresh()
      RETURN 0
   ENDIF

RETURN ::Super:WMMouseLeave( nWParam, nLParam, hWnd )

//------------------------------------------------------------------------------

METHOD WMKeyDown( nKey, nFlags, hWnd ) CLASS XCardBox

   LOCAL nPage, nIndex, nSpace

   nIndex := ::nIndex
   nPage  := Int( ::nClientHeight / ::CardSpace() )
   nSpace := ::CardSpace()

   SWITCH nKey
   CASE VK_DOWN
      nIndex ++
      EXIT
   CASE VK_UP
      nIndex --
      EXIT
   CASE VK_NEXT
      nIndex += nPage
      EXIT
   CASE VK_PRIOR
      nIndex -= nPage
      EXIT
   CASE VK_HOME
      nIndex := 1
      EXIT
   CASE VK_END
      nIndex := ::RecCount()
      EXIT
   END SWITCH

   IF nIndex > ::nIndex
      nIndex := Min( ::RecCount(), nIndex )
      DO WHILE !::IsCardVisible( nIndex, .T. )
         ::FnClientTop -= nSpace
      ENDDO
      ::nIndex := nIndex
      IF ::lMultipleSel
         IF lGetKeyState( VK_SHIFT )
            ::SelectItem( nIndex, MODE_RANGE )
         ELSE
            ::SelectItem( nIndex, MODE_DELETE )
         ENDIF
      ENDIF
      RETURN 0
   ELSEIF nIndex < ::nIndex
      nIndex := Max( 1, nIndex )
      DO WHILE !::IsCardVisible( nIndex, .T. )
         ::FnClientTop += nSpace
         ::FnClientTop := Min( ::FnClientTop, 0 )
      ENDDO
      ::nIndex := nIndex
      IF ::lMultipleSel
         IF lGetKeyState( VK_SHIFT )
            ::SelectItem( nIndex, MODE_RANGE )
         ELSE
            ::SelectItem( nIndex, MODE_DELETE )
         ENDIF
      ENDIF
      RETURN 0
   ENDIF

RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )

//------------------------------------------------------------------------------

METHOD WMMouseWheel( nWParam, nLParam ) CLASS XCardBox

   IF ::oLabelEx != NIL .AND. ::oLabelEx:lVisible
      ::oLabelEx:lVisible := .F.
   ENDIF

   IF !lGetKeyState( VK_SHIFT ) .AND. !lGetKeyState( VK_CONTROL )
      ::nClientTop := Min( 0, ::nClientTop + ::nScrollIncrement * IIF( HiInt( nWParam ) > 0, 1, -1 ) )
      ::Refresh()
      RETURN 0
   ENDIF

RETURN ::Super:WMMouseWheel( nWParam, nLParam )

//------------------------------------------------------------------------------

#pragma BEGINDUMP

#include <windows.h>
#include <commctrl.h>
#include <xailer.h>
#include <constants.ch>
#include <colors.ch>

//------------------------------------------------------------------------------
static void PaintShadow( HDC hdc, LPRECT prc, int nLen )
{
   HPEN hGray   = CreatePen( PS_SOLID, 1, clBtnShadow );
   HPEN hOldPen = SelectObject( hdc, hGray );
   int i;

   for( i = 1; i < nLen; i++ )
      {
      MoveToEx( hdc, prc->left + nLen + i, prc->bottom + i, NULL );
      LineTo( hdc, prc->right + i + 1, prc->bottom + i );
      }

   for( i = 1; i < nLen; i++ )
      {
      MoveToEx( hdc, prc->right + i, prc->top + nLen + i , NULL );
      LineTo( hdc, prc->right + i, prc->bottom + i );
      }

   SelectObject( hdc, hOldPen );
   DeleteObject( hGray );
}

//------------------------------------------------------------------------------

HB_FUNC_STATIC( CUTRESHADOW ) // CutreShadow( hDC, aRect, nCardShadow )
{
   HDC hdc = (HDC) hb_parnl( 1 );
   int nLen = hb_parnl( 3 ) - 2;
   RECT rc;

   if( nLen > 0 )
      {
      rc.left   = hb_parvnl( 2, 1 );
      rc.top    = hb_parvnl( 2, 2 );
      rc.right  = hb_parvnl( 2, 3 );
      rc.bottom = hb_parvnl( 2, 4 );

      PaintShadow( hdc, &rc, nLen );
      }
   hb_ret();
}

//------------------------------------------------------------------------------

#pragma ENDDUMP

