GIZMOBAR.C

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


#include "inoledll.h"


//In GIZMO.C
extern TOOLDISPLAYDATA g_tdd;


/*
* GizmoBarPAllocate
*
* Purpose:
* Allocates and initializes the control's primary data structure
* for each window that gets created.
*
* Parameters:
* pfSuccess int * indicating success of the function.
* hWnd HWND that is tied to this structure.
* hInst HINSTANCE of the DLL.
* hWndAssociate HWND to which we send messages.
* dwStyle DWORD initial style.
* uState UINT initial state.
* uID UINT identifier for this window.
*
* Return Value:
* PGIZMOBAR If NULL returned then GizmoBarPAllocate could not
* allocate memory. If a non-NULL pointer is
* returned with *pfSuccess, then call GizmoBarPFree
* immediately. If you get a non-NULL pointer and
* *pfSuccess==TRUE then the function succeeded.
*/

PGIZMOBAR GizmoBarPAllocate(int *pfSuccess, HWND hWnd
, HINSTANCE hInst, HWND hWndAssociate, DWORD dwStyle
, UINT uState , UINT uID)
{
PGIZMOBAR pGB;

if (NULL==pfSuccess)
return NULL;

*pfSuccess=FALSE;

//Allocate the structure
pGB=(PGIZMOBAR)(void *)LocalAlloc(LPTR, CBGIZMOBAR);

if (NULL==pGB)
return NULL;

//Initialize LibMain parameter holders.
pGB->hWnd =hWnd;
pGB->hInst =hInst;
pGB->hWndAssociate=hWndAssociate;
pGB->dwStyle =dwStyle;
pGB->uState =uState;
pGB->uID =uID;
pGB->fEnabled =TRUE;

pGB->crFace=GetSysColor(COLOR_BTNFACE);
pGB->hBrFace=CreateSolidBrush(pGB->crFace);

if (NULL==pGB->hBrFace)
return pGB;

pGB->hFont=GetStockObject(SYSTEM_FONT);

*pfSuccess=TRUE;
return pGB;
}




/*
* GizmoBarPFree
*
* Purpose:
* Reverses all initialization done by GizmoBarPAllocate, cleaning
* up any allocations including the application structure itself.
*
* Parameters:
* pGB PGIZMOBAR to the control's structure
*
* Return Value:
* PGIZMOBAR NULL if successful, pGB if not, meaning we
* couldn't free some allocation.
*/

PGIZMOBAR GizmoBarPFree(PGIZMOBAR pGB)
{
if (NULL==pGB)
return NULL;

/*
* Free all the gizmos we own. When we call GizmoPFree we always
* free the first one in the list which updates pGB->pGizmos for
* us, so we just have to keep going until pGizmos is NULL,
* meaning we're at the end of the list.
*/
while (NULL!=pGB->pGizmos)
GizmoPFree(&pGB->pGizmos, pGB->pGizmos);

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

/*
* Notice that since we never create a font, we aren't
* responsible for our hFont member.
*/

return (PGIZMOBAR)(void *)LocalFree((HLOCAL)(UINT)(LONG)pGB);
}








/*
* 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;
}




/*
* GizmoBarPaint
*
* Purpose:
* Handles all WM_PAINT messages for the control and paints either
* the entire thing or just one GizmoBar button if pGB->pGizmoPaint
* is non-NULL.
*
* Parameters:
* hWnd HWND Handle to the control.
* pGB PGIZMOBAR control data pointer.
*
* Return Value:
* None
*/

void GizmoBarPaint(HWND hWnd, PGIZMOBAR pGB)
{
RECT rc;
HDC hDC;
HBRUSH hBr=NULL;
HPEN hPen=NULL;
PAINTGIZMO pg;

hDC=BeginPaint(hWnd, &pg.ps);
GetClientRect(hWnd, &rc);

/*
* The only part of the frame we need to draw is the bottom line,
* so we inflate the rectangle such that all other parts are
* outside the visible region.
*/
hBr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

if (NULL!=hBr)
SelectObject(hDC, hBr);

hPen=CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));

if (NULL!=hPen)
SelectObject(hDC, hPen);

Rectangle(hDC, rc.left-1, rc.top-1, rc.right+1, rc.bottom);


/*
* All that we have to do to draw the controls is start through
* the list, ignoring anything but buttons, and calling BTTNCUR's
* UIToolButtonDraw for buttons. Since we don't even have to
* track positions of things, we can just use an enum.
*/

UIToolConfigureForDisplay(&pg.tdd);
GizmoPEnum(&pGB->pGizmos, FEnumPaintGizmos, (DWORD)(LPTSTR)&pg);

//Clean up
EndPaint(hWnd, &pg.ps);

if (NULL!=hBr)
DeleteObject(hBr);

if (NULL!=hPen)
DeleteObject(hPen);

return;
}





/*
* FEnumPaintGizmos
*
* Purpose:
* Enumeration callback for all the gizmos we know about in order to
* draw 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 a pointer to a PAINTGIZMO structure.
*
* Return Value:
* BOOL TRUE to continue the enumeration, FALSE
* otherwise.
*/

BOOL WINAPI FEnumPaintGizmos(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
{
PPAINTGIZMO ppg=(PPAINTGIZMO)dw;
RECT rc, rcI;

//Only draw those marked for repaint.
if ((GIZMOTYPE_DRAWN & pGizmo->iType))
{
SetRect(&rc, pGizmo->x, pGizmo->y
, pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

//Only draw gizmos in the repaint area
if (IntersectRect(&rcI, &rc, &ppg->ps.rcPaint))
{
UIToolButtonDraw(ppg->ps.hdc, pGizmo->x, pGizmo->y
, pGizmo->dx, pGizmo->dy, pGizmo->hBmp
, pGizmo->cxImage, pGizmo->cyImage, pGizmo->iBmp
, (UINT)pGizmo->uState, &ppg->tdd);
}
}

return TRUE;
}





/*
* 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;
}