[RSHELL]
[reactos.git] / base / shell / rshell / CMenuFocusManager.cpp
1 /*
2 * Shell Menu Band
3 *
4 * Copyright 2014 David Quintana
5 *
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.
10 *
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.
15 *
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
19 */
20 #include "precomp.h"
21 #include <windowsx.h>
22 #include <commoncontrols.h>
23 #include <shlwapi_undoc.h>
24
25 #include "CMenuFocusManager.h"
26 #include "CMenuToolbars.h"
27 #include "CMenuBand.h"
28
29 #if DBG
30 # undef _ASSERT
31 # define _ASSERT(x) DbgAssert(!!(x), __FILE__, __LINE__, #x)
32
33 bool DbgAssert(bool x, const char * filename, int line, const char * expr)
34 {
35 if (!x)
36 {
37 char szMsg[512];
38 const char *fname;
39
40 fname = strrchr(filename, '\\');
41 if (fname == NULL)
42 {
43 fname = strrchr(filename, '/');
44 }
45
46 if (fname == NULL)
47 fname = filename;
48 else
49 fname++;
50
51 sprintf(szMsg, "%s:%d: Assertion failed: %s\n", fname, line, expr);
52
53 OutputDebugStringA(szMsg);
54
55 __debugbreak();
56 }
57 return x;
58 }
59 #else
60 # undef _ASSERT
61 # define _ASSERT(x) (!!(x))
62 #endif
63
64 WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus);
65
66 DWORD CMenuFocusManager::TlsIndex = 0;
67
68 CMenuFocusManager * CMenuFocusManager::GetManager()
69 {
70 return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
71 }
72
73 CMenuFocusManager * CMenuFocusManager::AcquireManager()
74 {
75 CMenuFocusManager * obj = NULL;
76
77 if (!TlsIndex)
78 {
79 if ((TlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
80 return NULL;
81 }
82
83 obj = GetManager();
84
85 if (!obj)
86 {
87 obj = new CComObject<CMenuFocusManager>();
88 TlsSetValue(TlsIndex, obj);
89 }
90
91 obj->AddRef();
92
93 return obj;
94 }
95
96 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
97 {
98 if (!obj->Release())
99 {
100 TlsSetValue(TlsIndex, NULL);
101 }
102 }
103
104 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
105 {
106 return GetManager()->MsgFilterHook(nCode, wParam, lParam);
107 }
108
109 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
110 {
111 return GetManager()->GetMsgHook(nCode, wParam, lParam);
112 }
113
114 HRESULT CMenuFocusManager::PushToArray(StackEntryType type, CMenuBand * mb, HMENU hmenu)
115 {
116 if (m_bandCount >= MAX_RECURSE)
117 return E_OUTOFMEMORY;
118
119 m_bandStack[m_bandCount].type = type;
120 m_bandStack[m_bandCount].mb = mb;
121 m_bandStack[m_bandCount].hmenu = hmenu;
122 m_bandCount++;
123
124 return S_OK;
125 }
126
127 HRESULT CMenuFocusManager::PopFromArray(StackEntryType * pType, CMenuBand ** pMb, HMENU * pHmenu)
128 {
129 if (pType) *pType = NoEntry;
130 if (pMb) *pMb = NULL;
131 if (pHmenu) *pHmenu = NULL;
132
133 if (m_bandCount <= 0)
134 return S_FALSE;
135
136 m_bandCount--;
137
138 if (pType) *pType = m_bandStack[m_bandCount].type;
139 if (*pType == TrackedMenuEntry)
140 {
141 if (pHmenu) *pHmenu = m_bandStack[m_bandCount].hmenu;
142 }
143 else
144 {
145 if (pMb) *pMb = m_bandStack[m_bandCount].mb;
146 }
147
148 return S_OK;
149 }
150
151 CMenuFocusManager::CMenuFocusManager() :
152 m_current(NULL),
153 m_parent(NULL),
154 m_hMsgFilterHook(NULL),
155 m_hGetMsgHook(NULL),
156 m_mouseTrackDisabled(FALSE),
157 m_captureHwnd(0),
158 m_hwndUnderMouse(NULL),
159 m_entryUnderMouse(NULL),
160 m_selectedMenu(NULL),
161 m_selectedItem(0),
162 m_selectedItemFlags(0),
163 m_bandCount(0)
164 {
165 m_ptPrev.x = 0;
166 m_ptPrev.y = 0;
167 m_threadId = GetCurrentThreadId();
168 }
169
170 CMenuFocusManager::~CMenuFocusManager()
171 {
172 }
173
174 void CMenuFocusManager::DisableMouseTrack(HWND parent, BOOL disableThis)
175 {
176 BOOL bDisable = FALSE;
177 BOOL lastDisable = FALSE;
178
179 int i = m_bandCount;
180 while (--i >= 0)
181 {
182 StackEntry& entry = m_bandStack[i];
183
184 if (entry.type != TrackedMenuEntry)
185 {
186 HWND hwnd;
187 HRESULT hr = entry.mb->_GetTopLevelWindow(&hwnd);
188 if (FAILED_UNEXPECTEDLY(hr))
189 break;
190
191 if (hwnd == parent)
192 {
193 lastDisable = disableThis;
194 entry.mb->_DisableMouseTrack(disableThis);
195 bDisable = TRUE;
196 }
197 else
198 {
199 lastDisable = bDisable;
200 entry.mb->_DisableMouseTrack(bDisable);
201 }
202 }
203 }
204 m_mouseTrackDisabled = lastDisable;
205 }
206
207 void CMenuFocusManager::SetCapture(HWND child)
208 {
209 if (m_captureHwnd != child)
210 {
211 if (child)
212 {
213 ::SetCapture(child);
214 m_captureHwnd = child;
215 TRACE("MouseTrack is now capturing %p\n", child);
216 }
217 else
218 {
219 ::ReleaseCapture();
220 m_captureHwnd = NULL;
221 TRACE("MouseTrack is now off\n");
222 }
223
224 }
225 }
226
227 HRESULT CMenuFocusManager::IsTrackedWindow(HWND hWnd, StackEntry ** pentry)
228 {
229 if (pentry)
230 *pentry = NULL;
231
232 for (int i = m_bandCount; --i >= 0;)
233 {
234 StackEntry& entry = m_bandStack[i];
235
236 if (entry.type != TrackedMenuEntry)
237 {
238 HRESULT hr = entry.mb->IsWindowOwner(hWnd);
239 if (FAILED_UNEXPECTEDLY(hr))
240 return hr;
241 if (hr == S_OK)
242 {
243 if (pentry)
244 *pentry = &entry;
245 return S_OK;
246 }
247 }
248 }
249
250 return S_FALSE;
251 }
252
253 LRESULT CMenuFocusManager::ProcessMouseMove(MSG* msg)
254 {
255 HWND child;
256 int iHitTestResult = -1;
257
258 POINT pt2 = { GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam) };
259 ClientToScreen(msg->hwnd, &pt2);
260
261 // Don't do anything if the mouse has not been moved
262 POINT pt = msg->pt;
263 if (pt.x == m_ptPrev.x && pt.y == m_ptPrev.y)
264 return TRUE;
265
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)
269 return TRUE;
270
271 m_ptPrev = pt;
272
273 child = WindowFromPoint(pt);
274
275 StackEntry * entry = NULL;
276 IsTrackedWindow(child, &entry);
277
278 BOOL isTracking = FALSE;
279 if (entry)
280 {
281 ScreenToClient(child, &pt);
282 iHitTestResult = SendMessageW(child, TB_HITTEST, 0, (LPARAM) &pt);
283 isTracking = entry->mb->_IsTracking();
284
285 if (SendMessage(child, WM_USER_ISTRACKEDITEM, iHitTestResult, 0) == S_FALSE)
286 {
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, isTracking);
292 if (m_current->type == TrackedMenuEntry)
293 return FALSE;
294 }
295 }
296
297 if (m_entryUnderMouse != entry)
298 {
299 // Mouse moved away from a tracked window
300 if (m_entryUnderMouse)
301 {
302 m_entryUnderMouse->mb->_ChangeHotItem(NULL, -1, HICF_MOUSE);
303 }
304 if (cCapture == m_captureHwnd)
305 SetCapture(NULL);
306 }
307
308 if (m_hwndUnderMouse != child)
309 {
310 if (entry)
311 {
312 // Mouse moved to a tracked window
313 if (m_current->type == MenuPopupEntry)
314 {
315 ScreenToClient(child, &pt2);
316 SendMessage(child, WM_MOUSEMOVE, msg->wParam, MAKELPARAM(pt2.x, pt2.y));
317 }
318 }
319
320 m_hwndUnderMouse = child;
321 m_entryUnderMouse = entry;
322 }
323
324 if (m_current->type == MenuPopupEntry)
325 {
326 HWND parent = GetAncestor(child, GA_ROOT);
327 DisableMouseTrack(parent, FALSE);
328 }
329
330 return TRUE;
331 }
332
333 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
334 {
335 if (nCode < 0)
336 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
337
338 if (nCode == MSGF_MENU)
339 {
340 BOOL callNext = TRUE;
341 MSG* msg = reinterpret_cast<MSG*>(lParam);
342
343 switch (msg->message)
344 {
345 case WM_NCLBUTTONDOWN:
346 case WM_LBUTTONDOWN:
347 if (m_menuBar)
348 {
349 POINT pt = msg->pt;
350 HWND child = WindowFromPoint(pt);
351 BOOL hoveringMenuBar = m_menuBar->mb->IsWindowOwner(child) == S_OK;
352 if (hoveringMenuBar)
353 {
354 m_menuBar->mb->_DisableMouseTrack(TRUE);
355 }
356 }
357 break;
358 case WM_MOUSEMOVE:
359 callNext = ProcessMouseMove(msg);
360 break;
361 case WM_INITMENUPOPUP:
362 DbgPrint("WM_INITMENUPOPUP %p %p\n", wParam, lParam);
363 m_selectedMenu = reinterpret_cast<HMENU>(lParam);
364 m_selectedItem = -1;
365 m_selectedItemFlags = 0;
366 break;
367 case WM_MENUSELECT:
368 DbgPrint("WM_MENUSELECT %p %p\n", wParam, lParam);
369 m_selectedMenu = reinterpret_cast<HMENU>(lParam);
370 m_selectedItem = LOWORD(wParam);
371 m_selectedItemFlags = HIWORD(wParam);
372 break;
373 case WM_KEYDOWN:
374 switch (msg->wParam)
375 {
376 case VK_LEFT:
377 if (m_current->hmenu == m_selectedMenu)
378 {
379 m_parent->mb->_MenuItemHotTrack(VK_LEFT);
380 }
381 break;
382 case VK_RIGHT:
383 if (!(m_selectedItemFlags & MF_POPUP))
384 {
385 m_parent->mb->_MenuItemHotTrack(VK_RIGHT);
386 }
387 break;
388 }
389 break;
390 }
391
392 if (!callNext)
393 return 1;
394 }
395
396 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
397 }
398
399 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
400 {
401 if (nCode < 0)
402 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
403
404 if (nCode == HC_ACTION)
405 {
406 BOOL callNext = TRUE;
407 MSG* msg = reinterpret_cast<MSG*>(lParam);
408 POINT pt = msg->pt;
409
410 switch (msg->message)
411 {
412 case WM_NCLBUTTONDOWN:
413 case WM_LBUTTONDOWN:
414 if (m_current->type == MenuPopupEntry)
415 {
416 HWND child = WindowFromPoint(pt);
417
418 if (IsTrackedWindow(child) != S_OK)
419 {
420 SetCapture(NULL);
421 m_current->mb->_MenuItemHotTrack(MPOS_FULLCANCEL);
422 }
423 }
424 break;
425 case WM_MOUSEMOVE:
426 callNext = ProcessMouseMove(msg);
427 break;
428 case WM_MOUSELEAVE:
429 callNext = ProcessMouseMove(msg);
430 //callNext = ProcessMouseLeave(msg);
431 break;
432 case WM_SYSKEYDOWN:
433 case WM_KEYDOWN:
434 if (m_current->type == MenuPopupEntry)
435 {
436 DisableMouseTrack(m_current->hwnd, TRUE);
437 switch (msg->wParam)
438 {
439 case VK_ESCAPE:
440 case VK_MENU:
441 case VK_LMENU:
442 case VK_RMENU:
443 m_current->mb->_MenuItemHotTrack(MPOS_FULLCANCEL);
444 break;
445 case VK_LEFT:
446 m_current->mb->_MenuItemHotTrack(VK_LEFT);
447 break;
448 case VK_RIGHT:
449 m_current->mb->_MenuItemHotTrack(VK_RIGHT);
450 break;
451 case VK_UP:
452 m_current->mb->_MenuItemHotTrack(VK_UP);
453 break;
454 case VK_DOWN:
455 m_current->mb->_MenuItemHotTrack(VK_DOWN);
456 break;
457 }
458 }
459 break;
460 }
461
462 if (!callNext)
463 return 1;
464 }
465
466 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
467 }
468
469 HRESULT CMenuFocusManager::PlaceHooks()
470 {
471 if (m_current->type == TrackedMenuEntry)
472 {
473 TRACE("Entering MSGFILTER hook...\n");
474 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
475 }
476 else
477 {
478 TRACE("Entering GETMESSAGE hook...\n");
479 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
480 }
481 return S_OK;
482 }
483
484 HRESULT CMenuFocusManager::RemoveHooks()
485 {
486 TRACE("Removing all hooks...\n");
487 if (m_hMsgFilterHook)
488 UnhookWindowsHookEx(m_hMsgFilterHook);
489 if (m_hGetMsgHook)
490 UnhookWindowsHookEx(m_hGetMsgHook);
491 m_hMsgFilterHook = NULL;
492 m_hGetMsgHook = NULL;
493 return S_OK;
494 }
495
496 HRESULT CMenuFocusManager::UpdateFocus()
497 {
498 HRESULT hr;
499 StackEntry * old = m_current;
500
501 if (old)
502 SetCapture(NULL);
503
504 if (m_bandCount > 0)
505 m_current = &(m_bandStack[m_bandCount - 1]);
506 else
507 m_current = NULL;
508
509 if (m_current && m_current->type != TrackedMenuEntry)
510 {
511 hr = m_current->mb->_GetTopLevelWindow(&(m_current->hwnd));
512 if (FAILED_UNEXPECTEDLY(hr))
513 return hr;
514 }
515
516 if (m_bandCount >= 2)
517 {
518 m_parent = &(m_bandStack[m_bandCount - 2]);
519 _ASSERT(m_parent->type != TrackedMenuEntry);
520 }
521 else
522 {
523 m_parent = NULL;
524 }
525
526 if (m_bandCount >= 1 && m_bandStack[0].type == MenuBarEntry)
527 {
528 m_menuBar = &(m_bandStack[0]);
529 }
530 else
531 {
532 m_menuBar = NULL;
533 }
534
535 if (old && (!m_current || old->type != m_current->type))
536 {
537 if (m_current && m_current->type != TrackedMenuEntry)
538 {
539 DisableMouseTrack(m_current->hwnd, FALSE);
540 }
541
542 hr = RemoveHooks();
543 if (FAILED_UNEXPECTEDLY(hr))
544 return hr;
545 }
546
547 if (m_current && (!old || old->type != m_current->type))
548 {
549 hr = PlaceHooks();
550 if (FAILED_UNEXPECTEDLY(hr))
551 return hr;
552 }
553
554 if (m_parent)
555 {
556 DisableMouseTrack(m_parent->hwnd, TRUE);
557 }
558
559 if ((m_current && m_current->type == MenuPopupEntry) &&
560 (!m_parent || m_parent->type == MenuBarEntry))
561 {
562 // When the mouse moves, it should set itself to the proper band
563 SetCapture(m_current->hwnd);
564
565 if (old && old->type == TrackedMenuEntry)
566 {
567 // FIXME: Debugging code, probably not right
568 POINT pt2;
569 RECT rc2;
570 GetCursorPos(&pt2);
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));
575 else
576 SendMessage(m_current->hwnd, WM_MOUSELEAVE, 0, 0);
577 }
578 }
579
580 _ASSERT(!m_parent || m_parent->type != TrackedMenuEntry);
581
582 return S_OK;
583 }
584
585 HRESULT CMenuFocusManager::PushMenuBar(CMenuBand * mb)
586 {
587 _ASSERT(m_bandCount == 0);
588
589 HRESULT hr = PushToArray(MenuBarEntry, mb, NULL);
590 if (FAILED_UNEXPECTEDLY(hr))
591 return hr;
592
593 return UpdateFocus();
594 }
595
596 HRESULT CMenuFocusManager::PushMenuPopup(CMenuBand * mb)
597 {
598 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
599
600 HRESULT hr = PushToArray(MenuPopupEntry, mb, NULL);
601 if (FAILED_UNEXPECTEDLY(hr))
602 return hr;
603
604 hr = UpdateFocus();
605
606 if (m_parent && m_parent->type != TrackedMenuEntry)
607 {
608 m_parent->mb->_SetChildBand(mb);
609 mb->_SetParentBand(m_parent->mb);
610 }
611
612 return hr;
613 }
614
615 HRESULT CMenuFocusManager::PushTrackedPopup(HMENU popup)
616 {
617 _ASSERT(m_bandCount > 0);
618 _ASSERT(!m_current || m_current->type != TrackedMenuEntry);
619
620 HRESULT hr = PushToArray(TrackedMenuEntry, NULL, popup);
621 if (FAILED_UNEXPECTEDLY(hr))
622 return hr;
623
624 return UpdateFocus();
625 }
626
627 HRESULT CMenuFocusManager::PopMenuBar(CMenuBand * mb)
628 {
629 StackEntryType type;
630 CMenuBand * mbc;
631 HRESULT hr;
632
633 hr = PopFromArray(&type, &mbc, NULL);
634 if (FAILED_UNEXPECTEDLY(hr))
635 {
636 UpdateFocus();
637 return hr;
638 }
639
640 _ASSERT(type == MenuBarEntry);
641 if (type != MenuBarEntry)
642 return E_FAIL;
643
644 if (!mbc)
645 return E_FAIL;
646
647 mbc->_SetParentBand(NULL);
648
649 hr = UpdateFocus();
650 if (FAILED_UNEXPECTEDLY(hr))
651 return hr;
652
653 if (m_current)
654 {
655 _ASSERT(m_current->type != TrackedMenuEntry);
656 m_current->mb->_SetChildBand(NULL);
657 }
658
659 return S_OK;
660 }
661
662 HRESULT CMenuFocusManager::PopMenuPopup(CMenuBand * mb)
663 {
664 StackEntryType type;
665 CMenuBand * mbc;
666 HRESULT hr;
667
668 hr = PopFromArray(&type, &mbc, NULL);
669 if (FAILED_UNEXPECTEDLY(hr))
670 {
671 UpdateFocus();
672 return hr;
673 }
674
675 _ASSERT(type == MenuPopupEntry);
676 if (type != MenuPopupEntry)
677 return E_FAIL;
678
679 if (!mbc)
680 return E_FAIL;
681
682 mbc->_SetParentBand(NULL);
683
684 hr = UpdateFocus();
685 if (FAILED_UNEXPECTEDLY(hr))
686 return hr;
687
688 if (m_current)
689 {
690 _ASSERT(m_current->type != TrackedMenuEntry);
691 m_current->mb->_SetChildBand(NULL);
692 }
693
694 return S_OK;
695 }
696
697 HRESULT CMenuFocusManager::PopTrackedPopup(HMENU popup)
698 {
699 StackEntryType type;
700 HMENU hmenu;
701 HRESULT hr;
702
703 hr = PopFromArray(&type, NULL, &hmenu);
704 if (FAILED_UNEXPECTEDLY(hr))
705 {
706 UpdateFocus();
707 return hr;
708 }
709
710 _ASSERT(type == TrackedMenuEntry);
711 if (type != TrackedMenuEntry)
712 return E_FAIL;
713
714 if (hmenu != popup)
715 return E_FAIL;
716
717 hr = UpdateFocus();
718 if (FAILED_UNEXPECTEDLY(hr))
719 return hr;
720
721 return S_OK;
722 }