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
DbgAssert(bool x,const char * filename,int line,const char * expr)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
GetManager()91 CMenuFocusManager * CMenuFocusManager::GetManager()
92 {
93 return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
94 }
95
96 // Obtains a manager for the thread, with refcounting
AcquireManager()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
ReleaseManager(CMenuFocusManager * obj)121 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
122 {
123 if (!obj->Release())
124 {
125 TlsSetValue(TlsIndex, NULL);
126 }
127 }
128
s_MsgFilterHook(INT nCode,WPARAM wParam,LPARAM lParam)129 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
130 {
131 return GetManager()->MsgFilterHook(nCode, wParam, lParam);
132 }
133
s_GetMsgHook(INT nCode,WPARAM wParam,LPARAM lParam)134 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
135 {
136 return GetManager()->GetMsgHook(nCode, wParam, lParam);
137 }
138
PushToArray(StackEntryType type,CMenuBand * mb,HMENU hmenu)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
PopFromArray(StackEntryType * pType,CMenuBand ** pMb,HMENU * pHmenu)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
CMenuFocusManager()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
~CMenuFocusManager()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
DisableMouseTrack(HWND parent,BOOL disableThis)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
SetMenuCapture(HWND child)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
IsTrackedWindow(HWND hWnd,StackEntry ** pentry)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
IsTrackedWindowOrParent(HWND hWnd)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
ProcessMouseMove(MSG * msg)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\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 (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
ProcessMouseDown(MSG * msg,BOOL isLButton)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
ProcessMouseUp(MSG * msg,BOOL isLButton)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
MsgFilterHook(INT nCode,WPARAM hookWParam,LPARAM hookLParam)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
GetMsgHook(INT nCode,WPARAM hookWParam,LPARAM hookLParam)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_LBUTTONDBLCLK:
581 case WM_LBUTTONDOWN:
582 isLButton = TRUE;
583 TRACE("LB\n");
584
585 if (m_menuBar && m_current->type == MenuPopupEntry)
586 {
587 POINT pt = msg->pt;
588 HWND child = WindowFromPoint(pt);
589 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
590 if (hoveringMenuBar)
591 {
592 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
593 break;
594 }
595 }
596
597 if (m_current->type == MenuPopupEntry)
598 {
599 HWND child = WindowFromPoint(pt);
600
601 if (IsTrackedWindowOrParent(child) != S_OK)
602 {
603 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
604 break;
605 }
606 }
607
608 ProcessMouseDown(msg, isLButton);
609
610 break;
611 case WM_NCRBUTTONUP:
612 case WM_RBUTTONUP:
613 ProcessMouseUp(msg, isLButton);
614 break;
615 case WM_NCLBUTTONUP:
616 case WM_LBUTTONUP:
617 isLButton = TRUE;
618 ProcessMouseUp(msg, isLButton);
619 break;
620 case WM_MOUSEMOVE:
621 callNext = ProcessMouseMove(msg);
622 break;
623 case WM_MOUSELEAVE:
624 callNext = ProcessMouseMove(msg);
625 //callNext = ProcessMouseLeave(msg);
626 break;
627 case WM_SYSKEYDOWN:
628 case WM_KEYDOWN:
629 if (m_current->type == MenuPopupEntry)
630 {
631 DisableMouseTrack(m_current->hwnd, TRUE);
632 switch (msg->wParam)
633 {
634 case VK_ESCAPE:
635 case VK_MENU:
636 case VK_LMENU:
637 case VK_RMENU:
638 m_current->mb->_MenuItemSelect(MPOS_FULLCANCEL);
639 break;
640 case VK_RETURN:
641 m_current->mb->_MenuItemSelect(MPOS_EXECUTE);
642 break;
643 case VK_LEFT:
644 m_current->mb->_MenuItemSelect(VK_LEFT);
645 break;
646 case VK_RIGHT:
647 m_current->mb->_MenuItemSelect(VK_RIGHT);
648 break;
649 case VK_UP:
650 m_current->mb->_MenuItemSelect(VK_UP);
651 break;
652 case VK_DOWN:
653 m_current->mb->_MenuItemSelect(VK_DOWN);
654 break;
655 }
656 msg->message = WM_NULL;
657 msg->lParam = 0;
658 msg->wParam = 0;
659 }
660 break;
661 }
662
663 if (!callNext)
664 return 1;
665 }
666
667 return CallNextHookEx(m_hGetMsgHook, nCode, hookWParam, hookLParam);
668 }
669
PlaceHooks()670 HRESULT CMenuFocusManager::PlaceHooks()
671 {
672 if (m_hGetMsgHook)
673 {
674 WARN("GETMESSAGE hook already placed!\n");
675 return S_OK;
676 }
677 if (m_hMsgFilterHook)
678 {
679 WARN("MSGFILTER hook already placed!\n");
680 return S_OK;
681 }
682 if (m_current->type == TrackedMenuEntry)
683 {
684 TRACE("Entering MSGFILTER hook...\n");
685 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
686 }
687 else
688 {
689 TRACE("Entering GETMESSAGE hook...\n");
690 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
691 }
692 return S_OK;
693 }
694
RemoveHooks()695 HRESULT CMenuFocusManager::RemoveHooks()
696 {
697 if (m_hMsgFilterHook)
698 {
699 TRACE("Removing MSGFILTER hook...\n");
700 UnhookWindowsHookEx(m_hMsgFilterHook);
701 m_hMsgFilterHook = NULL;
702 }
703 if (m_hGetMsgHook)
704 {
705 TRACE("Removing GETMESSAGE hook...\n");
706 UnhookWindowsHookEx(m_hGetMsgHook);
707 m_hGetMsgHook = NULL;
708 }
709 return S_OK;
710 }
711
712 // Used to update the tracking info to account for a change in the top-level menu
UpdateFocus()713 HRESULT CMenuFocusManager::UpdateFocus()
714 {
715 HRESULT hr;
716 StackEntry * old = m_current;
717
718 TRACE("UpdateFocus\n");
719
720 // Assign the new current item
721 if (m_bandCount > 0)
722 m_current = &(m_bandStack[m_bandCount - 1]);
723 else
724 m_current = NULL;
725
726 // Remove the menu capture if necesary
727 if (!m_current || m_current->type != MenuPopupEntry)
728 {
729 SetMenuCapture(NULL);
730 if (old && old->type == MenuPopupEntry && m_PreviousForeground)
731 {
732 ::SetForegroundWindow(m_PreviousForeground);
733 m_PreviousForeground = NULL;
734 }
735 }
736
737 // Obtain the top-level window for the new active menu
738 if (m_current && m_current->type != TrackedMenuEntry)
739 {
740 hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
741 if (FAILED_UNEXPECTEDLY(hr))
742 return hr;
743 }
744
745 // Refresh the parent pointer
746 if (m_bandCount >= 2)
747 {
748 m_parent = &(m_bandStack[m_bandCount - 2]);
749 _ASSERT(m_parent->type != TrackedMenuEntry);
750 }
751 else
752 {
753 m_parent = NULL;
754 }
755
756 // Refresh the menubar pointer, if applicable
757 if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
758 {
759 m_menuBar = &(m_bandStack[0]);
760 }
761 else
762 {
763 m_menuBar = NULL;
764 }
765
766 // Remove the old hooks if the menu type changed, or we don't have a menu anymore
767 if (old && (!m_current || old->type != m_current->type))
768 {
769 if (m_current && m_current->type != TrackedMenuEntry)
770 {
771 DisableMouseTrack(m_current->hwnd, FALSE);
772 }
773
774 hr = RemoveHooks();
775 if (FAILED_UNEXPECTEDLY(hr))
776 return hr;
777 }
778
779 // And place new ones if necessary
780 if (m_current && (!old || old->type != m_current->type))
781 {
782 hr = PlaceHooks();
783 if (FAILED_UNEXPECTEDLY(hr))
784 return hr;
785 }
786
787 // Give the user a chance to move the mouse to the new menu
788 if (m_parent)
789 {
790 DisableMouseTrack(m_parent->hwnd, TRUE);
791 }
792
793 if (m_current && m_current->type == MenuPopupEntry)
794 {
795 if (m_captureHwnd == NULL)
796 {
797 // We need to restore the capture after a non-shell submenu or context menu is shown
798 StackEntry * topMenu = m_bandStack;
799 if (topMenu->type == MenuBarEntry)
800 topMenu++;
801
802 // Get the top-level window from the top popup
803 CComPtr<IServiceProvider> bandSite;
804 CComPtr<IOleWindow> deskBar;
805 hr = topMenu->mb->GetSite(IID_PPV_ARG(IServiceProvider, &bandSite));
806 if (FAILED(hr))
807 goto NoCapture;
808 hr = bandSite->QueryService(SID_SMenuPopup, IID_PPV_ARG(IOleWindow, &deskBar));
809 if (FAILED(hr))
810 goto NoCapture;
811
812 CComPtr<IOleWindow> deskBarSite;
813 hr = IUnknown_GetSite(deskBar, IID_PPV_ARG(IOleWindow, &deskBarSite));
814 if (FAILED(hr))
815 goto NoCapture;
816
817 // FIXME: Find the correct place for this
818 HWND hWndOwner;
819 hr = deskBarSite->GetWindow(&hWndOwner);
820 if (FAILED(hr))
821 goto NoCapture;
822
823 m_PreviousForeground = ::GetForegroundWindow();
824 if (m_PreviousForeground != hWndOwner)
825 ::SetForegroundWindow(hWndOwner);
826 else
827 m_PreviousForeground = NULL;
828
829 // Get the HWND of the top-level window
830 HWND hWndSite;
831 hr = deskBar->GetWindow(&hWndSite);
832 if (FAILED(hr))
833 goto NoCapture;
834 SetMenuCapture(hWndSite);
835 }
836 NoCapture:
837
838 if (!m_parent || m_parent->type == MenuBarEntry)
839 {
840 if (old && old->type == TrackedMenuEntry)
841 {
842 // FIXME: Debugging code, probably not right
843 POINT pt2;
844 RECT rc2;
845 GetCursorPos(&pt2);
846 ScreenToClient(m_current->hwnd, &pt2);
847 GetClientRect(m_current->hwnd, &rc2);
848 if (PtInRect(&rc2, pt2))
849 SendMessage(m_current->hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt2.x, pt2.y));
850 else
851 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
852 }
853 }
854 }
855
856 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
857
858 return S_OK;
859 }
860
861 // Begin tracking top-level menu bar (for file browser windows)
PushMenuBar(CMenuBand * mb)862 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
863 {
864 TRACE("PushMenuBar %p\n", mb);
865
866 mb->AddRef();
867
868 _ASSERT(m_bandCount == 0);
869
870 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
871 if (FAILED_UNEXPECTEDLY(hr))
872 return hr;
873
874 return UpdateFocus();
875 }
876
877 // Begin tracking a shell menu popup (start menu or submenus)
PushMenuPopup(CMenuBand * mb)878 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
879 {
880 TRACE("PushTrackedPopup %p\n", mb);
881
882 mb->AddRef();
883
884 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
885
886 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
887 if (FAILED_UNEXPECTEDLY(hr))
888 return hr;
889
890 hr = UpdateFocus();
891
892 m_menuDepth++;
893
894 if (m_parent && m_parent->type != TrackedMenuEntry)
895 {
896 m_parent->mb->_SetChildBand(mb);
897 mb->_SetParentBand(m_parent->mb);
898 }
899
900 return hr;
901 }
902
903 // Begin tracking a system popup submenu (submenu of the file browser windows)
PushTrackedPopup(HMENU popup)904 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
905 {
906 TRACE("PushTrackedPopup %p\n", popup);
907
908 _ASSERT(m_bandCount > 0);
909 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
910
911 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
912 if (FAILED_UNEXPECTEDLY(hr))
913 return hr;
914
915 TRACE("PushTrackedPopup %p\n", popup);
916 m_selectedMenu = popup;
917 m_selectedItem = -1;
918 m_selectedItemFlags = 0;
919
920 return UpdateFocus();
921 }
922
923 // Stop tracking the menubar
PopMenuBar(CMenuBand * mb)924 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
925 {
926 StackEntryType type;
927 CMenuBand * mbc;
928 HRESULT hr;
929
930 TRACE("PopMenuBar %p\n", mb);
931
932 if (m_current == m_entryUnderMouse)
933 {
934 m_entryUnderMouse = NULL;
935 }
936
937 hr = PopFromArray(&type, &mbc, NULL);
938 if (FAILED_UNEXPECTEDLY(hr))
939 {
940 UpdateFocus();
941 return hr;
942 }
943
944 _ASSERT(type == MenuBarEntry);
945 if (type != MenuBarEntry)
946 return E_FAIL;
947
948 if (!mbc)
949 return E_FAIL;
950
951 mbc->_SetParentBand(NULL);
952
953 mbc->Release();
954
955 hr = UpdateFocus();
956 if (FAILED_UNEXPECTEDLY(hr))
957 return hr;
958
959 if (m_current)
960 {
961 _ASSERT(m_current->type != TrackedMenuEntry);
962 m_current->mb->_SetChildBand(NULL);
963 }
964
965 return S_OK;
966 }
967
968 // Stop tracking a shell menu
PopMenuPopup(CMenuBand * mb)969 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
970 {
971 StackEntryType type;
972 CMenuBand * mbc;
973 HRESULT hr;
974
975 TRACE("PopMenuPopup %p\n", mb);
976
977 if (m_current == m_entryUnderMouse)
978 {
979 m_entryUnderMouse = NULL;
980 }
981
982 m_menuDepth--;
983
984 hr = PopFromArray(&type, &mbc, NULL);
985 if (FAILED_UNEXPECTEDLY(hr))
986 {
987 UpdateFocus();
988 return hr;
989 }
990
991 _ASSERT(type == MenuPopupEntry);
992 if (type != MenuPopupEntry)
993 return E_FAIL;
994
995 if (!mbc)
996 return E_FAIL;
997
998 mbc->_SetParentBand(NULL);
999
1000 mbc->Release();
1001
1002 hr = UpdateFocus();
1003 if (FAILED_UNEXPECTEDLY(hr))
1004 return hr;
1005
1006 if (m_current)
1007 {
1008 _ASSERT(m_current->type != TrackedMenuEntry);
1009 m_current->mb->_SetChildBand(NULL);
1010 }
1011
1012 return S_OK;
1013 }
1014
1015 // Stop tracking a system popup submenu
PopTrackedPopup(HMENU popup)1016 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
1017 {
1018 StackEntryType type;
1019 HMENU hmenu;
1020 HRESULT hr;
1021
1022 TRACE("PopTrackedPopup %p\n", popup);
1023
1024 hr = PopFromArray(&type, NULL, &hmenu);
1025 if (FAILED_UNEXPECTEDLY(hr))
1026 {
1027 UpdateFocus();
1028 return hr;
1029 }
1030
1031 _ASSERT(type == TrackedMenuEntry);
1032 if (type != TrackedMenuEntry)
1033 return E_FAIL;
1034
1035 if (hmenu != popup)
1036 return E_FAIL;
1037
1038 hr = UpdateFocus();
1039 if (FAILED_UNEXPECTEDLY(hr))
1040 return hr;
1041
1042 return S_OK;
1043 }
1044