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         if (m_psmc)
103             m_psmc->AddRef();
104         *ppsmc = m_psmc;
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         m_subMenuChild->AddRef();
659         *ppunkClient = m_subMenuChild;
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     *theResult = 0;
727 
728     if (uMsg == WM_WININICHANGE && wParam == SPI_SETFLATMENU)
729     {
730         BOOL bFlatMenus;
731         SystemParametersInfo(SPI_GETFLATMENU, 0, &bFlatMenus, 0);
732         AdjustForTheme(bFlatMenus);
733 
734         if (m_staticToolbar)
735             m_staticToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
736 
737         if (m_SFToolbar)
738             m_SFToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
739 
740         return S_OK;
741     }
742 
743     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hWnd) == S_OK)
744     {
745         return m_staticToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
746     }
747 
748     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hWnd) == S_OK)
749     {
750         return m_SFToolbar->OnWinEvent(hWnd, uMsg, wParam, lParam, theResult);
751     }
752 
753     return S_FALSE;
754 }
755 
756 HRESULT STDMETHODCALLTYPE CMenuBand::IsWindowOwner(HWND hWnd)
757 {
758     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hWnd) == S_OK)
759         return S_OK;
760 
761     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hWnd) == S_OK)
762         return S_OK;
763 
764     return S_FALSE;
765 }
766 
767 HRESULT CMenuBand::_CallCBWithItemId(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam)
768 {
769     return _CallCB(uMsg, wParam, lParam, id);
770 }
771 
772 HRESULT CMenuBand::_CallCBWithItemPidl(LPITEMIDLIST pidl, UINT uMsg, WPARAM wParam, LPARAM lParam)
773 {
774     return _CallCB(uMsg, wParam, lParam, 0, pidl);
775 }
776 
777 HRESULT CMenuBand::_CallCB(UINT uMsg, WPARAM wParam, LPARAM lParam, UINT id, LPITEMIDLIST pidl)
778 {
779     if (!m_psmc)
780         return S_FALSE;
781 
782     SMDATA smData = { 0 };
783     smData.punk = static_cast<IShellMenu2*>(this);
784     smData.uId = id;
785     smData.uIdParent = m_uId;
786     smData.uIdAncestor = m_uIdAncestor;
787     smData.pidlItem = pidl;
788     smData.hwnd = m_menuOwner ? m_menuOwner : m_topLevelWindow;
789     smData.hmenu = m_hmenu;
790     if (m_SFToolbar)
791         m_SFToolbar->GetShellFolder(NULL, &smData.pidlFolder, IID_PPV_ARG(IShellFolder, &smData.psf));
792     HRESULT hr = m_psmc->CallbackSM(&smData, uMsg, wParam, lParam);
793     ILFree(smData.pidlFolder);
794     if (smData.psf)
795         smData.psf->Release();
796     return hr;
797 }
798 
799 HRESULT CMenuBand::_TrackSubMenu(HMENU popup, INT x, INT y, RECT& rcExclude)
800 {
801     TPMPARAMS params = { sizeof(TPMPARAMS), rcExclude };
802     UINT      flags  = TPM_VERPOSANIMATION | TPM_VERTICAL | TPM_LEFTALIGN;
803     HWND      hwnd   = m_menuOwner ? m_menuOwner : m_topLevelWindow;
804 
805     m_trackedPopup = popup;
806     m_trackedHwnd = hwnd;
807 
808     m_focusManager->PushTrackedPopup(popup);
809     ::TrackPopupMenuEx(popup, flags, x, y, hwnd, &params);
810     m_focusManager->PopTrackedPopup(popup);
811 
812     m_trackedPopup = NULL;
813     m_trackedHwnd = NULL;
814 
815     _DisableMouseTrack(FALSE);
816 
817     return S_OK;
818 }
819 
820 HRESULT CMenuBand::_TrackContextMenu(IContextMenu * contextMenu, INT x, INT y)
821 {
822     HRESULT hr;
823     UINT uCommand;
824 
825     // Ensure that the menu doesn't disappear on us
826     CComPtr<IContextMenu> ctxMenu = contextMenu;
827 
828     HMENU popup = CreatePopupMenu();
829 
830     if (popup == NULL)
831         return E_FAIL;
832 
833     TRACE("Before Query\n");
834     hr = contextMenu->QueryContextMenu(popup, 0, 0, UINT_MAX, CMF_NORMAL);
835     if (FAILED_UNEXPECTEDLY(hr))
836     {
837         TRACE("Query failed\n");
838         DestroyMenu(popup);
839         return hr;
840     }
841 
842     HWND hwnd = m_menuOwner ? m_menuOwner : m_topLevelWindow;
843 
844     m_focusManager->PushTrackedPopup(popup);
845 
846     TRACE("Before Tracking\n");
847     uCommand = ::TrackPopupMenuEx(popup, TPM_RETURNCMD, x, y, hwnd, NULL);
848 
849     m_focusManager->PopTrackedPopup(popup);
850 
851     if (uCommand != 0)
852     {
853         _MenuItemSelect(MPOS_FULLCANCEL);
854 
855         TRACE("Before InvokeCommand\n");
856         CMINVOKECOMMANDINFO cmi = { 0 };
857         cmi.cbSize = sizeof(cmi);
858         cmi.lpVerb = MAKEINTRESOURCEA(uCommand);
859         cmi.hwnd = hwnd;
860         hr = contextMenu->InvokeCommand(&cmi);
861         TRACE("InvokeCommand returned hr=%08x\n", hr);
862     }
863     else
864     {
865         TRACE("TrackPopupMenu failed. Code=%d, LastError=%d\n", uCommand, GetLastError());
866         hr = S_FALSE;
867     }
868 
869     DestroyMenu(popup);
870     return hr;
871 }
872 
873 HRESULT CMenuBand::_GetTopLevelWindow(HWND*topLevel)
874 {
875     *topLevel = m_topLevelWindow;
876     return S_OK;
877 }
878 
879 HRESULT CMenuBand::_ChangeHotItem(CMenuToolbarBase * tb, INT id, DWORD dwFlags)
880 {
881     if (m_hotBar == tb && m_hotItem == id)
882         return S_FALSE;
883 
884     TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar, m_hotItem, tb, id);
885 
886     _KillPopupTimers();
887 
888     m_hotBar = tb;
889     m_hotItem = id;
890     if (m_staticToolbar) m_staticToolbar->ChangeHotItem(tb, id, dwFlags);
891     if (m_SFToolbar) m_SFToolbar->ChangeHotItem(tb, id, dwFlags);
892 
893     _MenuItemSelect(MPOS_CHILDTRACKING);
894 
895     return S_OK;
896 }
897 
898 HRESULT CMenuBand::_ChangePopupItem(CMenuToolbarBase * tb, INT id)
899 {
900     TRACE("Popup item changed from %p %p, to %p %p\n", m_popupBar, m_popupItem, tb, id);
901 
902     m_popupBar = tb;
903     m_popupItem = id;
904     if (m_staticToolbar) m_staticToolbar->ChangePopupItem(tb, id);
905     if (m_SFToolbar) m_SFToolbar->ChangePopupItem(tb, id);
906 
907     return S_OK;
908 }
909 
910 HRESULT  CMenuBand::_KeyboardItemChange(DWORD change)
911 {
912     HRESULT hr;
913     CMenuToolbarBase *tb = m_hotBar;
914 
915     if (!tb)
916     {
917         // If no hot item was selected choose the appropriate toolbar
918         if (change == VK_UP || change == VK_END)
919         {
920             if (m_staticToolbar)
921                 tb = m_staticToolbar;
922             else
923                 tb = m_SFToolbar;
924         }
925         else if (change == VK_DOWN || change == VK_HOME)
926         {
927             if (m_SFToolbar)
928                 tb = m_SFToolbar;
929             else
930                 tb = m_staticToolbar;
931         }
932     }
933 
934     // Ask the first toolbar to change
935     hr = tb->KeyboardItemChange(change);
936 
937     if (hr != S_FALSE)
938         return hr;
939 
940     // Select the second toolbar based on the first
941     if (tb == m_SFToolbar && m_staticToolbar)
942         tb = m_staticToolbar;
943     else if (m_SFToolbar)
944         tb = m_SFToolbar;
945 
946     if (!tb)
947         return hr;
948 
949     // Ask the second toolbar to change
950     return tb->KeyboardItemChange(change == VK_DOWN ? VK_HOME : VK_END);
951 }
952 
953 HRESULT CMenuBand::_MenuItemSelect(DWORD changeType)
954 {
955     // Needed to prevent the this point from vanishing mid-function
956     CComPtr<CMenuBand> safeThis = this;
957     HRESULT hr;
958 
959     if (m_dwFlags & SMINIT_VERTICAL)
960     {
961         switch (changeType)
962         {
963         case VK_UP:
964         case VK_DOWN:
965             return _KeyboardItemChange(changeType);
966 
967             // TODO: Left/Right across multi-column menus, if they ever work.
968         case VK_LEFT:
969             changeType = MPOS_SELECTLEFT;
970             break;
971         case VK_RIGHT:
972             changeType = MPOS_SELECTRIGHT;
973             break;
974         }
975     }
976     else
977     {
978         // In horizontal menubars, left/right are equivalent to vertical's up/down
979         switch (changeType)
980         {
981         case VK_LEFT:
982             hr = _KeyboardItemChange(VK_UP);
983             if (hr != S_FALSE)
984                 return hr;
985         case VK_RIGHT:
986             hr = _KeyboardItemChange(VK_DOWN);
987             if (hr != S_FALSE)
988                 return hr;
989         }
990     }
991 
992     // In this context, the parent is the CMenuDeskBar, so when it bubbles upward,
993     // it is notifying the deskbar, and not the the higher-level menu.
994     // Same for the child: since it points to a CMenuDeskBar, it's not just recursing.
995     switch (changeType)
996     {
997     case MPOS_EXECUTE:
998     {
999         CMenuToolbarBase * tb = m_hotBar;
1000         int item = m_hotItem;
1001         tb->PrepareExecuteItem(item);
1002         if (m_subMenuParent)
1003         {
1004             m_subMenuParent->OnSelect(changeType);
1005         }
1006         TRACE("Menu closed, executing item...\n");
1007         tb->ExecuteItem();
1008         break;
1009     }
1010     case MPOS_SELECTLEFT:
1011         if (m_parentBand && m_parentBand->_IsPopup()==S_FALSE)
1012             return m_parentBand->_MenuItemSelect(VK_LEFT);
1013         if (m_subMenuChild)
1014             return m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
1015         if (!m_subMenuParent)
1016             return S_OK;
1017         return m_subMenuParent->OnSelect(MPOS_CANCELLEVEL);
1018 
1019     case MPOS_SELECTRIGHT:
1020         if (m_hotBar && m_hotItem >= 0 && m_hotBar->PopupItem(m_hotItem, TRUE) == S_OK)
1021             return S_FALSE;
1022         if (m_parentBand)
1023             return m_parentBand->_MenuItemSelect(VK_RIGHT);
1024         if (!m_subMenuParent)
1025             return S_OK;
1026         return m_subMenuParent->OnSelect(MPOS_SELECTRIGHT);
1027 
1028     default:
1029         if (!m_subMenuParent)
1030             return S_OK;
1031         return m_subMenuParent->OnSelect(changeType);
1032     }
1033 
1034     return S_OK;
1035 }
1036 
1037 HRESULT CMenuBand::_CancelCurrentPopup()
1038 {
1039     if (m_subMenuChild)
1040     {
1041         HRESULT hr = m_subMenuChild->OnSelect(MPOS_CANCELLEVEL);
1042         return hr;
1043     }
1044 
1045     if (m_trackedPopup)
1046     {
1047         ::SendMessage(m_trackedHwnd, WM_CANCELMODE, 0, 0);
1048         return S_OK;
1049     }
1050 
1051     return S_FALSE;
1052 }
1053 
1054 HRESULT CMenuBand::_OnPopupSubMenu(IShellMenu * childShellMenu, POINTL * pAt, RECTL * pExclude, BOOL keyInitiated)
1055 {
1056     HRESULT hr = 0;
1057     CComPtr<IBandSite> pBandSite;
1058     CComPtr<IDeskBar> pDeskBar;
1059 
1060     // Create the necessary objects
1061     hr = CMenuSite_CreateInstance(IID_PPV_ARG(IBandSite, &pBandSite));
1062     if (FAILED_UNEXPECTEDLY(hr))
1063         return hr;
1064 
1065     hr = CMenuDeskBar_CreateInstance(IID_PPV_ARG(IDeskBar, &pDeskBar));
1066     if (FAILED_UNEXPECTEDLY(hr))
1067         return hr;
1068 
1069     hr = pDeskBar->SetClient(pBandSite);
1070     if (FAILED_UNEXPECTEDLY(hr))
1071         return hr;
1072 
1073     hr = pBandSite->AddBand(childShellMenu);
1074     if (FAILED_UNEXPECTEDLY(hr))
1075         return hr;
1076 
1077     //
1078     CComPtr<IMenuPopup> popup;
1079     hr = pDeskBar->QueryInterface(IID_PPV_ARG(IMenuPopup, &popup));
1080     if (FAILED_UNEXPECTEDLY(hr))
1081         return hr;
1082 
1083     m_subMenuChild = popup;
1084 
1085     if (m_subMenuParent)
1086         IUnknown_SetSite(popup, m_subMenuParent);
1087     else
1088         IUnknown_SetSite(popup, m_site);
1089 
1090     DWORD flags = MPPF_RIGHT;
1091 
1092     if (keyInitiated && m_dwFlags & SMINIT_VERTICAL)
1093         flags |= MPPF_INITIALSELECT;
1094 
1095     popup->Popup(pAt, pExclude, flags);
1096 
1097     return S_OK;
1098 }
1099 
1100 HRESULT CMenuBand::_BeforeCancelPopup()
1101 {
1102     if (m_staticToolbar)
1103         m_staticToolbar->BeforeCancelPopup();
1104     if (m_SFToolbar)
1105         m_SFToolbar->BeforeCancelPopup();
1106     return S_OK;
1107 }
1108 
1109 HRESULT CMenuBand::_DisableMouseTrack(BOOL bDisable)
1110 {
1111     if (m_staticToolbar)
1112         m_staticToolbar->DisableMouseTrack(bDisable);
1113     if (m_SFToolbar)
1114         m_SFToolbar->DisableMouseTrack(bDisable);
1115     return S_OK;
1116 }
1117 
1118 HRESULT CMenuBand::_KillPopupTimers()
1119 {
1120     HRESULT hr = S_OK;
1121     if (m_staticToolbar)
1122         hr = m_staticToolbar->KillPopupTimer();
1123     if (FAILED(hr))
1124         return hr;
1125 
1126     if (m_SFToolbar)
1127         hr = m_SFToolbar->KillPopupTimer();
1128 
1129     return hr;
1130 }
1131 
1132 HRESULT CMenuBand::_MenuBarMouseDown(HWND hwnd, INT item, BOOL isLButton)
1133 {
1134     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hwnd) == S_OK)
1135         m_staticToolbar->MenuBarMouseDown(item, isLButton);
1136     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hwnd) == S_OK)
1137         m_SFToolbar->MenuBarMouseDown(item, isLButton);
1138     return S_OK;
1139 }
1140 
1141 HRESULT CMenuBand::_MenuBarMouseUp(HWND hwnd, INT item, BOOL isLButton)
1142 {
1143     if (m_staticToolbar && m_staticToolbar->IsWindowOwner(hwnd) == S_OK)
1144         m_staticToolbar->MenuBarMouseUp(item, isLButton);
1145     if (m_SFToolbar && m_SFToolbar->IsWindowOwner(hwnd) == S_OK)
1146         m_SFToolbar->MenuBarMouseUp(item, isLButton);
1147     return S_OK;
1148 }
1149 
1150 HRESULT CMenuBand::_HasSubMenu()
1151 {
1152     return m_popupBar ? S_OK : S_FALSE;
1153 }
1154 
1155 HRESULT CMenuBand::AdjustForTheme(BOOL bFlatStyle)
1156 {
1157     return IUnknown_QueryServiceExec(m_site, SID_SMenuPopup, &CGID_MenuDeskBar, 4, bFlatStyle, NULL, NULL);
1158 }
1159 
1160 HRESULT STDMETHODCALLTYPE CMenuBand::InvalidateItem(LPSMDATA psmd, DWORD dwFlags)
1161 {
1162     UNIMPLEMENTED;
1163     return S_OK;
1164 }
1165 
1166 HRESULT STDMETHODCALLTYPE CMenuBand::GetState(LPSMDATA psmd)
1167 {
1168     UNIMPLEMENTED;
1169     return S_OK;
1170 }
1171 
1172 HRESULT STDMETHODCALLTYPE CMenuBand::SetMenuToolbar(IUnknown *punk, DWORD dwFlags)
1173 {
1174     UNIMPLEMENTED;
1175     return S_OK;
1176 }
1177 
1178 HRESULT STDMETHODCALLTYPE CMenuBand::ResizeBorderDW(LPCRECT prcBorder, IUnknown *punkToolbarSite, BOOL fReserved)
1179 {
1180     UNIMPLEMENTED;
1181     return S_OK;
1182 }
1183 
1184 HRESULT STDMETHODCALLTYPE CMenuBand::ContextSensitiveHelp(BOOL fEnterMode)
1185 {
1186     UNIMPLEMENTED;
1187     return S_OK;
1188 }
1189 
1190 HRESULT STDMETHODCALLTYPE CMenuBand::GetSubMenu(THIS)
1191 {
1192     UNIMPLEMENTED;
1193     return S_OK;
1194 }
1195 
1196 HRESULT STDMETHODCALLTYPE CMenuBand::SetToolbar(THIS)
1197 {
1198     UNIMPLEMENTED;
1199     return S_OK;
1200 }
1201 
1202 HRESULT STDMETHODCALLTYPE CMenuBand::SetMinWidth(THIS)
1203 {
1204     UNIMPLEMENTED;
1205     return S_OK;
1206 }
1207 
1208 HRESULT STDMETHODCALLTYPE CMenuBand::SetNoBorder(THIS)
1209 {
1210     UNIMPLEMENTED;
1211     return S_OK;
1212 }
1213 
1214 HRESULT STDMETHODCALLTYPE CMenuBand::SetTheme(THIS)
1215 {
1216     UNIMPLEMENTED;
1217     return S_OK;
1218 }
1219 
1220 HRESULT STDMETHODCALLTYPE CMenuBand::GetTop(THIS)
1221 {
1222     UNIMPLEMENTED;
1223     return S_OK;
1224 }
1225 
1226 HRESULT STDMETHODCALLTYPE CMenuBand::GetBottom(THIS)
1227 {
1228     UNIMPLEMENTED;
1229     return S_OK;
1230 }
1231 
1232 HRESULT STDMETHODCALLTYPE CMenuBand::GetTracked(THIS)
1233 {
1234     UNIMPLEMENTED;
1235     return S_OK;
1236 }
1237 
1238 HRESULT STDMETHODCALLTYPE CMenuBand::GetParentSite(THIS)
1239 {
1240     UNIMPLEMENTED;
1241     return S_OK;
1242 }
1243 
1244 HRESULT STDMETHODCALLTYPE CMenuBand::GetState(THIS)
1245 {
1246     UNIMPLEMENTED;
1247     return S_OK;
1248 }
1249 
1250 HRESULT STDMETHODCALLTYPE CMenuBand::DoDefaultAction(THIS)
1251 {
1252     UNIMPLEMENTED;
1253     return S_OK;
1254 }
1255 
1256 HRESULT STDMETHODCALLTYPE CMenuBand::IsEmpty(THIS)
1257 {
1258     UNIMPLEMENTED;
1259     return S_OK;
1260 }
1261 
1262 HRESULT STDMETHODCALLTYPE CMenuBand::HasFocusIO()
1263 {
1264     if (m_popupBar)
1265         return S_OK;
1266     return S_FALSE;
1267 }
1268 
1269 HRESULT STDMETHODCALLTYPE CMenuBand::TranslateAcceleratorIO(LPMSG lpMsg)
1270 {
1271     // TODO: Alt down -> toggle menu focus
1272     return S_FALSE;
1273 }
1274 
1275 HRESULT STDMETHODCALLTYPE CMenuBand::IsDirty()
1276 {
1277     UNIMPLEMENTED;
1278     return S_OK;
1279 }
1280 
1281 HRESULT STDMETHODCALLTYPE CMenuBand::Load(IStream *pStm)
1282 {
1283     UNIMPLEMENTED;
1284     return S_OK;
1285 }
1286 
1287 HRESULT STDMETHODCALLTYPE CMenuBand::Save(IStream *pStm, BOOL fClearDirty)
1288 {
1289     UNIMPLEMENTED;
1290     return S_OK;
1291 }
1292 
1293 HRESULT STDMETHODCALLTYPE CMenuBand::GetSizeMax(ULARGE_INTEGER *pcbSize)
1294 {
1295     UNIMPLEMENTED;
1296     return S_OK;
1297 }
1298 
1299 HRESULT STDMETHODCALLTYPE CMenuBand::GetClassID(CLSID *pClassID)
1300 {
1301     UNIMPLEMENTED;
1302     return S_OK;
1303 }
1304 
1305 HRESULT STDMETHODCALLTYPE CMenuBand::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds [], OLECMDTEXT *pCmdText)
1306 {
1307     UNIMPLEMENTED;
1308     return S_OK;
1309 }
1310 
1311 extern "C"
1312 HRESULT WINAPI RSHELL_CMenuBand_CreateInstance(REFIID riid, LPVOID *ppv)
1313 {
1314     return ShellObjectCreator<CMenuBand>(riid, ppv);
1315 }
1316