Reactos
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
21/*
22This file implements the CMenuFocusManager class.
23
24This class manages the shell menus, by overriding the hot-tracking behaviour.
25
26For the shell menus, it uses a GetMessage hook,
27where it intercepts messages directed to the menu windows.
28
29In order to show submenus using system popups, it also has a MessageFilter hook.
30
31The menu is tracked using a stack structure. When a CMenuBand wants to open a submenu,
32it pushes the submenu band, or HMENU to track in case of system popups,
33and when the menu has closed, it pops the same pointer or handle.
34
35While a shell menu is open, it overrides the menu toolbar's hottracking behaviour,
36using its own logic to track both the active menu item, and the opened submenu's parent item.
37
38While a system popup is open, it tracks the mouse movements so that it can cancel the popup,
39and switch to another submenu when the mouse goes over another item from the parent.
40
41*/
42#include "shellmenu.h"
43#include <windowsx.h>
44#include <commoncontrols.h>
45#include <shlwapi_undoc.h>
46
47#include "CMenuFocusManager.h"
48#include "CMenuToolbars.h"
49#include "CMenuBand.h"
50
51#if DBG
52# undef _ASSERT
53# define _ASSERT(x) DbgAssert(!!(x), __FILE__, __LINE__, #x)
54
55bool DbgAssert(bool x, const char * filename, int line, const char * expr)
56{
57 if (!x)
58 {
59 char szMsg[512];
60 const char *fname;
61
62 fname = strrchr(filename, '\\');
63 if (fname == NULL)
64 {
65 fname = strrchr(filename, '/');
66 }
67
68 if (fname == NULL)
69 fname = filename;
70 else
71 fname++;
72
73 sprintf(szMsg, "%s:%d: Assertion failed: %s\n", fname, line, expr);
74
75 OutputDebugStringA(szMsg);
76
77 __debugbreak();
78 }
79 return x;
80}
81#else
82# undef _ASSERT
83# define _ASSERT(x) (!!(x))
84#endif
85
86WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus);
87
88DWORD CMenuFocusManager::TlsIndex = 0;
89
90// Gets the thread's assigned manager without refcounting
91CMenuFocusManager * CMenuFocusManager::GetManager()
92{
93 return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
94}
95
96// Obtains a manager for the thread, with refcounting
97CMenuFocusManager * CMenuFocusManager::AcquireManager()
98{
99 CMenuFocusManager * obj = NULL;
100
101 if (!TlsIndex)
102 {
103 if ((TlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
104 return NULL;
105 }
106
107 obj = GetManager();
108
109 if (!obj)
110 {
111 obj = new CComObject<CMenuFocusManager>();
112 TlsSetValue(TlsIndex, obj);
113 }
114
115 obj->AddRef();
116
117 return obj;
118}
119
120// Releases a previously acquired manager, and deletes it if the refcount reaches 0
121void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
122{
123 if (!obj->Release())
124 {
125 TlsSetValue(TlsIndex, NULL);
126 }
127}
128
129LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
130{
131 return GetManager()->MsgFilterHook(nCode, wParam, lParam);
132}
133
134LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
135{
136 return GetManager()->GetMsgHook(nCode, wParam, lParam);
137}
138
139HRESULT CMenuFocusManager::PushToArray(StackEntryType type, CMenuBand * mb, HMENU hmenu)
140{
141 if (m_bandCount >= MAX_RECURSE)
142 return E_OUTOFMEMORY;
143
144 m_bandStack[m_bandCount].type = type;
145 m_bandStack[m_bandCount].mb = mb;
146 m_bandStack[m_bandCount].hmenu = hmenu;
147 m_bandCount++;
148
149 return S_OK;
150}
151
152HRESULT CMenuFocusManager::PopFromArray(StackEntryType * pType, CMenuBand ** pMb, HMENU * pHmenu)
153{
154 if (pType) *pType = NoEntry;
155 if (pMb) *pMb = NULL;
156 if (pHmenu) *pHmenu = NULL;
157
158 if (m_bandCount <= 0)
159 return S_FALSE;
160
161 m_bandCount--;
162
163 if (pType) *pType = m_bandStack[m_bandCount].type;
164 if (*pType == TrackedMenuEntry)
165 {
166 if (pHmenu) *pHmenu = m_bandStack[m_bandCount].hmenu;
167 }
168 else
169 {
170 if (pMb) *pMb = m_bandStack[m_bandCount].mb;
171 }
172
173 return S_OK;
174}
175
176CMenuFocusManager::CMenuFocusManager() :
177 m_current(NULL),
178 m_parent(NULL),
179 m_hMsgFilterHook(NULL),
180 m_hGetMsgHook(NULL),
181 m_mouseTrackDisabled(FALSE),
182 m_captureHwnd(0),
183 m_hwndUnderMouse(NULL),
184 m_entryUnderMouse(NULL),
185 m_selectedMenu(NULL),
186 m_selectedItem(0),
187 m_selectedItemFlags(0),
188 m_movedSinceDown(FALSE),
189 m_windowAtDown(NULL),
190 m_PreviousForeground(NULL),
191 m_bandCount(0),
192 m_menuDepth(0)
193{
194 m_ptPrev.x = 0;
195 m_ptPrev.y = 0;
196 m_threadId = GetCurrentThreadId();
197}
198
199CMenuFocusManager::~CMenuFocusManager()
200{
201}
202
203// Used so that the toolbar can properly ignore mouse events, when the menu is being used with the keyboard
204void CMenuFocusManager::DisableMouseTrack(HWND parent, BOOL disableThis)
205{
206 BOOL bDisable = FALSE;
207 BOOL lastDisable = FALSE;
208
209 int i = m_bandCount;
210 while (--i >= 0)
211 {
212 StackEntry& entry = m_bandStack[i];
213
214 if (entry.type != TrackedMenuEntry)
215 {
216 HWND hwnd;
217 HRESULT hr = entry.mb->_GetTopLevelWindow(&hwnd);
218 if (FAILED_UNEXPECTEDLY(hr))
219 break;
220
221 if (hwnd == parent)
222 {
223 lastDisable = disableThis;
224 entry.mb->_DisableMouseTrack(disableThis);
225 bDisable = TRUE;
226 }
227 else
228 {
229 lastDisable = bDisable;
230 entry.mb->_DisableMouseTrack(bDisable);
231 }
232 }
233 }
234 m_mouseTrackDisabled = lastDisable;
235}
236
237void CMenuFocusManager::SetMenuCapture(HWND child)
238{
239 if (m_captureHwnd != child)
240 {
241 if (child)
242 {
243 ::SetCapture(child);
244 m_captureHwnd = child;
245 TRACE("Capturing %p\n", child);
246 }
247 else
248 {
249 ::ReleaseCapture();
250 m_captureHwnd = NULL;
251 TRACE("Capture is now off\n");
252 }
253
254 }
255}
256
257HRESULT CMenuFocusManager::IsTrackedWindow(HWND hWnd, StackEntry ** pentry)
258{
259 if (pentry)
260 *pentry = NULL;
261
262 for (int i = m_bandCount; --i >= 0;)
263 {
264 StackEntry& entry = m_bandStack[i];
265
266 if (entry.type != TrackedMenuEntry)
267 {
268 HRESULT hr = entry.mb->IsWindowOwner(hWnd);
269 if (FAILED_UNEXPECTEDLY(hr))
270 return hr;
271 if (hr == S_OK)
272 {
273 if (pentry)
274 *pentry = &entry;
275 return S_OK;
276 }
277 }
278 }
279
280 return S_FALSE;
281}
282
283HRESULT CMenuFocusManager::IsTrackedWindowOrParent(HWND hWnd)
284{
285 for (int i = m_bandCount; --i >= 0;)
286 {
287 StackEntry& entry = m_bandStack[i];
288
289 if (entry.type != TrackedMenuEntry)
290 {
291 HRESULT hr = entry.mb->IsWindowOwner(hWnd);
292 if (FAILED_UNEXPECTEDLY(hr))
293 return hr;
294 if (hr == S_OK)
295 return S_OK;
296 if (entry.mb->_IsPopup() == S_OK)
297 {
298 CComPtr<IUnknown> site;
299 CComPtr<IOleWindow> pw;
300 hr = entry.mb->GetSite(IID_PPV_ARG(IUnknown, &site));
301 if (FAILED_UNEXPECTEDLY(hr))
302 continue;
303 hr = IUnknown_QueryService(site, SID_SMenuBandParent, IID_PPV_ARG(IOleWindow, &pw));
304 if (FAILED_UNEXPECTEDLY(hr))
305 continue;
306
307 HWND hParent;
308 if (pw->GetWindow(&hParent) == S_OK && hParent == hWnd)
309 return S_OK;
310 }
311 }
312 }
313
314 return S_FALSE;
315}
316
317LRESULT CMenuFocusManager::ProcessMouseMove(MSG* msg)
318{
319 HWND child;
320 int iHitTestResult = -1;
321
322 POINT pt2 = { GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam) };
323 ClientToScreen(msg->hwnd, &pt2);
324
325 POINT pt = msg->pt;
326
327 // Don't do anything if another window is capturing the mouse.
328 HWND cCapture = ::GetCapture();
329 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
330 return TRUE;
331
332 m_movedSinceDown = TRUE;
333
334 m_ptPrev = pt;
335
336 child = WindowFromPoint(pt);
337
338 StackEntry * entry = NULL;
339 if (IsTrackedWindow(child, &entry) == S_OK)
340 {
341 TRACE("MouseMove\n");
342 }
343
344 BOOL isTracking = FALSE;
345 if (entry && (entry->type == MenuBarEntry || m_current->type != TrackedMenuEntry))
346 {
347 ScreenToClient(child, &pt);
348 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
349 isTracking = entry->mb->_IsTracking();
350
351 if (iHitTestResult < -1)
352 {
353 // TB_HITTEST would return negative numbers for separators
354 iHitTestResult = -iHitTestResult;
355 }
356 else if (iHitTestResult == -1)
357 {
358 // TB_HITTEST would return -1 in two cases:
359 // 1. the mouse is outside the toolbar;
360 // 2. the mouse is over the first item, and that item is a separator.
361 // Confirm the second scenario by checking first item's rect.
362 RECT rc;
363 SendMessageW(child, TB_GETITEMRECT, 1, (LPARAM)&rc);
364 if (PtInRect(&rc, pt))
365 iHitTestResult = 1;
366 }
367
368 if (SendMessage(child, WM_USER_ISTRACKEDITEM, iHitTestResult, 0) == S_FALSE)
369 {
370 // The current tracked item has changed, notify the toolbar
371
372 TRACE("Hot item tracking detected a change (capture=%p / cCapture=%p)...\n", m_captureHwnd, cCapture);
373 DisableMouseTrack(NULL, FALSE);
374 if (isTracking && iHitTestResult >= 0 && m_current->type == TrackedMenuEntry)
375 SendMessage(entry->hwnd, WM_CANCELMODE, 0, 0);
376 PostMessage(child, WM_USER_CHANGETRACKEDITEM, iHitTestResult, MAKELPARAM(isTracking, TRUE));
377 if (m_current->type == TrackedMenuEntry)
378 return FALSE;
379 }
380 }
381
382 if (m_entryUnderMouse != entry)
383 {
384 // Mouse moved away from a tracked window
385 if (m_entryUnderMouse)
386 {
387 m_entryUnderMouse->mb->_ChangeHotItem(NULL, -1, HICF_MOUSE);
388 }
389 }
390
391 if (m_hwndUnderMouse != child)
392 {
393 if (entry)
394 {
395 // Mouse moved to a tracked window
396 if (m_current->type == MenuPopupEntry)
397 {
398 ScreenToClient(child, &pt2);
399 SendMessage(child, WM_MOUSEMOVE, msg->wParam, MAKELPARAM(pt2.x, pt2.y));
400 }
401 }
402
403 m_hwndUnderMouse = child;
404 m_entryUnderMouse = entry;
405 }
406
407 if (m_current->type == MenuPopupEntry)
408 {
409 HWND parent = GetAncestor(child, GA_ROOT);
410 DisableMouseTrack(parent, FALSE);
411 }
412
413 return TRUE;
414}
415
416LRESULT CMenuFocusManager::ProcessMouseDown(MSG* msg, BOOL isLButton)
417{
418 HWND child;
419 int iHitTestResult = -1;
420
421 TRACE("ProcessMouseDown %d %d %d\n", msg->message, msg->wParam, msg->lParam);
422
423 // Don't do anything if another window is capturing the mouse.
424 HWND cCapture = ::GetCapture();
425 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
426 {
427 TRACE("Foreign capture active.\n");
428 return TRUE;
429 }
430
431 POINT pt = msg->pt;
432
433 child = WindowFromPoint(pt);
434
435 StackEntry * entry = NULL;
436 if (IsTrackedWindow(child, &entry) != S_OK)
437 {
438 TRACE("Foreign window detected.\n");
439 return TRUE;
440 }
441
442 if (entry->type == MenuBarEntry)
443 {
444 if (entry != m_current)
445 {
446 TRACE("Menubar with popup active.\n");
447 return TRUE;
448 }
449 }
450
451 if (entry)
452 {
453 ScreenToClient(child, &pt);
454 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
455
456 if (iHitTestResult >= 0)
457 {
458 TRACE("MouseDown send %d\n", iHitTestResult);
459 entry->mb->_MenuBarMouseDown(child, iHitTestResult, isLButton);
460 }
461 }
462
463 msg->message = WM_NULL;
464
465 m_movedSinceDown = FALSE;
466 m_windowAtDown = child;
467
468 TRACE("MouseDown end\n");
469
470 return TRUE;
471}
472
473LRESULT CMenuFocusManager::ProcessMouseUp(MSG* msg, BOOL isLButton)
474{
475 HWND child;
476 int iHitTestResult = -1;
477
478 TRACE("ProcessMouseUp %d %d %d\n", msg->message, msg->wParam, msg->lParam);
479
480 // Don't do anything if another window is capturing the mouse.
481 HWND cCapture = ::GetCapture();
482 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
483 return TRUE;
484
485 POINT pt = msg->pt;
486
487 child = WindowFromPoint(pt);
488
489 StackEntry * entry = NULL;
490 if (IsTrackedWindow(child, &entry) != S_OK)
491 return TRUE;
492
493 if (entry)
494 {
495 ScreenToClient(child, &pt);
496 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
497
498 if (iHitTestResult >= 0)
499 {
500 TRACE("MouseUp send %d\n", iHitTestResult);
501 entry->mb->_MenuBarMouseUp(child, iHitTestResult, isLButton);
502 }
503 }
504
505 return TRUE;
506}
507
508LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
509{
510 if (nCode < 0)
511 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
512
513 if (nCode == MSGF_MENU)
514 {
515 BOOL callNext = TRUE;
516 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
517
518 switch (msg->message)
519 {
520 case WM_LBUTTONDOWN:
521 case WM_RBUTTONDOWN:
522 if (m_menuBar && m_current->type == TrackedMenuEntry)
523 {
524 POINT pt = msg->pt;
525 HWND child = WindowFromPoint(pt);
526 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
527 if (hoveringMenuBar)
528 {
529 m_menuBar->mb->_BeforeCancelPopup();
530 }
531 }
532 break;
533 case WM_MOUSEMOVE:
534 callNext = ProcessMouseMove(msg);
535 break;
536 case WM_INITMENUPOPUP:
537 TRACE("WM_INITMENUPOPUP %p %p\n", msg->wParam, msg->lParam);
538 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
539 m_selectedItem = -1;
540 m_selectedItemFlags = 0;
541 break;
542 case WM_MENUSELECT:
543 TRACE("WM_MENUSELECT %p %p\n", msg->wParam, msg->lParam);
544 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
545 m_selectedItem = GET_X_LPARAM(msg->wParam);
546 m_selectedItemFlags = HIWORD(msg->wParam);
547 break;
548 case WM_KEYDOWN:
549 switch (msg->wParam)
550 {
551 case VK_LEFT:
552 if (m_current->hmenu == m_selectedMenu)
553 {
554 m_parent->mb->_MenuItemSelect(VK_LEFT);
555 }
556 break;
557 case VK_RIGHT:
558 if (m_selectedItem < 0 || !(m_selectedItemFlags & MF_POPUP))
559 {
560 m_parent->mb->_MenuItemSelect(VK_RIGHT);
561 }
562 break;
563 }
564 break;
565 }
566
567 if (!callNext)
568 return 1;
569 }
570
571 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
572}
573
574LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
575{
576 BOOL isLButton = FALSE;
577 if (nCode < 0)
578 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
579
580 if (nCode == HC_ACTION)
581 {
582 BOOL callNext = TRUE;
583 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
584 POINT pt = msg->pt;
585
586 switch (msg->message)
587 {
588 case WM_CAPTURECHANGED:
589 if (m_captureHwnd)
590 {
591 TRACE("Capture lost.\n");
592 m_captureHwnd = NULL;
593 }
594 break;
595
596 case WM_NCLBUTTONDOWN:
597 case WM_LBUTTONDBLCLK:
598 case WM_LBUTTONDOWN:
599 isLButton = TRUE;
600 TRACE("LB\n");
601
602 if (m_menuBar && m_current->type == MenuPopupEntry)
603 {
604 POINT pt = msg->pt;
605 HWND child = WindowFromPoint(pt);
606 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
607 if (hoveringMenuBar)
608 {
609 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
610 break;
611 }
612 }
613
614 if (m_current->type == MenuPopupEntry)
615 {
616 HWND child = WindowFromPoint(pt);
617
618 if (IsTrackedWindowOrParent(child) != S_OK)
619 {
620 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
621 break;
622 }
623 }
624
625 ProcessMouseDown(msg, isLButton);
626
627 break;
628 case WM_NCRBUTTONUP:
629 case WM_RBUTTONUP:
630 ProcessMouseUp(msg, isLButton);
631 break;
632 case WM_NCLBUTTONUP:
633 case WM_LBUTTONUP:
634 isLButton = TRUE;
635 ProcessMouseUp(msg, isLButton);
636 break;
637 case WM_MOUSEMOVE:
638 callNext = ProcessMouseMove(msg);
639 break;
640 case WM_MOUSELEAVE:
641 callNext = ProcessMouseMove(msg);
642 //callNext = ProcessMouseLeave(msg);
643 break;
644 case WM_SYSKEYDOWN:
645 case WM_KEYDOWN:
646 if (m_current->type == MenuPopupEntry)
647 {
648 DisableMouseTrack(m_current->hwnd, TRUE);
649 switch (msg->wParam)
650 {
651 case VK_ESCAPE:
652 case VK_MENU:
653 case VK_LMENU:
654 case VK_RMENU:
655 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
656 break;
657 case VK_RETURN:
658 m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
659 break;
660 case VK_LEFT:
661 m_current->mb->_MenuItemSelect(VK_LEFT);
662 break;
663 case VK_RIGHT:
664 m_current->mb->_MenuItemSelect(VK_RIGHT);
665 break;
666 case VK_UP:
667 m_current->mb->_MenuItemSelect(VK_UP);
668 break;
669 case VK_DOWN:
670 m_current->mb->_MenuItemSelect(VK_DOWN);
671 break;
672 }
673 msg->message = WM_NULL;
674 msg->lParam = 0;
675 msg->wParam = 0;
676 }
677 break;
678 }
679
680 if (!callNext)
681 return 1;
682 }
683
684 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
685}
686
687HRESULT CMenuFocusManager::PlaceHooks()
688{
689 if (m_hGetMsgHook)
690 {
691 WARN("GETMESSAGE hook already placed!\n");
692 return S_OK;
693 }
694 if (m_hMsgFilterHook)
695 {
696 WARN("MSGFILTER hook already placed!\n");
697 return S_OK;
698 }
699 if (m_current->type == TrackedMenuEntry)
700 {
701 TRACE("Entering MSGFILTER hook...\n");
702 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
703 }
704 else
705 {
706 TRACE("Entering GETMESSAGE hook...\n");
707 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
708 }
709 return S_OK;
710}
711
712HRESULT CMenuFocusManager::RemoveHooks()
713{
714 if (m_hMsgFilterHook)
715 {
716 TRACE("Removing MSGFILTER hook...\n");
717 UnhookWindowsHookEx(m_hMsgFilterHook);
718 m_hMsgFilterHook = NULL;
719 }
720 if (m_hGetMsgHook)
721 {
722 TRACE("Removing GETMESSAGE hook...\n");
723 UnhookWindowsHookEx(m_hGetMsgHook);
724 m_hGetMsgHook = NULL;
725 }
726 return S_OK;
727}
728
729// Used to update the tracking info to account for a change in the top-level menu
730HRESULT CMenuFocusManager::UpdateFocus()
731{
732 HRESULT hr;
733 StackEntry * old = m_current;
734
735 TRACE("UpdateFocus\n");
736
737 // Assign the new current item
738 if (m_bandCount > 0)
739 m_current = &(m_bandStack[m_bandCount - 1]);
740 else
741 m_current = NULL;
742
743 // Remove the menu capture if necesary
744 if (!m_current || m_current->type != MenuPopupEntry)
745 {
746 SetMenuCapture(NULL);
747 if (old && old->type == MenuPopupEntry && m_PreviousForeground)
748 {
749 ::SetForegroundWindow(m_PreviousForeground);
750 m_PreviousForeground = NULL;
751 }
752 }
753
754 // Obtain the top-level window for the new active menu
755 if (m_current && m_current->type != TrackedMenuEntry)
756 {
757 hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
758 if (FAILED_UNEXPECTEDLY(hr))
759 return hr;
760 }
761
762 // Refresh the parent pointer
763 if (m_bandCount >= 2)
764 {
765 m_parent = &(m_bandStack[m_bandCount - 2]);
766 _ASSERT(m_parent->type != TrackedMenuEntry);
767 }
768 else
769 {
770 m_parent = NULL;
771 }
772
773 // Refresh the menubar pointer, if applicable
774 if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
775 {
776 m_menuBar = &(m_bandStack[0]);
777 }
778 else
779 {
780 m_menuBar = NULL;
781 }
782
783 // Remove the old hooks if the menu type changed, or we don't have a menu anymore
784 if (old && (!m_current || old->type != m_current->type))
785 {
786 if (m_current && m_current->type != TrackedMenuEntry)
787 {
788 DisableMouseTrack(m_current->hwnd, FALSE);
789 }
790
791 hr = RemoveHooks();
792 if (FAILED_UNEXPECTEDLY(hr))
793 return hr;
794 }
795
796 // And place new ones if necessary
797 if (m_current && (!old || old->type != m_current->type))
798 {
799 hr = PlaceHooks();
800 if (FAILED_UNEXPECTEDLY(hr))
801 return hr;
802 }
803
804 // Give the user a chance to move the mouse to the new menu
805 if (m_parent)
806 {
807 DisableMouseTrack(m_parent->hwnd, TRUE);
808 }
809
810 if (m_current && m_current->type == MenuPopupEntry)
811 {
812 if (m_captureHwnd == NULL)
813 {
814 // We need to restore the capture after a non-shell submenu or context menu is shown
815 StackEntry * topMenu = m_bandStack;
816 if (topMenu->type == MenuBarEntry)
817 topMenu++;
818
819 // Get the top-level window from the top popup
820 CComPtr<IServiceProvider> bandSite;
821 CComPtr<IOleWindow> deskBar;
822 hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
823 if (FAILED(hr))
824 goto NoCapture;
825 hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
826 if (FAILED(hr))
827 goto NoCapture;
828
829 CComPtr<IOleWindow> deskBarSite;
830 hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
831 if (FAILED(hr))
832 goto NoCapture;
833
834 // FIXME: Find the correct place for this
835 HWND hWndOwner;
836 hr = deskBarSite->GetWindow(&hWndOwner);
837 if (FAILED(hr))
838 goto NoCapture;
839
840 m_PreviousForeground = ::GetForegroundWindow();
841 if (m_PreviousForeground != hWndOwner)
842 ::SetForegroundWindow(hWndOwner);
843 else
844 m_PreviousForeground = NULL;
845
846 // Get the HWND of the top-level window
847 HWND hWndSite;
848 hr = deskBar->GetWindow(&hWndSite);
849 if (FAILED(hr))
850 goto NoCapture;
851 SetMenuCapture(hWndSite);
852 }
853NoCapture:
854
855 if (!m_parent || m_parent->type == MenuBarEntry)
856 {
857 if (old && old->type == TrackedMenuEntry)
858 {
859 // FIXME: Debugging code, probably not right
860 POINT pt2;
861 RECT rc2;
862 GetCursorPos(&pt2);
863 ScreenToClient(m_current->hwnd, &pt2);
864 GetClientRect(m_current->hwnd, &rc2);
865 if (PtInRect(&rc2, pt2))
866 SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
867 else
868 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
869 }
870 }
871 }
872
873 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
874
875 return S_OK;
876}
877
878// Begin tracking top-level menu bar (for file browser windows)
879HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
880{
881 TRACE("PushMenuBar %p\n", mb);
882
883 mb->AddRef();
884
885 _ASSERT(m_bandCount == 0);
886
887 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
888 if (FAILED_UNEXPECTEDLY(hr))
889 return hr;
890
891 return UpdateFocus();
892}
893
894// Begin tracking a shell menu popup (start menu or submenus)
895HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
896{
897 TRACE("PushTrackedPopup %p\n", mb);
898
899 mb->AddRef();
900
901 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
902
903 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
904 if (FAILED_UNEXPECTEDLY(hr))
905 return hr;
906
907 hr = UpdateFocus();
908
909 m_menuDepth++;
910
911 if (m_parent && m_parent->type != TrackedMenuEntry)
912 {
913 m_parent->mb->_SetChildBand(mb);
914 mb->_SetParentBand(m_parent->mb);
915 }
916
917 return hr;
918}
919
920// Begin tracking a system popup submenu (submenu of the file browser windows)
921HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
922{
923 TRACE("PushTrackedPopup %p\n", popup);
924
925 _ASSERT(m_bandCount > 0);
926 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
927
928 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
929 if (FAILED_UNEXPECTEDLY(hr))
930 return hr;
931
932 TRACE("PushTrackedPopup %p\n", popup);
933 m_selectedMenu = popup;
934 m_selectedItem = -1;
935 m_selectedItemFlags = 0;
936
937 return UpdateFocus();
938}
939
940// Stop tracking the menubar
941HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
942{
943 StackEntryType type;
944 CMenuBand * mbc;
945 HRESULT hr;
946
947 TRACE("PopMenuBar %p\n", mb);
948
949 if (m_current == m_entryUnderMouse)
950 {
951 m_entryUnderMouse = NULL;
952 }
953
954 hr = PopFromArray(&type, &mbc, NULL);
955 if (FAILED_UNEXPECTEDLY(hr))
956 {
957 UpdateFocus();
958 return hr;
959 }
960
961 _ASSERT(type == MenuBarEntry);
962 if (type != MenuBarEntry)
963 return E_FAIL;
964
965 if (!mbc)
966 return E_FAIL;
967
968 mbc->_SetParentBand(NULL);
969
970 mbc->Release();
971
972 hr = UpdateFocus();
973 if (FAILED_UNEXPECTEDLY(hr))
974 return hr;
975
976 if (m_current)
977 {
978 _ASSERT(m_current->type != TrackedMenuEntry);
979 m_current->mb->_SetChildBand(NULL);
980 }
981
982 return S_OK;
983}
984
985// Stop tracking a shell menu
986HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
987{
988 StackEntryType type;
989 CMenuBand * mbc;
990 HRESULT hr;
991
992 TRACE("PopMenuPopup %p\n", mb);
993
994 if (m_current == m_entryUnderMouse)
995 {
996 m_entryUnderMouse = NULL;
997 }
998
999 m_menuDepth--;
1000
1001 hr = PopFromArray(&type, &mbc, NULL);
1002 if (FAILED_UNEXPECTEDLY(hr))
1003 {
1004 UpdateFocus();
1005 return hr;
1006 }
1007
1008 _ASSERT(type == MenuPopupEntry);
1009 if (type != MenuPopupEntry)
1010 return E_FAIL;
1011
1012 if (!mbc)
1013 return E_FAIL;
1014
1015 mbc->_SetParentBand(NULL);
1016
1017 mbc->Release();
1018
1019 hr = UpdateFocus();
1020 if (FAILED_UNEXPECTEDLY(hr))
1021 return hr;
1022
1023 if (m_current)
1024 {
1025 _ASSERT(m_current->type != TrackedMenuEntry);
1026 m_current->mb->_SetChildBand(NULL);
1027 }
1028
1029 return S_OK;
1030}
1031
1032// Stop tracking a system popup submenu
1033HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1034{
1035 StackEntryType type;
1036 HMENU hmenu;
1037 HRESULT hr;
1038
1039 TRACE("PopTrackedPopup %p\n", popup);
1040
1041 hr = PopFromArray(&type, NULL, &hmenu);
1042 if (FAILED_UNEXPECTEDLY(hr))
1043 {
1044 UpdateFocus();
1045 return hr;
1046 }
1047
1048 _ASSERT(type == TrackedMenuEntry);
1049 if (type != TrackedMenuEntry)
1050 return E_FAIL;
1051
1052 if (hmenu != popup)
1053 return E_FAIL;
1054
1055 hr = UpdateFocus();
1056 if (FAILED_UNEXPECTEDLY(hr))
1057 return hr;
1058
1059 return S_OK;
1060}