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
20 #include "shellmenu.h"
21 #include <commoncontrols.h>
22 #include <shlwapi_undoc.h>
26 #include "CMenuBand.h"
27 #include "CMenuToolbars.h"
29 #define IDS_MENU_EMPTY 34561
31 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
32 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
34 WINE_DEFAULT_DEBUG_CHANNEL(CMenuToolbars
);
36 // FIXME: Enable if/when wine comctl supports this flag properly
37 #define USE_TBSTYLE_EX_VERTICAL 0
39 // User-defined timer ID used while hot-tracking around the menu
40 #define TIMERID_HOTTRACK 1
42 LRESULT
CMenuToolbarBase::OnWinEventWrap(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
45 bHandled
= OnWinEvent(m_hWnd
, uMsg
, wParam
, lParam
, &lr
) != S_FALSE
;
49 HRESULT
CMenuToolbarBase::OnWinEvent(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*theResult
)
60 //return OnCommand(wParam, lParam, theResult);
64 hdr
= reinterpret_cast<LPNMHDR
>(lParam
);
67 case TBN_DELETINGBUTTON
:
68 return OnDeletingButton(reinterpret_cast<LPNMTOOLBAR
>(hdr
));
71 return OnPagerCalcSize(reinterpret_cast<LPNMPGCALCSIZE
>(hdr
));
74 return ProcessClick(reinterpret_cast<LPNMTOOLBAR
>(hdr
)->iItem
);
76 case TBN_HOTITEMCHANGE
:
77 //return OnHotItemChange(reinterpret_cast<LPNMTBHOTITEM>(hdr), theResult);
81 hr
= OnCustomDraw(reinterpret_cast<LPNMTBCUSTOMDRAW
>(hdr
), &result
);
87 return OnGetInfoTip(reinterpret_cast<LPNMTBGETINFOTIP
>(hdr
));
89 // Silence unhandled items so that they don't print as unknown
96 case NM_RELEASEDCAPTURE
:
104 case TBN_GETDISPINFO
:
111 case NM_TOOLTIPSCREATED
:
114 case TBN_DRAGOUT
: return S_FALSE
;
117 TRACE("WM_NOTIFY unknown code %d, %d\n", hdr
->code
, hdr
->idFrom
);
121 case WM_WININICHANGE
:
122 if (wParam
== SPI_SETFLATMENU
)
124 SystemParametersInfo(SPI_GETFLATMENU
, 0, &m_useFlatMenus
, 0);
131 HRESULT
CMenuToolbarBase::DisableMouseTrack(BOOL bDisable
)
133 if (m_disableMouseTrack
!= bDisable
)
135 m_disableMouseTrack
= bDisable
;
136 TRACE("DisableMouseTrack %d\n", bDisable
);
141 HRESULT
CMenuToolbarBase::OnPagerCalcSize(LPNMPGCALCSIZE csize
)
144 GetSizes(NULL
, &tbs
, NULL
);
145 if (csize
->dwFlag
== PGF_CALCHEIGHT
)
147 csize
->iHeight
= tbs
.cy
;
149 else if (csize
->dwFlag
== PGF_CALCWIDTH
)
151 csize
->iWidth
= tbs
.cx
;
156 HRESULT
CMenuToolbarBase::OnCustomDraw(LPNMTBCUSTOMDRAW cdraw
, LRESULT
* theResult
)
158 bool isHot
, isPopup
, isActive
;
161 switch (cdraw
->nmcd
.dwDrawStage
)
164 *theResult
= CDRF_NOTIFYITEMDRAW
;
167 case CDDS_ITEMPREPAINT
:
170 m_menuBand
->_GetTopLevelWindow(&tlw
);
172 // The item with an active submenu gets the CHECKED flag.
173 isHot
= m_hotBar
== this && (int) cdraw
->nmcd
.dwItemSpec
== m_hotItem
;
174 isPopup
= m_popupBar
== this && (int) cdraw
->nmcd
.dwItemSpec
== m_popupItem
;
175 isActive
= (GetForegroundWindow() == tlw
) || (m_popupBar
== this);
177 if (m_hotItem
< 0 && isPopup
)
180 if ((m_useFlatMenus
&& isHot
) || (m_initFlags
& SMINIT_VERTICAL
))
184 RECT rc
= cdraw
->nmcd
.rc
;
185 HDC hdc
= cdraw
->nmcd
.hdc
;
187 // Remove HOT and CHECKED flags (will restore HOT if necessary)
188 cdraw
->nmcd
.uItemState
&= ~(CDIS_HOT
| CDIS_CHECKED
);
190 // Decide on the colors
193 cdraw
->nmcd
.uItemState
|= CDIS_HOT
;
195 clrText
= GetSysColor(COLOR_HIGHLIGHTTEXT
);
196 bgBrush
= GetSysColorBrush(m_useFlatMenus
? COLOR_MENUHILIGHT
: COLOR_HIGHLIGHT
);
200 clrText
= GetSysColor(COLOR_MENUTEXT
);
201 bgBrush
= GetSysColorBrush(COLOR_MENU
);
204 // Paint the background color with the selected color
205 FillRect(hdc
, &rc
, bgBrush
);
207 // Set the text color in advance, this color will be assigned when the ITEMPOSTPAINT triggers
208 SetTextColor(hdc
, clrText
);
210 // Set the text color, will be used by the internal drawing code
211 cdraw
->clrText
= clrText
;
212 cdraw
->iListGap
+= 4;
214 // Tell the default drawing code we don't want any fanciness, not even a background.
215 *theResult
= CDRF_NOTIFYPOSTPAINT
| TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| 0x00800000; // FIXME: the last bit is Vista+, useful for debugging only
219 // Set the text color, will be used by the internal drawing code
220 cdraw
->clrText
= GetSysColor(isActive
? COLOR_MENUTEXT
: COLOR_GRAYTEXT
);
222 // Remove HOT and CHECKED flags (will restore HOT if necessary)
223 cdraw
->nmcd
.uItemState
&= ~CDIS_HOT
;
225 // Decide on the colors
228 cdraw
->nmcd
.uItemState
|= CDIS_HOT
;
236 case CDDS_ITEMPOSTPAINT
:
238 // Fetch the button style
239 btni
.cbSize
= sizeof(btni
);
240 btni
.dwMask
= TBIF_STYLE
;
241 GetButtonInfo(cdraw
->nmcd
.dwItemSpec
, &btni
);
243 // Check if we need to draw a submenu arrow
244 if (btni
.fsStyle
& BTNS_DROPDOWN
)
246 // TODO: Support RTL text modes by drawing a leftwards arrow aligned to the left of the control
248 // "8" is the rightwards dropdown arrow in the Marlett font
249 WCHAR text
[] = L
"8";
251 // Configure the font to draw with Marlett, keeping the current background color as-is
252 SelectObject(cdraw
->nmcd
.hdc
, m_marlett
);
253 SetBkMode(cdraw
->nmcd
.hdc
, TRANSPARENT
);
255 // Tweak the alignment by 1 pixel so the menu draws like the Windows start menu.
256 RECT rc
= cdraw
->nmcd
.rc
;
259 // The arrow is drawn at the right of the item's rect, aligned vertically.
260 DrawTextEx(cdraw
->nmcd
.hdc
, text
, 1, &rc
, DT_NOCLIP
| DT_VCENTER
| DT_RIGHT
| DT_SINGLELINE
, NULL
);
268 CMenuToolbarBase::CMenuToolbarBase(CMenuBand
*menuBand
, BOOL usePager
) :
270 m_useFlatMenus(FALSE
),
271 m_disableMouseTrack(FALSE
),
272 m_timerEnabled(FALSE
),
273 m_menuBand(menuBand
),
276 m_usePager(usePager
),
281 m_isTrackingPopup(FALSE
),
282 m_cancelingPopup(FALSE
)
288 m_marlett
= CreateFont(
289 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET
,
290 OUT_DEFAULT_PRECIS
, CLIP_DEFAULT_PRECIS
,
291 DEFAULT_QUALITY
, FF_DONTCARE
, L
"Marlett");
294 CMenuToolbarBase::~CMenuToolbarBase()
302 m_pager
.DestroyWindow();
304 DeleteObject(m_marlett
);
307 void CMenuToolbarBase::InvalidateDraw()
309 InvalidateRect(NULL
, FALSE
);
312 HRESULT
CMenuToolbarBase::ShowDW(BOOL fShow
)
314 ShowWindow(fShow
? SW_SHOW
: SW_HIDE
);
316 // Ensure that the right image list is assigned to the toolbar
319 // For custom-drawing
320 SystemParametersInfo(SPI_GETFLATMENU
, 0, &m_useFlatMenus
, 0);
325 HRESULT
CMenuToolbarBase::UpdateImageLists()
327 if ((m_initFlags
& (SMINIT_TOPLEVEL
| SMINIT_VERTICAL
)) == SMINIT_TOPLEVEL
) // not vertical.
329 // No image list, prevents the buttons from having a margin at the left side
334 // Assign the correct imagelist and padding based on the current icon size
337 if (m_menuBand
->UseBigIcons())
349 HRESULT hr
= SHGetImageList(shiml
, IID_PPV_ARG(IImageList
, &piml
));
350 if (FAILED_UNEXPECTEDLY(hr
))
356 SetImageList((HIMAGELIST
)piml
);
361 HRESULT
CMenuToolbarBase::Close()
367 m_pager
.DestroyWindow();
372 HRESULT
CMenuToolbarBase::CreateToolbar(HWND hwndParent
, DWORD dwFlags
)
374 LONG tbStyles
= WS_CHILD
| WS_VISIBLE
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
|
375 TBSTYLE_TOOLTIPS
| TBSTYLE_TRANSPARENT
| TBSTYLE_REGISTERDROP
| TBSTYLE_LIST
| TBSTYLE_FLAT
| TBSTYLE_CUSTOMERASE
|
376 CCS_NODIVIDER
| CCS_NOPARENTALIGN
| CCS_NORESIZE
| CCS_TOP
;
377 LONG tbExStyles
= TBSTYLE_EX_DOUBLEBUFFER
| WS_EX_TOOLWINDOW
;
379 if (dwFlags
& SMINIT_VERTICAL
)
381 // Activate vertical semantics
382 tbStyles
|= CCS_VERT
;
384 #if USE_TBSTYLE_EX_VERTICAL
385 tbExStyles
|= TBSTYLE_EX_VERTICAL
;
389 m_initFlags
= dwFlags
;
391 // Get a temporary rect to use while creating the toolbar window.
392 // Ensure that it is not a null rect.
394 if (!::GetClientRect(hwndParent
, &rc
) ||
395 (rc
.left
== rc
.right
) ||
396 (rc
.top
== rc
.bottom
))
404 SubclassWindow(CToolbar::Create(hwndParent
, tbStyles
, tbExStyles
));
406 SetWindowTheme(m_hWnd
, L
"", L
"");
408 SystemParametersInfo(SPI_GETFLATMENU
, 0, &m_useFlatMenus
, 0);
410 m_menuBand
->AdjustForTheme(m_useFlatMenus
);
412 // If needed, create the pager.
415 LONG pgStyles
= PGS_VERT
| WS_CHILD
| WS_VISIBLE
;
418 HWND hwndPager
= CreateWindowEx(
419 pgExStyles
, WC_PAGESCROLLER
, NULL
,
420 pgStyles
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
,
421 hwndParent
, NULL
, _AtlBaseModule
.GetModuleInstance(), 0);
423 m_pager
.SubclassWindow(hwndPager
);
425 ::SetParent(m_hWnd
, hwndPager
);
427 m_pager
.SendMessageW(PGM_SETCHILD
, 0, reinterpret_cast<LPARAM
>(m_hWnd
));
430 // Configure the image lists
436 HRESULT
CMenuToolbarBase::GetSizes(SIZE
* pMinSize
, SIZE
* pMaxSize
, SIZE
* pIntegralSize
)
439 *pMinSize
= m_idealSize
;
441 *pMaxSize
= m_idealSize
;
443 *pIntegralSize
= m_itemSize
;
448 TRACE("Sizes out of date, recalculating.\n");
455 // Obtain the ideal size, to be used as min and max
456 GetMaxSize(&m_idealSize
);
457 GetIdealSize((m_initFlags
& SMINIT_VERTICAL
) != 0, &m_idealSize
);
459 TRACE("Ideal Size: (%d, %d) for %d buttons\n", m_idealSize
, GetButtonCount());
461 // Obtain the button size, to be used as the integral size
462 DWORD size
= GetButtonSize();
463 m_itemSize
.cx
= GET_X_LPARAM(size
);
464 m_itemSize
.cy
= GET_Y_LPARAM(size
);
468 *pMinSize
= m_idealSize
;
470 *pMaxSize
= m_idealSize
;
472 *pIntegralSize
= m_itemSize
;
477 HRESULT
CMenuToolbarBase::SetPosSize(int x
, int y
, int cx
, int cy
)
479 // Update the toolbar or pager to fit the requested rect
480 // If we have a pager, set the toolbar height to the ideal height of the toolbar
483 SetWindowPos(NULL
, x
, y
, cx
, m_idealSize
.cy
, 0);
484 m_pager
.SetWindowPos(NULL
, x
, y
, cx
, cy
, 0);
488 SetWindowPos(NULL
, x
, y
, cx
, cy
, 0);
491 // In a vertical menu, resize the buttons to fit the width
492 if (m_initFlags
& SMINIT_VERTICAL
)
494 DWORD btnSize
= GetButtonSize();
495 SetButtonSize(cx
, GET_Y_LPARAM(btnSize
));
501 HRESULT
CMenuToolbarBase::IsWindowOwner(HWND hwnd
)
503 if (m_hWnd
&& m_hWnd
== hwnd
) return S_OK
;
504 if (m_pager
.m_hWnd
&& m_pager
.m_hWnd
== hwnd
) return S_OK
;
508 HRESULT
CMenuToolbarBase::GetWindow(HWND
*phwnd
)
514 *phwnd
= m_pager
.m_hWnd
;
521 HRESULT
CMenuToolbarBase::OnGetInfoTip(NMTBGETINFOTIP
* tip
)
526 INT iItem
= tip
->iItem
;
528 GetDataFromId(iItem
, &index
, &dwData
);
530 return InternalGetTooltip(iItem
, index
, dwData
, tip
->pszText
, tip
->cchTextMax
);
533 HRESULT
CMenuToolbarBase::OnPopupTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
535 if (wParam
!= TIMERID_HOTTRACK
)
541 KillTimer(TIMERID_HOTTRACK
);
546 m_timerEnabled
= FALSE
;
551 // Returns S_FALSE if the current item did not show a submenu
552 HRESULT hr
= PopupItem(m_hotItem
, FALSE
);
556 // If we didn't switch submenus, cancel the current popup regardless
559 HRESULT hr
= CancelCurrentPopup();
560 if (FAILED_UNEXPECTEDLY(hr
))
567 HRESULT
CMenuToolbarBase::KillPopupTimer()
571 m_timerEnabled
= FALSE
;
572 KillTimer(TIMERID_HOTTRACK
);
578 HRESULT
CMenuToolbarBase::ChangeHotItem(CMenuToolbarBase
* toolbar
, INT item
, DWORD dwFlags
)
580 // Ignore the change if it already matches the stored info
581 if (m_hotBar
== toolbar
&& m_hotItem
== item
)
584 // Prevent a change of hot item if the change was triggered by the mouse,
585 // and mouse tracking is disabled.
586 if (m_disableMouseTrack
&& dwFlags
& HICF_MOUSE
)
588 TRACE("Hot item change prevented by DisableMouseTrack\n");
592 // Notify the toolbar if the hot-tracking left this toolbar
593 if (m_hotBar
== this && toolbar
!= this)
598 TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar
, m_hotItem
, toolbar
, item
);
602 if (m_hotBar
== this)
604 if (m_isTrackingPopup
&& !(m_initFlags
& SMINIT_VERTICAL
))
606 // If the menubar has an open submenu, switch to the new item's submenu immediately
607 PopupItem(m_hotItem
, FALSE
);
609 else if (dwFlags
& HICF_MOUSE
)
611 // Vertical menus show/hide the submenu after a delay,
612 // but only with the mouse.
613 if (m_initFlags
& SMINIT_VERTICAL
)
616 SystemParametersInfo(SPI_GETMENUSHOWDELAY
, 0, &elapsed
, 0);
617 SetTimer(TIMERID_HOTTRACK
, elapsed
);
618 m_timerEnabled
= TRUE
;
619 TRACE("SetTimer called with m_hotItem=%d\n", m_hotItem
);
625 info
.cbSize
= sizeof(info
);
628 int index
= GetButtonInfo(item
, &info
);
638 HRESULT
CMenuToolbarBase::ChangePopupItem(CMenuToolbarBase
* toolbar
, INT item
)
640 // Ignore the change if it already matches the stored info
641 if (m_popupBar
== toolbar
&& m_popupItem
== item
)
644 // Notify the toolbar if the popup-tracking this toolbar
645 if (m_popupBar
== this && toolbar
!= this)
647 CheckButton(m_popupItem
, FALSE
);
648 m_isTrackingPopup
= FALSE
;
651 m_popupBar
= toolbar
;
654 if (m_popupBar
== this)
656 CheckButton(m_popupItem
, TRUE
);
663 LRESULT
CMenuToolbarBase::IsTrackedItem(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
666 INT idx
= (INT
)wParam
;
668 if (m_hotBar
!= this)
674 if (!GetButton(idx
, &btn
))
677 if (m_hotItem
== btn
.idCommand
)
680 if (m_popupItem
== btn
.idCommand
)
686 LRESULT
CMenuToolbarBase::ChangeTrackedItem(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
689 BOOL wasTracking
= LOWORD(lParam
);
690 BOOL mouse
= HIWORD(lParam
);
691 INT idx
= (INT
)wParam
;
695 m_isTrackingPopup
= FALSE
;
696 return m_menuBand
->_ChangeHotItem(NULL
, -1, HICF_MOUSE
);
699 if (!GetButton(idx
, &btn
))
702 TRACE("ChangeTrackedItem %d, %d\n", idx
, wasTracking
);
703 m_isTrackingPopup
= wasTracking
;
704 return m_menuBand
->_ChangeHotItem(this, btn
.idCommand
, mouse
? HICF_MOUSE
: 0);
707 HRESULT
CMenuToolbarBase::PopupSubMenu(UINT iItem
, UINT index
, IShellMenu
* childShellMenu
, BOOL keyInitiated
)
709 // Calculate the submenu position and exclude area
712 if (!GetItemRect(index
, &rc
))
715 POINT a
= { rc
.left
, rc
.top
};
716 POINT b
= { rc
.right
, rc
.bottom
};
721 POINTL pt
= { a
.x
, b
.y
};
722 RECTL rcl
= { a
.x
, a
.y
, b
.x
, b
.y
};
724 if (m_initFlags
& SMINIT_VERTICAL
)
726 // FIXME: Hardcoding this here feels hacky.
739 // Display the submenu
740 m_isTrackingPopup
= TRUE
;
742 m_menuBand
->_ChangePopupItem(this, iItem
);
743 m_menuBand
->_OnPopupSubMenu(childShellMenu
, &pt
, &rcl
, keyInitiated
);
748 HRESULT
CMenuToolbarBase::PopupSubMenu(UINT iItem
, UINT index
, HMENU menu
)
750 // Calculate the submenu position and exclude area
753 if (!GetItemRect(index
, &rc
))
756 POINT a
= { rc
.left
, rc
.top
};
757 POINT b
= { rc
.right
, rc
.bottom
};
762 POINT pt
= { a
.x
, b
.y
};
763 RECT rcl
= { a
.x
, a
.y
, b
.x
, b
.y
};
765 if (m_initFlags
& SMINIT_VERTICAL
)
771 HMENU popup
= GetSubMenu(menu
, index
);
773 // Display the submenu
774 m_isTrackingPopup
= TRUE
;
775 m_menuBand
->_ChangePopupItem(this, iItem
);
776 m_menuBand
->_TrackSubMenu(popup
, pt
.x
, pt
.y
, rcl
);
777 m_menuBand
->_ChangePopupItem(NULL
, -1);
778 m_isTrackingPopup
= FALSE
;
783 HRESULT
CMenuToolbarBase::TrackContextMenu(IContextMenu
* contextMenu
, POINT pt
)
786 m_menuBand
->_KillPopupTimers();
788 m_menuBand
->_CancelCurrentPopup();
790 // Display the context menu
791 return m_menuBand
->_TrackContextMenu(contextMenu
, pt
.x
, pt
.y
);
794 HRESULT
CMenuToolbarBase::BeforeCancelPopup()
796 m_cancelingPopup
= TRUE
;
797 TRACE("BeforeCancelPopup\n");
801 HRESULT
CMenuToolbarBase::ProcessClick(INT iItem
)
803 if (m_disableMouseTrack
)
805 TRACE("Item click prevented by DisableMouseTrack\n");
809 // If a button is clicked while a submenu was open, cancel the submenu.
810 if (!(m_initFlags
& SMINIT_VERTICAL
) && m_isTrackingPopup
)
812 TRACE("OnCommand cancelled because it was tracking submenu.\n");
816 if (PopupItem(iItem
, FALSE
) == S_OK
)
818 TRACE("PopupItem returned S_OK\n");
822 TRACE("Executing...\n");
824 return m_menuBand
->_MenuItemSelect(MPOS_EXECUTE
);
827 HRESULT
CMenuToolbarBase::ProcessContextMenu(INT iItem
)
832 GetDataFromId(iItem
, &index
, &data
);
834 DWORD pos
= GetMessagePos();
835 POINT pt
= { GET_X_LPARAM(pos
), GET_Y_LPARAM(pos
) };
837 return InternalContextMenu(iItem
, index
, data
, pt
);
840 HRESULT
CMenuToolbarBase::MenuBarMouseDown(INT iIndex
, BOOL isLButton
)
844 GetButton(iIndex
, &btn
);
846 if ((m_initFlags
& SMINIT_VERTICAL
)
850 m_cancelingPopup
= FALSE
;
854 return ProcessClick(btn
.idCommand
);
857 HRESULT
CMenuToolbarBase::MenuBarMouseUp(INT iIndex
, BOOL isLButton
)
861 m_cancelingPopup
= FALSE
;
863 if (!(m_initFlags
& SMINIT_VERTICAL
))
866 GetButton(iIndex
, &btn
);
869 return ProcessClick(btn
.idCommand
);
871 return ProcessContextMenu(btn
.idCommand
);
874 HRESULT
CMenuToolbarBase::PrepareExecuteItem(INT iItem
)
876 this->m_menuBand
->_KillPopupTimers();
878 m_executeItem
= iItem
;
879 return GetDataFromId(iItem
, &m_executeIndex
, &m_executeData
);
882 HRESULT
CMenuToolbarBase::ExecuteItem()
884 return InternalExecuteItem(m_executeItem
, m_executeItem
, m_executeData
);
887 HRESULT
CMenuToolbarBase::KeyboardItemChange(DWORD dwSelectType
)
889 int prev
= m_hotItem
;
892 if (dwSelectType
!= 0xFFFFFFFF)
894 int count
= GetButtonCount();
896 if (dwSelectType
== VK_HOME
)
899 dwSelectType
= VK_DOWN
;
901 else if (dwSelectType
== VK_END
)
904 dwSelectType
= VK_UP
;
910 TBBUTTONINFO info
= { 0 };
911 info
.cbSize
= sizeof(TBBUTTONINFO
);
913 index
= GetButtonInfo(m_hotItem
, &info
);
918 if (dwSelectType
== VK_UP
)
922 else if (dwSelectType
== VK_DOWN
)
929 if (dwSelectType
== VK_UP
)
933 else if (dwSelectType
== VK_DOWN
)
940 TBBUTTON btn
= { 0 };
941 while (index
>= 0 && index
< count
)
943 DWORD res
= GetButton(index
, &btn
);
949 if (prev
!= btn
.idCommand
)
951 TRACE("Setting Hot item to %d\n", index
);
952 if (!(m_initFlags
& SMINIT_VERTICAL
) && m_isTrackingPopup
)
955 m_menuBand
->_GetTopLevelWindow(&tlw
);
956 SendMessageW(tlw
, WM_CANCELMODE
, 0, 0);
957 PostMessageW(WM_USER_CHANGETRACKEDITEM
, index
, MAKELPARAM(m_isTrackingPopup
, FALSE
));
960 m_menuBand
->_ChangeHotItem(this, btn
.idCommand
, 0);
965 if (dwSelectType
== VK_UP
)
969 else if (dwSelectType
== VK_DOWN
)
980 TRACE("Setting Hot item to null\n");
981 m_menuBand
->_ChangeHotItem(NULL
, -1, 0);
987 HRESULT
CMenuToolbarBase::AddButton(DWORD commandId
, LPCWSTR caption
, BOOL hasSubMenu
, INT iconId
, DWORD_PTR buttonData
, BOOL last
)
989 TBBUTTON tbb
= { 0 };
991 tbb
.fsState
= TBSTATE_ENABLED
;
992 #if !USE_TBSTYLE_EX_VERTICAL
993 if (!last
&& (m_initFlags
& SMINIT_VERTICAL
))
994 tbb
.fsState
|= TBSTATE_WRAP
;
996 tbb
.fsStyle
= BTNS_CHECKGROUP
;
998 if (hasSubMenu
&& (m_initFlags
& SMINIT_VERTICAL
))
999 tbb
.fsStyle
|= BTNS_DROPDOWN
;
1001 if (!(m_initFlags
& SMINIT_VERTICAL
))
1002 tbb
.fsStyle
|= BTNS_AUTOSIZE
;
1004 tbb
.iString
= (INT_PTR
) caption
;
1005 tbb
.idCommand
= commandId
;
1007 tbb
.iBitmap
= iconId
;
1008 tbb
.dwData
= buttonData
;
1012 if (!AddButtons(1, &tbb
))
1013 return HRESULT_FROM_WIN32(GetLastError());
1017 HRESULT
CMenuToolbarBase::AddSeparator(BOOL last
)
1019 TBBUTTON tbb
= { 0 };
1021 tbb
.fsState
= TBSTATE_ENABLED
;
1022 #if !USE_TBSTYLE_EX_VERTICAL
1023 if (!last
&& (m_initFlags
& SMINIT_VERTICAL
))
1024 tbb
.fsState
|= TBSTATE_WRAP
;
1026 tbb
.fsStyle
= BTNS_SEP
;
1031 if (!AddButtons(1, &tbb
))
1032 return HRESULT_FROM_WIN32(GetLastError());
1037 HRESULT
CMenuToolbarBase::AddPlaceholder()
1039 TBBUTTON tbb
= { 0 };
1040 WCHAR MenuString
[128];
1042 LoadStringW(GetModuleHandle(L
"shell32.dll"), IDS_MENU_EMPTY
, MenuString
, _countof(MenuString
));
1046 tbb
.iString
= (INT_PTR
) MenuString
;
1051 if (!AddButtons(1, &tbb
))
1052 return HRESULT_FROM_WIN32(GetLastError());
1057 HRESULT
CMenuToolbarBase::ClearToolbar()
1059 while (DeleteButton(0))
1067 HRESULT
CMenuToolbarBase::GetDataFromId(INT iItem
, INT
* pIndex
, DWORD_PTR
* pData
)
1078 TBBUTTONINFO info
= { 0 };
1080 info
.cbSize
= sizeof(TBBUTTONINFO
);
1081 info
.dwMask
= TBIF_COMMAND
| TBIF_LPARAM
;
1083 int index
= GetButtonInfo(iItem
, &info
);
1091 *pData
= info
.lParam
;
1096 HRESULT
CMenuToolbarBase::CancelCurrentPopup()
1098 return m_menuBand
->_CancelCurrentPopup();
1101 HRESULT
CMenuToolbarBase::PopupItem(INT iItem
, BOOL keyInitiated
)
1109 if (m_popupBar
== this && m_popupItem
== iItem
)
1112 GetDataFromId(iItem
, &index
, &dwData
);
1114 HRESULT hr
= InternalHasSubMenu(iItem
, index
, dwData
);
1120 HRESULT hr
= CancelCurrentPopup();
1121 if (FAILED_UNEXPECTEDLY(hr
))
1125 if (!(m_initFlags
& SMINIT_VERTICAL
))
1127 TRACE("PopupItem non-vertical %d %d\n", index
, iItem
);
1128 m_menuBand
->_ChangeHotItem(this, iItem
, 0);
1131 return InternalPopupItem(iItem
, index
, dwData
, keyInitiated
);
1134 CMenuStaticToolbar::CMenuStaticToolbar(CMenuBand
*menuBand
) :
1135 CMenuToolbarBase(menuBand
, FALSE
),
1141 CMenuStaticToolbar::~CMenuStaticToolbar()
1145 HRESULT
CMenuStaticToolbar::GetMenu(
1146 _Out_opt_ HMENU
*phmenu
,
1147 _Out_opt_ HWND
*phwnd
,
1148 _Out_opt_ DWORD
*pdwFlags
)
1153 *phwnd
= m_hwndMenu
;
1155 *pdwFlags
= m_dwMenuFlags
;
1160 HRESULT
CMenuStaticToolbar::SetMenu(
1167 m_dwMenuFlags
= dwFlags
;
1174 HRESULT
CMenuStaticToolbar::FillToolbar(BOOL clearFirst
)
1177 int ic
= GetMenuItemCount(m_hmenu
);
1185 for (i
= 0; i
< ic
; i
++)
1187 BOOL last
= i
+ 1 == ic
;
1191 info
.cbSize
= sizeof(info
);
1192 info
.dwTypeData
= NULL
;
1193 info
.fMask
= MIIM_FTYPE
| MIIM_STRING
| MIIM_ID
;
1195 if (!GetMenuItemInfoW(m_hmenu
, i
, TRUE
, &info
))
1197 TRACE("Error obtaining info for menu item at pos=%d\n", i
);
1203 if (info
.fType
& MFT_SEPARATOR
)
1207 else if (!(info
.fType
& MFT_BITMAP
))
1210 info
.dwTypeData
= (PWSTR
) HeapAlloc(GetProcessHeap(), 0, (info
.cch
+ 1) * sizeof(WCHAR
));
1212 info
.fMask
= MIIM_STRING
| MIIM_SUBMENU
| MIIM_ID
;
1213 GetMenuItemInfoW(m_hmenu
, i
, TRUE
, &info
);
1215 SMINFO
* sminfo
= new SMINFO();
1216 sminfo
->dwMask
= SMIM_ICON
| SMIM_FLAGS
;
1218 HRESULT hr
= m_menuBand
->_CallCBWithItemId(info
.wID
, SMC_GETINFO
, 0, reinterpret_cast<LPARAM
>(sminfo
));
1219 if (FAILED_UNEXPECTEDLY(hr
))
1225 AddButton(info
.wID
, info
.dwTypeData
, info
.hSubMenu
!= NULL
, sminfo
->iIcon
, reinterpret_cast<DWORD_PTR
>(sminfo
), last
);
1227 HeapFree(GetProcessHeap(), 0, info
.dwTypeData
);
1234 HRESULT
CMenuStaticToolbar::InternalGetTooltip(INT iItem
, INT index
, DWORD_PTR dwData
, LPWSTR pszText
, INT cchTextMax
)
1236 //SMINFO * info = reinterpret_cast<SMINFO*>(dwData);
1241 HRESULT
CMenuStaticToolbar::OnDeletingButton(const NMTOOLBAR
* tb
)
1243 delete reinterpret_cast<SMINFO
*>(tb
->tbButton
.dwData
);
1247 HRESULT
CMenuStaticToolbar::InternalContextMenu(INT iItem
, INT index
, DWORD_PTR dwData
, POINT pt
)
1249 CComPtr
<IContextMenu
> contextMenu
;
1250 HRESULT hr
= m_menuBand
->_CallCBWithItemId(iItem
, SMC_GETOBJECT
,
1251 reinterpret_cast<WPARAM
>(&IID_IContextMenu
), reinterpret_cast<LPARAM
>(&contextMenu
));
1255 return TrackContextMenu(contextMenu
, pt
);
1258 HRESULT
CMenuStaticToolbar::InternalExecuteItem(INT iItem
, INT index
, DWORD_PTR data
)
1260 return m_menuBand
->_CallCBWithItemId(iItem
, SMC_EXEC
, 0, 0);
1263 HRESULT
CMenuStaticToolbar::InternalPopupItem(INT iItem
, INT index
, DWORD_PTR dwData
, BOOL keyInitiated
)
1265 SMINFO
* nfo
= reinterpret_cast<SMINFO
*>(dwData
);
1269 if (nfo
->dwFlags
&SMIF_TRACKPOPUP
)
1271 return PopupSubMenu(iItem
, index
, m_hmenu
);
1275 CComPtr
<IShellMenu
> shellMenu
;
1276 HRESULT hr
= m_menuBand
->_CallCBWithItemId(iItem
, SMC_GETOBJECT
, reinterpret_cast<WPARAM
>(&IID_IShellMenu
), reinterpret_cast<LPARAM
>(&shellMenu
));
1277 if (FAILED_UNEXPECTEDLY(hr
))
1280 return PopupSubMenu(iItem
, index
, shellMenu
, keyInitiated
);
1284 HRESULT
CMenuStaticToolbar::InternalHasSubMenu(INT iItem
, INT index
, DWORD_PTR dwData
)
1286 return ::GetSubMenu(m_hmenu
, index
) ? S_OK
: S_FALSE
;
1289 CMenuSFToolbar::CMenuSFToolbar(CMenuBand
* menuBand
) :
1290 CMenuToolbarBase(menuBand
, TRUE
),
1291 m_shellFolder(NULL
),
1297 CMenuSFToolbar::~CMenuSFToolbar()
1301 int CALLBACK
PidlListSort(void* item1
, void* item2
, LPARAM lParam
)
1303 IShellFolder
* psf
= (IShellFolder
*) lParam
;
1304 PCUIDLIST_RELATIVE pidl1
= (PCUIDLIST_RELATIVE
) item1
;
1305 PCUIDLIST_RELATIVE pidl2
= (PCUIDLIST_RELATIVE
) item2
;
1306 HRESULT hr
= psf
->CompareIDs(0, pidl1
, pidl2
);
1309 // No way to cancel, so sort to equal.
1312 return (int)(short)LOWORD(hr
);
1315 HRESULT
CMenuSFToolbar::FillToolbar(BOOL clearFirst
)
1319 CComPtr
<IEnumIDList
> eidl
;
1320 hr
= m_shellFolder
->EnumObjects(GetToolbar(), SHCONTF_FOLDERS
| SHCONTF_NONFOLDERS
, &eidl
);
1321 if (FAILED_UNEXPECTEDLY(hr
))
1324 HDPA dpaSort
= DPA_Create(10);
1326 LPITEMIDLIST item
= NULL
;
1327 hr
= eidl
->Next(1, &item
, NULL
);
1330 if (m_menuBand
->_CallCBWithItemPidl(item
, 0x10000000, 0, 0) == S_FALSE
)
1332 DPA_AppendPtr(dpaSort
, item
);
1336 CoTaskMemFree(item
);
1339 hr
= eidl
->Next(1, &item
, NULL
);
1342 // If no items were added, show the "empty" placeholder
1343 if (DPA_GetPtrCount(dpaSort
) == 0)
1345 DPA_Destroy(dpaSort
);
1346 return AddPlaceholder();
1349 TRACE("FillToolbar added %d items to the DPA\n", DPA_GetPtrCount(dpaSort
));
1351 DPA_Sort(dpaSort
, PidlListSort
, (LPARAM
) m_shellFolder
.p
);
1353 for (int i
= 0; i
<DPA_GetPtrCount(dpaSort
);)
1360 STRRET sr
= { STRRET_CSTR
, { 0 } };
1362 item
= (LPITEMIDLIST
)DPA_GetPtr(dpaSort
, i
);
1364 hr
= m_shellFolder
->GetDisplayNameOf(item
, SIGDN_NORMALDISPLAY
, &sr
);
1365 if (FAILED_UNEXPECTEDLY(hr
))
1367 DPA_Destroy(dpaSort
);
1371 StrRetToStr(&sr
, NULL
, &MenuString
);
1373 index
= SHMapPIDLToSystemImageListIndex(m_shellFolder
, item
, &indexOpen
);
1375 LPCITEMIDLIST itemc
= item
;
1377 SFGAOF attrs
= SFGAO_FOLDER
;
1378 hr
= m_shellFolder
->GetAttributesOf(1, &itemc
, &attrs
);
1380 DWORD_PTR dwData
= reinterpret_cast<DWORD_PTR
>(item
);
1382 // Fetch next item already, so we know if the current one is the last
1385 AddButton(i
, MenuString
, attrs
& SFGAO_FOLDER
, index
, dwData
, i
>= DPA_GetPtrCount(dpaSort
));
1387 CoTaskMemFree(MenuString
);
1390 DPA_Destroy(dpaSort
);
1394 HRESULT
CMenuSFToolbar::InternalGetTooltip(INT iItem
, INT index
, DWORD_PTR dwData
, LPWSTR pszText
, INT cchTextMax
)
1396 //ITEMIDLIST * pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1401 HRESULT
CMenuSFToolbar::OnDeletingButton(const NMTOOLBAR
* tb
)
1403 ILFree(reinterpret_cast<LPITEMIDLIST
>(tb
->tbButton
.dwData
));
1407 HRESULT
CMenuSFToolbar::SetShellFolder(IShellFolder
*psf
, LPCITEMIDLIST pidlFolder
, HKEY hKey
, DWORD dwFlags
)
1409 m_shellFolder
= psf
;
1410 m_idList
= ILClone(pidlFolder
);
1412 m_dwMenuFlags
= dwFlags
;
1419 HRESULT
CMenuSFToolbar::GetShellFolder(DWORD
*pdwFlags
, LPITEMIDLIST
*ppidl
, REFIID riid
, void **ppv
)
1423 hr
= m_shellFolder
->QueryInterface(riid
, ppv
);
1424 if (FAILED_UNEXPECTEDLY(hr
))
1428 *pdwFlags
= m_dwMenuFlags
;
1432 LPITEMIDLIST pidl
= NULL
;
1436 pidl
= ILClone(m_idList
);
1439 ERR("ILClone failed!\n");
1440 (*reinterpret_cast<IUnknown
**>(ppv
))->Release();
1451 HRESULT
CMenuSFToolbar::InternalContextMenu(INT iItem
, INT index
, DWORD_PTR dwData
, POINT pt
)
1454 CComPtr
<IContextMenu
> contextMenu
= NULL
;
1455 LPCITEMIDLIST pidl
= reinterpret_cast<LPCITEMIDLIST
>(dwData
);
1457 hr
= m_shellFolder
->GetUIObjectOf(GetToolbar(), 1, &pidl
, IID_NULL_PPV_ARG(IContextMenu
, &contextMenu
));
1458 if (FAILED_UNEXPECTEDLY(hr
))
1463 hr
= TrackContextMenu(contextMenu
, pt
);
1468 HRESULT
CMenuSFToolbar::InternalExecuteItem(INT iItem
, INT index
, DWORD_PTR data
)
1470 return m_menuBand
->_CallCBWithItemPidl(reinterpret_cast<LPITEMIDLIST
>(data
), SMC_SFEXEC
, 0, 0);
1473 HRESULT
CMenuSFToolbar::InternalPopupItem(INT iItem
, INT index
, DWORD_PTR dwData
, BOOL keyInitiated
)
1479 CComPtr
<IShellMenuCallback
> psmc
;
1480 CComPtr
<IShellMenu
> shellMenu
;
1482 LPITEMIDLIST pidl
= reinterpret_cast<LPITEMIDLIST
>(dwData
);
1487 hr
= CMenuBand_CreateInstance(IID_PPV_ARG(IShellMenu
, &shellMenu
));
1488 if (FAILED_UNEXPECTEDLY(hr
))
1491 m_menuBand
->GetMenuInfo(&psmc
, &uId
, &uIdAncestor
, &flags
);
1493 // FIXME: not sure what to use as uId/uIdAncestor here
1494 hr
= shellMenu
->Initialize(psmc
, 0, uId
, SMINIT_VERTICAL
);
1495 if (FAILED_UNEXPECTEDLY(hr
))
1498 CComPtr
<IShellFolder
> childFolder
;
1499 hr
= m_shellFolder
->BindToObject(pidl
, NULL
, IID_PPV_ARG(IShellFolder
, &childFolder
));
1500 if (FAILED_UNEXPECTEDLY(hr
))
1503 hr
= shellMenu
->SetShellFolder(childFolder
, NULL
, NULL
, 0);
1504 if (FAILED_UNEXPECTEDLY(hr
))
1507 return PopupSubMenu(iItem
, index
, shellMenu
, keyInitiated
);
1510 HRESULT
CMenuSFToolbar::InternalHasSubMenu(INT iItem
, INT index
, DWORD_PTR dwData
)
1513 LPCITEMIDLIST pidl
= reinterpret_cast<LPITEMIDLIST
>(dwData
);
1515 SFGAOF attrs
= SFGAO_FOLDER
;
1516 hr
= m_shellFolder
->GetAttributesOf(1, &pidl
, &attrs
);
1517 if (FAILED_UNEXPECTEDLY(hr
))
1520 return (attrs
& SFGAO_FOLDER
) ? S_OK
: S_FALSE
;