/*
**
** Toolbar.c
**
** This is it, the incredibly famous toolbar control.  Most of
** the customization stuff is in another file.
**
*/
#include "ctlspriv.h"
#include <windowsx.h>

#define Reference(x) ((x)=(x))

#ifndef _WIN32
// we need both ansi and unicode constant strings
#define SZCODE static char _based(_segname("_CODE"))
#define SZCODEA static char _based(_segname("_CODE"))
#endif

TCHAR aszToolbarClassName[] = TOOLBARCLASSNAME;

SZCODE szUSER[] = TEXT("USER.EXE");
SZCODEA szDrawFrameControl[] = "DrawFrameControl";
SZCODE szKernel[] = TEXT("KERNEL.EXE");
SZCODEA szWriteProfileStruct[] = "WritePrivateProfileStruct";

// these values are defined by the UI gods...
#define DEFAULTBITMAPX 16
#define DEFAULTBITMAPY 15

#define DEFAULTBUTTONX 24
#define DEFAULTBUTTONY 22

// horizontal/vertical space taken up by button chisel, sides,
// and a 1 pixel margin.  used in GrowToolbar.
#define XSLOP 7
#define YSLOP 6

#define SLOPTOP 1
#define SLOPBOT 1
#define SLOPLFT 8

static int dxButtonSep = 8;
static int xFirstButton = SLOPLFT;  //!!! was 8

static int iInitCount = 0;

static int nSelectedBM = -1;
static HDC hdcGlyphs = NULL;           // globals for fast drawing
static HDC hdcMono = NULL;
static HBITMAP hbmMono = NULL;
static HBITMAP hbmDefault = NULL;

static HDC hdcButton = NULL;           // contains hbmFace (when it exists)
static HBITMAP hbmFace = NULL;
static int dxFace, dyFace;             // current dimensions of hbmFace (2*dxFace)

static HDC hdcFaceCache = NULL;        // used for button cache

static HFONT hIconFont = NULL;         // font used for strings in buttons
static int yIconFont;                  // height of the font

static BOOL g_bUseDFC = FALSE;         // use DrawFrameControl, if available
static BOOL g_bProfStruct = FALSE;     // use PrivateProfileStruct routines
static WORD g_dxOverlap = 1;           // overlap between buttons

static WORD wStateMasks[] = {
    TBSTATE_ENABLED,
    TBSTATE_CHECKED,
    TBSTATE_PRESSED,
    TBSTATE_HIDDEN,
    TBSTATE_INDETERMINATE
};

LRESULT CALLBACK _loadds ToolbarWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam);

#define HeightWithString(h) (h + yIconFont + 1)

static BOOL NEAR PASCAL InitGlobalObjects(void)
{
    LOGFONT lf;
    TEXTMETRIC tm;
    HFONT hOldFont;

    iInitCount++;

    if (iInitCount != 1)
        return TRUE;

    hdcGlyphs = CreateCompatibleDC(NULL);
    if (!hdcGlyphs)
        return FALSE;
    hdcMono = CreateCompatibleDC(NULL);
    if (!hdcMono)
        return FALSE;

    hbmMono = CreateBitmap(DEFAULTBUTTONX, DEFAULTBUTTONY, 1, 1, NULL);
    if (!hbmMono)
        return FALSE;

    hbmDefault = SelectObject(hdcMono, hbmMono);

    hdcButton = CreateCompatibleDC(NULL);
    if (!hdcButton)
        return FALSE;
    hdcFaceCache = CreateCompatibleDC(NULL);
    if (!hdcFaceCache)
        return FALSE;

    SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0);
    hIconFont = CreateFontIndirect(&lf);
    if (!hIconFont)
	return FALSE;

    hOldFont = SelectObject(hdcMono, hIconFont);
    GetTextMetrics(hdcMono, &tm);
    yIconFont = tm.tmHeight;
    if (hOldFont)
	SelectObject(hdcMono, hOldFont);

#if WINVER >= 0x0400
    // set a global flag to see if USER will draw for us
    if (GetProcAddress(LoadLibrary(szUSER), szDrawFrameControl))
    {
	g_bUseDFC = TRUE;
	g_dxOverlap = 0;	// buttons do NOT overlap with new look
    }
    // set a global flag to see if KERNEL does profile structs
    if (GetProcAddress(LoadLibrary(szKernel), szWriteProfileStruct))
        g_bProfStruct = TRUE;
#endif

    return TRUE;
}


static BOOL NEAR PASCAL FreeGlobalObjects(void)
{
    iInitCount--;

    if (iInitCount != 0)
        return TRUE;

    if (hdcMono) {
	if (hbmDefault)
	    SelectObject(hdcMono, hbmDefault);
	DeleteDC(hdcMono);		// toast the DCs
    }
    hdcMono = NULL;

    if (hdcGlyphs)
	DeleteDC(hdcGlyphs);
    hdcGlyphs = NULL;
    if (hdcFaceCache)
	DeleteDC(hdcFaceCache);
    hdcFaceCache = NULL;

    if (hdcButton) {
	if (hbmDefault)
	    SelectObject(hdcButton, hbmDefault);
	DeleteDC(hdcButton);
    }
    hdcButton = NULL;

    if (hbmFace)
	DeleteObject(hbmFace);
    hbmFace = NULL;

    if (hbmMono)
	DeleteObject(hbmMono);
    hbmMono = NULL;

    if (hIconFont)
	DeleteObject(hIconFont);
    hIconFont = NULL;
}

HWND WINAPI CreateToolbarEx(HWND hwnd, DWORD ws, UINT wID, int nBitmaps,
			HINSTANCE hBMInst, UINT wBMID, LPCTBBUTTON lpButtons,
			int iNumButtons, int dxButton, int dyButton,
			int dxBitmap, int dyBitmap, UINT uStructSize)
{

    HWND hwndToolbar;

    hwndToolbar = CreateWindow(aszToolbarClassName, NULL, WS_CHILD | ws,
	      0, 0, 100, 30, hwnd, (HMENU)wID,
	      GetWindowInstance(hwnd),NULL);
    if (!hwndToolbar)
	goto Error1;

    SendMessage(hwndToolbar, TB_BUTTONSTRUCTSIZE, uStructSize, 0L);

    if (dxBitmap && dyBitmap)
	if (!SendMessage(hwndToolbar, TB_SETBITMAPSIZE, 0, MAKELONG(dxBitmap, dyBitmap)))
	{
	    //!!!! do we actually need to deal with this?
	    DestroyWindow(hwndToolbar);
	    hwndToolbar = NULL;
	    goto Error1;
	}

    if (dxButton && dyButton)
	if (!SendMessage(hwndToolbar, TB_SETBUTTONSIZE, 0, MAKELONG(dxButton, dyButton)))
	{
	    //!!!! do we actually need to deal with this?
	    DestroyWindow(hwndToolbar);
	    hwndToolbar = NULL;
	    goto Error1;
	}
#ifdef _WIN32
    {
	TB_ADDBITMAPINFO tbai;

	tbai.idResource = wBMID;
	tbai.hBitmap = hBMInst;

	SendMessage(hwndToolbar, TB_ADDBITMAP, nBitmaps, (LPARAM) &tbai);
    }
#else
    SendMessage(hwndToolbar, TB_ADDBITMAP, nBitmaps, MAKELONG(hBMInst, wBMID));
#endif
    SendMessage(hwndToolbar, TB_ADDBUTTONS, iNumButtons, (LPARAM)lpButtons);

Error1:
    return hwndToolbar;
}



BOOL FAR PASCAL InitToolbarClass(HINSTANCE hInstance)
{
    WNDCLASS wc;

    if (!GetClassInfo(hInstance, aszToolbarClassName, &wc)) {

	wc.lpszClassName = aszToolbarClassName;
	wc.style	 = CS_GLOBALCLASS | CS_DBLCLKS;
	wc.lpfnWndProc	 = (WNDPROC)ToolbarWndProc;
	wc.cbClsExtra	 = 0;
	wc.cbWndExtra	 = sizeof(PTBSTATE);
	wc.hInstance	 = hInstance;
	wc.hIcon	 = NULL;
	wc.hCursor	 = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
	wc.lpszMenuName	 = NULL;

	if (!RegisterClass(&wc))
	    return FALSE;
    }

    return TRUE;
}



#define BEVEL   2
#define FRAME   1

static void NEAR PASCAL 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);
}

static void NEAR PASCAL DrawString(HDC hdc, int x, int y, int dx, PTSTR pszString)
{
    int oldMode;
    DWORD oldTextColor;
    HFONT oldhFont;
    DWORD dwExt;
    int len;

    oldMode = SetBkMode(hdc, TRANSPARENT);
    oldTextColor = SetTextColor(hdc, 0L);
    oldhFont = SelectObject(hdc, hIconFont);

    len = lstrlen(pszString);
#ifdef _WIN32
    {
        SIZE size;
        GetTextExtentPoint(hdc, (LPTSTR)pszString, len, &size);
        dwExt = LOWORD(size.cx) | (LOWORD(size.cy) >> 8);
    }
#else
    dwExt = GetTextExtent(hdc, (LPTSTR)pszString, len);
#endif
    // center the string horizontally
    x += (dx - LOWORD(dwExt) - 1)/2;

    TextOut(hdc, x, y, (LPTSTR)pszString, len);

    if (oldhFont)
	SelectObject(hdc, oldhFont);
    SetTextColor(hdc, oldTextColor);
    SetBkMode(hdc, oldMode);
}

// create a mono bitmap mask:
//   1's where color == COLOR_BTNFACE || COLOR_HILIGHT
//   0's everywhere else

static void NEAR PASCAL CreateMask(PTBSTATE pTBState, PTBBUTTON pTBButton, int xoffset, int yoffset, int dx, int dy)
{
    PTSTR pFoo;

    // initalize whole area with 1's
    PatBlt(hdcMono, 0, 0, dx, dy, WHITENESS);

    // create mask based on color bitmap
    // convert this to 1's
    SetBkColor(hdcGlyphs, rgbFace);
    BitBlt(hdcMono, xoffset, yoffset, pTBState->iDxBitmap, pTBState->iDyBitmap,
    	hdcGlyphs, pTBButton->iBitmap * pTBState->iDxBitmap, 0, SRCCOPY);
    // convert this to 1's
    SetBkColor(hdcGlyphs, rgbHilight);
    // OR in the new 1's
    BitBlt(hdcMono, xoffset, yoffset, pTBState->iDxBitmap, pTBState->iDyBitmap,
    	hdcGlyphs, pTBButton->iBitmap * pTBState->iDxBitmap, 0, SRCPAINT);

    if (pTBButton->iString != -1 && (pTBButton->iString < pTBState->nStrings))
    {
	pFoo = pTBState->pStrings[pTBButton->iString];
	DrawString(hdcMono, 1, yoffset + pTBState->iDyBitmap + 1, dx, pFoo);
    }
}


/* Given a button number, the corresponding bitmap is loaded and selected in,
 * and the Window origin set.
 * Returns NULL on Error, 1 if the necessary bitmap is already selected,
 * or the old bitmap otherwise.
 */
static HBITMAP FAR PASCAL SelectBM(HDC hDC, PTBSTATE pTBState, int nButton)
{
  PTBBMINFO pTemp;
  HBITMAP hRet;
  int nBitmap, nTot;

  for (pTemp=pTBState->pBitmaps, nBitmap=0, nTot=0; ; ++pTemp, ++nBitmap)
    {
      if (nBitmap >= pTBState->nBitmaps)
	  return(NULL);

      if (nButton < nTot+pTemp->nButtons)
	  break;

      nTot += pTemp->nButtons;
    }

  /* Special case when the required bitmap is already selected
   */
  if (nBitmap == nSelectedBM)
      return((HBITMAP)1);

  if (!pTemp->hbm || (hRet=SelectObject(hDC, pTemp->hbm))==NULL)
    {
      if (pTemp->hbm)
	  DeleteObject(pTemp->hbm);

      if (pTemp->hInst)
	  pTemp->hbm = CreateMappedBitmap(pTemp->hInst, pTemp->wID,
		TRUE, NULL, 0);
      else
	  pTemp->hbm = (HBITMAP)pTemp->wID;

      if (!pTemp->hbm || (hRet=SelectObject(hDC, pTemp->hbm))==NULL)
	  return(NULL);
    }

  nSelectedBM = nBitmap;
#ifdef _WIN32
  SetWindowOrgEx(hDC, nTot * pTBState->iDxBitmap, 0, NULL);
#else // _WIN32
  SetWindowOrg(hDC, nTot * pTBState->iDxBitmap, 0);
#endif

  return(hRet);
}

static void FAR PASCAL DrawBlankButton(HDC hdc, int x, int y, int dx, int dy, WORD state, WORD wButtType)
{
#if WINVER >= 0x0400
    RECT r1;
#endif

    // face color
    PatB(hdc, x, y, dx, dy, rgbFace);

#if WINVER >= 0x0400
    if (g_bUseDFC)
    {
	r1.left = x;
	r1.top = y;
	r1.right = x + dx;
	r1.bottom = y + dy;

	DrawFrameControl(hdc, &r1, wButtType,
		(state & TBSTATE_PRESSED) ? DFCS_PUSHED : 0);
    }
    else
#endif
    {
	if (state & TBSTATE_PRESSED) {
	    PatB(hdc, x + 1, y, dx - 2, 1, rgbFrame);
	    PatB(hdc, x + 1, y + dy - 1, dx - 2, 1, rgbFrame);
	    PatB(hdc, x, y + 1, 1, dy - 2, rgbFrame);
	    PatB(hdc, x + dx - 1, y +1, 1, dy - 2, rgbFrame);
	    PatB(hdc, x + 1, y + 1, 1, dy-2, rgbShadow);
	    PatB(hdc, x + 1, y + 1, dx-2, 1, rgbShadow);
	}
	else {
	    PatB(hdc, x + 1, y, dx - 2, 1, rgbFrame);
	    PatB(hdc, x + 1, y + dy - 1, dx - 2, 1, rgbFrame);
	    PatB(hdc, x, y + 1, 1, dy - 2, rgbFrame);
	    PatB(hdc, x + dx - 1, y + 1, 1, dy - 2, rgbFrame);
	    dx -= 2;
	    dy -= 2;
	    PatB(hdc, x + 1, y + 1, 1, dy - 1, rgbHilight);
	    PatB(hdc, x + 1, y + 1, dx - 1, 1, rgbHilight);
	    PatB(hdc, x + dx, y + 1, 1, dy, rgbShadow);
	    PatB(hdc, x + 1, y + dy, dx, 1,   rgbShadow);
	    PatB(hdc, x + dx - 1, y + 2, 1, dy - 2, rgbShadow);
	    PatB(hdc, x + 2, y + dy - 1, dx - 2, 1,   rgbShadow);
	}
    }
}

#define DSPDxax	 0x00E20746
#define PSDPxax  0x00B8074A

#define FillBkColor(hdc, prc) ExtTextOut(hdc,0,0,ETO_OPAQUE,prc,NULL,0,NULL)

static void NEAR PASCAL DrawFace(PTBSTATE pTBState, PTBBUTTON ptButton, HDC hdc, int x, int y,
			int offx, int offy, int dx)
{
    PTSTR pFoo;

    BitBlt(hdc, x + offx, y + offy, pTBState->iDxBitmap, pTBState->iDyBitmap,
	    hdcGlyphs, ptButton->iBitmap * pTBState->iDxBitmap, 0, SRCCOPY);

    if (ptButton->iString != -1 && (ptButton->iString < pTBState->nStrings))
    {
	pFoo = pTBState->pStrings[ptButton->iString];
	DrawString(hdc, x + 1, y + offy + pTBState->iDyBitmap + 1, dx, pFoo);
    }
}

static void FAR PASCAL DrawButton(HDC hdc, int x, int y, int dx, int dy, PTBSTATE pTBState, PTBBUTTON ptButton, BOOL bFaceCache)
{
    int yOffset;
    HBRUSH hbrOld, hbr;
    BOOL bMaskCreated = FALSE;
    BYTE state;
    int xButton = 0;		// assume button is down
    int dxFace, dyFace;
    int xCenterOffset;

    dxFace = dx - 4;
    dyFace = dy - 4;

    // make local copy of state and do proper overriding
    state = ptButton->fsState;
    if (state & TBSTATE_INDETERMINATE) {
	if (state & TBSTATE_PRESSED)
	    state &= ~TBSTATE_INDETERMINATE;
	else if (state & TBSTATE_ENABLED)
	    state = TBSTATE_INDETERMINATE;
	else
	    state &= ~TBSTATE_INDETERMINATE;
    }

    // get the proper button look-- up or down.
    if (!(state & (TBSTATE_PRESSED | TBSTATE_CHECKED))) {
	xButton = dx;	// use 'up' version of button
    }
    if (bFaceCache)
	BitBlt(hdc, x, y, dx, dy, hdcButton, xButton, 0, SRCCOPY);
    else
	DrawBlankButton(hdc, x, y, dx, dy, state, pTBState->wButtonType);


    // move coordinates inside border and away from upper left highlight.
    // the extents change accordingly.
    x += 2;
    y += 2;

    if (!SelectBM(hdcGlyphs, pTBState, ptButton->iBitmap))
	return;

    // calculate offset of face from (x,y).  y is always from the top,
    // so the offset is easy.  x needs to be centered in face.
    yOffset = 1;
    xCenterOffset = (dxFace - pTBState->iDxBitmap)/2;
    if (state & (TBSTATE_PRESSED | TBSTATE_CHECKED))
    {
	// pressed state moves down and to the right
	xCenterOffset++;
        yOffset++;
    }

    // now put on the face
    if (state & TBSTATE_ENABLED) {
        // regular version
	DrawFace(pTBState, ptButton, hdc, x, y, xCenterOffset, yOffset, dxFace);
    } else {
        // disabled version (or indeterminate)
	bMaskCreated = TRUE;
	CreateMask(pTBState, ptButton, xCenterOffset, yOffset, dxFace, dyFace);

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

	// draw glyph's white understrike
	if (!(state & TBSTATE_INDETERMINATE)) {
	    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, dxFace, dyFace, hdcMono, 0, 0, PSDPxax);
	            SelectObject(hdc, hbrOld);
	        }
	        DeleteObject(hbr);
	    }
	}

	// gray out glyph
	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, dxFace, dyFace, hdcMono, 0, 0, PSDPxax);
	        SelectObject(hdc, hbrOld);
	    }
	    DeleteObject(hbr);
	}

	if (state & TBSTATE_CHECKED) {
	    BitBlt(hdcMono, 1, 1, dxFace - 1, dyFace - 1, hdcMono, 0, 0, SRCAND);
	}
    }

    if (state & (TBSTATE_CHECKED | TBSTATE_INDETERMINATE)) {

        hbrOld = SelectObject(hdc, hbrDither);
	if (hbrOld) {

	    if (!bMaskCreated)
	        CreateMask(pTBState, ptButton, xCenterOffset, yOffset, dxFace, dyFace);

	    SetTextColor(hdc, 0L);		// 0 -> 0
	    SetBkColor(hdc, 0x00FFFFFF);	// 1 -> 1

	    // only draw the dither brush where the mask is 1's
            BitBlt(hdc, x, y, dxFace, dyFace, hdcMono, 0, 0, DSPDxax);
	
	    SelectObject(hdc, hbrOld);
	}
    }
}

static void NEAR PASCAL FlushButtonCache(PTBSTATE pTBState)
{
    if (pTBState->hbmCache) {
	DeleteObject(pTBState->hbmCache);
	pTBState->hbmCache = 0;
    }
}

// make sure that hbmMono is big enough to do masks for this
// size of button.  if not, fail.
static BOOL NEAR PASCAL CheckMonoMask(int width, int height)
{
    BITMAP bm;
    HBITMAP hbmTemp;

    GetObject(hbmMono, sizeof(BITMAP), &bm);
    if (width > bm.bmWidth || height > bm.bmHeight) {
	hbmTemp = CreateBitmap(width, height, 1, 1, NULL);
	if (!hbmTemp)
	    return FALSE;
	SelectObject(hdcMono, hbmTemp);
	DeleteObject(hbmMono);
	hbmMono = hbmTemp;
    }
    return TRUE;
}

/*
** GrowToolbar
**
** Attempt to grow the button size.
**
** The calling function can either specify a new internal measurement
** or a new external measurement.
*/
static BOOL NEAR PASCAL GrowToolbar(PTBSTATE pTBState, int newButWidth, int newButHeight, BOOL bInside)
{
    // if growing based on inside measurement, get full size
    if (bInside) {
	newButHeight += YSLOP;
	newButWidth += XSLOP;
	
	// if toolbar already has strings, don't shrink width it because it
	// might clip room for the string
	if ((newButWidth < pTBState->iButWidth) && pTBState->nStrings)
	    newButWidth = pTBState->iButWidth;
    }
    else {
    	if (newButHeight < pTBState->iButHeight)
	    newButHeight = pTBState->iButHeight;
    	if (newButWidth < pTBState->iButWidth)
	    newButWidth = pTBState->iButWidth;
    }

    // if the size of the toolbar is actually growing, see if shadow
    // bitmaps can be made sufficiently large.
    if ((newButWidth > pTBState->iButWidth) || (newButHeight > pTBState->iButHeight)) {
	if (!CheckMonoMask(newButWidth, newButHeight))
	    return(FALSE);
    }

    pTBState->iButWidth = newButWidth;
    pTBState->iButHeight = newButHeight;

//!!!ACK ACK ACK ACK
#if 0
    // bar height has 2 pixels above, 3 below
    pTBState->iBarHeight = pTBState->iButHeight + 5;
    pTBState->iYPos = 2;
#else
    pTBState->iBarHeight = pTBState->iButHeight + SLOPTOP+SLOPBOT;
    pTBState->iYPos = SLOPTOP;
#endif

    return TRUE;
}

static BOOL NEAR PASCAL SetBitmapSize(PTBSTATE pTBState, int width, int height)
{
    int realh = height;

    if (pTBState->nStrings)
	realh = HeightWithString(height);

    if (GrowToolbar(pTBState, width, realh, TRUE)) {
	pTBState->iDxBitmap = width;
	pTBState->iDyBitmap = height;
	return TRUE;
    }
    return FALSE;
}

static void NEAR PASCAL UpdateTBState(PTBSTATE pTBState)
{
	int i;
	PTBBMINFO pBitmap;

	if (pTBState->nSysColorChanges!=nSysColorChanges)
	{
		/* Reset all of the bitmaps if the sys colors have changed
		 * since the last time the bitmaps were created.
		 */
		for (i=pTBState->nBitmaps-1, pBitmap=pTBState->pBitmaps; i>=0;
			--i, ++pBitmap)
		{
			if (pBitmap->hInst && pBitmap->hbm)
			{
				DeleteObject(pBitmap->hbm);
				pBitmap->hbm = NULL;
			}
		}

		FlushButtonCache(pTBState);

		// now we're updated to latest color scheme
		pTBState->nSysColorChanges = nSysColorChanges;
	}
}

#define CACHE 0x01
#define BUILD 0x02

static void NEAR PASCAL ToolbarPaint(HWND hWnd, PTBSTATE pTBState)
{
    RECT rc;
    HDC hdc;
    PAINTSTRUCT ps;
    int iButton, xButton, yButton;
    int cButtons = pTBState->iNumButtons;
    PTBBUTTON pAllButtons = pTBState->Buttons;
    HBITMAP hbmOldGlyphs;
    int xCache = 0;
    WORD wFlags = 0;
    int iCacheWidth = 0;
    HBITMAP hbmTemp;
    BOOL bFaceCache = TRUE;		// assume face cache exists
    int dx,dy;

    CheckSysColors();
    UpdateTBState(pTBState);

    hdc = BeginPaint(hWnd, &ps);

    GetClientRect(hWnd, &rc);
    if (!rc.right)
	goto Error1;

    dx = pTBState->iButWidth;
    dy = pTBState->iButHeight;

    // setup global stuff for fast painting

    /* We need to kick-start the bitmap selection process.
     */
    nSelectedBM = -1;
    hbmOldGlyphs = SelectBM(hdcGlyphs, pTBState, 0);
    if (!hbmOldGlyphs)
	goto Error1;

    yButton = pTBState->iYPos;
    rc.top = yButton;
    rc.bottom = yButton + dy;

    if (!(pTBState->hbmCache)) {
	// calculate the width of the cache.
	for (iButton = 0; iButton < cButtons; iButton++) {
	    if (!(pAllButtons[iButton].fsState & TBSTATE_HIDDEN) &&
			!(pAllButtons[iButton].fsStyle & TBSTYLE_SEP))
		iCacheWidth += pTBState->iButWidth;
	}
	pTBState->hbmCache = CreateCompatibleBitmap(hdcGlyphs, iCacheWidth, dy);
	wFlags |= BUILD;

	// if needed, create or enlarge bitmap for pre-building button states
	if (!(hbmFace && (dx <= dxFace) && (dy <= dyFace))) {
	    hbmTemp = CreateCompatibleBitmap(hdcGlyphs, 2*dx, dy);
	    if (hbmTemp) {
		SelectObject(hdcButton, hbmTemp);
		if (hbmFace)
		    DeleteObject(hbmFace);
		hbmFace = hbmTemp;
		dxFace = dx;
		dyFace = dy;
	    }
	    else
		bFaceCache = FALSE;
	}
    }
    if (pTBState->hbmCache) {
        SelectObject(hdcFaceCache,pTBState->hbmCache);
	wFlags |= CACHE;
    }
    else
        wFlags = 0;

    if (bFaceCache) {
	DrawBlankButton(hdcButton, 0, 0, dx, dy, TBSTATE_PRESSED, pTBState->wButtonType);
	DrawBlankButton(hdcButton, dx, 0, dx, dy, 0, pTBState->wButtonType);
    }

    for (iButton = 0, xButton = xFirstButton;
	iButton < cButtons;
	iButton++) {

        PTBBUTTON ptbButton = &pAllButtons[iButton];

	if (ptbButton->fsState & TBSTATE_HIDDEN) {
	    /* Do nothing */ ;
        } else if (ptbButton->fsStyle & TBSTYLE_SEP) {
	    xButton += ptbButton->iBitmap;
        } else {
	    if (wFlags & BUILD)
	        DrawButton(hdcFaceCache, xCache, 0, dx, dy, pTBState, ptbButton, bFaceCache);

            rc.left = xButton;
            rc.right = xButton + dx;
	    if (RectVisible(hdc, &rc)) {
		if ((wFlags & CACHE) && !(ptbButton->fsState & TBSTATE_PRESSED))
		    BitBlt(hdc, xButton, yButton, dx, dy,
				hdcFaceCache, xCache, 0, SRCCOPY);
		else
		    DrawButton(hdc, xButton, yButton, dx, dy, pTBState, ptbButton, bFaceCache);
	    }
	    // advance the "pointer" in the cache
	    xCache += dx;

	    xButton += (dx - g_dxOverlap);
        }
    }

    if (wFlags & CACHE)
	SelectObject(hdcFaceCache, hbmDefault);
    SelectObject(hdcGlyphs, hbmOldGlyphs);

Error1:
    EndPaint(hWnd, &ps);
}


static BOOL NEAR PASCAL GetItemRect(PTBSTATE pTBState, UINT uButton, LPRECT lpRect)
{
	UINT iButton, xPos;
	PTBBUTTON pButton;

	if (uButton>=(UINT)pTBState->iNumButtons
		|| (pTBState->Buttons[uButton].fsState&TBSTATE_HIDDEN))
	{
		return(FALSE);
	}

	xPos = xFirstButton;

	for (iButton=0, pButton=pTBState->Buttons; iButton<uButton;
		++iButton, ++pButton)
	{
		if (pButton->fsState & TBSTATE_HIDDEN)
		{
			/* Do nothing */ ;
		}
		else if (pButton->fsStyle & TBSTYLE_SEP)
		{
			xPos += pButton->iBitmap;
		}
		else
		{
			xPos += (pTBState->iButWidth - g_dxOverlap);
		}
	}

	/* pButton should now point at the required button, and xPos should be
	 * its left edge.  Note that we already checked if the button was
	 * hidden above.
	 */
	lpRect->left   = xPos;
	lpRect->right  = xPos + (pButton->fsStyle&TBSTYLE_SEP
		? pButton->iBitmap : pTBState->iButWidth);
	lpRect->top    = pTBState->iYPos;
	lpRect->bottom = lpRect->top + pTBState->iButHeight;

	return(TRUE);
}


static void NEAR PASCAL InvalidateButton(HWND hwnd, PTBSTATE pTBState, PTBBUTTON pButtonToPaint)
{
	RECT rc;

	if (GetItemRect(pTBState, pButtonToPaint-pTBState->Buttons, &rc))
	{
		InvalidateRect(hwnd, &rc, FALSE);
	}
}


static int FAR PASCAL TBHitTest(PTBSTATE pTBState, int xPos, int yPos)
{
  int iButton;
  int cButtons = pTBState->iNumButtons;
  PTBBUTTON pButton;

  xPos -= xFirstButton;
  if (xPos < 0)
      return(-1);
  yPos -= pTBState->iYPos;

  for (iButton=0, pButton=pTBState->Buttons; iButton<cButtons;
	++iButton, ++pButton)
    {
      if (pButton->fsState & TBSTATE_HIDDEN)
	  /* Do nothing */ ;
      else if (pButton->fsStyle & TBSTYLE_SEP)
	  xPos -= pButton->iBitmap;
      else
	  xPos -= (pTBState->iButWidth - g_dxOverlap);

      if (xPos < 0)
	{
	  if (pButton->fsStyle&TBSTYLE_SEP
		|| (UINT)yPos>=(UINT)pTBState->iButHeight)
	      break;

	  return(iButton);
	}
    }

  return(-1 - iButton);
}


static int FAR PASCAL PositionFromID(PTBSTATE pTBState, int id)
{
    int i;
    int cButtons = pTBState->iNumButtons;
    PTBBUTTON pAllButtons = pTBState->Buttons;

    for (i = 0; i < cButtons; i++)
        if (pAllButtons[i].idCommand == id)
	    return i;		// position found

    return -1;		// ID not found!
}

// check a radio button by button index.
// the button matching idCommand was just pressed down.  this forces
// up all other buttons in the group.
// this does not work with buttons that are forced up with

static void NEAR PASCAL MakeGroupConsistant(HWND hWnd, PTBSTATE pTBState, int idCommand)
{
    int i, iFirst, iLast, iButton;
    int cButtons = pTBState->iNumButtons;
    PTBBUTTON pAllButtons = pTBState->Buttons;

    iButton = PositionFromID(pTBState, idCommand);

    if (iButton < 0)
        return;

    // assertion

//    if (!(pAllButtons[iButton].fsStyle & TBSTYLE_CHECK))
//	return;

    // did the pressed button just go down?
    if (!(pAllButtons[iButton].fsState & TBSTATE_CHECKED))
        return;         // no, can't do anything

    // find the limits of this radio group

    for (iFirst = iButton; (iFirst > 0) && (pAllButtons[iFirst].fsStyle & TBSTYLE_GROUP); iFirst--)
    if (!(pAllButtons[iFirst].fsStyle & TBSTYLE_GROUP))
        iFirst++;

    cButtons--;
    for (iLast = iButton; (iLast < cButtons) && (pAllButtons[iLast].fsStyle & TBSTYLE_GROUP); iLast++);
    if (!(pAllButtons[iLast].fsStyle & TBSTYLE_GROUP))
        iLast--;

    // search for the currently down button and pop it up
    for (i = iFirst; i <= iLast; i++) {
        if (i != iButton) {
            // is this button down?
            if (pAllButtons[i].fsState & TBSTATE_CHECKED) {
	        pAllButtons[i].fsState &= ~TBSTATE_CHECKED;     // pop it up
                InvalidateButton(hWnd, pTBState, &pAllButtons[i]);
                break;          // only one button is down right?
            }
        }
    }
}

static void NEAR PASCAL DestroyStrings(PTBSTATE pTBState)
{
    PTSTR *p;
    PTSTR end = 0, start = 0;
    int i;

    p = pTBState->pStrings;
    for (i = 0; i < pTBState->nStrings; i++) {
	if (!(*p < end) && (*p > start)) {
	    start = (*p);
	    end = start + LocalSize((HANDLE)*p);
	    LocalFree((HANDLE)*p);
	}
	p++;
	i++;
    }

    LocalFree((HANDLE)pTBState->pStrings);
}


/* Adds a new bitmap to the list of BMs available for this toolbar.
 * Returns the index of the first button in the bitmap or -1 if there
 * was an error.
 */
static int NEAR PASCAL AddBitmap(PTBSTATE pTBState, int nButtons,
      HINSTANCE hBMInst, UINT wBMID)
{
  PTBBMINFO pTemp;
  int nBM, nIndex;

  if (pTBState->pBitmaps)
    {
      /* Check if the bitmap has already been added
       */
      for (nBM=pTBState->nBitmaps, pTemp=pTBState->pBitmaps, nIndex=0;
	    nBM>0; --nBM, ++pTemp)
	{
	  if (pTemp->hInst==hBMInst && pTemp->wID==wBMID)
	    {
	      /* We already have this bitmap, but have we "registered" all
	       * the buttons in it?
	       */
	      if (pTemp->nButtons >= nButtons)
		  return(nIndex);
	      if (nBM == 1)
		{
		  /* If this is the last bitmap, we can easily increase the
		   * number of buttons without messing anything up.
		   */
		  pTemp->nButtons = nButtons;
		  return(nIndex);
		}
	    }

	  nIndex += pTemp->nButtons;
	}

      pTemp = (PTBBMINFO)LocalReAlloc(pTBState->pBitmaps,
	    (pTBState->nBitmaps+1)*sizeof(TBBMINFO), LMEM_MOVEABLE);
      if (!pTemp)
	  return(-1);
      pTBState->pBitmaps = pTemp;
    }
  else
    {
      pTBState->pBitmaps = (PTBBMINFO)LocalAlloc(LPTR, sizeof(TBBMINFO));
      if (!pTBState->pBitmaps)
	  return(-1);
    }

  pTemp = pTBState->pBitmaps + pTBState->nBitmaps;

  pTemp->hInst = hBMInst;
  pTemp->wID = wBMID;
  pTemp->nButtons = nButtons;
  pTemp->hbm = NULL;

  ++pTBState->nBitmaps;

  for (nButtons=0, --pTemp; pTemp>=pTBState->pBitmaps; --pTemp)
      nButtons += pTemp->nButtons;

  return(nButtons);
}


static BOOL NEAR PASCAL InsertButtons(HWND hWnd, PTBSTATE pTBState,
      UINT uWhere, UINT uButtons, LPTBBUTTON lpButtons)
{
  PTBBUTTON pIn, pOut;

  if (!pTBState || !pTBState->uStructSize)
      return(FALSE);

  pTBState = (PTBSTATE)LocalReAlloc(pTBState, sizeof(TBSTATE)-sizeof(TBBUTTON)
	+ (pTBState->iNumButtons+uButtons)*sizeof(TBBUTTON), LMEM_MOVEABLE);
  if (!pTBState)
      return(FALSE);

  SETWINDOWPOINTER(hWnd, PTBSTATE, pTBState);

  if (uWhere > (UINT)pTBState->iNumButtons)
      uWhere = pTBState->iNumButtons;

  for (pIn=pTBState->Buttons+pTBState->iNumButtons-1, pOut=pIn+uButtons,
	uWhere=(UINT)pTBState->iNumButtons-uWhere; uWhere>0;
	--pIn, --pOut, --uWhere)
      *pOut = *pIn;

  for (lpButtons=(LPTBBUTTON)((LPBYTE)lpButtons+pTBState->uStructSize*(uButtons-1)), pTBState->iNumButtons+=(int)uButtons; uButtons>0;
	--pOut, lpButtons=(LPTBBUTTON)((LPBYTE)lpButtons-pTBState->uStructSize), --uButtons)
    {
      TBInputStruct(pTBState, pOut, lpButtons);

      if ((pOut->fsStyle&TBSTYLE_SEP) && pOut->iBitmap<=0)
	  pOut->iBitmap = dxButtonSep;
    }
	
  // flush the cache
  FlushButtonCache(pTBState);

  /* We need to completely redraw the toolbar at this point.
   */
  InvalidateRect(hWnd, NULL, TRUE);

  return(TRUE);
}


/* Notice that the state structure is not realloc'ed smaller at this
 * point.  This is a time optimization, and the fact that the structure
 * will not move is used in other places.
 */
static BOOL NEAR PASCAL DeleteButton(HWND hWnd, PTBSTATE pTBState, UINT uIndex)
{
  PTBBUTTON pIn, pOut;

  if (uIndex >= (UINT)pTBState->iNumButtons)
      return(FALSE);

  --pTBState->iNumButtons;
  for (pOut=pTBState->Buttons+uIndex, pIn=pOut+1;
	uIndex<(UINT)pTBState->iNumButtons; ++uIndex, ++pIn, ++pOut)
      *pOut = *pIn;

  // flush the cache
  FlushButtonCache(pTBState);

  /* We need to completely redraw the toolbar at this point.
   */
  InvalidateRect(hWnd, NULL, TRUE);

  return(TRUE);
}


static void FAR PASCAL TBInputStruct(PTBSTATE pTBState, LPTBBUTTON pButtonInt, LPTBBUTTON pButtonExt)
{
	if (pTBState->uStructSize >= sizeof(TBBUTTON))
	{
		*pButtonInt = *pButtonExt;
	}
	else
	/* It is assumed the only other possibility is the OLDBUTTON struct */
	{
		*(LPOLDTBBUTTON)pButtonInt = *(LPOLDTBBUTTON)pButtonExt;
		/* We don't care about dwData */
		pButtonInt->iString = -1;
	}
}


static void FAR PASCAL TBOutputStruct(PTBSTATE pTBState, LPTBBUTTON pButtonInt, LPTBBUTTON pButtonExt)
{
	if (pTBState->uStructSize >= sizeof(TBBUTTON))
	{
		LPBYTE pOut;
		int i;

		/* Fill the part we know about and fill the rest with 0's
		*/
		*pButtonExt = *pButtonInt;
		for (i=pTBState->uStructSize-sizeof(TBBUTTON), pOut=(LPBYTE)(pButtonExt+1);
			i>0; --i, ++pOut)
		{
			*pOut = 0;
		}
	}
	else
	/* It is assumed the only other possibility is the OLDBUTTON struct */
	{
		*(LPOLDTBBUTTON)pButtonExt = *(LPOLDTBBUTTON)pButtonInt;
	}
}


LRESULT CALLBACK _loadds ToolbarWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    BOOL fSameButton;
    PTBBUTTON ptbButton;
    PTBSTATE pTBState;
    int iPos;
    BYTE fsState;
#if WINVER >= 0x0400
    DWORD dw;
#endif

    pTBState = GETWINDOWPOINTER(hWnd, PTBSTATE);

    switch (wMsg) {
    case WM_CREATE:

	#define lpcs ((LPCREATESTRUCT)lParam)

        if (!CreateDitherBrush(FALSE))
            return -1;

	if (!InitGlobalObjects()) {
            FreeGlobalObjects();
	    return -1;
        }

	/* create the state data for this toolbar */

	pTBState = ALLOCWINDOWPOINTER(PTBSTATE, sizeof(TBSTATE)-sizeof(TBBUTTON));
	if (!pTBState)
	    return -1;

	/* The struct is initialized to all NULL when created.
	 */
	pTBState->hwndCommand = lpcs->hwndParent;

	pTBState->uStructSize = 0;

	// grow the button size to the appropriate girth
	if (!SetBitmapSize(pTBState, DEFAULTBITMAPX, DEFAULTBITMAPX))
	    return -1;

	SETWINDOWPOINTER(hWnd, PTBSTATE, pTBState);

	if (!(lpcs->style&(CCS_TOP|CCS_NOMOVEY|CCS_BOTTOM)))
	  {
	    lpcs->style |= CCS_TOP;
	    SetWindowLong(hWnd, GWL_STYLE, lpcs->style);
	  }
	break;

    case WM_DESTROY:
	if (pTBState)
	  {
	    PTBBMINFO pTemp;
	    int i;

	    /* Free all the bitmaps before exiting
	     */
	    for (pTemp=pTBState->pBitmaps, i=pTBState->nBitmaps-1; i>=0;
		  ++pTemp, --i)
	      {
		if (pTemp->hInst && pTemp->hbm)
		    DeleteObject(pTemp->hbm);
	      }
	    FlushButtonCache(pTBState);
	    if (pTBState->nStrings > 0)
		DestroyStrings(pTBState);

	    FREEWINDOWPOINTER(pTBState);
	    SETWINDOWPOINTER(hWnd, PTBSTATE, 0);
	  }
	FreeGlobalObjects();
        FreeDitherBrush();
	break;

    case WM_NCCALCSIZE:
#if WINVER >= 0x0400
         /*
          * This is sent when the window manager wants to find out
          * how big our client area is to be.  If we have a mini-caption
          * then we trap this message and calculate the cleint area rect,
          * which is the client area rect calculated by DefWindowProc()
          * minus the width/height of the mini-caption bar
          */
         // let defwindowproc handle the standard borders etc...

	dw = DefWindowProc(hWnd, wMsg, wParam, lParam ) ;

	if (!(GetWindowLong(hWnd, GWL_STYLE) & CCS_NODIVIDER))
	{
	    NCCALCSIZE_PARAMS FAR *lpNCP;
	    lpNCP = (NCCALCSIZE_PARAMS FAR *)lParam;
	    lpNCP->rgrc[0].top += 2;
	}

        return dw;
#endif
	break;

    case WM_NCACTIVATE:
    case WM_NCPAINT:

#if WINVER >= 0x0400
	// old-style toolbars are forced to be without dividers above
	if (!(GetWindowLong(hWnd, GWL_STYLE) & CCS_NODIVIDER))
	{
	    HDC hdc;
	    RECT rc;

	    hdc = GetWindowDC(hWnd);
	    GetWindowRect(hWnd, &rc);
	    ScreenToClient(hWnd, (LPPOINT)&(rc.left));
	    ScreenToClient(hWnd, (LPPOINT)&(rc.right));
	    rc.bottom = (-rc.top);	// bottom of NC area
	    rc.top = rc.bottom - (2 * GetSystemMetrics(SM_CYBORDER));

	    DrawBorder(hdc, &rc, BDR_SUNKENOUTER, BF_TOP | BF_BOTTOM);
	    ReleaseDC(hWnd, hdc);
	}
	else
            goto DoDefault;
#endif
	break;

    case WM_PAINT:
	ToolbarPaint(hWnd, pTBState);
	break;


    case WM_HSCROLL:  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    case WM_COMMAND:
    case WM_DRAWITEM:
    case WM_MEASUREITEM:
    case WM_VKEYTOITEM:
    case WM_CHARTOITEM:
	SendMessage(pTBState->hwndCommand, wMsg, wParam, lParam);
        break;

#ifdef _WIN32
    case WM_CTLCOLORBTN:
    case WM_CTLCOLORDLG:
    case WM_CTLCOLOREDIT:
    case WM_CTLCOLORLISTBOX:
    case WM_CTLCOLORMSGBOX:
    case WM_CTLCOLORSCROLLBAR:
    case WM_CTLCOLORSTATIC:
#else
    case WM_CTLCOLOR:
#endif
        //!!!!! ack use COLOR_BTNFACE
        return (LRESULT)(UINT)GetStockObject(LTGRAY_BRUSH);


    case WM_LBUTTONDOWN:

        iPos = TBHitTest(pTBState, LOWORD(lParam), HIWORD(lParam));
#if 0
	if ((wParam&MK_SHIFT) &&(GetWindowLong(hWnd, GWL_STYLE)&CCS_ADJUSTABLE))
	  {
	    MoveButton(hWnd, pTBState, iPos);
	  } else
#endif
	if (iPos >= 0)
	  {
	    ptbButton = pTBState->Buttons + iPos;

	    pTBState->pCaptureButton = ptbButton;
	    SetCapture(hWnd);

	    if (ptbButton->fsState & TBSTATE_ENABLED)
	      {
		ptbButton->fsState |= TBSTATE_PRESSED;
		InvalidateButton(hWnd, pTBState, ptbButton);
		UpdateWindow(hWnd);         // imedeate feedback
	      }

#ifdef _WIN32
	    SendMessage(pTBState->hwndCommand, WM_COMMAND,  (UINT)GetWindowLong(hWnd, GWL_ID), MAKELONG(pTBState->pCaptureButton->idCommand, TBN_BEGINDRAG));
#else
	    SendMessage(pTBState->hwndCommand, WM_COMMAND, GETWINDOWID(hWnd), MAKELONG(pTBState->pCaptureButton->idCommand, TBN_BEGINDRAG));
#endif
	  }
	break;

    case WM_MOUSEMOVE:
	// if the toolbar has lost the capture for some reason, stop
	if ((hWnd != GetCapture()) && (pTBState->pCaptureButton != NULL)) {
#ifdef _WIN32
	    SendMessage(pTBState->hwndCommand, WM_COMMAND, (UINT)GetWindowLong(hWnd, GWL_ID),
	    		MAKELONG(pTBState->pCaptureButton->idCommand, TBN_ENDDRAG));
#else
	    SendMessage(pTBState->hwndCommand, WM_COMMAND, GETWINDOWID(hWnd),
	    		MAKELONG(pTBState->pCaptureButton->idCommand, TBN_ENDDRAG));
#endif
	    // if the button is still pressed, unpress it.
	    if (pTBState->pCaptureButton->fsState & TBSTATE_PRESSED)
	        SendMessage(hWnd, TB_PRESSBUTTON, pTBState->pCaptureButton->idCommand, 0L);
	    pTBState->pCaptureButton = NULL;
	}
	else if (pTBState->pCaptureButton!=NULL
	      && (pTBState->pCaptureButton->fsState & TBSTATE_ENABLED)) {

	    iPos = TBHitTest(pTBState, LOWORD(lParam), HIWORD(lParam));
	    fSameButton = (iPos>=0
		  && pTBState->pCaptureButton==pTBState->Buttons+iPos);
	    if (fSameButton == !(pTBState->pCaptureButton->fsState & TBSTATE_PRESSED)) {
		pTBState->pCaptureButton->fsState ^= TBSTATE_PRESSED;
		InvalidateButton(hWnd, pTBState, pTBState->pCaptureButton);
	    }
	}
	break;

    case WM_LBUTTONUP:
	if (pTBState->pCaptureButton != NULL) {

	    int idCommand;

	    idCommand = pTBState->pCaptureButton->idCommand;

	    ReleaseCapture();

#ifdef _WIN32
	    SendMessage(pTBState->hwndCommand, WM_COMMAND, (UINT)GetWindowLong(hWnd, GWL_ID), MAKELONG(idCommand, TBN_ENDDRAG));
#else
	    SendMessage(pTBState->hwndCommand, WM_COMMAND, GETWINDOWID(hWnd), MAKELONG(idCommand, TBN_ENDDRAG));
#endif

	    iPos = TBHitTest(pTBState, LOWORD(lParam), HIWORD(lParam));
	    if ((pTBState->pCaptureButton->fsState&TBSTATE_ENABLED) && iPos>=0
		  && (pTBState->pCaptureButton==pTBState->Buttons+iPos)) {
		pTBState->pCaptureButton->fsState &= ~TBSTATE_PRESSED;

		if (pTBState->pCaptureButton->fsStyle & TBSTYLE_CHECK) {
		    if (pTBState->pCaptureButton->fsStyle & TBSTYLE_GROUP) {

			// group buttons already checked can't be force
			// up by the user.

		        if (pTBState->pCaptureButton->fsState & TBSTATE_CHECKED) {
			    pTBState->pCaptureButton = NULL;
			    break;	// bail!
			}

			pTBState->pCaptureButton->fsState |= TBSTATE_CHECKED;
		        MakeGroupConsistant(hWnd, pTBState, idCommand);
		    } else {
			pTBState->pCaptureButton->fsState ^= TBSTATE_CHECKED; // toggle
		    }
		    // if we change a button's state, we need to flush the
		    // cache
		    FlushButtonCache(pTBState);
		}
		InvalidateButton(hWnd, pTBState, pTBState->pCaptureButton);
		pTBState->pCaptureButton = NULL;
		SendMessage(pTBState->hwndCommand, WM_COMMAND, idCommand, 0L);
	    }
	    else {
		pTBState->pCaptureButton = NULL;
	    }
	}
	break;

    case TB_SETSTATE:
	iPos = PositionFromID(pTBState, (int)wParam);
	if (iPos < 0)
	    return(FALSE);
	ptbButton = pTBState->Buttons + iPos;

	fsState = (BYTE)(LOWORD(lParam) ^ ptbButton->fsState);
        ptbButton->fsState = (BYTE)LOWORD(lParam);

	if (fsState)
	    // flush the button cache
	    //!!!! this could be much more intelligent
	    FlushButtonCache(pTBState);

	if (fsState & TBSTATE_HIDDEN)
	    InvalidateRect(hWnd, NULL, TRUE);
	else if (fsState)
	    InvalidateButton(hWnd, pTBState, ptbButton);
        return(TRUE);

    case TB_GETSTATE:
	iPos = PositionFromID(pTBState, (int)wParam);
	if (iPos < 0)
	    return(-1L);
        return(pTBState->Buttons[iPos].fsState);

    case TB_ENABLEBUTTON:
    case TB_CHECKBUTTON:
    case TB_PRESSBUTTON:
    case TB_HIDEBUTTON:
    case TB_INDETERMINATE:

        iPos = PositionFromID(pTBState, (int)wParam);
	if (iPos < 0)
	    return(FALSE);
        ptbButton = &pTBState->Buttons[iPos];
        fsState = ptbButton->fsState;

        if (LOWORD(lParam))
            ptbButton->fsState |= wStateMasks[wMsg - TB_ENABLEBUTTON];
	else
            ptbButton->fsState &= ~wStateMasks[wMsg - TB_ENABLEBUTTON];

        // did this actually change the state?
        if (fsState != ptbButton->fsState) {
            // is this button a member of a group?
	    if ((wMsg == TB_CHECKBUTTON) && (ptbButton->fsStyle & TBSTYLE_GROUP))
	        MakeGroupConsistant(hWnd, pTBState, (int)wParam);

	    // flush the button cache
	    //!!!! this could be much more intelligent
	    FlushButtonCache(pTBState);

	    if (wMsg == TB_HIDEBUTTON)
		InvalidateRect(hWnd, NULL, TRUE);
	    else
		InvalidateButton(hWnd, pTBState, ptbButton);
        }
        return(TRUE);

    case TB_ISBUTTONENABLED:
    case TB_ISBUTTONCHECKED:
    case TB_ISBUTTONPRESSED:
    case TB_ISBUTTONHIDDEN:
    case TB_ISBUTTONINDETERMINATE:
        iPos = PositionFromID(pTBState, (int)wParam);
	if (iPos < 0)
	    return(-1L);
        return (LRESULT)pTBState->Buttons[iPos].fsState
	      & wStateMasks[wMsg - TB_ISBUTTONENABLED];

    case TB_ADDBITMAP:
#ifdef _WIN32
    {	
	TB_ADDBITMAPINFO * ptbai;

	ptbai = (TB_ADDBITMAPINFO *)lParam;

	return AddBitmap(pTBState, wParam, ptbai->hBitmap, ptbai->idResource);
    }
#else
	return(AddBitmap(pTBState, wParam,
	      (HINSTANCE)LOWORD(lParam), HIWORD(lParam)));
#endif


    case TB_ADDBUTTONS:
	return(InsertButtons(hWnd, pTBState, (UINT)-1, wParam,
	      (LPTBBUTTON)lParam));

    case TB_INSERTBUTTON:
	return(InsertButtons(hWnd, pTBState, wParam, 1, (LPTBBUTTON)lParam));

    case TB_DELETEBUTTON:
	return(DeleteButton(hWnd, pTBState, wParam));

    case TB_GETBUTTON:
	if (wParam >= (UINT)pTBState->iNumButtons)
	    return(FALSE);

	TBOutputStruct(pTBState, pTBState->Buttons+wParam, (LPTBBUTTON)lParam);
	return(TRUE);

    case TB_BUTTONCOUNT:
	return(pTBState->iNumButtons);

    case TB_COMMANDTOINDEX:
        return(PositionFromID(pTBState, (int)wParam));


    case TB_GETITEMRECT:
	return(MAKELRESULT(GetItemRect(pTBState, wParam, (LPRECT)lParam), 0));
	break;

    case TB_BUTTONSTRUCTSIZE:
	/* You are not allowed to change this after adding buttons.
	*/
	if (!pTBState || pTBState->iNumButtons)
	{
		break;
	}
	pTBState->uStructSize = wParam;
	break;

    case TB_SETBUTTONSIZE:
	if (!LOWORD(lParam))
	    lParam = MAKELONG(DEFAULTBUTTONX, HIWORD(lParam));
	if (!HIWORD(lParam))
	    lParam = MAKELONG(LOWORD(lParam), DEFAULTBUTTONY);
	return(GrowToolbar(pTBState, LOWORD(lParam), HIWORD(lParam), FALSE));

    case TB_SETBITMAPSIZE:
	return(SetBitmapSize(pTBState, LOWORD(lParam), HIWORD(lParam)));

    case TB_SETBUTTONTYPE:
	pTBState->wButtonType = wParam;
	break;

    default:
#if WINVER >= 0x0400
DoDefault:
#endif
	return DefWindowProc(hWnd, wMsg, wParam, lParam);
    }

    return 0L;
}
