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),
167 m_threadId
= GetCurrentThreadId();
170 CMenuFocusManager::~CMenuFocusManager()
174 void CMenuFocusManager::DisableMouseTrack(HWND parent
, BOOL disableThis
)
176 BOOL bDisable
= FALSE
;
177 BOOL lastDisable
= FALSE
;
182 StackEntry
& entry
= m_bandStack
[i
];
184 if (entry
.type
!= TrackedMenuEntry
)
187 HRESULT hr
= entry
.mb
->_GetTopLevelWindow(&hwnd
);
188 if (FAILED_UNEXPECTEDLY(hr
))
193 lastDisable
= disableThis
;
194 entry
.mb
->_DisableMouseTrack(disableThis
);
199 lastDisable
= bDisable
;
200 entry
.mb
->_DisableMouseTrack(bDisable
);
204 m_mouseTrackDisabled
= lastDisable
;
207 void CMenuFocusManager::SetCapture(HWND child
)
209 if (m_captureHwnd
!= child
)
214 m_captureHwnd
= child
;
215 TRACE("MouseTrack is now capturing %p\n", child
);
220 m_captureHwnd
= NULL
;
221 TRACE("MouseTrack is now off\n");
227 HRESULT
CMenuFocusManager::IsTrackedWindow(HWND hWnd
, StackEntry
** pentry
)
232 for (int i
= m_bandCount
; --i
>= 0;)
234 StackEntry
& entry
= m_bandStack
[i
];
236 if (entry
.type
!= TrackedMenuEntry
)
238 HRESULT hr
= entry
.mb
->IsWindowOwner(hWnd
);
239 if (FAILED_UNEXPECTEDLY(hr
))
253 LRESULT
CMenuFocusManager::ProcessMouseMove(MSG
* msg
)
256 int iHitTestResult
= -1;
258 POINT pt2
= { GET_X_LPARAM(msg
->lParam
), GET_Y_LPARAM(msg
->lParam
) };
259 ClientToScreen(msg
->hwnd
, &pt2
);
261 // Don't do anything if the mouse has not been moved
263 if (pt
.x
== m_ptPrev
.x
&& pt
.y
== m_ptPrev
.y
)
266 // Don't do anything if another window is capturing the mouse.
267 HWND cCapture
= ::GetCapture();
268 if (cCapture
&& cCapture
!= m_captureHwnd
&& m_current
->type
!= TrackedMenuEntry
)
273 child
= WindowFromPoint(pt
);
275 StackEntry
* entry
= NULL
;
276 IsTrackedWindow(child
, &entry
);
278 BOOL isTracking
= FALSE
;
281 ScreenToClient(child
, &pt
);
282 iHitTestResult
= SendMessageW(child
, TB_HITTEST
, 0, (LPARAM
) &pt
);
283 isTracking
= entry
->mb
->_IsTracking();
285 if (SendMessage(child
, WM_USER_ISTRACKEDITEM
, iHitTestResult
, 0) == S_FALSE
)
287 TRACE("Hot item tracking detected a change (capture=%p)...\n", m_captureHwnd
);
288 DisableMouseTrack(NULL
, FALSE
);
289 if (isTracking
&& iHitTestResult
>=0 && m_current
->type
== TrackedMenuEntry
)
290 SendMessage(entry
->hwnd
, WM_CANCELMODE
, 0, 0);
291 PostMessage(child
, WM_USER_CHANGETRACKEDITEM
, iHitTestResult
, MAKELPARAM(isTracking
, TRUE
));
292 if (m_current
->type
== TrackedMenuEntry
)
297 if (m_entryUnderMouse
!= entry
)
299 // Mouse moved away from a tracked window
300 if (m_entryUnderMouse
)
302 m_entryUnderMouse
->mb
->_ChangeHotItem(NULL
, -1, HICF_MOUSE
);
304 if (cCapture
== m_captureHwnd
)
308 if (m_hwndUnderMouse
!= child
)
312 // Mouse moved to a tracked window
313 if (m_current
->type
== MenuPopupEntry
)
315 ScreenToClient(child
, &pt2
);
316 SendMessage(child
, WM_MOUSEMOVE
, msg
->wParam
, MAKELPARAM(pt2
.x
, pt2
.y
));
320 m_hwndUnderMouse
= child
;
321 m_entryUnderMouse
= entry
;
324 if (m_current
->type
== MenuPopupEntry
)
326 HWND parent
= GetAncestor(child
, GA_ROOT
);
327 DisableMouseTrack(parent
, FALSE
);
333 LRESULT
CMenuFocusManager::MsgFilterHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
336 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
338 if (nCode
== MSGF_MENU
)
340 BOOL callNext
= TRUE
;
341 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
343 switch (msg
->message
)
345 case WM_NCLBUTTONDOWN
:
350 HWND child
= WindowFromPoint(pt
);
351 BOOL hoveringMenuBar
= m_menuBar
->mb
->IsWindowOwner(child
) == S_OK
;
354 m_menuBar
->mb
->_DisableMouseTrack(TRUE
);
359 callNext
= ProcessMouseMove(msg
);
361 case WM_INITMENUPOPUP
:
362 TRACE("WM_INITMENUPOPUP %p %p\n", msg
->wParam
, msg
->lParam
);
363 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
365 m_selectedItemFlags
= 0;
368 TRACE("WM_MENUSELECT %p %p\n", msg
->wParam
, msg
->lParam
);
369 m_selectedMenu
= reinterpret_cast<HMENU
>(msg
->lParam
);
370 m_selectedItem
= GET_X_LPARAM(msg
->wParam
);
371 m_selectedItemFlags
= HIWORD(msg
->wParam
);
377 if (m_current
->hmenu
== m_selectedMenu
)
379 m_parent
->mb
->_MenuItemHotTrack(VK_LEFT
);
383 if (m_selectedItem
< 0 || !(m_selectedItemFlags
& MF_POPUP
))
385 m_parent
->mb
->_MenuItemHotTrack(VK_RIGHT
);
396 return CallNextHookEx(m_hMsgFilterHook
, nCode
, hookWParam
, hookLParam
);
399 LRESULT
CMenuFocusManager::GetMsgHook(INT nCode
, WPARAM hookWParam
, LPARAM hookLParam
)
402 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
404 if (nCode
== HC_ACTION
)
406 BOOL callNext
= TRUE
;
407 MSG
* msg
= reinterpret_cast<MSG
*>(hookLParam
);
410 switch (msg
->message
)
412 case WM_NCLBUTTONDOWN
:
414 if (m_current
->type
== MenuPopupEntry
)
416 HWND child
= WindowFromPoint(pt
);
418 if (IsTrackedWindow(child
) != S_OK
)
421 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
426 callNext
= ProcessMouseMove(msg
);
429 callNext
= ProcessMouseMove(msg
);
430 //callNext = ProcessMouseLeave(msg);
434 if (m_current
->type
== MenuPopupEntry
)
436 DisableMouseTrack(m_current
->hwnd
, TRUE
);
443 m_current
->mb
->_MenuItemHotTrack(MPOS_FULLCANCEL
);
446 m_current
->mb
->_MenuItemHotTrack(VK_LEFT
);
449 m_current
->mb
->_MenuItemHotTrack(VK_RIGHT
);
452 m_current
->mb
->_MenuItemHotTrack(VK_UP
);
455 m_current
->mb
->_MenuItemHotTrack(VK_DOWN
);
466 return CallNextHookEx(m_hGetMsgHook
, nCode
, hookWParam
, hookLParam
);
469 HRESULT
CMenuFocusManager::PlaceHooks()
471 if (m_current
->type
== TrackedMenuEntry
)
473 TRACE("Entering MSGFILTER hook...\n");
474 m_hMsgFilterHook
= SetWindowsHookEx(WH_MSGFILTER
, s_MsgFilterHook
, NULL
, m_threadId
);
478 TRACE("Entering GETMESSAGE hook...\n");
479 m_hGetMsgHook
= SetWindowsHookEx(WH_GETMESSAGE
, s_GetMsgHook
, NULL
, m_threadId
);
484 HRESULT
CMenuFocusManager::RemoveHooks()
486 TRACE("Removing all hooks...\n");
487 if (m_hMsgFilterHook
)
488 UnhookWindowsHookEx(m_hMsgFilterHook
);
490 UnhookWindowsHookEx(m_hGetMsgHook
);
491 m_hMsgFilterHook
= NULL
;
492 m_hGetMsgHook
= NULL
;
496 HRESULT
CMenuFocusManager::UpdateFocus()
499 StackEntry
* old
= m_current
;
505 m_current
= &(m_bandStack
[m_bandCount
- 1]);
509 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
511 hr
= m_current
->mb
->_GetTopLevelWindow(&(m_current
->hwnd
));
512 if (FAILED_UNEXPECTEDLY(hr
))
516 if (m_bandCount
>= 2)
518 m_parent
= &(m_bandStack
[m_bandCount
- 2]);
519 _ASSERT(m_parent
->type
!= TrackedMenuEntry
);
526 if (m_bandCount
>= 1 && m_bandStack
[0].type
== MenuBarEntry
)
528 m_menuBar
= &(m_bandStack
[0]);
535 if (old
&& (!m_current
|| old
->type
!= m_current
->type
))
537 if (m_current
&& m_current
->type
!= TrackedMenuEntry
)
539 DisableMouseTrack(m_current
->hwnd
, FALSE
);
543 if (FAILED_UNEXPECTEDLY(hr
))
547 if (m_current
&& (!old
|| old
->type
!= m_current
->type
))
550 if (FAILED_UNEXPECTEDLY(hr
))
556 DisableMouseTrack(m_parent
->hwnd
, TRUE
);
559 if ((m_current
&& m_current
->type
== MenuPopupEntry
) &&
560 (!m_parent
|| m_parent
->type
== MenuBarEntry
))
562 // When the mouse moves, it should set itself to the proper band
563 SetCapture(m_current
->hwnd
);
565 if (old
&& old
->type
== TrackedMenuEntry
)
567 // FIXME: Debugging code, probably not right
571 ScreenToClient(m_current
->hwnd
, &pt2
);
572 GetClientRect(m_current
->hwnd
, &rc2
);
573 if (PtInRect(&rc2
, pt2
))
574 SendMessage(m_current
->hwnd
, WM_MOUSEMOVE
, 0, MAKELPARAM(pt2
.x
, pt2
.y
));
576 SendMessage(m_current
->hwnd
, WM_MOUSELEAVE
, 0, 0);
580 _ASSERT(!m_parent
|| m_parent
->type
!= TrackedMenuEntry
);
585 HRESULT
CMenuFocusManager::PushMenuBar(CMenuBand
* mb
)
587 _ASSERT(m_bandCount
== 0);
589 HRESULT hr
= PushToArray(MenuBarEntry
, mb
, NULL
);
590 if (FAILED_UNEXPECTEDLY(hr
))
593 return UpdateFocus();
596 HRESULT
CMenuFocusManager::PushMenuPopup(CMenuBand
* mb
)
598 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
600 HRESULT hr
= PushToArray(MenuPopupEntry
, mb
, NULL
);
601 if (FAILED_UNEXPECTEDLY(hr
))
606 if (m_parent
&& m_parent
->type
!= TrackedMenuEntry
)
608 m_parent
->mb
->_SetChildBand(mb
);
609 mb
->_SetParentBand(m_parent
->mb
);
615 HRESULT
CMenuFocusManager::PushTrackedPopup(HMENU popup
)
617 _ASSERT(m_bandCount
> 0);
618 _ASSERT(!m_current
|| m_current
->type
!= TrackedMenuEntry
);
620 HRESULT hr
= PushToArray(TrackedMenuEntry
, NULL
, popup
);
621 if (FAILED_UNEXPECTEDLY(hr
))
624 TRACE("PushTrackedPopup %p\n", popup
);
625 m_selectedMenu
= popup
;
627 m_selectedItemFlags
= 0;
629 return UpdateFocus();
632 HRESULT
CMenuFocusManager::PopMenuBar(CMenuBand
* mb
)
638 hr
= PopFromArray(&type
, &mbc
, NULL
);
639 if (FAILED_UNEXPECTEDLY(hr
))
645 _ASSERT(type
== MenuBarEntry
);
646 if (type
!= MenuBarEntry
)
652 mbc
->_SetParentBand(NULL
);
655 if (FAILED_UNEXPECTEDLY(hr
))
660 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
661 m_current
->mb
->_SetChildBand(NULL
);
667 HRESULT
CMenuFocusManager::PopMenuPopup(CMenuBand
* mb
)
673 hr
= PopFromArray(&type
, &mbc
, NULL
);
674 if (FAILED_UNEXPECTEDLY(hr
))
680 _ASSERT(type
== MenuPopupEntry
);
681 if (type
!= MenuPopupEntry
)
687 mbc
->_SetParentBand(NULL
);
690 if (FAILED_UNEXPECTEDLY(hr
))
695 _ASSERT(m_current
->type
!= TrackedMenuEntry
);
696 m_current
->mb
->_SetChildBand(NULL
);
702 HRESULT
CMenuFocusManager::PopTrackedPopup(HMENU popup
)
708 hr
= PopFromArray(&type
, NULL
, &hmenu
);
709 if (FAILED_UNEXPECTEDLY(hr
))
715 _ASSERT(type
== TrackedMenuEntry
);
716 if (type
!= TrackedMenuEntry
)
723 if (FAILED_UNEXPECTEDLY(hr
))