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
;
322 if (entry
&& (entry
->type
== MenuBarEntry
|| m_current
->type
!= TrackedMenuEntry
))
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
);
488 if (m_current
->type
== TrackedMenuEntry
)
490 SendMessage(m_parent
->hwnd
, WM_CANCELMODE
, 0, 0);
491 msg
->message
= WM_NULL
;
500 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
502 msg
->message
= WM_NULL
;
506 callNext
= ProcessMouseMove(msg
);
508 case WM_INITMENUPOPUP
:
509 TRACE("WM_INITMENUPOPUP %p %p\n", msg
->wParam
, msg
->lParam
);
510 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
512 m_selectedItemFlags
= 0;
515 TRACE("WM_MENUSELECT %p %p\n", msg
->wParam
, msg
->lParam
);
516 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
517 m_selectedItem
= GET_X_LPARAM(msg
->wParam
);
518 m_selectedItemFlags
= HIWORD(msg
->wParam
);
524 if (m_current
->hmenu
== m_selectedMenu
)
526 m_parent
->mb
->_MenuItemHotTrack(VK_LEFT
);
530 if (m_selectedItem
< 0 || !(m_selectedItemFlags
& MF_POPUP
))
532 m_parent
->mb
->_MenuItemHotTrack(VK_RIGHT
);
543 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
546 LRESULT
CMenuFocusManager::GetMsgHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
548 BOOL isLButton
= FALSE
;
550 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
552 if (nCode
== HC_ACTION
)
554 BOOL callNext
= TRUE
;
555 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
558 switch (msg
->message
)
560 case WM_NCLBUTTONDOWN
:
565 case WM_NCRBUTTONDOWN
:
567 if (m_current
->type
== MenuPopupEntry
)
569 HWND child
= WindowFromPoint(pt
);
571 if (IsTrackedWindowOrParent(child
) != S_OK
)
574 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
581 ProcessMouseDown(msg
);
589 callNext
= ProcessMouseMove(msg
);
592 callNext
= ProcessMouseMove(msg
);
593 //callNext = ProcessMouseLeave(msg);
597 if (m_current
->type
== MenuPopupEntry
)
599 DisableMouseTrack(m_current
->hwnd
, TRUE
);
606 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
609 m_current
->mb
->_MenuItemHotTrack(MPOS_EXECUTE
);
612 m_current
->mb
->_MenuItemHotTrack(VK_LEFT
);
615 m_current
->mb
->_MenuItemHotTrack(VK_RIGHT
);
618 m_current
->mb
->_MenuItemHotTrack(VK_UP
);
621 m_current
->mb
->_MenuItemHotTrack(VK_DOWN
);
624 msg
->message
= WM_NULL
;
635 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
638 HRESULT
CMenuFocusManager::PlaceHooks()
640 if (m_current
->type
== TrackedMenuEntry
)
642 TRACE("Entering MSGFILTER hook...\n");
643 m_hMsgFilterHook
= SetWindowsHookEx(WH_MSGFILTER
, s_MsgFilterHook
, NULL
, m_threadId
);
647 TRACE("Entering GETMESSAGE hook...\n");
648 m_hGetMsgHook
= SetWindowsHookEx(WH_GETMESSAGE
, s_GetMsgHook
, NULL
, m_threadId
);
653 HRESULT
CMenuFocusManager::RemoveHooks()
655 TRACE("Removing all hooks...\n");
656 if (m_hMsgFilterHook
)
657 UnhookWindowsHookEx(m_hMsgFilterHook
);
659 UnhookWindowsHookEx(m_hGetMsgHook
);
660 m_hMsgFilterHook
= NULL
;
661 m_hGetMsgHook
= NULL
;
665 HRESULT
CMenuFocusManager::UpdateFocus()
668 StackEntry
* old
= m_current
;
674 m_current
= &(m_bandStack
[m_bandCount
- 1]);
678 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
680 hr
= m_current
->mb
->_GetTopLevelWindow(&(m_current
->hwnd
));
681 if (FAILED_UNEXPECTEDLY(hr
))
685 if (m_bandCount
>= 2)
687 m_parent
= &(m_bandStack
[m_bandCount
- 2]);
688 _ASSERT(m_parent
->type
!= TrackedMenuEntry
);
695 if (m_bandCount
>= 1 && m_bandStack
[0].type
== MenuBarEntry
)
697 m_menuBar
= &(m_bandStack
[0]);
704 if (old
&& (!m_current
|| old
->type
!= m_current
->type
))
706 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
708 DisableMouseTrack(m_current
->hwnd
, FALSE
);
712 if (FAILED_UNEXPECTEDLY(hr
))
716 if (m_current
&& (!old
|| old
->type
!= m_current
->type
))
719 if (FAILED_UNEXPECTEDLY(hr
))
725 DisableMouseTrack(m_parent
->hwnd
, TRUE
);
728 if ((m_current
&& m_current
->type
== MenuPopupEntry
) &&
729 (!m_parent
|| m_parent
->type
== MenuBarEntry
))
731 // When the mouse moves, it should set itself to the proper band
732 SetCapture(m_current
->hwnd
);
734 if (old
&& old
->type
== TrackedMenuEntry
)
736 // FIXME: Debugging code, probably not right
740 ScreenToClient(m_current
->hwnd
, &pt2
);
741 GetClientRect(m_current
->hwnd
, &rc2
);
742 if (PtInRect(&rc2
, pt2
))
743 SendMessage(m_current
->hwnd
, WM_MOUSEMOVE
, 0, MAKELPARAM(pt2
.x
, pt2
.y
));
745 SendMessage(m_current
->hwnd
, WM_MOUSELEAVE
, 0, 0);
749 _ASSERT(!m_parent
|| m_parent
->type
!= TrackedMenuEntry
);
754 HRESULT
CMenuFocusManager::PushMenuBar(CMenuBand
* mb
)
756 _ASSERT(m_bandCount
== 0);
758 HRESULT hr
= PushToArray(MenuBarEntry
, mb
, NULL
);
759 if (FAILED_UNEXPECTEDLY(hr
))
762 return UpdateFocus();
765 HRESULT
CMenuFocusManager::PushMenuPopup(CMenuBand
* mb
)
767 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
769 HRESULT hr
= PushToArray(MenuPopupEntry
, mb
, NULL
);
770 if (FAILED_UNEXPECTEDLY(hr
))
775 if (m_parent
&& m_parent
->type
!= TrackedMenuEntry
)
777 m_parent
->mb
->_SetChildBand(mb
);
778 mb
->_SetParentBand(m_parent
->mb
);
784 HRESULT
CMenuFocusManager::PushTrackedPopup(HMENU popup
)
786 _ASSERT(m_bandCount
> 0);
787 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
789 HRESULT hr
= PushToArray(TrackedMenuEntry
, NULL
, popup
);
790 if (FAILED_UNEXPECTEDLY(hr
))
793 TRACE("PushTrackedPopup %p\n", popup
);
794 m_selectedMenu
= popup
;
796 m_selectedItemFlags
= 0;
798 return UpdateFocus();
801 HRESULT
CMenuFocusManager::PopMenuBar(CMenuBand
* mb
)
807 hr
= PopFromArray(&type
, &mbc
, NULL
);
808 if (FAILED_UNEXPECTEDLY(hr
))
814 _ASSERT(type
== MenuBarEntry
);
815 if (type
!= MenuBarEntry
)
821 mbc
->_SetParentBand(NULL
);
824 if (FAILED_UNEXPECTEDLY(hr
))
829 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
830 m_current
->mb
->_SetChildBand(NULL
);
836 HRESULT
CMenuFocusManager::PopMenuPopup(CMenuBand
* mb
)
842 hr
= PopFromArray(&type
, &mbc
, NULL
);
843 if (FAILED_UNEXPECTEDLY(hr
))
849 _ASSERT(type
== MenuPopupEntry
);
850 if (type
!= MenuPopupEntry
)
856 mbc
->_SetParentBand(NULL
);
859 if (FAILED_UNEXPECTEDLY(hr
))
864 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
865 m_current
->mb
->_SetChildBand(NULL
);
871 HRESULT
CMenuFocusManager::PopTrackedPopup(HMENU popup
)
877 hr
= PopFromArray(&type
, NULL
, &hmenu
);
878 if (FAILED_UNEXPECTEDLY(hr
))
884 _ASSERT(type
== TrackedMenuEntry
);
885 if (type
!= TrackedMenuEntry
)
892 if (FAILED_UNEXPECTEDLY(hr
))