/*
 * Xailer source code:
 *
 * DateEdit.prg
 * Clase TDateEdit()
 *
 * Copyright 2003, 2007 Jose F. Gimenez
 * Copyright 2003, 2007 Xailer.com
 * All rights reserved
 *
 */

#include "Xailer.ch"


//------------------------------------------------------------------------------

CLASS XDateEdit FROM TMaskEdit

PUBLISHED:
   PROPERTY nWidth   INIT 82
   PROPERTY Value    INIT CTOD( "" ) WRITE METHOD SetValue

   PROPERTY oBitmap  WRITE METHOD SetBitmap  AS TPicture EDITOR PE_Picture

PUBLIC:
   PROPERTY cPicture
   PROPERTY nMaxLength

   METHOD Create( oParent ) CONSTRUCTOR
   METHOD Free() INLINE IIf( ! Empty( ::oBitmap ), ::oBitmap:End(), ), ::Super:Free()

RESERVED:
   METHOD SetBitmap( oBitmap )
   METHOD SetMargin()
   METHOD SetValue( Value, lFocused, lFireEvent )
   METHOD WMKeyDown( nKey, nFlags, hWnd )
   METHOD WMLButtonDown()
   METHOD WMLButtonUp()
   METHOD WMLButtonDblClk()
   METHOD WMMouseLeave() INLINE ::nBtnState := 1, ::Refresh( .F. ), Nil
   METHOD WMSetCursor() INLINE .T.
   METHOD WMPaint()
   METHOD WMMouseMove()
   METHOD XAClick( wParam, x, y )
   METHOD WMMouseWheel( nWParam, nLParam )
   METHOD WMXailer()       INLINE ::Refresh(), 0

   DATA lDropped  INIT .F.
   DATA nBtnState INIT 1
   DATA nBtnWidth

ENDCLASS

// ABW: Con temas en algunas tarjetas graficas, en determinados momentos
// llama internamente a su propia rutina de pintado sin generar ningun WM_PAINT,
// y por lo tanto se pinta mal el control - [JFG]

//------------------------------------------------------------------------------

METHOD Create( oParent ) CLASS XDateEdit

   ::Super:Create( oParent )
   ::SetMargin()

RETURN Self

//------------------------------------------------------------------------------

METHOD SetBitmap( oBitmap ) CLASS XDateEdit

   IF ::FoBitmap != Nil
      ::FoBitmap:Destroy()
      ::FoBitmap := Nil
   ENDIF

   IF oBitmap != Nil
      IF ValType( oBitmap ) != "O"
         oBitmap := TPicture():Create( oBitmap )
      ENDIF
      ::FoBitmap := oBitmap
   ENDIF

   ::SetMargin()

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetMargin() CLASS XDateEdit

   ::nBtnWidth := MulDiv( IIf( Empty( ::FoBitmap ), 17, ::FoBitmap:nWidth + 2 ), Application:nScale, 100 )
   ::SendMsg( EM_SETMARGINS, EC_RIGHTMARGIN, MakeLong( 0, ::nBtnWidth ) )

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetValue( Value, lFocused, lFireEvent ) CLASS XDateEdit

   IF ValType( Value ) == "T"
      Value := HB_TToD( Value )
   ENDIF

   IF Empty( Value ) .OR. ValType( Value ) != "D"
      Value := CTOD( "" )
   ENDIF

RETURN ::Super:SetValue( Value, lFocused,, lFireEvent )

//--------------------------------------------------------------------------

METHOD WMKeyDown( nKey, nFlags, hWnd ) CLASS XDateEdit

   ::oForm:SendMsg( WM_CHANGEUISTATE, MakeLong( UIS_CLEAR, UISF_HIDEFOCUS ) )
   ::Refresh()  // NOTA: Bug*
   IF nKey == VK_UP
      IF Day( ::Value ) > 0
         ::SetValue( ::Value + IIf( lGetKeyState( VK_CONTROL ), 30, 1 ), , .T. )
      ENDIF
      RETURN 0
   ELSEIF nKey == VK_DOWN
      IF Day( ::Value ) > 0
         ::SetValue( ::Value - IIf( lGetKeyState( VK_CONTROL ), 30, 1 ), , .T. )
      ENDIF
      RETURN 0
   ELSEIF nKey == VK_F4 .AND. ! lGetKeyState( VK_CONTROL )
      TDropCalendar():New( Self )
      RETURN 0
   ENDIF

RETURN ::Super:WMKeyDown( nKey, nFlags, hWnd )

//--------------------------------------------------------------------------

METHOD XAClick( wParam, x, y ) CLASS XDateEdit

   IF x >= ::nClientWidth - ::nBtnWidth .AND. ::SetFocus() .AND. ! ::lDropped
      TDropCalendar():New( Self ):SendMsg( WM_LBUTTONDOWN )
      RETURN .T.
   ENDIF

RETURN .F.

//--------------------------------------------------------------------------

METHOD WMLButtonDown( nWParam, nLParam ) CLASS XDateEdit

   LOCAL x := LoWord( nLParam )

   ::PostMsg( WM_XAILER ) // NOTA: Bug*
   IF x >= ::nClientWidth - ::nBtnWidth .AND. ::SetFocus()
      ::nBtnState := 3
      ::Refresh()
   ENDIF

RETURN ::Super:WMLButtonDown( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMLButtonUp( nWParam, nLParam ) CLASS XDateEdit

   LOCAL x := LoWord( nLParam )

   IF x >= ::nClientWidth - ::nBtnWidth
      ::nBtnState := 2
   ELSE
      ::nBtnState := 1
   ENDIF
   ::Refresh()

RETURN ::Super:WMLButtonUp( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMLButtonDblClk( nWParam, nLParam ) CLASS XDateEdit

   LOCAL x := LoWord( nLParam )

   ::Refresh()  // NOTA: Bug*

   IF ::XAClick( nWParam, x, HiWord( nLParam ) )
      RETURN 0
   ENDIF

RETURN ::Super:WMLButtonDblClk( nWParam, nLParam )

//--------------------------------------------------------------------------

METHOD WMMouseWheel( nWParam, nLParam ) CLASS XDateEdit

   LOCAL dDate, cPos
   LOCAL nMonth, nDay, nYear, nPos

   nPos := ::nPos()
   cPos := Upper( SubStr( Set( _SET_DATEFORMAT  ), nPos, 1 ) )

   IF !cPos $ "DMY"
      cPos := Upper( SubStr( Set( _SET_DATEFORMAT  ), nPos - 1, 1 ) )
   ENDIF

   SWITCH cPos
   CASE "D"
      IF nWParam > 0
         ::Value ++
      ELSE
         ::Value --
      ENDIF
      EXIT
   CASE "M"
      nMonth := Month( ::Value )
      IF nWParam > 0
         nMonth++
         IF nMonth > 12
            nMonth := 1
         ENDIF
      ELSE
         nMonth--
         IF nMonth < 1
            nMonth := 12
         ENDIF
      ENDIF
      nYear := Year( ::Value )
      nDay  := Day( ::Value )
      DO WHILE Empty( ( dDate := HB_Date( nYear, nMonth, nDay ) ) ) .AND. nDay > 0
         nDay --
      ENDDO
      IF !Empty( dDate )
         ::Value := dDate
         nPos--
         ::SetSel( nPos, nPos )
      ENDIF
      EXIT
   CASE "Y"
      nYear := Max( Year( ::Value ) + IIF( nWParam > 0, 1, -1 ), 1 )
      dDate := HB_Date( nYear, Month( ::Value ) , Day( ::Value ) )
      IF Empty( dDate ) // 29 Feb Bisiesto
         dDate := HB_Date( nYear, Month( ::Value ) , Day( ::Value ) - 1 )
      ENDIF
      ::Value := dDate
      nPos--
      ::SetSel( nPos, nPos )
      EXIT
   END SWITCH

RETURN NIL

//--------------------------------------------------------------------------

#pragma BEGINDUMP

#include <windows.h>
#include <commctrl.h>
#include <xailer.h>

//--------------------------------------------------------------------------

HB_FUNC_STATIC( XDATEEDIT_WMMOUSEMOVE )
{
   PHB_ITEM Self  = hb_stackSelfItem();
   HANDLE hWnd  = GetHandleOf( Self );
   int nBtnState = XA_ObjGetNL( Self, "nBtnState" );
   int nBtnWidth = XA_ObjGetNL( Self, "nBtnWidth" );
   BOOL lDropped = XA_ObjGetL( Self, "lDropped" );
   int nLParam = hb_parnl( 2 );
   POINT pt;
   RECT rect;

   GetClientRect( hWnd, &rect );
   pt.x = LOWORD( nLParam );
   pt.y = HIWORD( nLParam );

   if ( ( pt.x > rect.right - nBtnWidth ) && ( pt.x <= rect.right ) )
      {
      CursorArrow();
      if ( ( nBtnState <= 1 ) && !lDropped )
         {
         XA_ObjSendNL( Self, "_nBtnState", 2 );
         InvalidateRect( hWnd, &rect, FALSE );
         }
      }
   else
      {
      SetCursor( (HCURSOR) GetClassLong( hWnd, GCL_HCURSOR ) );
      if ( ( nBtnState > 1 ) && !lDropped )
         {
         XA_ObjSendNL( Self, "_nBtnState", 1 );
         InvalidateRect( hWnd, &rect, FALSE );
         }
      }
   // NOTA: Bug*
   PrevWindowProc( hWnd, WM_MOUSEMOVE, hb_parnl( 1 ), nLParam );
   if( nBtnWidth )
      {
      rect.left = rect.right - nBtnWidth;
      InvalidateRect( hWnd, &rect, FALSE );
      UpdateWindow( hWnd );
      }

   hb_retnl( 0 );
}

//--------------------------------------------------------------------------

#pragma ENDDUMP

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

CLASS TDropCalendar FROM TForm STATIC

   DATA oEdit  AS TEdit
   COMPONENT oCalendar

   METHOD New( oParent )
   METHOD WMActivate()
   METHOD WMNCDestroy()
   METHOD CalendarKeyDown()
   METHOD Ok()
   METHOD Initialize()
   METHOD Uninitialize()
   METHOD CalcSize()
   METHOD WMHelp( nWParam, nLParam )   INLINE ::oEdit:SendMsg( WM_HELP, nWParam, nLParam )

ENDCLASS

//--------------------------------------------------------------------------

METHOD New( oParent ) CLASS TDropCalendar

   LOCAL oForm, nTop, nLeft

   oForm := oParent:oForm
   IF oForm:nFormType == ftMDICHILD
      oForm := oForm:oParent
   ENDIF

   ::Super:New( oForm )

   ::Initialize()
   oParent:oForm:Update()
   ::oEdit := oParent
   ::oEdit:lDropped := .T.
   ::oEdit:Refresh()
   nTop := oParent:nHeight - 2
   nLeft := -2
   ClientToScreen( oParent:Handle, @nLeft, @nTop )

   ::lAutoScroll := .F.
   ::nBorderStyle := bsNONE
   IF Application:nWinVer > 5  // WinXP & >
      #define CS_DROPSHADOW       0x00020000
      ::nClassStyle := CS_DROPSHADOW
   ENDIF
   ::nClrPane := clBlack
   ::Create()
   ::SetBounds( nLeft, nTop )

   WITH OBJECT ::oCalendar := TMonthCalendar():New( Self )
      :lBorder := .F.
      IF ::oEdit:Value != Nil .AND. Day( ::oEdit:Value ) > 0
         :dValue := ::oEdit:Value
      ENDIF
      :OnSelect := {| oSender | oSender:PostMsg( WM_KEYDOWN, VK_RETURN ) }
      :OnKeyDown := "CalendarKeyDown"
      :Create()
   END
   ::CalcSize()
   WITH OBJECT oParent:GetMonitor()
      IF ::nLeft < :nClientLeft
         ::nLeft := :nClientLeft
      ELSEIF ::nLeft + ::nWidth >= :nClientLeft + :nClientWidth
         ::nLeft := :nClientLeft + :nClientWidth - ::nWidth - 1
      ENDIF
      IF ::nTop < :nClientTop
         ::nTop := :nClientTop
      ELSEIF ::nTop + ::nHeight >= :nClientTop + :nClientHeight
         ::nTop -= ::oEdit:nHeight + ::nHeight
      ENDIF
   END
   ::Show()
   ::oCalendar:SetFocus()
   oParent:oForm:lActive := .F.

RETURN Self

//--------------------------------------------------------------------------

METHOD WMActivate( nWParam, hNext ) CLASS TDropCalendar

   IF LoWord( nWParam ) == 0
      // Deactivate
      ::Uninitialize()
      IF hNext == 0 .OR. hNext != ::oParent:Handle
         // El foco pasa a otra ventana distinta de ::oParent
         ::PostMsg( WM_CLOSE, 0, 1 )
         ::oParent:PostMsg( WM_NCACTIVATE, .F. )
      ELSE
         // El foco pasa a ::oParent
         ::PostMsg( WM_CLOSE )
      ENDIF
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD WMNCDestroy() CLASS TDropCalendar

   ::Uninitialize()
   ::oEdit:lDropped := .F.
   ::oEdit:Refresh()
   ::oEdit:oForm:Refresh()

RETURN ::Super:WMNCDestroy()

//--------------------------------------------------------------------------

METHOD CalendarKeyDown( oSender, nKey, nFlags ) CLASS TDropCalendar

   IF nKey == VK_RETURN
      ::Ok( oSender, oSender:GetCurSel() )
   ELSEIF nKey == VK_F4 .OR. nKey == VK_ESCAPE
      ::End()
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD Ok( oSender, dValue ) CLASS TDropCalendar

   IF ! ::oEdit:lReadOnly
      ::oEdit:SetValue( dValue, , .T. )
   ENDIF
   ::End()

RETURN Nil

//--------------------------------------------------------------------------

#pragma BEGINDUMP

/* NOTA: oldWndProc esta como estatica sin problemas porque solo puede haber una
         instancia de TDropList() activa en todo el proceso - [JFG] */
static WNDPROC oldWndProc = NULL;

static LRESULT CALLBACK TDropCalendar_WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
   if ( uMsg == WM_ACTIVATE )
      return( 0 );
   else if ( uMsg == WM_NCACTIVATE )
      return( TRUE );

   return( CallWindowProc( oldWndProc, hwnd, uMsg, wParam, lParam ) );
}

//--------------------------------------------------------------------------

HB_FUNC_STATIC( TDROPCALENDAR_INITIALIZE )
{
   PHB_ITEM Self  = hb_stackSelfItem();
   HANDLE hParent = GetHandleOf( XA_ObjGetItem( Self, "oParent" ) );

   oldWndProc = (WNDPROC) GetWindowLong( hParent, GWL_WNDPROC );
   SetWindowLong( hParent, GWL_WNDPROC, (long) TDropCalendar_WndProc );
}

//--------------------------------------------------------------------------

HB_FUNC_STATIC( TDROPCALENDAR_UNINITIALIZE )
{
   PHB_ITEM Self  = hb_stackSelfItem();
   HANDLE hParent = GetHandleOf( XA_ObjGetItem( Self, "oParent" ) );

   if ( oldWndProc )
      {
      SetWindowLong( hParent, GWL_WNDPROC, (long) oldWndProc );
      oldWndProc = NULL;
      }
}

//--------------------------------------------------------------------------

HB_FUNC_STATIC( TDROPCALENDAR_CALCSIZE )
{
   PHB_ITEM Self  = hb_stackSelfItem();
   HWND hCalendar = GetHandleOf( XA_ObjGetItem( Self, "oCalendar" ) );
   RECT rect;

   SendMessage( hCalendar, MCM_GETMINREQRECT, 0, (LPARAM) &rect );
   SetWindowPos( hCalendar, NULL, 1, 1, rect.right, rect.bottom, 0 );
   XA_ObjSendNL( Self, "_nWidth", rect.right + 2 );
   XA_ObjSendNL( Self, "_nHeight", rect.bottom + 2 );
}

#pragma ENDDUMP



//--------------------------------------------------------------------------
