[SHELL32] Fix Control_RunDLLW (#5400)
[reactos.git] / dll / win32 / shell32 / shellmenu / CMenuFocusManager.cpp
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 /*
22 This file implements the CMenuFocusManager class.
23
24 This class manages the shell menus, by overriding the hot-tracking behaviour.
25
26 For the shell menus, it uses a GetMessage hook,
27 where it intercepts messages directed to the menu windows.
28
29 In order to show submenus using system popups, it also has a MessageFilter hook.
30
31 The menu is tracked using a stack structure. When a CMenuBand wants to open a submenu,
32 it pushes the submenu band, or HMENU to track in case of system popups,
33 and when the menu has closed, it pops the same pointer or handle.
34
35 While a shell menu is open, it overrides the menu toolbar's hottracking behaviour,
36 using its own logic to track both the active menu item, and the opened submenu's parent item.
37
38 While a system popup is open, it tracks the mouse movements so that it can cancel the popup,
39 and 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
55 bool 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
86 WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus);
87
88 DWORD CMenuFocusManager::TlsIndex = 0;
89
90 // Gets the thread's assigned manager without refcounting
91 CMenuFocusManager * CMenuFocusManager::GetManager()
92 {
93 return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
94 }
95
96 // Obtains a manager for the thread, with refcounting
97 CMenuFocusManager * 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
121 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
122 {
123 if (!obj->Release())
124 {
125 TlsSetValue(TlsIndex, NULL);
126 }
127 }
128
129 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
130 {
131 return GetManager()->MsgFilterHook(nCode, wParam, lParam);
132 }
133
134 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
135 {
136 return GetManager()->GetMsgHook(nCode, wParam, lParam);
137 }
138
139 HRESULT 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
152 HRESULT 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
176 CMenuFocusManager::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
199 CMenuFocusManager::~CMenuFocusManager()
200 {
201 }
202
203 // Used so that the toolbar can properly ignore mouse events, when the menu is being used with the keyboard
204 void 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
237 void 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
257 HRESULT 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
283 HRESULT 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
317 LRESULT 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");
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 (SendMessage(child, WM_USER_ISTRACKEDITEM, iHitTestResult, 0) == S_FALSE)
352 {
353 // The current tracked item has changed, notify the toolbar
354
355 TRACE("Hot item tracking detected a change (capture=%p / cCapture=%p)...\n", m_captureHwnd, cCapture);
356 DisableMouseTrack(NULL, FALSE);
357 if (isTracking && iHitTestResult >= 0 && m_current->type == TrackedMenuEntry)
358 SendMessage(entry->hwnd, WM_CANCELMODE, 0, 0);
359 PostMessage(child, WM_USER_CHANGETRACKEDITEM, iHitTestResult, MAKELPARAM(isTracking, TRUE));
360 if (m_current->type == TrackedMenuEntry)
361 return FALSE;
362 }
363 }
364
365 if (m_entryUnderMouse != entry)
366 {
367 // Mouse moved away from a tracked window
368 if (m_entryUnderMouse)
369 {
370 m_entryUnderMouse->mb->_ChangeHotItem(NULL, -1, HICF_MOUSE);
371 }
372 }
373
374 if (m_hwndUnderMouse != child)
375 {
376 if (entry)
377 {
378 // Mouse moved to a tracked window
379 if (m_current->type == MenuPopupEntry)
380 {
381 ScreenToClient(child, &pt2);
382 SendMessage(child, WM_MOUSEMOVE, msg->wParam, MAKELPARAM(pt2.x, pt2.y));
383 }
384 }
385
386 m_hwndUnderMouse = child;
387 m_entryUnderMouse = entry;
388 }
389
390 if (m_current->type == MenuPopupEntry)
391 {
392 HWND parent = GetAncestor(child, GA_ROOT);
393 DisableMouseTrack(parent, FALSE);
394 }
395
396 return TRUE;
397 }
398
399 LRESULT CMenuFocusManager::ProcessMouseDown(MSG* msg, BOOL isLButton)
400 {
401 HWND child;
402 int iHitTestResult = -1;
403
404 TRACE("ProcessMouseDown %d %d %d\n", msg->message, msg->wParam, msg->lParam);
405
406 // Don't do anything if another window is capturing the mouse.
407 HWND cCapture = ::GetCapture();
408 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
409 {
410 TRACE("Foreign capture active.\n");
411 return TRUE;
412 }
413
414 POINT pt = msg->pt;
415
416 child = WindowFromPoint(pt);
417
418 StackEntry * entry = NULL;
419 if (IsTrackedWindow(child, &entry) != S_OK)
420 {
421 TRACE("Foreign window detected.\n");
422 return TRUE;
423 }
424
425 if (entry->type == MenuBarEntry)
426 {
427 if (entry != m_current)
428 {
429 TRACE("Menubar with popup active.\n");
430 return TRUE;
431 }
432 }
433
434 if (entry)
435 {
436 ScreenToClient(child, &pt);
437 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
438
439 if (iHitTestResult >= 0)
440 {
441 TRACE("MouseDown send %d\n", iHitTestResult);
442 entry->mb->_MenuBarMouseDown(child, iHitTestResult, isLButton);
443 }
444 }
445
446 msg->message = WM_NULL;
447
448 m_movedSinceDown = FALSE;
449 m_windowAtDown = child;
450
451 TRACE("MouseDown end\n");
452
453 return TRUE;
454 }
455
456 LRESULT CMenuFocusManager::ProcessMouseUp(MSG* msg, BOOL isLButton)
457 {
458 HWND child;
459 int iHitTestResult = -1;
460
461 TRACE("ProcessMouseUp %d %d %d\n", msg->message, msg->wParam, msg->lParam);
462
463 // Don't do anything if another window is capturing the mouse.
464 HWND cCapture = ::GetCapture();
465 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
466 return TRUE;
467
468 POINT pt = msg->pt;
469
470 child = WindowFromPoint(pt);
471
472 StackEntry * entry = NULL;
473 if (IsTrackedWindow(child, &entry) != S_OK)
474 return TRUE;
475
476 if (entry)
477 {
478 ScreenToClient(child, &pt);
479 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
480
481 if (iHitTestResult >= 0)
482 {
483 TRACE("MouseUp send %d\n", iHitTestResult);
484 entry->mb->_MenuBarMouseUp(child, iHitTestResult, isLButton);
485 }
486 }
487
488 return TRUE;
489 }
490
491 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
492 {
493 if (nCode < 0)
494 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
495
496 if (nCode == MSGF_MENU)
497 {
498 BOOL callNext = TRUE;
499 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
500
501 switch (msg->message)
502 {
503 case WM_LBUTTONDOWN:
504 case WM_RBUTTONDOWN:
505 if (m_menuBar && m_current->type == TrackedMenuEntry)
506 {
507 POINT pt = msg->pt;
508 HWND child = WindowFromPoint(pt);
509 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
510 if (hoveringMenuBar)
511 {
512 m_menuBar->mb->_BeforeCancelPopup();
513 }
514 }
515 break;
516 case WM_MOUSEMOVE:
517 callNext = ProcessMouseMove(msg);
518 break;
519 case WM_INITMENUPOPUP:
520 TRACE("WM_INITMENUPOPUP %p %p\n", msg->wParam, msg->lParam);
521 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
522 m_selectedItem = -1;
523 m_selectedItemFlags = 0;
524 break;
525 case WM_MENUSELECT:
526 TRACE("WM_MENUSELECT %p %p\n", msg->wParam, msg->lParam);
527 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
528 m_selectedItem = GET_X_LPARAM(msg->wParam);
529 m_selectedItemFlags = HIWORD(msg->wParam);
530 break;
531 case WM_KEYDOWN:
532 switch (msg->wParam)
533 {
534 case VK_LEFT:
535 if (m_current->hmenu == m_selectedMenu)
536 {
537 m_parent->mb->_MenuItemSelect(VK_LEFT);
538 }
539 break;
540 case VK_RIGHT:
541 if (m_selectedItem < 0 || !(m_selectedItemFlags & MF_POPUP))
542 {
543 m_parent->mb->_MenuItemSelect(VK_RIGHT);
544 }
545 break;
546 }
547 break;
548 }
549
550 if (!callNext)
551 return 1;
552 }
553
554 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
555 }
556
557 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
558 {
559 BOOL isLButton = FALSE;
560 if (nCode < 0)
561 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
562
563 if (nCode == HC_ACTION)
564 {
565 BOOL callNext = TRUE;
566 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
567 POINT pt = msg->pt;
568
569 switch (msg->message)
570 {
571 case WM_CAPTURECHANGED:
572 if (m_captureHwnd)
573 {
574 TRACE("Capture lost.\n");
575 m_captureHwnd = NULL;
576 }
577 break;
578
579 case WM_NCLBUTTONDOWN:
580 case WM_LBUTTONDOWN:
581 isLButton = TRUE;
582 TRACE("LB\n");
583
584 if (m_menuBar && m_current->type == MenuPopupEntry)
585 {
586 POINT pt = msg->pt;
587 HWND child = WindowFromPoint(pt);
588 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
589 if (hoveringMenuBar)
590 {
591 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
592 break;
593 }
594 }
595
596 if (m_current->type == MenuPopupEntry)
597 {
598 HWND child = WindowFromPoint(pt);
599
600 if (IsTrackedWindowOrParent(child) != S_OK)
601 {
602 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
603 break;
604 }
605 }
606
607 ProcessMouseDown(msg, isLButton);
608
609 break;
610 case WM_NCRBUTTONUP:
611 case WM_RBUTTONUP:
612 ProcessMouseUp(msg, isLButton);
613 break;
614 case WM_NCLBUTTONUP:
615 case WM_LBUTTONUP:
616 isLButton = TRUE;
617 ProcessMouseUp(msg, isLButton);
618 break;
619 case WM_MOUSEMOVE:
620 callNext = ProcessMouseMove(msg);
621 break;
622 case WM_MOUSELEAVE:
623 callNext = ProcessMouseMove(msg);
624 //callNext = ProcessMouseLeave(msg);
625 break;
626 case WM_SYSKEYDOWN:
627 case WM_KEYDOWN:
628 if (m_current->type == MenuPopupEntry)
629 {
630 DisableMouseTrack(m_current->hwnd, TRUE);
631 switch (msg->wParam)
632 {
633 case VK_ESCAPE:
634 case VK_MENU:
635 case VK_LMENU:
636 case VK_RMENU:
637 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
638 break;
639 case VK_RETURN:
640 m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
641 break;
642 case VK_LEFT:
643 m_current->mb->_MenuItemSelect(VK_LEFT);
644 break;
645 case VK_RIGHT:
646 m_current->mb->_MenuItemSelect(VK_RIGHT);
647 break;
648 case VK_UP:
649 m_current->mb->_MenuItemSelect(VK_UP);
650 break;
651 case VK_DOWN:
652 m_current->mb->_MenuItemSelect(VK_DOWN);
653 break;
654 }
655 msg->message = WM_NULL;
656 msg->lParam = 0;
657 msg->wParam = 0;
658 }
659 break;
660 }
661
662 if (!callNext)
663 return 1;
664 }
665
666 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
667 }
668
669 HRESULT CMenuFocusManager::PlaceHooks()
670 {
671 if (m_hGetMsgHook)
672 {
673 WARN("GETMESSAGE hook already placed!\n");
674 return S_OK;
675 }
676 if (m_hMsgFilterHook)
677 {
678 WARN("MSGFILTER hook already placed!\n");
679 return S_OK;
680 }
681 if (m_current->type == TrackedMenuEntry)
682 {
683 TRACE("Entering MSGFILTER hook...\n");
684 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
685 }
686 else
687 {
688 TRACE("Entering GETMESSAGE hook...\n");
689 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
690 }
691 return S_OK;
692 }
693
694 HRESULT CMenuFocusManager::RemoveHooks()
695 {
696 if (m_hMsgFilterHook)
697 {
698 TRACE("Removing MSGFILTER hook...\n");
699 UnhookWindowsHookEx(m_hMsgFilterHook);
700 m_hMsgFilterHook = NULL;
701 }
702 if (m_hGetMsgHook)
703 {
704 TRACE("Removing GETMESSAGE hook...\n");
705 UnhookWindowsHookEx(m_hGetMsgHook);
706 m_hGetMsgHook = NULL;
707 }
708 return S_OK;
709 }
710
711 // Used to update the tracking info to account for a change in the top-level menu
712 HRESULT CMenuFocusManager::UpdateFocus()
713 {
714 HRESULT hr;
715 StackEntry * old = m_current;
716
717 TRACE("UpdateFocus\n");
718
719 // Assign the new current item
720 if (m_bandCount > 0)
721 m_current = &(m_bandStack[m_bandCount - 1]);
722 else
723 m_current = NULL;
724
725 // Remove the menu capture if necesary
726 if (!m_current || m_current->type != MenuPopupEntry)
727 {
728 SetMenuCapture(NULL);
729 if (old && old->type == MenuPopupEntry && m_PreviousForeground)
730 {
731 ::SetForegroundWindow(m_PreviousForeground);
732 m_PreviousForeground = NULL;
733 }
734 }
735
736 // Obtain the top-level window for the new active menu
737 if (m_current && m_current->type != TrackedMenuEntry)
738 {
739 hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
740 if (FAILED_UNEXPECTEDLY(hr))
741 return hr;
742 }
743
744 // Refresh the parent pointer
745 if (m_bandCount >= 2)
746 {
747 m_parent = &(m_bandStack[m_bandCount - 2]);
748 _ASSERT(m_parent->type != TrackedMenuEntry);
749 }
750 else
751 {
752 m_parent = NULL;
753 }
754
755 // Refresh the menubar pointer, if applicable
756 if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
757 {
758 m_menuBar = &(m_bandStack[0]);
759 }
760 else
761 {
762 m_menuBar = NULL;
763 }
764
765 // Remove the old hooks if the menu type changed, or we don't have a menu anymore
766 if (old && (!m_current || old->type != m_current->type))
767 {
768 if (m_current && m_current->type != TrackedMenuEntry)
769 {
770 DisableMouseTrack(m_current->hwnd, FALSE);
771 }
772
773 hr = RemoveHooks();
774 if (FAILED_UNEXPECTEDLY(hr))
775 return hr;
776 }
777
778 // And place new ones if necessary
779 if (m_current && (!old || old->type != m_current->type))
780 {
781 hr = PlaceHooks();
782 if (FAILED_UNEXPECTEDLY(hr))
783 return hr;
784 }
785
786 // Give the user a chance to move the mouse to the new menu
787 if (m_parent)
788 {
789 DisableMouseTrack(m_parent->hwnd, TRUE);
790 }
791
792 if (m_current && m_current->type == MenuPopupEntry)
793 {
794 if (m_captureHwnd == NULL)
795 {
796 // We need to restore the capture after a non-shell submenu or context menu is shown
797 StackEntry * topMenu = m_bandStack;
798 if (topMenu->type == MenuBarEntry)
799 topMenu++;
800
801 // Get the top-level window from the top popup
802 CComPtr<IServiceProvider> bandSite;
803 CComPtr<IOleWindow> deskBar;
804 hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
805 if (FAILED(hr))
806 goto NoCapture;
807 hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
808 if (FAILED(hr))
809 goto NoCapture;
810
811 CComPtr<IOleWindow> deskBarSite;
812 hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
813 if (FAILED(hr))
814 goto NoCapture;
815
816 // FIXME: Find the correct place for this
817 HWND hWndOwner;
818 hr = deskBarSite->GetWindow(&hWndOwner);
819 if (FAILED(hr))
820 goto NoCapture;
821
822 m_PreviousForeground = ::GetForegroundWindow();
823 if (m_PreviousForeground != hWndOwner)
824 ::SetForegroundWindow(hWndOwner);
825 else
826 m_PreviousForeground = NULL;
827
828 // Get the HWND of the top-level window
829 HWND hWndSite;
830 hr = deskBar->GetWindow(&hWndSite);
831 if (FAILED(hr))
832 goto NoCapture;
833 SetMenuCapture(hWndSite);
834 }
835 NoCapture:
836
837 if (!m_parent || m_parent->type == MenuBarEntry)
838 {
839 if (old && old->type == TrackedMenuEntry)
840 {
841 // FIXME: Debugging code, probably not right
842 POINT pt2;
843 RECT rc2;
844 GetCursorPos(&pt2);
845 ScreenToClient(m_current->hwnd, &pt2);
846 GetClientRect(m_current->hwnd, &rc2);
847 if (PtInRect(&rc2, pt2))
848 SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
849 else
850 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
851 }
852 }
853 }
854
855 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
856
857 return S_OK;
858 }
859
860 // Begin tracking top-level menu bar (for file browser windows)
861 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
862 {
863 TRACE("PushMenuBar %p\n", mb);
864
865 mb->AddRef();
866
867 _ASSERT(m_bandCount == 0);
868
869 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
870 if (FAILED_UNEXPECTEDLY(hr))
871 return hr;
872
873 return UpdateFocus();
874 }
875
876 // Begin tracking a shell menu popup (start menu or submenus)
877 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
878 {
879 TRACE("PushTrackedPopup %p\n", mb);
880
881 mb->AddRef();
882
883 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
884
885 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
886 if (FAILED_UNEXPECTEDLY(hr))
887 return hr;
888
889 hr = UpdateFocus();
890
891 m_menuDepth++;
892
893 if (m_parent && m_parent->type != TrackedMenuEntry)
894 {
895 m_parent->mb->_SetChildBand(mb);
896 mb->_SetParentBand(m_parent->mb);
897 }
898
899 return hr;
900 }
901
902 // Begin tracking a system popup submenu (submenu of the file browser windows)
903 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
904 {
905 TRACE("PushTrackedPopup %p\n", popup);
906
907 _ASSERT(m_bandCount > 0);
908 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
909
910 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
911 if (FAILED_UNEXPECTEDLY(hr))
912 return hr;
913
914 TRACE("PushTrackedPopup %p\n", popup);
915 m_selectedMenu = popup;
916 m_selectedItem = -1;
917 m_selectedItemFlags = 0;
918
919 return UpdateFocus();
920 }
921
922 // Stop tracking the menubar
923 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
924 {
925 StackEntryType type;
926 CMenuBand * mbc;
927 HRESULT hr;
928
929 TRACE("PopMenuBar %p\n", mb);
930
931 if (m_current == m_entryUnderMouse)
932 {
933 m_entryUnderMouse = NULL;
934 }
935
936 hr = PopFromArray(&type, &mbc, NULL);
937 if (FAILED_UNEXPECTEDLY(hr))
938 {
939 UpdateFocus();
940 return hr;
941 }
942
943 _ASSERT(type == MenuBarEntry);
944 if (type != MenuBarEntry)
945 return E_FAIL;
946
947 if (!mbc)
948 return E_FAIL;
949
950 mbc->_SetParentBand(NULL);
951
952 mbc->Release();
953
954 hr = UpdateFocus();
955 if (FAILED_UNEXPECTEDLY(hr))
956 return hr;
957
958 if (m_current)
959 {
960 _ASSERT(m_current->type != TrackedMenuEntry);
961 m_current->mb->_SetChildBand(NULL);
962 }
963
964 return S_OK;
965 }
966
967 // Stop tracking a shell menu
968 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
969 {
970 StackEntryType type;
971 CMenuBand * mbc;
972 HRESULT hr;
973
974 TRACE("PopMenuPopup %p\n", mb);
975
976 if (m_current == m_entryUnderMouse)
977 {
978 m_entryUnderMouse = NULL;
979 }
980
981 m_menuDepth--;
982
983 hr = PopFromArray(&type, &mbc, NULL);
984 if (FAILED_UNEXPECTEDLY(hr))
985 {
986 UpdateFocus();
987 return hr;
988 }
989
990 _ASSERT(type == MenuPopupEntry);
991 if (type != MenuPopupEntry)
992 return E_FAIL;
993
994 if (!mbc)
995 return E_FAIL;
996
997 mbc->_SetParentBand(NULL);
998
999 mbc->Release();
1000
1001 hr = UpdateFocus();
1002 if (FAILED_UNEXPECTEDLY(hr))
1003 return hr;
1004
1005 if (m_current)
1006 {
1007 _ASSERT(m_current->type != TrackedMenuEntry);
1008 m_current->mb->_SetChildBand(NULL);
1009 }
1010
1011 return S_OK;
1012 }
1013
1014 // Stop tracking a system popup submenu
1015 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1016 {
1017 StackEntryType type;
1018 HMENU hmenu;
1019 HRESULT hr;
1020
1021 TRACE("PopTrackedPopup %p\n", popup);
1022
1023 hr = PopFromArray(&type, NULL, &hmenu);
1024 if (FAILED_UNEXPECTEDLY(hr))
1025 {
1026 UpdateFocus();
1027 return hr;
1028 }
1029
1030 _ASSERT(type == TrackedMenuEntry);
1031 if (type != TrackedMenuEntry)
1032 return E_FAIL;
1033
1034 if (hmenu != popup)
1035 return E_FAIL;
1036
1037 hr = UpdateFocus();
1038 if (FAILED_UNEXPECTEDLY(hr))
1039 return hr;
1040
1041 return S_OK;
1042 }