[SHELL32]
[reactos.git] / reactos / 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_isLButtonDown(FALSE),
189 m_movedSinceDown(FALSE),
190 m_windowAtDown(NULL),
191 m_PreviousForeground(NULL),
192 m_bandCount(0),
193 m_menuDepth(0)
194 {
195 m_ptPrev.x = 0;
196 m_ptPrev.y = 0;
197 m_threadId = GetCurrentThreadId();
198 }
199
200 CMenuFocusManager::~CMenuFocusManager()
201 {
202 }
203
204 // Used so that the toolbar can properly ignore mouse events, when the menu is being used with the keyboard
205 void CMenuFocusManager::DisableMouseTrack(HWND parent, BOOL disableThis)
206 {
207 BOOL bDisable = FALSE;
208 BOOL lastDisable = FALSE;
209
210 int i = m_bandCount;
211 while (--i >= 0)
212 {
213 StackEntry& entry = m_bandStack[i];
214
215 if (entry.type != TrackedMenuEntry)
216 {
217 HWND hwnd;
218 HRESULT hr = entry.mb->_GetTopLevelWindow(&hwnd);
219 if (FAILED_UNEXPECTEDLY(hr))
220 break;
221
222 if (hwnd == parent)
223 {
224 lastDisable = disableThis;
225 entry.mb->_DisableMouseTrack(disableThis);
226 bDisable = TRUE;
227 }
228 else
229 {
230 lastDisable = bDisable;
231 entry.mb->_DisableMouseTrack(bDisable);
232 }
233 }
234 }
235 m_mouseTrackDisabled = lastDisable;
236 }
237
238 void CMenuFocusManager::SetMenuCapture(HWND child)
239 {
240 if (m_captureHwnd != child)
241 {
242 if (child)
243 {
244 ::SetCapture(child);
245 m_captureHwnd = child;
246 TRACE("Capturing %p\n", child);
247 }
248 else
249 {
250 ::ReleaseCapture();
251 m_captureHwnd = NULL;
252 TRACE("Capture is now off\n");
253 }
254
255 }
256 }
257
258 HRESULT CMenuFocusManager::IsTrackedWindow(HWND hWnd, StackEntry ** pentry)
259 {
260 if (pentry)
261 *pentry = NULL;
262
263 for (int i = m_bandCount; --i >= 0;)
264 {
265 StackEntry& entry = m_bandStack[i];
266
267 if (entry.type != TrackedMenuEntry)
268 {
269 HRESULT hr = entry.mb->IsWindowOwner(hWnd);
270 if (FAILED_UNEXPECTEDLY(hr))
271 return hr;
272 if (hr == S_OK)
273 {
274 if (pentry)
275 *pentry = &entry;
276 return S_OK;
277 }
278 }
279 }
280
281 return S_FALSE;
282 }
283
284 HRESULT CMenuFocusManager::IsTrackedWindowOrParent(HWND hWnd)
285 {
286 for (int i = m_bandCount; --i >= 0;)
287 {
288 StackEntry& entry = m_bandStack[i];
289
290 if (entry.type != TrackedMenuEntry)
291 {
292 HRESULT hr = entry.mb->IsWindowOwner(hWnd);
293 if (FAILED_UNEXPECTEDLY(hr))
294 return hr;
295 if (hr == S_OK)
296 return S_OK;
297 if (entry.mb->_IsPopup() == S_OK)
298 {
299 CComPtr<IUnknown> site;
300 CComPtr<IOleWindow> pw;
301 hr = entry.mb->GetSite(IID_PPV_ARG(IUnknown, &site));
302 if (FAILED_UNEXPECTEDLY(hr))
303 continue;
304 hr = IUnknown_QueryService(site, SID_SMenuBandParent, IID_PPV_ARG(IOleWindow, &pw));
305 if (FAILED_UNEXPECTEDLY(hr))
306 continue;
307
308 HWND hParent;
309 if (pw->GetWindow(&hParent) == S_OK && hParent == hWnd)
310 return S_OK;
311 }
312 }
313 }
314
315 return S_FALSE;
316 }
317
318 LRESULT CMenuFocusManager::ProcessMouseMove(MSG* msg)
319 {
320 HWND child;
321 int iHitTestResult = -1;
322
323 POINT pt2 = { GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam) };
324 ClientToScreen(msg->hwnd, &pt2);
325
326 // Don't do anything if the mouse has not been moved
327 POINT pt = msg->pt;
328 if (pt.x == m_ptPrev.x && pt.y == m_ptPrev.y)
329 return TRUE;
330
331 // Don't do anything if another window is capturing the mouse.
332 HWND cCapture = ::GetCapture();
333 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
334 return TRUE;
335
336 m_movedSinceDown = TRUE;
337
338 m_ptPrev = pt;
339
340 child = WindowFromPoint(pt);
341
342 StackEntry * entry = NULL;
343 if (IsTrackedWindow(child, &entry) == S_OK)
344 {
345 TRACE("MouseMove %d\n", m_isLButtonDown);
346 }
347
348 BOOL isTracking = FALSE;
349 if (entry && (entry->type == MenuBarEntry || m_current->type != TrackedMenuEntry))
350 {
351 ScreenToClient(child, &pt);
352 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
353 isTracking = entry->mb->_IsTracking();
354
355 if (SendMessage(child, WM_USER_ISTRACKEDITEM, iHitTestResult, 0) == S_FALSE)
356 {
357 // The current tracked item has changed, notify the toolbar
358
359 TRACE("Hot item tracking detected a change (capture=%p / cCapture=%p)...\n", m_captureHwnd, cCapture);
360 DisableMouseTrack(NULL, FALSE);
361 if (isTracking && iHitTestResult >= 0 && m_current->type == TrackedMenuEntry)
362 SendMessage(entry->hwnd, WM_CANCELMODE, 0, 0);
363 PostMessage(child, WM_USER_CHANGETRACKEDITEM, iHitTestResult, MAKELPARAM(isTracking, TRUE));
364 if (m_current->type == TrackedMenuEntry)
365 return FALSE;
366 }
367 }
368
369 if (m_entryUnderMouse != entry)
370 {
371 // Mouse moved away from a tracked window
372 if (m_entryUnderMouse)
373 {
374 m_entryUnderMouse->mb->_ChangeHotItem(NULL, -1, HICF_MOUSE);
375 }
376 }
377
378 if (m_hwndUnderMouse != child)
379 {
380 if (entry)
381 {
382 // Mouse moved to a tracked window
383 if (m_current->type == MenuPopupEntry)
384 {
385 ScreenToClient(child, &pt2);
386 SendMessage(child, WM_MOUSEMOVE, msg->wParam, MAKELPARAM(pt2.x, pt2.y));
387 }
388 }
389
390 m_hwndUnderMouse = child;
391 m_entryUnderMouse = entry;
392 }
393
394 if (m_current->type == MenuPopupEntry)
395 {
396 HWND parent = GetAncestor(child, GA_ROOT);
397 DisableMouseTrack(parent, FALSE);
398 }
399
400 return TRUE;
401 }
402
403 LRESULT CMenuFocusManager::ProcessMouseDown(MSG* msg, BOOL isLButton)
404 {
405 HWND child;
406 int iHitTestResult = -1;
407
408 TRACE("ProcessMouseDown %d %d %d\n", msg->message, msg->wParam, msg->lParam);
409
410 // Don't do anything if another window is capturing the mouse.
411 HWND cCapture = ::GetCapture();
412 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
413 {
414 TRACE("Foreign capture active.\n");
415 return TRUE;
416 }
417
418 POINT pt = msg->pt;
419
420 child = WindowFromPoint(pt);
421
422 StackEntry * entry = NULL;
423 if (IsTrackedWindow(child, &entry) != S_OK)
424 {
425 TRACE("Foreign window detected.\n");
426 return TRUE;
427 }
428
429 TRACE("MouseDown %d\n", m_isLButtonDown);
430
431 if (entry->type == MenuBarEntry)
432 {
433 if (entry != m_current)
434 {
435 TRACE("Menubar with popup active.\n");
436 return TRUE;
437 }
438 }
439
440 if (entry)
441 {
442 ScreenToClient(child, &pt);
443 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
444
445 if (iHitTestResult >= 0)
446 {
447 TRACE("MouseDown send %d\n", iHitTestResult);
448 entry->mb->_MenuBarMouseDown(child, iHitTestResult, isLButton);
449 }
450 }
451
452 msg->message = WM_NULL;
453
454 m_isLButtonDown = TRUE;
455 m_movedSinceDown = FALSE;
456 m_windowAtDown = child;
457
458 TRACE("MouseDown end %d\n", m_isLButtonDown);
459
460 return TRUE;
461 }
462
463 LRESULT CMenuFocusManager::ProcessMouseUp(MSG* msg)
464 {
465 HWND child;
466 int iHitTestResult = -1;
467
468 TRACE("ProcessMouseUp %d %d %d\n", msg->message, msg->wParam, msg->lParam);
469
470 // Don't do anything if another window is capturing the mouse.
471 HWND cCapture = ::GetCapture();
472 if (cCapture && cCapture != m_captureHwnd && m_current->type != TrackedMenuEntry)
473 return TRUE;
474
475 if (!m_isLButtonDown)
476 return TRUE;
477
478 m_isLButtonDown = FALSE;
479
480 POINT pt = msg->pt;
481
482 child = WindowFromPoint(pt);
483
484 StackEntry * entry = NULL;
485 if (IsTrackedWindow(child, &entry) != S_OK)
486 return TRUE;
487
488 TRACE("MouseUp %d\n", m_isLButtonDown);
489
490 if (entry)
491 {
492 ScreenToClient(child, &pt);
493 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
494
495 if (iHitTestResult >= 0)
496 {
497 TRACE("MouseUp send %d\n", iHitTestResult);
498 entry->mb->_MenuBarMouseUp(child, iHitTestResult);
499 }
500 }
501
502 return TRUE;
503 }
504
505 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
506 {
507 if (nCode < 0)
508 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
509
510 if (nCode == MSGF_MENU)
511 {
512 BOOL callNext = TRUE;
513 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
514
515 switch (msg->message)
516 {
517 case WM_LBUTTONDOWN:
518 case WM_RBUTTONDOWN:
519 if (m_menuBar && m_current->type == TrackedMenuEntry)
520 {
521 POINT pt = msg->pt;
522 HWND child = WindowFromPoint(pt);
523 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
524 if (hoveringMenuBar)
525 {
526 m_menuBar->mb->_BeforeCancelPopup();
527 }
528 }
529 break;
530 case WM_MOUSEMOVE:
531 callNext = ProcessMouseMove(msg);
532 break;
533 case WM_INITMENUPOPUP:
534 TRACE("WM_INITMENUPOPUP %p %p\n", msg->wParam, msg->lParam);
535 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
536 m_selectedItem = -1;
537 m_selectedItemFlags = 0;
538 break;
539 case WM_MENUSELECT:
540 TRACE("WM_MENUSELECT %p %p\n", msg->wParam, msg->lParam);
541 m_selectedMenu = reinterpret_cast<HMENU>(msg->lParam);
542 m_selectedItem = GET_X_LPARAM(msg->wParam);
543 m_selectedItemFlags = HIWORD(msg->wParam);
544 break;
545 case WM_KEYDOWN:
546 switch (msg->wParam)
547 {
548 case VK_LEFT:
549 if (m_current->hmenu == m_selectedMenu)
550 {
551 m_parent->mb->_MenuItemSelect(VK_LEFT);
552 }
553 break;
554 case VK_RIGHT:
555 if (m_selectedItem < 0 || !(m_selectedItemFlags & MF_POPUP))
556 {
557 m_parent->mb->_MenuItemSelect(VK_RIGHT);
558 }
559 break;
560 }
561 break;
562 }
563
564 if (!callNext)
565 return 1;
566 }
567
568 return CallNextHookEx(m_hMsgFilterHook, nCode, hookWParam, hookLParam);
569 }
570
571 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM hookWParam, LPARAM hookLParam)
572 {
573 BOOL isLButton = FALSE;
574 if (nCode < 0)
575 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
576
577 if (nCode == HC_ACTION)
578 {
579 BOOL callNext = TRUE;
580 MSG* msg = reinterpret_cast<MSG*>(hookLParam);
581 POINT pt = msg->pt;
582
583 switch (msg->message)
584 {
585 case WM_CAPTURECHANGED:
586 if (m_captureHwnd)
587 {
588 TRACE("Capture lost.\n");
589 m_captureHwnd = NULL;
590 }
591 break;
592
593 case WM_NCLBUTTONDOWN:
594 case WM_LBUTTONDOWN:
595 isLButton = TRUE;
596 TRACE("LB\n");
597
598 // fallthrough;
599 case WM_NCRBUTTONDOWN:
600 case WM_RBUTTONDOWN:
601 if (m_menuBar && m_current->type == MenuPopupEntry)
602 {
603 POINT pt = msg->pt;
604 HWND child = WindowFromPoint(pt);
605 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
606 if (hoveringMenuBar)
607 {
608 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
609 break;
610 }
611 }
612
613 if (m_current->type == MenuPopupEntry)
614 {
615 HWND child = WindowFromPoint(pt);
616
617 if (IsTrackedWindowOrParent(child) != S_OK)
618 {
619 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
620 break;
621 }
622 }
623
624 ProcessMouseDown(msg, isLButton);
625
626 break;
627 case WM_NCLBUTTONUP:
628 case WM_LBUTTONUP:
629 ProcessMouseUp(msg);
630 break;
631 case WM_MOUSEMOVE:
632 callNext = ProcessMouseMove(msg);
633 break;
634 case WM_MOUSELEAVE:
635 callNext = ProcessMouseMove(msg);
636 //callNext = ProcessMouseLeave(msg);
637 break;
638 case WM_SYSKEYDOWN:
639 case WM_KEYDOWN:
640 if (m_current->type == MenuPopupEntry)
641 {
642 DisableMouseTrack(m_current->hwnd, TRUE);
643 switch (msg->wParam)
644 {
645 case VK_ESCAPE:
646 case VK_MENU:
647 case VK_LMENU:
648 case VK_RMENU:
649 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
650 break;
651 case VK_RETURN:
652 m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
653 break;
654 case VK_LEFT:
655 m_current->mb->_MenuItemSelect(VK_LEFT);
656 break;
657 case VK_RIGHT:
658 m_current->mb->_MenuItemSelect(VK_RIGHT);
659 break;
660 case VK_UP:
661 m_current->mb->_MenuItemSelect(VK_UP);
662 break;
663 case VK_DOWN:
664 m_current->mb->_MenuItemSelect(VK_DOWN);
665 break;
666 }
667 msg->message = WM_NULL;
668 msg->lParam = 0;
669 msg->wParam = 0;
670 }
671 break;
672 }
673
674 if (!callNext)
675 return 1;
676 }
677
678 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
679 }
680
681 HRESULT CMenuFocusManager::PlaceHooks()
682 {
683 if (m_hMsgFilterHook)
684 {
685 WARN("GETMESSAGE hook already placed!\n");
686 return S_OK;
687 }
688 if (m_hMsgFilterHook)
689 {
690 WARN("MSGFILTER hook already placed!\n");
691 return S_OK;
692 }
693 if (m_current->type == TrackedMenuEntry)
694 {
695 TRACE("Entering MSGFILTER hook...\n");
696 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
697 }
698 else
699 {
700 TRACE("Entering GETMESSAGE hook...\n");
701 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
702 }
703 return S_OK;
704 }
705
706 HRESULT CMenuFocusManager::RemoveHooks()
707 {
708 if (m_hMsgFilterHook)
709 {
710 TRACE("Removing MSGFILTER hook...\n");
711 UnhookWindowsHookEx(m_hMsgFilterHook);
712 m_hMsgFilterHook = NULL;
713 }
714 if (m_hGetMsgHook)
715 {
716 TRACE("Removing GETMESSAGE hook...\n");
717 UnhookWindowsHookEx(m_hGetMsgHook);
718 m_hGetMsgHook = NULL;
719 }
720 return S_OK;
721 }
722
723 // Used to update the tracking info to account for a change in the top-level menu
724 HRESULT CMenuFocusManager::UpdateFocus()
725 {
726 HRESULT hr;
727 StackEntry * old = m_current;
728
729 TRACE("UpdateFocus\n");
730
731 // Assign the new current item
732 if (m_bandCount > 0)
733 m_current = &(m_bandStack[m_bandCount - 1]);
734 else
735 m_current = NULL;
736
737 // Remove the menu capture if necesary
738 if (!m_current || m_current->type != MenuPopupEntry)
739 {
740 SetMenuCapture(NULL);
741 if (old && old->type == MenuPopupEntry && m_PreviousForeground)
742 {
743 ::SetForegroundWindow(m_PreviousForeground);
744 m_PreviousForeground = NULL;
745 }
746 }
747
748 // Obtain the top-level window for the new active menu
749 if (m_current && m_current->type != TrackedMenuEntry)
750 {
751 hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
752 if (FAILED_UNEXPECTEDLY(hr))
753 return hr;
754 }
755
756 // Refresh the parent pointer
757 if (m_bandCount >= 2)
758 {
759 m_parent = &(m_bandStack[m_bandCount - 2]);
760 _ASSERT(m_parent->type != TrackedMenuEntry);
761 }
762 else
763 {
764 m_parent = NULL;
765 }
766
767 // Refresh the menubar pointer, if applicable
768 if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
769 {
770 m_menuBar = &(m_bandStack[0]);
771 }
772 else
773 {
774 m_menuBar = NULL;
775 }
776
777 // Remove the old hooks if the menu type changed, or we don't have a menu anymore
778 if (old && (!m_current || old->type != m_current->type))
779 {
780 if (m_current && m_current->type != TrackedMenuEntry)
781 {
782 DisableMouseTrack(m_current->hwnd, FALSE);
783 }
784
785 hr = RemoveHooks();
786 if (FAILED_UNEXPECTEDLY(hr))
787 return hr;
788 }
789
790 // And place new ones if necessary
791 if (m_current && (!old || old->type != m_current->type))
792 {
793 hr = PlaceHooks();
794 if (FAILED_UNEXPECTEDLY(hr))
795 return hr;
796 }
797
798 // Give the user a chance to move the mouse to the new menu
799 if (m_parent)
800 {
801 DisableMouseTrack(m_parent->hwnd, TRUE);
802 }
803
804 if (m_current && m_current->type == MenuPopupEntry)
805 {
806 if (m_captureHwnd == NULL)
807 {
808 // We need to restore the capture after a non-shell submenu or context menu is shown
809 StackEntry * topMenu = m_bandStack;
810 if (topMenu->type == MenuBarEntry)
811 topMenu++;
812
813 // Get the top-level window from the top popup
814 CComPtr<IServiceProvider> bandSite;
815 CComPtr<IOleWindow> deskBar;
816 hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
817 if (FAILED(hr))
818 goto NoCapture;
819 hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
820 if (FAILED(hr))
821 goto NoCapture;
822
823 CComPtr<IOleWindow> deskBarSite;
824 hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
825 if (FAILED(hr))
826 goto NoCapture;
827
828 // FIXME: Find the correct place for this
829 HWND hWndOwner;
830 hr = deskBarSite->GetWindow(&hWndOwner);
831 if (FAILED(hr))
832 goto NoCapture;
833
834 m_PreviousForeground = ::GetForegroundWindow();
835 if (m_PreviousForeground != hWndOwner)
836 ::SetForegroundWindow(hWndOwner);
837 else
838 m_PreviousForeground = NULL;
839
840 // Get the HWND of the top-level window
841 HWND hWndSite;
842 hr = deskBar->GetWindow(&hWndSite);
843 if (FAILED(hr))
844 goto NoCapture;
845 SetMenuCapture(hWndSite);
846 }
847 NoCapture:
848
849 if (!m_parent || m_parent->type == MenuBarEntry)
850 {
851 if (old && old->type == TrackedMenuEntry)
852 {
853 // FIXME: Debugging code, probably not right
854 POINT pt2;
855 RECT rc2;
856 GetCursorPos(&pt2);
857 ScreenToClient(m_current->hwnd, &pt2);
858 GetClientRect(m_current->hwnd, &rc2);
859 if (PtInRect(&rc2, pt2))
860 SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
861 else
862 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
863 }
864 }
865 }
866
867 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
868
869 return S_OK;
870 }
871
872 // Begin tracking top-level menu bar (for file browser windows)
873 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
874 {
875 TRACE("PushMenuBar %p\n", mb);
876
877 mb->AddRef();
878
879 _ASSERT(m_bandCount == 0);
880
881 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
882 if (FAILED_UNEXPECTEDLY(hr))
883 return hr;
884
885 return UpdateFocus();
886 }
887
888 // Begin tracking a shell menu popup (start menu or submenus)
889 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
890 {
891 TRACE("PushTrackedPopup %p\n", mb);
892
893 mb->AddRef();
894
895 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
896
897 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
898 if (FAILED_UNEXPECTEDLY(hr))
899 return hr;
900
901 hr = UpdateFocus();
902
903 m_menuDepth++;
904
905 if (m_parent && m_parent->type != TrackedMenuEntry)
906 {
907 m_parent->mb->_SetChildBand(mb);
908 mb->_SetParentBand(m_parent->mb);
909 }
910
911 return hr;
912 }
913
914 // Begin tracking a system popup submenu (submenu of the file browser windows)
915 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
916 {
917 TRACE("PushTrackedPopup %p\n", popup);
918
919 _ASSERT(m_bandCount > 0);
920 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
921
922 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
923 if (FAILED_UNEXPECTEDLY(hr))
924 return hr;
925
926 TRACE("PushTrackedPopup %p\n", popup);
927 m_selectedMenu = popup;
928 m_selectedItem = -1;
929 m_selectedItemFlags = 0;
930
931 return UpdateFocus();
932 }
933
934 // Stop tracking the menubar
935 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
936 {
937 StackEntryType type;
938 CMenuBand * mbc;
939 HRESULT hr;
940
941 TRACE("PopMenuBar %p\n", mb);
942
943 if (m_current == m_entryUnderMouse)
944 {
945 m_entryUnderMouse = NULL;
946 }
947
948 hr = PopFromArray(&type, &mbc, NULL);
949 if (FAILED_UNEXPECTEDLY(hr))
950 {
951 UpdateFocus();
952 return hr;
953 }
954
955 _ASSERT(type == MenuBarEntry);
956 if (type != MenuBarEntry)
957 return E_FAIL;
958
959 if (!mbc)
960 return E_FAIL;
961
962 mbc->_SetParentBand(NULL);
963
964 mbc->Release();
965
966 hr = UpdateFocus();
967 if (FAILED_UNEXPECTEDLY(hr))
968 return hr;
969
970 if (m_current)
971 {
972 _ASSERT(m_current->type != TrackedMenuEntry);
973 m_current->mb->_SetChildBand(NULL);
974 }
975
976 return S_OK;
977 }
978
979 // Stop tracking a shell menu
980 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
981 {
982 StackEntryType type;
983 CMenuBand * mbc;
984 HRESULT hr;
985
986 TRACE("PopMenuPopup %p\n", mb);
987
988 if (m_current == m_entryUnderMouse)
989 {
990 m_entryUnderMouse = NULL;
991 }
992
993 m_menuDepth--;
994
995 hr = PopFromArray(&type, &mbc, NULL);
996 if (FAILED_UNEXPECTEDLY(hr))
997 {
998 UpdateFocus();
999 return hr;
1000 }
1001
1002 _ASSERT(type == MenuPopupEntry);
1003 if (type != MenuPopupEntry)
1004 return E_FAIL;
1005
1006 if (!mbc)
1007 return E_FAIL;
1008
1009 mbc->_SetParentBand(NULL);
1010
1011 mbc->Release();
1012
1013 hr = UpdateFocus();
1014 if (FAILED_UNEXPECTEDLY(hr))
1015 return hr;
1016
1017 if (m_current)
1018 {
1019 _ASSERT(m_current->type != TrackedMenuEntry);
1020 m_current->mb->_SetChildBand(NULL);
1021 }
1022
1023 return S_OK;
1024 }
1025
1026 // Stop tracking a system popup submenu
1027 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1028 {
1029 StackEntryType type;
1030 HMENU hmenu;
1031 HRESULT hr;
1032
1033 TRACE("PopTrackedPopup %p\n", popup);
1034
1035 hr = PopFromArray(&type, NULL, &hmenu);
1036 if (FAILED_UNEXPECTEDLY(hr))
1037 {
1038 UpdateFocus();
1039 return hr;
1040 }
1041
1042 _ASSERT(type == TrackedMenuEntry);
1043 if (type != TrackedMenuEntry)
1044 return E_FAIL;
1045
1046 if (hmenu != popup)
1047 return E_FAIL;
1048
1049 hr = UpdateFocus();
1050 if (FAILED_UNEXPECTEDLY(hr))
1051 return hr;
1052
1053 return S_OK;
1054 }