/*
 * GIZMOBAR.C
 * GizmoBar Version 1.01
 *
 * Contains the main window procedure of the GizmoBar control
 * that handles mouse logic and Windows messages.
 *
 * Copyright (c)1993-1994 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */


#include <windows.h>
#include "gizmoint.h"



/*
 * GizmoBarWndProc
 *
 * Purpose:
 *  Window Procedure for the GizmoBar custom control.  Handles all
 *  messages like WM_PAINT just as a normal application window would.
 *  Any message not processed here should go to DefWindowProc.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Standard
 */

LRESULT WINAPI GizmoBarWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    BOOL            fSuccess;
    BOOL            fTemp;
    LPCREATESTRUCT  pCreate;
    PGIZMOBAR       pGB;
    PGIZMO          pGizmo;
    RECT            rc;
    POINT           pt;
    short           x, y;
    COLORREF        cr;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    //Pass control messages onto another function for processing.
    if (iMsg >= WM_USER)
        return GBMessageHandler(hWnd, iMsg, wParam, lParam, pGB);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pCreate=(LPCREATESTRUCT)lParam;

            pGB=GizmoBarPAllocate(&fSuccess, hWnd
                , pCreate->hInstance, pCreate->hwndParent
                , pCreate->style, 0, (UINT)pCreate->hMenu);

            if (!fSuccess)
                {
                GizmoBarPFree(pGB);
                return -1L;
                }
            else
                SetWindowLong(hWnd, GBWL_STRUCTURE, (LONG)pGB);

            return DefWindowProc(hWnd, iMsg, wParam, lParam);


        case WM_DESTROY:
            /*
             * We want to clean up before DestroyWindow nukes all the
             * children, so WM_DESTROY is a better to do it than
             * WM_NCDESTROY.
             */
            GizmoBarPFree(pGB);
            break;


        case WM_ERASEBKGND:
            /*
             * Eat this message to avoid erasing portions that
             * we are going to repaint in WM_PAINT.  Part of a
             * change-state-and-repaint strategy is to rely on
             * WM_PAINT to do anything visual, which includes
             * erasing invalid portions.  Letting WM_ERASEBKGND
             * erase the background is redundant.
             */
            return TRUE;

       #ifdef WIN32
        case WM_CTLCOLORBTN:
        case WM_CTLCOLORSTATIC:
            fTemp=TRUE;
       #else
        case WM_CTLCOLOR:
            //Change the color of static text on the GizmoBar.
            fTemp=(HIWORD(lParam)==CTLCOLOR_STATIC
                || HIWORD(lParam)==CTLCOLOR_BTN);
       #endif

            if (fTemp)
                {
                cr=GetSysColor(COLOR_BTNFACE);
                SetTextColor((HDC)wParam
                    , GetSysColor(COLOR_BTNTEXT));
                SetBkColor((HDC)wParam, cr);

                /*
                 * If the system colors have changed, then crFace
                 * will not be equal to COLOR_BTNFACE, so we
                 * reinitialize the background brush.  This scheme
                 * handles system color changes appropriately
                 * without processing WM_WININICHANGE and without
                 * blindly creating a new brush on every WM_CTLCOLOR
                 * message.
                 */
                if (cr!=pGB->crFace)
                    {
                    pGB->crFace=cr;

                    if (NULL!=pGB->hBrFace)
                        DeleteObject(pGB->hBrFace);

                    pGB->hBrFace=CreateSolidBrush(pGB->crFace);
                    }

                return (LONG)(UINT)pGB->hBrFace;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);


        case WM_PAINT:
            GizmoBarPaint(hWnd, pGB);
            break;


        case WM_SETFONT:
            /*
             * wParam has the new font that we now send to all other
             * windows controls in us.  We control repaints here to
             * prevent a lot of repainting for each control.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            if ((WPARAM)NULL!=wParam)
                {
                pGB->hFont=(HFONT)wParam;
                GizmoPEnum(&pGB->pGizmos, FEnumChangeFont
                    , (DWORD)(LPTSTR)pGB);

                DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
                InvalidateRect(hWnd, NULL, FALSE);
                UpdateWindow(hWnd);
                }

            break;


        case WM_GETFONT:
            return (LRESULT)(UINT)pGB->hFont;


        case WM_ENABLE:
            /*
             * wParam has the new enable flag that we use to enable
             * or disable ALL controls in us at one time.  We also
             * turn the redraw off to prevent a lot of flicker.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            pGB->fEnabled=(BOOL)wParam;
            GizmoPEnum(&pGB->pGizmos, FEnumEnable
                , (DWORD)(LPTSTR)pGB);

            DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;


        case WM_CANCELMODE:
            pGizmo=pGB->pGizmoTrack;

            pGB->fTracking=FALSE;
            pGB->fMouseOut=FALSE;

            if (NULL!=pGizmo)
                GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

            ReleaseCapture();
            break;


        case WM_LBUTTONDBLCLK:
        case WM_LBUTTONDOWN:
            //Get the mouse coordinates.
            x=LOWORD(lParam);
            y=HIWORD(lParam);


            /*
             * See if we hit a command or attribute gizmo or not.
             * Anything else that is a control will get the message
             * instead of us anyway, so we don't have to check.
             * FEnumHitTest also validates drawn gizmos, enabled, and
             * visible, so we don't.
             */
            pGizmo=GizmoPEnum(&pGB->pGizmos, FEnumHitTest, lParam);

            if (NULL==pGizmo)
                break;          //Didn't hit one matching our needs.

            /*
             * Inform the associate that a command was hit like a
             * menu item.
             */
            if (NULL!=pGB->hWndAssociate)
                {
                if (pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, pGizmo->uID
                        , 0, 0);
                    }
                }

            /*
             * We hit a button.  If it's a command or attribute,
             * then change the state and start tracking.
             */
            pGB->fTracking=TRUE;
            pGB->pGizmoTrack=pGizmo;
            pGB->fMouseOut=FALSE;
            SetCapture(hWnd);

            pGizmo->uStateOrg=pGizmo->uState;
            GizmoPStateSet(hWnd, pGizmo, ATTRIBUTEBUTTON_MOUSEDOWN);

            break;


        case WM_MOUSEMOVE:
            POINTFROMLPARAM(pt, lParam);

            if (!pGB->fTracking)
                break;

            pGizmo=pGB->pGizmoTrack;
            SetRect(&rc, pGizmo->x, pGizmo->y, pGizmo->x+pGizmo->dx
                , pGizmo->y+pGizmo->dy);

            fTemp=pGB->fMouseOut;
            pGB->fMouseOut=!PtInRect(&rc, pt);

            //If the mouse went out, change state to the original.
            if (!fTemp && pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo, pGizmo->uStateOrg);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we left the button
                    if (pGizmo->fNotify)
                        SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    }
                }

            if (fTemp && !pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo
                    , ATTRIBUTEBUTTON_MOUSEDOWN);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we pressed down again
                    if (pGizmo->fNotify)
                        {
                        SendMenuSelect(pGB->hWndAssociate
                            , pGizmo->uID , 0, 0);
                        }
                    }
                }

            break;


        case WM_LBUTTONUP:
            if (!pGB->fTracking)
                break;

            pGB->fTracking=FALSE;
            pGizmo=pGB->pGizmoTrack;
            ReleaseCapture();


            /*
             * Repaint if we were actually below the mouse when this
             * occurred.  For command buttons, pop the button up.
             * For attributes, either toggle the state (inclusive
             * buttons) or check the selected one (exclusive buttons)
             */

            if (!pGB->fMouseOut)
                {
                //Command buttons always come up.
                if (GIZMOTYPE_BUTTONCOMMAND==pGizmo->iType)
                    GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

                //Attribute inclusive buttons toggle
                if (GIZMOTYPE_BUTTONATTRIBUTEIN==pGizmo->iType)
                    {
                    GizmoPCheck(hWnd, pGizmo, !(BUTTONGROUP_DOWN
                        & pGizmo->uStateOrg));
                    }

                //Attribure exclusive buttons are always checked.
                if (GIZMOTYPE_BUTTONATTRIBUTEEX==pGizmo->iType)
                    GizmoPCheck(hWnd, pGizmo, TRUE);

                //Only send messages if notify is ON.
                if (NULL!=pGB->hWndAssociate && pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    SendCommand(pGB->hWndAssociate, pGizmo->uID
                        , BN_CLICKED, hWnd);
                    }
                }

            break;


        case WM_COMMAND:
            //Pass control messages on if the gizmo's notify is ON.
            if (NULL!=pGB->hWndAssociate)
                {
                pGizmo=PGizmoFromHwndID(hWnd, wID);

                if (NULL!=pGizmo)
                    {
                    if (pGizmo->fNotify)
                        {
                        SendMessage(pGB->hWndAssociate, iMsg, wParam
                            , lParam);
                        }
                    }
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }





/*
 * FEnumChangeFont
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  send a new font to them that's stored in PGIZMOBAR in dw.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE otherwise.
 */

BOOL WINAPI FEnumChangeFont(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;

    //Only need to change fonts in windowed controls using WM_SETFONT
    if (NULL!=pGizmo->hWnd)
        {
        SendMessage(pGizmo->hWnd, WM_SETFONT
            , (WPARAM)pGB->hFont, 1L);
        }

    return TRUE;
    }






/*
 * FEnumEnable
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  enable or disable them from the WM_ENABLE message.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumEnable(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;
    BOOL        fEnable=pGB->fEnabled;

    //NOTE:  This code is duplicated in GBGizmoEnable in API.C
    if (NULL!=pGizmo->hWnd)
        EnableWindow(pGizmo->hWnd, fEnable);
    else
        {
        //If we're not down, command and attribute buttons act same.
        if (!(BUTTONGROUP_DOWN & pGizmo->uState))
            {
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? COMMANDBUTTON_UP : COMMANDBUTTON_DISABLED);
            }
        else
            {
            /*
             * Attribute buttons are a little more sensitive
             * with DOWNDISABLED
             */
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? ATTRIBUTEBUTTON_DOWN
                : ATTRIBUTEBUTTON_DOWNDISABLED);
            }
        }

    return TRUE;
    }







/*
 * FEnumHitTest
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  hit-test them.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the hDC on which to draw.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumHitTest(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    RECT  rc;
    POINT pt;

    POINTFROMLPARAM(pt, dw);
	
    /*
     * Hit tests have to happen on visible, enabled, and drawn
     * controls only.
     */
    if (GIZMOTYPE_DRAWN & pGizmo->iType && !pGizmo->fHidden
        && !(BUTTONGROUP_DISABLED & pGizmo->uState))
        {
        SetRect(&rc, pGizmo->x, pGizmo->y
            , pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

        //Stop enumeration if we have a hit.
        return !PtInRect(&rc, pt);
        }

    return TRUE;
    }
