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