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 TRACE("ProcessMouseDown %d %d %d\n", msg
->message
, msg
->wParam
, msg
->lParam
);
383 // Don't do anything if another window is capturing the mouse.
384 HWND cCapture
= ::GetCapture();
385 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
391 child
= WindowFromPoint(pt
);
393 StackEntry
* entry
= NULL
;
394 if (IsTrackedWindow(child
, &entry
) != S_OK
)
397 TRACE("MouseDown %d\n", m_isLButtonDown
);
401 ScreenToClient(child
, &pt
);
402 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
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 TRACE("ProcessMouseUp %d %d %d\n", msg
->message
, msg
->wParam
, msg
->lParam
);
429 // Don't do anything if another window is capturing the mouse.
430 HWND cCapture
= ::GetCapture();
431 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
434 if (!m_isLButtonDown
)
437 m_isLButtonDown
= FALSE
;
441 child
= WindowFromPoint(pt
);
443 StackEntry
* entry
= NULL
;
444 if (IsTrackedWindow(child
, &entry
) != S_OK
)
447 TRACE("MouseUp %d\n", m_isLButtonDown
);
451 ScreenToClient(child
, &pt
);
452 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
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_hMsgFilterHook
)
642 WARN("GETMESSAGE hook already placed!\n");
645 if (m_hMsgFilterHook
)
647 WARN("MSGFILTER hook already placed!\n");
650 if (m_current
->type
== TrackedMenuEntry
)
652 TRACE("Entering MSGFILTER hook...\n");
653 m_hMsgFilterHook
= SetWindowsHookEx(WH_MSGFILTER
, s_MsgFilterHook
, NULL
, m_threadId
);
657 TRACE("Entering GETMESSAGE hook...\n");
658 m_hGetMsgHook
= SetWindowsHookEx(WH_GETMESSAGE
, s_GetMsgHook
, NULL
, m_threadId
);
663 HRESULT
CMenuFocusManager::RemoveHooks()
665 TRACE("Removing all hooks...\n");
666 if (m_hMsgFilterHook
)
667 UnhookWindowsHookEx(m_hMsgFilterHook
);
669 UnhookWindowsHookEx(m_hGetMsgHook
);
670 m_hMsgFilterHook
= NULL
;
671 m_hGetMsgHook
= NULL
;
675 HRESULT
CMenuFocusManager::UpdateFocus()
678 StackEntry
* old
= m_current
;
680 TRACE("UpdateFocus\n");
686 m_current
= &(m_bandStack
[m_bandCount
- 1]);
690 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
692 hr
= m_current
->mb
->_GetTopLevelWindow(&(m_current
->hwnd
));
693 if (FAILED_UNEXPECTEDLY(hr
))
697 if (m_bandCount
>= 2)
699 m_parent
= &(m_bandStack
[m_bandCount
- 2]);
700 _ASSERT(m_parent
->type
!= TrackedMenuEntry
);
707 if (m_bandCount
>= 1 && m_bandStack
[0].type
== MenuBarEntry
)
709 m_menuBar
= &(m_bandStack
[0]);
716 if (old
&& (!m_current
|| old
->type
!= m_current
->type
))
718 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
720 DisableMouseTrack(m_current
->hwnd
, FALSE
);
724 if (FAILED_UNEXPECTEDLY(hr
))
728 if (m_current
&& (!old
|| old
->type
!= m_current
->type
))
731 if (FAILED_UNEXPECTEDLY(hr
))
737 DisableMouseTrack(m_parent
->hwnd
, TRUE
);
740 if ((m_current
&& m_current
->type
== MenuPopupEntry
) &&
741 (!m_parent
|| m_parent
->type
== MenuBarEntry
))
743 // When the mouse moves, it should set itself to the proper band
744 SetCapture(m_current
->hwnd
);
746 if (old
&& old
->type
== TrackedMenuEntry
)
748 // FIXME: Debugging code, probably not right
752 ScreenToClient(m_current
->hwnd
, &pt2
);
753 GetClientRect(m_current
->hwnd
, &rc2
);
754 if (PtInRect(&rc2
, pt2
))
755 SendMessage(m_current
->hwnd
, WM_MOUSEMOVE
, 0, MAKELPARAM(pt2
.x
, pt2
.y
));
757 SendMessage(m_current
->hwnd
, WM_MOUSELEAVE
, 0, 0);
761 _ASSERT(!m_parent
|| m_parent
->type
!= TrackedMenuEntry
);
766 HRESULT
CMenuFocusManager::PushMenuBar(CMenuBand
* mb
)
768 DbgPrint("PushMenuBar %p\n", mb
);
772 _ASSERT(m_bandCount
== 0);
774 HRESULT hr
= PushToArray(MenuBarEntry
, mb
, NULL
);
775 if (FAILED_UNEXPECTEDLY(hr
))
778 return UpdateFocus();
781 HRESULT
CMenuFocusManager::PushMenuPopup(CMenuBand
* mb
)
783 DbgPrint("PushTrackedPopup %p\n", mb
);
787 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
789 HRESULT hr
= PushToArray(MenuPopupEntry
, mb
, NULL
);
790 if (FAILED_UNEXPECTEDLY(hr
))
795 if (m_parent
&& m_parent
->type
!= TrackedMenuEntry
)
797 m_parent
->mb
->_SetChildBand(mb
);
798 mb
->_SetParentBand(m_parent
->mb
);
804 HRESULT
CMenuFocusManager::PushTrackedPopup(HMENU popup
)
806 DbgPrint("PushTrackedPopup %p\n", popup
);
808 _ASSERT(m_bandCount
> 0);
809 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
811 HRESULT hr
= PushToArray(TrackedMenuEntry
, NULL
, popup
);
812 if (FAILED_UNEXPECTEDLY(hr
))
815 DbgPrint("PushTrackedPopup %p\n", popup
);
816 m_selectedMenu
= popup
;
818 m_selectedItemFlags
= 0;
820 return UpdateFocus();
823 HRESULT
CMenuFocusManager::PopMenuBar(CMenuBand
* mb
)
829 DbgPrint("PopMenuBar %p\n", mb
);
831 if (m_current
== m_entryUnderMouse
)
833 m_entryUnderMouse
= NULL
;
836 hr
= PopFromArray(&type
, &mbc
, NULL
);
837 if (FAILED_UNEXPECTEDLY(hr
))
843 _ASSERT(type
== MenuBarEntry
);
844 if (type
!= MenuBarEntry
)
850 mbc
->_SetParentBand(NULL
);
855 if (FAILED_UNEXPECTEDLY(hr
))
860 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
861 m_current
->mb
->_SetChildBand(NULL
);
867 HRESULT
CMenuFocusManager::PopMenuPopup(CMenuBand
* mb
)
873 DbgPrint("PopMenuPopup %p\n", mb
);
875 if (m_current
== m_entryUnderMouse
)
877 m_entryUnderMouse
= NULL
;
880 hr
= PopFromArray(&type
, &mbc
, NULL
);
881 if (FAILED_UNEXPECTEDLY(hr
))
887 _ASSERT(type
== MenuPopupEntry
);
888 if (type
!= MenuPopupEntry
)
894 mbc
->_SetParentBand(NULL
);
899 if (FAILED_UNEXPECTEDLY(hr
))
904 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
905 m_current
->mb
->_SetChildBand(NULL
);
911 HRESULT
CMenuFocusManager::PopTrackedPopup(HMENU popup
)
917 DbgPrint("PopTrackedPopup %p\n", popup
);
919 hr
= PopFromArray(&type
, NULL
, &hmenu
);
920 if (FAILED_UNEXPECTEDLY(hr
))
926 _ASSERT(type
== TrackedMenuEntry
);
927 if (type
!= TrackedMenuEntry
)
934 if (FAILED_UNEXPECTEDLY(hr
))