[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 return S_OK;
192 }
193
194 return S_FALSE;
195 }
196
197 LRESULT CMenuFocusManager::MsgFilterHook(INT nCode, WPARAM wParam, LPARAM lParam)
198 {
199 HWND parent;
200 HWND child;
201 POINT pt;
202 int iHitTestResult;
203
204 if (nCode < 0)
205 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
206
207 if (nCode == MSGF_MENU)
208 {
209 BOOL callNext = TRUE;
210 MSG* msg = reinterpret_cast<MSG*>(lParam);
211
212 // Do whatever is necessary here
213
214 switch (msg->message)
215 {
216 case WM_MOUSEMOVE:
217
218 pt = msg->pt;
219
220 parent = WindowFromPoint(pt);
221
222 ScreenToClient(parent, &pt);
223
224 child = ChildWindowFromPoint(parent, pt);
225
226 if (child != m_parentToolbar)
227 break;
228
229 ScreenToClient(m_parentToolbar, &msg->pt);
230
231 /* Don't do anything if the mouse has not been moved */
232 if (msg->pt.x == m_ptPrev.x && msg->pt.y == m_ptPrev.y)
233 return TRUE;
234
235 m_ptPrev = msg->pt;
236
237 iHitTestResult = SendMessageW(m_parentToolbar, TB_HITTEST, 0, (LPARAM) &msg->pt);
238
239 /* Make sure that iHitTestResult is one of the menu items and that it is not the current menu item */
240 if (iHitTestResult >= 0)
241 {
242 if (SendMessage(m_parentToolbar, WM_USER_ISTRACKEDITEM, iHitTestResult, 0))
243 {
244 SendMessage(m_currentFocus, WM_CANCELMODE, 0, 0);
245 PostMessage(m_parentToolbar, WM_USER_CHANGETRACKEDITEM, iHitTestResult, iHitTestResult);
246 return TRUE;
247 }
248 }
249 break;
250 }
251
252 if (!callNext)
253 return 0;
254 }
255
256 return CallNextHookEx(m_hMsgFilterHook, nCode, wParam, lParam);
257 }
258
259 LRESULT CMenuFocusManager::GetMsgHook(INT nCode, WPARAM wParam, LPARAM lParam)
260 {
261 if (nCode < 0)
262 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
263
264 LPARAM pos = (LPARAM) GetMessagePos();
265
266 if (nCode == HC_ACTION)
267 {
268 BOOL callNext = TRUE;
269 MSG* msg = reinterpret_cast<MSG*>(lParam);
270
271 // Do whatever is necessary here
272
273 switch (msg->message)
274 {
275 case WM_CLOSE:
276 break;
277
278 case WM_NCLBUTTONDOWN:
279 case WM_LBUTTONDOWN:
280 {
281 POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
282
283 HWND window = GetAncestor(WindowFromPoint(pt), GA_ROOT);
284
285 if (IsTrackedWindow(window) != S_OK)
286 {
287 DisableMouseTrack(NULL, FALSE);
288 m_currentBand->_MenuItemHotTrack(MPOS_FULLCANCEL);
289 }
290
291 break;
292 }
293 case WM_MOUSEMOVE:
294 if (m_lastMoveFlags != wParam || m_lastMovePos != pos)
295 {
296 m_lastMoveFlags = wParam;
297 m_lastMovePos = pos;
298
299 POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
300
301 HWND window = WindowFromPoint(pt);
302
303 if (IsTrackedWindow(window) == S_OK)
304 {
305 DisableMouseTrack(window, FALSE);
306 }
307 else
308 {
309 DisableMouseTrack(NULL, FALSE);
310 }
311 }
312 break;
313 case WM_SYSKEYDOWN:
314 case WM_KEYDOWN:
315 //if (!m_currentMenu)
316 {
317 DisableMouseTrack(m_currentFocus, TRUE);
318 switch (msg->wParam)
319 {
320 case VK_MENU:
321 case VK_LMENU:
322 case VK_RMENU:
323 m_currentBand->_MenuItemHotTrack(MPOS_FULLCANCEL);
324 break;
325 case VK_LEFT:
326 m_currentBand->_MenuItemHotTrack(MPOS_SELECTLEFT);
327 break;
328 case VK_RIGHT:
329 m_currentBand->_MenuItemHotTrack(MPOS_SELECTRIGHT);
330 break;
331 case VK_UP:
332 m_currentBand->_MenuItemHotTrack(VK_UP);
333 break;
334 case VK_DOWN:
335 m_currentBand->_MenuItemHotTrack(VK_DOWN);
336 break;
337 }
338 }
339 break;
340 }
341
342 if (!callNext)
343 return 0;
344 }
345
346 return CallNextHookEx(m_hGetMsgHook, nCode, wParam, lParam);
347 }
348
349 HRESULT CMenuFocusManager::PlaceHooks()
350 {
351 //SetCapture(window);
352 if (m_currentMenu)
353 {
354 m_hMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, s_MsgFilterHook, NULL, m_threadId);
355 }
356 else
357 {
358 m_hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, s_GetMsgHook, NULL, m_threadId);
359 }
360 return S_OK;
361 }
362
363 HRESULT CMenuFocusManager::RemoveHooks()
364 {
365 if (m_hMsgFilterHook)
366 UnhookWindowsHookEx(m_hMsgFilterHook);
367 if (m_hGetMsgHook)
368 UnhookWindowsHookEx(m_hGetMsgHook);
369 m_hMsgFilterHook = NULL;
370 m_hGetMsgHook = NULL;
371 return S_OK;
372 }
373
374 HRESULT CMenuFocusManager::UpdateFocus(CMenuBand * newBand, HMENU popupToTrack)
375 {
376 HRESULT hr;
377 HWND newFocus = NULL;
378 HWND oldFocus = m_currentFocus;
379 HMENU oldMenu = m_currentMenu;
380
381 if (newBand)
382 {
383 hr = newBand->_GetTopLevelWindow(&newFocus);
384 if (FAILED_UNEXPECTEDLY(hr))
385 return hr;
386 }
387
388 m_currentBand = newBand;
389 m_currentMenu = popupToTrack;
390 m_currentFocus = newFocus;
391 if (m_currentMenu)
392 {
393 m_currentBand->GetWindow(&m_parentToolbar);
394 }
395
396 if (oldFocus && (!newFocus || (oldMenu != popupToTrack)))
397 {
398 DisableMouseTrack(NULL, FALSE);
399
400 hr = RemoveHooks();
401 if (FAILED_UNEXPECTEDLY(hr))
402 return hr;
403 }
404
405 if (newFocus && (!oldFocus || (oldMenu != popupToTrack)))
406 {
407 hr = PlaceHooks();
408 if (FAILED_UNEXPECTEDLY(hr))
409 return hr;
410 }
411
412
413 return S_OK;
414 }
415
416 HRESULT CMenuFocusManager::PushMenu(CMenuBand * mb)
417 {
418 HRESULT hr;
419
420 CMenuBand * mbParent = m_currentBand;
421
422 hr = PushToArray(mb);
423 if (FAILED_UNEXPECTEDLY(hr))
424 return hr;
425
426 if (mbParent)
427 {
428 mbParent->SetClient(static_cast<IMenuPopup*>(mb));
429 }
430
431 return UpdateFocus(mb);
432 }
433
434 HRESULT CMenuFocusManager::PopMenu(CMenuBand * mb)
435 {
436 CMenuBand * mbc;
437 HRESULT hr;
438
439 HWND newFocus;
440 hr = mb->_GetTopLevelWindow(&newFocus);
441 if (FAILED_UNEXPECTEDLY(hr))
442 return hr;
443
444 DbgPrint("Trying to pop %08p, hwnd=%08x\n", mb, newFocus);
445
446 do {
447 hr = PopFromArray(&mbc);
448 if (FAILED_UNEXPECTEDLY(hr))
449 {
450 UpdateFocus(NULL);
451 return hr;
452 }
453 }
454 while (mbc && mb != mbc);
455
456 if (!mbc)
457 return E_FAIL;
458
459 hr = PeekArray(&mb);
460 if (FAILED_UNEXPECTEDLY(hr))
461 return hr;
462
463 hr = UpdateFocus(mb);
464 if (FAILED_UNEXPECTEDLY(hr))
465 return hr;
466
467 if (mb)
468 {
469 mb->SetClient(NULL);
470 }
471
472 return S_OK;
473 }
474
475 HRESULT CMenuFocusManager::PushTrackedPopup(CMenuBand * mb, HMENU popup)
476 {
477 HRESULT hr;
478
479 hr = PushToArray(mb);
480 if (FAILED_UNEXPECTEDLY(hr))
481 return hr;
482
483 return UpdateFocus(mb, popup);
484 }
485
486 HRESULT CMenuFocusManager::PopTrackedPopup(CMenuBand * mb, HMENU popup)
487 {
488 CMenuBand * mbc;
489 HRESULT hr;
490
491 HWND newFocus;
492 hr = mb->_GetTopLevelWindow(&newFocus);
493 if (FAILED_UNEXPECTEDLY(hr))
494 return hr;
495
496 DbgPrint("Trying to pop %08p, hwnd=%08x\n", mb, newFocus);
497
498 do {
499 hr = PopFromArray(&mbc);
500 if (FAILED_UNEXPECTEDLY(hr))
501 {
502 UpdateFocus(NULL);
503 return hr;
504 }
505 } while (mbc && mb != mbc);
506
507 if (!mbc)
508 return E_FAIL;
509
510 hr = PeekArray(&mb);
511 if (FAILED_UNEXPECTEDLY(hr))
512 return hr;
513
514 hr = UpdateFocus(mb);
515 if (FAILED_UNEXPECTEDLY(hr))
516 return hr;
517
518 if (mb)
519 {
520 mb->SetClient(NULL);
521 }
522
523 return S_OK;
524 }