/*
 * Xailer source code:
 *
 * ButtonEx.prg
 * Clase TButtonEx()
 *
 * Copyright 2017 Jose F. Gimenez
 * Copyright 2017 Xailer.com
 * All rights reserved
 *
 */

#include "Xailer.ch"

#ifdef __DLL_EXPORT__
DYNAMIC XBtnBmp_WMNCHitTest
#endif

//------------------------------------------------------------------------------

CLASS XButtonEx FROM TControl

PUBLISHED:
   PROPERTY nWidth         INIT 120
   PROPERTY nHeight        INIT 30
   PROPERTY cShortCut      INIT ""
   PROPERTY nStyle         INIT bsDEFAULT VALUES bsDEFAULT, bsWINDOWS7, bsMODERN  WRITE METHOD SetStyle
   PROPERTY oFontShortcut  WRITE METHOD SetFontShortcut  EDITOR PE_Font
   PROPERTY nClrPane       INIT clGrayText
   PROPERTY nClrPaneItems  INIT clWhite   EDITOR PE_Color
   PROPERTY nClrBorder     INIT 0x675921  EDITOR PE_Color
   PROPERTY nEffects       INIT beSLIDE   VALUES beNONE, beSLIDE
   PROPERTY aItems         INIT {}  EDITOR PE_StringList

   PROPERTY nBmpWidth      INIT 1
   PROPERTY nBmpHeight     INIT 1
   PROPERTY oBitmaps       WRITE METHOD SetBitmaps EDITOR PE_ButtonBitmap AS TImageList
   PROPERTY nMargin        INIT -1 // Espacio entre el borde del boton y la imagen
   PROPERTY nOrientation   INIT orLEFT VALUES orLEFT, orTOP, orRIGHT, orBOTTOM
   PROPERTY nAlignment     INIT taCENTER  VALUES taLEFT, taRIGHT, taCENTER
   PROPERTY nVAlignment    INIT vaCENTER  VALUES vaTOP, vaBOTTOM, vaCENTER
   PROPERTY lMultiLine     INIT .T.
   PROPERTY lPushed        INIT .F.

   EVENT OnClick( oSender, nItem )
   EVENT OnDrop( oSender, oDropMenu )

PUBLIC:
   PROPERTY lDropped    READONLY INIT .F.
   PROPERTY cBitmap     READONLY

PROTECTED:
   PROPERTY lHot        INIT .F. WRITE INLINE ::Refresh(), ::FlHot := Value
   PROPERTY lFlat       INIT .F.
   PROPERTY nFlatStyle
   PROPERTY lXPLook
   DATA nClassStyle  INIT 0

RESERVED:
   EVENT OnDblClick
   EVENT OnLButtonDown
   EVENT OnLButtonUp

   METHOD New( oParent )
   METHOD Free()
   METHOD SetStyle( nStyle )
   METHOD SetFontShortcut( oFont )
   METHOD SetBitmaps( oBitmaps )
   METHOD Click()                            INLINE IIF( ::lEnabled, IIF( Empty( ::aItems ), ::Super:Click(), IIF( ::lDropped,, TDropMenu():New( Self ) ) ), )
   METHOD WMLButtonDown( nWParam, nLParam )  INLINE ::lPushed := .T., ::Refresh()
   METHOD WMLButtonUp( nWParam, nLParam )    INLINE IIF( ::lPushed, ::Click(), ), ::lPushed := .F., ::Refresh()
   METHOD WMNCHitTest()                      EXTERN XBtnBmp_WMNCHitTest
   METHOD WMMouseLeave()                     INLINE IIF( ::lHot, ( ::lHot := .F., ::lPushed := .F., ::Refresh() ), )
   METHOD WMPaint()

ENDCLASS

//------------------------------------------------------------------------------

METHOD New( oParent ) CLASS XButtonEx

   ::Super:New( oParent )
   ::FoFontShortcut := TFont():Create( "Arial Narrow", 8,, 800 )

RETURN Self

//------------------------------------------------------------------------------

METHOD Free() CLASS XButtonEx

   IF !Empty( ::FoBitmaps ) .AND. ::FoBitmaps:oParent == Self
      ::FoBitmaps:End()
   ENDIF
   ::oFontShortcut:End()

RETURN ::Super:Free()

//------------------------------------------------------------------------------

METHOD SetStyle( nStyle ) CLASS XButtonEx

   IF nStyle != Nil
      ::FnStyle := nStyle
      ::Refresh( .T. )
   ENDIF

RETURN ::FnStyle

//------------------------------------------------------------------------------

METHOD SetFontShortcut( oFont ) CLASS XButtonEx

   IF ::FoFontShortcut != Nil
      ::FoFontShortcut:Destroy()
      ::FoFontShortcut := Nil
   ENDIF

   IF oFont != Nil
      IF ValType( oFont ) != "O"
         oFont := TFont():Create( oFont )
      ENDIF
      ::FoFontShortcut := oFont
   ENDIF

RETURN Nil

//--------------------------------------------------------------------------

METHOD SetBitmaps( oBitmaps ) CLASS XButtonEx

   IF ::FoBitmaps != Nil
      ::FoBitmaps:End()
      ::FoBitmaps := Nil
   ENDIF

   IF oBitmaps != Nil
      IF ValType( oBitmaps ) != "O"
         IF Empty( ::FoBitmaps )
            ::FoBitmaps := TImageList():Create( Self, ::nBmpWidth, ::nBmpHeight )
         ENDIF
         IF Valtype( oBitmaps ) = "A"
            ::cBitmap := oBitmaps[ 1 ]
            IF Len( oBitmaps ) = 1
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .T. )
            ELSEIF Len( oBitmaps ) = 2
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 2 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .T. )
            ELSEIF Len( oBitmaps ) = 3
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 2 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 1 ], .F. )
               ::FoBitmaps:Add( oBitmaps[ 3 ], .F. )
            ELSE
               AEval( oBitmaps, {| oBmp | ::FoBitmaps:Add( oBmp, .F. ) } )
            ENDIF
         ELSE
            ::cBitmap := oBitmaps
            ::FoBitmaps:Add( oBitmaps, .F. )
            ::FoBitmaps:Add( oBitmaps, .F. )
            ::FoBitmaps:Add( oBitmaps, .F. )
            ::FoBitmaps:Add( oBitmaps, .T. )
         ENDIF
      ELSE
         ::FoBitmaps := oBitmaps
      ENDIF
   ENDIF
   ::Refresh()

RETURN Nil

//--------------------------------------------------------------------------

#pragma BEGINDUMP

#define OEMRESOURCE

#include "Windows.h"
#include "CommCtrl.h"
#include "Xailer.h"
#include "Constants.ch"

extern XA_EXPORT HBITMAP XA_GetBmpTransparent( HBITMAP, COLORREF );

HB_FUNC_STATIC( XBUTTONEX_WMPAINT )
{
   PHB_ITEM Self = hb_stackSelfItem();
   HWND hWnd = GetHandleOf( Self );
   HANDLE hil = GetHandleOf( XA_ObjGetItem( Self, "oBitmaps" ) );
   int nCtlStyle = GetWindowLong( hWnd, GWL_STYLE );
   BOOL lHot = XA_ObjGetL( Self, "lHot" );
   BOOL lPushed = lHot && XA_ObjGetL( Self, "lPushed" );
   UINT uiStyle = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT;
   UINT uiState = PBS_NORMAL;
   UINT nTextLen, nShortcutLen, nImage = 0;
   char *cText = XA_ObjGetCLen( Self, "cText", &nTextLen );
   char *cShortcut = XA_ObjGetCLen( Self, "cShortcut", &nShortcutLen );
   int nImgWidth = 0, nImgHeight = 0, nTextWidth, nTextHeight;
   int nMargin = XA_ObjGetNL( Self, "nMargin" );
   int nOrientation = XA_ObjGetNL( Self, "nOrientation" );
   int nStyle = XA_ObjGetNL( Self, "nStyle" );
   UINT nFlags;
   RECT rect, rct = {0,0,0,0};
   CONTROLPAINT cp;
   HRGN hrgn;
   HFONT hFont;

   if( XA_ControlBeginPaint( Self, hWnd, &cp, (HDC) hb_parnl( 1 ) ) )
      {
      GetClientRect( hWnd, &rect );

      // si el puntero esta encima
      if( lHot )
         {
         nImage = 1;
         uiState = PBS_HOT;
         }

      // si esta pulsado
      if( lPushed )
         {
         uiStyle |= DFCS_PUSHED;
         uiState = PBS_PRESSED;
         nImage = 2;
         }

      // si esta desactivado
      else if( nCtlStyle & WS_DISABLED )
         {
         uiStyle |= DFCS_INACTIVE;
         uiState = PBS_DISABLED;
         nImage = 3;
         }

      // dibujar el marco y relleno del boton
      if( nStyle == bsWINDOWS7 )
         {
         DrawThemeParentBackground( hWnd, cp.hDC, &rect );
         hrgn = CreateRoundRectRgn( rect.left, rect.top, rect.right, rect.bottom, 2, 2 );
         SelectClipRgn( cp.hDC, hrgn );
         InflateRect( &rect, -1, -1 );
         if( lPushed )
            XA_CrystalFillV( cp.hDC, &rect, RGB( 211, 236, 249 ), RGB( 100, 177, 255 ), RGB(  44,  98, 139 ) );
         else if( lHot )
            XA_CrystalFillV( cp.hDC, &rect, RGB( 225, 243, 252 ), RGB( 175, 222, 248 ), RGB(  60, 127, 177 ) );
         else
            XA_CrystalFillV( cp.hDC, &rect, RGB( 240, 240, 240 ), RGB( 210, 210, 210 ), RGB( 114, 114, 114 ) );
         SelectClipRgn( cp.hDC, NULL );
         DeleteObject( hrgn );
         }
      else if( nStyle == bsMODERN )
         {
         int nClrPane = XA_ObjGetNL( Self, "nClrPane" );
         int nClrPane2 = RGB( min( MulDiv( GetRValue( nClrPane ), 120, 100 ), 255 ),
                              min( MulDiv( GetGValue( nClrPane ), 120, 100 ), 255 ),
                              min( MulDiv( GetBValue( nClrPane ), 120, 100 ), 255 ) );

         DrawThemeParentBackground( hWnd, cp.hDC, &rect );
         if( lPushed || lHot )
            InflateRect( &rect, -3, -3 );
         XA_GradientFillH( cp.hDC, &rect, nClrPane, nClrPane2 );
         if( lHot )
            {
            HPEN hPen = CreatePen( PS_SOLID, 3, XA_ObjGetNL( Self, "nClrBorder" ) ), hOldPen;

            if( !lPushed )
               InflateRect( &rect, 2, 2 );
            hOldPen = SelectObject( cp.hDC, hPen );
            MoveToEx( cp.hDC, rect.left, rect.top, NULL );
            LineTo( cp.hDC, rect.right - 1, rect.top );
            LineTo( cp.hDC, rect.right - 1, rect.bottom - 1 );
            LineTo( cp.hDC, rect.left, rect.bottom - 1 );
            LineTo( cp.hDC, rect.left, rect.top );
            SelectObject( cp.hDC, hOldPen );
            DeleteObject( hPen );
            }
         }
      else if( IsAppThemed() )
         {
         HTHEME hTheme = OpenThemeData( hWnd, L"Button" );
         hrgn = NULL;

         DrawThemeParentBackground( hWnd, cp.hDC, &rect );
         if( XA_GetWinVer() <= WINVER_7 )
            {
            hrgn = CreateRoundRectRgn( rect.left, rect.top, rect.right, rect.bottom, 1, 1 );
            SelectClipRgn( cp.hDC, hrgn );
            }
         DrawThemeBackground( hTheme, cp.hDC, BP_PUSHBUTTON, uiState, &rect, NULL );
         if( hrgn )
            {
            SelectClipRgn( cp.hDC, NULL );
            DeleteObject( hrgn );
            }
         InflateRect( &rect, -2, -2 );
         CloseThemeData( hTheme );
         }
      else
         {
         DrawFrameControl( cp.hDC, &rect, DFC_BUTTON, uiStyle );
         }

      rect.top    += 3;
      rect.left   += 3;
      rect.bottom -= 3;
      rect.right  -= 3;

      // obtiene las dimensiones del bitmap
      if ( hil )
         ImageList_GetIconSize( hil, &nImgWidth, &nImgHeight );

      // obtener el tamaņo que ocupara el texto
      hFont = SelectObject( cp.hDC, GetHandleOf( XA_ObjGetItem( Self, "oFont" ) ) );
      if( XA_ObjGetL( Self, "lMultiline" ) )
         nFlags = DT_WORDBREAK ;
      else
         nFlags = DT_END_ELLIPSIS | DT_SINGLELINE;
      switch( XA_ObjGetNL( Self, "nAlignment" ) )
         {
         case taRIGHT: nFlags |= DT_RIGHT; break;
         case taCENTER: nFlags |= DT_CENTER;
         }
      // Calcular las dimensiones del texto
      rct.right = rect.right - rect.left + 1;
      if( ( nOrientation == orLEFT ) || ( nOrientation == orRIGHT ) )
         rct.right -= nImgWidth;

      nTextHeight = DrawText( cp.hDC, cText, nTextLen, &rct, nFlags | DT_CALCRECT );
      nTextWidth = rct.right;

      // dibujar la imagen. Lo hace la propia ImageList
      if ( hil )
         {
         int nImgX = 0, nImgY = 0;

         if( nTextLen )
            {
            // si ::nMargin == -1, calcular el espacio entre borde e imagen
            if ( nMargin == -1 )
               {
               if( ( nOrientation == orLEFT ) || ( nOrientation == orRIGHT ) )
                  nMargin = ( rect.right - rect.left - nImgWidth - nTextWidth ) / 3;
               else
                  nMargin = ( rect.bottom - rect.top - nImgHeight - nTextHeight ) / 3;
               }

            switch( nOrientation )
               {
               case orLEFT:   nImgX = rect.left + nMargin;
                              nImgY = ( rect.top + rect.bottom - nImgHeight ) >> 1;
                              rect.left += nMargin + nImgWidth;
                              break;
               case orRIGHT:  nImgX = rect.right - nImgWidth - nMargin;
                              nImgY = ( rect.top + rect.bottom - nImgHeight ) >> 1;
                              rect.right = nImgX;
                              break;
               case orTOP:    nImgX = ( rect.left + rect.right - nImgWidth ) >> 1;
                              nImgY = rect.top + nMargin;
                              rect.top += nMargin + nImgHeight;
                              break;
               case orBOTTOM: nImgX = ( rect.left + rect.right - nImgWidth ) >> 1;
                              nImgY = rect.bottom - nImgHeight - nMargin;
                              rect.bottom = nImgY;
                              break;
               }
            }
         else  // No hay texto
            {
            nImgX = ( rect.left + rect.right - nImgWidth ) >> 1;
            nImgY = ( rect.top + rect.bottom - nImgHeight ) >> 1;
            }

         ImageList_Draw( hil, nImage, cp.hDC, nImgX, nImgY, ILD_TRANSPARENT );
         }

      // Dibujar el caption
      SetBkMode( cp.hDC, TRANSPARENT );

      // Calcular la posicion del texto
      switch( XA_ObjGetNL( Self, "nVAlignment" ) )
         {
         case vaCENTER: rect.bottom = ( rect.top + rect.bottom + nTextHeight ) / 2;
                        // NOTA: No hay break intencionadamente - [JFG]
                        /* fall through */
         case vaBOTTOM: rect.top = rect.bottom - nTextHeight;
         }

      // si esta desactivado, hay que dibujarlo "cincelado"
      if ( nCtlStyle & WS_DISABLED )
         {
         if( !IsAppThemed() )
            {
            rect.top++; rect.left++; rect.bottom++; rect.right++;
            SetTextColor( cp.hDC, GetSysColor( COLOR_3DHILIGHT ) );
            DrawText( cp.hDC, cText, nTextLen, &rect, nFlags );
            rect.top--; rect.left--; rect.bottom--; rect.right--;
            }
         SetTextColor( cp.hDC, GetSysColor( COLOR_3DSHADOW ) );
         }
      else
         SetTextColor( cp.hDC, nStyle == bsMODERN ? 0xFFFFFF : XA_ObjGetNL( Self, "nClrText" ) );

      DrawText( cp.hDC, cText, nTextLen, &rect, nFlags );

      // Pintar el "shortcut"
      if( nShortcutLen )
         {
         GetClientRect( hWnd, &rect );
         if( ( nStyle == bsMODERN ) && lPushed )
            {
            rect.top += 6;
            rect.right -= 8;
            }
         else
            {
            rect.top += 4;
            rect.right -= 6;
            }
         SelectObject( cp.hDC, GetHandleOf( XA_ObjGetItem( Self, "oFontShortcut" ) ) );
         DrawText( cp.hDC, cShortcut, nShortcutLen, &rect, DT_RIGHT | DT_SINGLELINE );
         }

      SelectObject( cp.hDC, hFont );
      }
   XA_ControlEndPaint( Self, hWnd, &cp );

   hb_retnl( 0 );
}

//------------------------------------------------------------------------------

HB_FUNC_STATIC( GETTEXTWIDTH )   // GetTextWidth( oFont, cText, [nFlags] )
{
   HFONT hFont = (HFONT) GetHandleOf( hb_param( 1, HB_IT_OBJECT ) ), hOldFont;
   LPCSTR cText = hb_parc( 2 );
   int nLen = hb_parclen( 2 ), nFlags = hb_parnl( 3 );
   HDC hdc = GetDC( GetDesktopWindow() );
   RECT rect;

   rect.left = 0;
   rect.top = 0;
   rect.right = 32767;
   rect.bottom = 32767;
   hOldFont = SelectObject( hdc, hFont );
   DrawText( hdc, cText, nLen, &rect, nFlags | DT_CALCRECT );
   SelectObject( hdc, hOldFont );
   ReleaseDC( GetDesktopWindow(), hdc );

   hb_retnl( rect.right );
}

#pragma ENDDUMP

//------------------------------------------------------------------------------

CLASS TDropMenu FROM TForm STATIC

   PROPERTY oButton
   PROPERTY oBevel
   PROPERTY oList
   PROPERTY nDirection  INIT orBOTTOM
   PROPERTY lBusy       INIT .F.

   METHOD New( oParent )
   METHOD Drop( nHeight )
   METHOD Closeup()
   METHOD KeyClick( oSender, nKey, cChar, nKeyCode )
   METHOD KeyDown( oSender, nKey, nFlags )
   METHOD WMActivate()
   METHOD WMNCDestroy() INLINE ::oButton:lDropped := .F., ::Super:WMNCDestroy()

ENDCLASS

//------------------------------------------------------------------------------

METHOD New( oParent ) CLASS TDropMenu

   LOCAL nTop, nLeft, nWidth := 0, nHeight := 0, n, aItems := {}
   LOCAL nItemHeight := 60, nItemSpacing, nItemWidth, nCols := 1
   LOCAL oMonitor := oParent:GetMonitor()

   oParent:lDropped := .T.
   oParent:oForm:Update()
   oParent:oForm:lActive := .T.
   ::oButton := oParent

   ::Super:New( oParent:oForm )
   ::lAutoScroll := .F.
   ::nBorderStyle := bsNONE
   IF Application:nWinVer > 5  // WinXP & >
      #define CS_DROPSHADOW       0x00020000
      ::nClassStyle := CS_DROPSHADOW
   ENDIF
   ::nClrPane := oParent:nClrPaneItems
   IF oParent:nStyle != bsMODERN
      ::nClrPaneEnd := oParent:nClrPane
      ::nGradient := grVERTICAL
   ENDIF
   ::oFont := TFont():Create( oParent:oFont:cName, oParent:oFont:nSize + 4,, oParent:oFont:nWeight )
   ::OnKeyDown := "KeyDown"
   ::Create()

   FOR n := 1 TO Len( oParent:aItems )
      IF Empty( oParent:aItems[ n ] )
         nHeight += 24
      ELSE
         AAdd( aItems, { 0, nHeight, 0, nItemHeight, 0, 0, oParent:aItems[ n ] } )
         nHeight += nItemHeight + 10
         nWidth := Max( nWidth, GetTextWidth( ::oFont, ATail( aItems )[ 7 ] ) )
      ENDIF
   NEXT
   nWidth += 30
   AEval( aItems, {|x| x[ 3 ] := nWidth } )
   nWidth += 10

   WITH OBJECT ::oBevel := TBevel():New( Self )
      :nAlign := alCLIENT
      :nBorderStyle := bvFLAT
      :lTransparent := .T.
      :Create()
   END

   WITH OBJECT ::oList := TScreenKeyboard():New( ::oBevel )
      :SetBounds( 10, 10, nWidth + 2, nHeight + 2 )
      :nBorderStyle := bvNONE
      :lTransparent := .T.
      :SetKeys( aItems, nWidth, nHeight )
      :nStyle := oParent:nStyle
      :nClrKeys := oParent:nClrPane
      :nClrText := oParent:nClrText
      :OnKeyClick := "KeyClick"
      :Create()
      :nHotKey := 1
      :nSelectedKey := 1
   END

   ::nWidth := ::oList:nWidth + ::oList:nLeft + 2
   nTop := oParent:nHeight
   nLeft := 0
   ClientToScreen( oParent:Handle, @nLeft, @nTop )
   ::SetBounds( nLeft, nTop )
   IF ::nTop + ::oList:nTop + ::oList:nHeight > oMonitor:nClientTop + oMonitor:nClientHeight
      IF ::oList:nTop + ::oList:nHeight <= nTop - oParent:nHeight  // Cabe por arriba
         ::nTop := nTop + oMonitor:nClientTop - oParent:nHeight
         ::nDirection := orTOP
      ELSE  // Partir en columnas
         nItemWidth := ::oList:aKeys[ 1, 3 ] - 24
         nItemHeight := ::oList:aKeys[ 1, 4 ]
         nItemSpacing := ::oList:nTop
         nHeight := Int( ( oMonitor:nClientHeight - ::nTop - ::oList:nTop ) / ( nItemHeight + nItemSpacing ) )
         nCols := Int( ( Len( aItems ) - 1 ) / nHeight ) + 1
         IF ::nLeft + nCols * ( nItemWidth + nItemSpacing ) > oMonitor:nClientLeft + oMonitor:nClientWidth
            IF nCols * ( nItemWidth + nItemSpacing ) > oMonitor:nClientWidth
               nCols := Int( ( oMonitor:nClientLeft + oMonitor:nClientWidth - ::nLeft ) / ( nItemWidth + nItemSpacing ) )
            ELSE
               ::nLeft := oMonitor:nClientLeft + Max( 0, ::nLeft - nCols * ( nItemWidth + nItemSpacing ) - nItemSpacing + oParent:nWidth )
            ENDIF
         ENDIF
         nHeight := Int( ( Len( aItems ) - 1 ) / nCols + 1 ) * ( nItemHeight + nItemSpacing )
         ::SetBounds( ,, nCols * ( nItemWidth + nItemSpacing ) + nItemSpacing, nHeight + nItemSpacing )
         ::oList:SetBounds( nItemSpacing, nItemSpacing, nCols * ( nItemWidth + nItemSpacing ), nHeight )
         nHeight := 0
         nLeft := 0
         FOR n := 1 TO Len( aItems )
            IF nHeight >= ::oList:nHeight
               nHeight := 0
               nLeft += nItemWidth + nItemSpacing
            ENDIF
            aItems[ n, 1 ] := nLeft
            aItems[ n, 2 ] := nHeight
            aItems[ n, 3 ] := nItemWidth
            aItems[ n, 4 ] := nItemHeight
            nHeight += nItemHeight + nItemSpacing
         NEXT
         ::oList:SetKeys( aItems, ::oList:nWidth, ::oList:nHeight )
      ENDIF
   ENDIF
   IF ::nLeft + ::nWidth >= oMonitor:nClientLeft + oMonitor:nClientWidth
      ::nLeft := oMonitor:nClientLeft + oMonitor:nClientWidth - ::nWidth - 1
      IF ::nLeft < oMonitor:nClientLeft
         ::nLeft := oMonitor:nClientLeft
      ENDIF
   ENDIF
   IF ::nTop + ::nHeight >= oMonitor:nClientTop + oMonitor:nClientHeight
      ::nTop := oMonitor:nClientTop + oMonitor:nClientHeight - ::nHeight - 1
      IF ::nTop < oMonitor:nClientTop
         ::nTop := oMonitor:nClientTop
      ENDIF
   ENDIF
   ::Drop( ::oList:nTop + ::oList:nHeight + 2 )
   oParent:oForm:lActive := .F.

RETURN Self

//------------------------------------------------------------------------------

METHOD Drop( nHeight ) CLASS TDropMenu

   LOCAL n, h := Round( nHeight / 5, 0 )
   LOCAL nTop := ::nTop

   IF ::nDirection == orBOTTOM
      ::nHeight := nHeight
   ELSE
      ::SetBounds( , nTop - nHeight,, nHeight )
   ENDIF
   ::oButton:OnDrop( Self )
   IF !lAnd( ::oButton:nEffects, beSLIDE ) .OR. !AnimateWindow( ::Handle, 120, IIF( ::nDirection == orBOTTOM, AW_VER_POSITIVE, AW_VER_NEGATIVE ) + AW_ACTIVATE + AW_SLIDE )
      ::Show()
   ENDIF
   ::Refresh( .T. )

RETURN Nil

//------------------------------------------------------------------------------

METHOD Closeup() CLASS TDropMenu

   LOCAL n, h := Round( ::nHeight / 5, 0 )

   IF IsWindowVisible( ::Handle )
      IF lAnd( ::oButton:nEffects, beSLIDE )
         AnimateWindow( ::Handle, 120, IIF( ::nDirection == orBOTTOM, AW_VER_NEGATIVE, AW_VER_POSITIVE ) + AW_HIDE + AW_SLIDE )
      ENDIF
      ::Hide()
      ::PostMsg( WM_CLOSE )
   ENDIF

RETURN Nil

//------------------------------------------------------------------------------

METHOD KeyClick( oSender, nKey, cChar, nKeyCode ) CLASS TDropMenu

   LOCAL lOk

   IF Empty( ::oButton:nEffects )
      // NOTA: Si el OnClick de un item de menu del boton cierra el formulario, el foco pasa
      // a otra ventana. Para evitarlo, basta con poner nEffects = beNONE
      ::Closeup()
   ENDIF

   ::lBusy := .T.
   lOk := ::oButton:OnClick( nKey )
   ::lBusy := .F.

   IF ValType( lOk ) != "L" .OR. lOk
      ::Closeup()
   ENDIF

RETURN .F.

//------------------------------------------------------------------------------

METHOD KeyDown( oSender, nKey, nFlags ) CLASS TDropMenu

   IF nKey == VK_RETURN
      IF ::oList:nSelectedKey > 0
         ::KeyClick( oSender, ::oList:nSelectedKey )
      ENDIF
      RETURN 0
   ELSEIF nKey == VK_ESCAPE
      ::Closeup()
      RETURN 0
   ELSEIF nKey == VK_DOWN
      WITH OBJECT ::oList
         IF ++:nSelectedKey > Len( :aKeys )
            :nSelectedKey := 1
         ENDIF
         :nHotKey := :nSelectedKey
         :Refresh()
      END
      RETURN 0
   ELSEIF nKey == VK_UP
      WITH OBJECT ::oList
         IF --:nSelectedKey <= 0
            :nSelectedKey := Len( :aKeys )
         ENDIF
         :nHotKey := :nSelectedKey
         :Refresh()
      END
      RETURN 0
   ENDIF

RETURN Nil

//------------------------------------------------------------------------------

METHOD WMActivate( nWParam, hNext ) CLASS TDropMenu

   IF LoWord( nWParam ) == 0 .AND. !::lBusy
      // Deactivate
      IF hNext == 0 .OR. hNext != ::oParent:Handle
         // El foco pasa a otra ventana distinta de ::oParent
         ::Closeup()
         ::oParent:PostMsg( WM_NCACTIVATE, .F. )
      ELSE
         // El foco pasa a ::oParent
         ::Closeup()
      ENDIF
   ENDIF

RETURN Nil

//------------------------------------------------------------------------------
