[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 WINE_DEFAULT_DEBUG_CHANNEL(CMenuFocus);
30
31 DWORD CMenuFocusManager::TlsIndex = 0;
32
33 CMenuFocusManager * CMenuFocusManager::GetManager()
34 {
35 return reinterpret_cast<CMenuFocusManager *>(TlsGetValue(TlsIndex));
36 }
37
38 CMenuFocusManager * CMenuFocusManager::AcquireManager()
39 {
40 CMenuFocusManager * obj = NULL;
41
42 if (!TlsIndex)
43 {
44 if ((TlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
45 return NULL;
46 }
47
48 obj = GetManager();
49
50 if (!obj)
51 {
52 obj = new CComObject<CMenuFocusManager>();
53 TlsSetValue(TlsIndex, obj);
54 }
55
56 obj->AddRef();
57
58 return obj;
59 }
60
61 void CMenuFocusManager::ReleaseManager(CMenuFocusManager * obj)
62 {
63 if (!obj->Release())
64 {
65 TlsSetValue(TlsIndex, NULL);
66 }
67 }
68
69 LRESULT CALLBACK CMenuFocusManager::s_MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
70 {
71 return GetManager()->MsgFilterHook(nCode, wParam, lParam);
72 }
73
74 LRESULT CALLBACK CMenuFocusManager::s_GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
75 {
76 return GetManager()->GetMsgHook(nCode, wParam, lParam);
77 }
78
79 HRESULT CMenuFocusManager::PushToArray(CMenuBand * item)
80 {
81 if (m_bandCount >= MAX_RECURSE)
82 return E_OUTOFMEMORY;
83
84 m_bandStack[m_bandCount++] = item;
85 return S_OK;
86 }
87
88 HRESULT CMenuFocusManager::PopFromArray(CMenuBand ** pItem)
89 {
90 if (pItem)
91 *pItem = NULL;
92
93 if (m_bandCount <= 0)
94 return S_FALSE;
95
96 m_bandCount--;
97
98 if (pItem)
99 *pItem = m_bandStack[m_bandCount];
100
101 m_bandStack[m_bandCount] = NULL;
102
103 return S_OK;
104 }
105
106 HRESULT CMenuFocusManager::PeekArray(CMenuBand ** pItem)
107 {
108 if (!pItem)
109 return E_FAIL;
110
111 *pItem = NULL;
112
113 if (m_bandCount <= 0)
114 return S_FALSE;
115
116 *pItem = m_bandStack[m_bandCount - 1];
117
118 return S_OK;
119 }
120
121 CMenuFocusManager::CMenuFocusManager() :
122 m_currentBand(NULL),
123 m_currentFocus(NULL),
124 m_currentMenu(NULL),
125 m_parentToolbar(NULL),
126 m_hMsgFilterHook(NULL),
127 m_hGetMsgHook(NULL),
128 m_mouseTrackDisabled(FALSE),
129 m_lastMoveFlags(0),
130 m_lastMovePos(0),
131 m_bandCount(0)
132 {
133 m_threadId = GetCurrentThreadId();
134 }
135
136 CMenuFocusManager::~CMenuFocusManager()
137 {
138 }
139
140 void CMenuFocusManager::DisableMouseTrack(HWND enableTo, BOOL disableThis)
141 {
142 BOOL bDisable = FALSE;
143
144 int i = m_bandCount;
145 while (--i >= 0)
146 {
147 CMenuBand * band = m_bandStack[i];
148
149 HWND hwnd;
150 HRESULT hr = band->_GetTopLevelWindow(&hwnd);
151 if (FAILED_UNEXPECTEDLY(hr))
152 break;
153
154 if (hwnd == enableTo)
155 {
156 band->_DisableMouseTrack(disableThis);
157 bDisable = TRUE;
158 }
159 else
160 {
161 band->_DisableMouseTrack(bDisable);
162 }
163 }
164
165 if (m_mouseTrackDisabled == bDisable)
166 {
167 if (bDisable)
168 {
169 SetCapture(m_currentFocus);
170 }
171 else
172 ReleaseCapture();
173
174 m_mouseTrackDisabled = bDisable;
175 }
176 }
177
178 HRESULT CMenuFocusManager::IsTrackedWindow(HWND hWnd)
179 {
180 int i = m_bandCount;
181 while (--i >= 0)
182 {
183 CMenuBand * band = m_bandStack[i];
184
185 HWND hwnd;
186 HRESULT hr = band->_GetTopLevelWindow(&hwnd);
187 if (FAILED_UNEXPECTEDLY(hr))
188 return hr;
189
190 if (hwnd == hWnd)
191 {
192 return band->_IsPopup();
193 }
194 }
195
196 return S_FALSE;
197 }
198
199 LRESULT CMenuFocusManager::ProcessMouseMove(MSG* msg)
200 {
201 HWND parent;
202 HWND child;
203 POINT pt;
204 int iHitTestResult;
205
206 pt = msg->pt;
207
208 parent = WindowFromPoint(pt);
209
210 ScreenToClient(parent, &pt);
211
212 child = ChildWindowFromPoint(parent, pt);
213
214 if (child != m_parentToolbar)
215 return TRUE;
216
217 ScreenToClient(m_parentToolbar, &msg->pt);
218
219 /* Don't do anything if the mouse has not been moved */
220 if (msg->pt.x == m_ptPrev.x && msg->pt.y == m_ptPrev.y)
221 return TRUE;
222
223 m_ptPrev = msg->pt;
224
225 iHitTestResult = SendMessageW(m_parentToolbar, TB_HITTEST, 0, (LPARAM) &msg->pt);
226
227 /* Make sure that iHitTestResult is one of the menu items and that it is not the current menu item */
228 if (iHitTestResult >= 0)
229 {
230 HWND hwndToolbar = m_parentToolbar;
231 if (SendMessage(hwndToolbar, WM_USER_ISTRACKEDITEM, iHitTestResult, 0))
232 {
233 DbgPrint("Hot item tracking detected a change...\n");
234 if (m_currentMenu)
235 SendMessage(m_currentFocus, WM_CANCELMODE, 0, 0);
236 else
237 m_currentBand->_MenuItemHotTrack(MPOS_CANCELLEVEL);
238 DbgPrint("Active popup cancelled, notifying of change...\n");
239 PostMessage(hwndToolbar, WM_USER_CHANGETRACKEDITEM, iHitTestResult, iHitTestResult);
240 return FALSE;
241 }
242 }
243
244 return TRUE;
245 }
246
247 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
248 {
249 if (nCode < 0)
250 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
251
252 if (nCode == MSGF_MENU)
253 {
254 BOOL callNext = TRUE;
255 MSG* msg = reinterpret_cast<MSG*>(lParam);
256
257 // Do whatever is necessary here
258
259 switch (msg->message)
260 {
261 case WM_MOUSEMOVE:
262 callNext = ProcessMouseMove(msg);
263 break;
264 }
265
266 if (!callNext)
267 return 0;
268 }
269
270 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
271 }
272
273 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
274 {
275 if (nCode < 0)
276 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
277
278 LPARAM pos = (LPARAM) GetMessagePos();
279
280 if (nCode == HC_ACTION)
281 {
282 BOOL callNext = TRUE;
283 MSG* msg = reinterpret_cast<MSG*>(lParam);
284
285 // Do whatever is necessary here
286
287 switch (msg->message)
288 {
289 case WM_CLOSE:
290 break;
291
292 case WM_NCLBUTTONDOWN:
293 case WM_LBUTTONDOWN:
294 {
295 POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
296
297 HWND window = GetAncestor(WindowFromPoint(pt), GA_ROOT);
298
299 if (IsTrackedWindow(window) != S_OK)
300 {
301 DisableMouseTrack(NULL, FALSE);
302 m_currentBand->_MenuItemHotTrack(MPOS_FULLCANCEL);
303 }
304
305 break;
306 }
307 case WM_MOUSEMOVE:
308 if (m_lastMoveFlags != wParam || m_lastMovePos != pos)
309 {
310 m_lastMoveFlags = wParam;
311 m_lastMovePos = pos;
312
313 POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
314
315 HWND window = WindowFromPoint(pt);
316
317 if (IsTrackedWindow(window) == S_OK)
318 {
319 DisableMouseTrack(window, FALSE);
320 }
321 else
322 {
323 DisableMouseTrack(NULL, FALSE);
324 }
325 }
326 callNext = ProcessMouseMove(msg);
327 break;
328 case WM_SYSKEYDOWN:
329 case WM_KEYDOWN:
330 //if (!m_currentMenu)
331 {
332 DisableMouseTrack(m_currentFocus, TRUE);
333 switch (msg->wParam)
334 {
335 case VK_MENU:
336 case VK_LMENU:
337 case VK_RMENU:
338 m_currentBand->_MenuItemHotTrack(MPOS_FULLCANCEL);
339 break;
340 case VK_LEFT:
341 m_currentBand->_MenuItemHotTrack(MPOS_SELECTLEFT);
342 break;
343 case VK_RIGHT:
344 m_currentBand->_MenuItemHotTrack(MPOS_SELECTRIGHT);
345 break;
346 case VK_UP:
347 m_currentBand->_MenuItemHotTrack(VK_UP);
348 break;
349 case VK_DOWN:
350 m_currentBand->_MenuItemHotTrack(VK_DOWN);
351 break;
352 }
353 }
354 break;
355 }
356
357 if (!callNext)
358 return 0;
359 }
360
361 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
362 }
363
364 HRESULT CMenuFocusManager::PlaceHooks()
365 {
366 //SetCapture(window);
367 if (m_currentMenu)
368 {
369 DbgPrint("Entering MSGFILTER hook...\n");
370 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
371 }
372 else
373 {
374 DbgPrint("Entering GETMESSAGE hook...\n");
375 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
376 }
377 return S_OK;
378 }
379
380 HRESULT CMenuFocusManager::RemoveHooks()
381 {
382 DbgPrint("Removing all hooks...\n");
383 if (m_hMsgFilterHook)
384 UnhookWindowsHookEx(m_hMsgFilterHook);
385 if (m_hGetMsgHook)
386 UnhookWindowsHookEx(m_hGetMsgHook);
387 m_hMsgFilterHook = NULL;
388 m_hGetMsgHook = NULL;
389 return S_OK;
390 }
391
392 HRESULT CMenuFocusManager::UpdateFocus(CMenuBand * newBand, HMENU popupToTrack)
393 {
394 HRESULT hr;
395 HWND newFocus = NULL;
396 HWND oldFocus = m_currentFocus;
397 HMENU oldMenu = m_currentMenu;
398
399 if (newBand)
400 {
401 hr = newBand->_GetTopLevelWindow(&newFocus);
402 if (FAILED_UNEXPECTEDLY(hr))
403 return hr;
404 }
405
406 m_currentBand = newBand;
407 m_currentMenu = popupToTrack;
408 m_currentFocus = newFocus;
409 m_parentToolbar = NULL;
410 if (popupToTrack)
411 {
412 m_currentBand->GetWindow(&m_parentToolbar);
413 }
414 else if (m_bandCount >= 2)
415 {
416 m_bandStack[m_bandCount - 2]->GetWindow(&m_parentToolbar);
417 }
418
419 if (oldFocus && (!newFocus || (oldMenu != popupToTrack)))
420 {
421 DisableMouseTrack(NULL, FALSE);
422
423 hr = RemoveHooks();
424 if (FAILED_UNEXPECTEDLY(hr))
425 return hr;
426 }
427
428 if (newFocus && (!oldFocus || (oldMenu != popupToTrack)))
429 {
430 hr = PlaceHooks();
431 if (FAILED_UNEXPECTEDLY(hr))
432 return hr;
433 }
434
435
436 return S_OK;
437 }
438
439 HRESULT CMenuFocusManager::PushMenu(CMenuBand * mb)
440 {
441 HRESULT hr;
442
443 CMenuBand * mbParent = m_currentBand;
444
445 hr = PushToArray(mb);
446 if (FAILED_UNEXPECTEDLY(hr))
447 return hr;
448
449 if (mbParent)
450 {
451 mbParent->_SetChildBand(mb);
452 mb->_SetParentBand(mbParent);
453 }
454
455 return UpdateFocus(mb);
456 }
457
458 HRESULT CMenuFocusManager::PopMenu(CMenuBand * mb)
459 {
460 CMenuBand * mbc;
461 HRESULT hr;
462
463 if (m_currentBand)
464 {
465 m_currentBand->_SetParentBand(NULL);
466 }
467
468 HWND newFocus;
469 hr = mb->_GetTopLevelWindow(&newFocus);
470 if (FAILED_UNEXPECTEDLY(hr))
471 return hr;
472
473 DbgPrint("Trying to pop %08p, hwnd=%08x\n", mb, newFocus);
474
475 do {
476 hr = PopFromArray(&mbc);
477 if (FAILED_UNEXPECTEDLY(hr))
478 {
479 UpdateFocus(NULL);
480 return hr;
481 }
482 }
483 while (mbc && mb != mbc);
484
485 if (!mbc)
486 return E_FAIL;
487
488 hr = PeekArray(&mb);
489 if (FAILED_UNEXPECTEDLY(hr))
490 return hr;
491
492 hr = UpdateFocus(mb);
493 if (FAILED_UNEXPECTEDLY(hr))
494 return hr;
495
496 if (mb)
497 {
498 mb->_SetChildBand(NULL);
499 }
500
501 return S_OK;
502 }
503
504 HRESULT CMenuFocusManager::PushTrackedPopup(CMenuBand * mb, HMENU popup)
505 {
506 HRESULT hr;
507
508 hr = PushToArray(mb);
509 if (FAILED_UNEXPECTEDLY(hr))
510 return hr;
511
512 return UpdateFocus(mb, popup);
513 }
514
515 HRESULT CMenuFocusManager::PopTrackedPopup(CMenuBand * mb, HMENU popup)
516 {
517 CMenuBand * mbc;
518 HRESULT hr;
519
520 HWND newFocus;
521 hr = mb->_GetTopLevelWindow(&newFocus);
522 if (FAILED_UNEXPECTEDLY(hr))
523 return hr;
524
525 DbgPrint("Trying to pop %08p, hwnd=%08x\n", mb, newFocus);
526
527 do {
528 hr = PopFromArray(&mbc);
529 if (FAILED_UNEXPECTEDLY(hr))
530 {
531 UpdateFocus(NULL);
532 return hr;
533 }
534 } while (mbc && mb != mbc);
535
536 if (!mbc)
537 return E_FAIL;
538
539 hr = PeekArray(&mb);
540 if (FAILED_UNEXPECTEDLY(hr))
541 return hr;
542
543 hr = UpdateFocus(mb);
544 if (FAILED_UNEXPECTEDLY(hr))
545 return hr;
546
547 return S_OK;
548 }