[RSHELL]
[reactos.git] / base / shell / rshell / CMenuDeskBar.cpp
1 /*
2 * Shell Menu Desk Bar
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 <atlwin.h>
22 #include <shlwapi_undoc.h>
23
24 #include "CMenuDeskBar.h"
25
26 WINE_DEFAULT_DEBUG_CHANNEL(CMenuDeskBar);
27
28 const static GUID CGID_MenuDeskBar = { 0x5C9F0A12, 0x959E, 0x11D0, { 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x08, 0x26, 0x36 } };
29
30 extern "C"
31 HRESULT WINAPI CMenuDeskBar_Constructor(REFIID riid, LPVOID *ppv)
32 {
33 *ppv = NULL;
34
35 CMenuDeskBar * deskbar = new CComObject<CMenuDeskBar>();
36
37 if (!deskbar)
38 return E_OUTOFMEMORY;
39
40 HRESULT hr = deskbar->QueryInterface(riid, ppv);
41
42 if (FAILED_UNEXPECTEDLY(hr))
43 deskbar->Release();
44
45 return hr;
46 }
47
48 CMenuDeskBar::CMenuDeskBar() :
49 m_Client(NULL),
50 m_Banner(NULL),
51 m_Shown(FALSE)
52 {
53 }
54
55 CMenuDeskBar::~CMenuDeskBar()
56 {
57 }
58
59 HRESULT STDMETHODCALLTYPE CMenuDeskBar::Initialize(THIS)
60 {
61 return S_OK;
62 }
63
64 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetWindow(HWND *lphwnd)
65 {
66 if (lphwnd == NULL)
67 return E_POINTER;
68 *lphwnd = m_hWnd;
69 return S_OK;
70 }
71
72 HRESULT STDMETHODCALLTYPE CMenuDeskBar::ContextSensitiveHelp(BOOL fEnterMode)
73 {
74 return E_NOTIMPL;
75 }
76
77 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnFocusChangeIS(IUnknown *punkObj, BOOL fSetFocus)
78 {
79 CComPtr<IInputObjectSite> ios;
80
81 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IInputObjectSite, &ios));
82 if (FAILED_UNEXPECTEDLY(hr))
83 return hr;
84
85 return ios->OnFocusChangeIS(punkObj, fSetFocus);
86 }
87
88 HRESULT STDMETHODCALLTYPE CMenuDeskBar::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
89 OLECMD prgCmds [], OLECMDTEXT *pCmdText)
90 {
91 return E_NOTIMPL;
92 }
93
94 HRESULT STDMETHODCALLTYPE CMenuDeskBar::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
95 DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
96 {
97 if (IsEqualIID(*pguidCmdGroup, CGID_MenuDeskBar))
98 {
99 switch (nCmdID)
100 {
101 case 2: // refresh
102 return S_OK;
103 case 3: // load complete
104 return S_OK;
105 case 4: // set font metrics
106 return S_OK;
107 }
108 }
109 if (IsEqualIID(*pguidCmdGroup, CGID_Explorer))
110 {
111 }
112 else if (IsEqualIID(*pguidCmdGroup, IID_IDeskBarClient))
113 {
114 switch (nCmdID)
115 {
116 case 0:
117 // hide current band
118 break;
119 case 2:
120 break;
121 case 3:
122 break;
123 }
124 }
125 return E_NOTIMPL;
126 }
127
128 HRESULT STDMETHODCALLTYPE CMenuDeskBar::QueryService(REFGUID guidService, REFIID riid, void **ppvObject)
129 {
130 if (IsEqualGUID(guidService, SID_SMenuPopup) ||
131 IsEqualGUID(guidService, SID_SMenuBandParent) ||
132 IsEqualGUID(guidService, SID_STopLevelBrowser))
133 {
134 return this->QueryInterface(riid, ppvObject);
135 }
136
137 if (IsEqualGUID(guidService, SID_SMenuBandBottom) ||
138 IsEqualGUID(guidService, SID_SMenuBandBottomSelected) ||
139 IsEqualGUID(guidService, SID_SMenuBandChild))
140 {
141 if (m_Client == NULL)
142 return E_NOINTERFACE;
143
144 return IUnknown_QueryService(m_Client, guidService, riid, ppvObject);
145 }
146
147
148 if (m_Site == NULL)
149 return E_NOINTERFACE;
150
151 return IUnknown_QueryService(m_Site, guidService, riid, ppvObject);
152 }
153
154 HRESULT STDMETHODCALLTYPE CMenuDeskBar::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
155 {
156 return IUnknown_UIActivateIO(m_Client, fActivate, lpMsg);
157 }
158
159 HRESULT STDMETHODCALLTYPE CMenuDeskBar::HasFocusIO()
160 {
161 CComPtr<IInputObject> io;
162
163 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IInputObject, &io));
164 if (FAILED_UNEXPECTEDLY(hr))
165 return hr;
166
167 return io->HasFocusIO();
168 }
169
170 HRESULT STDMETHODCALLTYPE CMenuDeskBar::TranslateAcceleratorIO(LPMSG lpMsg)
171 {
172 CComPtr<IInputObject> io;
173
174 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IInputObject, &io));
175 if (FAILED_UNEXPECTEDLY(hr))
176 return hr;
177
178 return io->TranslateAcceleratorIO(lpMsg);
179 }
180
181 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetClient(IUnknown *punkClient)
182 {
183 CComPtr<IDeskBarClient> pDeskBandClient;
184 HRESULT hr;
185
186 m_Client.Release();
187
188 if (punkClient == NULL)
189 return S_OK;
190
191 if (m_hWnd == NULL)
192 {
193 Create(NULL);
194 }
195
196 hr = punkClient->QueryInterface(IID_PPV_ARG(IUnknown, &m_Client));
197 if (FAILED_UNEXPECTEDLY(hr))
198 return hr;
199
200 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDeskBandClient));
201 if (FAILED_UNEXPECTEDLY(hr))
202 return hr;
203
204 hr = pDeskBandClient->SetDeskBarSite(static_cast<IDeskBar*>(this));
205 if (FAILED_UNEXPECTEDLY(hr))
206 return hr;
207
208 return IUnknown_GetWindow(m_Client, &m_ClientWindow);
209 }
210
211 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetClient(IUnknown **ppunkClient)
212 {
213 if (ppunkClient == NULL)
214 return E_POINTER;
215
216 if (!m_Client)
217 return E_FAIL;
218
219 return m_Client->QueryInterface(IID_PPV_ARG(IUnknown, ppunkClient));
220 }
221
222 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnPosRectChangeDB(LPRECT prc)
223 {
224 if (prc == NULL)
225 return E_POINTER;
226
227 return S_OK;
228 }
229
230 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetSite(IUnknown *pUnkSite)
231 {
232 // Windows closes the bar if this is called when the bar is shown
233
234 if (m_Shown)
235 _CloseBar();
236
237 m_Site = pUnkSite;
238
239 IUnknown_QueryService(m_Site, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &m_SubMenuParent));
240
241 return S_OK;
242 }
243
244 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetSite(REFIID riid, void **ppvSite)
245 {
246 if (m_Site == NULL)
247 return E_FAIL;
248
249 return m_Site->QueryInterface(riid, ppvSite);
250 }
251
252 HRESULT STDMETHODCALLTYPE CMenuDeskBar::Popup(POINTL *ppt, RECTL *prcExclude, MP_POPUPFLAGS dwFlags)
253 {
254 HRESULT hr;
255 CComPtr<IOleCommandTarget> oct;
256 CComPtr<IInputObject> io;
257 CComPtr<IDeskBand> band;
258 CComPtr<IDeskBarClient> dbc;
259
260 if (m_hWnd == NULL)
261 return E_FAIL;
262
263 hr = IUnknown_QueryService(m_Client, SID_SMenuBandChild, IID_PPV_ARG(IOleCommandTarget, &oct));
264 if (FAILED_UNEXPECTEDLY(hr))
265 return hr;
266
267 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &dbc));
268 if (FAILED_UNEXPECTEDLY(hr))
269 return hr;
270
271 // Windows calls this, but it appears to be unimplemented?
272 hr = dbc->SetModeDBC(1);
273 // Allow it to fail with E_NOTIMPL.
274
275 // No clue about the arg, using anything != 0
276 hr = dbc->UIActivateDBC(TRUE);
277 if (FAILED_UNEXPECTEDLY(hr))
278 return hr;
279
280 RECT rc = { 0 };
281 hr = dbc->GetSize(0, &rc);
282 if (FAILED_UNEXPECTEDLY(hr))
283 return hr;
284
285 // Unknown meaning
286 const int CMD = 19;
287 const int CMD_EXEC_OPT = 0;
288
289 hr = IUnknown_QueryServiceExec(m_Client, SID_SMenuBandChild, &CLSID_MenuBand, CMD, CMD_EXEC_OPT, NULL, NULL);
290 if (FAILED_UNEXPECTEDLY(hr))
291 return hr;
292
293 ::AdjustWindowRect(&rc, ::GetWindowLong(m_hWnd, GWL_STYLE), FALSE);
294 ::OffsetRect(&rc, -rc.left, -rc.top);
295
296 if (m_Banner != NULL)
297 {
298 BITMAP bm;
299 ::GetObject(m_Banner, sizeof(bm), &bm);
300 rc.right += bm.bmWidth;
301 }
302
303 int x, y, cx, cy;
304
305 RECT rcWorkArea;
306 GetWindowRect(GetDesktopWindow(), &rcWorkArea);
307 int waHeight = rcWorkArea.bottom - rcWorkArea.top;
308
309 switch (dwFlags & 0xFF000000)
310 {
311 case MPPF_BOTTOM:
312 x = ppt->x;
313 cx = rc.right - rc.left;
314 y = ppt->y - rc.bottom;
315 cy = rc.bottom - rc.top;
316 break;
317 case MPPF_RIGHT:
318 x = ppt->x + rc.left;
319 cx = rc.right - rc.left;
320 y = ppt->y + rc.top;
321 cy = rc.bottom - rc.top;
322 break;
323 case MPPF_TOP | MPPF_ALIGN_LEFT:
324 x = ppt->x - rc.right;
325 cx = rc.right - rc.left;
326 y = ppt->y + rc.top;
327 cy = rc.bottom - rc.top;
328 break;
329 case MPPF_TOP | MPPF_ALIGN_RIGHT:
330 x = ppt->x;
331 cx = rc.right - rc.left;
332 y = ppt->y + rc.top;
333 cy = rc.bottom - rc.top;
334 break;
335 }
336
337 if (x + cx > rcWorkArea.right)
338 {
339 // FIXME: Works, but it's oversimplified.
340 x = prcExclude->left - cx;
341 dwFlags = (dwFlags & (~MPPF_TOP)) | MPPF_LEFT;
342 }
343
344 if (y < rcWorkArea.top)
345 {
346 y = rcWorkArea.top;
347 }
348
349 if (cy > waHeight)
350 {
351 cy = waHeight;
352 }
353
354 if (y + cy > rcWorkArea.bottom)
355 {
356 y = rcWorkArea.bottom - cy;
357 }
358
359 this->SetWindowPos(HWND_TOPMOST, x, y, cx, cy, SWP_SHOWWINDOW | SWP_NOACTIVATE);
360
361 m_ShowFlags = dwFlags;
362 m_Shown = true;
363
364 // HACK: The bar needs to be notified of the size AFTER it is shown.
365 // Quick & dirty way of getting it done.
366 BOOL bHandled;
367 _OnSize(WM_SIZE, 0, 0, bHandled);
368
369 UIActivateIO(TRUE, NULL);
370
371 return S_OK;
372 }
373
374 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetIconSize(THIS_ DWORD iIcon)
375 {
376 HRESULT hr;
377 m_IconSize = iIcon;
378
379 // Unknown meaning (set flags? set icon size?)
380 const int CMD = 16;
381 const int CMD_EXEC_OPT = iIcon ? 0 : 2; // seems to work
382
383 hr = IUnknown_QueryServiceExec(m_Client, SID_SMenuBandChild, &CLSID_MenuBand, CMD, CMD_EXEC_OPT, NULL, NULL);
384 if (FAILED_UNEXPECTEDLY(hr))
385 return hr;
386
387 BOOL bHandled;
388 _OnSize(WM_SIZE, 0, 0, bHandled);
389
390 return hr;
391 }
392
393 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetIconSize(THIS_ DWORD* piIcon)
394 {
395 if (piIcon)
396 *piIcon = m_IconSize;
397 return S_OK;
398 }
399
400 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetBitmap(THIS_ HBITMAP hBitmap)
401 {
402 m_Banner = hBitmap;
403
404 BOOL bHandled;
405 _OnSize(WM_SIZE, 0, 0, bHandled);
406
407 return S_OK;
408 }
409
410 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetBitmap(THIS_ HBITMAP* phBitmap)
411 {
412 if (phBitmap)
413 *phBitmap = m_Banner;
414 return S_OK;
415 }
416
417 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetSubMenu(IMenuPopup *pmp, BOOL fSet)
418 {
419 // Called by the MenuBand to assign itself as the logical child of the DeskBar
420
421 if (fSet)
422 {
423 m_SubMenuChild = pmp;
424 }
425 else
426 {
427 if (m_SubMenuChild)
428 {
429 if (pmp == m_SubMenuChild)
430 {
431 m_SubMenuChild = NULL;
432 }
433 }
434 }
435 return S_OK;
436 }
437
438 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnSelect(DWORD dwSelectType)
439 {
440 /* As far as I can tell, the submenu hierarchy looks like this:
441
442 The DeskBar's Child is the Band it contains.
443 The DeskBar's Parent is the SID_SMenuPopup of the Site.
444
445 The Band's Child is the IMenuPopup of the child submenu.
446 The Band's Parent is the SID_SMenuPopup of the Site (the DeskBar).
447
448 When the DeskBar receives a selection event:
449 If it requires closing the window, it will notify the Child (Band) using CancelLevel.
450 If it has to spread upwards (everything but CancelLevel), it will notify the Parent.
451
452 When the Band receives a selection event, this is where it gets fuzzy:
453 In which cases does it call the Parent? Probably not CancelLevel.
454 In which cases does it call the Child?
455 How does it react to calls?
456
457 */
458
459 switch (dwSelectType)
460 {
461 case MPOS_EXECUTE:
462 case MPOS_FULLCANCEL:
463 case MPOS_CANCELLEVEL:
464
465 _CloseBar();
466
467 if (dwSelectType == MPOS_CANCELLEVEL)
468 return S_OK;
469
470 case MPOS_SELECTLEFT:
471 case MPOS_SELECTRIGHT:
472 case MPOS_CHILDTRACKING:
473 if (m_SubMenuParent)
474 return m_SubMenuParent->OnSelect(dwSelectType);
475 break;
476 }
477
478 return S_OK;
479 }
480
481 HRESULT CMenuDeskBar::_CloseBar()
482 {
483 CComPtr<IDeskBarClient> dbc;
484 HRESULT hr;
485
486 m_Shown = false;
487
488 if (m_SubMenuChild)
489 {
490 hr = m_SubMenuChild->OnSelect(MPOS_CANCELLEVEL);
491 if (FAILED_UNEXPECTEDLY(hr))
492 return hr;
493 }
494
495 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &dbc));
496 if (FAILED_UNEXPECTEDLY(hr))
497 return hr;
498
499 hr = dbc->UIActivateDBC(FALSE);
500 if (FAILED_UNEXPECTEDLY(hr))
501 return hr;
502
503 SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE);
504
505 return UIActivateIO(FALSE, NULL);
506 }
507
508 BOOL CMenuDeskBar::_IsSubMenuParent(HWND hwnd)
509 {
510 CComPtr<IMenuPopup> popup = m_SubMenuParent;
511
512 while (popup)
513 {
514 HRESULT hr;
515 CComPtr<IOleWindow> window;
516
517 hr = popup->QueryInterface(IID_PPV_ARG(IOleWindow, &window));
518 if (FAILED_UNEXPECTEDLY(hr))
519 return FALSE;
520
521 HWND parent;
522
523 hr = window->GetWindow(&parent);
524 if (SUCCEEDED(hr) && hwnd == parent)
525 return TRUE;
526
527 popup = NULL;
528 hr = IUnknown_GetSite(window, IID_PPV_ARG(IMenuPopup, &popup));
529 if (FAILED(hr))
530 return FALSE;
531 }
532
533 return FALSE;
534 }
535
536 LRESULT CMenuDeskBar::_OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
537 {
538 if (m_Client)
539 {
540 RECT rc;
541
542 GetClientRect(&rc);
543
544 if (m_Banner != NULL)
545 {
546 BITMAP bm;
547 ::GetObject(m_Banner, sizeof(bm), &bm);
548 rc.left += bm.bmWidth;
549 }
550
551 ::SetWindowPos(m_ClientWindow, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, 0);
552 }
553
554 return 0;
555 }
556
557 LRESULT CMenuDeskBar::_OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
558 {
559 if (!m_Client)
560 return 0;
561
562 CComPtr<IWinEventHandler> winEventHandler;
563 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IWinEventHandler, &winEventHandler));
564 if (FAILED_UNEXPECTEDLY(hr))
565 return 0;
566
567 if (winEventHandler)
568 {
569 LRESULT result;
570 hr = winEventHandler->OnWinEvent(NULL, uMsg, wParam, lParam, &result);
571 if (FAILED_UNEXPECTEDLY(hr))
572 return 0;
573 return result;
574 }
575
576 return 0;
577 }
578
579 LRESULT CMenuDeskBar::_OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
580 {
581 bHandled = FALSE;
582
583 if (m_Banner && !m_IconSize)
584 {
585 BITMAP bm;
586 PAINTSTRUCT ps;
587 HDC hdc = BeginPaint(&ps);
588
589 HDC hdcMem = ::CreateCompatibleDC(hdc);
590 HGDIOBJ hbmOld = ::SelectObject(hdcMem, m_Banner);
591
592 ::GetObject(m_Banner, sizeof(bm), &bm);
593
594 RECT rc;
595 if (!GetClientRect(&rc))
596 WARN("GetClientRect failed\n");
597
598 const int bx = bm.bmWidth;
599 const int by = bm.bmHeight;
600 const int cy = rc.bottom;
601
602 TRACE("Painting banner: %d by %d\n", bm.bmWidth, bm.bmHeight);
603
604 if (!::StretchBlt(hdc, 0, 0, bx, cy - by, hdcMem, 0, 0, bx, 1, SRCCOPY))
605 WARN("StretchBlt failed\n");
606
607 if (!::BitBlt(hdc, 0, cy - by, bx, by, hdcMem, 0, 0, SRCCOPY))
608 WARN("BitBlt failed\n");
609
610 ::SelectObject(hdcMem, hbmOld);
611 ::DeleteDC(hdcMem);
612
613 EndPaint(&ps);
614 }
615
616 return TRUE;
617 }
618
619 LRESULT CMenuDeskBar::_OnActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
620 {
621 // BUG in ReactOS: WM_ACTIVATE/WA_INACTIVE makes no sense with lParam==hWnd
622 if (LOWORD(wParam) != 0 || reinterpret_cast<HWND>(lParam) == m_hWnd)
623 {
624 return 0;
625 }
626
627 // HACK! I just want it to work !!!
628 CComPtr<IDeskBar> db;
629 HRESULT hr = IUnknown_QueryService(m_Client, SID_SMenuBandChild, IID_PPV_ARG(IDeskBar, &db));
630 if (FAILED_UNEXPECTEDLY(hr))
631 return 0;
632
633 CComPtr<IUnknown> punk;
634
635 hr = db->GetClient(&punk);
636 if (FAILED_UNEXPECTEDLY(hr))
637 return 0;
638
639 if (!punk && m_Shown)
640 {
641 if (!_IsSubMenuParent(reinterpret_cast<HWND>(lParam)))
642 {
643 OnSelect(MPOS_FULLCANCEL);
644 }
645 }
646
647 return 0;
648 }
649
650 LRESULT CMenuDeskBar::_OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
651 {
652 return MA_NOACTIVATE;
653 }
654
655 LRESULT CMenuDeskBar::_OnAppActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
656 {
657 if (wParam == 0 && m_Shown)
658 {
659 OnSelect(MPOS_FULLCANCEL);
660 }
661 return 0;
662 }