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 <windowsx.h>
22 #include <commoncontrols.h>
23 #include <shlwapi_undoc.h>
24 
25 #include "CMenuBand.h"
26 #include "CMenuToolbars.h"
27 #include "CMenuFocusManager.h"
28 
29 WINE_DEFAULT_DEBUG_CHANNEL(CMenuBand);
30 
31 #undef UNIMPLEMENTED
32 
33 #define UNIMPLEMENTED TRACE("%s is UNIMPLEMENTED!\n", __FUNCTION__)
34 
35 CMenuBand::CMenuBand() :
36     m_staticToolbar(NULL),
37     m_SFToolbar(NULL),
38     m_site(NULL),
39     m_psmc(NULL),
40     m_subMenuChild(NULL),
41     m_subMenuParent(NULL),
42     m_childBand(NULL),
43     m_parentBand(NULL),
44     m_hmenu(NULL),
45     m_menuOwner(NULL),
46     m_useBigIcons(FALSE),
47     m_topLevelWindow(NULL),
48     m_hotBar(NULL),
49     m_hotItem(-1),
50     m_popupBar(NULL),
51     m_popupItem(-1),
52     m_Show(FALSE),
53     m_shellBottom(FALSE),
54     m_trackedPopup(NULL),
55     m_trackedHwnd(NULL)
56 {
57     m_focusManager = CMenuFocusManager::AcquireManager();
58 }
59 
60 CMenuBand::~CMenuBand()
61 {
62     CMenuFocusManager::ReleaseManager(m_focusManager);
63 
64     delete m_staticToolbar;
65     delete m_SFToolbar;
66 
67     if (m_hmenu)
68         DestroyMenu(m_hmenu);
69 }
70 
71 HRESULT STDMETHODCALLTYPE  CMenuBand::Initialize(
72     IShellMenuCallback *psmc,
73     UINT uId,
74     UINT uIdAncestor,
75     DWORD dwFlags)
76 {
77     if (m_psmc != psmc)
78         m_psmc = psmc;
79     m_uId = uId;
80     m_uIdAncestor = uIdAncestor;
81     m_dwFlags = dwFlags;
82 
83     if (m_psmc)
84     {
85         _CallCB(SMC_CREATE, 0, reinterpret_cast<LPARAM>(&m_UserData));
86     }
87 
88     return S_OK;
89 }
90 
91 HRESULT STDMETHODCALLTYPE  CMenuBand::GetMenuInfo(
92     IShellMenuCallback **ppsmc,
93     UINT *puId,
94     UINT *puIdAncestor,
95     DWORD *pdwFlags)
96 {
97     if (!pdwFlags) // maybe?
98         return E_INVALIDARG;
99 
100     if (ppsmc)
101     {
102         *ppsmc = m_psmc;
103         if (*ppsmc)
104             (*ppsmc)->AddRef();
105     }
106 
107     if (puId)
108         *puId = m_uId;
109 
110     if (puIdAncestor)
111         *puIdAncestor = m_uIdAncestor;
112 
113     *pdwFlags = m_dwFlags;
114 
115     return S_OK;
116 }
117 
118 HRESULT STDMETHODCALLTYPE  CMenuBand::SetMenu(
119     HMENU hmenu,
120     HWND hwnd,
121     DWORD dwFlags)
122 {
123     HRESULT hr;
124 
125     TRACE("CMenuBand::SetMenu called, hmenu=%p; hwnd=%p, flags=%x\n", hmenu, hwnd, dwFlags);
126 
127     BOOL created = FALSE;
128 
129     if (m_hmenu && m_hmenu != hmenu)
130     {
131         DestroyMenu(m_hmenu);
132         m_hmenu = NULL;
133     }
134 
135     m_hmenu = hmenu;
136     m_menuOwner = hwnd;
137 
138     if (m_hmenu && m_staticToolbar == NULL)
139     {
140         m_staticToolbar = new CMenuStaticToolbar(this);
141         created = true;
142     }
143 
144     if (m_staticToolbar)
145     {
146         hr = m_staticToolbar->SetMenu(hmenu, hwnd, dwFlags);
147         if (FAILED_UNEXPECTEDLY(hr))
148             return hr;
149     }
150 
151     if (m_site)
152     {
153         HWND hwndParent;
154 
155         hr = m_site->GetWindow(&hwndParent);
156         if (FAILED_UNEXPECTEDLY(hr))
157             return hr;
158 
159         if (created)
160         {
161             hr = m_staticToolbar->CreateToolbar(hwndParent, m_dwFlags);
162             if (FAILED_UNEXPECTEDLY(hr))
163                 return hr;
164 
165             hr = m_staticToolbar->FillToolbar();
166         }
167         else
168         {
169             hr = m_staticToolbar->FillToolbar(TRUE);
170         }
171     }
172 
173     return hr;
174 }
175 
176 HRESULT STDMETHODCALLTYPE  CMenuBand::GetMenu(
177     HMENU *phmenu,
178     HWND *phwnd,
179     DWORD *pdwFlags)
180 {
181     if (m_staticToolbar == NULL)
182         return E_FAIL;
183 
184     return m_staticToolbar->GetMenu(phmenu, phwnd, pdwFlags);
185 }
186 
187 HRESULT STDMETHODCALLTYPE  CMenuBand::SetSite(IUnknown *pUnkSite)
188 {
189     HWND    hwndParent;
190     HRESULT hr;
191 
192     m_site = NULL;
193 
194     if (pUnkSite == NULL)
195         return S_OK;
196 
197     hwndParent = NULL;
198     hr = pUnkSite->QueryInterface(IID_PPV_ARG(IOleWindow, &m_site));
199     if (FAILED_UNEXPECTEDLY(hr))
200         return hr;
201 
202     hr = m_site->GetWindow(&hwndParent);
203     if (FAILED_UNEXPECTEDLY(hr))
204         return hr;
205 
206     if (!::IsWindow(hwndParent))
207         return E_FAIL;
208 
209     if (m_staticToolbar != NULL)
210     {
211         hr = m_staticToolbar->CreateToolbar(hwndParent, m_dwFlags);
212         if (FAILED_UNEXPECTEDLY(hr))
213             return hr;
214 
215         hr = m_staticToolbar->FillToolbar();
216         if (FAILED_UNEXPECTEDLY(hr))
217             return hr;
218     }
219 
220     if (m_SFToolbar != NULL)
221     {
222         hr = m_SFToolbar->CreateToolbar(hwndParent, m_dwFlags);
223         if (FAILED_UNEXPECTEDLY(hr))
224             return hr;
225 
226         hr = m_SFToolbar->FillToolbar();
227         if (FAILED_UNEXPECTEDLY(hr))
228             return hr;
229     }
230 
231     hr = IUnknown_QueryService(m_site, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &m_subMenuParent));
232     if (hr != E_NOINTERFACE && FAILED_UNEXPECTEDLY(hr))
233         return hr;
234 
235     CComPtr<IOleWindow> pTopLevelWindow;
236     hr = IUnknown_QueryService(m_site, SID_STopLevelBrowser, IID_PPV_ARG(IOleWindow, &pTopLevelWindow));
237     if (SUCCEEDED(hr))
238     {
239         hr = pTopLevelWindow->GetWindow(&m_topLevelWindow);
240         if (FAILED_UNEXPECTEDLY(hr))
241             return hr;
242     }
243     else
244     {
245         m_topLevelWindow = hwndParent;
246     }
247 
248     return S_OK;
249 }
250 
251 HRESULT STDMETHODCALLTYPE  CMenuBand::GetSite(REFIID riid, PVOID *ppvSite)
252 {
253     if (m_site == NULL)
254         return E_FAIL;
255 
256     return m_site->QueryInterface(riid, ppvSite);
257 }
258 
259 HRESULT STDMETHODCALLTYPE CMenuBand::GetWindow(HWND *phwnd)
260 {
261     if (m_SFToolbar != NULL)
262         return m_SFToolbar->GetWindow(phwnd);
263 
264     if (m_staticToolbar != NULL)
265         return m_staticToolbar->GetWindow(phwnd);
266 
267     if (phwnd) *phwnd = NULL;
268 
269     return E_FAIL;
270 }
271 
272 HRESULT STDMETHODCALLTYPE CMenuBand::OnPosRectChangeDB(RECT *prc)
273 {
274     SIZE maxStatic = { 0 };
275     SIZE maxShlFld = { 0 };
276     HRESULT hr = S_OK;
277 
278     if (m_staticToolbar != NULL)
279         hr = m_staticToolbar->GetSizes(NULL, &maxStatic, NULL);
280     if (FAILED_UNEXPECTEDLY(hr))
281         return hr;
282 
283     if (m_SFToolbar != NULL)
284         hr = m_SFToolbar->GetSizes(NULL, &maxShlFld, NULL);
285     if (FAILED_UNEXPECTEDLY(hr))
286         return hr;
287 
288     if (m_staticToolbar == NULL && m_SFToolbar == NULL)
289         return E_FAIL;
290 
291     int sy = min(prc->bottom - prc->top, maxStatic.cy + maxShlFld.cy);
292 
293     int syStatic = maxStatic.cy;
294     int syShlFld = sy - syStatic;
295 
296     // TODO: Windows has a more complex system to decide ordering.
297     // Because we only support two toolbars at once, this is enough for us.
298     if (m_shellBottom)
299     {
300         // Static menu on top
301         if (m_SFToolbar)
302         {
303             m_SFToolbar->SetPosSize(
304                 prc->left,
305                 prc->top + syStatic,
306                 prc->right - prc->left,
307                 syShlFld);
308         }
309         if (m_staticToolbar)
310         {
311             m_staticToolbar->SetPosSize(
312                 prc->left,
313                 prc->top,
314                 prc->right - prc->left,
315                 syStatic);
316         }
317     }
318     else
319     {
320         // Folder menu on top
321         if (m_SFToolbar)
322         {
323             m_SFToolbar->SetPosSize(
324                 prc->left,
325                 prc->top,
326                 prc->right - prc->left,
327                 syShlFld);
328         }
329         if (m_staticToolbar)
330         {
331             m_staticToolbar->SetPosSize(
332                 prc->left,
333                 prc->top + syShlFld,
334                 prc->right - prc->left,
335                 syStatic);
336         }
337     }
338 
339     return S_OK;
340 }
341 
342 HRESULT STDMETHODCALLTYPE  CMenuBand::GetBandInfo(
343     DWORD dwBandID,
344     DWORD dwViewMode,
345     DESKBANDINFO *pdbi)
346 {
347     SIZE minStatic = { 0 };
348     SIZE minShlFld = { 0 };
349     SIZE maxStatic = { 0 };
350     SIZE maxShlFld = { 0 };
351     SIZE intStatic = { 0 };
352     SIZE intShlFld = { 0 };
353 
354     HRESULT hr = S_OK;
355 
356     if (m_staticToolbar != NULL)
357         hr = m_staticToolbar->GetSizes(&minStatic, &maxStatic, &intStatic);
358     if (FAILED_UNEXPECTEDLY(hr))
359         return hr;
360 
361     if (m_SFToolbar != NULL)
362         hr = m_SFToolbar->GetSizes(&minShlFld, &maxShlFld, &intShlFld);
363     if (FAILED_UNEXPECTEDLY(hr))
364         return hr;
365 
366     if (m_staticToolbar == NULL && m_SFToolbar == NULL)
367         return E_FAIL;
368 
369     if (m_dwFlags & SMINIT_VERTICAL)
370     {
371         pdbi->ptMinSize.x = max(minStatic.cx, minShlFld.cx) + 20;
372         pdbi->ptMinSize.y = minStatic.cy + minShlFld.cy;
373         pdbi->ptMaxSize.x = max(maxStatic.cx, maxShlFld.cx) + 20;
374         pdbi->ptMaxSize.y = maxStatic.cy + maxShlFld.cy;
375         pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT;
376     }
377     else
378     {
379         pdbi->ptMinSize.x = minStatic.cx + minShlFld.cx;
380         pdbi->ptMinSize.y = max(minStatic.cy, minShlFld.cy);
381         pdbi->ptMaxSize.x = maxStatic.cx + maxShlFld.cx;
382         pdbi->ptMaxSize.y = max(maxStatic.cy, maxShlFld.cy);
383     }
384     pdbi->ptIntegral.x = max(intStatic.cx, intShlFld.cx);
385     pdbi->ptIntegral.y = max(intStatic.cy, intShlFld.cy);
386     pdbi->ptActual = pdbi->ptMinSize;
387 
388     return S_OK;
389 }
390 
391 HRESULT STDMETHODCALLTYPE  CMenuBand::ShowDW(BOOL fShow)
392 {
393     HRESULT hr = S_OK;
394 
395     if (m_Show == fShow)
396         return S_OK;
397 
398     m_Show = fShow;
399 
400     if (m_staticToolbar != NULL)
401     {
402         hr = m_staticToolbar->ShowDW(fShow);
403         if (FAILED_UNEXPECTEDLY(hr))
404             return hr;
405     }
406 
407     if (m_SFToolbar != NULL)
408     {
409         hr = m_SFToolbar->ShowDW(fShow);
410         if (FAILED_UNEXPECTEDLY(hr))
411             return hr;
412     }
413 
414     if (fShow)
415     {
416         hr = _CallCB(SMC_INITMENU, 0, 0);
417         if (FAILED_UNEXPECTEDLY(hr))
418             return hr;
419     }
420     else if (m_parentBand)
421     {
422         m_parentBand->SetClient(NULL);
423     }
424 
425     if (_IsPopup() == S_OK)
426     {
427         if (fShow)
428             hr = m_focusManager->PushMenuPopup(this);
429         else
430             hr = m_focusManager->PopMenuPopup(this);
431     }
432     else
433     {
434         if (fShow)
435             hr = m_focusManager->PushMenuBar(this);
436         else
437             hr = m_focusManager->PopMenuBar(this);
438     }
439 
440     return S_OK;
441 }
442 
443 HRESULT STDMETHODCALLTYPE CMenuBand::CloseDW(DWORD dwReserved)
444 {
445     if (m_subMenuChild)
446     {
447         m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
448     }
449 
450     if (m_subMenuChild)
451     {
452         TRACE("Child object should have removed itself.\n");
453     }
454 
455     ShowDW(FALSE);
456 
457     if (m_staticToolbar != NULL)
458     {
459         m_staticToolbar->Close();
460     }
461 
462     if (m_SFToolbar != NULL)
463     {
464         m_SFToolbar->Close();
465     }
466 
467     if (m_site) m_site.Release();
468     if (m_subMenuChild) m_subMenuChild.Release();
469     if (m_subMenuParent) m_subMenuParent.Release();
470     if (m_childBand) m_childBand.Release();
471     if (m_parentBand) m_parentBand.Release();
472 
473     return S_OK;
474 }
475 
476 HRESULT STDMETHODCALLTYPE CMenuBand::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
477 {
478     HRESULT hr;
479 
480     if (m_subMenuParent)
481     {
482         hr = m_subMenuParent->SetSubMenu(this, fActivate);
483         if (FAILED_UNEXPECTEDLY(hr))
484             return hr;
485     }
486 
487     if (fActivate)
488     {
489         CComPtr<IOleWindow> pTopLevelWindow;
490         hr = IUnknown_QueryService(m_site, SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &pTopLevelWindow));
491         if (FAILED_UNEXPECTEDLY(hr))
492             return hr;
493 
494         hr = pTopLevelWindow->GetWindow(&m_topLevelWindow);
495         if (FAILED_UNEXPECTEDLY(hr))
496             return hr;
497     }
498     else
499     {
500         m_topLevelWindow = NULL;
501     }
502 
503     return S_FALSE;
504 }
505 
506 HRESULT STDMETHODCALLTYPE CMenuBand::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
507 {
508     if (!pguidCmdGroup)
509         return E_FAIL;
510 
511     if (IsEqualGUID(*pguidCmdGroup, CLSID_MenuBand))
512     {
513         if (nCmdID == 16) // set (big) icon size
514         {
515             this->m_useBigIcons = nCmdexecopt == 2;
516             return S_OK;
517         }
518         else if (nCmdID == 19) // popup-related
519         {
520             return S_FALSE;
521         }
522         else if (nCmdID == 5) // select an item
523         {
524             if (nCmdexecopt == 0) // first
525             {
526                 _KeyboardItemChange(VK_HOME);
527             }
528             else // last
529             {
530                 _KeyboardItemChange(VK_END);
531             }
532             return S_FALSE;
533         }
534 
535         return S_FALSE;
536     }
537 
538     UNIMPLEMENTED;
539     return S_OK;
540 }
541 
542 HRESULT STDMETHODCALLTYPE CMenuBand::QueryService(REFGUID guidService, REFIID riid, void **ppvObject)
543 {
544     if (IsEqualIID(guidService, SID_SMenuBandChild) ||
545         IsEqualIID(guidService, SID_SMenuBandBottom) ||
546         IsEqualIID(guidService, SID_SMenuBandBottomSelected))
547         return this->QueryInterface(riid, ppvObject);
548     WARN("Unknown service requested %s\n", wine_dbgstr_guid(&guidService));
549     return E_NOINTERFACE;
550 }
551 
552 HRESULT STDMETHODCALLTYPE CMenuBand::Popup(POINTL *ppt, RECTL *prcExclude, MP_POPUPFLAGS dwFlags)
553 {
554     UNIMPLEMENTED;
555     return S_OK;
556 }
557 
558 HRESULT STDMETHODCALLTYPE CMenuBand::OnSelect(DWORD dwSelectType)
559 {
560     // When called from outside, this is straightforward:
561     // Things that a submenu needs to know, are spread down, and
562     // things that the parent needs to know, are spread up. No drama.
563     // The fun is in _MenuItemSelect (internal method).
564     switch (dwSelectType)
565     {
566     case MPOS_CHILDTRACKING:
567         if (!m_subMenuParent)
568             break;
569         // TODO: Cancel timers?
570         return m_subMenuParent->OnSelect(dwSelectType);
571     case MPOS_SELECTLEFT:
572         if (m_subMenuChild)
573             m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
574         if (!m_subMenuParent)
575             break;
576         return m_subMenuParent->OnSelect(dwSelectType);
577     case MPOS_SELECTRIGHT:
578         if (!m_subMenuParent)
579             break;
580         return m_subMenuParent->OnSelect(dwSelectType);
581     case MPOS_EXECUTE:
582     case MPOS_FULLCANCEL:
583         if (m_subMenuChild)
584             m_subMenuChild->OnSelect(dwSelectType);
585         if (!m_subMenuParent)
586             break;
587         return m_subMenuParent->OnSelect(dwSelectType);
588     case MPOS_CANCELLEVEL:
589         if (m_subMenuChild)
590             m_subMenuChild->OnSelect(dwSelectType);
591         break;
592     }
593     return S_FALSE;
594 }
595 
596 HRESULT STDMETHODCALLTYPE CMenuBand::SetSubMenu(IMenuPopup *pmp, BOOL fSet)
597 {
598     UNIMPLEMENTED;
599     return S_OK;
600 }
601 
602 // Used by the focus manager to update the child band pointer
603 HRESULT CMenuBand::_SetChildBand(CMenuBand * child)
604 {
605     m_childBand = child;
606     if (!child)
607     {
608         _ChangePopupItem(NULL, -1);
609     }
610     return S_OK;
611 }
612 
613 // User by the focus manager to update the parent band pointer
614 HRESULT CMenuBand::_SetParentBand(CMenuBand * parent)
615 {
616     m_parentBand = parent;
617     return S_OK;
618 }
619 
620 HRESULT CMenuBand::_IsPopup()
621 {
622     return !(m_dwFlags & SMINIT_VERTICAL);
623 }
624 
625 HRESULT CMenuBand::_IsTracking()
626 {
627     return m_popupBar != NULL;
628 }
629 
630 HRESULT STDMETHODCALLTYPE CMenuBand::SetClient(IUnknown *punkClient)
631 {
632     CComPtr<IMenuPopup> child = m_subMenuChild;
633 
634     m_subMenuChild = NULL;
635 
636     if (child)
637     {
638         IUnknown_SetSite(child, NULL);
639         child.Release();
640     }
641 
642     if (!punkClient)
643     {
644         return S_OK;
645     }
646 
647     return punkClient->QueryInterface(IID_PPV_ARG(IMenuPopup, &m_subMenuChild));
648 }
649 
650 HRESULT STDMETHODCALLTYPE CMenuBand::GetClient(IUnknown **ppunkClient)
651 {
652     if (!ppunkClient)
653         return E_POINTER;
654     *ppunkClient = NULL;
655 
656     if (m_subMenuChild)
657     {
658         *ppunkClient = m_subMenuChild;
659         (*ppunkClient)->AddRef();
660     }
661 
662     return S_OK;
663 }
664 
665 HRESULT STDMETHODCALLTYPE CMenuBand::IsMenuMessage(MSG *pmsg)
666 {
667     return S_FALSE;
668 }
669 
670 HRESULT STDMETHODCALLTYPE CMenuBand::TranslateMenuMessage(MSG *pmsg, LRESULT *plRet)
671 {
672     if (pmsg->message == WM_ACTIVATE && _IsPopup() == S_FALSE)
673     {
674         if (m_staticToolbar)
675             m_staticToolbar->Invalidate();
676         if (m_SFToolbar)
677             m_SFToolbar->Invalidate();
678     }
679 
680     return S_FALSE;
681 }
682 
683 HRESULT STDMETHODCALLTYPE CMenuBand::SetShellFolder(IShellFolder *psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags)
684 {
685     if (!psf)
686         return E_INVALIDARG;
687 
688     if (m_SFToolbar == NULL)
689     {
690         m_SFToolbar = new CMenuSFToolbar(this);
691     }
692 
693     HRESULT hr = m_SFToolbar->SetShellFolder(psf, pidlFolder, hKey, dwFlags);
694     if (FAILED_UNEXPECTEDLY(hr))
695         return hr;
696 
697     m_shellBottom = (dwFlags & SMSET_BOTTOM) != 0;
698 
699     if (m_site)
700     {
701         HWND hwndParent;
702 
703         hr = m_site->GetWindow(&hwndParent);
704         if (FAILED_UNEXPECTEDLY(hr))
705             return hr;
706 
707         hr = m_SFToolbar->CreateToolbar(hwndParent, m_dwFlags);
708         if (FAILED_UNEXPECTEDLY(hr))
709             return hr;
710 
711         hr = m_SFToolbar->FillToolbar();
712     }
713 
714     return hr;
715 }
716 
717 HRESULT STDMETHODCALLTYPE CMenuBand::GetShellFolder(DWORD *pdwFlags, LPITEMIDLIST *ppidl, REFIID riid, void **ppv)
718 {
719     if (m_SFToolbar)
720         return m_SFToolbar->GetShellFolder(pdwFlags, ppidl, riid, ppv);
721     return E_FAIL;
722 }
723 
724 HRESULT STDMETHODCALLTYPE CMenuBand::OnWinEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult)
725 {
726     if (theResult)
727         *theResult = 0;
728 
729     if (uMsg == WM_WININICHANGE && wParam == SPI_SETFLATMENU)
730     {
731         BOOL bFlatMenus;
732         SystemParametersInfo(SPI_GETFLATMENU, 0, &bFlatMenus, 0);
733         AdjustForTheme(bFlatMenus);
734 
735         if (m_staticToolbar)
736             m_staticToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
737 
738         if (m_SFToolbar)
739             m_SFToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
740 
741         return S_OK;
742     }
743 
744     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hWnd) == S_OK)
745     {
746         return m_staticToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
747     }
748 
749     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hWnd) == S_OK)
750     {
751         return m_SFToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
752     }
753 
754     return S_FALSE;
755 }
756 
757 HRESULT STDMETHODCALLTYPE CMenuBand::IsWindowOwner(HWND hWnd)
758 {
759     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hWnd) == S_OK)
760         return S_OK;
761 
762     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hWnd) == S_OK)
763         return S_OK;
764 
765     return S_FALSE;
766 }
767 
768 HRESULT CMenuBand::_CallCBWithItemId(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam)
769 {
770     return _CallCB(uMsg, wParam, lParam, id);
771 }
772 
773 HRESULT CMenuBand::_CallCBWithItemPidl(LPITEMIDLIST pidl, UINT uMsg, WPARAM wParam, LPARAM lParam)
774 {
775     return _CallCB(uMsg, wParam, lParam, 0, pidl);
776 }
777 
778 HRESULT CMenuBand::_CallCB(UINT uMsg, WPARAM wParam, LPARAM lParam, UINT id, LPITEMIDLIST pidl)
779 {
780     if (!m_psmc)
781         return S_FALSE;
782 
783     SMDATA smData = { 0 };
784     smData.punk = static_cast<IShellMenu2*>(this);
785     smData.uId = id;
786     smData.uIdParent = m_uId;
787     smData.uIdAncestor = m_uIdAncestor;
788     smData.pidlItem = pidl;
789     smData.hwnd = m_menuOwner ? m_menuOwner : m_topLevelWindow;
790     smData.hmenu = m_hmenu;
791     if (m_SFToolbar)
792         m_SFToolbar->GetShellFolder(NULL, &smData.pidlFolder, IID_PPV_ARG(IShellFolder, &smData.psf));
793     HRESULT hr = m_psmc->CallbackSM(&smData, uMsg, wParam, lParam);
794     ILFree(smData.pidlFolder);
795     if (smData.psf)
796         smData.psf->Release();
797     return hr;
798 }
799 
800 HRESULT CMenuBand::_TrackSubMenu(HMENU popup, INT x, INT y, RECT& rcExclude)
801 {
802     TPMPARAMS params = { sizeof(TPMPARAMS), rcExclude };
803     UINT      flags  = TPM_VERPOSANIMATION | TPM_VERTICAL | TPM_LEFTALIGN;
804     HWND      hwnd   = m_menuOwner ? m_menuOwner : m_topLevelWindow;
805 
806     m_trackedPopup = popup;
807     m_trackedHwnd = hwnd;
808 
809     m_focusManager->PushTrackedPopup(popup);
810     ::TrackPopupMenuEx(popup, flags, x, y, hwnd, &params);
811     m_focusManager->PopTrackedPopup(popup);
812 
813     m_trackedPopup = NULL;
814     m_trackedHwnd = NULL;
815 
816     _DisableMouseTrack(FALSE);
817 
818     return S_OK;
819 }
820 
821 HRESULT CMenuBand::_TrackContextMenu(IContextMenu * contextMenu, INT x, INT y)
822 {
823     HRESULT hr;
824     UINT uCommand;
825 
826     // Ensure that the menu doesn't disappear on us
827     CComPtr<IContextMenu> ctxMenu = contextMenu;
828 
829     HMENU popup = CreatePopupMenu();
830 
831     if (popup == NULL)
832         return E_FAIL;
833 
834     TRACE("Before Query\n");
835     hr = contextMenu->QueryContextMenu(popup, 0, 0, UINT_MAX, CMF_NORMAL);
836     if (FAILED_UNEXPECTEDLY(hr))
837     {
838         TRACE("Query failed\n");
839         DestroyMenu(popup);
840         return hr;
841     }
842 
843     HWND hwnd = m_menuOwner ? m_menuOwner : m_topLevelWindow;
844 
845     m_focusManager->PushTrackedPopup(popup);
846 
847     TRACE("Before Tracking\n");
848     uCommand = ::TrackPopupMenuEx(popup, TPM_RETURNCMD, x, y, hwnd, NULL);
849 
850     m_focusManager->PopTrackedPopup(popup);
851 
852     if (uCommand != 0)
853     {
854         _MenuItemSelect(MPOS_FULLCANCEL);
855 
856         TRACE("Before InvokeCommand\n");
857         CMINVOKECOMMANDINFO cmi = { 0 };
858         cmi.cbSize = sizeof(cmi);
859         cmi.lpVerb = MAKEINTRESOURCEA(uCommand);
860         cmi.hwnd = hwnd;
861         hr = contextMenu->InvokeCommand(&cmi);
862         TRACE("InvokeCommand returned hr=%08x\n", hr);
863     }
864     else
865     {
866         TRACE("TrackPopupMenu failed. Code=%d, LastError=%d\n", uCommand, GetLastError());
867         hr = S_FALSE;
868     }
869 
870     DestroyMenu(popup);
871     return hr;
872 }
873 
874 HRESULT CMenuBand::_GetTopLevelWindow(HWND*topLevel)
875 {
876     *topLevel = m_topLevelWindow;
877     return S_OK;
878 }
879 
880 HRESULT CMenuBand::_ChangeHotItem(CMenuToolbarBase * tb, INT id, DWORD dwFlags)
881 {
882     if (m_hotBar == tb && m_hotItem == id)
883         return S_FALSE;
884 
885     TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar, m_hotItem, tb, id);
886 
887     _KillPopupTimers();
888 
889     m_hotBar = tb;
890     m_hotItem = id;
891     if (m_staticToolbar) m_staticToolbar->ChangeHotItem(tb, id, dwFlags);
892     if (m_SFToolbar) m_SFToolbar->ChangeHotItem(tb, id, dwFlags);
893 
894     _MenuItemSelect(MPOS_CHILDTRACKING);
895 
896     return S_OK;
897 }
898 
899 HRESULT CMenuBand::_ChangePopupItem(CMenuToolbarBase * tb, INT id)
900 {
901     TRACE("Popup item changed from %p %p, to %p %p\n", m_popupBar, m_popupItem, tb, id);
902 
903     m_popupBar = tb;
904     m_popupItem = id;
905     if (m_staticToolbar) m_staticToolbar->ChangePopupItem(tb, id);
906     if (m_SFToolbar) m_SFToolbar->ChangePopupItem(tb, id);
907 
908     return S_OK;
909 }
910 
911 HRESULT  CMenuBand::_KeyboardItemChange(DWORD change)
912 {
913     HRESULT hr;
914     CMenuToolbarBase *tb = m_hotBar;
915 
916     if (!tb)
917     {
918         // If no hot item was selected choose the appropriate toolbar
919         if (change == VK_UP || change == VK_END)
920         {
921             if (m_staticToolbar)
922                 tb = m_staticToolbar;
923             else
924                 tb = m_SFToolbar;
925         }
926         else if (change == VK_DOWN || change == VK_HOME)
927         {
928             if (m_SFToolbar)
929                 tb = m_SFToolbar;
930             else
931                 tb = m_staticToolbar;
932         }
933     }
934 
935     // Ask the first toolbar to change
936     hr = tb->KeyboardItemChange(change);
937 
938     if (hr != S_FALSE)
939         return hr;
940 
941     // Select the second toolbar based on the first
942     if (tb == m_SFToolbar && m_staticToolbar)
943         tb = m_staticToolbar;
944     else if (m_SFToolbar)
945         tb = m_SFToolbar;
946 
947     if (!tb)
948         return hr;
949 
950     // Ask the second toolbar to change
951     return tb->KeyboardItemChange(change == VK_DOWN ? VK_HOME : VK_END);
952 }
953 
954 HRESULT CMenuBand::_MenuItemSelect(DWORD changeType)
955 {
956     // Needed to prevent the this point from vanishing mid-function
957     CComPtr<CMenuBand> safeThis = this;
958     HRESULT hr;
959 
960     if (m_dwFlags & SMINIT_VERTICAL)
961     {
962         switch (changeType)
963         {
964         case VK_UP:
965         case VK_DOWN:
966             return _KeyboardItemChange(changeType);
967 
968             // TODO: Left/Right across multi-column menus, if they ever work.
969         case VK_LEFT:
970             changeType = MPOS_SELECTLEFT;
971             break;
972         case VK_RIGHT:
973             changeType = MPOS_SELECTRIGHT;
974             break;
975         }
976     }
977     else
978     {
979         // In horizontal menubars, left/right are equivalent to vertical's up/down
980         switch (changeType)
981         {
982         case VK_LEFT:
983             hr = _KeyboardItemChange(VK_UP);
984             if (hr != S_FALSE)
985                 return hr;
986         case VK_RIGHT:
987             hr = _KeyboardItemChange(VK_DOWN);
988             if (hr != S_FALSE)
989                 return hr;
990         }
991     }
992 
993     // In this context, the parent is the CMenuDeskBar, so when it bubbles upward,
994     // it is notifying the deskbar, and not the the higher-level menu.
995     // Same for the child: since it points to a CMenuDeskBar, it's not just recursing.
996     switch (changeType)
997     {
998     case MPOS_EXECUTE:
999     {
1000         CMenuToolbarBase * tb = m_hotBar;
1001         int item = m_hotItem;
1002         tb->PrepareExecuteItem(item);
1003         if (m_subMenuParent)
1004         {
1005             m_subMenuParent->OnSelect(changeType);
1006         }
1007         TRACE("Menu closed, executing item...\n");
1008         tb->ExecuteItem();
1009         break;
1010     }
1011     case MPOS_SELECTLEFT:
1012         if (m_parentBand && m_parentBand->_IsPopup()==S_FALSE)
1013             return m_parentBand->_MenuItemSelect(VK_LEFT);
1014         if (m_subMenuChild)
1015             return m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
1016         if (!m_subMenuParent)
1017             return S_OK;
1018         return m_subMenuParent->OnSelect(MPOS_CANCELLEVEL);
1019 
1020     case MPOS_SELECTRIGHT:
1021         if (m_hotBar && m_hotItem >= 0 && m_hotBar->PopupItem(m_hotItem, TRUE) == S_OK)
1022             return S_FALSE;
1023         if (m_parentBand)
1024             return m_parentBand->_MenuItemSelect(VK_RIGHT);
1025         if (!m_subMenuParent)
1026             return S_OK;
1027         return m_subMenuParent->OnSelect(MPOS_SELECTRIGHT);
1028 
1029     default:
1030         if (!m_subMenuParent)
1031             return S_OK;
1032         return m_subMenuParent->OnSelect(changeType);
1033     }
1034 
1035     return S_OK;
1036 }
1037 
1038 HRESULT CMenuBand::_CancelCurrentPopup()
1039 {
1040     if (m_subMenuChild)
1041     {
1042         HRESULT hr = m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
1043         return hr;
1044     }
1045 
1046     if (m_trackedPopup)
1047     {
1048         ::SendMessage(m_trackedHwnd, WM_CANCELMODE, 0, 0);
1049         return S_OK;
1050     }
1051 
1052     return S_FALSE;
1053 }
1054 
1055 HRESULT CMenuBand::_OnPopupSubMenu(IShellMenu * childShellMenu, POINTL * pAt, RECTL * pExclude, BOOL keyInitiated)
1056 {
1057     HRESULT hr = 0;
1058     CComPtr<IBandSite> pBandSite;
1059     CComPtr<IDeskBar> pDeskBar;
1060 
1061     // Create the necessary objects
1062     hr = CMenuSite_CreateInstance(IID_PPV_ARG(IBandSite, &pBandSite));
1063     if (FAILED_UNEXPECTEDLY(hr))
1064         return hr;
1065 
1066     hr = CMenuDeskBar_CreateInstance(IID_PPV_ARG(IDeskBar, &pDeskBar));
1067     if (FAILED_UNEXPECTEDLY(hr))
1068         return hr;
1069 
1070     hr = pDeskBar->SetClient(pBandSite);
1071     if (FAILED_UNEXPECTEDLY(hr))
1072         return hr;
1073 
1074     hr = pBandSite->AddBand(childShellMenu);
1075     if (FAILED_UNEXPECTEDLY(hr))
1076         return hr;
1077 
1078     //
1079     CComPtr<IMenuPopup> popup;
1080     hr = pDeskBar->QueryInterface(IID_PPV_ARG(IMenuPopup, &popup));
1081     if (FAILED_UNEXPECTEDLY(hr))
1082         return hr;
1083 
1084     m_subMenuChild = popup;
1085 
1086     if (m_subMenuParent)
1087         IUnknown_SetSite(popup, m_subMenuParent);
1088     else
1089         IUnknown_SetSite(popup, m_site);
1090 
1091     DWORD flags = MPPF_RIGHT;
1092 
1093     if (keyInitiated && m_dwFlags & SMINIT_VERTICAL)
1094         flags |= MPPF_INITIALSELECT;
1095 
1096     popup->Popup(pAt, pExclude, flags);
1097 
1098     return S_OK;
1099 }
1100 
1101 HRESULT CMenuBand::_BeforeCancelPopup()
1102 {
1103     if (m_staticToolbar)
1104         m_staticToolbar->BeforeCancelPopup();
1105     if (m_SFToolbar)
1106         m_SFToolbar->BeforeCancelPopup();
1107     return S_OK;
1108 }
1109 
1110 HRESULT CMenuBand::_DisableMouseTrack(BOOL bDisable)
1111 {
1112     if (m_staticToolbar)
1113         m_staticToolbar->DisableMouseTrack(bDisable);
1114     if (m_SFToolbar)
1115         m_SFToolbar->DisableMouseTrack(bDisable);
1116     return S_OK;
1117 }
1118 
1119 HRESULT CMenuBand::_KillPopupTimers()
1120 {
1121     HRESULT hr = S_OK;
1122     if (m_staticToolbar)
1123         hr = m_staticToolbar->KillPopupTimer();
1124     if (FAILED(hr))
1125         return hr;
1126 
1127     if (m_SFToolbar)
1128         hr = m_SFToolbar->KillPopupTimer();
1129 
1130     return hr;
1131 }
1132 
1133 HRESULT CMenuBand::_MenuBarMouseDown(HWND hwnd, INT item, BOOL isLButton)
1134 {
1135     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hwnd) == S_OK)
1136         m_staticToolbar->MenuBarMouseDown(item, isLButton);
1137     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hwnd) == S_OK)
1138         m_SFToolbar->MenuBarMouseDown(item, isLButton);
1139     return S_OK;
1140 }
1141 
1142 HRESULT CMenuBand::_MenuBarMouseUp(HWND hwnd, INT item, BOOL isLButton)
1143 {
1144     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hwnd) == S_OK)
1145         m_staticToolbar->MenuBarMouseUp(item, isLButton);
1146     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hwnd) == S_OK)
1147         m_SFToolbar->MenuBarMouseUp(item, isLButton);
1148     return S_OK;
1149 }
1150 
1151 HRESULT CMenuBand::_HasSubMenu()
1152 {
1153     return m_popupBar ? S_OK : S_FALSE;
1154 }
1155 
1156 HRESULT CMenuBand::AdjustForTheme(BOOL bFlatStyle)
1157 {
1158     return IUnknown_QueryServiceExec(m_site, SID_SMenuPopup, &CGID_MenuDeskBar, 4, bFlatStyle, NULL, NULL);
1159 }
1160 
1161 HRESULT STDMETHODCALLTYPE CMenuBand::InvalidateItem(LPSMDATA psmd, DWORD dwFlags)
1162 {
1163     UNIMPLEMENTED;
1164     return S_OK;
1165 }
1166 
1167 HRESULT STDMETHODCALLTYPE CMenuBand::GetState(LPSMDATA psmd)
1168 {
1169     UNIMPLEMENTED;
1170     return S_OK;
1171 }
1172 
1173 HRESULT STDMETHODCALLTYPE CMenuBand::SetMenuToolbar(IUnknown *punk, DWORD dwFlags)
1174 {
1175     UNIMPLEMENTED;
1176     return S_OK;
1177 }
1178 
1179 HRESULT STDMETHODCALLTYPE CMenuBand::ResizeBorderDW(LPCRECT prcBorder, IUnknown *punkToolbarSite, BOOL fReserved)
1180 {
1181     UNIMPLEMENTED;
1182     return S_OK;
1183 }
1184 
1185 HRESULT STDMETHODCALLTYPE CMenuBand::ContextSensitiveHelp(BOOL fEnterMode)
1186 {
1187     UNIMPLEMENTED;
1188     return S_OK;
1189 }
1190 
1191 HRESULT STDMETHODCALLTYPE CMenuBand::GetSubMenu(THIS)
1192 {
1193     UNIMPLEMENTED;
1194     return S_OK;
1195 }
1196 
1197 HRESULT STDMETHODCALLTYPE CMenuBand::SetToolbar(THIS)
1198 {
1199     UNIMPLEMENTED;
1200     return S_OK;
1201 }
1202 
1203 HRESULT STDMETHODCALLTYPE CMenuBand::SetMinWidth(THIS)
1204 {
1205     UNIMPLEMENTED;
1206     return S_OK;
1207 }
1208 
1209 HRESULT STDMETHODCALLTYPE CMenuBand::SetNoBorder(THIS)
1210 {
1211     UNIMPLEMENTED;
1212     return S_OK;
1213 }
1214 
1215 HRESULT STDMETHODCALLTYPE CMenuBand::SetTheme(THIS)
1216 {
1217     UNIMPLEMENTED;
1218     return S_OK;
1219 }
1220 
1221 HRESULT STDMETHODCALLTYPE CMenuBand::GetTop(THIS)
1222 {
1223     UNIMPLEMENTED;
1224     return S_OK;
1225 }
1226 
1227 HRESULT STDMETHODCALLTYPE CMenuBand::GetBottom(THIS)
1228 {
1229     UNIMPLEMENTED;
1230     return S_OK;
1231 }
1232 
1233 HRESULT STDMETHODCALLTYPE CMenuBand::GetTracked(THIS)
1234 {
1235     UNIMPLEMENTED;
1236     return S_OK;
1237 }
1238 
1239 HRESULT STDMETHODCALLTYPE CMenuBand::GetParentSite(THIS)
1240 {
1241     UNIMPLEMENTED;
1242     return S_OK;
1243 }
1244 
1245 HRESULT STDMETHODCALLTYPE CMenuBand::GetState(THIS)
1246 {
1247     UNIMPLEMENTED;
1248     return S_OK;
1249 }
1250 
1251 HRESULT STDMETHODCALLTYPE CMenuBand::DoDefaultAction(THIS)
1252 {
1253     UNIMPLEMENTED;
1254     return S_OK;
1255 }
1256 
1257 HRESULT STDMETHODCALLTYPE CMenuBand::IsEmpty(THIS)
1258 {
1259     UNIMPLEMENTED;
1260     return S_OK;
1261 }
1262 
1263 HRESULT STDMETHODCALLTYPE CMenuBand::HasFocusIO()
1264 {
1265     if (m_popupBar)
1266         return S_OK;
1267     return S_FALSE;
1268 }
1269 
1270 HRESULT STDMETHODCALLTYPE CMenuBand::TranslateAcceleratorIO(LPMSG lpMsg)
1271 {
1272     // TODO: Alt down -> toggle menu focus
1273     return S_FALSE;
1274 }
1275 
1276 HRESULT STDMETHODCALLTYPE CMenuBand::IsDirty()
1277 {
1278     UNIMPLEMENTED;
1279     return S_OK;
1280 }
1281 
1282 HRESULT STDMETHODCALLTYPE CMenuBand::Load(IStream *pStm)
1283 {
1284     UNIMPLEMENTED;
1285     return S_OK;
1286 }
1287 
1288 HRESULT STDMETHODCALLTYPE CMenuBand::Save(IStream *pStm, BOOL fClearDirty)
1289 {
1290     UNIMPLEMENTED;
1291     return S_OK;
1292 }
1293 
1294 HRESULT STDMETHODCALLTYPE CMenuBand::GetSizeMax(ULARGE_INTEGER *pcbSize)
1295 {
1296     UNIMPLEMENTED;
1297     return S_OK;
1298 }
1299 
1300 HRESULT STDMETHODCALLTYPE CMenuBand::GetClassID(CLSID *pClassID)
1301 {
1302     UNIMPLEMENTED;
1303     return S_OK;
1304 }
1305 
1306 HRESULT STDMETHODCALLTYPE CMenuBand::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds [], OLECMDTEXT *pCmdText)
1307 {
1308     UNIMPLEMENTED;
1309     return S_OK;
1310 }
1311 
1312 extern "C"
1313 HRESULT WINAPI RSHELL_CMenuBand_CreateInstance(REFIID riid, LPVOID *ppv)
1314 {
1315     return ShellObjectCreator<CMenuBand>(riid, ppv);
1316 }
1317