mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7785 lines
226 KiB
7785 lines
226 KiB
/*
|
|
** 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 <limits.h>
|
|
#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
|
|
// +-+-+-------+--+-+
|
|
// | | | | | |
|
|
// | | | | | |
|
|
// +-+-+-------+--+-+
|
|
// | <text> |
|
|
// +----------------+
|
|
//
|
|
// |<--- 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; i<ptb->iNumButtons; 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; i<ptb->iNumButtons; 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; pButton<pBtnLast; pButton++) {
|
|
if (pButton->fsState & 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 (rows<prevRows && rows>height)
|
|
{
|
|
prevWidth = dx;
|
|
prevRows = rows;
|
|
}
|
|
}
|
|
|
|
if (rows<height && fLarger)
|
|
{
|
|
WrapToolbar(ptb, prevWidth, &rcCur, NULL);
|
|
}
|
|
|
|
if (lpRect)
|
|
*lpRect = rcCur;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int PositionFromID(PTBSTATE ptb, LONG_PTR id)
|
|
{
|
|
int i;
|
|
|
|
// Handle case where this is sent at the wrong time..
|
|
if (ptb == NULL || id == -1)
|
|
return -1;
|
|
|
|
// note, we don't skip separators, so you better not have conflicting
|
|
// cmd ids and separator ids.
|
|
for (i = 0; i < ptb->iNumButtons; 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; i<ptb->iNumButtons; 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;
|
|
}
|