45bb4aa6f8623949181c63275a6b179ca3b5b817
[reactos.git] / reactos / base / shell / rshell / 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 "precomp.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 hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
818
819 CComPtr<IOleWindow> deskBarSite;
820 hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
821
822 // FIXME: Find the correct place for this
823 HWND hWndOwner;
824 deskBarSite->GetWindow(&hWndOwner);
825
826 m_PreviousForeground = ::GetForegroundWindow();
827 if (m_PreviousForeground != hWndOwner)
828 ::SetForegroundWindow(hWndOwner);
829 else
830 m_PreviousForeground = NULL;
831
832 // Get the HWND of the top-level window
833 HWND hWndSite;
834 hr = deskBar->GetWindow(&hWndSite);
835 SetMenuCapture(hWndSite);
836
837 }
838
839 if (!m_parent || m_parent->type == MenuBarEntry)
840 {
841 if (old && old->type == TrackedMenuEntry)
842 {
843 // FIXME: Debugging code, probably not right
844 POINT pt2;
845 RECT rc2;
846 GetCursorPos(&pt2);
847 ScreenToClient(m_current->hwnd, &pt2);
848 GetClientRect(m_current->hwnd, &rc2);
849 if (PtInRect(&rc2, pt2))
850 SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
851 else
852 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
853 }
854 }
855 }
856
857 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
858
859 return S_OK;
860 }
861
862 // Begin tracking top-level menu bar (for file browser windows)
863 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
864 {
865 TRACE("PushMenuBar %p\n", mb);
866
867 mb->AddRef();
868
869 _ASSERT(m_bandCount == 0);
870
871 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
872 if (FAILED_UNEXPECTEDLY(hr))
873 return hr;
874
875 return UpdateFocus();
876 }
877
878 // Begin tracking a shell menu popup (start menu or submenus)
879 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
880 {
881 TRACE("PushTrackedPopup %p\n", mb);
882
883 mb->AddRef();
884
885 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
886
887 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
888 if (FAILED_UNEXPECTEDLY(hr))
889 return hr;
890
891 hr = UpdateFocus();
892
893 m_menuDepth++;
894
895 if (m_parent && m_parent->type != TrackedMenuEntry)
896 {
897 m_parent->mb->_SetChildBand(mb);
898 mb->_SetParentBand(m_parent->mb);
899 }
900
901 return hr;
902 }
903
904 // Begin tracking a system popup submenu (submenu of the file browser windows)
905 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
906 {
907 TRACE("PushTrackedPopup %p\n", popup);
908
909 _ASSERT(m_bandCount > 0);
910 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
911
912 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
913 if (FAILED_UNEXPECTEDLY(hr))
914 return hr;
915
916 TRACE("PushTrackedPopup %p\n", popup);
917 m_selectedMenu = popup;
918 m_selectedItem = -1;
919 m_selectedItemFlags = 0;
920
921 return UpdateFocus();
922 }
923
924 // Stop tracking the menubar
925 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
926 {
927 StackEntryType type;
928 CMenuBand * mbc;
929 HRESULT hr;
930
931 TRACE("PopMenuBar %p\n", mb);
932
933 if (m_current == m_entryUnderMouse)
934 {
935 m_entryUnderMouse = NULL;
936 }
937
938 hr = PopFromArray(&type, &mbc, NULL);
939 if (FAILED_UNEXPECTEDLY(hr))
940 {
941 UpdateFocus();
942 return hr;
943 }
944
945 _ASSERT(type == MenuBarEntry);
946 if (type != MenuBarEntry)
947 return E_FAIL;
948
949 if (!mbc)
950 return E_FAIL;
951
952 mbc->_SetParentBand(NULL);
953
954 mbc->Release();
955
956 hr = UpdateFocus();
957 if (FAILED_UNEXPECTEDLY(hr))
958 return hr;
959
960 if (m_current)
961 {
962 _ASSERT(m_current->type != TrackedMenuEntry);
963 m_current->mb->_SetChildBand(NULL);
964 }
965
966 return S_OK;
967 }
968
969 // Stop tracking a shell menu
970 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
971 {
972 StackEntryType type;
973 CMenuBand * mbc;
974 HRESULT hr;
975
976 TRACE("PopMenuPopup %p\n", mb);
977
978 if (m_current == m_entryUnderMouse)
979 {
980 m_entryUnderMouse = NULL;
981 }
982
983 m_menuDepth--;
984
985 hr = PopFromArray(&type, &mbc, NULL);
986 if (FAILED_UNEXPECTEDLY(hr))
987 {
988 UpdateFocus();
989 return hr;
990 }
991
992 _ASSERT(type == MenuPopupEntry);
993 if (type != MenuPopupEntry)
994 return E_FAIL;
995
996 if (!mbc)
997 return E_FAIL;
998
999 mbc->_SetParentBand(NULL);
1000
1001 mbc->Release();
1002
1003 hr = UpdateFocus();
1004 if (FAILED_UNEXPECTEDLY(hr))
1005 return hr;
1006
1007 if (m_current)
1008 {
1009 _ASSERT(m_current->type != TrackedMenuEntry);
1010 m_current->mb->_SetChildBand(NULL);
1011 }
1012
1013 return S_OK;
1014 }
1015
1016 // Stop tracking a system popup submenu
1017 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1018 {
1019 StackEntryType type;
1020 HMENU hmenu;
1021 HRESULT hr;
1022
1023 TRACE("PopTrackedPopup %p\n", popup);
1024
1025 hr = PopFromArray(&type, NULL, &hmenu);
1026 if (FAILED_UNEXPECTEDLY(hr))
1027 {
1028 UpdateFocus();
1029 return hr;
1030 }
1031
1032 _ASSERT(type == TrackedMenuEntry);
1033 if (type != TrackedMenuEntry)
1034 return E_FAIL;
1035
1036 if (hmenu != popup)
1037 return E_FAIL;
1038
1039 hr = UpdateFocus();
1040 if (FAILED_UNEXPECTEDLY(hr))
1041 return hr;
1042
1043 return S_OK;
1044 }