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