4 * Copyright 2014 David Quintana
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.
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.
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
22 #include <commoncontrols.h>
23 #include <shlwapi_undoc.h>
25 #include "CMenuFocusManager.h"
26 #include "CMenuToolbars.h"
27 #include "CMenuBand.h"
31 # define _ASSERT(x) DbgAssert(!!(x), __FILE__, __LINE__, #x)
33 bool DbgAssert(bool x
, const char * filename
, int line
, const char * expr
)
40 fname
= strrchr(filename
, '\\');
43 fname
= strrchr(filename
, '/');
51 sprintf(szMsg
, "%s:%d: Assertion failed: %s\n", fname
, line
, expr
);
53 OutputDebugStringA(szMsg
);
61 # define _ASSERT(x) (!!(x))
64 WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus
);
66 DWORD
CMenuFocusManager::TlsIndex
= 0;
68 CMenuFocusManager
* CMenuFocusManager::GetManager()
70 return reinterpret_cast<CMenuFocusManager
*>(TlsGetValue(TlsIndex
));
73 CMenuFocusManager
* CMenuFocusManager::AcquireManager()
75 CMenuFocusManager
* obj
= NULL
;
79 if ((TlsIndex
= TlsAlloc()) == TLS_OUT_OF_INDEXES
)
87 obj
= new CComObject
<CMenuFocusManager
>();
88 TlsSetValue(TlsIndex
, obj
);
96 void CMenuFocusManager::ReleaseManager(CMenuFocusManager
* obj
)
100 TlsSetValue(TlsIndex
, NULL
);
104 LRESULT CALLBACK
CMenuFocusManager::s_MsgFilterHook(INT nCode
, WPARAM wParam
, LPARAM lParam
)
106 return GetManager()->MsgFilterHook(nCode
, wParam
, lParam
);
109 LRESULT CALLBACK
CMenuFocusManager::s_GetMsgHook(INT nCode
, WPARAM wParam
, LPARAM lParam
)
111 return GetManager()->GetMsgHook(nCode
, wParam
, lParam
);
114 HRESULT
CMenuFocusManager::PushToArray(StackEntryType type
, CMenuBand
* mb
, HMENU hmenu
)
116 if (m_bandCount
>= MAX_RECURSE
)
117 return E_OUTOFMEMORY
;
119 m_bandStack
[m_bandCount
].type
= type
;
120 m_bandStack
[m_bandCount
].mb
= mb
;
121 m_bandStack
[m_bandCount
].hmenu
= hmenu
;
127 HRESULT
CMenuFocusManager::PopFromArray(StackEntryType
* pType
, CMenuBand
** pMb
, HMENU
* pHmenu
)
129 if (pType
) *pType
= NoEntry
;
130 if (pMb
) *pMb
= NULL
;
131 if (pHmenu
) *pHmenu
= NULL
;
133 if (m_bandCount
<= 0)
138 if (pType
) *pType
= m_bandStack
[m_bandCount
].type
;
139 if (*pType
== TrackedMenuEntry
)
141 if (pHmenu
) *pHmenu
= m_bandStack
[m_bandCount
].hmenu
;
145 if (pMb
) *pMb
= m_bandStack
[m_bandCount
].mb
;
151 CMenuFocusManager::CMenuFocusManager() :
154 m_hMsgFilterHook(NULL
),
156 m_mouseTrackDisabled(FALSE
),
158 m_hwndUnderMouse(NULL
),
159 m_entryUnderMouse(NULL
),
160 m_selectedMenu(NULL
),
162 m_selectedItemFlags(0),
163 m_isLButtonDown(FALSE
),
164 m_movedSinceDown(FALSE
),
165 m_windowAtDown(NULL
),
170 m_threadId
= GetCurrentThreadId();
173 CMenuFocusManager::~CMenuFocusManager()
177 void CMenuFocusManager::DisableMouseTrack(HWND parent
, BOOL disableThis
)
179 BOOL bDisable
= FALSE
;
180 BOOL lastDisable
= FALSE
;
185 StackEntry
& entry
= m_bandStack
[i
];
187 if (entry
.type
!= TrackedMenuEntry
)
190 HRESULT hr
= entry
.mb
->_GetTopLevelWindow(&hwnd
);
191 if (FAILED_UNEXPECTEDLY(hr
))
196 lastDisable
= disableThis
;
197 entry
.mb
->_DisableMouseTrack(disableThis
);
202 lastDisable
= bDisable
;
203 entry
.mb
->_DisableMouseTrack(bDisable
);
207 m_mouseTrackDisabled
= lastDisable
;
210 void CMenuFocusManager::SetCapture(HWND child
)
212 if (m_captureHwnd
!= child
)
217 m_captureHwnd
= child
;
218 TRACE("MouseTrack is now capturing %p\n", child
);
223 m_captureHwnd
= NULL
;
224 TRACE("MouseTrack is now off\n");
230 HRESULT
CMenuFocusManager::IsTrackedWindow(HWND hWnd
, StackEntry
** pentry
)
235 for (int i
= m_bandCount
; --i
>= 0;)
237 StackEntry
& entry
= m_bandStack
[i
];
239 if (entry
.type
!= TrackedMenuEntry
)
241 HRESULT hr
= entry
.mb
->IsWindowOwner(hWnd
);
242 if (FAILED_UNEXPECTEDLY(hr
))
256 HRESULT
CMenuFocusManager::IsTrackedWindowOrParent(HWND hWnd
)
258 for (int i
= m_bandCount
; --i
>= 0;)
260 StackEntry
& entry
= m_bandStack
[i
];
262 if (entry
.type
!= TrackedMenuEntry
)
264 HRESULT hr
= entry
.mb
->IsWindowOwner(hWnd
);
265 if (FAILED_UNEXPECTEDLY(hr
))
269 if (entry
.mb
->_IsPopup() == S_OK
)
271 CComPtr
<IUnknown
> site
;
272 CComPtr
<IOleWindow
> pw
;
273 hr
= entry
.mb
->GetSite(IID_PPV_ARG(IUnknown
, &site
));
274 if (FAILED_UNEXPECTEDLY(hr
))
276 hr
= IUnknown_QueryService(site
, SID_SMenuBandParent
, IID_PPV_ARG(IOleWindow
, &pw
));
277 if (FAILED_UNEXPECTEDLY(hr
))
281 if (pw
->GetWindow(&hParent
) == S_OK
&& hParent
== hWnd
)
290 LRESULT
CMenuFocusManager::ProcessMouseMove(MSG
* msg
)
293 int iHitTestResult
= -1;
295 POINT pt2
= { GET_X_LPARAM(msg
->lParam
), GET_Y_LPARAM(msg
->lParam
) };
296 ClientToScreen(msg
->hwnd
, &pt2
);
298 // Don't do anything if the mouse has not been moved
300 if (pt
.x
== m_ptPrev
.x
&& pt
.y
== m_ptPrev
.y
)
303 // Don't do anything if another window is capturing the mouse.
304 HWND cCapture
= ::GetCapture();
305 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
309 m_movedSinceDown
= TRUE
;
313 child
= WindowFromPoint(pt
);
315 StackEntry
* entry
= NULL
;
316 if (IsTrackedWindow(child
, &entry
) == S_OK
)
318 TRACE("MouseMove %d\n", m_isLButtonDown
);
321 BOOL isTracking
= FALSE
;
324 ScreenToClient(child
, &pt
);
325 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
326 isTracking
= entry
->mb
->_IsTracking();
328 if (SendMessage(child
, WM_USER_ISTRACKEDITEM
, iHitTestResult
, 0) == S_FALSE
)
330 TRACE("Hot item tracking detected a change (capture=%p / cCapture=%p)...\n", m_captureHwnd
, cCapture
);
331 DisableMouseTrack(NULL
, FALSE
);
332 if (isTracking
&& iHitTestResult
>= 0 && m_current
->type
== TrackedMenuEntry
)
333 SendMessage(entry
->hwnd
, WM_CANCELMODE
, 0, 0);
334 PostMessage(child
, WM_USER_CHANGETRACKEDITEM
, iHitTestResult
, MAKELPARAM(isTracking
, TRUE
));
335 if (m_current
->type
== TrackedMenuEntry
)
340 if (m_entryUnderMouse
!= entry
)
342 // Mouse moved away from a tracked window
343 if (m_entryUnderMouse
)
345 m_entryUnderMouse
->mb
->_ChangeHotItem(NULL
, -1, HICF_MOUSE
);
347 if (cCapture
== m_captureHwnd
)
351 if (m_hwndUnderMouse
!= child
)
355 // Mouse moved to a tracked window
356 if (m_current
->type
== MenuPopupEntry
)
358 ScreenToClient(child
, &pt2
);
359 SendMessage(child
, WM_MOUSEMOVE
, msg
->wParam
, MAKELPARAM(pt2
.x
, pt2
.y
));
363 m_hwndUnderMouse
= child
;
364 m_entryUnderMouse
= entry
;
367 if (m_current
->type
== MenuPopupEntry
)
369 HWND parent
= GetAncestor(child
, GA_ROOT
);
370 DisableMouseTrack(parent
, FALSE
);
376 LRESULT
CMenuFocusManager::ProcessMouseDown(MSG
* msg
)
379 int iHitTestResult
= -1;
381 // Don't do anything if another window is capturing the mouse.
382 HWND cCapture
= ::GetCapture();
383 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
389 child
= WindowFromPoint(pt
);
391 StackEntry
* entry
= NULL
;
392 if (IsTrackedWindow(child
, &entry
) != S_OK
)
395 TRACE("MouseDown %d\n", m_isLButtonDown
);
397 BOOL isTracking
= FALSE
;
400 ScreenToClient(child
, &pt
);
401 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
402 isTracking
= entry
->mb
->_IsTracking();
404 if (iHitTestResult
>= 0)
406 TRACE("MouseDown send %d\n", iHitTestResult
);
407 entry
->mb
->_MenuBarMouseDown(child
, iHitTestResult
);
411 msg
->message
= WM_NULL
;
413 m_isLButtonDown
= TRUE
;
414 m_movedSinceDown
= FALSE
;
415 m_windowAtDown
= child
;
417 TRACE("MouseDown end %d\n", m_isLButtonDown
);
422 LRESULT
CMenuFocusManager::ProcessMouseUp(MSG
* msg
)
425 int iHitTestResult
= -1;
427 // Don't do anything if another window is capturing the mouse.
428 HWND cCapture
= ::GetCapture();
429 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
432 if (!m_isLButtonDown
)
435 m_isLButtonDown
= FALSE
;
439 child
= WindowFromPoint(pt
);
441 StackEntry
* entry
= NULL
;
442 if (IsTrackedWindow(child
, &entry
) != S_OK
)
445 TRACE("MouseUp %d\n", m_isLButtonDown
);
447 BOOL isTracking
= FALSE
;
450 ScreenToClient(child
, &pt
);
451 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
452 isTracking
= entry
->mb
->_IsTracking();
454 if (iHitTestResult
>= 0)
456 TRACE("MouseUp send %d\n", iHitTestResult
);
457 entry
->mb
->_MenuBarMouseUp(child
, iHitTestResult
);
464 LRESULT
CMenuFocusManager::MsgFilterHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
467 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
469 if (nCode
== MSGF_MENU
)
471 BOOL callNext
= TRUE
;
472 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
474 switch (msg
->message
)
476 case WM_NCLBUTTONDOWN
:
478 case WM_NCRBUTTONDOWN
:
483 HWND child
= WindowFromPoint(pt
);
484 BOOL hoveringMenuBar
= m_menuBar
->mb
->IsWindowOwner(child
) == S_OK
;
487 m_menuBar
->mb
->_DisableMouseTrack(TRUE
);
492 callNext
= ProcessMouseMove(msg
);
494 case WM_INITMENUPOPUP
:
495 TRACE("WM_INITMENUPOPUP %p %p\n", msg
->wParam
, msg
->lParam
);
496 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
498 m_selectedItemFlags
= 0;
501 TRACE("WM_MENUSELECT %p %p\n", msg
->wParam
, msg
->lParam
);
502 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
503 m_selectedItem
= GET_X_LPARAM(msg
->wParam
);
504 m_selectedItemFlags
= HIWORD(msg
->wParam
);
510 if (m_current
->hmenu
== m_selectedMenu
)
512 m_parent
->mb
->_MenuItemHotTrack(VK_LEFT
);
516 if (m_selectedItem
< 0 || !(m_selectedItemFlags
& MF_POPUP
))
518 m_parent
->mb
->_MenuItemHotTrack(VK_RIGHT
);
529 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
532 LRESULT
CMenuFocusManager::GetMsgHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
534 BOOL isLButton
= FALSE
;
536 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
538 if (nCode
== HC_ACTION
)
540 BOOL callNext
= TRUE
;
541 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
544 switch (msg
->message
)
546 case WM_NCLBUTTONDOWN
:
551 case WM_NCRBUTTONDOWN
:
553 if (m_current
->type
== MenuPopupEntry
)
555 HWND child
= WindowFromPoint(pt
);
557 if (IsTrackedWindowOrParent(child
) != S_OK
)
560 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
567 ProcessMouseDown(msg
);
575 callNext
= ProcessMouseMove(msg
);
578 callNext
= ProcessMouseMove(msg
);
579 //callNext = ProcessMouseLeave(msg);
583 if (m_current
->type
== MenuPopupEntry
)
585 DisableMouseTrack(m_current
->hwnd
, TRUE
);
592 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
595 m_current
->mb
->_MenuItemHotTrack(MPOS_EXECUTE
);
598 m_current
->mb
->_MenuItemHotTrack(VK_LEFT
);
601 m_current
->mb
->_MenuItemHotTrack(VK_RIGHT
);
604 m_current
->mb
->_MenuItemHotTrack(VK_UP
);
607 m_current
->mb
->_MenuItemHotTrack(VK_DOWN
);
610 msg
->message
= WM_NULL
;
621 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
624 HRESULT
CMenuFocusManager::PlaceHooks()
626 if (m_current
->type
== TrackedMenuEntry
)
628 TRACE("Entering MSGFILTER hook...\n");
629 m_hMsgFilterHook
= SetWindowsHookEx(WH_MSGFILTER
, s_MsgFilterHook
, NULL
, m_threadId
);
633 TRACE("Entering GETMESSAGE hook...\n");
634 m_hGetMsgHook
= SetWindowsHookEx(WH_GETMESSAGE
, s_GetMsgHook
, NULL
, m_threadId
);
639 HRESULT
CMenuFocusManager::RemoveHooks()
641 TRACE("Removing all hooks...\n");
642 if (m_hMsgFilterHook
)
643 UnhookWindowsHookEx(m_hMsgFilterHook
);
645 UnhookWindowsHookEx(m_hGetMsgHook
);
646 m_hMsgFilterHook
= NULL
;
647 m_hGetMsgHook
= NULL
;
651 HRESULT
CMenuFocusManager::UpdateFocus()
654 StackEntry
* old
= m_current
;
660 m_current
= &(m_bandStack
[m_bandCount
- 1]);
664 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
666 hr
= m_current
->mb
->_GetTopLevelWindow(&(m_current
->hwnd
));
667 if (FAILED_UNEXPECTEDLY(hr
))
671 if (m_bandCount
>= 2)
673 m_parent
= &(m_bandStack
[m_bandCount
- 2]);
674 _ASSERT(m_parent
->type
!= TrackedMenuEntry
);
681 if (m_bandCount
>= 1 && m_bandStack
[0].type
== MenuBarEntry
)
683 m_menuBar
= &(m_bandStack
[0]);
690 if (old
&& (!m_current
|| old
->type
!= m_current
->type
))
692 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
694 DisableMouseTrack(m_current
->hwnd
, FALSE
);
698 if (FAILED_UNEXPECTEDLY(hr
))
702 if (m_current
&& (!old
|| old
->type
!= m_current
->type
))
705 if (FAILED_UNEXPECTEDLY(hr
))
711 DisableMouseTrack(m_parent
->hwnd
, TRUE
);
714 if ((m_current
&& m_current
->type
== MenuPopupEntry
) &&
715 (!m_parent
|| m_parent
->type
== MenuBarEntry
))
717 // When the mouse moves, it should set itself to the proper band
718 SetCapture(m_current
->hwnd
);
720 if (old
&& old
->type
== TrackedMenuEntry
)
722 // FIXME: Debugging code, probably not right
726 ScreenToClient(m_current
->hwnd
, &pt2
);
727 GetClientRect(m_current
->hwnd
, &rc2
);
728 if (PtInRect(&rc2
, pt2
))
729 SendMessage(m_current
->hwnd
, WM_MOUSEMOVE
, 0, MAKELPARAM(pt2
.x
, pt2
.y
));
731 SendMessage(m_current
->hwnd
, WM_MOUSELEAVE
, 0, 0);
735 _ASSERT(!m_parent
|| m_parent
->type
!= TrackedMenuEntry
);
740 HRESULT
CMenuFocusManager::PushMenuBar(CMenuBand
* mb
)
742 _ASSERT(m_bandCount
== 0);
744 HRESULT hr
= PushToArray(MenuBarEntry
, mb
, NULL
);
745 if (FAILED_UNEXPECTEDLY(hr
))
748 return UpdateFocus();
751 HRESULT
CMenuFocusManager::PushMenuPopup(CMenuBand
* mb
)
753 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
755 HRESULT hr
= PushToArray(MenuPopupEntry
, mb
, NULL
);
756 if (FAILED_UNEXPECTEDLY(hr
))
761 if (m_parent
&& m_parent
->type
!= TrackedMenuEntry
)
763 m_parent
->mb
->_SetChildBand(mb
);
764 mb
->_SetParentBand(m_parent
->mb
);
770 HRESULT
CMenuFocusManager::PushTrackedPopup(HMENU popup
)
772 _ASSERT(m_bandCount
> 0);
773 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
775 HRESULT hr
= PushToArray(TrackedMenuEntry
, NULL
, popup
);
776 if (FAILED_UNEXPECTEDLY(hr
))
779 TRACE("PushTrackedPopup %p\n", popup
);
780 m_selectedMenu
= popup
;
782 m_selectedItemFlags
= 0;
784 return UpdateFocus();
787 HRESULT
CMenuFocusManager::PopMenuBar(CMenuBand
* mb
)
793 hr
= PopFromArray(&type
, &mbc
, NULL
);
794 if (FAILED_UNEXPECTEDLY(hr
))
800 _ASSERT(type
== MenuBarEntry
);
801 if (type
!= MenuBarEntry
)
807 mbc
->_SetParentBand(NULL
);
810 if (FAILED_UNEXPECTEDLY(hr
))
815 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
816 m_current
->mb
->_SetChildBand(NULL
);
822 HRESULT
CMenuFocusManager::PopMenuPopup(CMenuBand
* mb
)
828 hr
= PopFromArray(&type
, &mbc
, NULL
);
829 if (FAILED_UNEXPECTEDLY(hr
))
835 _ASSERT(type
== MenuPopupEntry
);
836 if (type
!= MenuPopupEntry
)
842 mbc
->_SetParentBand(NULL
);
845 if (FAILED_UNEXPECTEDLY(hr
))
850 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
851 m_current
->mb
->_SetChildBand(NULL
);
857 HRESULT
CMenuFocusManager::PopTrackedPopup(HMENU popup
)
863 hr
= PopFromArray(&type
, NULL
, &hmenu
);
864 if (FAILED_UNEXPECTEDLY(hr
))
870 _ASSERT(type
== TrackedMenuEntry
);
871 if (type
!= TrackedMenuEntry
)
878 if (FAILED_UNEXPECTEDLY(hr
))