/* ** Toolbar.c ** ** This is it, the incredibly famous toolbar control. Most of ** the customization stuff is in another file. */ #include "ctlspriv.h" #include "toolbar.h" extern "C" { #include "image.h" } #include #define __IOleControl_INTERFACE_DEFINED__ // There is a conflich with the IOleControl's def of CONTROLINFO #include "shlobj.h" #define TBP_ONRELEASECAPTURE (WM_USER + 0x500) #define TBIMAGELIST // these values are defined by the UI gods... #define DEFAULTBITMAPX 16 #define DEFAULTBITMAPY 15 #define LIST_GAP (g_cxEdge * 2) #define DROPDOWN_GAP (g_cxEdge * 2) #define CX_TOP_FUDGE (g_cxEdge * 2) #define SMALL_DXYBITMAP 16 // new dx dy for sdt images #define LARGE_DXYBITMAP 24 #define DEFAULTBUTTONX 24 #define DEFAULTBUTTONY 22 // the insert mark is 6 pixels high/wide depending on horizontal or vertical mode... #define INSERTMARKSIZE 6 const int g_dxButtonSep = 8; const int s_xFirstButton = 0; // was 8 in 3.1 #define USE_MIXED_BUTTONS(ptb) (((ptb)->dwStyleEx & TBSTYLE_EX_MIXEDBUTTONS) && ((ptb)->ci.style & TBSTYLE_LIST)) #define BTN_NO_SHOW_TEXT(ptb, ptbb) (!(ptb)->nTextRows || (USE_MIXED_BUTTONS(ptb) && !((ptbb)->fsStyle & BTNS_SHOWTEXT))) #define BTN_IS_AUTOSIZE(ptb, ptbb) (((ptbb)->fsStyle & BTNS_AUTOSIZE) || (USE_MIXED_BUTTONS(ptb) && !((ptbb)->fsStyle & BTNS_SEP))) #define DRAW_MONO_BTN(ptb, state) (!(state & TBSTATE_ENABLED) || ((ptb->ci.style & WS_DISABLED))) #ifdef DPITEST #define ToolBar_IsDPIScaled(ptb) TRUE #else #define ToolBar_IsDPIScaled(ptb) (CCDPIScale(ptb->ci)) #endif // Globals - since all of these globals are used durring a paint we have to // take a criticial section around all toolbar paints. can we do better? // const UINT wStateMasks[] = { TBSTATE_ENABLED, TBSTATE_CHECKED, TBSTATE_PRESSED, TBSTATE_HIDDEN, TBSTATE_INDETERMINATE, TBSTATE_MARKED }; #define TBISSTRINGPTR(iString) (((iString) != -1) && (!IS_INTRESOURCE(iString))) #define TBDraw_State(ptbdraw) ((ptbdraw)->tbcd.nmcd.uItemState) LRESULT CALLBACK ToolbarWndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam); void TBOnButtonStructSize(PTBSTATE ptb, UINT uStructSize); BOOL SetBitmapSize(PTBSTATE ptb, int width, int height); int AddBitmap(PTBSTATE ptb, int nButtons, HINSTANCE hBMInst, UINT_PTR wBMID); void TBBuildImageList(PTBSTATE ptb); BOOL GetInsertMarkRect(PTBSTATE ptb, LPRECT lpRect, BOOL fHorizMode); extern "C" LPTSTR TB_StrForButton(PTBSTATE ptb, LPTBBUTTONDATA pTBButton); UINT TBGetDrawTextFlags(PTBSTATE ptb, UINT uiStyle, LPTBBUTTONDATA); BOOL TBGetMaxSize( PTBSTATE ptb, LPSIZE lpsize ); void TBGetItem(PTBSTATE ptb,LPTBBUTTONDATA ptButton, LPNMTBDISPINFO ptbdi); #define GT_INSIDE 0x0001 #define GT_MASKONLY 0x0002 BOOL GrowToolbar(PTBSTATE ptb, int newButWidth, int newButHeight, UINT flags); //Pager Control Functions LRESULT TB_OnScroll(PTBSTATE ptb, LPNMHDR pnm); LRESULT TB_OnPagerControlNotify(PTBSTATE ptb,LPNMHDR pnm); void TBAutoSize(PTBSTATE ptb); LRESULT TB_OnCalcSize(PTBSTATE ptb, LPNMHDR pnm); #define TBInvalidateImageList(ptb) ((ptb)->fHimlValid = FALSE) #define TBHasStrings(ptb) ((ptb)->nStrings || (ptb)->fNoStringPool) #ifdef DEBUG #if 0 void _InvalidateRect(HWND hwnd, LPRECT prc, BOOL fInval) { if (!(GetAsyncKeyState(VK_SHIFT) < 0) ) InvalidateRect(hwnd, prc, fInval); } void _RedrawWindow(HWND hwnd, LPRECT prc, HANDLE hrgn, UINT uFlags) { if (!(GetAsyncKeyState(VK_SHIFT) < 0) ) RedrawWindow(hwnd, prc, hrgn, uFlags); } void _SetWindowPos(HWND hwnd, HWND hwnd2, int x, int y, int cx, int cy, UINT uFlags) { if (GetAsyncKeyState(VK_SHIFT) < 0) uFlags &= ~( SWP_FRAMECHANGED); SetWindowPos(hwnd, hwnd2, x, y, cx, cy, uFlags); } #define InvalidateRect(hwnd, prc, fInval) _InvalidateRect(hwnd, prc, fInval) #define RedrawWindow(hwnd, prc, hrgn, uFlags) _RedrawWindow(hwnd, prc, hrgn, uFlags) #define SetWindowPos(hwnd, hwnd2, x, y, cx, cy, uFlags) _SetWindowPos(hwnd, hwnd2, x, y, cx, cy, uFlags) #endif #endif __inline BOOL TB_IsDropDown(LPTBBUTTONDATA ptbb) { BOOL fRet = (ptbb->fsStyle & (BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN)); return fRet; } __inline BOOL TB_HasDDArrow(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { BOOL fRet = (((ptb->dwStyleEx & TBSTYLE_EX_DRAWDDARROWS) && (ptbb->fsStyle & BTNS_DROPDOWN)) || (ptbb->fsStyle & BTNS_WHOLEDROPDOWN)); return fRet; } __inline BOOL TB_HasSplitDDArrow(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { // If the button is both BTNS_DROPDOWN and BTNS_WHOLEDROPDOWN, // BTNS_WHOLEDROPDOWN wins. BOOL fRet = ((ptb->dwStyleEx & TBSTYLE_EX_DRAWDDARROWS) && (ptbb->fsStyle & BTNS_DROPDOWN) && !(ptbb->fsStyle & BTNS_WHOLEDROPDOWN)); return fRet; } __inline BOOL TB_HasUnsplitDDArrow(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { BOOL fRet = (ptbb->fsStyle & BTNS_WHOLEDROPDOWN); return fRet; } __inline BOOL TB_HasTopDDArrow(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { BOOL fRet = (!(ptb->ci.style & TBSTYLE_LIST) && TB_HasUnsplitDDArrow(ptb, ptbb) && (ptb->nTextRows > 0) && TB_StrForButton(ptb, ptbb)); return fRet; } // check if toolbar is double buffered __inline BOOL TB_IsDoubleBuffer(PTBSTATE ptb) { #ifdef FULL_DEBUG static fOn = TRUE; if (GetKeyState(VK_SCROLL) < 0) { fOn = !fOn; } return (BOOL)fOn && (ptb->dwStyleEx & TBSTYLE_EX_DOUBLEBUFFER) && !(ptb->ci.dwExStyle & WS_EX_TRANSPARENT); #endif return (BOOL)((ptb->dwStyleEx & TBSTYLE_EX_DOUBLEBUFFER) || ptb->fForcedDoubleBuffer) && !(ptb->ci.dwExStyle & WS_EX_TRANSPARENT); } // Cancel tracking tooltips which are activated by item focus via keyboard void TB_CancelTipTrack(PTBSTATE ptb) { // Make sure in tracking mode if (ptb->hwndToolTips) { // Cancel any pending timer KillTimer(ptb->ci.hwnd, IDT_TRACKINGTIP); if (TB_IsKbdTipTracking(ptb) && ptb->iTracking < ptb->iNumButtons) { TOOLINFO ti = {0}; ti.cbSize = sizeof(TOOLINFO); ti.hwnd = ptb->ci.hwnd; ti.uId = ptb->Buttons[ptb->iTracking].idCommand; SendMessage(ptb->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti); SendMessage(ptb->hwndToolTips, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti); // Switch tooltip window back to non-tracking (manual) mode ti.uFlags &= ~TTF_TRACK; SendMessage(ptb->hwndToolTips, TTM_SETTOOLINFO, 0, (LPARAM)&ti); // Nothing being tracked ptb->iTracking = TBKTT_NOTRACK; } } } BOOL TBIsHotTrack(PTBSTATE ptb, LPTBBUTTONDATA ptButton, UINT state) { BOOL fHotTrack = FALSE; if (&ptb->Buttons[ptb->iHot]==ptButton) fHotTrack = TRUE; // The following is in place to prevent hot tracking during the following conds: // - drag & drop toolbar customization // - when the mouse capture is on a particular button-press. // This does _not_ drop out of the loop because we don't want to break update // behavior; thus we'll have a little flickering on refresh as we pass over // these buttons. if (!(state & TBSTATE_PRESSED) && (GetKeyState (VK_LBUTTON) < 0) && GetCapture() == ptb->ci.hwnd) { fHotTrack = FALSE; } if (!fHotTrack && (ptb->iPressedDD == ptButton - ptb->Buttons)) fHotTrack = TRUE; return fHotTrack; } UINT StateFromCDIS(UINT uItemState) { UINT state = 0; if (uItemState & CDIS_CHECKED) state |= TBSTATE_CHECKED; if (uItemState & CDIS_SELECTED) state |= TBSTATE_PRESSED; if (!(uItemState & CDIS_DISABLED)) state |= TBSTATE_ENABLED; if (uItemState & CDIS_MARKED) state |= TBSTATE_MARKED; if (uItemState & CDIS_INDETERMINATE) state |= TBSTATE_INDETERMINATE; return state; } UINT CDISFromState(UINT state) { UINT uItemState = 0; // Here are the TBSTATE - to - CDIS mappings: // // TBSTATE_CHECKED = CDIS_CHECKED // TBSTATE_PRESSED = CDIS_SELECTED // !TBSTATE_ENABLED = CDIS_DISABLED // TBSTATE_MARKED = CDIS_MARKED // TBSTATE_INDETERMINATE = CDIS_INDETERMINATE // // Hot tracked item = CDIS_HOT // if (state & TBSTATE_CHECKED) uItemState |= CDIS_CHECKED; if (state & TBSTATE_PRESSED) uItemState |= CDIS_SELECTED; if (!(state & TBSTATE_ENABLED)) uItemState |= CDIS_DISABLED; if (state & TBSTATE_MARKED) uItemState |= CDIS_MARKED; if (state & TBSTATE_INDETERMINATE) uItemState |= CDIS_INDETERMINATE; return uItemState; } void FlushToolTipsMgrNow(PTBSTATE ptb); void TB_ForceCreateTooltips(PTBSTATE ptb) { if (ptb->ci.style & TBSTYLE_TOOLTIPS && !ptb->hwndToolTips) { TOOLINFO ti; // don't bother setting the rect because we'll do it below // in TBInvalidateItemRects; ti.cbSize = sizeof(ti); ti.uFlags = TTF_IDISHWND|TTF_ABSOLUTE; if (ptb->dwStyleEx & TBSTYLE_EX_TOOLTIPSEXCLUDETOOLBAR) ti.uFlags |= TTF_EXCLUDETOOLAREA; ti.hwnd = ptb->ci.hwnd; ti.uId = (UINT_PTR)ptb->ci.hwnd; ti.lpszText = 0; ptb->hwndToolTips = CreateWindowEx(WS_EX_TRANSPARENT, c_szSToolTipsClass, NULL, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, ptb->ci.hwnd, NULL, HINST_THISDLL, NULL); if (ptb->hwndToolTips) { int i; NMTOOLTIPSCREATED nm; CCSetInfoTipWidth(ptb->ci.hwnd, ptb->hwndToolTips); SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); nm.hwndToolTips = ptb->hwndToolTips; CCSendNotify(&ptb->ci, NM_TOOLTIPSCREATED, &nm.hdr); // don't bother setting the rect because we'll do it below // in TBInvalidateItemRects; ti.uFlags = 0; ti.lpszText = LPSTR_TEXTCALLBACK; for (i = 0; i < ptb->iNumButtons; i++) { if (!(ptb->Buttons[i].fsStyle & BTNS_SEP)) { ti.uId = ptb->Buttons[i].idCommand; SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } } FlushToolTipsMgrNow(ptb); } } } void TBRelayToToolTips(PTBSTATE ptb, UINT wMsg, WPARAM wParam, LPARAM lParam) { TB_ForceCreateTooltips(ptb); if (ptb->hwndToolTips) { RelayToToolTips(ptb->hwndToolTips, ptb->ci.hwnd, wMsg, wParam, lParam); } } LRESULT ToolbarDragCallback(HWND hwnd, UINT code, WPARAM wp, LPARAM lp) { PTBSTATE ptb = (PTBSTATE)GetWindowInt(hwnd, 0); LRESULT lres; switch (code) { case DPX_DRAGHIT: if (lp) { POINT pt; int item; pt.x = ((POINTL *)lp)->x; pt.y = ((POINTL *)lp)->y; MapWindowPoints(NULL, ptb->ci.hwnd, &pt, 1); item = TBHitTest(ptb, pt.x, pt.y); if (0 <= item && item < ptb->iNumButtons) lres = (LRESULT)ptb->Buttons[item].idCommand; else lres = (LRESULT)-1; } else lres = -1; break; case DPX_GETOBJECT: lres = (LRESULT)GetItemObject(&ptb->ci, TBN_GETOBJECT, &IID_IDropTarget, (LPNMOBJECTNOTIFY)lp); break; case DPX_SELECT: if ((int)wp >= 0) { NMTBHOTITEM nmhi; nmhi.idNew = (int) wp; if (!CCSendNotify(&ptb->ci, TBN_DRAGOVER, &nmhi.hdr)) { SendMessage(ptb->ci.hwnd, TB_MARKBUTTON, wp, MAKELPARAM((lp != DROPEFFECT_NONE), 0)); } } lres = 0; break; default: lres = -1; break; } return lres; } int TBMixedButtonHeight(PTBSTATE ptb, int iIndex) { int iHeight; LPTBBUTTONDATA ptbb = &(ptb->Buttons[iIndex]); if (ptbb->fsStyle & BTNS_SHOWTEXT) // text and icon iHeight = max(ptb->iDyBitmap, ptb->dyIconFont); else // icon, no text iHeight = ptb->iDyBitmap; return iHeight; } int TBMixedButtonsHeight(PTBSTATE ptb) { int i; int iHeightMax = 0; int iHeight; ASSERT(ptb->ci.style & TBSTYLE_LIST); ASSERT(USE_MIXED_BUTTONS(ptb)); for (i = 0; i < ptb->iNumButtons; i++) { iHeight = TBMixedButtonHeight(ptb, i); iHeightMax = max(iHeightMax, iHeight); } return iHeightMax; } int HeightWithString(PTBSTATE ptb, int h) { if (USE_MIXED_BUTTONS(ptb)) { int hMixed = TBMixedButtonsHeight(ptb); return (max(h, hMixed)); } else if (ptb->ci.style & TBSTYLE_LIST) return (max(h, ptb->dyIconFont)); else if (ptb->dyIconFont) return (h + ptb->dyIconFont + 1); else return (h); } int TBGetSepHeight(PTBSTATE ptb, LPTBBUTTONDATA pbtn) { ASSERT(pbtn->fsStyle & BTNS_SEP); // THEMESBUMMER: Can't change the size of a separator because apps expect a certain size // If we want people to use V6 we can't change that behaviour if (ptb->ci.style & (CCS_VERT | TBSTYLE_FLAT) ) return pbtn->cxySep; else return pbtn->cxySep * 2 / 3; } UINT TBWidthOfString(PTBSTATE ptb, LPTBBUTTONDATA ptbb, HDC hdc) { UINT uiWidth = 0; LPTSTR pstr = TB_StrForButton(ptb, ptbb); if (pstr) { HDC hdcCreated = NULL; HFONT hOldFont; UINT uiStyle; RECT rcText = {0,0,1000,10}; if (!hdc) { hdcCreated = GetDC(ptb->ci.hwnd); hdc = hdcCreated; } hOldFont = (HFONT)SelectObject(hdc, ptb->hfontIcon); uiStyle = DT_CALCRECT | TBGetDrawTextFlags(ptb, 0, ptbb); HRESULT hr = E_FAIL; if (ptb->hTheme) hr = GetThemeTextExtent(ptb->hTheme, hdc, 0, 0, pstr, -1, uiStyle, &rcText, &rcText); if (FAILED(hr)) DrawText(hdc, pstr, -1, &rcText, uiStyle); uiWidth += rcText.right; SelectObject(hdc, hOldFont); if (hdcCreated) ReleaseDC(ptb->ci.hwnd, hdcCreated); } return uiWidth; } // TBDDArrowAdjustment(ptb, ptbb): the amount by which we change the width of // this button to accomodate the drop-down arrow. not necessarily the same as // ptb->dxDDArrowChar. int TBDDArrowAdjustment(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { int iAdjust = 0; if (TB_HasDDArrow(ptb, ptbb)) { // If a whole dd, non-autosize button, then we'll just use the standard // button width which ought to have room for this button (i.e., return 0). if (!TB_HasTopDDArrow(ptb, ptbb) || BTN_IS_AUTOSIZE(ptb, ptbb)) { iAdjust += (WORD)ptb->dxDDArrowChar; if (TB_HasUnsplitDDArrow(ptb, ptbb)) { // subtract off a bit since there won't be a border // around dd arrow part of this button iAdjust -= 2 * g_cxEdge; if (ptbb->iBitmap != I_IMAGENONE) { // nudge over a bit more to overlap bitmap border padding iAdjust -= g_cxEdge; } } if (TB_HasTopDDArrow(ptb, ptbb)) { // If string width >= icon width + iAdjust, then no need // to add extra space for the arrow. if ((int)TBWidthOfString(ptb, ptbb, NULL) >= ptb->iDxBitmap + iAdjust) iAdjust = 0; } } } return max(iAdjust, 0); } void TBGetPartAndState(PTBSTATE ptb, LPTBBUTTONDATA ptButton, int* piPart, int* piState) { int state = ptButton->fsState; BOOL fHotTrack = TBIsHotTrack(ptb, ptButton, state); *piPart = TP_BUTTON; // Whole dropdown if (TB_HasDDArrow(ptb, ptButton)) { *piPart = TP_DROPDOWNBUTTON; if (!TB_HasUnsplitDDArrow(ptb, ptButton)) // unless it's split { *piPart = TP_SPLITBUTTON; } } *piState = TS_NORMAL; if (state & TBSTATE_PRESSED) *piState = TS_PRESSED; else if (DRAW_MONO_BTN(ptb, state)) *piState = TS_DISABLED; else if (fHotTrack && (state & TBSTATE_CHECKED)) *piState = TS_HOTCHECKED; else if (fHotTrack) *piState = TS_HOT; else if (state & TBSTATE_CHECKED) *piState = TS_CHECKED; } int TBWidthOfButton(PTBSTATE ptb, LPTBBUTTONDATA pButton, HDC hdc) { RECT rc; UINT uiStringWidth; if (BTN_IS_AUTOSIZE(ptb, pButton)) { // if they've set this button for autosize, calculate it and cache // it in cx if (BTN_NO_SHOW_TEXT(ptb, pButton)) { pButton->cx = 0; goto CalcIconWidth; } if (pButton->cx == 0) { uiStringWidth = TBWidthOfString(ptb, pButton, hdc); pButton->cx = (WORD) ptb->cxPad + uiStringWidth; if (uiStringWidth) { // Since we have a string for this button, we need to add // some padding around it. if ((ptb->ci.style & TBSTYLE_LIST) && TB_HasSplitDDArrow(ptb, pButton)) pButton->cx += (WORD) ptb->iDropDownGap; else pButton->cx += 2 * g_cxEdge; } CalcIconWidth: if (pButton->iBitmap != I_IMAGENONE) { if (ptb->ci.style & TBSTYLE_LIST) { pButton->cx += ptb->iDxBitmap + ptb->iListGap; if (BTN_NO_SHOW_TEXT(ptb, pButton)) pButton->cx += g_cxEdge * 2; } else { // Use wider of string width (pButton->cx so far) and bitmap width. pButton->cx = max(pButton->cx, ptb->iDxBitmap + ptb->cxPad); } } pButton->cx += (USHORT)TBDDArrowAdjustment(ptb, pButton); if (ptb->hTheme) { RECT rc = {0, 0, pButton->cx, ptb->iButHeight}; int iPartId; int iStateId; TBGetPartAndState(ptb, pButton, &iPartId, &iStateId); GetThemeBackgroundExtent(ptb->hTheme, hdc, iPartId, iStateId, &rc, &rc); pButton->cx = (USHORT)RECTWIDTH(rc); } } } if (pButton->cx) { return (int)pButton->cx; } else if (pButton->fsStyle & BTNS_SEP) { if (ptb->ci.style & CCS_VERT) { GetWindowRect(ptb->ci.hwnd, &rc); return RECTWIDTH(rc); } else { // Compat: Corel (Font navigator) expects the separators to be // 8 pixels wide. So do not return pButton->cxySep here, since // that can be calculated differently depending on the flat style. // // No. owner draw items are added by specifying separator, and // the iBitmap width which is then copied down to cxySep. // the preserving of size for corel needs to be done at that point. return pButton->cxySep; } } else if (!(TBSTYLE_EX_VERTICAL & ptb->dwStyleEx) && !(TBSTYLE_EX_FIXEDDROPDOWN & ptb->dwStyleEx)) { return ptb->iButWidth + TBDDArrowAdjustment(ptb, pButton); } else { return ptb->iButWidth; } } UINT TBGetDrawTextFlags(PTBSTATE ptb, UINT uiStyle, TBBUTTONDATA* ptbb) { if (ptb->nTextRows > 1) uiStyle |= DT_WORDBREAK | DT_EDITCONTROL; else uiStyle |= DT_SINGLELINE; if (ptb->ci.style & TBSTYLE_LIST) { uiStyle |= DT_LEFT | DT_VCENTER | DT_SINGLELINE; if (ptbb->iBitmap == I_IMAGENONE) { uiStyle |= DT_CENTER; } } else { uiStyle |= DT_CENTER; } uiStyle &= ~(ptb->uDrawTextMask); uiStyle |= ptb->uDrawText; if (ptbb->fsStyle & BTNS_NOPREFIX) uiStyle |= DT_NOPREFIX; if (CCGetUIState(&(ptb->ci)) & UISF_HIDEACCEL) { uiStyle |= DT_HIDEPREFIX; } return uiStyle; } BOOL TBRecalc(PTBSTATE ptb) { TEXTMETRIC tm = {0}; int i; HDC hdc; int cxMax = 0, cxMask, cy; HFONT hOldFont=NULL; if (ptb->fRedrawOff) { // redraw is off; defer recalc until redraw is turned back on ptb->fRecalc = TRUE; return TRUE; // The recalc "succeeded" - actual work will happen later } ptb->dyIconFont = 0; if (!TBHasStrings(ptb) || !ptb->nTextRows ) { cxMax = ptb->iDxBitmap; cxMask = cxMax; } else { SIZE size = {0}; LPCTSTR pstr; RECT rcText = {0,0,0,0}; int cxExtra = ptb->cxPad; ptb->iButWidth = 0; hdc = GetDC(ptb->ci.hwnd); if (!hdc) return(FALSE); if (ptb->hfontIcon) hOldFont = (HFONT)SelectObject(hdc, ptb->hfontIcon); if (ptb->hTheme) { GetThemeTextMetrics(ptb->hTheme, hdc, 0, 0, &tm); } else { GetTextMetrics(hdc, &tm); } if (ptb->nTextRows) { ptb->dyIconFont = (tm.tmHeight * ptb->nTextRows) + (tm.tmExternalLeading * (ptb->nTextRows - 1)); // add an edge ? } if (ptb->ci.style & TBSTYLE_LIST) cxExtra += ptb->iDxBitmap + ptb->iListGap; // default to the image size... cxMax = ptb->iDxBitmap; // walk strings to find max width for (i = 0; i < ptb->iNumButtons; i++) { if (ptb->Buttons[i].fsState & TBSTATE_HIDDEN) continue; if (BTN_IS_AUTOSIZE(ptb, &ptb->Buttons[i])) ptb->Buttons[i].cx = 0; pstr = TB_StrForButton(ptb, &ptb->Buttons[i]); if (pstr) { // wordbreak is not allowed in the calcrect w/ singleline UINT uiStyle = DT_CALCRECT | DT_SINGLELINE | (TBGetDrawTextFlags(ptb, 0, &ptb->Buttons[i]) & ~DT_WORDBREAK); RECT rcTemp = {0,0,0,0}; rcTemp.bottom = ptb->dyIconFont; HRESULT hr = E_FAIL; if (ptb->hTheme) hr = GetThemeTextExtent(ptb->hTheme, hdc, 0, 0, pstr, -1, uiStyle, &rcTemp, &rcTemp); if (FAILED(hr)) DrawText(hdc, pstr, -1, &rcTemp, uiStyle); size.cx = RECTWIDTH(rcTemp); size.cy = RECTHEIGHT(rcTemp); } else { size.cx = 0; } if (TB_HasTopDDArrow(ptb, &ptb->Buttons[i])) { int iBmpWithArrow = CX_TOP_FUDGE + ptb->iDxBitmap + ptb->dxDDArrowChar; size.cx = max(size.cx, iBmpWithArrow); } else if ((ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) && TB_HasDDArrow(ptb, &ptb->Buttons[i])) { // for vertical toolbars, buttons with drop-down arrows // are drawn with the same width as normal buttons, so // we need to figure them into our max width calculation. size.cx += ptb->dxDDArrowChar; } if (cxMax < size.cx) cxMax = size.cx; } // if cxMax is less than the iButMinWidth - dxBitmap (if LIST) then // cxMax = iButMinWidth if (ptb->iButMinWidth && (ptb->iButMinWidth > (cxMax + cxExtra))) cxMax = ptb->iButMinWidth - cxExtra; cxMask = cxMax; // Is the cxMax + dxBitmap (if LIST) more than the max width ? if (ptb->iButMaxWidth && (ptb->iButMaxWidth < (cxMax + cxExtra))) { int cyMax = 0; int cxTemp = 0; cxMax = ptb->iButMaxWidth - cxExtra; // But leave cxMask at its old value since AUTOSIZE buttons // are exempt from button truncation. This exemption is a bug, // but IE4 shipped that way so we're stuck with it. (You can // tell it's a bug because we go ahead and flip TBSTATE_ELLIPSIS // even on AUTOSIZE buttons, only to "forget" about the ellipsis // in TBWidthOfString().) // walk strings to set the TBSTATE_ELLIPSES for (i = 0; i < ptb->iNumButtons; i++) { BOOL fEllipsed = FALSE; UINT uiStyle; if (ptb->Buttons[i].fsState & TBSTATE_HIDDEN) continue; if (BTN_NO_SHOW_TEXT(ptb, &ptb->Buttons[i])) pstr = NULL; else { pstr = TB_StrForButton(ptb, &ptb->Buttons[i]); uiStyle = DT_CALCRECT | TBGetDrawTextFlags(ptb, 0, &ptb->Buttons[i]); } if (pstr) { int cxMaxText; if ((ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) && TB_HasDDArrow(ptb, &ptb->Buttons[i])) { // if a drop-down button on a vertical toolbar, // need to make space for drop-down arrow cxMaxText = cxMax - ptb->dxDDArrowChar; } else { cxMaxText = cxMax; } // DrawText doesn't like it when cxMaxText <= 0 cxMaxText = max(cxMaxText, 1); rcText.bottom = ptb->dyIconFont; rcText.right = cxMaxText; HRESULT hr = E_FAIL; if (ptb->hTheme) hr = GetThemeTextExtent(ptb->hTheme, hdc, 0, 0, pstr, -1, uiStyle, &rcText, &rcText); if (FAILED(hr)) DrawText(hdc, pstr, -1, &rcText, uiStyle); if (ptb->nTextRows > 1) { // width is width of text plus width we might // have lopped off for drop-down arrow int cx = rcText.right + (cxMax - cxMaxText); if (cx > cxTemp) { // this is our new multiline text hack max cxTemp = cx; } fEllipsed = (BOOL)(rcText.bottom > ptb->dyIconFont); } else fEllipsed = (BOOL)(rcText.right > cxMaxText); if (cyMax < rcText.bottom) cyMax = rcText.bottom; } if (fEllipsed) ptb->Buttons[i].fsState |= TBSTATE_ELLIPSES; else ptb->Buttons[i].fsState &= ~TBSTATE_ELLIPSES; } if (cxTemp && (ptb->nTextRows > 1 )) cxMax = cxTemp; // Set the text height to the tallest text, with the top end being the number // of rows specified by MAXTEXTROWS if (ptb->dyIconFont > cyMax) ptb->dyIconFont = cyMax; } else { for (i = 0; i < ptb->iNumButtons; i++) ptb->Buttons[i].fsState &= ~TBSTATE_ELLIPSES; if ((ptb->nTextRows) && ptb->iNumButtons && (ptb->dyIconFont > size.cy)) ptb->dyIconFont = size.cy; } if (ptb->iButMinWidth && (ptb->iButMinWidth > (cxMax + cxExtra))) cxMax = ptb->iButMinWidth - cxExtra; if (hOldFont) SelectObject(hdc, hOldFont); ReleaseDC(ptb->ci.hwnd, hdc); } // // Need to call GrowToolbar twice, once to grow the mask, and again // to grow the buttons. (Yes, this is sick.) // cy = HeightWithString(ptb, ptb->iDyBitmap); if (!GrowToolbar(ptb, max(cxMax, cxMask), cy, GT_INSIDE | GT_MASKONLY)) return(FALSE); return(GrowToolbar(ptb, cxMax, cy, GT_INSIDE)); } BOOL TBChangeFont(PTBSTATE ptb, WPARAM wParam, HFONT hFont) { LOGFONT lf; BOOL fWasFontCreated = ptb->fFontCreated; if ((wParam != 0) && (wParam != SPI_SETICONTITLELOGFONT) && (wParam != SPI_SETNONCLIENTMETRICS)) return(FALSE); if (!SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, 0)) return(FALSE); if (!hFont) { if (!(hFont = CreateFontIndirect(&lf))) return(FALSE); ptb->fFontCreated = TRUE; } else { ptb->fFontCreated = FALSE; } if (ptb->hfontIcon && fWasFontCreated) DeleteObject(ptb->hfontIcon); ptb->hfontIcon = hFont; return(TBRecalc(ptb)); } void TBSetFont(PTBSTATE ptb, HFONT hFont, BOOL fInval) { TBChangeFont(ptb, 0, hFont); if (fInval) InvalidateRect(ptb->ci.hwnd, NULL, TRUE); } EXTERN_C HWND WINAPI CreateToolbarEx(HWND hwnd, DWORD ws, UINT wID, int nBitmaps, HINSTANCE hBMInst, UINT_PTR wBMID, LPCTBBUTTON lpButtons, int iNumButtons, int dxButton, int dyButton, int dxBitmap, int dyBitmap, UINT uStructSize) { HWND hwndToolbar = CreateWindow(c_szToolbarClass, NULL, WS_CHILD | ws, 0, 0, 100, 30, hwnd, IntToPtr_(HMENU, wID), HINST_THISDLL, NULL); if (hwndToolbar) { PTBSTATE ptb = (PTBSTATE)GetWindowInt(hwndToolbar, 0); TBOnButtonStructSize(ptb, uStructSize); if ((dxBitmap && dyBitmap && !SetBitmapSize(ptb, dxBitmap, dyBitmap)) || (dxButton && dyButton && !SetBitmapSize(ptb,dxButton, dyButton))) { //!!!! do we actually need to deal with this? DestroyWindow(hwndToolbar); hwndToolbar = NULL; goto Error; } AddBitmap(ptb, nBitmaps, hBMInst, wBMID); TBInsertButtons(ptb, (UINT)-1, iNumButtons, (LPTBBUTTON)lpButtons, TRUE); // ptb may be bogus now after above button insert } Error: return hwndToolbar; } /* This is no longer declared in COMMCTRL.H. It only exists for compatibility ** with existing apps; new apps must use CreateToolbarEx. */ HWND WINAPI CreateToolbar(HWND hwnd, DWORD ws, UINT wID, int nBitmaps, HINSTANCE hBMInst, UINT_PTR wBMID, LPCTBBUTTON lpButtons, int iNumButtons) { // old-style toolbar, so no divider. return CreateToolbarEx(hwnd, ws | CCS_NODIVIDER, wID, nBitmaps, hBMInst, wBMID, lpButtons, iNumButtons, 0, 0, 0, 0, sizeof(OLDTBBUTTON)); } #pragma code_seg(CODESEG_INIT) BOOL InitToolbarClass(HINSTANCE hInstance) { WNDCLASS wc; wc.lpfnWndProc = ToolbarWndProc; wc.lpszClassName = c_szToolbarClass; wc.style = CS_DBLCLKS | CS_GLOBALCLASS; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(PTBSTATE); wc.hInstance = hInstance; // use DLL instance if in DLL wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); wc.lpszMenuName = NULL; if (!RegisterClass(&wc) && !GetClassInfo(hInstance, c_szToolbarClass, &wc)) return FALSE; return TRUE; } #pragma code_seg() void PatB(HDC hdc,int x,int y,int dx,int dy, DWORD rgb) { RECT rc; SetBkColor(hdc,rgb); rc.left = x; rc.top = y; rc.right = x + dx; rc.bottom = y + dy; ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL); } // Parameter fHighlight determines whether to draw text highlighted, for // new TBSTATE_MARKED // void DrawString(HDC hdc, int x, int y, int dx, int dy, PTSTR pszString, BOOL fHighlight, TBDRAWITEM * ptbdraw) { int oldMode; COLORREF oldBkColor; COLORREF oldTextColor; RECT rcText; UINT uiStyle = 0; PTBSTATE ptb; LPTBBUTTONDATA ptbb; ASSERT(ptbdraw); ptb = ptbdraw->ptb; ptbb = ptbdraw->pbutton; if (!(ptb->ci.style & TBSTYLE_LIST) && ((ptb->iDyBitmap + ptb->cyPad + g_cyEdge) >= ptb->iButHeight)) // there's no room to show the text -- bail out return; if (BTN_NO_SHOW_TEXT(ptb, ptbb)) // don't show text for this button -- bail out return; if (fHighlight) { oldMode = SetBkMode (hdc, ptbdraw->tbcd.nHLStringBkMode); oldBkColor = SetBkColor (hdc, ptbdraw->tbcd.clrMark); oldTextColor = SetTextColor (hdc, ptbdraw->tbcd.clrTextHighlight); } else oldMode = SetBkMode(hdc, ptbdraw->tbcd.nStringBkMode); uiStyle = TBGetDrawTextFlags(ptb, DT_END_ELLIPSIS, ptbb); SetRect( &rcText, x, y, x + dx, y + dy); // The text rect (x,y,dx,dy) that was passed in covers a larger area than the text. dy is the height of the // entire button. If we're using the DT_SINGLELINE flag, that's fine, the text will be centered in that // height if necessary (e.g. if DT_VCENTER is set). Otherwise, the text could run over the max number of lines // (nTextRows), and off the button. So, if DT_SINGLELINE isn't set, we adjust the height of the text rect to // be exactly the height of the text. if ((uiStyle & DT_SINGLELINE) == 0) { rcText.bottom = y + ptb->dyIconFont; } HRESULT hr = E_FAIL; if (ptb->hTheme) { int iPartId; int iStateId; // Get the state back from what custom draw may have set TBGetPartAndState(ptb, ptbb, &iPartId, &iStateId); hr = DrawThemeText(ptb->hTheme, hdc, 0, iStateId, pszString, -1, uiStyle, 0, &rcText); } if (FAILED(hr)) DrawText(hdc, (LPTSTR)pszString, -1, &rcText, uiStyle); SetBkMode(hdc, oldMode); if (fHighlight) { SetBkColor (hdc, oldBkColor); SetTextColor (hdc, oldTextColor); } } LPTSTR TB_StrForButton(PTBSTATE ptb, LPTBBUTTONDATA pTBButton) { if (TBISSTRINGPTR(pTBButton->iString)) return (LPTSTR)pTBButton->iString; else { if (pTBButton->iString != -1 && pTBButton->iString < ptb->nStrings) return ptb->pStrings[pTBButton->iString]; return NULL; } } HIMAGELIST TBGetImageList(PTBSTATE ptb, int iMode, int iIndex) { HIMAGELIST himl = NULL; ASSERT(iMode <= HIML_MAX); if (iIndex >= 0 && iIndex < ptb->cPimgs) { himl = ptb->pimgs[iIndex].himl[iMode]; } return himl; } // // v5 toolbars support multiple imagelists. To use images from an alternate // imagelist, set the imagelist handle via TB_SETIMAGELIST(iIndex, himlAlt) // and set your button's iImage to MAKELONG(iImage, iIndex). // // APP COMPAT: GroupWise 5.5 passes garbage as the iIndex (even though it // was documented as "must be zero"), so we enable this functionality // only for v5 toolbars. IE4 ignored the iIndex, which is why they got // away with it up until now. // #define MAX_TBIMAGELISTS 20 // arbitrary limit HIMAGELIST TBSetImageList(PTBSTATE ptb, int iMode, int iIndex, HIMAGELIST himl) { HIMAGELIST himlOld = NULL; // Watch out for app compat or for totally bogus parameters if (iIndex < 0 || iIndex >= MAX_TBIMAGELISTS) iIndex = 0; ASSERT(iMode <= HIML_MAX); if (iIndex >= ptb->cPimgs) { // asking for more than we have, realloc. void *p = CCLocalReAlloc(ptb->pimgs, (iIndex+1) * SIZEOF(TBIMAGELISTS)); if (p) { ptb->pimgs = (TBIMAGELISTS*)p; ZeroMemory(&ptb->pimgs[ptb->cPimgs], (iIndex + 1 - ptb->cPimgs) * sizeof(TBIMAGELISTS)); ptb->cPimgs = iIndex + 1; // iIndex is 0 based, but cPimgs is 1 based (it's a count, not an index) } } if (iIndex < ptb->cPimgs) { himlOld = ptb->pimgs[iIndex].himl[iMode]; ptb->pimgs[iIndex].himl[iMode] = himl; } return himlOld; } // create a mono bitmap mask: // 1's where color == COLOR_BTNFACE || COLOR_3DHILIGHT // 0's everywhere else void CreateMask(PRECT prc, int xoffset, int yoffset, int dx, int dy, BOOL fDrawGlyph, TBDRAWITEM * ptbdraw) { LPTSTR psz; IMAGELISTDRAWPARAMS imldp; HIMAGELIST himl; PTBSTATE ptb = ptbdraw->ptb; LPTBBUTTONDATA pTBButton = ptbdraw->pbutton; // create mask based on color bitmap // convert this to 1's int xIcon, yIcon, xText, yText; if (ptb->ci.style & TBSTYLE_LIST) { if (BTN_NO_SHOW_TEXT(ptb, pTBButton)) { xIcon = (RECTWIDTH(*prc) - ptb->iDxBitmap) / 2; } else { xIcon = ptb->cxPad / 2; } yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap)/2; if (!(pTBButton->iBitmap == I_IMAGENONE && (pTBButton->fsStyle & BTNS_AUTOSIZE))) { xText = xIcon + ptb->iDxBitmap + ptb->iListGap; dx -= xIcon + ptb->iDxBitmap + ptb->iListGap; } else { xText = 0; } yText = 0; dy = RECTHEIGHT(*prc); } else { if (TB_HasTopDDArrow(ptb, pTBButton)) { xIcon = (RECTWIDTH(*prc) + CX_TOP_FUDGE - (ptb->iDxBitmap + ptb->dxDDArrowChar)) / 2; } else { xIcon = (RECTWIDTH(*prc) - ptb->iDxBitmap) / 2; } // No text to display? if (dx == 0) { yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap) / 2; } else { yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap - ptb->dyIconFont) / 2; } yText = yIcon + ptb->iDyBitmap + 1; xText = (RECTWIDTH(*prc) - dx) / 2; } TEXTMETRIC tm; GetTextMetrics(ptb->hdcMono, &tm); // initalize whole area with 1's // we adjust by tmMaxCharWidth because DrawText can draw outside the rectangle // by up to one character. PatBlt(ptb->hdcMono, 0, 0, xText+dx+tm.tmMaxCharWidth, yText+dy, WHITENESS); himl = TBGetImageList(ptb, HIML_NORMAL, ptbdraw->iIndex); if (fDrawGlyph && himl) { imldp.cbSize = sizeof(imldp); imldp.himl = himl; imldp.i = ptbdraw->iImage; imldp.hdcDst = ptb->hdcMono; imldp.x = xIcon; imldp.y = yIcon; imldp.cx = 0; imldp.cy = 0; imldp.xBitmap= 0; imldp.yBitmap= 0; imldp.rgbBk = g_clrBtnFace; imldp.rgbFg = CLR_DEFAULT; imldp.fStyle = ILD_ROP | ILD_MASK; imldp.dwRop = SRCCOPY; imldp.fState = 0; if (ToolBar_IsDPIScaled(ptb)) { imldp.fStyle |= ILD_DPISCALE; } ImageList_DrawIndirect(&imldp); imldp.fStyle = ILD_ROP | ILD_IMAGE; imldp.rgbBk = g_clrBtnHighlight; imldp.dwRop = SRCPAINT; ImageList_DrawIndirect(&imldp); } psz = TB_StrForButton(ptb, pTBButton); if (psz) { // The FALSE in 4th param is so we don't get a box in the mask. DrawString(ptb->hdcMono, xText, yText, dx, dy, psz, FALSE, ptbdraw); } } void DrawBlankButton(HDC hdc, int x, int y, int dx, int dy, TBDRAWITEM * ptbdraw) { RECT r1; UINT state; // face color // The Office toolbar sends us bitmaps that are smaller than they claim they are // So we need to do the PatB or the window background shows through around the // edges of the button bitmap -jjk ASSERT(ptbdraw); state = ptbdraw->state; if (!(state & TBSTATE_CHECKED)) PatB(hdc, x, y, dx, dy, ptbdraw->tbcd.clrBtnFace); if ( !(ptbdraw->dwCustom & TBCDRF_NOEDGES)) { r1.left = x; r1.top = y; r1.right = x + dx; r1.bottom = y + dy; if (ptbdraw->fHotTrack) DrawEdge(hdc, &r1, EDGE_SUNKEN, BF_RECT | BF_SOFT); else DrawEdge(hdc, &r1, (state & (TBSTATE_CHECKED | TBSTATE_PRESSED)) ? EDGE_SUNKEN : EDGE_RAISED, BF_RECT | BF_SOFT); } } // these are raster ops #define PSDPxax 0x00B8074A HWND g_hwndDebug = NULL; void DrawFace(HDC hdc, PRECT prc, int x, int y, int offx, int offy, int dxText, int dyText, TBDRAWITEM * ptbdraw, int iListGap, PRECT prcText) { IMAGELISTDRAWPARAMS imldp; BOOL fHotTrack = FALSE; UINT state; PTBSTATE ptb = ptbdraw->ptb; LPTBBUTTONDATA ptButton = ptbdraw->pbutton; BOOL fImage = TRUE; // !fImage means no image (as opposed to a blank image) LPTSTR psz = TB_StrForButton(ptb, ptButton); int xPressedOffset = 0; int yPressedOffset = 0; DWORD frame = 0; DWORD fState = 0; BOOL bCheckForDisabledDesat = FALSE; // AutosizeTextNoImage if ((ptbdraw->iImage == I_IMAGENONE) || ((ptbdraw->iImage == I_IMAGENONE) && (ptb->ci.style & TBSTYLE_LIST) && (ptButton->fsStyle & BTNS_AUTOSIZE))) { fImage = FALSE; } state = ptbdraw->state; if (state & TBSTATE_ENABLED) { fHotTrack = ptbdraw->fHotTrack; if (ptb->ci.style & TBSTYLE_FLAT && !ptb->hTheme) { UINT bdr = 0; if (state & (TBSTATE_PRESSED | TBSTATE_CHECKED)) bdr = BDR_SUNKENOUTER; else if (fHotTrack) bdr = BDR_RAISEDINNER; if (bdr) { RECT rc; TB_GetItemRect(ptb, (UINT)(ptButton - ptb->Buttons), &rc); if (TB_HasSplitDDArrow(ptb, ptButton)) rc.right -= ptb->dxDDArrowChar; if (!(ptbdraw->dwCustom & TBCDRF_NOEDGES) && ptb) CCDrawEdge(hdc, &rc, bdr, BF_RECT, &(ptb->clrsc)); } } } imldp.himl = NULL; if (fHotTrack || (state & TBSTATE_CHECKED)) { imldp.himl = TBGetImageList(ptb, HIML_HOT, ptbdraw->iIndex); if (imldp.himl == NULL) imldp.himl = TBGetImageList(ptb, HIML_NORMAL, ptbdraw->iIndex); } else if (DRAW_MONO_BTN(ptb, state)) { imldp.himl = TBGetImageList(ptb, HIML_DISABLED, ptbdraw->iIndex); if (imldp.himl == NULL) { // If there isn't a specific 'disabled' imagelist, we'll use the // regular one, in which case we want to desat any 32bit alpha image. bCheckForDisabledDesat = TRUE; } } if (imldp.himl == NULL) { imldp.himl = TBGetImageList(ptb, HIML_NORMAL, ptbdraw->iIndex); if (bCheckForDisabledDesat) { // If we have an alpha channel, then we'll desaturate. if (ImageList_GetItemFlags(imldp.himl, GET_IMAGE_INDEX(ptbdraw->iIndex)) == ILIF_ALPHA) { fState = ILS_SATURATE; frame = -100; } } } int xIcon = 0, yIcon = 0, xText, yText; if (ptb->ci.style & TBSTYLE_LIST) { if (BTN_NO_SHOW_TEXT(ptb, ptButton)) { xIcon = (RECTWIDTH(*prc) - ptb->iDxBitmap) / 2; } else if (fImage) { xIcon = ptb->cxPad / 2; } yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap)/2; xText = prc->left; yText = prc->top; if (fImage) { xText += ptb->iDxBitmap + iListGap + xIcon; dxText -= (ptb->iDxBitmap + iListGap); } dyText = RECTHEIGHT(*prc); } else { if (TB_HasTopDDArrow(ptb, ptButton)) { xIcon = (RECTWIDTH(*prc) + CX_TOP_FUDGE - (ptb->iDxBitmap + ptb->dxDDArrowChar)) / 2; } else { xIcon = (RECTWIDTH(*prc) - ptb->iDxBitmap) / 2; } // No text to display? if (psz && ((ptb->iDyBitmap + ptb->cyPad + g_cyEdge) < ptb->iButHeight)) { yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap - ptb->dyIconFont) / 2; } else { yIcon = (RECTHEIGHT(*prc) - ptb->iDyBitmap) / 2; } yText = prc->top + yIcon + ptb->iDyBitmap + 1; xText = prc->left + (RECTWIDTH(*prc) - dxText) / 2; } if ((state & (TBSTATE_PRESSED | TBSTATE_CHECKED)) && !(ptbdraw->dwCustom & TBCDRF_NOOFFSET)) { xPressedOffset++; if (ptb->ci.style & TBSTYLE_LIST) yPressedOffset++; } if (imldp.himl && (ptbdraw->iImage != -1) && fImage) { COLORREF rgbBk = ptbdraw->tbcd.clrBtnFace; if (ptb->ci.style & TBSTYLE_TRANSPARENT) rgbBk = CLR_NONE; if (ptb->dwStyleEx & TBSTYLE_EX_INVERTIBLEIMAGELIST) rgbBk = CLR_DEFAULT; imldp.cbSize = sizeof(imldp); imldp.i = ptbdraw->iImage; imldp.hdcDst = hdc; imldp.x = prc->left + xIcon + xPressedOffset; imldp.y = prc->top + yIcon + yPressedOffset; imldp.cx = 0; imldp.cy = 0; imldp.xBitmap= 0; imldp.yBitmap= 0; imldp.rgbBk = rgbBk; imldp.rgbFg = CLR_DEFAULT; imldp.fStyle = ILD_NORMAL; imldp.fState = fState; imldp.Frame = frame; if (state & (TBSTATE_CHECKED | TBSTATE_INDETERMINATE) || ptb->hTheme) imldp.fStyle = ILD_TRANSPARENT; if (ptbdraw->dwCustom & TBCDRF_BLENDICON) imldp.fStyle = ILD_TRANSPARENT | ILD_BLEND50; if (ToolBar_IsDPIScaled(ptb)) { imldp.fStyle |= ILD_DPISCALE; } ImageList_DrawIndirect(&imldp); } if (psz && !DRAW_MONO_BTN(ptb, state)) { BOOL bHighlight = (state & TBSTATE_MARKED) && (ptb->ci.style & TBSTYLE_LIST) && !(ptbdraw->dwCustom & TBCDRF_NOMARK); xText += xPressedOffset; yText += yPressedOffset; prcText->left = xText; prcText->top = yText; prcText->right = xText + dxText; prcText->bottom = yText + dyText; DrawString(hdc, xText, yText, dxText, dyText, psz, bHighlight, ptbdraw); } } void InitTBDrawItem(TBDRAWITEM * ptbdraw, PTBSTATE ptb, LPTBBUTTONDATA pbutton, UINT state, BOOL fHotTrack, int dxText, int dyText) { NMTBCUSTOMDRAW * ptbcd; NMCUSTOMDRAW * pnmcd; ASSERT(ptbdraw); ptbdraw->ptb = ptb; ptbdraw->pbutton = pbutton; ptbdraw->fHotTrack = fHotTrack; ptbdraw->iIndex = GET_HIML_INDEX(pbutton->iBitmap); ptbdraw->iImage = GET_IMAGE_INDEX(pbutton->iBitmap); ptbdraw->state = state; ptbcd = &ptbdraw->tbcd; ptbcd->hbrMonoDither = g_hbrMonoDither; ptbcd->hbrLines = (HBRUSH)GetStockObject(BLACK_BRUSH); ptbcd->hpenLines = (HPEN)GetStockObject(BLACK_PEN); ptbcd->clrMark = g_clrHighlight; ptbcd->clrBtnHighlight = g_clrBtnHighlight; ptbcd->clrTextHighlight = g_clrHighlightText; ptbcd->clrBtnFace = g_clrBtnFace; ptbcd->nStringBkMode = TRANSPARENT; ptbcd->nHLStringBkMode = OPAQUE; ptbcd->clrText = g_clrBtnText; SetRect(&ptbcd->rcText, 0, 0, dxText, dyText); ptbcd->iListGap = ptb->iListGap; pnmcd = &ptbcd->nmcd; pnmcd->uItemState = CDISFromState(state); if (fHotTrack) pnmcd->uItemState |= CDIS_HOT; } void DrawButton(HDC hdc, int x, int y, PTBSTATE ptb, LPTBBUTTONDATA ptButton, BOOL fActive) { int yOffset; HBRUSH hbrOld; UINT state; int dxFace, dyFace; int dxText, dyText; int xCenterOffset; int dx = TBWidthOfButton(ptb, ptButton, hdc); HFONT oldhFont; int dy = ptb->iButHeight; TBDRAWITEM tbdraw = { 0 }; NMTBCUSTOMDRAW * ptbcd = &tbdraw.tbcd; NMCUSTOMDRAW * pnmcd = &ptbcd->nmcd; COLORREF clrSave; BOOL fHotTrack; HFONT hFontNoAntiAlias = NULL; state = (UINT)ptButton->fsState; // make local copy of state and do proper overriding if (state & TBSTATE_INDETERMINATE) { if (state & TBSTATE_PRESSED) state &= ~TBSTATE_INDETERMINATE; else if (state & TBSTATE_ENABLED) state = TBSTATE_INDETERMINATE; else state &= ~TBSTATE_INDETERMINATE; } if (!fActive) { state &= ~TBSTATE_ENABLED; } fHotTrack = TBIsHotTrack(ptb, ptButton, state); pnmcd->hdc = hdc; pnmcd->dwItemSpec = ptButton->idCommand; pnmcd->uItemState = 0; pnmcd->lItemlParam = (LPARAM)ptButton->dwData; SetRect(&pnmcd->rc, x, y, x + dx, y + dy); dxText = dx - ptb->cxPad; if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) { dyText = dy; } else { dyText = dy - (2 * g_cyEdge); } InitTBDrawItem(&tbdraw, ptb, ptButton, state, fHotTrack, dxText, dyText); tbdraw.dwCustom = CICustomDrawNotify(&ptb->ci, CDDS_ITEMPREPAINT, &ptbcd->nmcd); // We gotta update our concept of hotness tbdraw.fHotTrack = fHotTrack = pnmcd->uItemState & CDIS_HOT; if (!(tbdraw.dwCustom & CDRF_SKIPDEFAULT )) { int iPartId; int iStateId; // Get the state back from what custom draw may have set state = tbdraw.state = StateFromCDIS(pnmcd->uItemState); TBGetPartAndState(ptb, ptButton, &iPartId, &iStateId); RECT rcContent = pnmcd->rc; if (ptb->hTheme) { GetThemeBackgroundContentRect(ptb->hTheme, hdc, iPartId, iStateId, &pnmcd->rc, &rcContent); dxFace = RECTWIDTH(rcContent); dyFace = RECTHEIGHT(rcContent); dxText = ptbcd->rcText.right - ptbcd->rcText.left; dyText = ptbcd->rcText.bottom - ptbcd->rcText.top; dxText -= RECTWIDTH(pnmcd->rc) - dxFace; // Text width has been calculated. Need to adjust based on content rect } else { dxFace = dx - (2 * g_cxEdge); dyFace = dy - (2 * g_cyEdge); dxText = ptbcd->rcText.right - ptbcd->rcText.left; dyText = ptbcd->rcText.bottom - ptbcd->rcText.top; } if (TB_HasDDArrow(ptb, ptButton) && !TB_HasTopDDArrow(ptb, ptButton)) { int iAdjust = TBDDArrowAdjustment(ptb, ptButton); if (!(ptb->dwStyleEx & TBSTYLE_EX_FIXEDDROPDOWN)) { dxFace -= iAdjust; } dxText -= iAdjust; rcContent.right -= iAdjust; } // Should we display the font using the GDI AntiAliasing? if (!ptb->fAntiAlias && !(tbdraw.dwCustom & CDRF_NEWFONT)) { // No. Must be doing drag and drop. We don't want to AntiAlias because the // Purple color key will show through and it looks ugly. LOGFONT lfFont; if (GetObject(ptb->hfontIcon, sizeof(lfFont), &lfFont)) { lfFont.lfQuality = NONANTIALIASED_QUALITY; hFontNoAntiAlias = CreateFontIndirect(&lfFont); } } if (!(tbdraw.dwCustom & CDRF_NEWFONT)) { if (hFontNoAntiAlias) oldhFont = (HFONT)SelectObject(hdc, hFontNoAntiAlias); else oldhFont = (HFONT)SelectObject(hdc, ptb->hfontIcon); } clrSave = SetTextColor(hdc, ptbcd->clrText); if (ptb->hTheme) { if (!(tbdraw.dwCustom & TBCDRF_NOBACKGROUND)) { if (TB_HasDDArrow(ptb, ptButton)) { RECT rcNoArrow = pnmcd->rc; if (!TB_HasUnsplitDDArrow(ptb, ptButton)) // unless it's split { rcNoArrow.right -= ptb->dxDDArrowChar; } DrawThemeBackground(ptb->hTheme, hdc, iPartId, iStateId, &rcNoArrow, 0); } else { DrawThemeBackground(ptb->hTheme, hdc, iPartId, iStateId, &pnmcd->rc, 0); } } x = rcContent.left; y = rcContent.top; } else { if (!(tbdraw.dwCustom & TBCDRF_NOBACKGROUND)) { if (!(ptb->ci.style & TBSTYLE_FLAT)) DrawBlankButton(hdc, x, y, dx, dy, &tbdraw); } // move coordinates inside border and away from upper left highlight. // the extents change accordingly. x += g_cxEdge; y += g_cyEdge; } yOffset = (RECTHEIGHT(pnmcd->rc) - RECTHEIGHT(rcContent)) / 2; if (yOffset < 0) yOffset = 0; if ((ptb->ci.style & TBSTYLE_LIST) && !BTN_NO_SHOW_TEXT(ptb, ptButton)) { xCenterOffset = ptb->cxPad / 2; } else if (TB_HasTopDDArrow(ptb, ptButton)) { // // Layout of "top dropdown" buttons looks like this: // // icon // fudge | dropdown arrow // | | | // v v v // +-+-+-------+--+-+ // | | | | | | // | | | | | | // +-+-+-------+--+-+ // | | // +----------------+ // // |<--- dxFace --->| // // xCenterOffset is the offset at which to start drawing the icon. // xCenterOffset = (dxFace + CX_TOP_FUDGE - (ptb->iDxBitmap + ptb->dxDDArrowChar)) / 2; } else { xCenterOffset = (dxFace - ptb->iDxBitmap) / 2; } if (state & (TBSTATE_PRESSED | TBSTATE_CHECKED) && !(tbdraw.dwCustom & TBCDRF_NOOFFSET)) { // pressed state moves down and to the right xCenterOffset++; yOffset++; } if (!ptb->hTheme) { // draw the dithered background if (!fHotTrack && (((state & (TBSTATE_CHECKED | TBSTATE_INDETERMINATE)) || ((state & TBSTATE_MARKED) && !(ptb->ci.style & TBSTYLE_FLAT) && !(tbdraw.dwCustom & TBCDRF_NOMARK))))) { //Custom Draw can set hbrMonoDither to be NULL. Validate it before using it hbrOld = ptbcd->hbrMonoDither ? (HBRUSH)SelectObject(hdc, ptbcd->hbrMonoDither) : NULL; if (hbrOld) { COLORREF clrText, clrBack; clrText = SetTextColor(hdc, ptbcd->clrBtnHighlight); // 0 -> 0 clrBack = SetBkColor(hdc, ptbcd->clrBtnFace); // 1 -> 1 // only draw the dither brush where the mask is 1's if (!(tbdraw.dwCustom & TBCDRF_NOBACKGROUND)) { PatBlt(hdc, x, y, dxFace, dyFace, PATCOPY); } SelectObject(hdc, hbrOld); SetTextColor(hdc, clrText); SetBkColor(hdc, clrBack); } } } // Paint the background of the hot-tracked item if the // custom draw said so if ((tbdraw.dwCustom & TBCDRF_HILITEHOTTRACK) && fHotTrack && !(tbdraw.dwCustom & TBCDRF_NOBACKGROUND)) { PatB(hdc, pnmcd->rc.left, pnmcd->rc.top, pnmcd->rc.right - pnmcd->rc.left, pnmcd->rc.bottom - pnmcd->rc.top, ptbcd->clrHighlightHotTrack); } tbdraw.iImage = ptButton->iBitmap; if((ptButton->iBitmap == I_IMAGECALLBACK) && ptb->fHimlNative) { NMTBDISPINFO tbgdi = {0}; tbgdi.dwMask = TBNF_IMAGE; TBGetItem(ptb,ptButton,&tbgdi); tbdraw.iImage = tbgdi.iImage; } tbdraw.iIndex = GET_HIML_INDEX(tbdraw.iImage); tbdraw.iImage = GET_IMAGE_INDEX(tbdraw.iImage); // Now put on the face. if (!DRAW_MONO_BTN(ptb, state) || TBGetImageList(ptb, HIML_DISABLED, tbdraw.iIndex) || (ImageList_GetItemFlags(TBGetImageList(ptb, HIML_NORMAL, tbdraw.iIndex), tbdraw.iImage) == ILIF_ALPHA)) { // regular version int yStart = y; if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) yStart -= g_cyEdge; DrawFace(hdc, &rcContent, x, yStart, xCenterOffset, yOffset, dxText, dyText, &tbdraw, ptbcd->iListGap, &ptbcd->rcText); } if (DRAW_MONO_BTN(ptb, state)) { HBITMAP hbmOld; //initialize the monochrome dc if (!ptb->hdcMono) { ptb->hdcMono = CreateCompatibleDC(hdc); if (!ptb->hdcMono) return; SetTextColor(ptb->hdcMono, 0L); SelectObject(ptb->hdcMono, ptb->hfontIcon); } hbmOld = (HBITMAP)SelectObject(ptb->hdcMono, ptb->hbmMono); // // If we a mirrored DC, mirror the Memory DC so that // text written on the bitmap won't get flipped. // if ((IS_DC_RTL_MIRRORED(hdc)) && (!(IS_DC_RTL_MIRRORED(ptb->hdcMono)))) { SET_DC_RTL_MIRRORED(ptb->hdcMono); } BOOL fDrawMono = TRUE; // If we have a disabled image, or an alpha image, then we don't draw mono if (TBGetImageList(ptb, HIML_DISABLED, tbdraw.iIndex) != NULL || (ImageList_GetItemFlags(TBGetImageList(ptb, HIML_NORMAL, tbdraw.iIndex), tbdraw.iImage) == ILIF_ALPHA)) { fDrawMono = FALSE; } // disabled version (or indeterminate) CreateMask(&rcContent, xCenterOffset, yOffset, dxFace, dyFace, fDrawMono, &tbdraw); SetTextColor(hdc, 0L); // 0's in mono -> 0 (for ROP) SetBkColor(hdc, 0x00FFFFFF); // 1's in mono -> 1 // draw glyph's etched-effect if (!(state & TBSTATE_INDETERMINATE) && !(tbdraw.dwCustom & TBCDRF_NOETCHEDEFFECT)) { hbrOld = (HBRUSH)SelectObject(hdc, g_hbrBtnHighlight); if (hbrOld) { // draw hilight color where we have 0's in the mask BitBlt(hdc, rcContent.left + 1, rcContent.top + 1, dxFace, dyFace, ptb->hdcMono, 0, 0, PSDPxax); SelectObject(hdc, hbrOld); } } // gray out glyph hbrOld = (HBRUSH)SelectObject(hdc, g_hbrBtnShadow); if (hbrOld) { // draw the shadow color where we have 0's in the mask BitBlt(hdc, rcContent.left, rcContent.top, dxFace, dyFace, ptb->hdcMono, 0, 0, PSDPxax); SelectObject(hdc, hbrOld); } if (state & TBSTATE_CHECKED) { BitBlt(ptb->hdcMono, 1, 1, dxFace - 1, dyFace - 1, ptb->hdcMono, 0, 0, SRCAND); } SelectObject(ptb->hdcMono, hbmOld); } if (TB_HasDDArrow(ptb, ptButton)) { WORD wDSAFlags = DCHF_TRANSPARENT | DCHF_FLIPPED; BOOL fPressedDD = ((ptb->Buttons + ptb->iPressedDD) == ptButton); RECT rc; if (TB_HasTopDDArrow(ptb, ptButton)) { // position the dd arrow up next to the bitmap rc.left = x + xCenterOffset + ptb->iDxBitmap; rc.right = rc.left + ptb->dxDDArrowChar; rc.top = y + yOffset; rc.bottom = rc.top + ptb->iDyBitmap; } else { // position the dd arrow to the right of the text & bitmap TB_GetItemRect(ptb, (UINT)(ptButton - ptb->Buttons), &rc); rc.left = rc.right - ptb->dxDDArrowChar; } if (TB_HasUnsplitDDArrow(ptb, ptButton)) { // if a non-split dd arrow, don't draw a border. wDSAFlags |= DCHF_NOBORDER; } if (DRAW_MONO_BTN(ptb, state)) { // DFCS_INACTIVE means "draw the arrow part grayed" wDSAFlags |= DCHF_INACTIVE; } // if TB_HasTopDDArrow, we've already offset rect, so don't draw DCHF_PUSHED else if ((fPressedDD || (state & (TBSTATE_CHECKED | TBSTATE_PRESSED))) && !TB_HasTopDDArrow(ptb, ptButton)) { // DCHF_PUSHED means "offset the arrow and draw indented border" wDSAFlags |= DCHF_PUSHED; } else if (fHotTrack || !(ptb->ci.style & TBSTYLE_FLAT)) { // DCHF_HOT means "draw raised border" // non-flat dropdown arrows are either pushed or hot wDSAFlags |= DCHF_HOT; } if (ptb->hTheme && !TB_HasUnsplitDDArrow(ptb, ptButton)) { int iState = TS_NORMAL; wDSAFlags |= DCHF_NOBORDER; if (wDSAFlags & DCHF_PUSHED) iState = TS_PRESSED; else if (wDSAFlags & DCHF_INACTIVE) iState = TS_DISABLED; else if (wDSAFlags & DCHF_HOT) iState = TS_HOT; DrawThemeBackground(ptb->hTheme, hdc, TP_SPLITBUTTONDROPDOWN, iState, &rc, 0); } else { COLORREF crText; if (ptb->hTheme) { GetThemeColor(ptb->hTheme, 0, 0, TMT_TEXTCOLOR, &crText); } DrawScrollArrow(hdc, &rc, wDSAFlags, ptb->hTheme ? crText : CLR_INVALID); } } if (!(tbdraw.dwCustom & CDRF_NEWFONT)) { SelectObject(hdc, oldhFont); } SetTextColor(hdc, clrSave); if (hFontNoAntiAlias) { DeleteObject(hFontNoAntiAlias); } } if (tbdraw.dwCustom & CDRF_NOTIFYPOSTPAINT) { if (ptb->hTheme) { int iPartId; int iStateId; // Get the state back from what custom draw may have set TBGetPartAndState(ptb, ptButton, &iPartId, &iStateId); RECT rcTemp; GetThemeBackgroundContentRect(ptb->hTheme, hdc, iPartId, iStateId, &pnmcd->rc, &rcTemp); pnmcd->rc = rcTemp; } CICustomDrawNotify(&ptb->ci, CDDS_ITEMPOSTPAINT, &ptbcd->nmcd); } } // make sure that g_hbmMono is big enough to do masks for this // size of button. if not, fail. BOOL CheckMonoMask(PTBSTATE ptb, int width, int height) { BITMAP bm; HBITMAP hbmTemp; if (ptb->hbmMono) { GetObject(ptb->hbmMono, sizeof(BITMAP), &bm); if (width <= bm.bmWidth && height <= bm.bmHeight) { return TRUE; } } // Add a bit of fudge to keep this from being reallocated too often. hbmTemp = CreateMonoBitmap(width+8, height+8); if (!hbmTemp) return FALSE; if (ptb->hbmMono) DeleteObject(ptb->hbmMono); ptb->hbmMono = hbmTemp; return TRUE; } /* ** GrowToolbar ** ** Attempt to grow the button size. ** ** The calling function can either specify a new internal measurement ** (GT_INSIDE) or a new external measurement. ** ** GT_MASKONLY updates the mono mask and nothing else. */ BOOL GrowToolbar(PTBSTATE ptb, int newButWidth, int newButHeight, UINT flags) { BOOL fGetNewSize = (!newButWidth) || (!newButHeight); if (!newButWidth) newButWidth = DEFAULTBUTTONX; if (!newButHeight) newButHeight = DEFAULTBUTTONY; // if growing based on inside measurement, get full size if (flags & GT_INSIDE) { if (ptb->ci.style & TBSTYLE_LIST) newButWidth += ptb->iDxBitmap + ptb->iListGap; newButHeight += ptb->cyPad; newButWidth += ptb->cxPad; // if toolbar already has strings, don't shrink width it because it // might clip room for the string if ((newButWidth < ptb->iButWidth) && ptb->nStrings && ptb->nTextRows > 0) newButWidth = ptb->iButWidth; } else { if (newButHeight == -1) newButHeight = ptb->iButHeight; if (newButWidth == -1) newButWidth = ptb->iButWidth; int dyInner = ptb->iDyBitmap; HFONT hfontIcon = NULL; BOOL fDeleteFont = FALSE; if (ptb->hTheme) { LOGFONT lf; if (SUCCEEDED(GetThemeFont(ptb->hTheme, NULL, 0, 0, TMT_FONT, &lf))) { hfontIcon = CreateFontIndirect(&lf); fDeleteFont = TRUE; } } else { hfontIcon = ptb->hfontIcon; } if (hfontIcon) { HDC hdc = GetDC(ptb->ci.hwnd); if (hdc) { HFONT hfontOld = (HFONT)SelectObject(hdc, hfontIcon); TEXTMETRIC tm; GetTextMetrics(hdc, &tm); dyInner = max(dyInner, tm.tmHeight); SelectObject(hdc, hfontOld); ReleaseDC(ptb->ci.hwnd, hdc); } if (fDeleteFont) { DeleteObject(hfontIcon); } } if (newButHeight < dyInner + ptb->cyPad) newButHeight = dyInner + ptb->cyPad; if (newButWidth < ptb->iDxBitmap + ptb->cxPad) newButWidth = ptb->iDxBitmap + ptb->cxPad; } // if the size of the toolbar is actually growing, see if shadow // bitmaps can be made sufficiently large. if (!ptb->hbmMono || (newButWidth > ptb->iButWidth) || (newButHeight > ptb->iButHeight)) { if (!CheckMonoMask(ptb, newButWidth, newButHeight)) return(FALSE); } if (flags & GT_MASKONLY) return(TRUE); if (!(flags & GT_INSIDE) && ((ptb->iButWidth != newButWidth) || (ptb->iButHeight != newButHeight))) InvalidateRect(ptb->ci.hwnd, NULL, TRUE); if (ptb->hTheme && (fGetNewSize || (ptb->iButWidth != newButWidth || ptb->iButHeight != newButHeight))) { int cx = newButWidth; int cy = newButHeight; RECT rc = {0, 0, newButWidth, newButHeight}; int iPartId = TP_BUTTON; int iStateId = TS_NORMAL; // start with -1 so we can get sensible defaults in the case of no buttons for (int iButton = -1; iButton < ptb->iNumButtons; iButton++) { RECT rcOut; if (iButton != -1) TBGetPartAndState(ptb, &ptb->Buttons[iButton], &iPartId, &iStateId); GetThemeBackgroundExtent(ptb->hTheme, NULL, iPartId, iStateId, &rc, &rcOut); cx = max(cx, RECTWIDTH(rcOut)); cy = max(cy, RECTHEIGHT(rcOut)); } ptb->iButWidth = cx; ptb->iButHeight = cy; } else { ptb->iButWidth = newButWidth; ptb->iButHeight = newButHeight; } // bar height has 2 pixels above, 2 below ptb->iYPos = ptb->cyBarPad; TBInvalidateItemRects(ptb); return TRUE; } BOOL SetBitmapSize(PTBSTATE ptb, int width, int height) { int realh; if (!width) width = 1; if (!height) height = 1; if (width == -1) width = ptb->iDxBitmap; if (height == -1) height = ptb->iDyBitmap; realh = height; if ((ptb->iDxBitmap == width) && (ptb->iDyBitmap == height)) return TRUE; if (TBHasStrings(ptb)) realh = HeightWithString(ptb, height); if (GrowToolbar(ptb, width, realh, GT_INSIDE)) { ptb->iDxBitmap = width; ptb->iDyBitmap = height; // the size changed, we need to rebuild the imagelist InvalidateRect(ptb->ci.hwnd, NULL, TRUE); TBInvalidateImageList(ptb); return TRUE; } return FALSE; } void TB_OnSysColorChange(PTBSTATE ptb) { int i; InitGlobalColors(); // Reset all of the bitmaps for (i = 0; i < ptb->cPimgs; i++) { HIMAGELIST himl = TBGetImageList(ptb, HIML_NORMAL, i); if (himl) ImageList_SetBkColor(himl, (ptb->ci.style & TBSTYLE_TRANSPARENT) ? CLR_NONE : g_clrBtnFace); himl = TBGetImageList(ptb, HIML_HOT, i); if (himl) ImageList_SetBkColor(himl, (ptb->ci.style & TBSTYLE_TRANSPARENT) ? CLR_NONE : g_clrBtnFace); } } #define CACHE 0x01 #define BUILD 0x02 void ReleaseMonoDC(PTBSTATE ptb) { if (ptb->hdcMono) { SelectObject(ptb->hdcMono, g_hfontSystem); DeleteDC(ptb->hdcMono); ptb->hdcMono = NULL; } } void TB_DrawBackground(PTBSTATE ptb, HDC hdc, NMTBCUSTOMDRAW *ptbcd, RECT* prcClip) { if (ptb->ci.style & TBSTYLE_CUSTOMERASE) { ptb->ci.dwCustom = CICustomDrawNotify(&ptb->ci, CDDS_PREERASE, &ptbcd->nmcd); } else { ptb->ci.dwCustom = CDRF_DODEFAULT; } if (!(ptb->ci.dwCustom & CDRF_SKIPDEFAULT)) { BOOL fPaintBackground = TRUE; if (ptb->ci.style & TBSTYLE_TRANSPARENT) { // Explicitly check here. Double buffer passed in means "Efficent flicker free painting". // Only callers that know about this flag know to handle WM_PRINTCLIENT correctly, // so they get the efficient rendering. if (ptb->dwStyleEx & TBSTYLE_EX_DOUBLEBUFFER) { // V6 Behaviour Change: Toolbar doesn't use Erase background for transparency any more. // Erase is much less efficent than WM_PRINT. if (!ptb->hTheme || CCShouldAskForBits(&ptb->ci, ptb->hTheme, TP_BUTTON, 1)) // Cheat: We assume transparency for all if the button is { if (CCSendPrintRect(&ptb->ci, hdc, prcClip)) fPaintBackground = FALSE; } } else { if (CCForwardEraseBackground(ptb->ci.hwnd, hdc)) fPaintBackground = FALSE; } } if (fPaintBackground) { if (ptb->hTheme) { RECT rc; GetWindowRect(ptb->ci.hwnd, &rc); OffsetRect(&rc, -rc.left, -rc.top); DebugPaintRect(hdc, &rc); DrawThemeBackground(ptb->hTheme, hdc, 0, 0, &rc, NULL /*prcClip*/); } else { DefWindowProc(ptb->ci.hwnd, WM_ERASEBKGND, (WPARAM) hdc, 0); } } } if (ptb->ci.dwCustom & CDRF_NOTIFYPOSTERASE) CICustomDrawNotify(&ptb->ci, CDDS_POSTERASE, &ptbcd->nmcd); } void TB_OnEraseBkgnd(PTBSTATE ptb, HDC hdc) { if (!TB_IsDoubleBuffer(ptb)) { NMTBCUSTOMDRAW tbcd = { 0 }; tbcd.nmcd.hdc = hdc; TB_DrawBackground(ptb, hdc, &tbcd, NULL); } } BOOL TBIsRectClipped(PTBSTATE ptb, LPRECT prc) { RECT rc; RECT rcTB; if (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN) CopyRect(&rcTB, &ptb->rc); else GetClientRect(ptb->ci.hwnd, &rcTB); if (IntersectRect(&rc, &rcTB, prc)) { if (EqualRect(prc, &rc)) return FALSE; } return TRUE; } BOOL TBShouldDrawButton(PTBSTATE ptb, LPRECT prcBtn, HDC hdc) { // don't bother drawing buttons that aren't in the dc clipping region if (RectVisible(hdc, prcBtn)) { if (ptb->dwStyleEx & TBSTYLE_EX_HIDECLIPPEDBUTTONS) return !TBIsRectClipped(ptb, prcBtn); else return TRUE; } return FALSE; } // goin horizontal . . . void DrawToolbarH(PTBSTATE ptb, HDC hdc, LPRECT prc) { int iButton, xButton, yButton, cxBar; LPTBBUTTONDATA pAllButtons = ptb->Buttons; cxBar = prc->right - prc->left; yButton = ptb->iYPos; prc->top = ptb->iYPos; prc->bottom = ptb->iYPos + ptb->iButHeight; // Bug#16338 (scotth): what if first btn is a separator? for (iButton = 0, xButton = ptb->xFirstButton; iButton < ptb->iNumButtons; iButton++) { LPTBBUTTONDATA pButton = &pAllButtons[iButton]; if (!(pButton->fsState & TBSTATE_HIDDEN)) { int cxButton = TBWidthOfButton(ptb, pButton, hdc); // Is there anything to draw? if (!(pButton->fsStyle & BTNS_SEP) || (ptb->ci.style & TBSTYLE_FLAT)) { // Yes prc->left = xButton; prc->right = xButton + cxButton; if (TBShouldDrawButton(ptb, prc, hdc)) { // Draw separator? if (pButton->fsStyle & BTNS_SEP) { // Yes; must be a flat separator. Is this toolbar vertical? if (ptb->ci.style & CCS_VERT) { // Yes; draw a horizontal separator. Center w/in the // button rect if (ptb->hTheme) DrawThemeBackground(ptb->hTheme, hdc, TP_SEPARATORVERT, 0, prc, 0); else { int iSave = prc->top; prc->top += (TBGetSepHeight(ptb, pButton) - 1) / 2; InflateRect(prc, -g_cxEdge, 0); CCDrawEdge(hdc, prc, EDGE_ETCHED, BF_TOP, &(ptb->clrsc)); InflateRect(prc, g_cxEdge, 0); prc->top = iSave; } } else { // No; draw a vertical separator if (ptb->hTheme) DrawThemeBackground(ptb->hTheme, hdc, TP_SEPARATOR, 0, prc, 0); else { prc->left += (cxButton - 1) / 2; InflateRect(prc, 0, -g_cyEdge); CCDrawEdge(hdc, prc, EDGE_ETCHED, BF_LEFT, &(ptb->clrsc)); InflateRect(prc, 0, g_cyEdge); } } } else { // No DrawButton(hdc, xButton, yButton, ptb, pButton, ptb->fActive); } } } xButton += (cxButton + ptb->cxButtonSpacing); if (pButton->fsState & TBSTATE_WRAP) { int dy; if (pButton->fsStyle & BTNS_SEP) { if (ptb->ci.style & CCS_VERT) dy = TBGetSepHeight(ptb, pButton); else { if (ptb->ci.style & TBSTYLE_FLAT) { // Draw a separator across the entire toolbar to separate rows. // For horizontal toolbars only. RECT rcMid; rcMid.top = prc->top + ptb->iButHeight + ((TBGetSepHeight(ptb, pButton) - 1) / 2); rcMid.bottom = rcMid.top + g_cxEdge; rcMid.left = g_cxEdge; rcMid.right = cxBar - g_cxEdge; CCDrawEdge(hdc, &rcMid, EDGE_ETCHED, BF_TOP, &(ptb->clrsc)); } dy = ptb->iButHeight + TBGetSepHeight(ptb, pButton); } } else dy = ptb->iButHeight; xButton = ptb->xFirstButton; yButton += dy + ptb->cyButtonSpacing; prc->top += dy + ptb->cyButtonSpacing; prc->bottom += dy + ptb->cyButtonSpacing; } } } } // goin vertical . . . void DrawToolbarV(PTBSTATE ptb, HDC hdc, LPRECT prc) { int iButton, xButton, yButton, cyBar; LPTBBUTTONDATA pAllButtons = ptb->Buttons; NMTBCUSTOMDRAW tbcd = { 0 }; LPTBBUTTONDATA pButton = pAllButtons; cyBar = prc->bottom - prc->top; xButton = ptb->xFirstButton; prc->left = xButton; prc->right = prc->left + ptb->iButWidth; for (iButton = 0, yButton = 0; iButton < ptb->iNumButtons; iButton++, pButton++) { if (!(pButton->fsState & TBSTATE_HIDDEN)) { // Is there anything to draw? if (!(pButton->fsStyle & BTNS_SEP) || (ptb->ci.style & TBSTYLE_FLAT)) { int cyButton; if (pButton->fsStyle & BTNS_SEP) cyButton = TBGetSepHeight(ptb, pButton); else cyButton = ptb->iButHeight; prc->top = yButton; prc->bottom = yButton + cyButton; if (TBShouldDrawButton(ptb, prc, hdc)) { // Draw separator? if (pButton->fsStyle & BTNS_SEP) { DWORD dwCustRet; NMTBCUSTOMDRAW tbcd = { 0 }; tbcd.nmcd.hdc = hdc; tbcd.nmcd.dwItemSpec = -1; CopyRect(&tbcd.nmcd.rc, prc); dwCustRet = CICustomDrawNotify(&ptb->ci, CDDS_ITEMPREPAINT, &tbcd.nmcd); if ( !(CDRF_SKIPDEFAULT & dwCustRet) ) { if (ptb->hTheme) DrawThemeBackground(ptb->hTheme, hdc, TP_SEPARATORVERT, 0, prc, 0); else { // Yes; must be a flat separator. InflateRect(prc, -g_cxEdge, 0); CCDrawEdge(hdc, prc, EDGE_ETCHED, BF_TOP, &(ptb->clrsc)); InflateRect(prc, g_cxEdge, 0); } } } else { // No DrawButton(hdc, xButton, yButton, ptb, pButton, ptb->fActive); } } yButton += cyButton; } if (pButton->fsState & TBSTATE_WRAP) { int dx; if (ptb->ci.style & TBSTYLE_FLAT) { // Draw a separator vertival across the entire toolbar to separate cols. // For vertical toolbars only. RECT rcMid; rcMid.top = ptb->rc.top + g_cxEdge; rcMid.bottom = ptb->rc.bottom - g_cxEdge; rcMid.left = xButton + ptb->iButWidth; rcMid.right = rcMid.left + g_cxEdge; CCDrawEdge(hdc, &rcMid, EDGE_ETCHED, BF_LEFT, &(ptb->clrsc)); } dx = ptb->iButWidth + g_cxEdge; yButton = 0; xButton += dx; prc->left += dx; prc->right += dx; } } } } COLORREF TB_GetInsertMarkColor(PTBSTATE ptb) { if (ptb->clrim == CLR_DEFAULT) return g_clrBtnText; else return ptb->clrim; } void TBPaint(PTBSTATE ptb, HDC hdcIn) { RECT rc; HDC hdc; PAINTSTRUCT ps; NMTBCUSTOMDRAW tbcd = { 0 }; CCDBUFFER db = {0}; GetClientRect(ptb->ci.hwnd, &rc); if (hdcIn) { hdc = hdcIn; GetClipBox(hdc, &ps.rcPaint); } else hdc = BeginPaint(ptb->ci.hwnd, &ps); if (!rc.right) goto Error1; // Create memory surface and map rendering context if double buffering if (TB_IsDoubleBuffer(ptb)) { hdc = CCBeginDoubleBuffer(hdc, &ps.rcPaint, &db); } if (!hdc) return; tbcd.nmcd.hdc = hdc; tbcd.nmcd.rc = rc; // Draw background in this pass if double buffering, otherwise, it was handled in WM_ERASEBKGND if (TB_IsDoubleBuffer(ptb)) { TB_DrawBackground(ptb, hdc, &tbcd, &ps.rcPaint); } //Draw foreground ptb->ci.dwCustom = CICustomDrawNotify(&ptb->ci, CDDS_PREPAINT, &tbcd.nmcd); if (!(ptb->ci.dwCustom & CDRF_SKIPDEFAULT)) { if (!ptb->fHimlValid) TBBuildImageList(ptb); if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) DrawToolbarV(ptb, hdc, &rc); else DrawToolbarH(ptb, hdc, &rc); if (ptb->iInsert!=-1) { BOOL fHorizMode = !(ptb->ci.style & CCS_VERT); RECT rc; if (GetInsertMarkRect(ptb, &rc, fHorizMode)) { CCDrawInsertMark(hdc, &rc, fHorizMode, TB_GetInsertMarkColor(ptb)); } } ReleaseMonoDC(ptb); } if (ptb->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) { tbcd.nmcd.hdc = hdc; tbcd.nmcd.uItemState = 0; tbcd.nmcd.lItemlParam = 0; CICustomDrawNotify(&ptb->ci, CDDS_POSTPAINT, &tbcd.nmcd); } CCEndDoubleBuffer(&db); Error1: if (hdcIn == NULL) EndPaint(ptb->ci.hwnd, &ps); } void TB_GetItemDropDownRect(PTBSTATE ptb, UINT uButton, LPRECT lpRect) { TB_GetItemRect(ptb,uButton,lpRect); lpRect->left = lpRect->right - ptb->dxDDArrowChar; } int TBHeightOfButton(PTBSTATE ptb, LPTBBUTTONDATA ptbb) { int dy; if ((ptbb->fsStyle & BTNS_SEP) && (ptbb->fsState & TBSTATE_WRAP || ptb->dwStyleEx & TBSTYLE_EX_VERTICAL)) { if (!(ptb->ci.style & CCS_VERT) && !(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL)) { dy = TBGetSepHeight(ptb, ptbb) + ptb->iButHeight; } else { dy = TBGetSepHeight(ptb, ptbb); } } else { dy = ptb->iButHeight; } return dy; } void TB_CalcItemRects(PTBSTATE ptb) { int iButton, xPos, yPos; ASSERT(!ptb->fItemRectsValid); xPos = ptb->xFirstButton; yPos = ptb->iYPos; for (iButton = 0; iButton < ptb->iNumButtons; iButton++) { int xPosButton; LPTBBUTTONDATA pButton = &ptb->Buttons[iButton]; if (!(pButton->fsState & TBSTATE_HIDDEN)) { if ((pButton->fsState & TBSTATE_WRAP) && (pButton->fsStyle & BTNS_SEP)) xPosButton = ptb->xFirstButton; else xPosButton = xPos; pButton->pt.x = xPosButton; pButton->pt.y = yPos; if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) { if (pButton->fsState & TBSTATE_WRAP) { xPos += (ptb->iButWidth + g_cxEdge); // to not overwrite the edge. yPos = 0; } else if (pButton->fsStyle & BTNS_SEP) yPos += (TBGetSepHeight(ptb, pButton)); else yPos += ptb->iButHeight + ptb->cyButtonSpacing; } else // standard horizontal toolbar. { xPos += TBWidthOfButton(ptb, pButton, NULL) + ptb->cxButtonSpacing; if (pButton->fsState & TBSTATE_WRAP) { yPos += ptb->iButHeight + ptb->cyButtonSpacing; if (pButton->fsStyle & BTNS_SEP) { if (ptb->ci.style & CCS_VERT) { yPos -= ptb->iButHeight + ptb->cyButtonSpacing; } yPos += (TBGetSepHeight(ptb, pButton)); } xPos = ptb->xFirstButton; } } } } } BOOL TB_GetItemRect(PTBSTATE ptb, UINT uButton, LPRECT lpRect) { int dy = ptb->iButHeight; if (uButton >= (UINT)ptb->iNumButtons || (ptb->Buttons[uButton].fsState & TBSTATE_HIDDEN)) { return FALSE; } if (!ptb->fItemRectsValid) { TB_CalcItemRects(ptb); ptb->fItemRectsValid = TRUE; } lpRect->left = ptb->Buttons[uButton].pt.x; lpRect->right = lpRect->left + TBWidthOfButton(ptb, &ptb->Buttons[uButton], NULL); lpRect->top = ptb->Buttons[uButton].pt.y; lpRect->bottom = lpRect->top + TBHeightOfButton(ptb, &ptb->Buttons[uButton]); return TRUE; } void InvalidateButton(PTBSTATE ptb, LPTBBUTTONDATA pButtonToPaint, BOOL fErase) { RECT rc; if (TB_GetItemRect(ptb, (UINT) (pButtonToPaint - ptb->Buttons), &rc)) { InvalidateRect(ptb->ci.hwnd, &rc, fErase); } } /*---------------------------------------------------------- Purpose: Toggles the button as a dropdown Returns: TRUE if handled */ BOOL TBToggleDropDown(PTBSTATE ptb, int iPos, BOOL fEatMsg) { BOOL bRet = FALSE; LPTBBUTTONDATA ptbButton = &ptb->Buttons[iPos]; ASSERT(TB_IsDropDown(ptbButton)); if (ptbButton->fsState & TBSTATE_ENABLED) { UINT nVal; HWND hwnd = ptb->ci.hwnd; ptb->iPressedDD = iPos; if (TB_HasUnsplitDDArrow(ptb, ptbButton)) ptbButton->fsState |= TBSTATE_PRESSED; InvalidateButton(ptb, ptbButton, TRUE); UpdateWindow(hwnd); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, iPos+1); nVal = (UINT) SendItemNotify(ptb, ptbButton->idCommand, TBN_DROPDOWN); if (TBDDRET_DEFAULT == nVal || TBDDRET_TREATPRESSED == nVal) { if (fEatMsg) { MSG msg; PeekMessage(&msg, hwnd, WM_LBUTTONDOWN, WM_LBUTTONDOWN, PM_REMOVE); if (!IsWindow(hwnd)) return FALSE; } ptb->iPressedDD = -1; if (TB_HasUnsplitDDArrow(ptb, ptbButton)) ptbButton->fsState &= ~TBSTATE_PRESSED; InvalidateButton(ptb, ptbButton, TRUE); UpdateWindow(hwnd); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, iPos+1); } bRet = (TBDDRET_DEFAULT == nVal); } return bRet; } void TBInvalidateButton(PTBSTATE ptb, int i, BOOL fErase) { if (i != -1) { InvalidateButton(ptb, &ptb->Buttons[i], fErase); } } void TBSetHotItem(PTBSTATE ptb, int iPos, DWORD dwReason) { HWND hwnd; // Either one of these values can be -1, but refrain // from processing if both are negative b/c it is wasteful // and very common if ((ptb->iHot != iPos || (dwReason & HICF_RESELECT)) && (0 <= ptb->iHot || 0 <= iPos) && iPos < ptb->iNumButtons) { NMTBHOTITEM nmhot = {0}; int iHot = ptb->iHot; // Has the mouse moved away from the toolbar but // do we still anchor the highlight? if (0 > iPos && ptb->fAnchorHighlight && (dwReason & HICF_MOUSE)) return ; // Yes; deny the hot item change // Send a notification about the hot item change if (0 > ptb->iHot) { if (iPos >= 0) nmhot.idNew = ptb->Buttons[iPos].idCommand; nmhot.dwFlags = HICF_ENTERING; } else if (0 > iPos) { if (ptb->iHot >= 0 && ptb->iHot < ptb->iNumButtons) nmhot.idOld = ptb->Buttons[ptb->iHot].idCommand; nmhot.dwFlags = HICF_LEAVING; } else { if (ptb->iHot < ptb->iNumButtons) nmhot.idOld = ptb->Buttons[ptb->iHot].idCommand; nmhot.idNew = ptb->Buttons[iPos].idCommand; } nmhot.dwFlags |= dwReason; // must save this for revalidation hwnd = ptb->ci.hwnd; if (CCSendNotify(&ptb->ci, TBN_HOTITEMCHANGE, &nmhot.hdr)) return; // deny the hot item change // Revalidate the window if (!IsWindow(hwnd)) return; TBInvalidateButton(ptb, ptb->iHot, TRUE); if ((iPos < 0) || !(ptb->Buttons[iPos].fsState & TBSTATE_ENABLED)) iPos = -1; ptb->iHot = iPos; // Hot state change, cancel tracking tooltips if (ptb->iHot == -1) TB_CancelTipTrack(ptb); // Item focus changed, start tracking tooltip timeout for keyboard nav popups if ((ptb->iHot != -1) && !(nmhot.dwFlags & HICF_MOUSE)) { if (ptb->hwndToolTips) { TB_CancelTipTrack(ptb); ptb->iTracking = ptb->iHot; // Delay will be replaced with an SPI SetTimer(ptb->ci.hwnd, IDT_TRACKINGTIP, GetDoubleClickTime() * 2, NULL); } } if (GetFocus() == ptb->ci.hwnd && iHot != ptb->iHot) { NotifyWinEvent(EVENT_OBJECT_FOCUS, ptb->ci.hwnd, OBJID_CLIENT, iPos + 1); } TBInvalidateButton(ptb, ptb->iHot, TRUE); if ((iPos >= 0 && iPos < ptb->iNumButtons) && (TB_IsDropDown(&ptb->Buttons[iPos])) && (dwReason & HICF_TOGGLEDROPDOWN)) { TBToggleDropDown(ptb, iPos, FALSE); } } } BOOL GetInsertMarkRect(PTBSTATE ptb, LPRECT prc, BOOL fHorizMode) { BOOL fRet = TB_GetItemRect(ptb, ptb->iInsert, prc); if (fRet) { // if we are in horizontal mode, we need a vertical insertion marker if ( fHorizMode ) { if (ptb->fInsertAfter) prc->left = prc->right; else prc->right = prc->left; prc->left -= INSERTMARKSIZE/2; prc->right += INSERTMARKSIZE/2 + 1; } else { if (ptb->fInsertAfter) prc->top = prc->bottom; else prc->bottom = prc->top; prc->top -= INSERTMARKSIZE/2; prc->bottom += INSERTMARKSIZE/2 + 1; } } return fRet; } void TBInvalidateMark(PTBSTATE ptb) { RECT rc; if (GetInsertMarkRect(ptb, &rc, !(ptb->ci.style & CCS_VERT))) { InvalidateRect(ptb->ci.hwnd, &rc, TRUE); } } void TBSetInsertMark(PTBSTATE ptb, LPTBINSERTMARK ptbim) { if (ptbim->iButton != ptb->iInsert || BOOLIFY(ptb->fInsertAfter) != BOOLIFY(ptbim->dwFlags & TBIMHT_AFTER)) { if (ptb->iInsert != -1) TBInvalidateMark(ptb); ptb->iInsert = ptbim->iButton; ptb->fInsertAfter = BOOLIFY(ptbim->dwFlags & TBIMHT_AFTER); if (ptb->iInsert != -1) TBInvalidateMark(ptb); } } void TBCycleHotItem(PTBSTATE ptb, int iStart, int iDirection, UINT nReason) { int i; int iPrev; NMTBWRAPHOTITEM nmwh; nmwh.iDir = iDirection; nmwh.nReason = nReason; //When cycling around the menu, without this check, the second to last menu //item would be selected. if (iStart == -1 && iDirection == -1) iStart = 0; for (i = 0; i < ptb->iNumButtons; i++) { iPrev = iStart; iStart += iDirection + ptb->iNumButtons; iStart %= ptb->iNumButtons; if ( ( iPrev + iDirection >= ptb->iNumButtons) || (iPrev + iDirection < 0) ) { nmwh.iStart = iStart; if (CCSendNotify(&ptb->ci, TBN_WRAPHOTITEM, &nmwh.hdr)) return; } if (ptb->Buttons[iStart].fsState & TBSTATE_ENABLED && !(ptb->Buttons[iStart].fsState & TBSTATE_HIDDEN) && !(ptb->Buttons[iStart].fsStyle & BTNS_SEP)) { // if the old hot item was dropped down, undrop it. if (ptb->iHot != -1 && ptb->iHot == ptb->iPressedDD) TBToggleDropDown(ptb, ptb->iHot, FALSE); TBSetHotItem(ptb, iStart, nReason); break; } } } // Do hit testing by sliding the origin of the supplied point // // returns: // >= 0 index of non separator item hit // < 0 index of separator or nearest non separator item (area // just below and to the left) // // +-------------------------------------- // | -1 -1 -1 -1 // | btn sep btn // | +-----+ +-----+ // | | | | | // | -1 | 0 | -1 | 2 | -3 // | | | | | // | +-----+ +-----+ // | // | -1 -1 -1 -2 -3 // int TBHitTest(PTBSTATE ptb, int xPos, int yPos) { int prev = 0; int last = 0; int i; RECT rc; if (ptb->iNumButtons == 0) return(-1); for (i=0; iiNumButtons; i++) { if (TB_GetItemRect(ptb, i, &rc)) { // ignore this button if hidden because of HideClippedButtons style if (!(ptb->dwStyleEx & TBSTYLE_EX_HIDECLIPPEDBUTTONS) || !(TBIsRectClipped(ptb, &rc))) { // From PtInRect docs: // A point is within a rectangle if it lies on the left or top // side or is within all four sides. A point on the right or // bottom side is considered outside the rectangle. if (yPos >= rc.top && yPos < rc.bottom) { if (xPos >= rc.left && xPos < rc.right) { if (ptb->Buttons[i].fsStyle & BTNS_SEP) return - i - 1; else return i; } else { prev = i + 1; } } else { last = i; } } } } if (prev) return -1 - prev; else if (yPos > rc.bottom) // this means that we are off the bottom of the toolbar return(- i - 1); return -1 - last; } // Same as above except: // - returns TRUE if the cursor is on the button edge. // - returns FALSE is the cursor is b/t buttons or on the button itself BOOL TBInsertMarkHitTest(PTBSTATE ptb, int xPos, int yPos, LPTBINSERTMARK ptbim) { TBINSERTMARK prev = {-1, TBIMHT_AFTER|TBIMHT_BACKGROUND}; // best guess if we hit a row TBINSERTMARK last = {-1, TBIMHT_AFTER|TBIMHT_BACKGROUND}; // best guess if we don't int i; // restrict hit testing depending upon whether we are vertical or horizontal BOOL fHorizMode = !(ptb->ci.style & CCS_VERT); for (i=0; iiNumButtons; i++) { RECT rc; if (TB_GetItemRect(ptb, i, &rc)) { if (yPos >= rc.top && yPos < rc.bottom) { if (xPos >= rc.left && xPos < rc.right) { ptbim->iButton = i; if ( fHorizMode ) { if (xPos < rc.left + g_cxEdge*4) { ptbim->dwFlags = 0; return TRUE; } else if (xPos > rc.right - g_cxEdge*4) { ptbim->dwFlags = TBIMHT_AFTER; return TRUE; } } else { // vertical.... if (yPos < rc.top + g_cyEdge*4) { ptbim->dwFlags = 0; return TRUE; } else if (yPos > rc.bottom - g_cyEdge*4) { ptbim->dwFlags = TBIMHT_AFTER; return TRUE; } } // else we are just on a button... ptbim->dwFlags = 0; return FALSE; } else { if (xPos < rc.left) { // since buttons are laid out left to right // and rows are laid out top to bottom, // if we ever hit this case, we can't hit anything else ptbim->iButton = i; ptbim->dwFlags = TBIMHT_BACKGROUND; return FALSE; } else // (xPos > rc.right) { // remember the last one we've seen on this row prev.iButton = i; } } } else { if (yPos < rc.top) { if (prev.iButton != -1) { *ptbim = prev; } else { ptbim->iButton = i; ptbim->dwFlags = TBIMHT_BACKGROUND; } } else { // remember the last one we've seen last.iButton = i; } } } } if (prev.iButton != -1) *ptbim = prev; else *ptbim = last; return FALSE; } int CountRows(PTBSTATE ptb) { LPTBBUTTONDATA pButton, pBtnLast; int rows = 1; pBtnLast = &(ptb->Buttons[ptb->iNumButtons]); for (pButton = ptb->Buttons; pButtonfsState & TBSTATE_WRAP) { rows++; if (pButton->fsStyle & BTNS_SEP) rows++; } } return rows; } #define CountCols(ptb) CountRows(ptb) void WrapToolbarCol(PTBSTATE ptb, int dy, LPRECT lpRect, int *pCols) { LPTBBUTTONDATA pButton, pBtnLast, pBtnPrev; LPTBBUTTONDATA pbtnLastVisible = NULL; LPTBBUTTONDATA pbtnPrev = NULL; int xPos, yPos; int dyButton; int yPosWrap = 0; int cCols = 1; DEBUG_CODE( int cItemsPerCol = 0; ) ASSERT(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL); TraceMsg(TF_TOOLBAR, "Toolbar: calculating WrapToolbar"); // dy must be at least the button height, otherwise the final // rect is mis-calculated and will be too big. if (dy < ptb->iButHeight) dy = ptb->iButHeight; dyButton = ptb->iButHeight; xPos = ptb->xFirstButton; yPos = ptb->iYPos; pBtnLast = &(ptb->Buttons[ptb->iNumButtons]); ptb->szCached.cx = -1; ptb->szCached.cy = -1; if (pCols) (*pCols) = 1; pBtnPrev = ptb->Buttons; for (pButton = ptb->Buttons; pButton < pBtnLast; pButton++) { DEBUG_CODE( cItemsPerCol++; ) // we nuke the wrap state at the start of the loop. // so we don't know if/when we are adding on a wrap bit that wasn't there // before. we overstep the button, then back up when we've gone too far, pButton->fsState &= ~TBSTATE_WRAP; if (!(pButton->fsState & TBSTATE_HIDDEN)) { if (pButton->fsStyle & BTNS_SEP) yPos += (TBGetSepHeight(ptb, pButton)); else yPos += dyButton; // Is this button out of bounds? if (yPos > dy) { // Yes; wrap it. if ((pButton->fsStyle & BTNS_SEP) && yPos - TBGetSepHeight(ptb, pButton) > yPosWrap) { yPosWrap = yPos - TBGetSepHeight(ptb, pButton); // wrap at first in next col. } else if (yPos - dyButton > yPosWrap) yPosWrap = yPos - dyButton; // wrap at first in next col. if (xPos + ptb->iButWidth <= ptb->sizeBound.cx) xPos += ptb->iButWidth; yPos = dyButton; cCols++; pBtnPrev->fsState |= TBSTATE_WRAP; DEBUG_CODE( cItemsPerCol = 0; ) } // button in bounds gets handled above. pBtnPrev = pButton; // save previous for wrap point } } yPos = yPosWrap ? yPosWrap : yPos; if (pCols) *pCols = cCols; ptb->rc.left = 0; ptb->rc.right = xPos + ptb->iButWidth; ptb->rc.top = 0; ptb->rc.bottom = yPos; if (lpRect) CopyRect(lpRect, &ptb->rc); InvalidateRect(ptb->ci.hwnd, NULL, TRUE); } /**** WrapToolbar: * The buttons in the toolbar is layed out from left to right, * top to bottom. If adding another button to the current row, * while computing the layout, would cause that button to extend * beyond the right edge or the client area, then locate a break- * point (marked with the TBSTATE_WRAP flag). A break-point is: * * a) The right-most separator on the current row. * * b) The right-most button if there is no separator on the current row. * * A new row is also started at the end of any button group (sequence * of buttons that are delimited by separators) that are taller than * or equal to two rows. */ void WrapToolbar(PTBSTATE ptb, int dx, LPRECT lpRect, int *pRows) { BOOL fInvalidate = FALSE; LPTBBUTTONDATA pButton, pBtnT, pBtnLast; LPTBBUTTONDATA pbtnLastVisible = NULL; LPTBBUTTONDATA pbtnPrev = NULL; BOOL fLastVisibleWrapped = FALSE; int xPos, yPos, xMax; int dyButton; BOOL bWrapAtNextSeparator = FALSE; ASSERT(!(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL)); TraceMsg(TF_TOOLBAR, "Toolbar: calculating WrapToolbar"); if (ptb->iNumButtons == 0) { // no buttons, so we're not going to go through the loop below; initialize // dyButton to 0 so that we fill in lpRect with 0 height. this fixes ideal // size calculation for empty toolbars (NT5 #180430) dyButton = 0; } else { if (dx < ptb->iButWidth) { // dx must be at least the button width, otherwise the final // rect is mis-calculated and will be too big. dx = ptb->iButWidth; } dyButton = ptb->iButHeight; } xMax = 0; xPos = ptb->xFirstButton; yPos = ptb->iYPos; pBtnLast = &(ptb->Buttons[ptb->iNumButtons]); ptb->szCached.cx = -1; ptb->szCached.cy = -1; if (pRows) (*pRows)=1; for (pButton = ptb->Buttons; pButton < pBtnLast; pButton++) { // we nuke the wrap state at the start of the loop. // so we don't know if/when we are adding on a wrap bit that wasn't there // before. we overstep the button, then back up when we've gone too far, // so we can't simply keep the at the start of the loop // we need to keep it over to the next iteration BOOL fNextLastVisibleWrapped = (pButton->fsState & TBSTATE_WRAP); LPTBBUTTONDATA pbtnSav = pButton; pButton->fsState &= ~TBSTATE_WRAP; if (!(pButton->fsState & TBSTATE_HIDDEN)) { LPTBBUTTONDATA pbtnNextLastVisible = pButton; xPos += TBWidthOfButton(ptb, pButton, NULL) + ptb->cxButtonSpacing; // Is this a normal button and is the button out of bounds? if (!(pButton->fsStyle & BTNS_SEP) && (xPos > dx)) { // Yes; wrap it. Go back to the first non-hidden separator // as a break-point candidate. for (pBtnT=pButton; pBtnT>ptb->Buttons && !(pBtnT->fsState & TBSTATE_WRAP); pBtnT--) { if ((pBtnT->fsStyle & BTNS_SEP) && !(pBtnT->fsState & TBSTATE_HIDDEN)) { yPos += (TBGetSepHeight(ptb, pBtnT)) + dyButton + ptb->cyButtonSpacing; bWrapAtNextSeparator = FALSE; if (pRows) (*pRows)++; goto SetWrapHere; } } pBtnT = pButton; // Are we at the first button? if (pButton != ptb->Buttons) { // No; back up to first non-hidden button do { pBtnT--; } while ((pBtnT>ptb->Buttons) && (pBtnT->fsState & TBSTATE_HIDDEN)); // Is it already wrapped? if (pBtnT->fsState & TBSTATE_WRAP) { // Yes; wrap the button we were looking at originally pBtnT = pButton; } } // Wrap at the next separator because we've now wrapped in the middle // of a group of buttons. bWrapAtNextSeparator = TRUE; yPos += dyButton + ptb->cyButtonSpacing; SetWrapHere: pBtnT->fsState |= TBSTATE_WRAP; // find out if this wrap bit is new... // it isn't if this button was the last visible button // and that last visible button started off wrapped if (pBtnT != pbtnLastVisible || !fLastVisibleWrapped) fInvalidate = TRUE; xPos = ptb->xFirstButton; pButton = pBtnT; // Count another row. if (pRows) (*pRows)++; } else { // No; this is a separator (in or out of bounds) or a button that is in-bounds. if (pButton->fsStyle & BTNS_SEP) { if (ptb->ci.style & CCS_VERT) { if (pbtnPrev && !(pbtnPrev->fsState & TBSTATE_WRAP)) { pbtnPrev->fsState |= TBSTATE_WRAP; yPos += dyButton + ptb->cyButtonSpacing; } xPos = ptb->xFirstButton; yPos += TBGetSepHeight(ptb, pButton); pButton->fsState |= TBSTATE_WRAP; if (pRows) (*pRows)++; } else if (bWrapAtNextSeparator) { bWrapAtNextSeparator = FALSE; pButton->fsState |= TBSTATE_WRAP; xPos = ptb->xFirstButton; yPos += dyButton + (TBGetSepHeight(ptb, pButton)) + ptb->cyButtonSpacing; if (pRows) (*pRows)+=2; } } // This button is visible and it's one we cached at the top of the loop // set it for the next loop if (pButton == pbtnNextLastVisible) { ASSERT(!(pButton->fsState & TBSTATE_HIDDEN)); if (!(pButton->fsState & TBSTATE_HIDDEN)) { // we don't know that we're not going to re-wrap an item that was initially wrapped // until this point if (pbtnLastVisible && fLastVisibleWrapped && !(pbtnLastVisible->fsState & TBSTATE_WRAP)) fInvalidate = TRUE; pbtnLastVisible = pButton; fLastVisibleWrapped = fNextLastVisibleWrapped; } } } if (!(pButton->fsStyle&BTNS_SEP)) xMax = max(xPos, xMax); pbtnPrev = pbtnSav; } } if (lpRect) { lpRect->left = 0; lpRect->right = xMax; lpRect->top = 0; lpRect->bottom = yPos + ptb->iYPos + dyButton; } if (fInvalidate) InvalidateRect(ptb->ci.hwnd, NULL, TRUE); } // only called from TB_SETROWS so no worry's about TBSTYLE_EX_MULTICOLUMN BOOL BoxIt(PTBSTATE ptb, int height, BOOL fLarger, LPRECT lpRect) { int dx, bwidth; int rows, prevRows, prevWidth; RECT rcCur; if (height<1) height = 1; rows = CountRows(ptb); if (height==rows || ptb->iNumButtons==0) { GetClientRect(ptb->ci.hwnd, lpRect); return FALSE; } bwidth = ptb->iButWidth + ptb->cxButtonSpacing; prevRows = ptb->iNumButtons+1; prevWidth = bwidth; for (rows=height+1, dx = bwidth; rows>height;dx+=bwidth/4) { WrapToolbar(ptb, dx, &rcCur, &rows); if (rowsheight) { prevWidth = dx; prevRows = rows; } } if (rowsiNumButtons; i++) if (ptb->Buttons[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 void MakeGroupConsistant(PTBSTATE ptb, int idCommand) { int i, iFirst, iLast, iButton; int cButtons = ptb->iNumButtons; LPTBBUTTONDATA pAllButtons = ptb->Buttons; iButton = PositionFromID(ptb, idCommand); if (iButton < 0) return; // assertion // if (!(pAllButtons[iButton].fsStyle & BTNS_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 // there was a bug here since win95 days -- ; there was no ; at the end of for loop // and if was part of it -- some apps may rely on that (reljai 6/16/98) for (iFirst = iButton; (iFirst > 0) && (pAllButtons[iFirst].fsStyle & BTNS_GROUP); iFirst--); if (!(pAllButtons[iFirst].fsStyle & BTNS_GROUP)) iFirst++; cButtons--; for (iLast = iButton; (iLast < cButtons) && (pAllButtons[iLast].fsStyle & BTNS_GROUP); iLast++); if (!(pAllButtons[iLast].fsStyle & BTNS_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 TBInvalidateButton(ptb, i, TRUE); break; // only one button is down right? } } } } void DestroyStrings(PTBSTATE ptb) { PTSTR *p; PTSTR end = 0, start = 0; int i; p = ptb->pStrings; for (i = 0; i < ptb->nStrings; i++) { if (!((*p < end) && (*p > start))) { start = (*p); end = start + (LocalSize((HANDLE)*p) / sizeof(TCHAR)); LocalFree((HANDLE)*p); } p++; } LocalFree((HANDLE)ptb->pStrings); } // gets the iString from pStrings and copies it to pszText. // returns the lstrlen. // pszText can be null to just fetch the length. int TBGetString(PTBSTATE ptb, int iString, int cchText, LPTSTR pszText) { int iRet = -1; if (iString < ptb->nStrings) { iRet = lstrlen(ptb->pStrings[iString]); if (pszText) { lstrcpyn(pszText, ptb->pStrings[iString], cchText); } } return iRet; } // gets the iString from pStrings and copies it to pszText. // returns the lstrlen. // pszText can be null to just fetch the length. int TBGetStringA(PTBSTATE ptb, int iString, int cchText, LPSTR pszText) { int iRet = -1; if (iString < ptb->nStrings) { iRet = lstrlenW(ptb->pStrings[iString]); if (pszText) { WideCharToMultiByte (CP_ACP, 0, ptb->pStrings[iString], -1, pszText, cchText, NULL, NULL); } } return iRet; } #define MAXSTRINGSIZE 1024 int TBAddStrings(PTBSTATE ptb, WPARAM wParam, LPARAM lParam) { int i = 0,j = 0, cxMax = 0; LPTSTR lpsz; PTSTR pString, pStringAlloc, psz; int numstr; PTSTR *pFoo; PTSTR *pOffset; TCHAR cSeparator; int len; // read the string as a resource if (wParam != 0) { pString = (PTSTR)LocalAlloc(LPTR, (MAXSTRINGSIZE * sizeof (TCHAR))); if (!pString) return -1; i = LoadString((HINSTANCE)wParam, LOWORD(lParam), (LPTSTR)pString, MAXSTRINGSIZE); if (!i) { LocalFree(pString); return -1; } // realloc string buffer to actual needed size psz = (PTSTR)LocalReAlloc(pString, (i+1) * sizeof (TCHAR), LMEM_MOVEABLE); if (psz) pString = psz; // convert separators to '\0' and count number of strings cSeparator = *pString; for (numstr = 0, psz = pString + 1, i--; i; i--, psz++) { if (*psz == cSeparator) { if (i != 1) // We don't want to count the second terminator as another string numstr++; *psz = 0; // terminate with 0 } // shift string to the left to overwrite separator identifier *(psz - 1) = *psz; } } // read explicit string. copy it into local memory, too. else { // Common mistake is to forget to check the return value of // LoadLibrary and accidentally pass wParam=NULL. if (IS_INTRESOURCE(lParam)) return -1; // find total length and number of strings for (i = 0, numstr = 0, lpsz = (LPTSTR)lParam;;) { i++; if (*lpsz == 0) { numstr++; if (*(lpsz + 1) == 0) break; } lpsz++; } pString = (PTSTR)LocalAlloc(LPTR, (i * sizeof (TCHAR))); if (!pString) return -1; hmemcpy(pString, (void *)lParam, i * sizeof(TCHAR)); } pStringAlloc = pString; // in case something bad happens // make room for increased string pointer table pFoo = (PTSTR *)CCLocalReAlloc(ptb->pStrings, (ptb->nStrings + numstr) * sizeof(PTSTR)); if (!pFoo) { goto Failure; } ptb->pStrings = pFoo; // pointer to next open slot in string index table. pOffset = ptb->pStrings + ptb->nStrings; for (i = 0; i < numstr; i++, pOffset++) { *pOffset = pString; len = lstrlen(pString); pString += len + 1; } // is the world big enough to handle the larger buttons? i = ptb->nStrings; ptb->nStrings += numstr; if (!TBRecalc(ptb)) { ptb->nStrings -= numstr; // back out changes. pFoo = (PTSTR *)CCLocalReAlloc(ptb->pStrings, ptb->nStrings * sizeof(PTSTR)); if (pFoo || (ptb->nStrings == 0)) ptb->pStrings = pFoo; // don't get mad if pFoo == NULL; it means the shrink failed, no big deal Failure: LocalFree(pStringAlloc); return -1; } return i; // index of first added string } void MapToStandardBitmaps(HINSTANCE *phinst, UINT_PTR *pidBM, int *pnButtons) { if (*phinst == HINST_COMMCTRL) { *phinst = g_hinst; // low 2 bits are coded M(mono == ~color) L(large == ~small) // 0 0 -> color small // 0 1 -> color large // ... // 1 1 -> mono large switch (*pidBM) { case IDB_STD_SMALL_COLOR: case IDB_STD_LARGE_COLOR: case IDB_STD_SMALL_MONO: case IDB_STD_LARGE_MONO: *pidBM = IDB_STDTB_SMALL_COLOR + (*pidBM & 1); *pnButtons = STD_PRINT + 1; break; case IDB_HIST_SMALL_COLOR: case IDB_HIST_LARGE_COLOR: //case IDB_HIST_SMALL_MONO: //case IDB_HIST_LARGE_MONO: *pidBM = IDB_HISTTB_SMALL_COLOR + (*pidBM & 1); *pnButtons = HIST_LAST + 1; break; case IDB_VIEW_SMALL_COLOR: case IDB_VIEW_LARGE_COLOR: case IDB_VIEW_SMALL_MONO: case IDB_VIEW_LARGE_MONO: *pidBM = IDB_VIEWTB_SMALL_COLOR + (*pidBM & 1); *pnButtons = VIEW_NEWFOLDER + 1; break; } } } // // the PBITMAP points to the BITMAP structure that was GetObject'd from // the hbm, except that pbm->bmWidth and pbm->bmHeight have been adjusted // to represent the *desired* height and width, not the actual height // and width. // HBITMAP _CopyBitmap(PTBSTATE ptb, HBITMAP hbm, PBITMAP pbm) { HBITMAP hbmCopy = NULL; HDC hdcWin; HDC hdcSrc, hdcDest; // Old code called CreateColorBitmap, which is bad on multimon systems // because it will create a bitmap that ImageList_AddMasked can't handle, // resulting in disabled toolbar buttons looking bad. // so we have to create the bitmap copy in the same format as the source hdcWin = GetDC(ptb->ci.hwnd); hdcSrc = CreateCompatibleDC(hdcWin); hdcDest = CreateCompatibleDC(hdcWin); if (hdcWin && hdcSrc && hdcDest) { SelectObject(hdcSrc, hbm); if (pbm->bmBits) { // Source was a DIB section. Create a DIB section in the same // color format with the same palette. // // Man, creating a DIB section is so annoying. struct { // Our private version of BITMAPINFO BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[256]; } bmi; UINT cBitsPixel; LPVOID pvDummy; ZeroMemory(&bmi.bmiHeader, sizeof(bmi.bmiHeader)); bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = pbm->bmWidth; bmi.bmiHeader.biHeight = pbm->bmHeight; bmi.bmiHeader.biPlanes = 1; // DIB color depths must be exactly 1, 4, 8 or 24. cBitsPixel = pbm->bmPlanes * pbm->bmBitsPixel; if (cBitsPixel <= 1) bmi.bmiHeader.biBitCount = 1; else if (cBitsPixel <= 4) bmi.bmiHeader.biBitCount = 4; else if (cBitsPixel <= 8) bmi.bmiHeader.biBitCount = 8; else goto CreateDDB; // ImageList_AddMasked doesn't like DIBs deeper than 8bpp // And get the color table too ASSERT(bmi.bmiHeader.biBitCount <= 8); bmi.bmiHeader.biClrUsed = GetDIBColorTable(hdcSrc, 0, 1 << bmi.bmiHeader.biBitCount, bmi.bmiColors); ASSERT(bmi.bmiHeader.biCompression == BI_RGB); ASSERT(bmi.bmiHeader.biSizeImage == 0); hbmCopy = CreateDIBSection(hdcWin, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, &pvDummy, NULL, 0); } else { // Source was a DDB. Create a duplicate DDB. CreateDDB: // Since the caller may have dorked the bmWidth, // we have to recompute the bmWidthBytes, because GDI // gets mad if it's not exactly right, even in the bmBits == NULL // case. pbm->bmBits = NULL; pbm->bmWidthBytes = ((pbm->bmBitsPixel * pbm->bmWidth + 15) >> 4) << 1; hbmCopy = CreateBitmapIndirect(pbm); } SelectObject(hdcDest, hbmCopy); // fill the background PatB(hdcDest, 0, 0, pbm->bmWidth, pbm->bmHeight, g_clrBtnFace); BitBlt(hdcDest, 0, 0, pbm->bmWidth, pbm->bmHeight, hdcSrc, 0, 0, SRCCOPY); } if (hdcWin) ReleaseDC(ptb->ci.hwnd, hdcWin); if (hdcSrc) DeleteDC(hdcSrc); if (hdcDest) DeleteDC(hdcDest); return hbmCopy; } BOOL TBAddBitmapToImageList(PTBSTATE ptb, PTBBMINFO pTemp) { HBITMAP hbm = NULL, hbmTemp = NULL; HIMAGELIST himl = TBGetImageList(ptb, HIML_NORMAL, 0); BOOL bSkipFixup = FALSE; if (!himl) { himl = ImageList_Create(ptb->iDxBitmap, ptb->iDyBitmap, ILC_MASK | ILC_COLOR32, 4, 4); if (!himl) return(FALSE); TBSetImageList(ptb, HIML_NORMAL, 0, himl); ImageList_SetBkColor(himl, (ptb->ci.style & TBSTYLE_TRANSPARENT) ? CLR_NONE : g_clrBtnFace); } if (pTemp->hInst) { // can't use LoadImage(..., LR_MAP3DCOLORS) - more than 3 colors hbm = hbmTemp = CreateMappedBitmap(pTemp->hInst, pTemp->wID, CMB_DIBSECTION, NULL, 0); // fixup is converting 32bit DIBs to DDB, which breaks icons in <32bit color. // pfortier: need to figure out the proper fixup mechanism for >8bit bitmaps. // for now, assume that bitmap resources from comctl32 don't need this. if (pTemp->hInst == g_hinst) bSkipFixup = TRUE; } else if (pTemp->wID) { hbm = (HBITMAP)pTemp->wID; } if (hbm && !bSkipFixup) { // // Fix up bitmaps that aren't iDxBitmap x iDyBitmap // BITMAP bm; GetObject( hbm, sizeof(bm), &bm); if (bm.bmWidth < ptb->iDxBitmap) { bm.bmWidth = ptb->iDxBitmap; } if (bm.bmHeight < ptb->iDyBitmap) { bm.bmHeight = ptb->iDyBitmap; } // The error cases we are catching are: // If the pTemp->nButtons is 0 then we assume there is one button // If width of the bitmap is less than what it is supposed to be, we fix it. if (!pTemp->nButtons) bm.bmWidth = ptb->iDxBitmap; else if (pTemp->nButtons > (bm.bmWidth / ptb->iDxBitmap)) bm.bmWidth = ptb->iDxBitmap * pTemp->nButtons; // Must preserve color depth to keep ImageList_AddMasked happy // And if we started with a DIB section, then create a DIB section. // (Curiously, CopyImage does not preserve DIB-ness.) hbm = (HBITMAP)_CopyBitmap(ptb, hbm, &bm); } // AddMasked parties on the bitmap, so we want to use a local copy if (hbm) { ImageList_AddMasked(himl, hbm, g_clrBtnFace); DeleteObject(hbm); } if (hbmTemp) { DeleteObject(hbmTemp); } return(TRUE); } void TBBuildImageList(PTBSTATE ptb) { int i; PTBBMINFO pTemp; HIMAGELIST himl; ptb->fHimlValid = TRUE; // is the parent dealing natively with imagelists? if so, // don't do this back compat building if (ptb->fHimlNative) return; himl = TBSetImageList(ptb, HIML_NORMAL, 0, NULL); ImageList_Destroy(himl); for (i = 0, pTemp = ptb->pBitmaps; i < ptb->nBitmaps; i++, pTemp++) { TBAddBitmapToImageList(ptb, pTemp); } } /* 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. */ int AddBitmap(PTBSTATE ptb, int nButtons, HINSTANCE hBMInst, UINT_PTR idBM) { PTBBMINFO pTemp; int nBM, nIndex; // map things to the standard toolbar images if (hBMInst == HINST_COMMCTRL) // -1 { // set the proper dimensions... if (idBM & 1) SetBitmapSize(ptb, LARGE_DXYBITMAP, LARGE_DXYBITMAP); else SetBitmapSize(ptb, SMALL_DXYBITMAP, SMALL_DXYBITMAP); MapToStandardBitmaps(&hBMInst, &idBM, &nButtons); } if (ptb->pBitmaps) { /* Check if the bitmap has already been added */ for (nBM=ptb->nBitmaps, pTemp=ptb->pBitmaps, nIndex=0; nBM>0; --nBM, ++pTemp) { if (pTemp->hInst==hBMInst && pTemp->wID==idBM) { /* 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)CCLocalReAlloc(ptb->pBitmaps, (ptb->nBitmaps + 1)*sizeof(TBBMINFO)); if (!pTemp) return(-1); ptb->pBitmaps = pTemp; pTemp = ptb->pBitmaps + ptb->nBitmaps; pTemp->hInst = hBMInst; pTemp->wID = idBM; pTemp->nButtons = nButtons; if (!TBAddBitmapToImageList(ptb, pTemp)) return(-1); ++ptb->nBitmaps; for (nButtons=0, --pTemp; pTemp>=ptb->pBitmaps; --pTemp) nButtons += pTemp->nButtons; return(nButtons); } /* Adds a 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. */ int TBLoadImages(PTBSTATE ptb, UINT_PTR id, HINSTANCE hinst) { int iTemp = 0; TBBMINFO bmi; HIMAGELIST himl; MapToStandardBitmaps(&hinst, &id, &iTemp); bmi.hInst = hinst; bmi.wID = id; bmi.nButtons = iTemp; himl = TBGetImageList(ptb, HIML_NORMAL, 0); if (himl) iTemp = ImageList_GetImageCount(himl); else iTemp = 0; if (!TBAddBitmapToImageList(ptb, &bmi)) return(-1); ptb->fHimlNative = TRUE; return iTemp; } BOOL ReplaceBitmap(PTBSTATE ptb, LPTBREPLACEBITMAP lprb) { int nBM; PTBBMINFO pTemp; int iTemp; MapToStandardBitmaps(&lprb->hInstOld, &lprb->nIDOld, &iTemp); MapToStandardBitmaps(&lprb->hInstNew, &lprb->nIDNew, &lprb->nButtons); for (nBM=ptb->nBitmaps, pTemp=ptb->pBitmaps; nBM>0; --nBM, ++pTemp) { if (pTemp->hInst==lprb->hInstOld && pTemp->wID==lprb->nIDOld) { // number of buttons must match pTemp->hInst = lprb->hInstNew; pTemp->wID = lprb->nIDNew; pTemp->nButtons = lprb->nButtons; TBInvalidateImageList(ptb); return TRUE; } } return FALSE; } void TBInvalidateItemRects(PTBSTATE ptb) { // Invalidate item rect cache ptb->fItemRectsValid = FALSE; // Invalidate the tooltips ptb->fTTNeedsFlush = TRUE; // Invalidate the ideal size cache ptb->szCached.cx = -1; ptb->szCached.cy = -1; } void FlushToolTipsMgrNow(PTBSTATE ptb) { // change all the rects for the tool tips mgr. this is // cheap, and we don't do it often, so go ahead // and do them all. if(ptb->hwndToolTips) { UINT i; TOOLINFO ti; LPTBBUTTONDATA pButton; ti.cbSize = SIZEOF(ti); ti.hwnd = ptb->ci.hwnd; ti.lpszText = LPSTR_TEXTCALLBACK; for ( i = 0, pButton = ptb->Buttons; i < (UINT)ptb->iNumButtons; i++, pButton++) { if (!(pButton->fsStyle & BTNS_SEP)) { ti.uId = pButton->idCommand; if (!TB_GetItemRect(ptb, i, &ti.rect) || ((ptb->dwStyleEx & TBSTYLE_EX_HIDECLIPPEDBUTTONS) && TBIsRectClipped(ptb, &ti.rect))) { ti.rect.left = ti.rect.right = ti.rect.top = ti.rect.bottom = 0; } SendMessage(ptb->hwndToolTips, TTM_NEWTOOLRECT, 0, (LPARAM)((LPTOOLINFO)&ti)); } } ptb->fTTNeedsFlush = FALSE; } } BOOL TBReallocButtons(PTBSTATE ptb, UINT uButtons) { LPTBBUTTONDATA ptbbNew; LPTBBUTTONDATA pOldCaptureButton; if (!ptb || !ptb->uStructSize) return FALSE; // When we realloc the Button array, make sure all interior pointers // move with it. (This should probably be an index.) pOldCaptureButton = ptb->pCaptureButton; // realloc the button table ptbbNew = (LPTBBUTTONDATA)CCLocalReAlloc(ptb->Buttons, uButtons * sizeof(TBBUTTONDATA)); if (!ptbbNew) return FALSE; if (pOldCaptureButton) ptb->pCaptureButton = (LPTBBUTTONDATA)( (LPBYTE)ptbbNew + ((LPBYTE)pOldCaptureButton - (LPBYTE)ptb->Buttons)); ptb->Buttons = ptbbNew; return TRUE; } BOOL TBInsertButtons(PTBSTATE ptb, UINT uWhere, UINT uButtons, LPTBBUTTON lpButtons, BOOL fNative) { LPTBBUTTONDATA pOut; LPTBBUTTONDATA ptbbIn; UINT uAdded; UINT uStart; BOOL fRecalc; int idHot = -1; if (!TBReallocButtons(ptb, ptb->iNumButtons + uButtons)) return FALSE; TB_CancelTipTrack(ptb); // if where points beyond the end, set it at the end if (uWhere > (UINT)ptb->iNumButtons) uWhere = ptb->iNumButtons; // Need to save these since the values gues toasted. uAdded = uButtons; uStart = uWhere; // Correct the hot item when we add something something. Since the hot item is index based, the index // has probrably changed if (ptb->iHot >= 0 && ptb->iHot < ptb->iNumButtons) idHot = ptb->Buttons[ptb->iHot].idCommand; // move buttons above uWhere up uButton spaces // the uWhere gets inverted and counts to zero.. // // REVIEW: couldn't this be done with MoveMemory? // MoveMemory(&ptb->Buttons[uWhere], &ptb->Buttons[uWhere+uButtons], sizeof(ptb->Buttons[0])*(ptb->iNumButtons - uWhere)); // for (ptbbIn = &ptb->Buttons[ptb->iNumButtons-1], pOut = ptbbIn+uButtons, uWhere=(UINT)ptb->iNumButtons-uWhere; uWhere>0; --ptbbIn, --pOut, --uWhere) *pOut = *ptbbIn; // only need to recalc if there are strings & room enough to actually show them fRecalc = (TBHasStrings(ptb) && ((ptb->ci.style & TBSTYLE_LIST) || ((ptb->iDyBitmap + ptb->cyPad + g_cyEdge) < ptb->iButHeight))); // now do the copy. for (lpButtons=(LPTBBUTTON)((LPBYTE)lpButtons+ptb->uStructSize*(uButtons-1)), ptb->iNumButtons+=(int)uButtons; // init uButtons>0; //test --pOut, lpButtons=(LPTBBUTTON)((LPBYTE)lpButtons-ptb->uStructSize), --uButtons) { TBInputStruct(ptb, pOut, lpButtons); // If this button is a seperator, then should not use the string // buffer passed in, because it could be bogus data. if (pOut->fsStyle & BTNS_SEP) pOut->iString = -1; if (TBISSTRINGPTR(pOut->iString)) { LPTSTR psz = (LPTSTR)pOut->iString; if (!fNative) { psz = ProduceWFromA(ptb->ci.uiCodePage, (LPSTR)psz); } pOut->iString = 0; Str_Set((LPTSTR*)&pOut->iString, psz); if (!fNative) FreeProducedString(psz); if (!ptb->fNoStringPool) fRecalc = TRUE; ptb->fNoStringPool = TRUE; } if(ptb->hwndToolTips && !(lpButtons->fsStyle & BTNS_SEP)) { TOOLINFO ti; // don't bother setting the rect because we'll do it below // in TBInvalidateItemRects; ti.cbSize = sizeof(ti); ti.uFlags = 0; if (ptb->dwStyleEx & TBSTYLE_EX_TOOLTIPSEXCLUDETOOLBAR) ti.uFlags |= TTF_EXCLUDETOOLAREA; ti.hwnd = ptb->ci.hwnd; ti.uId = lpButtons->idCommand; ti.lpszText = LPSTR_TEXTCALLBACK; SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } if (pOut->fsStyle & BTNS_SEP && pOut->cxySep <= 0) { // Compat: Corel (Font navigator) expects the separators to be // 8 pixels wide. // as do many old apps. // // so if it's not flat or not vertical, put it to defautl to win95 size pOut->cxySep = g_dxButtonSep; } } // Re-compute layout if toolbar is wrappable. if ((ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN) || (ptb->ci.style & TBSTYLE_WRAPABLE)) { // NOTE: we used to do send ourself a message instead of call directly... //SendMessage(ptb->ci.hwnd, TB_AUTOSIZE, 0, 0); TBAutoSize(ptb); } TBInvalidateItemRects(ptb); // adding and removing buttons during toolbar customization shouldn't // result in recalcing the sizes of buttons. if (fRecalc && !ptb->hdlgCust) TBRecalc(ptb); // // Reorder notification so apps can go requery what's on the toolbar if // more than 1 button was added; otherwise, just say create. // if (uAdded == 1) NotifyWinEvent(EVENT_OBJECT_CREATE, ptb->ci.hwnd, OBJID_CLIENT, uWhere+1); else NotifyWinEvent(EVENT_OBJECT_REORDER, ptb->ci.hwnd, OBJID_CLIENT, 0); // was there a hot item before the delete? if (idHot != -1) { // Yes; Then update it to the current index ptb->iHot = PositionFromID(ptb, idHot); } TBInvalidateItemRects(ptb); // We need to completely redraw the toolbar at this point. // this MUST be done last! // tbrecalc and others will nuke out invalid area and we won't paint if this isn't last InvalidateRect(ptb->ci.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. */ BOOL DeleteButton(PTBSTATE ptb, UINT uIndex) { TBNOTIFY tbn = { 0 }; LPTBBUTTONDATA pIn, pOut; BOOL fRecalc; int idHot = -1; if (uIndex >= (UINT)ptb->iNumButtons) return FALSE; if (&ptb->Buttons[uIndex] == ptb->pCaptureButton) { if (ptb->uStructSize == 0x14) ptb->fRequeryCapture = TRUE; if (!CCReleaseCapture(&ptb->ci)) return FALSE; ptb->pCaptureButton = NULL; } TB_CancelTipTrack(ptb); // Correct the hot item when we remove something. Since the hot item is index based, the index // has probrably changed if (ptb->iHot >= 0 && ptb->iHot < ptb->iNumButtons) idHot = ptb->Buttons[ptb->iHot].idCommand; // Notify Active Accessibility of the delete NotifyWinEvent(EVENT_OBJECT_DESTROY, ptb->ci.hwnd, OBJID_CLIENT, uIndex+1); // Notify client of the delete tbn.iItem = ptb->Buttons[uIndex].idCommand; TBOutputStruct(ptb, &ptb->Buttons[uIndex], &tbn.tbButton); CCSendNotify(&ptb->ci, TBN_DELETINGBUTTON, &tbn.hdr); if (TBISSTRINGPTR(ptb->Buttons[uIndex].iString)) Str_Set((LPTSTR*)&ptb->Buttons[uIndex].iString, NULL); if (ptb->hwndToolTips) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.hwnd = ptb->ci.hwnd; ti.uId = ptb->Buttons[uIndex].idCommand; SendMessage(ptb->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } --ptb->iNumButtons; pOut = ptb->Buttons + uIndex; fRecalc = (pOut->fsState & TBSTATE_WRAP); for (pIn = pOut + 1; uIndex<(UINT)ptb->iNumButtons; ++uIndex, ++pIn, ++pOut) { fRecalc |= (pIn->fsState & TBSTATE_WRAP); *pOut = *pIn; } // We need to completely recalc or redraw the toolbar at this point. if (((ptb->ci.style & TBSTYLE_WRAPABLE) || (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN)) && fRecalc) { RECT rc; HWND hwnd = ptb->ci.hwnd; if (!(ptb->ci.style & CCS_NORESIZE) && !(ptb->ci.style & CCS_NOPARENTALIGN)) hwnd = GetParent(hwnd); GetWindowRect(hwnd, &rc); if (ptb->ci.style & TBSTYLE_WRAPABLE) WrapToolbar(ptb, rc.right - rc.left, &rc, NULL); else WrapToolbarCol(ptb, ptb->sizeBound.cy, &rc, NULL); } // was there a hot item before the delete? if (idHot != -1) { // Yes; Then update it to the current index ptb->iHot = PositionFromID(ptb, idHot); } InvalidateRect(ptb->ci.hwnd, NULL, TRUE); TBInvalidateItemRects(ptb); return TRUE; } // move button at location iOld to location iNew, sliding everything // after iNew UP. BOOL TBMoveButton(PTBSTATE ptb, UINT iOld, UINT iNew) { TBBUTTONDATA tbd, *ptbdOld, *ptbdNew; if (iOld >= (UINT)ptb->iNumButtons) return FALSE; if (iNew > (UINT)ptb->iNumButtons-1) iNew = (UINT)ptb->iNumButtons-1; if (iOld == iNew) return FALSE; TBInvalidateItemRects(ptb); ptbdOld = &(ptb->Buttons[iOld]); ptbdNew = &(ptb->Buttons[iNew]); tbd = *ptbdOld; #if 0 if (iOld < iNew) MoveMemory(ptbdOld+1, ptbdOld, (iNew - iOld) * SIZEOF(tbd)); else MoveMemory(ptbdNew, ptbdNew+1, (iOld - iNew) * SIZEOF(tbd)); #else { TBBUTTONDATA *ptbdSrc; TBBUTTONDATA *ptbdDst; int iCount, iInc; if (iOld < iNew) { // move [iOld+1..iNew] to [iOld..iNew-1] iCount = iNew - iOld; iInc = 1; ptbdSrc = ptbdOld + 1; ptbdDst = ptbdOld; if (ptb->pCaptureButton > ptbdOld && ptb->pCaptureButton <= ptbdNew) ptb->pCaptureButton--; } else { ASSERT(iNew < iOld); // move [iNew..iOld-1] to [iNew+1..iOld] iCount = iOld - iNew; iInc = -1; ptbdSrc = ptbdNew + iCount - 1; ptbdDst = ptbdNew + iCount; if (ptb->pCaptureButton >= ptbdNew && ptb->pCaptureButton < ptbdOld) ptb->pCaptureButton++; } do { *ptbdDst = *ptbdSrc; ptbdDst += iInc; ptbdSrc += iInc; iCount--; } while (iCount); } #endif *ptbdNew = tbd; if (ptb->pCaptureButton == ptbdOld) ptb->pCaptureButton = ptbdNew; TBAutoSize(ptb); InvalidateRect(ptb->ci.hwnd, NULL, TRUE); return TRUE; } // deal with old TBBUTON structs for compatibility void TBInputStruct(PTBSTATE ptb, LPTBBUTTONDATA pButtonInt, LPTBBUTTON pButtonExt) { pButtonInt->iBitmap = pButtonExt->iBitmap; pButtonInt->idCommand = pButtonExt->idCommand; pButtonInt->fsState = pButtonExt->fsState; pButtonInt->fsStyle = pButtonExt->fsStyle; pButtonInt->cx = 0; if (ptb->uStructSize >= sizeof(TBBUTTON)) { pButtonInt->dwData = pButtonExt->dwData; pButtonInt->iString = pButtonExt->iString; } else { /* It is assumed the only other possibility is the OLDBUTTON struct */ /* We don't care about dwData */ pButtonInt->dwData = 0; pButtonInt->iString = -1; } } void TBOutputStruct(PTBSTATE ptb, LPTBBUTTONDATA pButtonInt, LPTBBUTTON pButtonExt) { ZeroMemory(pButtonExt, ptb->uStructSize); pButtonExt->iBitmap = pButtonInt->iBitmap; pButtonExt->idCommand = pButtonInt->idCommand; pButtonExt->fsState = pButtonInt->fsState; pButtonExt->fsStyle = pButtonInt->fsStyle; // We're returning cx in the bReserved field COMPILETIME_ASSERT(FIELD_OFFSET(TBBUTTONDATA, cx) == FIELD_OFFSET(TBBUTTON, bReserved)); COMPILETIME_ASSERT(sizeof(pButtonInt->cx) <= sizeof(pButtonExt->bReserved)); ((LPTBBUTTONDATA)pButtonExt)->cx = pButtonInt->cx; if (ptb->uStructSize >= sizeof(TBBUTTON)) { pButtonExt->dwData = pButtonInt->dwData; pButtonExt->iString = pButtonInt->iString; } } void TBOnButtonStructSize(PTBSTATE ptb, UINT uStructSize) { /* You are not allowed to change this after adding buttons. */ if (ptb && !ptb->iNumButtons) { ptb->uStructSize = uStructSize; } } void TBAutoSize(PTBSTATE ptb) { HWND hwndParent; RECT rc; int nTBThickness = 0; if (ptb->fRedrawOff) { // redraw is off; defer autosize until redraw is turned back on ptb->fRecalc = TRUE; return; } if (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN) { ASSERT(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL); nTBThickness = ptb->iButWidth * CountCols(ptb) + g_cyEdge * 2; } else nTBThickness = (ptb->iButHeight + ptb->cyButtonSpacing) * CountRows(ptb) + g_cxEdge * 2 - ptb->cyButtonSpacing; hwndParent = GetParent(ptb->ci.hwnd); if (!hwndParent) return; if ((ptb->ci.style & TBSTYLE_WRAPABLE) || (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN)) { RECT rcNew; if ((ptb->ci.style & CCS_NORESIZE) || (ptb->ci.style & CCS_NOPARENTALIGN)) GetWindowRect(ptb->ci.hwnd, &rc); else GetWindowRect(hwndParent, &rc); if (ptb->ci.style & TBSTYLE_WRAPABLE) WrapToolbar(ptb, rc.right - rc.left, &rcNew, NULL); else WrapToolbarCol(ptb, ptb->sizeBound.cy, &rcNew, NULL); // Some sample app found a bug in our autosize code which this line // fixes. Unfortunately Carbon Copy 32 (IE4 bug 31943) relies on the // broken behavior and fixing this clips the buttons. // //nTBThickness = rcNew.bottom - rcNew.top + g_cxEdge; } if ((ptb->ci.style & TBSTYLE_WRAPABLE) || (ptb->dwStyleEx & (TBSTYLE_EX_MULTICOLUMN | TBSTYLE_EX_HIDECLIPPEDBUTTONS))) { TBInvalidateItemRects(ptb); } GetWindowRect(ptb->ci.hwnd, &rc); MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rc, 2); NewSize(ptb->ci.hwnd, nTBThickness, ptb->ci.style, rc.left, rc.top, rc.right, rc.bottom); } void TBSetStyle(PTBSTATE ptb, DWORD dwStyle) { BOOL fSizeChanged = FALSE; if ((BOOL)(ptb->ci.style & TBSTYLE_WRAPABLE) != (BOOL)(dwStyle & TBSTYLE_WRAPABLE)) { int i; fSizeChanged = TRUE; for (i=0; iiNumButtons; i++) ptb->Buttons[i].fsState &= ~TBSTATE_WRAP; } ptb->ci.style = dwStyle; if (fSizeChanged) TBRecalc(ptb); TBAutoSize(ptb); TraceMsg(TF_TOOLBAR, "toolbar window style changed %x", ptb->ci.style); } void TBSetStyleEx(PTBSTATE ptb, DWORD dwStyleEx, DWORD dwStyleMaskEx) { BOOL fSizeChanged = FALSE; if (dwStyleMaskEx) dwStyleEx = (ptb->dwStyleEx & ~dwStyleMaskEx) | (dwStyleEx & dwStyleMaskEx); // Second, we can validate a few of the bits: // Multicolumn should never be set w/o the vertical style... ASSERT((ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) || !(ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN)); // also can't be set with hide clipped buttons style (for now) ASSERT(!(ptb->dwStyleEx & TBSTYLE_EX_HIDECLIPPEDBUTTONS) || !(ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN)); // ...but just in case someone gets it wrong, we'll set the vertical // style and rip off the hide clipped buttons style if (dwStyleEx & TBSTYLE_EX_MULTICOLUMN) { dwStyleEx |= TBSTYLE_EX_VERTICAL; dwStyleEx &= ~TBSTYLE_EX_HIDECLIPPEDBUTTONS; } // Then, some things need to be tweaked when they change if ((ptb->dwStyleEx ^ dwStyleEx) & TBSTYLE_EX_MULTICOLUMN) { int i; // Clear all the wrap states if we're changing multicolumn styles for (i = 0; i < ptb->iNumButtons; i++) ptb->Buttons[i].fsState &= ~TBSTATE_WRAP; fSizeChanged = TRUE; } if ((ptb->dwStyleEx ^ dwStyleEx) & TBSTYLE_EX_MIXEDBUTTONS) { int i; for (i = 0; i < ptb->iNumButtons; i++) (ptb->Buttons[i]).cx = 0; fSizeChanged = TRUE; InvalidateRect(ptb->ci.hwnd, NULL, TRUE); } if ((ptb->dwStyleEx ^ dwStyleEx) & TBSTYLE_EX_HIDECLIPPEDBUTTONS) InvalidateRect(ptb->ci.hwnd, NULL, TRUE); ptb->dwStyleEx = dwStyleEx; if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) TBSetStyle(ptb, CCS_VERT); // vertical sep and insert mark orientation if (fSizeChanged) { TBRecalc(ptb); TBAutoSize(ptb); } TraceMsg(TF_TOOLBAR, "toolbar window extended style changed %x", ptb->dwStyleEx); } LRESULT TB_OnSetImage(PTBSTATE ptb, LPTBBUTTONDATA ptbButton, int iImage) { if (!ptb->fHimlNative) { if (ptb->fHimlValid) { if (!TBGetImageList(ptb, HIML_NORMAL, 0) || iImage >= ImageList_GetImageCount(TBGetImageList(ptb, HIML_NORMAL, 0))) { return FALSE; } } else { PTBBMINFO pTemp; int nBitmap; UINT nTot; // we're not natively himl and we've got some invalid // image state, so we need to count the bitmaps ourselvesa pTemp = ptb->pBitmaps; nTot = 0; for (nBitmap=0; nBitmap < ptb->nBitmaps; nBitmap++) { nTot += pTemp->nButtons; pTemp++; } if (iImage >= (int)nTot) return FALSE; } } ptbButton->iBitmap = iImage; InvalidateButton(ptb, ptbButton, IsUsingCleartype()); UpdateWindow(ptb->ci.hwnd); return TRUE; } void TB_OnDestroy(PTBSTATE ptb) { HWND hwnd = ptb->ci.hwnd; int i; for (i = 0; i < ptb->iNumButtons; i++) { if (TBISSTRINGPTR(ptb->Buttons[i].iString)) Str_Set((LPTSTR*)&ptb->Buttons[i].iString, NULL); } // // If the toolbar created tooltips, then destroy them. // if ((ptb->ci.style & TBSTYLE_TOOLTIPS) && IsWindow(ptb->hwndToolTips)) { DestroyWindow (ptb->hwndToolTips); ptb->hwndToolTips = NULL; } if (ptb->hDragProxy) DestroyDragProxy(ptb->hDragProxy); if (ptb->hbmMono) DeleteObject(ptb->hbmMono); ReleaseMonoDC(ptb); if (ptb->nStrings > 0) DestroyStrings(ptb); if (ptb->hfontIcon && ptb->fFontCreated) DeleteObject(ptb->hfontIcon); // only do this destroy if pBitmaps exists.. // this is our signal that it was from an old style toolba // and we created it ourselves. if (ptb->pBitmaps) ImageList_Destroy(TBGetImageList(ptb, HIML_NORMAL, 0)); if (ptb->pBitmaps) LocalFree(ptb->pBitmaps); // couldn't have created tb if pimgs creation failed CCLocalReAlloc(ptb->pimgs, 0); Str_Set(&ptb->pszTip, NULL); if (ptb->hTheme) CloseThemeData(ptb->hTheme); if (ptb->Buttons) LocalFree(ptb->Buttons); LocalFree((HLOCAL)ptb); SetWindowInt(hwnd, 0, 0); TerminateDitherBrush(); } void TB_OnSetState(PTBSTATE ptb, LPTBBUTTONDATA ptbButton, BYTE bState, int iPos) { BYTE fsState; fsState = bState ^ ptbButton->fsState; ptbButton->fsState = bState; if (fsState) { if (ptb->fRedrawOff) { ptb->fInvalidate = ptb->fRecalc = TRUE; } else { if (fsState & TBSTATE_HIDDEN) { InvalidateRect(ptb->ci.hwnd, NULL, TRUE); TBRecalc(ptb); } else InvalidateButton(ptb, ptbButton, TRUE); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, ptb->ci.hwnd, OBJID_CLIENT, iPos+1); } } } void TB_OnSetCmdID(PTBSTATE ptb, LPTBBUTTONDATA ptbButton, UINT idCommand) { UINT uiOldID; uiOldID = ptbButton->idCommand; ptbButton->idCommand = idCommand; // // If the app was using tooltips, then // we need to update the command id there also. // if(ptb->hwndToolTips) { TOOLINFO ti; // // Query the old information // ti.cbSize = sizeof(ti); ti.hwnd = ptb->ci.hwnd; ti.uId = uiOldID; SendMessage(ptb->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)(LPTOOLINFO)&ti); // // Delete the old tool since we can't just // change the command id. // SendMessage(ptb->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); // // Add the new tool with the new command id. // ti.uId = idCommand; SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } } LRESULT TB_OnSetButtonInfo(PTBSTATE ptb, int idBtn, LPTBBUTTONINFO ptbbi) { int iPos; BOOL fInvalidateAll = FALSE; if (ptbbi->cbSize != SIZEOF(TBBUTTONINFO)) return 0; if (ptbbi->dwMask & TBIF_BYINDEX) iPos = idBtn; else iPos = PositionFromID(ptb, idBtn); if (iPos != -1) { LPTBBUTTONDATA ptbButton; BOOL fInvalidate = FALSE; ptbButton = ptb->Buttons + iPos; if (ptbbi->dwMask & TBIF_STYLE) { if ((ptbButton->fsStyle ^ ptbbi->fsStyle) & (BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN)) { // Width may have changed! fInvalidateAll = TRUE; } if ((ptbButton->fsStyle ^ ptbbi->fsStyle) & BTNS_AUTOSIZE) ptbButton->cx = 0; ptbButton->fsStyle = ptbbi->fsStyle; fInvalidate = TRUE; } if (ptbbi->dwMask & TBIF_STATE) { TB_OnSetState(ptb, ptbButton, ptbbi->fsState, iPos); } if (ptbbi->dwMask & TBIF_IMAGE) { TB_OnSetImage(ptb, ptbButton, ptbbi->iImage); } if (ptbbi->dwMask & TBIF_SIZE) { ptbButton->cx = ptbbi->cx; fInvalidate = TRUE; fInvalidateAll = TRUE; } if (ptbbi->dwMask & TBIF_TEXT) { // changing the text on an autosize button means recalc if (BTN_IS_AUTOSIZE(ptb, ptbButton)) { fInvalidateAll = TRUE; ptbButton->cx = (WORD)0; } ptb->fNoStringPool = TRUE; if (!TBISSTRINGPTR(ptbButton->iString)) { ptbButton->iString = 0; } Str_Set((LPTSTR*)&ptbButton->iString, ptbbi->pszText); fInvalidate = TRUE; } if (ptbbi->dwMask & TBIF_LPARAM) { ptbButton->dwData = ptbbi->lParam; } if (ptbbi->dwMask & TBIF_COMMAND) { TB_OnSetCmdID(ptb, ptbButton, ptbbi->idCommand); } if (fInvalidateAll || fInvalidate) { TBInvalidateItemRects(ptb); if (fInvalidateAll) InvalidateRect(ptb->ci.hwnd, NULL, TRUE); else InvalidateButton(ptb, ptbButton, TRUE); } return TRUE; } return FALSE; } LRESULT TB_OnGetButtonInfo(PTBSTATE ptb, int idBtn, LPTBBUTTONINFO ptbbi) { int iPos; if (ptbbi->cbSize != SIZEOF(TBBUTTONINFO)) return -1; if (ptbbi->dwMask & TBIF_BYINDEX) iPos = idBtn; else iPos = PositionFromID(ptb, idBtn); if (iPos >= 0 && iPos < ptb->iNumButtons) { LPTBBUTTONDATA ptbButton; ptbButton = ptb->Buttons + iPos; if (ptbbi->dwMask & TBIF_STYLE) { ptbbi->fsStyle = ptbButton->fsStyle; } if (ptbbi->dwMask & TBIF_STATE) { ptbbi->fsState = ptbButton->fsState; } if (ptbbi->dwMask & TBIF_IMAGE) { ptbbi->iImage = ptbButton->iBitmap; } if (ptbbi->dwMask & TBIF_SIZE) { ptbbi->cx = (WORD) ptbButton->cx; } if (ptbbi->dwMask & TBIF_TEXT) { LPTSTR psz = TB_StrForButton(ptb, ptbButton); if (psz) { lstrcpyn(ptbbi->pszText, psz, ptbbi->cchText); } } if (ptbbi->dwMask & TBIF_LPARAM) { ptbbi->lParam = ptbButton->dwData; } if (ptbbi->dwMask & TBIF_COMMAND) { ptbbi->idCommand = ptbButton->idCommand; } } else iPos = -1; return iPos; } UINT GetAccelerator(LPTSTR psz) { UINT ch = (UINT)-1; LPTSTR pszAccel = psz; // then prefixes are allowed.... see if it has one do { pszAccel = StrChr(pszAccel, CH_PREFIX); if (pszAccel) { pszAccel = FastCharNext(pszAccel); // handle having && if (*pszAccel != CH_PREFIX) ch = *pszAccel; else pszAccel = FastCharNext(pszAccel); } } while (pszAccel && (ch == (UINT)-1)); return ch; } UINT TBButtonAccelerator(PTBSTATE ptb, LPTBBUTTONDATA ptbn) { UINT ch = (UINT)-1; LPTSTR psz = TB_StrForButton(ptb, ptbn); if (psz && *psz) { if (!(ptb->uDrawTextMask & ptb->uDrawText & DT_NOPREFIX)) { ch = GetAccelerator(psz); } if (ch == (UINT)-1) { // no prefix found. use the first char ch = (UINT)*psz; } } return (UINT)ch; } /*---------------------------------------------------------- Purpose: Returns the number of buttons that have the passed in char as their accelerator */ int TBHasAccelerator(PTBSTATE ptb, UINT ch) { int i; int c = 0; for (i = 0; i < ptb->iNumButtons; i++) { if (!ChrCmpI((WORD)TBButtonAccelerator(ptb, &ptb->Buttons[i]), (WORD)ch)) c++; } if (c == 0) { NMCHAR nm = {0}; nm.ch = ch; nm.dwItemPrev = 0; nm.dwItemNext = -1; // The duplicate accelerator is used to expand or execute a menu item, // if we determine that there are no items, we still want to ask the // owner if there are any... if (CCSendNotify(&ptb->ci, TBN_MAPACCELERATOR, &nm.hdr) && nm.dwItemNext != -1) { c++; } } return c; } /*---------------------------------------------------------- Purpose: Returns TRUE if the character maps to more than one button. */ BOOL TBHasDupChar(PTBSTATE ptb, UINT ch) { BOOL bRet = FALSE; NMTBDUPACCELERATOR nmda; int c = 0; nmda.ch = ch; if (CCSendNotify(&ptb->ci, TBN_DUPACCELERATOR, &nmda.hdr)) { bRet = nmda.fDup; } else { if (TBHasAccelerator(ptb, ch) > 1) bRet = TRUE; } return bRet; } /*---------------------------------------------------------- Purpose: Returns the index of the item whose accelerator matches the given character. Starts at the current hot item. Returns: -1 if nothing found */ int TBItemFromAccelerator(PTBSTATE ptb, UINT ch, BOOL * pbDup) { int iRet = -1; int i; int iStart = ptb->iHot; NMTBWRAPACCELERATOR nmwa; NMCHAR nm = {0}; nm.ch = ch; nm.dwItemPrev = iStart; nm.dwItemNext = -1; // Ask the client if they want to handle this keyboard press if (CCSendNotify(&ptb->ci, TBN_MAPACCELERATOR, &nm.hdr) && (int)nm.dwItemNext > iStart && (int)nm.dwItemNext < ptb->iNumButtons) { // They handled it, so we're just going to return the position // that they said. iRet = nm.dwItemNext; } else for (i = 0; i < ptb->iNumButtons; i++) { if ( iStart + 1 >= ptb->iNumButtons ) { nmwa.ch = ch; if (CCSendNotify(&ptb->ci, TBN_WRAPACCELERATOR, &nmwa.hdr)) return nmwa.iButton; } iStart += 1 + ptb->iNumButtons; iStart %= ptb->iNumButtons; if ((ptb->Buttons[iStart].fsState & TBSTATE_ENABLED) && !ChrCmpI((WORD)TBButtonAccelerator(ptb, &ptb->Buttons[iStart]), (WORD)ch)) { iRet = iStart; break; } } *pbDup = TBHasDupChar(ptb, ch); return iRet; } BOOL TBOnChar(PTBSTATE ptb, UINT ch) { NMCHAR nm = {0}; BOOL bDupChar; int iPos = TBItemFromAccelerator(ptb, ch, &bDupChar); BOOL fHandled = FALSE; // Send the notification. Parent may want to change the next button. nm.ch = ch; nm.dwItemPrev = (0 <= ptb->iHot) ? ptb->Buttons[ptb->iHot].idCommand : -1; nm.dwItemNext = (0 <= iPos) ? ptb->Buttons[iPos].idCommand : -1; if (CCSendNotify(&ptb->ci, NM_CHAR, (LPNMHDR)&nm)) return TRUE; iPos = PositionFromID(ptb, nm.dwItemNext); if (-1 != iPos) { DWORD dwFlags = HICF_ACCELERATOR; if (ptb->iHot == iPos) dwFlags |= HICF_RESELECT; if (bDupChar) dwFlags |= HICF_DUPACCEL; TBSetHotItem(ptb, iPos, dwFlags); if (bDupChar) iPos = -1; fHandled = TRUE; } else { // handle this here instead of VK_KEYDOWN // because a typical thing to do is to pop down a menu // which will beep when it gets the WM_CHAR resulting from // the VK_KEYDOWN switch (ch) { case ' ': case 13: if (ptb->iHot != -1) { LPTBBUTTONDATA ptbButton = &ptb->Buttons[ptb->iHot]; if (TB_IsDropDown(ptbButton) && !TB_HasSplitDDArrow(ptb, ptbButton)) { iPos = ptb->iHot; fHandled = TRUE; } break; } } } if (-1 != iPos) { LPTBBUTTONDATA ptbButton = &ptb->Buttons[iPos]; if (TB_IsDropDown(ptbButton)) TBToggleDropDown(ptb, iPos, FALSE); } //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); return fHandled; } BOOL TBOnMapAccelerator(PTBSTATE ptb, UINT ch, UINT * pidCmd) { int iPos; BOOL bDupChar; ASSERT(IS_VALID_WRITE_PTR(pidCmd, UINT)); iPos = TBItemFromAccelerator(ptb, ch, &bDupChar); if (-1 != iPos) { *pidCmd = ptb->Buttons[iPos].idCommand; return TRUE; } return FALSE; } void TBSendUpClick(PTBSTATE ptb, int iPos, LPARAM lParam) { NMCLICK nm = { 0 }; if ((iPos >= 0) && (iPos < ptb->iNumButtons)) { nm.dwItemSpec = ptb->Buttons[iPos].idCommand; nm.dwItemData = ptb->Buttons[iPos].dwData; } else { nm.dwItemSpec = (UINT_PTR) -1; } LPARAM_TO_POINT(lParam, nm.pt); CCSendNotify(&ptb->ci, NM_CLICK, (LPNMHDR )&nm); } BOOL TBOnKey(PTBSTATE ptb, int nVirtKey, UINT uFlags) { NMKEY nm; TB_CancelTipTrack(ptb); // Send the notification nm.nVKey = nVirtKey; nm.uFlags = uFlags; if (CCSendNotify(&ptb->ci, NM_KEYDOWN, &nm.hdr)) return TRUE; // Swap the left and right arrow key if the control is mirrored. nVirtKey = RTLSwapLeftRightArrows(&ptb->ci, nVirtKey); if (ptb->iHot != -1 && TB_IsDropDown(&ptb->Buttons[ptb->iHot])) { // if we're on a dropdown button and you hit the up/down arrow (left/rigth in vert mode) // then drop the button down. // escape undrops it if it's dropped switch (nVirtKey) { case VK_RIGHT: case VK_LEFT: if (!(ptb->ci.style & CCS_VERT)) break; goto DropDown; case VK_DOWN: case VK_UP: if ((ptb->ci.style & CCS_VERT) || (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL)) break; goto DropDown; case VK_ESCAPE: if (ptb->iHot != ptb->iPressedDD) break; DropDown: TBToggleDropDown(ptb, ptb->iHot, FALSE); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); return TRUE; } } switch (nVirtKey) { case VK_RIGHT: case VK_DOWN: TBCycleHotItem(ptb, ptb->iHot, 1, HICF_ARROWKEYS); break; case VK_LEFT: case VK_UP: TBCycleHotItem(ptb, ptb->iHot, -1, HICF_ARROWKEYS); break; case VK_SPACE: case VK_RETURN: if (ptb->iHot != -1) { FORWARD_WM_COMMAND(ptb->ci.hwndParent, ptb->Buttons[ptb->iHot].idCommand, ptb->ci.hwnd, BN_CLICKED, SendMessage); } break; default: return FALSE; } //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS | UISF_HIDEACCEL); return TRUE; } LRESULT TB_OnSetButtonInfoA(PTBSTATE ptb, int idBtn, LPTBBUTTONINFOA ptbbiA) { TBBUTTONINFO tbbi = *(LPTBBUTTONINFO)ptbbiA; WCHAR szText[256]; if ((ptbbiA->dwMask & TBIF_TEXT) && ptbbiA->pszText) { tbbi.pszText = szText; tbbi.cchText = ARRAYSIZE(szText); MultiByteToWideChar(CP_ACP, 0, (LPCSTR) ptbbiA->pszText, -1, szText, ARRAYSIZE(szText)); } return TB_OnSetButtonInfo(ptb, idBtn, (LPTBBUTTONINFO)&tbbi); } LRESULT TB_OnGetButtonInfoA(PTBSTATE ptb, int idBtn, LPTBBUTTONINFOA ptbbiA) { LPTBBUTTONDATA ptbButton; int iPos; DWORD dwMask = ptbbiA->dwMask; ptbbiA->dwMask &= ~TBIF_TEXT; iPos = (int) TB_OnGetButtonInfo(ptb, idBtn, (LPTBBUTTONINFO)ptbbiA); if (iPos != -1) { ptbButton = ptb->Buttons + iPos; ptbbiA->dwMask = dwMask; if (ptbbiA->dwMask & TBIF_TEXT) { if (TBISSTRINGPTR(ptbButton->iString)) { WideCharToMultiByte (CP_ACP, 0, (LPCTSTR)ptbButton->iString, -1, ptbbiA->pszText , ptbbiA->cchText, NULL, NULL); } else { ptbbiA->pszText[0] = 0; } } } return iPos; } void TBOnMouseMove(PTBSTATE ptb, HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { // Only cancel tip track if cursor really did move if (ptb->lLastMMove != lParam) TB_CancelTipTrack(ptb); ptb->lLastMMove = lParam; if (ptb->fActive) { BOOL fSameButton; BOOL fDragOut = FALSE; int iPos; // do drag notifies/drawing first if (ptb->pCaptureButton != NULL) { if (hwnd != GetCapture()) { //DebugMsg(DM_TRACE, TEXT("capture isn't us")); SendItemNotify(ptb, ptb->pCaptureButton->idCommand, TBN_ENDDRAG); // Revalidate after calling out if (!IsWindow(hwnd)) return; // if the button is still pressed, unpress it. if (EVAL(ptb->pCaptureButton) && (ptb->pCaptureButton->fsState & TBSTATE_PRESSED)) SendMessage(hwnd, TB_PRESSBUTTON, ptb->pCaptureButton->idCommand, 0L); ptb->pCaptureButton = NULL; ptb->fRightDrag = FALSE; // just in case we were right dragging } else { //DebugMsg(DM_TRACE, TEXT("capture IS us, and state is enabled")); iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); fSameButton = (iPos >= 0 && ptb->pCaptureButton == ptb->Buttons + iPos); // notify on first drag out if (!fSameButton && !ptb->fDragOutNotify) { ptb->fDragOutNotify = TRUE; fDragOut = (BOOL)SendItemNotify(ptb, ptb->pCaptureButton->idCommand, TBN_DRAGOUT); // Revalidate after calling out if (!IsWindow(hwnd)) return; } // Check for ptb->pCaptureButton in case it was somehow nuked // in TBN_DRAGOUT. // This happens in the case when dragging an item out of start menu. When the // notify TBN_DRAGOUT is received, they go into a modal drag drop loop. Before // This loop finishes, the file is moved, causing a shell change notify to nuke // the button, which invalidates pCatpure button. So I'm getting rid of the // eval (lamadio) 4.14.98 if (ptb->pCaptureButton && (ptb->pCaptureButton->fsState & TBSTATE_ENABLED) && (fSameButton == !(ptb->pCaptureButton->fsState & TBSTATE_PRESSED)) && !ptb->fRightDrag) { //DebugMsg(DM_TRACE, TEXT("capture IS us, and Button is different")); ptb->pCaptureButton->fsState ^= TBSTATE_PRESSED; InvalidateButton(ptb, ptb->pCaptureButton, TRUE); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, (LONG)(ptb->pCaptureButton - ptb->Buttons) + 1); // Cast is ok because this is just an index } } } if (!fDragOut) { TBRelayToToolTips(ptb, wMsg, wParam, lParam); // Yes; set the hot item iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if ((ptb->ci.style & TBSTYLE_FLAT) || (ptb->hTheme)) TBSetHotItem(ptb, iPos, HICF_MOUSE); // Track mouse events now? if (!ptb->fMouseTrack && !ptb->fAnchorHighlight) { // Yes TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; ptb->fMouseTrack = TRUE; TrackMouseEvent(&tme); } } } } void TBHandleLButtonDown(PTBSTATE ptb, LPARAM lParam, int iPos) { LPTBBUTTONDATA ptbButton; HWND hwnd = ptb->ci.hwnd; if (iPos >= 0 && iPos < ptb->iNumButtons) { POINT pt; RECT rcDropDown; LPARAM_TO_POINT(lParam, pt); // should this check for the size of the button struct? ptbButton = ptb->Buttons + iPos; if (TB_IsDropDown(ptbButton)) TB_GetItemDropDownRect(ptb, iPos, &rcDropDown); if (TB_IsDropDown(ptbButton) && (!TB_HasSplitDDArrow(ptb, ptbButton) || PtInRect(&rcDropDown, pt))) { // Was the dropdown handled? if (!TBToggleDropDown(ptb, iPos, TRUE)) { // No; consider it a drag-out ptb->pCaptureButton = ptbButton; SetCapture(hwnd); ptb->fDragOutNotify = FALSE; SendItemNotify(ptb, ptb->pCaptureButton->idCommand, TBN_BEGINDRAG); GetMessagePosClient(ptb->ci.hwnd, &ptb->ptCapture); } } else { ptb->pCaptureButton = ptbButton; SetCapture(hwnd); if (ptbButton->fsState & TBSTATE_ENABLED) { ptbButton->fsState |= TBSTATE_PRESSED; InvalidateButton(ptb, ptbButton, TRUE); UpdateWindow(hwnd); // immediate feedback NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, iPos+1); } ptb->fDragOutNotify = FALSE; // pCaptureButton may have changed if (ptb->pCaptureButton) SendItemNotify(ptb, ptb->pCaptureButton->idCommand, TBN_BEGINDRAG); GetMessagePosClient(ptb->ci.hwnd, &ptb->ptCapture); } } } void TBOnLButtonDown(PTBSTATE ptb, HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { int iPos; NMCLICK nm = {0}; ptb->fRequeryCapture = FALSE; TBRelayToToolTips(ptb, wMsg, wParam, lParam); iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if ((ptb->ci.style & CCS_ADJUSTABLE) && (((wParam & MK_SHIFT) && !(ptb->ci.style & TBSTYLE_ALTDRAG)) || ((GetKeyState(VK_MENU) & ~1) && (ptb->ci.style & TBSTYLE_ALTDRAG)))) { MoveButton(ptb, iPos); } else { TBHandleLButtonDown(ptb, lParam, iPos); } if ((iPos >= 0) && (iPos < ptb->iNumButtons)) { nm.dwItemSpec = ptb->Buttons[iPos].idCommand; nm.dwItemData = ptb->Buttons[iPos].dwData; } else nm.dwItemSpec = (UINT_PTR) -1; LPARAM_TO_POINT(lParam, nm.pt); CCSendNotify(&ptb->ci, NM_LDOWN, (LPNMHDR )&nm); } void TBOnLButtonUp(PTBSTATE ptb, HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { int iPos = -1; TBRelayToToolTips(ptb, wMsg, wParam, lParam); if (lParam != (LPARAM)-1) iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (ptb->fRequeryCapture && iPos >= 0) { // hack for broderbund (and potentially mand mfc apps. // on button down, they delete the pressed button and insert another one right underneat that // has pretty much the same characteristics. // on win95, we allowed pCaptureButton to temporarily point to garbage. // now we validate against it. // we detect this case on delete now and if the creation size (uStructSize == old 0x14 size) // we reget the capture button here ptb->pCaptureButton = &ptb->Buttons[iPos]; } if (ptb->pCaptureButton != NULL) { int idCommand = ptb->pCaptureButton->idCommand; if (!CCReleaseCapture(&ptb->ci)) return; SendItemNotify(ptb, idCommand, TBN_ENDDRAG); if (!IsWindow(hwnd)) return; if (ptb->pCaptureButton && (ptb->pCaptureButton->fsState & TBSTATE_ENABLED) && iPos >=0 && (ptb->pCaptureButton == ptb->Buttons+iPos)) { ptb->pCaptureButton->fsState &= ~TBSTATE_PRESSED; if (ptb->pCaptureButton->fsStyle & BTNS_CHECK) { if (ptb->pCaptureButton->fsStyle & BTNS_GROUP) { // group buttons already checked can't be force // up by the user. if (ptb->pCaptureButton->fsState & TBSTATE_CHECKED) { ptb->pCaptureButton = NULL; return; // bail! } ptb->pCaptureButton->fsState |= TBSTATE_CHECKED; MakeGroupConsistant(ptb, idCommand); } else { ptb->pCaptureButton->fsState ^= TBSTATE_CHECKED; // toggle } } InvalidateButton(ptb, ptb->pCaptureButton, TRUE); ptb->pCaptureButton = NULL; NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, iPos+1); FORWARD_WM_COMMAND(ptb->ci.hwndParent, idCommand, hwnd, BN_CLICKED, SendMessage); // do not dereference ptb... it might have been destroyed on the WM_COMMAND. // if the window has been destroyed, bail out. if (!IsWindow(hwnd)) return; TBSendUpClick(ptb, iPos, lParam); } else { ptb->pCaptureButton = NULL; } } else { TBSendUpClick(ptb, iPos, lParam); } } BOOL CALLBACK GetUpdateRectEnumProc(HWND hwnd, LPARAM lParam) { PTBSTATE ptb = (PTBSTATE)lParam; if (IsWindowVisible(hwnd)) { RECT rcInvalid; if (GetUpdateRect(hwnd, &rcInvalid, FALSE)) { RECT rcNew; MapWindowPoints(hwnd, ptb->ci.hwnd, (LPPOINT)&rcInvalid, 2); UnionRect(&rcNew, &rcInvalid, &ptb->rcInvalid); ptb->rcInvalid = rcNew; } } return TRUE; } void TB_OnSize(PTBSTATE ptb, int nWidth, int nHeight) { BOOL fResizeH, fResizeV; fResizeH = (nWidth != RECTWIDTH(ptb->rc)); fResizeV = (nHeight != RECTHEIGHT(ptb->rc)); if (ptb->dwStyleEx & TBSTYLE_EX_DOUBLEBUFFER) { RECT rcClient, rcNonclient; GetWindowRect(ptb->ci.hwnd, &rcNonclient); GetClientRect(ptb->ci.hwnd, &rcClient); if (!(IsRectEmpty(&rcNonclient) || IsRectEmpty(&rcClient)) && (RECTWIDTH(rcNonclient) != RECTWIDTH(rcClient) || RECTHEIGHT(rcNonclient) != RECTHEIGHT(rcClient)) ) { // Turn off double buffering if the client and non-client aren't the same. This is // a hack for MFC apps who expect the toolbar to paint it's non-client area in the // WM_ERASEBKGND handler, which we can't if we are double buffered ptb->dwStyleEx &= ~TBSTYLE_EX_DOUBLEBUFFER; } } if (ptb->dwStyleEx & TBSTYLE_EX_HIDECLIPPEDBUTTONS) { // figure out which buttons intersect the resized region // and invalidate the rects for those buttons // // +---------------+------+ // | | <--- rcResizeH // | | | // +---------------+------+ // | ^ | | // +---|-----------+------+ // rcResizeV int i; RECT rcResizeH, rcResizeV; SetRect(&rcResizeH, min(ptb->rc.right, nWidth), ptb->rc.top, max(ptb->rc.right, nWidth), min(ptb->rc.bottom, nHeight)); SetRect(&rcResizeV, ptb->rc.left, min(ptb->rc.bottom, nHeight), min(ptb->rc.right, nWidth), max(ptb->rc.bottom, nHeight)); for (i = 0; i < ptb->iNumButtons; i++) { RECT rcTemp, rcBtn; TB_GetItemRect(ptb, i, &rcBtn); if (IntersectRect(&rcTemp, &rcBtn, &rcResizeH) || IntersectRect(&rcTemp, &rcBtn, &rcResizeV)) { InvalidateRect(ptb->ci.hwnd, &rcBtn, TRUE); } } } if (ptb->hTheme) { MARGINS margin = {0}; RECT rc; GetThemeMargins(ptb->hTheme, NULL, 0, 0, TMT_SIZINGMARGINS, NULL, &margin); if (fResizeH) { SetRect(&rc, min(ptb->rc.right, nWidth) - margin.cxRightWidth, 0, nWidth, nHeight); InvalidateRect(ptb->ci.hwnd, &rc, TRUE); } if (fResizeV) { SetRect(&rc, 0, min(ptb->rc.bottom, nHeight) - margin.cyBottomHeight, nWidth, nHeight); InvalidateRect(ptb->ci.hwnd, &rc, TRUE); } } SetRect(&ptb->rc, 0, 0, nWidth, nHeight); } BOOL TB_TranslateAccelerator(HWND hwnd, LPMSG lpmsg) { if (!lpmsg) return FALSE; if (GetFocus() != hwnd) return FALSE; switch (lpmsg->message) { case WM_KEYUP: case WM_KEYDOWN: switch (lpmsg->wParam) { case VK_RIGHT: case VK_LEFT: case VK_UP: case VK_DOWN: case VK_ESCAPE: case VK_SPACE: case VK_RETURN: TranslateMessage(lpmsg); DispatchMessage(lpmsg); return TRUE; } break; case WM_CHAR: switch (lpmsg->wParam) { case VK_ESCAPE: case VK_SPACE: case VK_RETURN: TranslateMessage(lpmsg); DispatchMessage(lpmsg); return TRUE; } break; } return FALSE; } void TBInitMetrics(PTBSTATE ptb) { // init our g_clr's InitGlobalColors(); // get the size of a drop down arrow ptb->dxDDArrowChar = GetSystemMetrics(SM_CYMENUCHECK); } LRESULT TBGenerateDragImage(PTBSTATE ptb, SHDRAGIMAGE* pshdi) { HBITMAP hbmpOld = NULL; NMTBCUSTOMDRAW tbcd = { 0 }; HDC hdcDragImage; // Do we have a hot item? if (ptb->iHot == -1) return 0; // No? Return... hdcDragImage = CreateCompatibleDC(NULL); if (!hdcDragImage) return 0; // // Mirror the the DC, if the toolbar is mirrored. // if (ptb->ci.dwExStyle & RTL_MIRRORED_WINDOW) { SET_DC_RTL_MIRRORED(hdcDragImage); } tbcd.nmcd.hdc = hdcDragImage; ptb->ci.dwCustom = CICustomDrawNotify(&ptb->ci, CDDS_PREPAINT, &tbcd.nmcd); pshdi->sizeDragImage.cx = TBWidthOfButton(ptb, &ptb->Buttons[ptb->iHot], hdcDragImage); pshdi->sizeDragImage.cy = ptb->iButHeight; pshdi->hbmpDragImage = CreateBitmap( pshdi->sizeDragImage.cx, pshdi->sizeDragImage.cy, GetDeviceCaps(hdcDragImage, PLANES), GetDeviceCaps(hdcDragImage, BITSPIXEL), NULL); if (pshdi->hbmpDragImage) { DWORD dwStyle; RECT rc = {0, 0, pshdi->sizeDragImage.cx, pshdi->sizeDragImage.cy}; hbmpOld = (HBITMAP)SelectObject(hdcDragImage, pshdi->hbmpDragImage); pshdi->crColorKey = RGB(0xFF, 0x00, 0x55); FillRectClr(hdcDragImage, &rc, pshdi->crColorKey); // We want the button to be drawn transparent. This is a hack, because I // don't want to rewrite the draw code. Fake a transparent draw. dwStyle = ptb->ci.style; ptb->ci.style |= TBSTYLE_TRANSPARENT; ptb->fAntiAlias = FALSE; DrawButton(hdcDragImage, 0, 0, ptb, &ptb->Buttons[ptb->iHot], TRUE); ptb->fAntiAlias = TRUE; ptb->ci.style = dwStyle; TB_GetItemRect(ptb, ptb->iHot, &rc); if (PtInRect(&rc, ptb->ptCapture)) { if (ptb->ci.dwExStyle & RTL_MIRRORED_WINDOW) pshdi->ptOffset.x = rc.right - ptb->ptCapture.x; else pshdi->ptOffset.x = ptb->ptCapture.x - rc.left; pshdi->ptOffset.y = ptb->ptCapture.y - rc.top; } SelectObject(hdcDragImage, hbmpOld); DeleteDC(hdcDragImage); // We're passing back the created HBMP. return 1; } return 0; } void TB_OnTimer(PTBSTATE ptb, UINT id) { KillTimer(ptb->ci.hwnd, id); if (id == IDT_TRACKINGTIP) { // Display keyboard nav tracking tooltip popups if (TB_IsKbdTipTracking(ptb)) // Item requires tracking popup { TOOLINFO ti = {0}; ti.cbSize = sizeof(TOOLINFO); ti.hwnd = ptb->ci.hwnd; ti.uId = ptb->Buttons[ptb->iTracking].idCommand; // Cancel previous SendMessage(ptb->hwndToolTips, TTM_TRACKACTIVATE, FALSE, (LPARAM)&ti); // Switch ListView's tooltip window to "tracking" (manual) mode SendMessage(ptb->hwndToolTips, TTM_GETTOOLINFO, 0, (LPARAM)&ti); ti.uFlags |= TTF_TRACK; SendMessage(ptb->hwndToolTips, TTM_SETTOOLINFO, 0, (LPARAM)&ti); // Activate and establish size SendMessage(ptb->hwndToolTips, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); } } } BOOL TB_GetIdealSize(PTBSTATE ptb, LPSIZE psize, BOOL fCalcHeight) { if (psize) { NMPGCALCSIZE nm; nm.dwFlag = fCalcHeight ? PGF_CALCHEIGHT : PGF_CALCWIDTH; nm.iWidth = psize->cx; nm.iHeight = psize->cy; TB_OnCalcSize(ptb, (LPNMHDR)&nm); // Since both values may have changed, reset the out-param. psize->cy = nm.iHeight; psize->cx = nm.iWidth; return TRUE; } return FALSE; } LRESULT CALLBACK ToolbarWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LPTBBUTTONDATA ptbButton; int iPos; LRESULT dw; PTBSTATE ptb = (PTBSTATE)GetWindowPtr0(hwnd); // GetWindowPtr(hwnd, 0) if (uMsg == WM_NCCREATE) { LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; InitDitherBrush(); // create the state data for this toolbar ptb = (PTBSTATE)LocalAlloc(LPTR, sizeof(TBSTATE)); if (!ptb) return 0; // WM_NCCREATE failure is 0 // note, zero init memory from above CIInitialize(&ptb->ci, hwnd, lpcs); ptb->xFirstButton = s_xFirstButton; ptb->iHot = -1; ptb->iPressedDD = -1; ptb->iInsert = -1; ptb->clrim = CLR_DEFAULT; ptb->fAntiAlias = TRUE; // Anti Alias fonts by default. // initialize system metric-dependent stuff TBInitMetrics(ptb); // horizontal/vertical space taken up by button chisel, sides, // and a 1 pixel margin. used in GrowToolbar. ptb->cxPad = 7; ptb->cyPad = 6; ptb->fShowPrefix = TRUE; ptb->iListGap = LIST_GAP; ptb->iDropDownGap = DROPDOWN_GAP; ptb->clrsc.clrBtnHighlight = ptb->clrsc.clrBtnShadow = CLR_DEFAULT; ptb->hTheme = OpenThemeData(ptb->ci.hwnd, L"Toolbar"); ptb->iTracking = TBKTT_NOTRACK; ASSERT(ptb->uStructSize == 0); ASSERT(ptb->hfontIcon == NULL); // initialize to null. ASSERT(ptb->iButMinWidth == 0); ASSERT(ptb->iButMaxWidth == 0); ptb->nTextRows = 1; ptb->fActive = TRUE; // IE 3 passes in TBSTYLE_FLAT, but they really // wanted TBSTYLE_TRANSPARENT also. // if (ptb->ci.style & TBSTYLE_FLAT) { ptb->ci.style |= TBSTYLE_TRANSPARENT; } // Turn it on to reduce flicker. if (ptb->ci.style & TBSTYLE_TRANSPARENT) { ptb->fForcedDoubleBuffer = TRUE; } // Now Initialize the hfont we will use. TBChangeFont(ptb, 0, NULL); // grow the button size to the appropriate girth if (!SetBitmapSize(ptb, DEFAULTBITMAPX, DEFAULTBITMAPX)) { goto Failure; } SetWindowPtr(hwnd, 0, ptb); if (!(ptb->ci.style & (CCS_TOP | CCS_NOMOVEY | CCS_BOTTOM))) { ptb->ci.style |= CCS_TOP; SetWindowLong(hwnd, GWL_STYLE, ptb->ci.style); } return TRUE; Failure: if (ptb) { ASSERT(!ptb->Buttons); // App hasn't had a change to AddButtons yet LocalFree(ptb); } return FALSE; } if (!ptb) goto DoDefault; switch (uMsg) { case WM_CREATE: if (ptb->ci.style & TBSTYLE_REGISTERDROP) { ptb->hDragProxy = CreateDragProxy(ptb->ci.hwnd, ToolbarDragCallback, TRUE); } goto DoDefault; case WM_DESTROY: TB_OnDestroy(ptb); break; case WM_KEYDOWN: if (TBOnKey(ptb, (int) wParam, HIWORD(lParam))) break; goto DoDefault; case WM_UPDATEUISTATE: { if (CCOnUIState(&(ptb->ci), WM_UPDATEUISTATE, wParam, lParam)) { BOOL fSmooth = IsUsingCleartype(); // We erase background only if we are removing underscores or focus rect, // or if Font smooting is enabled InvalidateRect(hwnd, NULL, fSmooth || ((UIS_SET == LOWORD(wParam)) ? TRUE : FALSE)); } goto DoDefault; } case WM_GETDLGCODE: return (LRESULT) (DLGC_WANTARROWS | DLGC_WANTCHARS); case WM_SYSCHAR: case WM_CHAR: if (!TBOnChar(ptb, (UINT) wParam)) { // didn't handle it & client is >= v5 // forward to default handler goto DoDefault; } break; case WM_SETFOCUS: if (ptb->iHot == -1) { // set hot the first enabled button TBCycleHotItem(ptb, -1, 1, HICF_OTHER); } break; case WM_KILLFOCUS: TBSetHotItem(ptb, -1, HICF_OTHER); break; case WM_SETFONT: TBSetFont(ptb, (HFONT)wParam, (BOOL)lParam); return TRUE; case WM_NCCALCSIZE: // let defwindowproc handle the standard borders etc... dw = DefWindowProc(hwnd, uMsg, wParam, lParam ) ; // add the extra edge at the top of the toolbar to seperate from the menu bar if (!(ptb->ci.style & CCS_NODIVIDER)) { ((NCCALCSIZE_PARAMS *)lParam)->rgrc[0].top += g_cyEdge; } return dw; case WM_NCHITTEST: if (ptb->dwStyleEx & TBSTYLE_EX_TRANSPARENTDEADAREA) { POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ScreenToClient(hwnd, &pt); if (TBHitTest(ptb, pt.x, pt.y) < 0) { if (ptb->lLastMMove != lParam) TB_CancelTipTrack(ptb); ptb->lLastMMove = lParam; return HTTRANSPARENT; } } return HTCLIENT; case WM_NCACTIVATE: // only make sense to do this stuff if we're top level if ((BOOLIFY(ptb->fActive) != (BOOL)wParam && !GetParent(hwnd))) { int iButton; ptb->fActive = (BOOL) wParam; for (iButton = 0; iButton < ptb->iNumButtons; iButton++) { ptbButton = &ptb->Buttons[iButton]; InvalidateButton(ptb, ptbButton, FALSE); } } // fall through... case WM_NCPAINT: // old-style toolbars are forced to be without dividers above if (!(ptb->ci.style & CCS_NODIVIDER)) { RECT rc; HDC hdc = GetWindowDC(hwnd); GetWindowRect(hwnd, &rc); MapWindowRect(NULL, hwnd, &rc); // screen -> client rc.bottom = -rc.top; // bottom of NC area rc.top = rc.bottom - g_cyEdge; CCDrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_TOP | BF_BOTTOM, &(ptb->clrsc)); ReleaseDC(hwnd, hdc); } goto DoDefault; case WM_ENABLE: if (wParam) { ptb->ci.style &= ~WS_DISABLED; } else { ptb->ci.style |= WS_DISABLED; } InvalidateRect(hwnd, NULL, ptb->ci.style & TBSTYLE_TRANSPARENT); goto DoDefault; case WM_PRINTCLIENT: case WM_PAINT: if (ptb->fTTNeedsFlush) FlushToolTipsMgrNow(ptb); if (ptb->fRedrawOff) { if (!wParam) { HDC hdcPaint; PAINTSTRUCT ps; hdcPaint = BeginPaint(hwnd, &ps); EndPaint(hwnd, &ps); } // we got a paint region, so invalidate // when we get redraw back on... ptb->fInvalidate = TRUE; } else { TBPaint(ptb, (HDC)wParam); } break; case WM_SETREDRAW: { BOOL fRedrawOld = !ptb->fRedrawOff; if ( wParam && ptb->fRedrawOff ) { if ( ptb->fInvalidate ) { // If font smoothing is enabled, then we need to erase the background too. BOOL fSmooth = IsUsingCleartype(); // invalidate before turning back on ... RedrawWindow( hwnd, NULL, NULL, (fSmooth? RDW_ERASE: 0) | RDW_INVALIDATE ); ptb->fInvalidate = FALSE; } ptb->fRedrawOff = FALSE; if ( ptb->fRecalc ) { // recalc & autosize after turning back on TBRecalc(ptb); TBAutoSize(ptb); ptb->fRecalc = FALSE; } } else { ptb->fRedrawOff = !wParam; } return fRedrawOld; } break; case WM_ERASEBKGND: TB_OnEraseBkgnd(ptb, (HDC) wParam); return(TRUE); case WM_SYSCOLORCHANGE: TB_OnSysColorChange(ptb); if (ptb->hwndToolTips) SendMessage(ptb->hwndToolTips, uMsg, wParam, lParam); break; case WM_TIMER: TB_OnTimer(ptb, (UINT)wParam); break; case TB_GETROWS: return CountRows(ptb); break; case TB_GETPADDING: lParam = MAKELONG(-1, -1); // fall through case TB_SETPADDING: { LRESULT lres = MAKELONG(ptb->cxPad, ptb->cyPad); int xPad = GET_X_LPARAM(lParam); int yPad = GET_Y_LPARAM(lParam); if (xPad != -1) ptb->cxPad = xPad; if (yPad != -1) ptb->cyPad = yPad; return lres; } case TB_GETMETRICS: { LPTBMETRICS ptbm = (LPTBMETRICS)lParam; if (ptbm && (ptbm->cbSize == sizeof(TBMETRICS))) { if (ptbm->dwMask & TBMF_PAD) { ptbm->cxPad = ptb->cxPad; ptbm->cyPad = ptb->cyPad; } if (ptbm->dwMask & TBMF_BARPAD) { ptbm->cxBarPad = ptb->cxBarPad; ptbm->cyBarPad = ptb->cyBarPad; } if (ptbm->dwMask & TBMF_BUTTONSPACING) { ptbm->cxButtonSpacing = ptb->cxButtonSpacing; ptbm->cyButtonSpacing = ptb->cyButtonSpacing; } } } break; case TB_SETMETRICS: { LPTBMETRICS ptbm = (LPTBMETRICS)lParam; if (ptbm && (ptbm->cbSize == sizeof(TBMETRICS))) { if (ptbm->dwMask & TBMF_PAD) { ptb->cxPad = ptbm->cxPad; ptb->cyPad = ptbm->cyPad; } if (ptbm->dwMask & TBMF_BARPAD) { ptb->cxBarPad = ptbm->cxBarPad; ptb->cyBarPad = ptbm->cyBarPad; } if (ptbm->dwMask & TBMF_BUTTONSPACING) { ptb->cxButtonSpacing = ptbm->cxButtonSpacing; ptb->cyButtonSpacing = ptbm->cyButtonSpacing; } } } break; case TB_SETROWS: { RECT rc; if (BoxIt(ptb, LOWORD(wParam), HIWORD(wParam), &rc)) { TBInvalidateItemRects(ptb); SetWindowPos(hwnd, NULL, 0, 0, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); InvalidateRect(hwnd, NULL, TRUE); } if (lParam) *((RECT *)lParam) = rc; } break; case WM_MOVE: // JJK TODO: This needs to be double buffered to get rid of the flicker if (ptb->ci.style & TBSTYLE_TRANSPARENT) InvalidateRect(hwnd, NULL, TRUE); goto DoDefault; case WM_SIZE: TB_OnSize(ptb, LOWORD(lParam), HIWORD(lParam)); // fall through case TB_AUTOSIZE: TBAutoSize(ptb); break; case WM_WINDOWPOSCHANGING: if ((ptb->ci.style & TBSTYLE_TRANSPARENT) || (ptb->hTheme)) { LPWINDOWPOS pwp = (LPWINDOWPOS)lParam; if (pwp) { pwp->flags |= SWP_NOCOPYBITS; } } break; case WM_COMMAND: case WM_DRAWITEM: case WM_MEASUREITEM: case WM_VKEYTOITEM: case WM_CHARTOITEM: SendMessage(ptb->ci.hwndParent, uMsg, wParam, lParam); break; case WM_RBUTTONDBLCLK: if (!CCSendNotify(&ptb->ci, NM_RDBLCLK, NULL)) goto DoDefault; break; case WM_RBUTTONUP: { NMCLICK nm = {0}; int iIndex; if (ptb->pCaptureButton != NULL) { if (!CCReleaseCapture(&ptb->ci)) break; ptb->pCaptureButton = NULL; ptb->fRightDrag = FALSE; } iIndex = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if ((iIndex >= 0) && (iIndex < ptb->iNumButtons)) { nm.dwItemSpec = ptb->Buttons[iIndex].idCommand; nm.dwItemData = ptb->Buttons[iIndex].dwData; } else nm.dwItemSpec = (UINT_PTR) -1; LPARAM_TO_POINT(lParam, nm.pt); if (!CCSendNotify(&ptb->ci, NM_RCLICK, (LPNMHDR )&nm)) goto DoDefault; } break; case WM_LBUTTONDBLCLK: iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (iPos < 0 && (ptb->ci.style & CCS_ADJUSTABLE)) { iPos = -1 - iPos; CustomizeTB(ptb, iPos); } else { TBHandleLButtonDown(ptb, lParam, iPos); } break; case WM_LBUTTONDOWN: TBOnLButtonDown(ptb, hwnd, uMsg, wParam, lParam); break; case WM_CAPTURECHANGED: // do this only for newer apps because some apps // do things like delete a button when you // mouse down and add it back in immediately. // also do it on a post because we call ReleaseCapture // internally and only want to catch this on external release PostMessage(hwnd, TBP_ONRELEASECAPTURE, 0, 0); break; case TBP_ONRELEASECAPTURE: if (ptb->pCaptureButton) { // abort current capture // simulate a lost capture mouse move. this will restore state TBOnMouseMove(ptb, hwnd, WM_MOUSEMOVE, 0, (LPARAM)-1); ptb->pCaptureButton = NULL; } break; case WM_RBUTTONDOWN: if (ptb->pCaptureButton) { // abort current capture if (hwnd == GetCapture()) { // we were left clicking. abort that now if (!CCReleaseCapture(&ptb->ci)) break; // simulate a lost capture mouse move. this will restore state TBOnMouseMove(ptb, hwnd, WM_MOUSEMOVE, 0, (LPARAM)-1); } } iPos = TBHitTest(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); // we need to check VK_RBUTTON because some apps subclass us to pick off rbuttondown to do their menu // (instead of up, or the notify, or wm_contextmenu) // then after it's done and the button is up, they then send us a button down if ((iPos >= 0) && (iPos < ptb->iNumButtons) && (GetAsyncKeyState(VK_RBUTTON) < 0)) { ptb->pCaptureButton = ptb->Buttons + iPos; ptb->fRightDrag = TRUE; SetCapture(hwnd); GetMessagePosClient(ptb->ci.hwnd, &ptb->ptCapture); SendItemNotify(ptb, ptb->pCaptureButton->idCommand, TBN_BEGINDRAG); if (!IsWindow(hwnd)) break; ptb->fDragOutNotify = FALSE; } break; case WM_MOUSELEAVE: { TRACKMOUSEEVENT tme; // Cancel the mouse event tracking tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_CANCEL | TME_LEAVE; tme.hwndTrack = hwnd; TrackMouseEvent(&tme); ptb->fMouseTrack = FALSE; TBSetHotItem(ptb, -1, HICF_MOUSE); } break; case WM_MOUSEMOVE: TBOnMouseMove(ptb, hwnd, uMsg, wParam, lParam); break; case WM_LBUTTONUP: TBOnLButtonUp(ptb, hwnd, uMsg, wParam, lParam); break; case WM_SETTINGCHANGE: InitGlobalMetrics(wParam); if (ptb->fFontCreated) TBChangeFont(ptb, wParam, NULL); if (ptb->hwndToolTips) SendMessage(ptb->hwndToolTips, uMsg, wParam, lParam); // recalc & redraw TBInitMetrics(ptb); TBRecalc(ptb); InvalidateRect(hwnd, NULL, TRUE); break; case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&ptb->ci, lParam); break; case WM_NOTIFY: #define lpNmhdr ((LPNMHDR)(lParam)) //The following statement traps all pager control notification messages. if((lpNmhdr->code <= PGN_FIRST) && (lpNmhdr->code >= PGN_LAST)) { return TB_OnPagerControlNotify(ptb, lpNmhdr); } { LRESULT lres = 0; if (lpNmhdr->code == TTN_NEEDTEXT) { int i = TB_IsKbdTipTracking(ptb) ? ptb->iTracking : PositionFromID(ptb, lpNmhdr->idFrom); BOOL fEllipsied = FALSE; LRESULT lres; LPTOOLTIPTEXT lpnmTT = ((LPTOOLTIPTEXT) lParam); if (i != -1) { // if infotip not supported, try for TTN_NEEDTEXT in client. if (!TBGetInfoTip(ptb, lpnmTT, &ptb->Buttons[i])) lres = SendNotifyEx(ptb->ci.hwndParent, (HWND) -1, lpNmhdr->code, lpNmhdr, ptb->ci.bUnicode); #define IsTextPtr(lpszText) (((lpszText) != LPSTR_TEXTCALLBACK) && (!IS_INTRESOURCE(lpszText))) fEllipsied = (BOOL)(ptb->Buttons[i].fsState & TBSTATE_ELLIPSES); // if we don't get a string from TTN_NEEDTEXT try to use the title text. if ((lpNmhdr->code == TTN_NEEDTEXT) && (BTN_NO_SHOW_TEXT(ptb, &ptb->Buttons[i]) || fEllipsied) && lpnmTT->lpszText && IsTextPtr(lpnmTT->lpszText) && !lpnmTT->lpszText[0]) { LPCTSTR psz = TB_StrForButton(ptb, &ptb->Buttons[i]); if (psz) lpnmTT->lpszText = (LPTSTR)psz; } } } else if (lpNmhdr->code == TTN_SHOW) { if (TB_IsKbdTipTracking(ptb)) // Size tip when keyboard tracking { RECT rcTT; RECT rcItem; POINT ptTT; POINT ptItem; MONITORINFO mi = {0}; mi.cbSize = sizeof(MONITORINFO); // Establish item screen position and size SendMessage(ptb->ci.hwnd, TB_GETITEMRECT, ptb->iTracking, (LPARAM)&rcItem); ptItem.x = rcItem.left; ptItem.y = rcItem.top; ClientToScreen(ptb->ci.hwnd, &ptItem); // Get tip rect GetWindowRect(ptb->hwndToolTips, &rcTT); // Init tooltip position ptTT.x = ptItem.x + RECTWIDTH(rcItem) - g_cxIconMargin; ptTT.y = ptItem.y + RECTHEIGHT(rcItem); // Get screen info where tooltip is being displayed GetMonitorInfo(MonitorFromPoint(ptTT, MONITOR_DEFAULTTONEAREST), &mi); // Update tooltip position if it runs off the screen if ((ptTT.x + RECTWIDTH(rcTT)) > mi.rcMonitor.right) ptTT.x = (ptItem.x + g_cxIconMargin) - RECTWIDTH(rcTT); if ((ptTT.y + RECTHEIGHT(rcTT)) > mi.rcMonitor.bottom) ptTT.y = ptItem.y - RECTHEIGHT(rcTT); SetWindowPos(ptb->hwndToolTips, NULL, ptTT.x, ptTT.y, 0, 0, SWP_NOSIZE|SWP_NOACTIVATE); return TRUE; } } else { // // We are just going to pass this on to the // real parent. Note that -1 is used as // the hwndFrom. This prevents SendNotifyEx // from updating the NMHDR structure. // lres = SendNotifyEx(ptb->ci.hwndParent, (HWND) -1, lpNmhdr->code, lpNmhdr, ptb->ci.bUnicode); } return(lres); } case WM_STYLECHANGING: if (wParam == GWL_STYLE) { LPSTYLESTRUCT lpStyle = (LPSTYLESTRUCT) lParam; // is MFC dorking with just our visibility bit? if ((lpStyle->styleOld ^ lpStyle->styleNew) == WS_VISIBLE) { if (lpStyle->styleNew & WS_VISIBLE) { BOOL fSmooth = IsUsingCleartype(); // MFC trying to make us visible, // convert it to WM_SETREDRAW instead. DefWindowProc(hwnd, WM_SETREDRAW, TRUE, 0); // Reinvalidate everything we lost when we // did the WM_SETREDRAW stuff. RedrawWindow(hwnd, &ptb->rcInvalid, NULL, (fSmooth? RDW_ERASE: 0) | RDW_INVALIDATE | RDW_ALLCHILDREN); ZeroMemory(&ptb->rcInvalid, SIZEOF(ptb->rcInvalid)); } else { // Save the invalid rectangle in ptb->rcInvalid since // WM_SETREDRAW will blow it away. ZeroMemory(&ptb->rcInvalid, SIZEOF(ptb->rcInvalid)); GetUpdateRect(ptb->ci.hwnd, &ptb->rcInvalid, FALSE); EnumChildWindows(ptb->ci.hwnd, GetUpdateRectEnumProc, (LPARAM)ptb); // MFC trying to make us invisible, // convert it to WM_SETREDRAW instead. DefWindowProc(hwnd, WM_SETREDRAW, FALSE, 0); } } } break; case WM_STYLECHANGED: if (wParam == GWL_STYLE) { TBSetStyle(ptb, ((LPSTYLESTRUCT)lParam)->styleNew); } else if (wParam == GWL_EXSTYLE) { // // If the RTL_MIRROR extended style bit had changed, let's // repaint the control window // if ((ptb->ci.dwExStyle&RTL_MIRRORED_WINDOW) != (((LPSTYLESTRUCT)lParam)->styleNew&RTL_MIRRORED_WINDOW)) TBAutoSize(ptb); // // Save the new ex-style bits // ptb->ci.dwExStyle = ((LPSTYLESTRUCT)lParam)->styleNew; } return 0; case TB_GETIDEALSIZE: return TB_GetIdealSize(ptb, (LPSIZE)lParam, (BOOL)wParam); case TB_SETSTYLE: TBSetStyle(ptb, (DWORD) lParam); break; case TB_GETSTYLE: return (ptb->ci.style); case TB_GETBUTTONSIZE: return (MAKELONG(ptb->iButWidth,ptb->iButHeight)); case TB_SETBUTTONWIDTH: if (ptb->iButMinWidth != LOWORD(lParam) || ptb->iButMaxWidth != HIWORD(lParam)) { ptb->iButMinWidth = LOWORD(lParam); ptb->iButMaxWidth = HIWORD(lParam); ptb->iButWidth = 0; TBRecalc(ptb); InvalidateRect(hwnd, NULL, TRUE); } return TRUE; case TB_TRANSLATEACCELERATOR: return TB_TranslateAccelerator(hwnd, (LPMSG)lParam); case TB_SETSTATE: iPos = PositionFromID(ptb, wParam); if (iPos < 0) return FALSE; ptbButton = ptb->Buttons + iPos; TB_OnSetState(ptb, ptbButton, (BYTE)(LOWORD(lParam)), iPos); TBInvalidateItemRects(ptb); return TRUE; // set the cmd ID of a button based on its position case TB_SETCMDID: if (wParam >= (UINT)ptb->iNumButtons) return FALSE; TB_OnSetCmdID(ptb, &ptb->Buttons[wParam], (UINT)lParam); return TRUE; case TB_GETSTATE: iPos = PositionFromID(ptb, wParam); if (iPos < 0) return -1L; return ptb->Buttons[iPos].fsState; case TB_MAPACCELERATORA: { char szAcl[2]; WCHAR wszAcl[2]; szAcl[0] = (BYTE)wParam; szAcl[1] = '\0'; MultiByteToWideChar(CP_ACP, 0, (LPCSTR)szAcl, ARRAYSIZE(szAcl), wszAcl, ARRAYSIZE(wszAcl)); // no need to check return we just take junk if MbtoWc has failed wParam = (WPARAM)wszAcl[0]; } // fall through... case TB_MAPACCELERATOR: return TBOnMapAccelerator(ptb, (UINT)wParam, (UINT *)lParam); case TB_ENABLEBUTTON: case TB_CHECKBUTTON: case TB_PRESSBUTTON: case TB_HIDEBUTTON: case TB_INDETERMINATE: case TB_MARKBUTTON: { BYTE fsState; iPos = PositionFromID(ptb, wParam); if (iPos < 0) return FALSE; ptbButton = &ptb->Buttons[iPos]; fsState = ptbButton->fsState; if (LOWORD(lParam)) ptbButton->fsState |= wStateMasks[uMsg - TB_ENABLEBUTTON]; else ptbButton->fsState &= ~wStateMasks[uMsg - TB_ENABLEBUTTON]; // did this actually change the state? if (fsState != ptbButton->fsState) { // is this button a member of a group? if ((uMsg == TB_CHECKBUTTON) && (ptbButton->fsStyle & BTNS_GROUP)) MakeGroupConsistant(ptb, (int)wParam); if (uMsg == TB_HIDEBUTTON) { InvalidateRect(hwnd, NULL, TRUE); TBInvalidateItemRects(ptb); } else InvalidateButton(ptb, ptbButton, TRUE); NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hwnd, OBJID_CLIENT, iPos+1); } return(TRUE); } case TB_ISBUTTONENABLED: case TB_ISBUTTONCHECKED: case TB_ISBUTTONPRESSED: case TB_ISBUTTONHIDDEN: case TB_ISBUTTONINDETERMINATE: case TB_ISBUTTONHIGHLIGHTED: iPos = PositionFromID(ptb, wParam); if (iPos < 0) return(-1L); return (LRESULT)ptb->Buttons[iPos].fsState & wStateMasks[uMsg - TB_ISBUTTONENABLED]; case TB_ADDBITMAP: case TB_ADDBITMAP32: // only for compatibility with mail { LPTBADDBITMAP pab = (LPTBADDBITMAP)lParam; return AddBitmap(ptb, (int) wParam, pab->hInst, pab->nID); } case TB_REPLACEBITMAP: return ReplaceBitmap(ptb, (LPTBREPLACEBITMAP)lParam); case TB_ADDSTRINGA: { LPWSTR lpStrings; UINT uiCount; LPSTR lpAnsiString = (LPSTR) lParam; int iResult; BOOL bAllocatedMem = FALSE; if (!wParam && !IS_INTRESOURCE(lpAnsiString)) { // // We have to figure out how many characters // are in this string. // uiCount = 0; while (TRUE) { uiCount++; if ((*lpAnsiString == 0) && (*(lpAnsiString+1) == 0)) { uiCount++; // needed for double null break; } lpAnsiString++; } lpStrings = (PTSTR)LocalAlloc(LPTR, uiCount * sizeof(TCHAR)); if (!lpStrings) return -1; bAllocatedMem = TRUE; MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lParam, uiCount, lpStrings, uiCount); } else { lpStrings = (LPWSTR)lParam; } iResult = TBAddStrings(ptb, wParam, (LPARAM)lpStrings); if (bAllocatedMem) LocalFree(lpStrings); return iResult; } case TB_ADDSTRING: return TBAddStrings(ptb, wParam, lParam); case TB_GETSTRING: return TBGetString(ptb, HIWORD(wParam), LOWORD(wParam), (LPTSTR)lParam); case TB_GETSTRINGA: return TBGetStringA(ptb, HIWORD(wParam), LOWORD(wParam), (LPSTR)lParam); case TB_ADDBUTTONSA: return TBInsertButtons(ptb, (UINT)-1, (UINT) wParam, (LPTBBUTTON)lParam, FALSE); case TB_INSERTBUTTONA: return TBInsertButtons(ptb, (UINT) wParam, 1, (LPTBBUTTON)lParam, FALSE); case TB_ADDBUTTONS: return TBInsertButtons(ptb, (UINT)-1, (UINT) wParam, (LPTBBUTTON)lParam, TRUE); case TB_INSERTBUTTON: return TBInsertButtons(ptb, (UINT) wParam, 1, (LPTBBUTTON)lParam, TRUE); case TB_DELETEBUTTON: return DeleteButton(ptb, (UINT) wParam); case TB_GETBUTTON: if (wParam >= (UINT)ptb->iNumButtons) return(FALSE); TBOutputStruct(ptb, ptb->Buttons + wParam, (LPTBBUTTON)lParam); return TRUE; case TB_SETANCHORHIGHLIGHT: BLOCK { BOOL bAnchor = BOOLIFY(ptb->fAnchorHighlight); ptb->fAnchorHighlight = BOOLFROMPTR(wParam); return bAnchor; } break; case TB_GETANCHORHIGHLIGHT: return BOOLIFY(ptb->fAnchorHighlight); case TB_HASACCELERATOR: ASSERT(IS_VALID_WRITE_PTR(lParam, int*)); *((int*)lParam) = TBHasAccelerator(ptb, (UINT)wParam); break; case TB_SETHOTITEM: lParam = HICF_OTHER; // Fall through case TB_SETHOTITEM2: BLOCK { int iPos = ptb->iHot; TBSetHotItem(ptb, (int)wParam, (DWORD)lParam); return iPos; } break; case TB_GETHOTITEM: return ptb->iHot; case TB_SETINSERTMARK: TBSetInsertMark(ptb, (LPTBINSERTMARK)lParam); break; case TB_GETINSERTMARK: { LPTBINSERTMARK ptbim = (LPTBINSERTMARK)lParam; ptbim->iButton = ptb->iInsert; ptbim->dwFlags = ptb->fInsertAfter ? TBIMHT_AFTER : 0; return TRUE; } case TB_SETINSERTMARKCOLOR: { LRESULT lres = (LRESULT)TB_GetInsertMarkColor(ptb); ptb->clrim = (COLORREF) lParam; return lres; } case TB_GETINSERTMARKCOLOR: return TB_GetInsertMarkColor(ptb); case TB_INSERTMARKHITTEST: return (LRESULT)TBInsertMarkHitTest(ptb, ((LPPOINT)wParam)->x, ((LPPOINT)wParam)->y, (LPTBINSERTMARK)lParam); case TB_MOVEBUTTON: return (LRESULT)TBMoveButton(ptb, (UINT)wParam, (UINT)lParam); case TB_GETMAXSIZE: return (LRESULT)TBGetMaxSize(ptb, (LPSIZE) lParam ); case TB_BUTTONCOUNT: return ptb->iNumButtons; case TB_COMMANDTOINDEX: return PositionFromID(ptb, wParam); case TB_SAVERESTOREA: { LPWSTR lpSubKeyW, lpValueNameW; TBSAVEPARAMSA * lpSaveA = (TBSAVEPARAMSA *) lParam; BOOL bResult; lpSubKeyW = ProduceWFromA (CP_ACP, lpSaveA->pszSubKey); lpValueNameW = ProduceWFromA (CP_ACP, lpSaveA->pszValueName); bResult = SaveRestoreFromReg(ptb, (BOOL) wParam, lpSaveA->hkr, lpSubKeyW, lpValueNameW); FreeProducedString(lpSubKeyW); FreeProducedString(lpValueNameW); return bResult; } case TB_SAVERESTORE: { TBSAVEPARAMS* psr = (TBSAVEPARAMS *)lParam; return SaveRestoreFromReg(ptb, (BOOL) wParam, psr->hkr, psr->pszSubKey, psr->pszValueName); } case TB_CUSTOMIZE: CustomizeTB(ptb, ptb->iNumButtons); break; case TB_GETRECT: // PositionFromID() accepts NULL ptbs! wParam = PositionFromID(ptb, wParam); // fall through case TB_GETITEMRECT: if (!lParam) break; return TB_GetItemRect(ptb, (UINT) wParam, (LPRECT)lParam); case TB_BUTTONSTRUCTSIZE: TBOnButtonStructSize(ptb, (UINT) wParam); break; case TB_SETBUTTONSIZE: return GrowToolbar(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); case TB_SETBITMAPSIZE: return SetBitmapSize(ptb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); case TB_SETIMAGELIST: { HIMAGELIST himl = (HIMAGELIST)lParam; HIMAGELIST himlOld = TBSetImageList(ptb, HIML_NORMAL, (int) wParam, himl); DWORD dwFlags = ImageList_GetFlags(himl); ptb->fHimlNative = TRUE; if (!ptb->uStructSize) { // let people get away without calling TB_BUTTONSTRUCTSIZE... no other control requires this ptb->uStructSize = sizeof(TBBUTTON); } // The bitmap size is based on the primary image list if (wParam == 0) { int cx = 0, cy = 0; if (himl) { // Update the bitmap size based on this image list CCGetIconSize(&ptb->ci, himl, &cx, &cy); } SetBitmapSize(ptb, cx, cy); } return (LRESULT)himlOld; } case TB_GETIMAGELIST: return (LRESULT)TBGetImageList(ptb, HIML_NORMAL, (int) wParam); case TB_GETIMAGELISTCOUNT: return ptb->cPimgs; case TB_SETHOTIMAGELIST: return (LRESULT)TBSetImageList(ptb, HIML_HOT, (int) wParam, (HIMAGELIST)lParam); case TB_GETHOTIMAGELIST: return (LRESULT)TBGetImageList(ptb, HIML_HOT, (int) wParam); case TB_GETDISABLEDIMAGELIST: return (LRESULT)TBGetImageList(ptb, HIML_DISABLED, (int) wParam); case TB_SETDISABLEDIMAGELIST: return (LRESULT)TBSetImageList(ptb, HIML_DISABLED, (int) wParam, (HIMAGELIST)lParam); case TB_GETOBJECT: if (IsEqualIID(*(IID *)wParam, IID_IDropTarget)) { // if we have not already registered create an unregistered target now if (ptb->hDragProxy == NULL) ptb->hDragProxy = CreateDragProxy(ptb->ci.hwnd, ToolbarDragCallback, FALSE); if (ptb->hDragProxy) return (LRESULT)GetDragProxyTarget(ptb->hDragProxy, (IDropTarget **)lParam); } return E_FAIL; case WM_GETFONT: return (LRESULT)(ptb? ptb->hfontIcon : 0); case TB_LOADIMAGES: return TBLoadImages(ptb, (UINT_PTR) wParam, (HINSTANCE)lParam); case TB_GETTOOLTIPS: TB_ForceCreateTooltips(ptb); return (LRESULT)ptb->hwndToolTips; case TB_SETTOOLTIPS: ptb->hwndToolTips = (HWND)wParam; break; case TB_SETPARENT: { HWND hwndOld = ptb->ci.hwndParent; ptb->ci.hwndParent = (HWND)wParam; return (LRESULT)hwndOld; } case TB_GETBUTTONINFOA: return TB_OnGetButtonInfoA(ptb, (int)wParam, (LPTBBUTTONINFOA)lParam); case TB_SETBUTTONINFOA: return TB_OnSetButtonInfoA(ptb, (int)wParam, (LPTBBUTTONINFOA)lParam); case TB_GETBUTTONINFO: return TB_OnGetButtonInfo(ptb, (int)wParam, (LPTBBUTTONINFO)lParam); case TB_SETBUTTONINFO: return TB_OnSetButtonInfo(ptb, (int)wParam, (LPTBBUTTONINFO)lParam); case TB_CHANGEBITMAP: iPos = PositionFromID(ptb, wParam); if (iPos < 0) return(FALSE); // // Check to see if the new bitmap ID is // valid. // ptbButton = &ptb->Buttons[iPos]; return TB_OnSetImage(ptb, ptbButton, LOWORD(lParam)); case TB_GETBITMAP: iPos = PositionFromID(ptb, wParam); if (iPos < 0) return(FALSE); ptbButton = &ptb->Buttons[iPos]; return ptbButton->iBitmap; case TB_GETBUTTONTEXTA: iPos = PositionFromID(ptb, wParam); if (iPos >= 0) { LPTSTR psz; ptbButton = &ptb->Buttons[iPos]; psz = TB_StrForButton(ptb, ptbButton); if (psz) { // Passing a 0 for the length of the buffer when the // buffer is NULL returns the number bytes required // to convert the string. int cbBuff = WideCharToMultiByte (CP_ACP, 0, psz, -1, NULL, 0, NULL, NULL); // We used to pass an obscenly large number for the buffer length, // but on checked builds, this causes badness. So no we double-dip // into WideCharToMultiByte to calculate the real size required. if (lParam) { WideCharToMultiByte (CP_ACP, 0, psz, -1, (LPSTR)lParam, cbBuff, NULL, NULL); } // WideChar include a trailing NULL but we don't want to. return cbBuff - 1; } } return -1; case TB_GETBUTTONTEXT: iPos = PositionFromID(ptb, wParam); if (iPos >= 0) { LPCTSTR psz; ptbButton = &ptb->Buttons[iPos]; psz = TB_StrForButton(ptb, ptbButton); if (psz) { if (lParam) { lstrcpy((LPTSTR)lParam, psz); } return lstrlen(psz); } } return -1; case TB_GETBITMAPFLAGS: { DWORD fFlags = 0; HDC hdc = GetDC(NULL); if (GetDeviceCaps(hdc, LOGPIXELSY) >= 120) fFlags |= TBBF_LARGE; ReleaseDC(NULL, hdc); return fFlags; } case TB_SETINDENT: ptb->xFirstButton = (int) wParam; InvalidateRect (hwnd, NULL, TRUE); TBInvalidateItemRects(ptb); return 1; case TB_SETMAXTEXTROWS: if (ptb->nTextRows != (int)wParam) { ptb->nTextRows = (int) wParam; TBRecalc(ptb); InvalidateRect(hwnd, NULL, TRUE); } return 1; case TB_SETLISTGAP: ptb->iListGap = (int) wParam; InvalidateRect(hwnd, NULL, TRUE); break; case TB_SETDROPDOWNGAP: ptb->iDropDownGap = (int) wParam; InvalidateRect(hwnd, NULL, TRUE); break; case TB_GETTEXTROWS: return ptb->nTextRows; case TB_HITTEST: return TBHitTest(ptb, ((LPPOINT)lParam)->x, ((LPPOINT)lParam)->y); case TB_SETDRAWTEXTFLAGS: { UINT uOld = ptb->uDrawText; ptb->uDrawText = (UINT) (lParam & wParam); ptb->uDrawTextMask = (UINT) wParam; return uOld; } case TB_GETEXTENDEDSTYLE: return (ptb->dwStyleEx); case TB_SETEXTENDEDSTYLE: { DWORD dwRet = ptb->dwStyleEx; TBSetStyleEx(ptb, (DWORD) lParam, (DWORD) wParam); return dwRet; } case TB_SETBOUNDINGSIZE: { LPSIZE lpSize = (LPSIZE)lParam; ptb->sizeBound = *lpSize; break; } case TB_GETCOLORSCHEME: { LPCOLORSCHEME lpclrsc = (LPCOLORSCHEME) lParam; if (lpclrsc) { if (lpclrsc->dwSize == sizeof(COLORSCHEME)) *lpclrsc = ptb->clrsc; } return (LRESULT) lpclrsc; } case TB_SETCOLORSCHEME: { if (lParam) { if (((LPCOLORSCHEME) lParam)->dwSize == sizeof(COLORSCHEME)) { ptb->clrsc.clrBtnHighlight = ((LPCOLORSCHEME) lParam)->clrBtnHighlight; ptb->clrsc.clrBtnShadow = ((LPCOLORSCHEME) lParam)->clrBtnShadow; InvalidateRect(hwnd, NULL, FALSE); if (ptb->ci.style & WS_BORDER) CCInvalidateFrame(hwnd); } } } break; case TB_SETWINDOWTHEME: if (lParam) { SetWindowTheme(hwnd, (LPWSTR)lParam, NULL); TB_ForceCreateTooltips(ptb); if (ptb->hwndToolTips) { SendMessage(ptb->hwndToolTips, TTM_SETWINDOWTHEME, wParam, lParam); } } break; case WM_GETOBJECT: if( lParam == OBJID_QUERYCLASSNAMEIDX ) return MSAA_CLASSNAMEIDX_TOOLBAR; goto DoDefault; case WM_NULL: // Trap failed RegsiterWindowMessages; break; case WM_THEMECHANGED: if (ptb->hTheme) CloseThemeData(ptb->hTheme); ptb->hTheme = OpenThemeData(ptb->ci.hwnd, L"Toolbar"); TBAutoSize(ptb); InvalidateRect(ptb->ci.hwnd, NULL, TRUE); CCSendNotify(&ptb->ci, NM_THEMECHANGED, NULL); break; default: { LRESULT lres; if (g_uDragImages == uMsg) return TBGenerateDragImage(ptb, (SHDRAGIMAGE*)lParam); if (CCWndProc(&ptb->ci, uMsg, wParam, lParam, &lres)) return lres; } DoDefault: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0L; } int TB_CalcWidth(PTBSTATE ptb, int iHeight) { RECT rc; int iWidth = 0; int iMaxBtnWidth = 0; // ptb->iButWidth isn't always width of widest button LPTBBUTTONDATA pButton, pBtnLast; pBtnLast = &(ptb->Buttons[ptb->iNumButtons]); for(pButton = ptb->Buttons; pButton < pBtnLast; pButton++) { if (!(pButton->fsState & TBSTATE_HIDDEN)) { int iBtnWidth = TBWidthOfButton(ptb, pButton, NULL); iWidth += iBtnWidth + ptb->cxButtonSpacing; iMaxBtnWidth = max(iMaxBtnWidth, iBtnWidth); } } if (ptb->ci.style & TBSTYLE_WRAPABLE) { //Make sure the height is a multiple of button height iHeight += ptb->cyButtonSpacing; iHeight -= (iHeight % (ptb->iButHeight + ptb->cyButtonSpacing)); iHeight -= ptb->cyButtonSpacing; if (iHeight < ptb->iButHeight) iHeight = ptb->iButHeight; WrapToolbar(ptb, iWidth, &rc, NULL); // if wrapping at full width gives us a height that's too big, // then there's nothing we can do because widening it still keeps us at 1 row if (iHeight > RECTHEIGHT(rc)) { int iPrevWidth; BOOL fDivide = TRUE; //first start by dividing for speed, then narrow it down by subtraction TraceMsg(TF_TOOLBAR, "Toolbar: performing expensive width calculation!"); while (iMaxBtnWidth < iWidth) { iPrevWidth = iWidth; if (fDivide) iWidth = (iWidth * 2) / 3; else iWidth -= ptb->iButWidth; if (iWidth == iPrevWidth) break; WrapToolbar(ptb, iWidth, &rc, NULL); if (iHeight < RECTHEIGHT(rc)) { iWidth = iPrevWidth; if (fDivide) { // we've overstepped on dividing. go to the previous width // that was ok, and now try subtracting one button at a time fDivide = FALSE; } else break; } }; WrapToolbar(ptb, iWidth, &rc, NULL); iWidth = max(RECTWIDTH(rc), iMaxBtnWidth); } // WrapToolbar above has the side effect of actually modifying // the layout. we need to restore it after doing all this calculations TBAutoSize(ptb); } return iWidth; } LRESULT TB_OnScroll(PTBSTATE ptb, LPNMHDR pnm) { POINT pt, ptTemp; LPNMPGSCROLL pscroll = (LPNMPGSCROLL)pnm; int iDir = pscroll->iDir; RECT rcTemp, rc = pscroll->rcParent; int parentsize = 0; int scroll = pscroll->iScroll; int iButton = 0; int iButtonSize = ptb->iButHeight; int y = 0; int iCurrentButton = 0; //This variable holds the number of buttons in a row int iButInRow = 0; pt.x = pscroll->iXpos; pt.y = pscroll->iYpos; ptTemp = pt; //We need to add the offset of the toolbar to the scroll position to get the //correct scroll positon in terms of toolbar window pt.x += ptb->xFirstButton; pt.y += ptb->iYPos; ptTemp = pt; if ((iDir == PGF_SCROLLUP) || (iDir == PGF_SCROLLDOWN)) { //Vertical Mode if (ptb->iButWidth == 0 ) { iButInRow = 1; } else { iButInRow = RECTWIDTH(rc) / ptb->iButWidth; } } else { //Horizontal Mode iButInRow = 1; } // if the parent height/width is less than button height/width then set the number of // buttons in a row to be 1 if (0 == iButInRow) { iButInRow = 1; } iCurrentButton = TBHitTest(ptb, pt.x + 1, pt.y + 1); //if the button is negative then we have hit a seperator. //Convert the index of the seperator into button index if (iCurrentButton < 0) iCurrentButton = -iCurrentButton - 1; switch ( iDir ) { case PGF_SCROLLUP: case PGF_SCROLLLEFT: if(iDir == PGF_SCROLLLEFT) { FlipRect(&rc); FlipPoint(&pt); FlipPoint(&ptTemp); iButtonSize = ptb->iButWidth; } //Check if any button is partially visible at the left/top. if so then set the bottom // of that button to be our current offset and then scroll. This avoids skipping over // certain buttons when partial buttons are displayed at the left or top y = pt.y; TB_GetItemRect(ptb, iCurrentButton, &rcTemp); if(iDir == PGF_SCROLLLEFT) { FlipRect(&rcTemp); } if (rcTemp.top < y-1) { iCurrentButton += iButInRow; } //Now do the actual calculation parentsize = RECTHEIGHT(rc); //if the control key is down and we have more than parentsize size of child window // then scroll by that amount if (pscroll->fwKeys & PGK_CONTROL) { if ((y - parentsize) > 0 ) { scroll = parentsize; } else { scroll = y; return 0L; } } else if ((y - iButtonSize) > 0 ){ // we dont have control key down so scroll by one buttonsize scroll = iButtonSize; } else { scroll = pt.y; return 0L; } ptTemp.y -= scroll; if(iDir == PGF_SCROLLLEFT) { FlipPoint(&ptTemp); } iButton = TBHitTest(ptb, ptTemp.x, ptTemp.y); //if the button is negative then we have hit a seperator. //Convert the index of the seperator into button index if (iButton < 0) iButton = -iButton -1 ; // if the hit test gives us the same button as our prevbutton then set the button // to one button to the left of the prev button if ((iButton == iCurrentButton) && (iButton >= iButInRow)) { iButton -= iButInRow; if ((ptb->Buttons[iButton].fsStyle & BTNS_SEP) && (iButton >= iButInRow)) { iButton -= iButInRow; } } //When scrolling left if we end up in the middle of some button then we align it to the //right of that button this is to avoid scrolling more than the pager window width but if the // button happens to be the left button of our current button then we end up in not scrolling //if thats the case then move one more button to the left. if (iButton == iCurrentButton-iButInRow) { iButton -= iButInRow; } TB_GetItemRect(ptb, iButton, &rcTemp); if(iDir == PGF_SCROLLLEFT) { FlipRect(&rcTemp); } scroll = pt.y - rcTemp.bottom; //Set the scroll value pscroll->iScroll = scroll; break; case PGF_SCROLLDOWN: case PGF_SCROLLRIGHT: { RECT rcChild; int childsize; GetWindowRect(ptb->ci.hwnd, &rcChild); if( iDir == PGF_SCROLLRIGHT) { FlipRect(&rcChild); FlipRect(&rc); FlipPoint(&pt); FlipPoint(&ptTemp); iButtonSize = ptb->iButWidth; } childsize = RECTHEIGHT(rcChild); parentsize = RECTHEIGHT(rc); //if the control key is down and we have more than parentsize size of child window // then scroll by that amount if (pscroll->fwKeys & PGK_CONTROL) { if ((childsize - pt.y - parentsize) > parentsize) { scroll = parentsize; } else { scroll = childsize - pt.y - parentsize; return 0L; } } else if (childsize - pt.y - parentsize > iButtonSize) { // we dont have control key down so scroll by one buttonsize scroll = iButtonSize; } else { pscroll->iScroll = childsize - pt.y - parentsize; return 0L; } ptTemp.y += scroll; if(iDir == PGF_SCROLLRIGHT) { FlipPoint(&ptTemp); } iButton = TBHitTest(ptb, ptTemp.x, ptTemp.y); //if the button is negative then we have hit a seperator. //Convert the index of the seperator into button index if (iButton < 0) iButton = -iButton - 1 ; if ((iButton == iCurrentButton) && ((iButton + iButInRow) < ptb->iNumButtons)) { iButton += iButInRow; if ((ptb->Buttons[iButton].fsStyle & BTNS_SEP) && ((iButton + iButInRow) < ptb->iNumButtons)) { iButton += iButInRow; } } TB_GetItemRect(ptb, iButton, &rcTemp); if(iDir == PGF_SCROLLRIGHT) { FlipRect(&rcTemp); } scroll = rcTemp.top - pt.y ; //Set the scroll value pscroll->iScroll = scroll; break; } } return 0L; } int TB_CalcHeight(PTBSTATE ptb) { int iHeight = 0; int i; ASSERT(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL); ASSERT(!(ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN)); for (i = 0; i < ptb->iNumButtons; i++) { if (!(ptb->Buttons[i].fsState & TBSTATE_HIDDEN)) { if (ptb->Buttons[i].fsStyle & BTNS_SEP) iHeight += (TBGetSepHeight(ptb, &ptb->Buttons[i])) + ptb->cyButtonSpacing; else iHeight += ptb->iButHeight + ptb->cyButtonSpacing; } } if (ptb->iNumButtons > 0) iHeight -= ptb->cyButtonSpacing; return iHeight; } LRESULT TB_OnCalcSize(PTBSTATE ptb, LPNMHDR pnm) { LPNMPGCALCSIZE pcalcsize = (LPNMPGCALCSIZE)pnm; RECT rc; BOOL fUpdate = FALSE; switch(pcalcsize->dwFlag) { case PGF_CALCHEIGHT: if (ptb->szCached.cx == pcalcsize->iWidth) pcalcsize->iHeight = ptb->szCached.cy; else { if (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN) { WrapToolbarCol(ptb, ptb->sizeBound.cy, &rc, NULL); pcalcsize->iWidth = RECTWIDTH(rc); pcalcsize->iHeight = RECTHEIGHT(rc); } else if (ptb->dwStyleEx & TBSTYLE_EX_VERTICAL) { pcalcsize->iHeight = TB_CalcHeight(ptb); } else { // Bug#94368: this WrapToolbar call can modify toolbar layout ... // seems busted. should perhaps call TBAutoSize after to restore. WrapToolbar(ptb, pcalcsize->iWidth, &rc, NULL); pcalcsize->iHeight = RECTHEIGHT(rc); } fUpdate = TRUE; } break; case PGF_CALCWIDTH: if (ptb->szCached.cy == pcalcsize->iHeight) { pcalcsize->iWidth = ptb->szCached.cx; } else { pcalcsize->iWidth = TB_CalcWidth(ptb, pcalcsize->iHeight); fUpdate = TRUE; } break; } ptb->szCached.cx = pcalcsize->iWidth; ptb->szCached.cy = pcalcsize->iHeight; return 0L; } LRESULT TB_OnPagerControlNotify(PTBSTATE ptb, LPNMHDR pnm) { switch(pnm->code) { case PGN_SCROLL: return TB_OnScroll(ptb, pnm); break; case PGN_CALCSIZE: return TB_OnCalcSize(ptb, pnm); break; } return 0L; } BOOL TBGetMaxSize(PTBSTATE ptb, LPSIZE lpsize) { if (lpsize) { if (ptb->dwStyleEx & TBSTYLE_EX_MULTICOLUMN) { ASSERT(ptb->dwStyleEx & TBSTYLE_EX_VERTICAL); lpsize->cx = RECTWIDTH(ptb->rc); lpsize->cy = RECTHEIGHT(ptb->rc); } else { lpsize->cx = ptb->iButWidth; lpsize->cy = ptb->iButHeight; TB_GetIdealSize(ptb, lpsize, (ptb->ci.style & CCS_VERT)); } return TRUE; } return FALSE; } void TBGetItem(PTBSTATE ptb, LPTBBUTTONDATA ptButton, LPNMTBDISPINFO ptbdi) { ptbdi->idCommand = ptButton->idCommand; ptbdi->iImage = -1; ptbdi->lParam = ptButton->dwData; CCSendNotify(&ptb->ci, TBN_GETDISPINFO, &(ptbdi->hdr)); if(ptbdi->dwMask & TBNF_DI_SETITEM) { if(ptbdi->dwMask & TBNF_IMAGE) ptButton->iBitmap = ptbdi->iImage; } } BOOL TBGetInfoTip(PTBSTATE ptb, LPTOOLTIPTEXT lpttt, LPTBBUTTONDATA pTBButton) { NMTBGETINFOTIP git; TCHAR szBuf[INFOTIPSIZE]; szBuf[0] = 0; git.pszText = szBuf; git.cchTextMax = ARRAYSIZE(szBuf); git.iItem = pTBButton->idCommand; git.lParam = pTBButton->dwData; CCSendNotify(&ptb->ci, TBN_GETINFOTIP, &git.hdr); if (git.pszText && git.pszText[0]) { // if they didn't fill anything in, go to the default stuff // without modifying the notify structure Str_Set(&ptb->pszTip, git.pszText); lpttt->lpszText = ptb->pszTip; return lpttt->lpszText && lpttt->lpszText[0]; } return FALSE; }