/******************************Module*Header*******************************\
* Module Name: buttons.c
*
* Bitmap button support.  On Daytona bitmap buttons are provided by
* mmcntrls.  On Chicago there is no mmcntrls, so we use the functions
* in this file.
*
*
* Created: 19-04-94
* Author:  Stephen Estrop [StephenE]
*
* Copyright (c) 1993 Microsoft Corporation
\**************************************************************************/
#pragma warning( once : 4201 4214 )

#define NOOLE
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#include "buttons.h"
#include "literals.h"


/* -------------------------------------------------------------------------
** Color globals
** -------------------------------------------------------------------------
*/
int         nSysColorChanges = 0;
DWORD       rgbFace;
DWORD       rgbShadow;
DWORD       rgbHilight;
DWORD       rgbFrame;



/*****************************Private*Routine******************************\
* PatB
*
* Fast way to fill an rectangle with a solid colour.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
PatB(
    HDC hdc,
    int x,
    int y,
    int dx,
    int dy,
    DWORD rgb
    )
{
    RECT    rc;

    SetBkColor(hdc,rgb);
    rc.left   = x;
    rc.top    = y;
    rc.right  = x + dx;
    rc.bottom = y + dy;

    ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
}




/*****************************Private*Routine******************************\
* CheckSysColors
*
* Checks the system colors and updates the cached global variables if
* they have changed.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
CheckSysColors(
    void
    )
{
   static COLORREF rgbSaveFace    = 0xffffffffL,
                   rgbSaveShadow  = 0xffffffffL,
                   rgbSaveHilight = 0xffffffffL,
                   rgbSaveFrame   = 0xffffffffL;

   rgbFace    = GetSysColor(COLOR_BTNFACE);
   rgbShadow  = GetSysColor(COLOR_BTNSHADOW);
   rgbHilight = GetSysColor(COLOR_BTNHIGHLIGHT);
   rgbFrame   = GetSysColor(COLOR_WINDOWFRAME);

   if (rgbSaveFace!=rgbFace || rgbSaveShadow!=rgbShadow
      || rgbSaveHilight!=rgbHilight || rgbSaveFrame!=rgbFrame)
   {
      ++nSysColorChanges;

      rgbSaveFace    = rgbFace;
      rgbSaveShadow  = rgbShadow;
      rgbSaveHilight = rgbHilight;
      rgbSaveFrame   = rgbFrame;

   }
}


/* -------------------------------------------------------------------------
** Button globals  -- some of these should be constants
** -------------------------------------------------------------------------
*/
const TCHAR   szBbmProp[]     = TEXT("ButtonBitmapProp");
const TCHAR   szButtonProp[]  = TEXT("ButtonProp");

typedef struct tagBTNSTATE {      /* instance data for toolbar window */
    WNDPROC     lpfnDefProc;
    HWND        hwndToolTips;
    HINSTANCE   hInst;
    UINT        wID;
    UINT        uStyle;
    HBITMAP     hbm;
    HDC         hdcGlyphs;
    HDC         hdcMono;
    HBITMAP     hbmMono;
    HBITMAP     hbmDefault;
    int         dxBitmap;
    int         dyBitmap;
    int         nButtons;
    int         nSysColorChanges;
    BITMAPBTN   Buttons[1];
} BTNSTATE, NEAR *PBTNSTATE, FAR *LPBTNSTATE;

typedef struct {
    WNDPROC     lpfnDefProc;
    HWND        hwndParent;
    HWND        hwndToolTips;
} BTN_INFO, *LPBTN_INFO;


LRESULT CALLBACK
ButtonSubclassProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    );

LRESULT CALLBACK
ParentSubclassProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    );

void FAR PASCAL
RelayToToolTips(
    HWND hwndToolTips,
    HWND hWnd,
    UINT wMsg,
    WPARAM wParam,
    LPARAM lParam
    );

BOOL
InitObjects(
    LPBTNSTATE pTBState
    );

BOOL
FreeObjects(
    LPBTNSTATE pTBState
    );

void
CreateButtonMask(
    LPBTNSTATE pTBState,
    PBITMAPBTN pTBButton
    );


/*****************************Private*Routine******************************\
* InitObjects
*
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
InitObjects(
    LPBTNSTATE pTBState
    )
{
    pTBState->hdcGlyphs = CreateCompatibleDC(NULL);
    if (pTBState->hdcGlyphs == NULL ) {
        return FALSE;
    }

    pTBState->hdcMono = CreateCompatibleDC(NULL);
    if (pTBState->hdcMono == NULL ) {
        DeleteObject( pTBState->hdcGlyphs );
        return FALSE;
    }

    pTBState->hbmMono = CreateBitmap( pTBState->dxBitmap,
                                      pTBState->dyBitmap, 1, 1, NULL);
    if ( pTBState->hbmMono == NULL ) {
        DeleteObject( pTBState->hdcGlyphs );
        DeleteObject( pTBState->hdcMono );
        return FALSE;
    }

    pTBState->hbmDefault = SelectObject(pTBState->hdcMono, pTBState->hbmMono);

    return TRUE;
}


/*****************************Private*Routine******************************\
* FreeObjects
*
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
FreeObjects(
    LPBTNSTATE pTBState
    )
{
    if (pTBState->hdcMono) {
        SelectObject(pTBState->hdcMono, pTBState->hbmDefault);
        DeleteDC(pTBState->hdcMono);              /* toast the DCs */
    }

    if (pTBState->hdcGlyphs) {
        DeleteDC(pTBState->hdcGlyphs);
    }

    if (pTBState->hbmMono) {
        DeleteObject(pTBState->hbmMono);
    }

    return TRUE;
}



/*****************************Private*Routine******************************\
* CreateButtonMask
*
* create a mono bitmap mask:
*   1's where color == COLOR_BTNFACE || COLOR_HILIGHT
*   0's everywhere else
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
CreateButtonMask(
    LPBTNSTATE pTBState,
    PBITMAPBTN pTBButton
    )
{
    /* initalize whole area with 0's */
    PatBlt( pTBState->hdcMono, 0, 0, pTBState->dxBitmap,
            pTBState->dyBitmap, WHITENESS);

    /* create mask based on color bitmap
    ** convert this to 1's
    */
    SetBkColor(pTBState->hdcGlyphs, rgbFace);
    BitBlt( pTBState->hdcMono, 0, 0, pTBState->dxBitmap, pTBState->dyBitmap,
            pTBState->hdcGlyphs, pTBButton->iBitmap * pTBState->dxBitmap, 0,
            SRCCOPY );

    /* convert this to 1's */
    SetBkColor(pTBState->hdcGlyphs, rgbHilight);

    /* OR in the new 1's */
    BitBlt( pTBState->hdcMono, 0, 0, pTBState->dxBitmap, pTBState->dyBitmap,
            pTBState->hdcGlyphs, pTBButton->iBitmap * pTBState->dxBitmap, 0,
            SRCPAINT );
}



#define PSDPxax     0x00B8074A


/*****************************Private*Routine******************************\
* BtnDrawButton
*
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void WINAPI
BtnDrawButton(
    HWND hwnd,
    HDC hdc,
    int dx,
    int dy,
    LPBITMAPBTN ptButton
    )
{
    int         glyph_offset;
    HBRUSH      hbrOld, hbr;
    BOOL        bMaskCreated = FALSE;
    RECT        rcFocus;
    PBTNSTATE   pTBState;
    int         x = 0, y = 0;


    pTBState = (PBTNSTATE)GetProp(hwnd, szBbmProp);

    CheckSysColors();
    if (pTBState->nSysColorChanges != nSysColorChanges) {

        DeleteObject( pTBState->hbm );
        pTBState->hbm = CreateMappedBitmap( pTBState->hInst,
                                            pTBState->wID, TRUE, NULL, 0);
        pTBState->nSysColorChanges = nSysColorChanges;
    }

    /*
    ** erase with face color
    */

    PatB(hdc, x, y, dx, dy, rgbFace);
    SetRect( &rcFocus, x, y, x + dx, y + dy );

    if (ptButton->fsState & BTNSTATE_PRESSED) {
        DrawEdge( hdc, &rcFocus, EDGE_SUNKEN, BF_RECT );
        glyph_offset = 1;
    }
    else {
        DrawEdge( hdc, &rcFocus, EDGE_RAISED, BF_RECT );
        glyph_offset = 0;
    }


    /*
    ** make the coordinates the interior of the button
    */
    x += 2;
    y += 2;
    dx -= 4;
    dy -= 4;

    SelectObject( pTBState->hdcGlyphs, pTBState->hbm );

    /* now put on the face */

    /*
    ** We need to centre the Bitmap here within the button
    */
    x += (dx - pTBState->dxBitmap ) / 2;
    y += (dy - pTBState->dyBitmap ) / 2;

    if (!(ptButton->fsState & BTNSTATE_DISABLED)) {

        /* regular version */
        BitBlt( hdc, x + glyph_offset, y + glyph_offset,
                pTBState->dxBitmap, pTBState->dyBitmap,
                pTBState->hdcGlyphs,
                ptButton->iBitmap * pTBState->dxBitmap, 0, SRCCOPY);
    }
    else {

        /* disabled version */
        bMaskCreated = TRUE;
        CreateButtonMask(pTBState, ptButton );

        SetTextColor(hdc, 0L);          /* 0's in mono -> 0 (for ROP) */
        SetBkColor(hdc, 0x00FFFFFF);    /* 1's in mono -> 1 */

        hbr = CreateSolidBrush(rgbHilight);
        if (hbr) {
            hbrOld = SelectObject(hdc, hbr);
            if (hbrOld) {
                /* draw hilight color where we have 0's in the mask */
                BitBlt( hdc, x + 1, y + 1,
                        pTBState->dxBitmap, pTBState->dyBitmap,
                        pTBState->hdcMono, 0, 0, PSDPxax);
                SelectObject(hdc, hbrOld);
            }
            DeleteObject(hbr);
        }

        hbr = CreateSolidBrush(rgbShadow);
        if (hbr) {
            hbrOld = SelectObject(hdc, hbr);
            if (hbrOld) {
                /* draw the shadow color where we have 0's in the mask */
                BitBlt(hdc, x, y, pTBState->dxBitmap, pTBState->dyBitmap,
                       pTBState->hdcMono, 0, 0, PSDPxax);
                SelectObject(hdc, hbrOld);
            }
            DeleteObject(hbr);
        }
    }

    if (ptButton->fsState & ODS_FOCUS) {

        BtnDrawFocusRect(hdc, &rcFocus, ptButton->fsState);
    }
}



/*****************************Private*Routine******************************\
* BtnCreateBitmapButtons
*
* Returns TRUE if successful, otherwise FALSE;
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL WINAPI
BtnCreateBitmapButtons(
    HWND hWnd,
    HINSTANCE hInst,
    UINT wID,
    UINT uStyle,
    LPBITMAPBTN lpButtons,
    int nButtons,
    int dxBitmap,
    int dyBitmap
    )
{
    PBTNSTATE pTBState;


    /*
    ** If we have already created Bitmap Buttons for this
    ** window just return.
    */
    if (GetProp(hWnd, szBbmProp)) {
        return TRUE;
    }


    CheckSysColors();

    /*
    ** Allocate the required storage and save the pointer in the window
    ** property list.
    */
    pTBState = (PBTNSTATE)LocalAlloc( LMEM_FIXED,
                                      (sizeof(BTNSTATE) - sizeof(BITMAPBTN)) +
                                      (nButtons * sizeof(BITMAPBTN)) );
    if (pTBState == NULL ) {
        return FALSE;
    }
    SetProp(hWnd, szBbmProp, (HANDLE)pTBState);


    pTBState->hInst       = hInst;
    pTBState->wID         = wID;
    pTBState->uStyle      = uStyle;
    pTBState->nButtons    = nButtons;
    pTBState->hbm         = CreateMappedBitmap( hInst, wID, TRUE, NULL, 0);
    pTBState->dxBitmap    = dxBitmap;
    pTBState->dyBitmap    = dyBitmap;

    InitObjects( pTBState );

    CopyMemory( pTBState->Buttons, lpButtons, nButtons * sizeof(BITMAPBTN) );

    /*
    ** Does the caller want tool tips ?
    */
    if (pTBState->uStyle & BBS_TOOLTIPS) {

        pTBState->hwndToolTips = CreateWindow(TOOLTIPS_CLASS, g_szEmpty,
                                              WS_POPUP,
                                              CW_USEDEFAULT, CW_USEDEFAULT,
                                              CW_USEDEFAULT, CW_USEDEFAULT,
                                              hWnd, NULL, hInst, NULL);

        if (pTBState->hwndToolTips != (HWND)NULL ) {

            int         i;
            TOOLINFO    ti;

            pTBState->lpfnDefProc = SubclassWindow( hWnd, ParentSubclassProc );

            ti.uFlags = 0;
            ti.cbSize = sizeof(ti);
            ti.lpszText = LPSTR_TEXTCALLBACK;

            for ( i = 0; i < nButtons; i++ ) {

                LPBTN_INFO  lpBtnInfo;
                HWND        hwndBtn;

                hwndBtn = GetDlgItem(hWnd, pTBState->Buttons[i].uId);
                if ( hwndBtn == (HWND)NULL ) {
                    break;
                }

                lpBtnInfo = (LPBTN_INFO)LocalAlloc(LPTR, sizeof(BTN_INFO));
                if (lpBtnInfo == NULL ) {
                    break;
                }

                SetProp(hwndBtn, szButtonProp, (HANDLE)lpBtnInfo);
                lpBtnInfo->hwndToolTips = pTBState->hwndToolTips;
                lpBtnInfo->hwndParent   = hWnd;
                lpBtnInfo->lpfnDefProc = SubclassWindow( hwndBtn,
                                                         ButtonSubclassProc );

                ti.hwnd = hwndBtn;
                ti.uId = pTBState->Buttons[i].uId;

                GetClientRect( hwndBtn, &ti.rect );
                SendMessage( lpBtnInfo->hwndToolTips, TTM_ADDTOOL,
                             (WPARAM)0, (LPARAM)&ti );


                /*
                ** Add the same rectangle in parent co-ordinates so that
                ** the tooltip still gets displayed even though the button
                ** is disabled.
                */
                MapWindowRect( hwndBtn, hWnd, &ti.rect );
                ti.hwnd = hWnd;
                SendMessage( lpBtnInfo->hwndToolTips, TTM_ADDTOOL,
                             (WPARAM)0, (LPARAM)&ti );
            }

        }
        else {

            /*
            ** No tips available, just remove the BBS_TOOLTIPS style
            */
            pTBState->uStyle &= ~BBS_TOOLTIPS;
        }
    }

    return TRUE;
}

/******************************Public*Routine******************************\
* BtnDestroyBitmapButtons
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
void WINAPI
BtnDestroyBitmapButtons(
    HWND hwnd
    )
{
    PBTNSTATE pTBState;

    pTBState = (PBTNSTATE)GetProp(hwnd, szBbmProp);
    if ( pTBState != NULL ) {

        DeleteObject( pTBState->hbm );
        FreeObjects( pTBState );
    }
    RemoveProp(hwnd, szBbmProp);
}


/******************************Public*Routine******************************\
* BtnDrawFocusRect
*
* Use this function to draw focus rectangle around a bitmap button.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void WINAPI
BtnDrawFocusRect(
    HDC hdc,
    const RECT *lpRect,
    UINT fsState
    )
{
    int     iFaceOffset;
    RECT    rc;

    CopyRect( &rc, lpRect );

    rc.top = rc.left = 3;

    if (fsState & ODS_SELECTED) {
        iFaceOffset = 2;
    }
    else {
        iFaceOffset = 4;
    }

    rc.right  -= iFaceOffset;
    rc.bottom -= iFaceOffset;

    SetBkColor( hdc, rgbFace );
    DrawFocusRect( hdc, &rc );
}


/******************************Public*Routine******************************\
* BtnUpdateColors
*
* After a WM_SYSCOLORCHANGE message is received this function should be
* called to update the colors of the button bitmaps.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void WINAPI
BtnUpdateColors(
    HWND hwnd
    )
{
    PBTNSTATE   pTBState;

    pTBState = (PBTNSTATE)GetProp(hwnd, szBbmProp);
    if (pTBState->nSysColorChanges != nSysColorChanges)
    {
        DeleteObject( pTBState->hbm );
        pTBState->hbm = CreateMappedBitmap( pTBState->hInst,
                                            pTBState->wID, TRUE, NULL, 0);

        pTBState->nSysColorChanges = nSysColorChanges;
    }
}


/******************************Public*Routine******************************\
* ButtonSubclassProc
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
LRESULT CALLBACK
ButtonSubclassProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    LPBTN_INFO  lpBtnInfo;
    WNDPROC     lpfnDefProc;


    lpBtnInfo = (LPBTN_INFO)GetProp( hwnd, szButtonProp );

    /*
    ** Save this in case anything happens to lpBtnInfo before we return.
    */
    lpfnDefProc = lpBtnInfo->lpfnDefProc;

    switch ( uMsg ) {

    case WM_DESTROY:
        SubclassWindow( hwnd, lpfnDefProc );
        if (lpBtnInfo) {
            LocalFree((HLOCAL)lpBtnInfo);
            RemoveProp(hwnd, szButtonProp);
        }
        break;

    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_MOUSEMOVE:
        RelayToToolTips( lpBtnInfo->hwndToolTips, hwnd, uMsg, wParam, lParam );
        break;

    case WM_MOVE:
        {
            TOOLINFO    ti;

            ti.cbSize = sizeof(ti);
            ti.uFlags = 0;
            ti.hwnd = hwnd;
            ti.lpszText = LPSTR_TEXTCALLBACK;
            ti.uId = GetDlgCtrlID( hwnd );

            GetClientRect( hwnd, &ti.rect );

            SendMessage( lpBtnInfo->hwndToolTips, TTM_NEWTOOLRECT, 0,
                         (LPARAM)&ti );

            /*
            ** Add the same rectangle in parent co-ordinates so that
            ** the tooltip still gets displayed even though the button
            ** is disabled.
            */
            MapWindowRect( hwnd, lpBtnInfo->hwndParent, &ti.rect );
            ti.hwnd = lpBtnInfo->hwndParent;
            SendMessage( lpBtnInfo->hwndToolTips, TTM_NEWTOOLRECT,
                         (WPARAM)0, (LPARAM)&ti );
        }
        break;

    case WM_NOTIFY:
        SendMessage(lpBtnInfo->hwndParent, WM_NOTIFY, wParam, lParam);
        break;

    }

    return CallWindowProc(lpfnDefProc, hwnd, uMsg, wParam, lParam);
}


/******************************Public*Routine******************************\
* ParentSubclassProc
*
* Why do I need to subclass the buttons parent window ?  Well,
* if a button is disable it will not receive mouse messages, the
* messages go to the window underneath the button (ie. the parent).
* Therefore we detect this and relay the mouse message to the tool tips
* window as above.
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
LRESULT CALLBACK
ParentSubclassProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    WNDPROC     lpfnDefProc;
    PBTNSTATE   pTBState;


    pTBState = (PBTNSTATE)GetProp(hwnd, szBbmProp);

    /*
    ** Save this in case anything happens to lpBtnInfo before we return.
    */
    lpfnDefProc = pTBState->lpfnDefProc;

    switch ( uMsg ) {

    case TB_GETTOOLTIPS:
        return (LRESULT)(UINT)pTBState->hwndToolTips;

    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_MOUSEMOVE:
    case WM_NCMOUSEMOVE:
        RelayToToolTips( pTBState->hwndToolTips, hwnd, uMsg, wParam, lParam );
        break;
    }

    return CallWindowProc(lpfnDefProc, hwnd, uMsg, wParam, lParam);
}

/******************************Public*Routine******************************\
* RelayToToolTips
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
void FAR PASCAL
RelayToToolTips(
    HWND hwndToolTips,
    HWND hWnd,
    UINT wMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    if(hwndToolTips) {
        MSG msg;
        msg.lParam = lParam;
        msg.wParam = wParam;
        msg.message = wMsg;
        msg.hwnd = hWnd;
        SendMessage(hwndToolTips, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
    }
}