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