4 * Copyright 2014 David Quintana
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include <commoncontrols.h>
23 #include <shlwapi_undoc.h>
25 #include "CMenuBand.h"
26 #include "CMenuToolbars.h"
28 WINE_DEFAULT_DEBUG_CHANNEL(CMenuToolbars
);
31 HRESULT WINAPI
SHGetImageList(
37 // FIXME: Enable if/when wine comctl supports this flag properly
38 #define USE_TBSTYLE_EX_VERTICAL 0
40 // User-defined timer ID used while hot-tracking around the menu
41 #define TIMERID_HOTTRACK 1
43 HRESULT
CMenuToolbarBase::OnWinEvent(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*theResult
)
51 return OnCommand(wParam
, lParam
, theResult
);
54 hdr
= reinterpret_cast<LPNMHDR
>(lParam
);
57 case TBN_DELETINGBUTTON
:
58 return OnDeletingButton(reinterpret_cast<LPNMTOOLBAR
>(hdr
));
61 return OnPagerCalcSize(reinterpret_cast<LPNMPGCALCSIZE
>(hdr
));
64 return OnCommand(reinterpret_cast<LPNMTOOLBAR
>(hdr
)->iItem
, 0, theResult
);
66 case TBN_HOTITEMCHANGE
:
67 //return OnHotItemChange(reinterpret_cast<LPNMTBHOTITEM>(hdr), theResult);
71 return OnContextMenu(reinterpret_cast<LPNMMOUSE
>(hdr
));
74 return OnCustomDraw(reinterpret_cast<LPNMTBCUSTOMDRAW
>(hdr
), theResult
);
77 return OnGetInfoTip(reinterpret_cast<LPNMTBGETINFOTIP
>(hdr
));
79 // Silence unhandled items so that they don't print as unknown
86 case NM_RELEASEDCAPTURE
:
101 case NM_TOOLTIPSCREATED
:
105 case -714: return S_FALSE
;
108 TRACE("WM_NOTIFY unknown code %d, %d\n", hdr
->code
, hdr
->idFrom
);
117 LRESULT CALLBACK
CMenuToolbarBase::s_SubclassProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
119 CMenuToolbarBase
* pthis
= reinterpret_cast<CMenuToolbarBase
*>(GetWindowLongPtr(hWnd
, GWLP_USERDATA
));
120 return pthis
->SubclassProc(hWnd
, uMsg
, wParam
, lParam
);
123 LRESULT
CMenuToolbarBase::SubclassProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
129 case WM_USER_ISTRACKEDITEM
:
130 m_SubclassOld(hWnd
, uMsg
, wParam
, lParam
);
131 return IsTrackedItem(wParam
);
132 case WM_USER_CHANGETRACKEDITEM
:
133 m_SubclassOld(hWnd
, uMsg
, wParam
, lParam
);
134 return ChangeTrackedItem(wParam
, LOWORD(lParam
), HIWORD(lParam
));
137 OnWinEvent(hWnd
, uMsg
, wParam
, lParam
, &lr
);
140 OnWinEvent(hWnd
, uMsg
, wParam
, lParam
, &lr
);
143 OnPopupTimer(wParam
);
146 return m_SubclassOld(hWnd
, uMsg
, wParam
, lParam
);
149 HRESULT
CMenuToolbarBase::DisableMouseTrack(BOOL bDisable
)
151 if (m_disableMouseTrack
!= bDisable
)
153 m_disableMouseTrack
= bDisable
;
154 TRACE("DisableMouseTrack %d\n", bDisable
);
159 HRESULT
CMenuToolbarBase::OnPagerCalcSize(LPNMPGCALCSIZE csize
)
162 GetSizes(NULL
, &tbs
, NULL
);
163 if (csize
->dwFlag
== PGF_CALCHEIGHT
)
165 csize
->iHeight
= tbs
.cy
;
167 else if (csize
->dwFlag
== PGF_CALCWIDTH
)
169 csize
->iWidth
= tbs
.cx
;
174 HRESULT
CMenuToolbarBase::OnCustomDraw(LPNMTBCUSTOMDRAW cdraw
, LRESULT
* theResult
)
183 switch (cdraw
->nmcd
.dwDrawStage
)
186 *theResult
= CDRF_NOTIFYITEMDRAW
;
189 case CDDS_ITEMPREPAINT
:
192 hdc
= cdraw
->nmcd
.hdc
;
194 // The item with an active submenu gets the CHECKED flag.
195 isHot
= m_hotBar
== this && (int) cdraw
->nmcd
.dwItemSpec
== m_hotItem
;
196 isPopup
= m_popupBar
== this && (int) cdraw
->nmcd
.dwItemSpec
== m_popupItem
;
198 if (m_initFlags
& SMINIT_VERTICAL
)
200 // Remove HOT and CHECKED flags (will restore HOT if necessary)
201 cdraw
->nmcd
.uItemState
&= ~(CDIS_HOT
| CDIS_CHECKED
);
203 // Decide on the colors
204 if (isHot
|| (m_hotItem
< 0 && isPopup
))
206 cdraw
->nmcd
.uItemState
|= CDIS_HOT
;
208 clrText
= GetSysColor(COLOR_HIGHLIGHTTEXT
);
209 bgBrush
= GetSysColorBrush(m_useFlatMenus
? COLOR_MENUHILIGHT
: COLOR_HIGHLIGHT
);
213 clrText
= GetSysColor(COLOR_MENUTEXT
);
214 bgBrush
= GetSysColorBrush(COLOR_MENU
);
217 // Paint the background color with the selected color
218 FillRect(hdc
, &rc
, bgBrush
);
220 // Set the text color in advance, this color will be assigned when the ITEMPOSTPAINT triggers
221 SetTextColor(hdc
, clrText
);
223 // Set the text color, will be used by the internal drawing code
224 cdraw
->clrText
= clrText
;
225 cdraw
->iListGap
+= 4;
227 // Tell the default drawing code we don't want any fanciness, not even a background.
228 *theResult
= CDRF_NOTIFYPOSTPAINT
| TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| 0x00800000; // FIXME: the last bit is Vista+, useful for debugging only
232 // Remove HOT and CHECKED flags (will restore HOT if necessary)
233 cdraw
->nmcd
.uItemState
&= ~CDIS_HOT
;
235 // Decide on the colors
236 if (isHot
|| (m_hotItem
< 0 && isPopup
))
238 cdraw
->nmcd
.uItemState
|= CDIS_HOT
;
246 case CDDS_ITEMPOSTPAINT
:
248 // Fetch the button style
249 btni
.cbSize
= sizeof(btni
);
250 btni
.dwMask
= TBIF_STYLE
;
251 SendMessage(m_hwndToolbar
, TB_GETBUTTONINFO
, cdraw
->nmcd
.dwItemSpec
, reinterpret_cast<LPARAM
>(&btni
));
253 // Check if we need to draw a submenu arrow
254 if (btni
.fsStyle
& BTNS_DROPDOWN
)
256 // TODO: Support RTL text modes by drawing a leftwards arrow aligned to the left of the control
258 // "8" is the rightwards dropdown arrow in the Marlett font
259 WCHAR text
[] = L
"8";
261 // Configure the font to draw with Marlett, keeping the current background color as-is
262 SelectObject(cdraw
->nmcd
.hdc
, m_marlett
);
263 SetBkMode(cdraw
->nmcd
.hdc
, TRANSPARENT
);
265 // Tweak the alignment by 1 pixel so the menu draws like the Windows start menu.
266 RECT rc
= cdraw
->nmcd
.rc
;
269 // The arrow is drawn at the right of the item's rect, aligned vertically.
270 DrawTextEx(cdraw
->nmcd
.hdc
, text
, 1, &rc
, DT_NOCLIP
| DT_VCENTER
| DT_RIGHT
| DT_SINGLELINE
, NULL
);
278 CMenuToolbarBase::CMenuToolbarBase(CMenuBand
*menuBand
, BOOL usePager
) :
281 m_useFlatMenus(FALSE
),
283 m_disableMouseTrack(FALSE
),
284 m_timerEnabled(FALSE
),
285 m_menuBand(menuBand
),
288 m_usePager(usePager
),
291 m_isTrackingPopup(FALSE
)
297 m_marlett
= CreateFont(
298 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET
,
299 OUT_DEFAULT_PRECIS
, CLIP_DEFAULT_PRECIS
,
300 DEFAULT_QUALITY
, FF_DONTCARE
, L
"Marlett");
303 CMenuToolbarBase::~CMenuToolbarBase()
305 if (m_hwndToolbar
&& m_hwndToolbar
!= m_hwnd
)
306 DestroyWindow(m_hwndToolbar
);
309 DestroyWindow(m_hwnd
);
311 DeleteObject(m_marlett
);
314 void CMenuToolbarBase::InvalidateDraw()
316 InvalidateRect(m_hwnd
, NULL
, FALSE
);
319 HRESULT
CMenuToolbarBase::ShowWindow(BOOL fShow
)
321 ::ShowWindow(m_hwnd
, fShow
? SW_SHOW
: SW_HIDE
);
323 // Ensure that the right image list is assigned to the toolbar
326 // For custom-drawing
327 SystemParametersInfo(SPI_GETFLATMENU
, 0, &m_useFlatMenus
, 0);
332 HRESULT
CMenuToolbarBase::UpdateImageLists()
334 if ((m_initFlags
& (SMINIT_TOPLEVEL
| SMINIT_VERTICAL
)) == SMINIT_TOPLEVEL
) // not vertical.
336 // No image list, prevents the buttons from having a margin at the left side
337 SendMessageW(m_hwnd
, TB_SETIMAGELIST
, 0, 0);
341 // Assign the correct imagelist and padding based on the current icon size
344 if (m_menuBand
->UseBigIcons())
347 SendMessageW(m_hwndToolbar
, TB_SETPADDING
, 0, MAKELPARAM(4, 0));
352 SendMessageW(m_hwndToolbar
, TB_SETPADDING
, 0, MAKELPARAM(4, 4));
356 HRESULT hr
= SHGetImageList(shiml
, IID_PPV_ARG(IImageList
, &piml
));
357 if (FAILED_UNEXPECTEDLY(hr
))
359 SendMessageW(m_hwndToolbar
, TB_SETIMAGELIST
, 0, 0);
363 SendMessageW(m_hwndToolbar
, TB_SETIMAGELIST
, 0, reinterpret_cast<LPARAM
>(piml
));
368 HRESULT
CMenuToolbarBase::Close()
370 if (m_hwndToolbar
!= m_hwnd
)
371 DestroyWindow(m_hwndToolbar
);
373 DestroyWindow(m_hwnd
);
375 m_hwndToolbar
= NULL
;
381 HRESULT
CMenuToolbarBase::CreateToolbar(HWND hwndParent
, DWORD dwFlags
)
383 LONG tbStyles
= WS_CHILD
| WS_VISIBLE
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
|
384 TBSTYLE_TOOLTIPS
| TBSTYLE_TRANSPARENT
| TBSTYLE_REGISTERDROP
| TBSTYLE_LIST
| TBSTYLE_FLAT
| TBSTYLE_CUSTOMERASE
|
385 CCS_NODIVIDER
| CCS_NOPARENTALIGN
| CCS_NORESIZE
| CCS_TOP
;
386 LONG tbExStyles
= TBSTYLE_EX_DOUBLEBUFFER
| WS_EX_TOOLWINDOW
;
388 if (dwFlags
& SMINIT_VERTICAL
)
390 // Activate vertical semantics
391 tbStyles
|= CCS_VERT
;
393 #if USE_TBSTYLE_EX_VERTICAL
394 tbExStyles
|= TBSTYLE_EX_VERTICAL
;
398 m_initFlags
= dwFlags
;
400 // Get a temporary rect to use while creating the toolbar window.
401 // Ensure that it is not a null rect.
403 if (!::GetClientRect(hwndParent
, &rc
) ||
404 (rc
.left
== rc
.right
) ||
405 (rc
.top
== rc
.bottom
))
413 HWND hwndToolbar
= CreateWindowEx(
414 tbExStyles
, TOOLBARCLASSNAMEW
, NULL
,
415 tbStyles
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
416 hwndParent
, NULL
, _AtlBaseModule
.GetModuleInstance(), 0);
418 if (hwndToolbar
== NULL
)
421 // If needed, create the pager.
424 LONG pgStyles
= PGS_VERT
| WS_CHILD
| WS_VISIBLE
;
427 HWND hwndPager
= CreateWindowEx(
428 pgExStyles
, WC_PAGESCROLLER
, NULL
,
429 pgStyles
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
430 hwndParent
, NULL
, _AtlBaseModule
.GetModuleInstance(), 0);
432 ::SetParent(hwndToolbar
, hwndPager
);
433 ::SetParent(hwndPager
, hwndParent
);
435 SendMessage(hwndPager
, PGM_SETCHILD
, 0, reinterpret_cast<LPARAM
>(hwndToolbar
));
436 m_hwndToolbar
= hwndToolbar
;
441 ::SetParent(hwndToolbar
, hwndParent
);
442 m_hwndToolbar
= hwndToolbar
;
443 m_hwnd
= hwndToolbar
;
446 // Identify the version of the used Common Controls DLL by sending the size of the TBBUTTON structure.
447 SendMessageW(hwndToolbar
, TB_BUTTONSTRUCTSIZE
, sizeof(TBBUTTON
), 0);
450 SetWindowLongPtr(hwndToolbar
, GWLP_USERDATA
, reinterpret_cast<LONG_PTR
>(this));
451 m_SubclassOld
= (WNDPROC
) SetWindowLongPtr(hwndToolbar
, GWLP_WNDPROC
, reinterpret_cast<LONG_PTR
>(CMenuToolbarBase::s_SubclassProc
));
453 // Configure the image lists
459 HRESULT
CMenuToolbarBase::GetSizes(SIZE
* pMinSize
, SIZE
* pMaxSize
, SIZE
* pIntegralSize
)
462 *pMinSize
= m_idealSize
;
464 *pMaxSize
= m_idealSize
;
466 *pIntegralSize
= m_itemSize
;
474 // Obtain the ideal size, to be used as min and max
475 SendMessageW(m_hwndToolbar
, TB_AUTOSIZE
, 0, 0);
476 SendMessageW(m_hwndToolbar
, TB_GETMAXSIZE
, 0, reinterpret_cast<LPARAM
>(&m_idealSize
));
477 SendMessageW(m_hwndToolbar
, TB_GETIDEALSIZE
, (m_initFlags
& SMINIT_VERTICAL
) != 0, reinterpret_cast<LPARAM
>(&m_idealSize
));
479 // Obtain the button size, to be used as the integral size
480 DWORD size
= SendMessageW(m_hwndToolbar
, TB_GETBUTTONSIZE
, 0, 0);
481 m_itemSize
.cx
= GET_X_LPARAM(size
);
482 m_itemSize
.cy
= GET_Y_LPARAM(size
);
486 *pMinSize
= m_idealSize
;
488 *pMaxSize
= m_idealSize
;
490 *pIntegralSize
= m_itemSize
;
495 HRESULT
CMenuToolbarBase::SetPosSize(int x
, int y
, int cx
, int cy
)
497 // If we have a pager, set the toolbar height to the ideal height of the toolbar
498 if (m_hwnd
!= m_hwndToolbar
)
500 SetWindowPos(m_hwndToolbar
, NULL
, x
, y
, cx
, m_idealSize
.cy
, 0);
503 // Update the toolbar or pager to fit the requested rect
504 SetWindowPos(m_hwnd
, NULL
, x
, y
, cx
, cy
, 0);
506 // In a vertical menu, resize the buttons to fit the width
507 if (m_initFlags
& SMINIT_VERTICAL
)
509 DWORD btnSize
= SendMessage(m_hwndToolbar
, TB_GETBUTTONSIZE
, 0, 0);
510 SendMessage(m_hwndToolbar
, TB_SETBUTTONSIZE
, 0, MAKELPARAM(cx
, HIWORD(btnSize
)));
516 HRESULT
CMenuToolbarBase::IsWindowOwner(HWND hwnd
)
518 if (m_hwnd
&& m_hwnd
== hwnd
) return S_OK
;
519 if (m_hwndToolbar
&& m_hwndToolbar
== hwnd
) return S_OK
;
523 HRESULT
CMenuToolbarBase::GetWindow(HWND
*phwnd
)
533 HRESULT
CMenuToolbarBase::OnGetInfoTip(NMTBGETINFOTIP
* tip
)
538 INT iItem
= tip
->iItem
;
540 GetDataFromId(iItem
, &index
, &dwData
);
542 return InternalGetTooltip(iItem
, index
, dwData
, tip
->pszText
, tip
->cchTextMax
);
545 HRESULT
CMenuToolbarBase::OnPopupTimer(DWORD timerId
)
547 if (timerId
!= TIMERID_HOTTRACK
)
550 KillTimer(m_hwndToolbar
, TIMERID_HOTTRACK
);
555 m_timerEnabled
= FALSE
;
560 // Returns S_FALSE if the current item did not show a submenu
561 HRESULT hr
= PopupItem(m_hotItem
, FALSE
);
565 // If we didn't switch submenus, cancel the current popup regardless
568 HRESULT hr
= CancelCurrentPopup();
569 if (FAILED_UNEXPECTEDLY(hr
))
576 HRESULT
CMenuToolbarBase::KillPopupTimer()
580 m_timerEnabled
= FALSE
;
581 KillTimer(m_hwndToolbar
, TIMERID_HOTTRACK
);
587 HRESULT
CMenuToolbarBase::ChangeHotItem(CMenuToolbarBase
* toolbar
, INT item
, DWORD dwFlags
)
589 // Ignore the change if it already matches the stored info
590 if (m_hotBar
== toolbar
&& m_hotItem
== item
)
593 // Prevent a change of hot item if the change was triggered by the mouse,
594 // and mouse tracking is disabled.
595 if (m_disableMouseTrack
&& dwFlags
& HICF_MOUSE
)
597 TRACE("Hot item change prevented by DisableMouseTrack\n");
601 // Notify the toolbar if the hot-tracking left this toolbar
602 if (m_hotBar
== this && toolbar
!= this)
604 SendMessage(m_hwndToolbar
, TB_SETHOTITEM
, (WPARAM
) -1, 0);
607 TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar
, m_hotItem
, toolbar
, item
);
611 if (m_hotBar
== this)
613 if (m_isTrackingPopup
&& !(m_initFlags
& SMINIT_VERTICAL
))
615 // If the menubar has an open submenu, switch to the new item's submenu immediately
616 PopupItem(m_hotItem
, FALSE
);
618 else if (dwFlags
& HICF_MOUSE
)
620 // Vertical menus show/hide the submenu after a delay,
621 // but only with the mouse.
622 if (m_initFlags
& SMINIT_VERTICAL
)
625 SystemParametersInfo(SPI_GETMENUSHOWDELAY
, 0, &elapsed
, 0);
626 SetTimer(m_hwndToolbar
, TIMERID_HOTTRACK
, elapsed
, NULL
);
627 m_timerEnabled
= TRUE
;
628 TRACE("SetTimer called with m_hotItem=%d\n", m_hotItem
);
634 info
.cbSize
= sizeof(info
);
637 int index
= SendMessage(m_hwndToolbar
, TB_GETBUTTONINFO
, item
, reinterpret_cast<LPARAM
>(&info
));
639 SendMessage(m_hwndToolbar
, TB_SETHOTITEM
, index
, 0);
647 HRESULT
CMenuToolbarBase::ChangePopupItem(CMenuToolbarBase
* toolbar
, INT item
)
649 // Ignore the change if it already matches the stored info
650 if (m_popupBar
== toolbar
&& m_popupItem
== item
)
653 // Notify the toolbar if the popup-tracking this toolbar
654 if (m_popupBar
== this && toolbar
!= this)
656 SendMessage(m_hwndToolbar
, TB_CHECKBUTTON
, m_popupItem
, FALSE
);
657 m_isTrackingPopup
= FALSE
;
660 m_popupBar
= toolbar
;
663 if (m_popupBar
== this)
665 SendMessage(m_hwndToolbar
, TB_CHECKBUTTON
, m_popupItem
, TRUE
);
672 HRESULT
CMenuToolbarBase::IsTrackedItem(INT index
)
676 if (m_hotBar
!= this)
682 if (!SendMessage(m_hwndToolbar
, TB_GETBUTTON
, index
, reinterpret_cast<LPARAM
>(&btn
)))
685 if (m_hotItem
== btn
.idCommand
)
688 if (m_popupItem
== btn
.idCommand
)
694 HRESULT
CMenuToolbarBase::ChangeTrackedItem(INT index
, BOOL wasTracking
, BOOL mouse
)
700 m_isTrackingPopup
= FALSE
;
701 return m_menuBand
->_ChangeHotItem(NULL
, -1, HICF_MOUSE
);
704 if (!SendMessage(m_hwndToolbar
, TB_GETBUTTON
, index
, reinterpret_cast<LPARAM
>(&btn
)))
707 TRACE("ChangeTrackedItem %d, %d\n", index
, wasTracking
);
708 m_isTrackingPopup
= wasTracking
;
709 return m_menuBand
->_ChangeHotItem(this, btn
.idCommand
, mouse
? HICF_MOUSE
: 0);
712 HRESULT
CMenuToolbarBase::PopupSubMenu(UINT iItem
, UINT index
, IShellMenu
* childShellMenu
, BOOL keyInitiated
)
714 // Calculate the submenu position and exclude area
718 if (!SendMessage(m_hwndToolbar
, TB_GETITEMRECT
, index
, reinterpret_cast<LPARAM
>(&rc
)))
721 GetWindowRect(m_hwnd
, &rcx
);
723 POINT a
= { rc
.left
, rc
.top
};
724 POINT b
= { rc
.right
, rc
.bottom
};
725 POINT c
= { rcx
.left
, rcx
.top
};
726 POINT d
= { rcx
.right
, rcx
.bottom
};
728 ClientToScreen(m_hwndToolbar
, &a
);
729 ClientToScreen(m_hwndToolbar
, &b
);
730 ClientToScreen(m_hwnd
, &c
);
731 ClientToScreen(m_hwnd
, &d
);
733 POINTL pt
= { a
.x
, b
.y
};
734 RECTL rcl
= { c
.x
, c
.y
, d
.x
, d
.y
};
736 if (m_initFlags
& SMINIT_VERTICAL
)
742 // Display the submenu
743 m_isTrackingPopup
= TRUE
;
745 m_menuBand
->_ChangePopupItem(this, iItem
);
746 m_menuBand
->_OnPopupSubMenu(childShellMenu
, &pt
, &rcl
, keyInitiated
);
751 HRESULT
CMenuToolbarBase::PopupSubMenu(UINT iItem
, UINT index
, HMENU menu
)
753 // Calculate the submenu position and exclude area
756 if (!SendMessage(m_hwndToolbar
, TB_GETITEMRECT
, index
, reinterpret_cast<LPARAM
>(&rc
)))
759 POINT a
= { rc
.left
, rc
.top
};
760 POINT b
= { rc
.right
, rc
.bottom
};
762 ClientToScreen(m_hwndToolbar
, &a
);
763 ClientToScreen(m_hwndToolbar
, &b
);
765 POINT pt
= { a
.x
, b
.y
};
766 RECT rcl
= { a
.x
, a
.y
, b
.x
, b
.y
};
768 if (m_initFlags
& SMINIT_VERTICAL
)
774 HMENU popup
= GetSubMenu(menu
, index
);
776 // Display the submenu
777 m_isTrackingPopup
= TRUE
;
778 m_menuBand
->_ChangePopupItem(this, iItem
);
779 m_menuBand
->_TrackSubMenu(popup
, pt
.x
, pt
.y
, rcl
);
780 m_menuBand
->_ChangePopupItem(NULL
, -1);
781 m_isTrackingPopup
= FALSE
;
783 m_menuBand
->_ChangeHotItem(NULL
, -1, 0);
788 HRESULT
CMenuToolbarBase::TrackContextMenu(IContextMenu
* contextMenu
, POINT pt
)
791 m_menuBand
->_KillPopupTimers();
793 m_menuBand
->_CancelCurrentPopup();
795 // Display the context menu
796 return m_menuBand
->_TrackContextMenu(contextMenu
, pt
.x
, pt
.y
);
799 HRESULT
CMenuToolbarBase::OnCommand(WPARAM wParam
, LPARAM lParam
, LRESULT
*theResult
)
801 if (m_disableMouseTrack
)
804 TRACE("Item click prevented by DisableMouseTrack\n");
808 // If a button is clicked while a submenu was open, cancel the submenu.
809 if (!(m_initFlags
& SMINIT_VERTICAL
) && m_isTrackingPopup
)
811 TRACE("OnCommand cancelled because it was tracking submenu.\n");
817 INT iItem
= (INT
)wParam
;
819 if (PopupItem(iItem
, FALSE
) == S_OK
)
821 TRACE("PopupItem returned S_OK\n");
825 TRACE("Executing...\n");
827 return m_menuBand
->_MenuItemHotTrack(MPOS_EXECUTE
);
830 HRESULT
CMenuToolbarBase::ExecuteItem(INT iItem
)
832 m_menuBand
->_KillPopupTimers();
837 GetDataFromId(iItem
, &index
, &data
);
839 return InternalExecuteItem(iItem
, index
, data
);
842 HRESULT
CMenuToolbarBase::OnContextMenu(NMMOUSE
* rclick
)
844 INT iItem
= rclick
->dwItemSpec
;
845 INT index
= rclick
->dwHitInfo
;
846 DWORD_PTR data
= rclick
->dwItemData
;
848 GetDataFromId(iItem
, &index
, &data
);
850 return InternalContextMenu(iItem
, index
, data
, rclick
->pt
);
853 HRESULT
CMenuToolbarBase::KeyboardItemChange(DWORD dwSelectType
)
855 int prev
= m_hotItem
;
858 if (dwSelectType
!= 0xFFFFFFFF)
860 int count
= SendMessage(m_hwndToolbar
, TB_BUTTONCOUNT
, 0, 0);
862 if (dwSelectType
== VK_HOME
)
865 dwSelectType
= VK_DOWN
;
867 else if (dwSelectType
== VK_END
)
870 dwSelectType
= VK_UP
;
876 TBBUTTONINFO info
= { 0 };
877 info
.cbSize
= sizeof(TBBUTTONINFO
);
879 index
= SendMessage(m_hwndToolbar
, TB_GETBUTTONINFO
, m_hotItem
, reinterpret_cast<LPARAM
>(&info
));
884 if (dwSelectType
== VK_UP
)
888 else if (dwSelectType
== VK_DOWN
)
895 if (dwSelectType
== VK_UP
)
899 else if (dwSelectType
== VK_DOWN
)
906 TBBUTTON btn
= { 0 };
907 while (index
>= 0 && index
< count
)
909 DWORD res
= SendMessage(m_hwndToolbar
, TB_GETBUTTON
, index
, reinterpret_cast<LPARAM
>(&btn
));
915 if (prev
!= btn
.idCommand
)
917 TRACE("Setting Hot item to %d\n", index
);
918 if (!(m_initFlags
& SMINIT_VERTICAL
) && m_isTrackingPopup
)
921 m_menuBand
->_GetTopLevelWindow(&tlw
);
922 SendMessage(tlw
, WM_CANCELMODE
, 0, 0);
923 PostMessage(m_hwndToolbar
, WM_USER_CHANGETRACKEDITEM
, index
, MAKELPARAM(m_isTrackingPopup
, FALSE
));
926 m_menuBand
->_ChangeHotItem(this, btn
.idCommand
, 0);
931 if (dwSelectType
== VK_UP
)
935 else if (dwSelectType
== VK_DOWN
)
946 TRACE("Setting Hot item to null\n");
947 m_menuBand
->_ChangeHotItem(NULL
, -1, 0);
953 HRESULT
CMenuToolbarBase::AddButton(DWORD commandId
, LPCWSTR caption
, BOOL hasSubMenu
, INT iconId
, DWORD_PTR buttonData
, BOOL last
)
955 TBBUTTON tbb
= { 0 };
957 tbb
.fsState
= TBSTATE_ENABLED
;
958 #if !USE_TBSTYLE_EX_VERTICAL
959 if (!last
&& (m_initFlags
& SMINIT_VERTICAL
))
960 tbb
.fsState
|= TBSTATE_WRAP
;
962 tbb
.fsStyle
= BTNS_CHECKGROUP
;
964 if (hasSubMenu
&& (m_initFlags
& SMINIT_VERTICAL
))
965 tbb
.fsStyle
|= BTNS_DROPDOWN
;
967 if (!(m_initFlags
& SMINIT_VERTICAL
))
968 tbb
.fsStyle
|= BTNS_AUTOSIZE
;
970 tbb
.iString
= (INT_PTR
) caption
;
971 tbb
.idCommand
= commandId
;
973 tbb
.iBitmap
= iconId
;
974 tbb
.dwData
= buttonData
;
976 if (!SendMessageW(m_hwndToolbar
, TB_ADDBUTTONS
, 1, reinterpret_cast<LPARAM
>(&tbb
)))
977 return HRESULT_FROM_WIN32(GetLastError());
982 HRESULT
CMenuToolbarBase::AddSeparator(BOOL last
)
984 TBBUTTON tbb
= { 0 };
986 tbb
.fsState
= TBSTATE_ENABLED
;
987 #if !USE_TBSTYLE_EX_VERTICAL
988 if (!last
&& (m_initFlags
& SMINIT_VERTICAL
))
989 tbb
.fsState
|= TBSTATE_WRAP
;
991 tbb
.fsStyle
= BTNS_SEP
;
994 if (!SendMessageW(m_hwndToolbar
, TB_ADDBUTTONS
, 1, reinterpret_cast<LPARAM
>(&tbb
)))
995 return HRESULT_FROM_WIN32(GetLastError());
1000 HRESULT
CMenuToolbarBase::AddPlaceholder()
1002 TBBUTTON tbb
= { 0 };
1003 PCWSTR MenuString
= L
"(Empty)";
1007 tbb
.iString
= (INT_PTR
) MenuString
;
1010 if (!SendMessageW(m_hwndToolbar
, TB_ADDBUTTONS
, 1, reinterpret_cast<LPARAM
>(&tbb
)))
1011 return HRESULT_FROM_WIN32(GetLastError());
1016 HRESULT
CMenuToolbarBase::ClearToolbar()
1018 while (SendMessage(m_hwndToolbar
, TB_DELETEBUTTON
, 0, 0))
1025 HRESULT
CMenuToolbarBase::GetDataFromId(INT iItem
, INT
* pIndex
, DWORD_PTR
* pData
)
1036 TBBUTTONINFO info
= { 0 };
1038 info
.cbSize
= sizeof(TBBUTTONINFO
);
1039 info
.dwMask
= TBIF_COMMAND
| TBIF_LPARAM
;
1041 int index
= SendMessage(m_hwndToolbar
, TB_GETBUTTONINFO
, iItem
, reinterpret_cast<LPARAM
>(&info
));
1049 *pData
= info
.lParam
;
1054 HRESULT
CMenuToolbarBase::CancelCurrentPopup()
1056 return m_menuBand
->_CancelCurrentPopup();
1059 HRESULT
CMenuToolbarBase::PopupItem(INT iItem
, BOOL keyInitiated
)
1067 if (m_popupBar
== this && m_popupItem
== iItem
)
1070 GetDataFromId(iItem
, &index
, &dwData
);
1072 HRESULT hr
= InternalHasSubMenu(iItem
, index
, dwData
);
1078 HRESULT hr
= CancelCurrentPopup();
1079 if (FAILED_UNEXPECTEDLY(hr
))
1083 if (!(m_initFlags
& SMINIT_VERTICAL
))
1085 TRACE("PopupItem non-vertical %d %d\n", index
, iItem
);
1086 m_menuBand
->_ChangeHotItem(this, iItem
, 0);
1089 return InternalPopupItem(iItem
, index
, dwData
, keyInitiated
);
1092 CMenuStaticToolbar::CMenuStaticToolbar(CMenuBand
*menuBand
) :
1093 CMenuToolbarBase(menuBand
, FALSE
),
1098 CMenuStaticToolbar::~CMenuStaticToolbar()
1102 HRESULT
CMenuStaticToolbar::GetMenu(
1103 _Out_opt_ HMENU
*phmenu
,
1104 _Out_opt_ HWND
*phwnd
,
1105 _Out_opt_ DWORD
*pdwFlags
)
1112 *pdwFlags
= m_dwMenuFlags
;
1117 HRESULT
CMenuStaticToolbar::SetMenu(
1123 m_dwMenuFlags
= dwFlags
;
1128 HRESULT
CMenuStaticToolbar::FillToolbar(BOOL clearFirst
)
1131 int ic
= GetMenuItemCount(m_hmenu
);
1139 for (i
= 0; i
< ic
; i
++)
1141 BOOL last
= i
+ 1 == ic
;
1145 info
.cbSize
= sizeof(info
);
1146 info
.dwTypeData
= NULL
;
1147 info
.fMask
= MIIM_FTYPE
| MIIM_STRING
| MIIM_ID
;
1149 if (!GetMenuItemInfoW(m_hmenu
, i
, TRUE
, &info
))
1151 TRACE("Error obtaining info for menu item at pos=%d\n", i
);
1157 if (info
.fType
& MFT_SEPARATOR
)
1161 else if (!(info
.fType
& MFT_BITMAP
))
1164 info
.dwTypeData
= (PWSTR
) HeapAlloc(GetProcessHeap(), 0, (info
.cch
+ 1) * sizeof(WCHAR
));
1166 info
.fMask
= MIIM_STRING
| MIIM_SUBMENU
| MIIM_ID
;
1167 GetMenuItemInfoW(m_hmenu
, i
, TRUE
, &info
);
1169 SMINFO
* sminfo
= new SMINFO();
1170 sminfo
->dwMask
= SMIM_ICON
| SMIM_FLAGS
;
1171 // FIXME: remove before deleting the toolbar or it will leak
1173 HRESULT hr
= m_menuBand
->_CallCBWithItemId(info
.wID
, SMC_GETINFO
, 0, reinterpret_cast<LPARAM
>(sminfo
));
1174 if (FAILED_UNEXPECTEDLY(hr
))
1177 AddButton(info
.wID
, info
.dwTypeData
, info
.hSubMenu
!= NULL
, sminfo
->iIcon
, reinterpret_cast<DWORD_PTR
>(sminfo
), last
);
1179 HeapFree(GetProcessHeap(), 0, info
.dwTypeData
);
1186 HRESULT
CMenuStaticToolbar::InternalGetTooltip(INT iItem
, INT index
, DWORD_PTR dwData
, LPWSTR pszText
, INT cchTextMax
)
1188 //SMINFO * info = reinterpret_cast<SMINFO*>(dwData);
1193 HRESULT
CMenuStaticToolbar::OnDeletingButton(const NMTOOLBAR
* tb
)
1195 delete reinterpret_cast<SMINFO
*>(tb
->tbButton
.dwData
);
1199 HRESULT
CMenuStaticToolbar::InternalContextMenu(INT iItem
, INT index
, DWORD_PTR dwData
, POINT pt
)
1201 CComPtr
<IContextMenu
> contextMenu
;
1202 HRESULT hr
= m_menuBand
->_CallCBWithItemId(iItem
, SMC_GETOBJECT
,
1203 reinterpret_cast<WPARAM
>(&IID_IContextMenu
), reinterpret_cast<LPARAM
>(&contextMenu
));
1207 return TrackContextMenu(contextMenu
, pt
);
1210 HRESULT
CMenuStaticToolbar::InternalExecuteItem(INT iItem
, INT index
, DWORD_PTR data
)
1212 return m_menuBand
->_CallCBWithItemId(iItem
, SMC_EXEC
, 0, 0);
1215 HRESULT
CMenuStaticToolbar::InternalPopupItem(INT iItem
, INT index
, DWORD_PTR dwData
, BOOL keyInitiated
)
1217 SMINFO
* nfo
= reinterpret_cast<SMINFO
*>(dwData
);
1221 if (nfo
->dwFlags
&SMIF_TRACKPOPUP
)
1223 return PopupSubMenu(iItem
, index
, m_hmenu
);
1227 CComPtr
<IShellMenu
> shellMenu
;
1228 HRESULT hr
= m_menuBand
->_CallCBWithItemId(iItem
, SMC_GETOBJECT
, reinterpret_cast<WPARAM
>(&IID_IShellMenu
), reinterpret_cast<LPARAM
>(&shellMenu
));
1229 if (FAILED_UNEXPECTEDLY(hr
))
1232 return PopupSubMenu(iItem
, index
, shellMenu
, keyInitiated
);
1236 HRESULT
CMenuStaticToolbar::InternalHasSubMenu(INT iItem
, INT index
, DWORD_PTR dwData
)
1238 return ::GetSubMenu(m_hmenu
, index
) ? S_OK
: S_FALSE
;
1241 CMenuSFToolbar::CMenuSFToolbar(CMenuBand
* menuBand
) :
1242 CMenuToolbarBase(menuBand
, TRUE
),
1243 m_shellFolder(NULL
),
1249 CMenuSFToolbar::~CMenuSFToolbar()
1253 HRESULT
CMenuSFToolbar::FillToolbar(BOOL clearFirst
)
1260 m_shellFolder
->EnumObjects(GetToolbar(), SHCONTF_FOLDERS
| SHCONTF_NONFOLDERS
, &eidl
);
1262 LPITEMIDLIST item
= static_cast<LPITEMIDLIST
>(CoTaskMemAlloc(sizeof(ITEMIDLIST
)));
1264 hr
= eidl
->Next(1, &item
, &fetched
);
1265 while (SUCCEEDED(hr
) && fetched
> 0)
1270 STRRET sr
= { STRRET_CSTR
, { 0 } };
1272 hr
= m_shellFolder
->GetDisplayNameOf(item
, SIGDN_NORMALDISPLAY
, &sr
);
1273 if (FAILED_UNEXPECTEDLY(hr
))
1276 StrRetToStr(&sr
, NULL
, &MenuString
);
1278 index
= SHMapPIDLToSystemImageListIndex(m_shellFolder
, item
, &indexOpen
);
1280 LPCITEMIDLIST itemc
= item
;
1282 SFGAOF attrs
= SFGAO_FOLDER
;
1283 hr
= m_shellFolder
->GetAttributesOf(1, &itemc
, &attrs
);
1285 DWORD_PTR dwData
= reinterpret_cast<DWORD_PTR
>(ILClone(item
));
1287 // Fetch next item already, so we know if the current one is the last
1288 hr
= eidl
->Next(1, &item
, &fetched
);
1290 AddButton(++i
, MenuString
, attrs
& SFGAO_FOLDER
, index
, dwData
, FAILED(hr
) || fetched
== 0);
1292 CoTaskMemFree(MenuString
);
1294 CoTaskMemFree(item
);
1296 // If no items were added, show the "empty" placeholder
1299 return AddPlaceholder();
1305 HRESULT
CMenuSFToolbar::InternalGetTooltip(INT iItem
, INT index
, DWORD_PTR dwData
, LPWSTR pszText
, INT cchTextMax
)
1307 //ITEMIDLIST * pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1312 HRESULT
CMenuSFToolbar::OnDeletingButton(const NMTOOLBAR
* tb
)
1314 ILFree(reinterpret_cast<LPITEMIDLIST
>(tb
->tbButton
.dwData
));
1318 HRESULT
CMenuSFToolbar::SetShellFolder(IShellFolder
*psf
, LPCITEMIDLIST pidlFolder
, HKEY hKey
, DWORD dwFlags
)
1320 m_shellFolder
= psf
;
1321 m_idList
= ILClone(pidlFolder
);
1323 m_dwMenuFlags
= dwFlags
;
1327 HRESULT
CMenuSFToolbar::GetShellFolder(DWORD
*pdwFlags
, LPITEMIDLIST
*ppidl
, REFIID riid
, void **ppv
)
1331 hr
= m_shellFolder
->QueryInterface(riid
, ppv
);
1332 if (FAILED_UNEXPECTEDLY(hr
))
1336 *pdwFlags
= m_dwMenuFlags
;
1340 LPITEMIDLIST pidl
= NULL
;
1344 pidl
= ILClone(m_idList
);
1347 (*reinterpret_cast<IUnknown
**>(ppv
))->Release();
1358 HRESULT
CMenuSFToolbar::InternalContextMenu(INT iItem
, INT index
, DWORD_PTR dwData
, POINT pt
)
1361 CComPtr
<IContextMenu
> contextMenu
= NULL
;
1362 LPCITEMIDLIST pidl
= reinterpret_cast<LPCITEMIDLIST
>(dwData
);
1364 hr
= m_shellFolder
->GetUIObjectOf(GetToolbar(), 1, &pidl
, IID_NULL_PPV_ARG(IContextMenu
, &contextMenu
));
1365 if (FAILED_UNEXPECTEDLY(hr
))
1370 hr
= TrackContextMenu(contextMenu
, pt
);
1375 HRESULT
CMenuSFToolbar::InternalExecuteItem(INT iItem
, INT index
, DWORD_PTR data
)
1377 return m_menuBand
->_CallCBWithItemPidl(reinterpret_cast<LPITEMIDLIST
>(data
), SMC_SFEXEC
, 0, 0);
1380 HRESULT
CMenuSFToolbar::InternalPopupItem(INT iItem
, INT index
, DWORD_PTR dwData
, BOOL keyInitiated
)
1386 CComPtr
<IShellMenuCallback
> psmc
;
1387 CComPtr
<IShellMenu
> shellMenu
;
1389 LPITEMIDLIST pidl
= reinterpret_cast<LPITEMIDLIST
>(dwData
);
1394 hr
= CMenuBand_Constructor(IID_PPV_ARG(IShellMenu
, &shellMenu
));
1395 if (FAILED_UNEXPECTEDLY(hr
))
1398 m_menuBand
->GetMenuInfo(&psmc
, &uId
, &uIdAncestor
, &flags
);
1400 // FIXME: not sure what to use as uId/uIdAncestor here
1401 hr
= shellMenu
->Initialize(psmc
, 0, uId
, SMINIT_VERTICAL
);
1402 if (FAILED_UNEXPECTEDLY(hr
))
1405 CComPtr
<IShellFolder
> childFolder
;
1406 hr
= m_shellFolder
->BindToObject(pidl
, NULL
, IID_PPV_ARG(IShellFolder
, &childFolder
));
1407 if (FAILED_UNEXPECTEDLY(hr
))
1410 hr
= shellMenu
->SetShellFolder(childFolder
, NULL
, NULL
, 0);
1411 if (FAILED_UNEXPECTEDLY(hr
))
1414 return PopupSubMenu(iItem
, index
, shellMenu
, keyInitiated
);
1417 HRESULT
CMenuSFToolbar::InternalHasSubMenu(INT iItem
, INT index
, DWORD_PTR dwData
)
1420 LPCITEMIDLIST pidl
= reinterpret_cast<LPITEMIDLIST
>(dwData
);
1422 SFGAOF attrs
= SFGAO_FOLDER
;
1423 hr
= m_shellFolder
->GetAttributesOf(1, &pidl
, &attrs
);
1424 if (FAILED_UNEXPECTEDLY(hr
))
1427 return (attrs
& SFGAO_FOLDER
) ? S_OK
: S_FALSE
;