[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 }