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
);
399 BOOL isTracking
= FALSE
;
402 ScreenToClient(child
, &pt
);
403 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
404 isTracking
= entry
->mb
->_IsTracking();
406 if (iHitTestResult
>= 0)
408 TRACE("MouseDown send %d\n", iHitTestResult
);
409 entry
->mb
->_MenuBarMouseDown(child
, iHitTestResult
);
413 msg
->message
= WM_NULL
;
415 m_isLButtonDown
= TRUE
;
416 m_movedSinceDown
= FALSE
;
417 m_windowAtDown
= child
;
419 TRACE("MouseDown end %d\n", m_isLButtonDown
);
424 LRESULT
CMenuFocusManager::ProcessMouseUp(MSG
* msg
)
427 int iHitTestResult
= -1;
429 TRACE("ProcessMouseUp %d %d %d\n", msg
->message
, msg
->wParam
, msg
->lParam
);
431 // Don't do anything if another window is capturing the mouse.
432 HWND cCapture
= ::GetCapture();
433 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
436 if (!m_isLButtonDown
)
439 m_isLButtonDown
= FALSE
;
443 child
= WindowFromPoint(pt
);
445 StackEntry
* entry
= NULL
;
446 if (IsTrackedWindow(child
, &entry
) != S_OK
)
449 TRACE("MouseUp %d\n", m_isLButtonDown
);
451 BOOL isTracking
= FALSE
;
454 ScreenToClient(child
, &pt
);
455 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
456 isTracking
= entry
->mb
->_IsTracking();
458 if (iHitTestResult
>= 0)
460 TRACE("MouseUp send %d\n", iHitTestResult
);
461 entry
->mb
->_MenuBarMouseUp(child
, iHitTestResult
);
468 LRESULT
CMenuFocusManager::MsgFilterHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
471 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
473 if (nCode
== MSGF_MENU
)
475 BOOL callNext
= TRUE
;
476 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
478 switch (msg
->message
)
480 case WM_NCLBUTTONDOWN
:
482 case WM_NCRBUTTONDOWN
:
487 HWND child
= WindowFromPoint(pt
);
488 BOOL hoveringMenuBar
= m_menuBar
->mb
->IsWindowOwner(child
) == S_OK
;
491 m_menuBar
->mb
->_DisableMouseTrack(TRUE
);
492 if (m_current
->type
== TrackedMenuEntry
)
494 SendMessage(m_parent
->hwnd
, WM_CANCELMODE
, 0, 0);
495 msg
->message
= WM_NULL
;
504 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
506 msg
->message
= WM_NULL
;
510 callNext
= ProcessMouseMove(msg
);
512 case WM_INITMENUPOPUP
:
513 TRACE("WM_INITMENUPOPUP %p %p\n", msg
->wParam
, msg
->lParam
);
514 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
516 m_selectedItemFlags
= 0;
519 TRACE("WM_MENUSELECT %p %p\n", msg
->wParam
, msg
->lParam
);
520 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
521 m_selectedItem
= GET_X_LPARAM(msg
->wParam
);
522 m_selectedItemFlags
= HIWORD(msg
->wParam
);
528 if (m_current
->hmenu
== m_selectedMenu
)
530 m_parent
->mb
->_MenuItemHotTrack(VK_LEFT
);
534 if (m_selectedItem
< 0 || !(m_selectedItemFlags
& MF_POPUP
))
536 m_parent
->mb
->_MenuItemHotTrack(VK_RIGHT
);
547 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
550 LRESULT
CMenuFocusManager::GetMsgHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
552 BOOL isLButton
= FALSE
;
554 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
556 if (nCode
== HC_ACTION
)
558 BOOL callNext
= TRUE
;
559 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
562 switch (msg
->message
)
564 case WM_NCLBUTTONDOWN
:
569 case WM_NCRBUTTONDOWN
:
571 if (m_current
->type
== MenuPopupEntry
)
573 HWND child
= WindowFromPoint(pt
);
575 if (IsTrackedWindowOrParent(child
) != S_OK
)
578 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
585 ProcessMouseDown(msg
);
593 callNext
= ProcessMouseMove(msg
);
596 callNext
= ProcessMouseMove(msg
);
597 //callNext = ProcessMouseLeave(msg);
601 if (m_current
->type
== MenuPopupEntry
)
603 DisableMouseTrack(m_current
->hwnd
, TRUE
);
610 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
613 m_current
->mb
->_MenuItemHotTrack(MPOS_EXECUTE
);
616 m_current
->mb
->_MenuItemHotTrack(VK_LEFT
);
619 m_current
->mb
->_MenuItemHotTrack(VK_RIGHT
);
622 m_current
->mb
->_MenuItemHotTrack(VK_UP
);
625 m_current
->mb
->_MenuItemHotTrack(VK_DOWN
);
628 msg
->message
= WM_NULL
;
639 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
642 HRESULT
CMenuFocusManager::PlaceHooks()
644 if (m_hMsgFilterHook
)
646 WARN("GETMESSAGE hook already placed!\n");
649 if (m_hMsgFilterHook
)
651 WARN("MSGFILTER hook already placed!\n");
654 if (m_current
->type
== TrackedMenuEntry
)
656 TRACE("Entering MSGFILTER hook...\n");
657 m_hMsgFilterHook
= SetWindowsHookEx(WH_MSGFILTER
, s_MsgFilterHook
, NULL
, m_threadId
);
661 TRACE("Entering GETMESSAGE hook...\n");
662 m_hGetMsgHook
= SetWindowsHookEx(WH_GETMESSAGE
, s_GetMsgHook
, NULL
, m_threadId
);
667 HRESULT
CMenuFocusManager::RemoveHooks()
669 TRACE("Removing all hooks...\n");
670 if (m_hMsgFilterHook
)
671 UnhookWindowsHookEx(m_hMsgFilterHook
);
673 UnhookWindowsHookEx(m_hGetMsgHook
);
674 m_hMsgFilterHook
= NULL
;
675 m_hGetMsgHook
= NULL
;
679 HRESULT
CMenuFocusManager::UpdateFocus()
682 StackEntry
* old
= m_current
;
684 TRACE("UpdateFocus\n");
690 m_current
= &(m_bandStack
[m_bandCount
- 1]);
694 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
696 hr
= m_current
->mb
->_GetTopLevelWindow(&(m_current
->hwnd
));
697 if (FAILED_UNEXPECTEDLY(hr
))
701 if (m_bandCount
>= 2)
703 m_parent
= &(m_bandStack
[m_bandCount
- 2]);
704 _ASSERT(m_parent
->type
!= TrackedMenuEntry
);
711 if (m_bandCount
>= 1 && m_bandStack
[0].type
== MenuBarEntry
)
713 m_menuBar
= &(m_bandStack
[0]);
720 if (old
&& (!m_current
|| old
->type
!= m_current
->type
))
722 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
724 DisableMouseTrack(m_current
->hwnd
, FALSE
);
728 if (FAILED_UNEXPECTEDLY(hr
))
732 if (m_current
&& (!old
|| old
->type
!= m_current
->type
))
735 if (FAILED_UNEXPECTEDLY(hr
))
741 DisableMouseTrack(m_parent
->hwnd
, TRUE
);
744 if ((m_current
&& m_current
->type
== MenuPopupEntry
) &&
745 (!m_parent
|| m_parent
->type
== MenuBarEntry
))
747 // When the mouse moves, it should set itself to the proper band
748 SetCapture(m_current
->hwnd
);
750 if (old
&& old
->type
== TrackedMenuEntry
)
752 // FIXME: Debugging code, probably not right
756 ScreenToClient(m_current
->hwnd
, &pt2
);
757 GetClientRect(m_current
->hwnd
, &rc2
);
758 if (PtInRect(&rc2
, pt2
))
759 SendMessage(m_current
->hwnd
, WM_MOUSEMOVE
, 0, MAKELPARAM(pt2
.x
, pt2
.y
));
761 SendMessage(m_current
->hwnd
, WM_MOUSELEAVE
, 0, 0);
765 _ASSERT(!m_parent
|| m_parent
->type
!= TrackedMenuEntry
);
770 HRESULT
CMenuFocusManager::PushMenuBar(CMenuBand
* mb
)
772 DbgPrint("PushMenuBar %p\n", mb
);
776 _ASSERT(m_bandCount
== 0);
778 HRESULT hr
= PushToArray(MenuBarEntry
, mb
, NULL
);
779 if (FAILED_UNEXPECTEDLY(hr
))
782 return UpdateFocus();
785 HRESULT
CMenuFocusManager::PushMenuPopup(CMenuBand
* mb
)
787 DbgPrint("PushTrackedPopup %p\n", mb
);
791 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
793 HRESULT hr
= PushToArray(MenuPopupEntry
, mb
, NULL
);
794 if (FAILED_UNEXPECTEDLY(hr
))
799 if (m_parent
&& m_parent
->type
!= TrackedMenuEntry
)
801 m_parent
->mb
->_SetChildBand(mb
);
802 mb
->_SetParentBand(m_parent
->mb
);
808 HRESULT
CMenuFocusManager::PushTrackedPopup(HMENU popup
)
810 DbgPrint("PushTrackedPopup %p\n", popup
);
812 _ASSERT(m_bandCount
> 0);
813 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
815 HRESULT hr
= PushToArray(TrackedMenuEntry
, NULL
, popup
);
816 if (FAILED_UNEXPECTEDLY(hr
))
819 DbgPrint("PushTrackedPopup %p\n", popup
);
820 m_selectedMenu
= popup
;
822 m_selectedItemFlags
= 0;
824 return UpdateFocus();
827 HRESULT
CMenuFocusManager::PopMenuBar(CMenuBand
* mb
)
833 DbgPrint("PopMenuBar %p\n", mb
);
835 hr
= PopFromArray(&type
, &mbc
, NULL
);
836 if (FAILED_UNEXPECTEDLY(hr
))
842 _ASSERT(type
== MenuBarEntry
);
843 if (type
!= MenuBarEntry
)
849 mbc
->_SetParentBand(NULL
);
854 if (FAILED_UNEXPECTEDLY(hr
))
859 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
860 m_current
->mb
->_SetChildBand(NULL
);
866 HRESULT
CMenuFocusManager::PopMenuPopup(CMenuBand
* mb
)
872 DbgPrint("PopMenuPopup %p\n", mb
);
874 hr
= PopFromArray(&type
, &mbc
, NULL
);
875 if (FAILED_UNEXPECTEDLY(hr
))
881 _ASSERT(type
== MenuPopupEntry
);
882 if (type
!= MenuPopupEntry
)
888 mbc
->_SetParentBand(NULL
);
893 if (FAILED_UNEXPECTEDLY(hr
))
898 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
899 m_current
->mb
->_SetChildBand(NULL
);
905 HRESULT
CMenuFocusManager::PopTrackedPopup(HMENU popup
)
911 DbgPrint("PopTrackedPopup %p\n", popup
);
913 hr
= PopFromArray(&type
, NULL
, &hmenu
);
914 if (FAILED_UNEXPECTEDLY(hr
))
920 _ASSERT(type
== TrackedMenuEntry
);
921 if (type
!= TrackedMenuEntry
)
928 if (FAILED_UNEXPECTEDLY(hr
))