/* ** ** Toolbar.c ** ** This is it, the incredibly famous toolbar control. Most of ** the customization stuff is in another file. ** */ #include "ctlspriv.h" #include #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; iButtonfsState & 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; iButtonfsState & 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; }