1 /*
2  * Shell Menu Band
3  *
4  * Copyright 2014 David Quintana
5  *
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.
10  *
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.
15  *
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
19  */
20 #include "shellmenu.h"
21 #include <commoncontrols.h>
22 #include <shlwapi_undoc.h>
23 #include <uxtheme.h>
24 #include <vssym32.h>
25 
26 #include "CMenuBand.h"
27 #include "CMenuToolbars.h"
28 
29 #define IDS_MENU_EMPTY 34561
30 
31 #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
32 #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
33 
34 WINE_DEFAULT_DEBUG_CHANNEL(CMenuToolbars);
35 
36 // FIXME: Enable if/when wine comctl supports this flag properly
37 #define USE_TBSTYLE_EX_VERTICAL 0
38 
39 // User-defined timer ID used while hot-tracking around the menu
40 #define TIMERID_HOTTRACK 1
41 
42 LRESULT CMenuToolbarBase::OnWinEventWrap(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
43 {
44     LRESULT lr;
45     bHandled = OnWinEvent(m_hWnd, uMsg, wParam, lParam, &lr) != S_FALSE;
46     return lr;
47 }
48 
49 HRESULT CMenuToolbarBase::OnWinEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult)
50 {
51     NMHDR * hdr;
52     HRESULT hr;
53     LRESULT result;
54 
55     if (theResult)
56         *theResult = 0;
57     switch (uMsg)
58     {
59     case WM_COMMAND:
60         //return OnCommand(wParam, lParam, theResult);
61         return S_OK;
62 
63     case WM_NOTIFY:
64         hdr = reinterpret_cast<LPNMHDR>(lParam);
65         switch (hdr->code)
66         {
67         case TBN_DELETINGBUTTON:
68             return OnDeletingButton(reinterpret_cast<LPNMTOOLBAR>(hdr));
69 
70         case PGN_CALCSIZE:
71             return OnPagerCalcSize(reinterpret_cast<LPNMPGCALCSIZE>(hdr));
72 
73         case TBN_DROPDOWN:
74             return ProcessClick(reinterpret_cast<LPNMTOOLBAR>(hdr)->iItem);
75 
76         case TBN_HOTITEMCHANGE:
77             //return OnHotItemChange(reinterpret_cast<LPNMTBHOTITEM>(hdr), theResult);
78             return S_OK;
79 
80         case NM_CUSTOMDRAW:
81             hr = OnCustomDraw(reinterpret_cast<LPNMTBCUSTOMDRAW>(hdr), &result);
82             if (theResult)
83                 *theResult = result;
84             return hr;
85 
86         case TBN_GETINFOTIP:
87             return OnGetInfoTip(reinterpret_cast<LPNMTBGETINFOTIP>(hdr));
88 
89             // Silence unhandled items so that they don't print as unknown
90         case RBN_CHILDSIZE:
91             return S_OK;
92 
93         case TTN_GETDISPINFO:
94             return S_OK;
95 
96         case NM_RELEASEDCAPTURE:
97             break;
98 
99         case NM_CLICK:
100         case NM_RDOWN:
101         case NM_LDOWN:
102             break;
103 
104         case TBN_GETDISPINFO:
105             break;
106 
107         case TBN_BEGINDRAG:
108         case TBN_ENDDRAG:
109             break;
110 
111         case NM_TOOLTIPSCREATED:
112             break;
113 
114         case TBN_DRAGOUT: return S_FALSE;
115 
116         default:
117             TRACE("WM_NOTIFY unknown code %d, %d\n", hdr->code, hdr->idFrom);
118             return S_OK;
119         }
120         return S_FALSE;
121     case WM_WININICHANGE:
122         if (wParam == SPI_SETFLATMENU)
123         {
124             SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
125         }
126     }
127 
128     return S_FALSE;
129 }
130 
131 HRESULT CMenuToolbarBase::DisableMouseTrack(BOOL bDisable)
132 {
133     if (m_disableMouseTrack != bDisable)
134     {
135         m_disableMouseTrack = bDisable;
136         TRACE("DisableMouseTrack %d\n", bDisable);
137     }
138     return S_OK;
139 }
140 
141 HRESULT CMenuToolbarBase::OnPagerCalcSize(LPNMPGCALCSIZE csize)
142 {
143     SIZE tbs;
144     GetSizes(NULL, &tbs, NULL);
145     if (csize->dwFlag == PGF_CALCHEIGHT)
146     {
147         csize->iHeight = tbs.cy;
148     }
149     else if (csize->dwFlag == PGF_CALCWIDTH)
150     {
151         csize->iWidth = tbs.cx;
152     }
153     return S_OK;
154 }
155 
156 HRESULT CMenuToolbarBase::OnCustomDraw(LPNMTBCUSTOMDRAW cdraw, LRESULT * theResult)
157 {
158     bool     isHot, isPopup, isActive;
159     TBBUTTONINFO btni;
160 
161     switch (cdraw->nmcd.dwDrawStage)
162     {
163     case CDDS_PREPAINT:
164         *theResult = CDRF_NOTIFYITEMDRAW;
165         return S_OK;
166 
167     case CDDS_ITEMPREPAINT:
168 
169         HWND tlw;
170         m_menuBand->_GetTopLevelWindow(&tlw);
171 
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);
176 
177         if (m_hotItem < 0 && isPopup)
178             isHot = TRUE;
179 
180         if ((m_useFlatMenus && isHot) || (m_initFlags & SMINIT_VERTICAL))
181         {
182             COLORREF clrText;
183             HBRUSH   bgBrush;
184             RECT rc = cdraw->nmcd.rc;
185             HDC hdc = cdraw->nmcd.hdc;
186 
187             // Remove HOT and CHECKED flags (will restore HOT if necessary)
188             cdraw->nmcd.uItemState &= ~(CDIS_HOT | CDIS_CHECKED);
189 
190             // Decide on the colors
191             if (isHot)
192             {
193                 cdraw->nmcd.uItemState |= CDIS_HOT;
194 
195                 clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
196                 bgBrush = GetSysColorBrush(m_useFlatMenus ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT);
197             }
198             else
199             {
200                 clrText = GetSysColor(COLOR_MENUTEXT);
201                 bgBrush = GetSysColorBrush(COLOR_MENU);
202             }
203 
204             // Paint the background color with the selected color
205             FillRect(hdc, &rc, bgBrush);
206 
207             // Set the text color in advance, this color will be assigned when the ITEMPOSTPAINT triggers
208             SetTextColor(hdc, clrText);
209 
210             // Set the text color, will be used by the internal drawing code
211             cdraw->clrText = clrText;
212             cdraw->iListGap += 4;
213 
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
216         }
217         else
218         {
219             // Set the text color, will be used by the internal drawing code
220             cdraw->clrText = GetSysColor(isActive ? COLOR_MENUTEXT : COLOR_GRAYTEXT);
221 
222             // Remove HOT and CHECKED flags (will restore HOT if necessary)
223             cdraw->nmcd.uItemState &= ~CDIS_HOT;
224 
225             // Decide on the colors
226             if (isHot)
227             {
228                 cdraw->nmcd.uItemState |= CDIS_HOT;
229             }
230 
231             *theResult = 0;
232         }
233 
234         return S_OK;
235 
236     case CDDS_ITEMPOSTPAINT:
237 
238         // Fetch the button style
239         btni.cbSize = sizeof(btni);
240         btni.dwMask = TBIF_STYLE;
241         GetButtonInfo(cdraw->nmcd.dwItemSpec, &btni);
242 
243         // Check if we need to draw a submenu arrow
244         if (btni.fsStyle & BTNS_DROPDOWN)
245         {
246             // TODO: Support RTL text modes by drawing a leftwards arrow aligned to the left of the control
247 
248             // "8" is the rightwards dropdown arrow in the Marlett font
249             WCHAR text [] = L"8";
250 
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);
254 
255             // Tweak the alignment by 1 pixel so the menu draws like the Windows start menu.
256             RECT rc = cdraw->nmcd.rc;
257             rc.right += 1;
258 
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);
261         }
262         *theResult = TRUE;
263         return S_OK;
264     }
265     return S_OK;
266 }
267 
268 CMenuToolbarBase::CMenuToolbarBase(CMenuBand *menuBand, BOOL usePager) :
269     m_pager(this, 1),
270     m_useFlatMenus(FALSE),
271     m_disableMouseTrack(FALSE),
272     m_timerEnabled(FALSE),
273     m_menuBand(menuBand),
274     m_dwMenuFlags(0),
275     m_hasSizes(FALSE),
276     m_usePager(usePager),
277     m_hotBar(NULL),
278     m_hotItem(-1),
279     m_popupBar(NULL),
280     m_popupItem(-1),
281     m_isTrackingPopup(FALSE),
282     m_cancelingPopup(FALSE)
283 {
284     m_idealSize.cx = 0;
285     m_idealSize.cy = 0;
286     m_itemSize.cx = 0;
287     m_itemSize.cy = 0;
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");
292 }
293 
294 CMenuToolbarBase::~CMenuToolbarBase()
295 {
296     ClearToolbar();
297 
298     if (m_hWnd)
299         DestroyWindow();
300 
301     if (m_pager.m_hWnd)
302         m_pager.DestroyWindow();
303 
304     DeleteObject(m_marlett);
305 }
306 
307 void CMenuToolbarBase::InvalidateDraw()
308 {
309     InvalidateRect(NULL, FALSE);
310 }
311 
312 HRESULT CMenuToolbarBase::ShowDW(BOOL fShow)
313 {
314     ShowWindow(fShow ? SW_SHOW : SW_HIDE);
315 
316     // Ensure that the right image list is assigned to the toolbar
317     UpdateImageLists();
318 
319     // For custom-drawing
320     SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
321 
322     return S_OK;
323 }
324 
325 HRESULT CMenuToolbarBase::UpdateImageLists()
326 {
327     if ((m_initFlags & (SMINIT_TOPLEVEL | SMINIT_VERTICAL)) == SMINIT_TOPLEVEL) // not vertical.
328     {
329         // No image list, prevents the buttons from having a margin at the left side
330         SetImageList(NULL);
331         return S_OK;
332     }
333 
334     // Assign the correct imagelist and padding based on the current icon size
335 
336     int shiml;
337     if (m_menuBand->UseBigIcons())
338     {
339         shiml = SHIL_LARGE;
340         SetPadding(4, 0);
341     }
342     else
343     {
344         shiml = SHIL_SMALL;
345         SetPadding(4, 4);
346     }
347 
348     IImageList * piml;
349     HRESULT hr = SHGetImageList(shiml, IID_PPV_ARG(IImageList, &piml));
350     if (FAILED_UNEXPECTEDLY(hr))
351     {
352         SetImageList(NULL);
353     }
354     else
355     {
356         SetImageList((HIMAGELIST)piml);
357     }
358     return S_OK;
359 }
360 
361 HRESULT CMenuToolbarBase::Close()
362 {
363     if (m_hWnd)
364         DestroyWindow();
365 
366     if (m_pager.m_hWnd)
367         m_pager.DestroyWindow();
368 
369     return S_OK;
370 }
371 
372 HRESULT CMenuToolbarBase::CreateToolbar(HWND hwndParent, DWORD dwFlags)
373 {
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;
378 
379     if (dwFlags & SMINIT_VERTICAL)
380     {
381         // Activate vertical semantics
382         tbStyles |= CCS_VERT;
383 
384 #if USE_TBSTYLE_EX_VERTICAL
385         tbExStyles |= TBSTYLE_EX_VERTICAL;
386 #endif
387     }
388 
389     m_initFlags = dwFlags;
390 
391     // Get a temporary rect to use while creating the toolbar window.
392     // Ensure that it is not a null rect.
393     RECT rc;
394     if (!::GetClientRect(hwndParent, &rc) ||
395         (rc.left == rc.right) ||
396         (rc.top == rc.bottom))
397     {
398         rc.left = 0;
399         rc.top = 0;
400         rc.right = 1;
401         rc.bottom = 1;
402     }
403 
404     SubclassWindow(CToolbar::Create(hwndParent, tbStyles, tbExStyles));
405 
406     SetWindowTheme(m_hWnd, L"", L"");
407 
408     SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
409 
410     m_menuBand->AdjustForTheme(m_useFlatMenus);
411 
412     // If needed, create the pager.
413     if (m_usePager)
414     {
415         LONG pgStyles = PGS_VERT | WS_CHILD | WS_VISIBLE;
416         LONG pgExStyles = 0;
417 
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);
422 
423         m_pager.SubclassWindow(hwndPager);
424 
425         ::SetParent(m_hWnd, hwndPager);
426 
427         m_pager.SendMessageW(PGM_SETCHILD, 0, reinterpret_cast<LPARAM>(m_hWnd));
428     }
429 
430     // Configure the image lists
431     UpdateImageLists();
432 
433     return S_OK;
434 }
435 
436 HRESULT CMenuToolbarBase::GetSizes(SIZE* pMinSize, SIZE* pMaxSize, SIZE* pIntegralSize)
437 {
438     if (pMinSize)
439         *pMinSize = m_idealSize;
440     if (pMaxSize)
441         *pMaxSize = m_idealSize;
442     if (pIntegralSize)
443         *pIntegralSize = m_itemSize;
444 
445     if (m_hasSizes)
446         return S_OK;
447 
448     TRACE("Sizes out of date, recalculating.\n");
449 
450     if (!m_hWnd)
451     {
452         return S_OK;
453     }
454 
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);
458 
459     TRACE("Ideal Size: (%d, %d) for %d buttons\n", m_idealSize, GetButtonCount());
460 
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);
465     m_hasSizes = TRUE;
466 
467     if (pMinSize)
468         *pMinSize = m_idealSize;
469     if (pMaxSize)
470         *pMaxSize = m_idealSize;
471     if (pIntegralSize)
472         *pIntegralSize = m_itemSize;
473 
474     return S_OK;
475 }
476 
477 HRESULT CMenuToolbarBase::SetPosSize(int x, int y, int cx, int cy)
478 {
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
481     if (m_pager.m_hWnd)
482     {
483         SetWindowPos(NULL, x, y, cx, m_idealSize.cy, 0);
484         m_pager.SetWindowPos(NULL, x, y, cx, cy, 0);
485     }
486     else
487     {
488         SetWindowPos(NULL, x, y, cx, cy, 0);
489     }
490 
491     // In a vertical menu, resize the buttons to fit the width
492     if (m_initFlags & SMINIT_VERTICAL)
493     {
494         DWORD btnSize = GetButtonSize();
495         SetButtonSize(cx, GET_Y_LPARAM(btnSize));
496     }
497 
498     return S_OK;
499 }
500 
501 HRESULT CMenuToolbarBase::IsWindowOwner(HWND hwnd)
502 {
503     if (m_hWnd && m_hWnd == hwnd) return S_OK;
504     if (m_pager.m_hWnd && m_pager.m_hWnd == hwnd) return S_OK;
505     return S_FALSE;
506 }
507 
508 HRESULT CMenuToolbarBase::GetWindow(HWND *phwnd)
509 {
510     if (!phwnd)
511         return E_FAIL;
512 
513     if (m_pager.m_hWnd)
514         *phwnd = m_pager.m_hWnd;
515     else
516         *phwnd = m_hWnd;
517 
518     return S_OK;
519 }
520 
521 HRESULT CMenuToolbarBase::OnGetInfoTip(NMTBGETINFOTIP * tip)
522 {
523     INT index;
524     DWORD_PTR dwData;
525 
526     INT iItem = tip->iItem;
527 
528     GetDataFromId(iItem, &index, &dwData);
529 
530     return InternalGetTooltip(iItem, index, dwData, tip->pszText, tip->cchTextMax);
531 }
532 
533 HRESULT CMenuToolbarBase::OnPopupTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
534 {
535     if (wParam != TIMERID_HOTTRACK)
536     {
537         bHandled = FALSE;
538         return 0;
539     }
540 
541     KillTimer(TIMERID_HOTTRACK);
542 
543     if (!m_timerEnabled)
544         return 0;
545 
546     m_timerEnabled = FALSE;
547 
548     if (m_hotItem < 0)
549         return 0;
550 
551     // Returns S_FALSE if the current item did not show a submenu
552     HRESULT hr = PopupItem(m_hotItem, FALSE);
553     if (hr != S_FALSE)
554         return 0;
555 
556     // If we didn't switch submenus, cancel the current popup regardless
557     if (m_popupBar)
558     {
559         HRESULT hr = CancelCurrentPopup();
560         if (FAILED_UNEXPECTEDLY(hr))
561             return 0;
562     }
563 
564     return 0;
565 }
566 
567 HRESULT CMenuToolbarBase::KillPopupTimer()
568 {
569     if (m_timerEnabled)
570     {
571         m_timerEnabled = FALSE;
572         KillTimer(TIMERID_HOTTRACK);
573         return S_OK;
574     }
575     return S_FALSE;
576 }
577 
578 HRESULT CMenuToolbarBase::ChangeHotItem(CMenuToolbarBase * toolbar, INT item, DWORD dwFlags)
579 {
580     // Ignore the change if it already matches the stored info
581     if (m_hotBar == toolbar && m_hotItem == item)
582         return S_FALSE;
583 
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)
587     {
588         TRACE("Hot item change prevented by DisableMouseTrack\n");
589         return S_OK;
590     }
591 
592     // Notify the toolbar if the hot-tracking left this toolbar
593     if (m_hotBar == this && toolbar != this)
594     {
595         SetHotItem(-1);
596     }
597 
598     TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar, m_hotItem, toolbar, item);
599     m_hotBar = toolbar;
600     m_hotItem = item;
601 
602     if (m_hotBar == this)
603     {
604         if (m_isTrackingPopup && !(m_initFlags & SMINIT_VERTICAL))
605         {
606             // If the menubar has an open submenu, switch to the new item's submenu immediately
607             PopupItem(m_hotItem, FALSE);
608         }
609         else if (dwFlags & HICF_MOUSE)
610         {
611             // Vertical menus show/hide the submenu after a delay,
612             // but only with the mouse.
613             if (m_initFlags & SMINIT_VERTICAL)
614             {
615                 DWORD elapsed = 0;
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);
620             }
621         }
622         else
623         {
624             TBBUTTONINFO info;
625             info.cbSize = sizeof(info);
626             info.dwMask = 0;
627 
628             int index = GetButtonInfo(item, &info);
629 
630             SetHotItem(index);
631         }
632     }
633 
634     InvalidateDraw();
635     return S_OK;
636 }
637 
638 HRESULT CMenuToolbarBase::ChangePopupItem(CMenuToolbarBase * toolbar, INT item)
639 {
640     // Ignore the change if it already matches the stored info
641     if (m_popupBar == toolbar && m_popupItem == item)
642         return S_FALSE;
643 
644     // Notify the toolbar if the popup-tracking this toolbar
645     if (m_popupBar == this && toolbar != this)
646     {
647         CheckButton(m_popupItem, FALSE);
648         m_isTrackingPopup = FALSE;
649     }
650 
651     m_popupBar = toolbar;
652     m_popupItem = item;
653 
654     if (m_popupBar == this)
655     {
656         CheckButton(m_popupItem, TRUE);
657     }
658 
659     InvalidateDraw();
660     return S_OK;
661 }
662 
663 LRESULT CMenuToolbarBase::IsTrackedItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
664 {
665     TBBUTTON btn;
666     INT idx = (INT)wParam;
667 
668     if (m_hotBar != this)
669         return S_FALSE;
670 
671     if (idx < 0)
672         return S_FALSE;
673 
674     if (!GetButton(idx, &btn))
675         return E_FAIL;
676 
677     if (m_hotItem == btn.idCommand)
678         return S_OK;
679 
680     if (m_popupItem == btn.idCommand)
681         return S_OK;
682 
683     return S_FALSE;
684 }
685 
686 LRESULT CMenuToolbarBase::ChangeTrackedItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
687 {
688     TBBUTTON btn;
689     BOOL wasTracking = LOWORD(lParam);
690     BOOL mouse = HIWORD(lParam);
691     INT idx = (INT)wParam;
692 
693     if (idx < 0)
694     {
695         m_isTrackingPopup = FALSE;
696         return m_menuBand->_ChangeHotItem(NULL, -1, HICF_MOUSE);
697     }
698 
699     if (!GetButton(idx, &btn))
700         return E_FAIL;
701 
702     TRACE("ChangeTrackedItem %d, %d\n", idx, wasTracking);
703     m_isTrackingPopup = wasTracking;
704     return m_menuBand->_ChangeHotItem(this, btn.idCommand, mouse ? HICF_MOUSE : 0);
705 }
706 
707 HRESULT CMenuToolbarBase::PopupSubMenu(UINT iItem, UINT index, IShellMenu* childShellMenu, BOOL keyInitiated)
708 {
709     // Calculate the submenu position and exclude area
710     RECT rc = { 0 };
711 
712     if (!GetItemRect(index, &rc))
713         return E_FAIL;
714 
715     POINT a = { rc.left, rc.top };
716     POINT b = { rc.right, rc.bottom };
717 
718     ClientToScreen(&a);
719     ClientToScreen(&b);
720 
721     POINTL pt = { a.x, b.y };
722     RECTL rcl = { a.x, a.y, b.x, b.y };
723 
724     if (m_initFlags & SMINIT_VERTICAL)
725     {
726         // FIXME: Hardcoding this here feels hacky.
727         if (IsAppThemed())
728         {
729             pt.x = b.x - 1;
730             pt.y = a.y - 1;
731         }
732         else
733         {
734             pt.x = b.x - 3;
735             pt.y = a.y - 3;
736         }
737     }
738 
739     // Display the submenu
740     m_isTrackingPopup = TRUE;
741 
742     m_menuBand->_ChangePopupItem(this, iItem);
743     m_menuBand->_OnPopupSubMenu(childShellMenu, &pt, &rcl, keyInitiated);
744 
745     return S_OK;
746 }
747 
748 HRESULT CMenuToolbarBase::PopupSubMenu(UINT iItem, UINT index, HMENU menu)
749 {
750     // Calculate the submenu position and exclude area
751     RECT rc = { 0 };
752 
753     if (!GetItemRect(index, &rc))
754         return E_FAIL;
755 
756     POINT a = { rc.left, rc.top };
757     POINT b = { rc.right, rc.bottom };
758 
759     ClientToScreen(&a);
760     ClientToScreen(&b);
761 
762     POINT pt = { a.x, b.y };
763     RECT rcl = { a.x, a.y, b.x, b.y };
764 
765     if (m_initFlags & SMINIT_VERTICAL)
766     {
767         pt.x = b.x;
768         pt.y = a.y;
769     }
770 
771     HMENU popup = GetSubMenu(menu, index);
772 
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;
779 
780     return S_OK;
781 }
782 
783 HRESULT CMenuToolbarBase::TrackContextMenu(IContextMenu* contextMenu, POINT pt)
784 {
785     // Cancel submenus
786     m_menuBand->_KillPopupTimers();
787     if (m_popupBar)
788         m_menuBand->_CancelCurrentPopup();
789 
790     // Display the context menu
791     return m_menuBand->_TrackContextMenu(contextMenu, pt.x, pt.y);
792 }
793 
794 HRESULT CMenuToolbarBase::BeforeCancelPopup()
795 {
796     m_cancelingPopup = TRUE;
797     TRACE("BeforeCancelPopup\n");
798     return S_OK;
799 }
800 
801 HRESULT CMenuToolbarBase::ProcessClick(INT iItem)
802 {
803     if (m_disableMouseTrack)
804     {
805         TRACE("Item click prevented by DisableMouseTrack\n");
806         return S_OK;
807     }
808 
809     // If a button is clicked while a submenu was open, cancel the submenu.
810     if (!(m_initFlags & SMINIT_VERTICAL) && m_isTrackingPopup)
811     {
812         TRACE("OnCommand cancelled because it was tracking submenu.\n");
813         return S_FALSE;
814     }
815 
816     if (PopupItem(iItem, FALSE) == S_OK)
817     {
818         TRACE("PopupItem returned S_OK\n");
819         return S_FALSE;
820     }
821 
822     TRACE("Executing...\n");
823 
824     return m_menuBand->_MenuItemSelect(MPOS_EXECUTE);
825 }
826 
827 HRESULT CMenuToolbarBase::ProcessContextMenu(INT iItem)
828 {
829     INT index;
830     DWORD_PTR data;
831 
832     GetDataFromId(iItem, &index, &data);
833 
834     DWORD pos = GetMessagePos();
835     POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
836 
837     return InternalContextMenu(iItem, index, data, pt);
838 }
839 
840 HRESULT CMenuToolbarBase::MenuBarMouseDown(INT iIndex, BOOL isLButton)
841 {
842     TBBUTTON btn;
843 
844     GetButton(iIndex, &btn);
845 
846     if ((m_initFlags & SMINIT_VERTICAL)
847         || m_popupBar
848         || m_cancelingPopup)
849     {
850         m_cancelingPopup = FALSE;
851         return S_OK;
852     }
853 
854     return ProcessClick(btn.idCommand);
855 }
856 
857 HRESULT CMenuToolbarBase::MenuBarMouseUp(INT iIndex, BOOL isLButton)
858 {
859     TBBUTTON btn;
860 
861     m_cancelingPopup = FALSE;
862 
863     if (!(m_initFlags & SMINIT_VERTICAL))
864         return S_OK;
865 
866     GetButton(iIndex, &btn);
867 
868     if (isLButton)
869         return ProcessClick(btn.idCommand);
870     else
871         return ProcessContextMenu(btn.idCommand);
872 }
873 
874 HRESULT CMenuToolbarBase::PrepareExecuteItem(INT iItem)
875 {
876     this->m_menuBand->_KillPopupTimers();
877 
878     m_executeItem = iItem;
879     return GetDataFromId(iItem, &m_executeIndex, &m_executeData);
880 }
881 
882 HRESULT CMenuToolbarBase::ExecuteItem()
883 {
884     return InternalExecuteItem(m_executeItem, m_executeItem, m_executeData);
885 }
886 
887 HRESULT CMenuToolbarBase::KeyboardItemChange(DWORD dwSelectType)
888 {
889     int prev = m_hotItem;
890     int index = -1;
891 
892     if (dwSelectType != 0xFFFFFFFF)
893     {
894         int count = GetButtonCount();
895 
896         if (dwSelectType == VK_HOME)
897         {
898             index = 0;
899             dwSelectType = VK_DOWN;
900         }
901         else if (dwSelectType == VK_END)
902         {
903             index = count - 1;
904             dwSelectType = VK_UP;
905         }
906         else
907         {
908             if (m_hotItem >= 0)
909             {
910                 TBBUTTONINFO info = { 0 };
911                 info.cbSize = sizeof(TBBUTTONINFO);
912                 info.dwMask = 0;
913                 index = GetButtonInfo(m_hotItem, &info);
914             }
915 
916             if (index < 0)
917             {
918                 if (dwSelectType == VK_UP)
919                 {
920                     index = count - 1;
921                 }
922                 else if (dwSelectType == VK_DOWN)
923                 {
924                     index = 0;
925                 }
926             }
927             else
928             {
929                 if (dwSelectType == VK_UP)
930                 {
931                     index--;
932                 }
933                 else if (dwSelectType == VK_DOWN)
934                 {
935                     index++;
936                 }
937             }
938         }
939 
940         TBBUTTON btn = { 0 };
941         while (index >= 0 && index < count)
942         {
943             DWORD res = GetButton(index, &btn);
944             if (!res)
945                 return E_FAIL;
946 
947             if (btn.dwData)
948             {
949                 if (prev != btn.idCommand)
950                 {
951                     TRACE("Setting Hot item to %d\n", index);
952                     if (!(m_initFlags & SMINIT_VERTICAL) && m_isTrackingPopup)
953                     {
954                         HWND tlw;
955                         m_menuBand->_GetTopLevelWindow(&tlw);
956                         SendMessageW(tlw, WM_CANCELMODE, 0, 0);
957                         PostMessageW(WM_USER_CHANGETRACKEDITEM, index, MAKELPARAM(m_isTrackingPopup, FALSE));
958                     }
959                     else
960                         m_menuBand->_ChangeHotItem(this, btn.idCommand, 0);
961                 }
962                 return S_OK;
963             }
964 
965             if (dwSelectType == VK_UP)
966             {
967                 index--;
968             }
969             else if (dwSelectType == VK_DOWN)
970             {
971                 index++;
972             }
973         }
974 
975         return S_FALSE;
976     }
977 
978     if (prev != -1)
979     {
980         TRACE("Setting Hot item to null\n");
981         m_menuBand->_ChangeHotItem(NULL, -1, 0);
982     }
983 
984     return S_FALSE;
985 }
986 
987 HRESULT CMenuToolbarBase::AddButton(DWORD commandId, LPCWSTR caption, BOOL hasSubMenu, INT iconId, DWORD_PTR buttonData, BOOL last)
988 {
989     TBBUTTON tbb = { 0 };
990 
991     tbb.fsState = TBSTATE_ENABLED;
992 #if !USE_TBSTYLE_EX_VERTICAL
993     if (!last && (m_initFlags & SMINIT_VERTICAL))
994         tbb.fsState |= TBSTATE_WRAP;
995 #endif
996     tbb.fsStyle = BTNS_CHECKGROUP;
997 
998     if (hasSubMenu && (m_initFlags & SMINIT_VERTICAL))
999         tbb.fsStyle |= BTNS_DROPDOWN;
1000 
1001     if (!(m_initFlags & SMINIT_VERTICAL))
1002         tbb.fsStyle |= BTNS_AUTOSIZE;
1003 
1004     tbb.iString = (INT_PTR) caption;
1005     tbb.idCommand = commandId;
1006 
1007     tbb.iBitmap = iconId;
1008     tbb.dwData = buttonData;
1009 
1010     m_hasSizes = FALSE;
1011 
1012     if (!AddButtons(1, &tbb))
1013         return HRESULT_FROM_WIN32(GetLastError());
1014     return S_OK;
1015 }
1016 
1017 HRESULT CMenuToolbarBase::AddSeparator(BOOL last)
1018 {
1019     TBBUTTON tbb = { 0 };
1020 
1021     tbb.fsState = TBSTATE_ENABLED;
1022 #if !USE_TBSTYLE_EX_VERTICAL
1023     if (!last && (m_initFlags & SMINIT_VERTICAL))
1024         tbb.fsState |= TBSTATE_WRAP;
1025 #endif
1026     tbb.fsStyle = BTNS_SEP;
1027     tbb.iBitmap = 0;
1028 
1029     m_hasSizes = FALSE;
1030 
1031     if (!AddButtons(1, &tbb))
1032         return HRESULT_FROM_WIN32(GetLastError());
1033 
1034     return S_OK;
1035 }
1036 
1037 HRESULT CMenuToolbarBase::AddPlaceholder()
1038 {
1039     TBBUTTON tbb = { 0 };
1040     WCHAR MenuString[128];
1041 
1042     LoadStringW(GetModuleHandle(L"shell32.dll"), IDS_MENU_EMPTY, MenuString, _countof(MenuString));
1043 
1044     tbb.fsState = 0;
1045     tbb.fsStyle = 0;
1046     tbb.iString = (INT_PTR) MenuString;
1047     tbb.iBitmap = -1;
1048 
1049     m_hasSizes = FALSE;
1050 
1051     if (!AddButtons(1, &tbb))
1052         return HRESULT_FROM_WIN32(GetLastError());
1053 
1054     return S_OK;
1055 }
1056 
1057 HRESULT CMenuToolbarBase::ClearToolbar()
1058 {
1059     while (DeleteButton(0))
1060     {
1061         // empty;
1062     }
1063     m_hasSizes = FALSE;
1064     return S_OK;
1065 }
1066 
1067 HRESULT CMenuToolbarBase::GetDataFromId(INT iItem, INT* pIndex, DWORD_PTR* pData)
1068 {
1069     if (pData)
1070         *pData = NULL;
1071 
1072     if (pIndex)
1073         *pIndex = -1;
1074 
1075     if (iItem < 0)
1076         return S_OK;
1077 
1078     TBBUTTONINFO info = { 0 };
1079 
1080     info.cbSize = sizeof(TBBUTTONINFO);
1081     info.dwMask = TBIF_COMMAND | TBIF_LPARAM;
1082 
1083     int index = GetButtonInfo(iItem, &info);
1084     if (index < 0)
1085         return E_FAIL;
1086 
1087     if (pIndex)
1088         *pIndex = index;
1089 
1090     if (pData)
1091         *pData = info.lParam;
1092 
1093     return S_OK;
1094 }
1095 
1096 HRESULT CMenuToolbarBase::CancelCurrentPopup()
1097 {
1098     return m_menuBand->_CancelCurrentPopup();
1099 }
1100 
1101 HRESULT CMenuToolbarBase::PopupItem(INT iItem, BOOL keyInitiated)
1102 {
1103     INT index;
1104     DWORD_PTR dwData;
1105 
1106     if (iItem < 0)
1107         return S_OK;
1108 
1109     if (m_popupBar == this && m_popupItem == iItem)
1110         return S_OK;
1111 
1112     GetDataFromId(iItem, &index, &dwData);
1113 
1114     HRESULT hr = InternalHasSubMenu(iItem, index, dwData);
1115     if (hr != S_OK)
1116         return hr;
1117 
1118     if (m_popupBar)
1119     {
1120         HRESULT hr = CancelCurrentPopup();
1121         if (FAILED_UNEXPECTEDLY(hr))
1122             return hr;
1123     }
1124 
1125     if (!(m_initFlags & SMINIT_VERTICAL))
1126     {
1127         TRACE("PopupItem non-vertical %d %d\n", index, iItem);
1128         m_menuBand->_ChangeHotItem(this, iItem, 0);
1129     }
1130 
1131     return InternalPopupItem(iItem, index, dwData, keyInitiated);
1132 }
1133 
1134 CMenuStaticToolbar::CMenuStaticToolbar(CMenuBand *menuBand) :
1135     CMenuToolbarBase(menuBand, FALSE),
1136     m_hmenu(NULL),
1137     m_hwndMenu(NULL)
1138 {
1139 }
1140 
1141 CMenuStaticToolbar::~CMenuStaticToolbar()
1142 {
1143 }
1144 
1145 HRESULT  CMenuStaticToolbar::GetMenu(
1146     _Out_opt_ HMENU *phmenu,
1147     _Out_opt_ HWND *phwnd,
1148     _Out_opt_ DWORD *pdwFlags)
1149 {
1150     if (phmenu)
1151         *phmenu = m_hmenu;
1152     if (phwnd)
1153         *phwnd = m_hwndMenu;
1154     if (pdwFlags)
1155         *pdwFlags = m_dwMenuFlags;
1156 
1157     return S_OK;
1158 }
1159 
1160 HRESULT  CMenuStaticToolbar::SetMenu(
1161     HMENU hmenu,
1162     HWND hwnd,
1163     DWORD dwFlags)
1164 {
1165     m_hmenu = hmenu;
1166     m_hwndMenu = hwnd;
1167     m_dwMenuFlags = dwFlags;
1168 
1169     ClearToolbar();
1170 
1171     return S_OK;
1172 }
1173 
1174 HRESULT CMenuStaticToolbar::FillToolbar(BOOL clearFirst)
1175 {
1176     int i;
1177     int ic = GetMenuItemCount(m_hmenu);
1178 
1179     if (clearFirst)
1180     {
1181         ClearToolbar();
1182     }
1183 
1184     int count = 0;
1185     for (i = 0; i < ic; i++)
1186     {
1187         BOOL last = i + 1 == ic;
1188 
1189         MENUITEMINFOW info;
1190 
1191         info.cbSize = sizeof(info);
1192         info.dwTypeData = NULL;
1193         info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID;
1194 
1195         if (!GetMenuItemInfoW(m_hmenu, i, TRUE, &info))
1196         {
1197             TRACE("Error obtaining info for menu item at pos=%d\n", i);
1198             continue;
1199         }
1200 
1201         count++;
1202 
1203         if (info.fType & MFT_SEPARATOR)
1204         {
1205             AddSeparator(last);
1206         }
1207         else if (!(info.fType & MFT_BITMAP))
1208         {
1209             info.cch++;
1210             info.dwTypeData = (PWSTR) HeapAlloc(GetProcessHeap(), 0, (info.cch + 1) * sizeof(WCHAR));
1211 
1212             info.fMask = MIIM_STRING | MIIM_SUBMENU | MIIM_ID;
1213             GetMenuItemInfoW(m_hmenu, i, TRUE, &info);
1214 
1215             SMINFO * sminfo = new SMINFO();
1216             sminfo->dwMask = SMIM_ICON | SMIM_FLAGS;
1217 
1218             HRESULT hr = m_menuBand->_CallCBWithItemId(info.wID, SMC_GETINFO, 0, reinterpret_cast<LPARAM>(sminfo));
1219             if (FAILED_UNEXPECTEDLY(hr))
1220             {
1221                 delete sminfo;
1222                 return hr;
1223             }
1224 
1225             AddButton(info.wID, info.dwTypeData, info.hSubMenu != NULL, sminfo->iIcon, reinterpret_cast<DWORD_PTR>(sminfo), last);
1226 
1227             HeapFree(GetProcessHeap(), 0, info.dwTypeData);
1228         }
1229     }
1230 
1231     return S_OK;
1232 }
1233 
1234 HRESULT CMenuStaticToolbar::InternalGetTooltip(INT iItem, INT index, DWORD_PTR dwData, LPWSTR pszText, INT cchTextMax)
1235 {
1236     //SMINFO * info = reinterpret_cast<SMINFO*>(dwData);
1237     UNIMPLEMENTED;
1238     return E_NOTIMPL;
1239 }
1240 
1241 HRESULT CMenuStaticToolbar::OnDeletingButton(const NMTOOLBAR * tb)
1242 {
1243     delete reinterpret_cast<SMINFO*>(tb->tbButton.dwData);
1244     return S_OK;
1245 }
1246 
1247 HRESULT CMenuStaticToolbar::InternalContextMenu(INT iItem, INT index, DWORD_PTR dwData, POINT pt)
1248 {
1249     CComPtr<IContextMenu> contextMenu;
1250     HRESULT hr = m_menuBand->_CallCBWithItemId(iItem, SMC_GETOBJECT,
1251         reinterpret_cast<WPARAM>(&IID_IContextMenu), reinterpret_cast<LPARAM>(&contextMenu));
1252     if (hr != S_OK)
1253         return hr;
1254 
1255     return TrackContextMenu(contextMenu, pt);
1256 }
1257 
1258 HRESULT CMenuStaticToolbar::InternalExecuteItem(INT iItem, INT index, DWORD_PTR data)
1259 {
1260     return m_menuBand->_CallCBWithItemId(iItem, SMC_EXEC, 0, 0);
1261 }
1262 
1263 HRESULT CMenuStaticToolbar::InternalPopupItem(INT iItem, INT index, DWORD_PTR dwData, BOOL keyInitiated)
1264 {
1265     SMINFO * nfo = reinterpret_cast<SMINFO*>(dwData);
1266     if (!nfo)
1267         return E_FAIL;
1268 
1269     if (nfo->dwFlags&SMIF_TRACKPOPUP)
1270     {
1271         return PopupSubMenu(iItem, index, m_hmenu);
1272     }
1273     else
1274     {
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))
1278             return hr;
1279 
1280         return PopupSubMenu(iItem, index, shellMenu, keyInitiated);
1281     }
1282 }
1283 
1284 HRESULT CMenuStaticToolbar::InternalHasSubMenu(INT iItem, INT index, DWORD_PTR dwData)
1285 {
1286     return ::GetSubMenu(m_hmenu, index) ? S_OK : S_FALSE;
1287 }
1288 
1289 CMenuSFToolbar::CMenuSFToolbar(CMenuBand * menuBand) :
1290     CMenuToolbarBase(menuBand, TRUE),
1291     m_shellFolder(NULL),
1292     m_idList(NULL),
1293     m_hKey(NULL)
1294 {
1295 }
1296 
1297 CMenuSFToolbar::~CMenuSFToolbar()
1298 {
1299 }
1300 
1301 int CALLBACK PidlListSort(void* item1, void* item2, LPARAM lParam)
1302 {
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);
1307     if (FAILED(hr))
1308     {
1309         // No way to cancel, so sort to equal.
1310         return 0;
1311     }
1312     return (int)(short)LOWORD(hr);
1313 }
1314 
1315 HRESULT CMenuSFToolbar::FillToolbar(BOOL clearFirst)
1316 {
1317     HRESULT hr;
1318 
1319     CComPtr<IEnumIDList> eidl;
1320     hr = m_shellFolder->EnumObjects(GetToolbar(), SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &eidl);
1321     if (FAILED_UNEXPECTEDLY(hr))
1322         return hr;
1323 
1324     HDPA dpaSort = DPA_Create(10);
1325 
1326     LPITEMIDLIST item = NULL;
1327     hr = eidl->Next(1, &item, NULL);
1328     while (hr == S_OK)
1329     {
1330         if (m_menuBand->_CallCBWithItemPidl(item, 0x10000000, 0, 0) == S_FALSE)
1331         {
1332             DPA_AppendPtr(dpaSort, item);
1333         }
1334         else
1335         {
1336             CoTaskMemFree(item);
1337         }
1338 
1339         hr = eidl->Next(1, &item, NULL);
1340     }
1341 
1342     // If no items were added, show the "empty" placeholder
1343     if (DPA_GetPtrCount(dpaSort) == 0)
1344     {
1345         DPA_Destroy(dpaSort);
1346         return AddPlaceholder();
1347     }
1348 
1349     TRACE("FillToolbar added %d items to the DPA\n", DPA_GetPtrCount(dpaSort));
1350 
1351     DPA_Sort(dpaSort, PidlListSort, (LPARAM) m_shellFolder.p);
1352 
1353     for (int i = 0; i<DPA_GetPtrCount(dpaSort);)
1354     {
1355         PWSTR MenuString;
1356 
1357         INT index = 0;
1358         INT indexOpen = 0;
1359 
1360         STRRET sr = { STRRET_CSTR, { 0 } };
1361 
1362         item = (LPITEMIDLIST)DPA_GetPtr(dpaSort, i);
1363 
1364         hr = m_shellFolder->GetDisplayNameOf(item, SIGDN_NORMALDISPLAY, &sr);
1365         if (FAILED_UNEXPECTEDLY(hr))
1366         {
1367             DPA_Destroy(dpaSort);
1368             return hr;
1369         }
1370 
1371         StrRetToStr(&sr, NULL, &MenuString);
1372 
1373         index = SHMapPIDLToSystemImageListIndex(m_shellFolder, item, &indexOpen);
1374 
1375         LPCITEMIDLIST itemc = item;
1376 
1377         SFGAOF attrs = SFGAO_FOLDER;
1378         hr = m_shellFolder->GetAttributesOf(1, &itemc, &attrs);
1379 
1380         DWORD_PTR dwData = reinterpret_cast<DWORD_PTR>(item);
1381 
1382         // Fetch next item already, so we know if the current one is the last
1383         i++;
1384 
1385         AddButton(i, MenuString, attrs & SFGAO_FOLDER, index, dwData, i >= DPA_GetPtrCount(dpaSort));
1386 
1387         CoTaskMemFree(MenuString);
1388     }
1389 
1390     DPA_Destroy(dpaSort);
1391     return hr;
1392 }
1393 
1394 HRESULT CMenuSFToolbar::InternalGetTooltip(INT iItem, INT index, DWORD_PTR dwData, LPWSTR pszText, INT cchTextMax)
1395 {
1396     //ITEMIDLIST * pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1397     UNIMPLEMENTED;
1398     return E_NOTIMPL;
1399 }
1400 
1401 HRESULT CMenuSFToolbar::OnDeletingButton(const NMTOOLBAR * tb)
1402 {
1403     ILFree(reinterpret_cast<LPITEMIDLIST>(tb->tbButton.dwData));
1404     return S_OK;
1405 }
1406 
1407 HRESULT CMenuSFToolbar::SetShellFolder(IShellFolder *psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags)
1408 {
1409     m_shellFolder = psf;
1410     m_idList = ILClone(pidlFolder);
1411     m_hKey = hKey;
1412     m_dwMenuFlags = dwFlags;
1413 
1414     ClearToolbar();
1415 
1416     return S_OK;
1417 }
1418 
1419 HRESULT CMenuSFToolbar::GetShellFolder(DWORD *pdwFlags, LPITEMIDLIST *ppidl, REFIID riid, void **ppv)
1420 {
1421     HRESULT hr;
1422 
1423     hr = m_shellFolder->QueryInterface(riid, ppv);
1424     if (FAILED_UNEXPECTEDLY(hr))
1425         return hr;
1426 
1427     if (pdwFlags)
1428         *pdwFlags = m_dwMenuFlags;
1429 
1430     if (ppidl)
1431     {
1432         LPITEMIDLIST pidl = NULL;
1433 
1434         if (m_idList)
1435         {
1436             pidl = ILClone(m_idList);
1437             if (!pidl)
1438             {
1439                 ERR("ILClone failed!\n");
1440                 (*reinterpret_cast<IUnknown**>(ppv))->Release();
1441                 return E_FAIL;
1442             }
1443         }
1444 
1445         *ppidl = pidl;
1446     }
1447 
1448     return hr;
1449 }
1450 
1451 HRESULT CMenuSFToolbar::InternalContextMenu(INT iItem, INT index, DWORD_PTR dwData, POINT pt)
1452 {
1453     HRESULT hr;
1454     CComPtr<IContextMenu> contextMenu = NULL;
1455     LPCITEMIDLIST pidl = reinterpret_cast<LPCITEMIDLIST>(dwData);
1456 
1457     hr = m_shellFolder->GetUIObjectOf(GetToolbar(), 1, &pidl, IID_NULL_PPV_ARG(IContextMenu, &contextMenu));
1458     if (FAILED_UNEXPECTEDLY(hr))
1459     {
1460         return hr;
1461     }
1462 
1463     hr = TrackContextMenu(contextMenu, pt);
1464 
1465     return hr;
1466 }
1467 
1468 HRESULT CMenuSFToolbar::InternalExecuteItem(INT iItem, INT index, DWORD_PTR data)
1469 {
1470     return m_menuBand->_CallCBWithItemPidl(reinterpret_cast<LPITEMIDLIST>(data), SMC_SFEXEC, 0, 0);
1471 }
1472 
1473 HRESULT CMenuSFToolbar::InternalPopupItem(INT iItem, INT index, DWORD_PTR dwData, BOOL keyInitiated)
1474 {
1475     HRESULT hr;
1476     UINT uId;
1477     UINT uIdAncestor;
1478     DWORD flags;
1479     CComPtr<IShellMenuCallback> psmc;
1480     CComPtr<IShellMenu> shellMenu;
1481 
1482     LPITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1483 
1484     if (!pidl)
1485         return E_FAIL;
1486 
1487     hr = CMenuBand_CreateInstance(IID_PPV_ARG(IShellMenu, &shellMenu));
1488     if (FAILED_UNEXPECTEDLY(hr))
1489         return hr;
1490 
1491     m_menuBand->GetMenuInfo(&psmc, &uId, &uIdAncestor, &flags);
1492 
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))
1496         return hr;
1497 
1498     CComPtr<IShellFolder> childFolder;
1499     hr = m_shellFolder->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &childFolder));
1500     if (FAILED_UNEXPECTEDLY(hr))
1501         return hr;
1502 
1503     hr = shellMenu->SetShellFolder(childFolder, NULL, NULL, 0);
1504     if (FAILED_UNEXPECTEDLY(hr))
1505         return hr;
1506 
1507     return PopupSubMenu(iItem, index, shellMenu, keyInitiated);
1508 }
1509 
1510 HRESULT CMenuSFToolbar::InternalHasSubMenu(INT iItem, INT index, DWORD_PTR dwData)
1511 {
1512     HRESULT hr;
1513     LPCITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1514 
1515     SFGAOF attrs = SFGAO_FOLDER;
1516     hr = m_shellFolder->GetAttributesOf(1, &pidl, &attrs);
1517     if (FAILED_UNEXPECTEDLY(hr))
1518         return hr;
1519 
1520     return (attrs & SFGAO_FOLDER) ? S_OK : S_FALSE;
1521 }
1522