[SHLWAPI]
[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 (m_Site == NULL)
138 return E_NOINTERFACE;
139
140 return IUnknown_QueryService(m_Site, guidService, riid, ppvObject);
141 }
142
143 HRESULT STDMETHODCALLTYPE CMenuDeskBar::UIActivateIO(BOOL fActivate, LPMSG lpMsg)
144 {
145 return IUnknown_UIActivateIO(m_Client, fActivate, lpMsg);
146 }
147
148 HRESULT STDMETHODCALLTYPE CMenuDeskBar::HasFocusIO()
149 {
150 CComPtr<IInputObject> io;
151
152 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IInputObject, &io));
153 if (FAILED_UNEXPECTEDLY(hr))
154 return hr;
155
156 return io->HasFocusIO();
157 }
158
159 HRESULT STDMETHODCALLTYPE CMenuDeskBar::TranslateAcceleratorIO(LPMSG lpMsg)
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->TranslateAcceleratorIO(lpMsg);
168 }
169
170 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetClient(IUnknown *punkClient)
171 {
172 CComPtr<IDeskBarClient> pDeskBandClient;
173 HRESULT hr;
174
175 m_Client.Release();
176
177 if (punkClient == NULL)
178 return S_OK;
179
180 if (m_hWnd == NULL)
181 {
182 Create(NULL);
183 }
184
185 hr = punkClient->QueryInterface(IID_PPV_ARG(IUnknown, &m_Client));
186 if (FAILED_UNEXPECTEDLY(hr))
187 return hr;
188
189 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDeskBandClient));
190 if (FAILED_UNEXPECTEDLY(hr))
191 return hr;
192
193 hr = pDeskBandClient->SetDeskBarSite(static_cast<IDeskBar*>(this));
194 if (FAILED_UNEXPECTEDLY(hr))
195 return hr;
196
197 return IUnknown_GetWindow(m_Client, &m_ClientWindow);
198 }
199
200 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetClient(IUnknown **ppunkClient)
201 {
202 if (ppunkClient == NULL)
203 return E_POINTER;
204
205 if (!m_Client)
206 return E_FAIL;
207
208 return m_Client->QueryInterface(IID_PPV_ARG(IUnknown, ppunkClient));
209 }
210
211 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnPosRectChangeDB(LPRECT prc)
212 {
213 if (prc == NULL)
214 return E_POINTER;
215
216 return S_OK;
217 }
218
219 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetSite(IUnknown *pUnkSite)
220 {
221 // Windows closes the bar if this is called when the bar is shown
222
223 if (m_Shown)
224 _CloseBar();
225
226 m_Site = pUnkSite;
227
228 IUnknown_QueryService(m_Site, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &m_SubMenuParent));
229
230 return S_OK;
231 }
232
233 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetSite(REFIID riid, void **ppvSite)
234 {
235 if (m_Site == NULL)
236 return E_FAIL;
237
238 return m_Site->QueryInterface(riid, ppvSite);
239 }
240
241 HRESULT STDMETHODCALLTYPE CMenuDeskBar::Popup(POINTL *ppt, RECTL *prcExclude, MP_POPUPFLAGS dwFlags)
242 {
243 HRESULT hr;
244 CComPtr<IOleCommandTarget> oct;
245 CComPtr<IInputObject> io;
246 CComPtr<IDeskBand> band;
247 CComPtr<IDeskBarClient> dbc;
248
249 if (m_hWnd == NULL)
250 return E_FAIL;
251
252 hr = IUnknown_QueryService(m_Client, SID_SMenuBandChild, IID_PPV_ARG(IOleCommandTarget, &oct));
253 if (FAILED_UNEXPECTEDLY(hr))
254 return hr;
255
256 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &dbc));
257 if (FAILED_UNEXPECTEDLY(hr))
258 return hr;
259
260 // Windows calls this, but it appears to be unimplemented?
261 hr = dbc->SetModeDBC(1);
262 // Allow it to fail with E_NOTIMPL.
263
264 // No clue about the arg, using anything != 0
265 hr = dbc->UIActivateDBC(TRUE);
266 if (FAILED_UNEXPECTEDLY(hr))
267 return hr;
268
269 RECT rc = { 0 };
270 hr = dbc->GetSize(0, &rc);
271 if (FAILED_UNEXPECTEDLY(hr))
272 return hr;
273
274 // Unknown meaning
275 const int CMD = 19;
276 const int CMD_EXEC_OPT = 0;
277
278 hr = IUnknown_QueryServiceExec(m_Client, SID_SMenuBandChild, &CLSID_MenuBand, CMD, CMD_EXEC_OPT, NULL, NULL);
279 if (FAILED_UNEXPECTEDLY(hr))
280 return hr;
281
282 ::AdjustWindowRect(&rc, ::GetWindowLong(m_hWnd, GWL_STYLE), FALSE);
283 ::OffsetRect(&rc, -rc.left, -rc.top);
284
285 if (m_Banner != NULL)
286 {
287 BITMAP bm;
288 ::GetObject(m_Banner, sizeof(bm), &bm);
289 rc.right += bm.bmWidth;
290 }
291
292 int x, y, cx, cy;
293
294 RECT rcWorkArea;
295 GetWindowRect(GetDesktopWindow(), &rcWorkArea);
296 int waHeight = rcWorkArea.bottom - rcWorkArea.top;
297
298 switch (dwFlags & 0xFF000000)
299 {
300 case MPPF_BOTTOM:
301 x = ppt->x;
302 cx = rc.right - rc.left;
303 y = ppt->y - rc.bottom;
304 cy = rc.bottom - rc.top;
305 break;
306 case MPPF_RIGHT:
307 x = ppt->x + rc.left;
308 cx = rc.right - rc.left;
309 y = ppt->y + rc.top;
310 cy = rc.bottom - rc.top;
311 break;
312 case MPPF_TOP | MPPF_ALIGN_LEFT:
313 x = ppt->x - rc.right;
314 cx = rc.right - rc.left;
315 y = ppt->y + rc.top;
316 cy = rc.bottom - rc.top;
317 break;
318 case MPPF_TOP | MPPF_ALIGN_RIGHT:
319 x = ppt->x;
320 cx = rc.right - rc.left;
321 y = ppt->y + rc.top;
322 cy = rc.bottom - rc.top;
323 break;
324 }
325
326 if (x + cx > rcWorkArea.right)
327 {
328 // FIXME: Works, but it's oversimplified.
329 x = prcExclude->left - cx;
330 dwFlags = (dwFlags & (~MPPF_TOP)) | MPPF_LEFT;
331 }
332
333 if (y < rcWorkArea.top)
334 {
335 y = rcWorkArea.top;
336 }
337
338 if (cy > waHeight)
339 {
340 cy = waHeight;
341 }
342
343 if (y + cy > rcWorkArea.bottom)
344 {
345 y = rcWorkArea.bottom - cy;
346 }
347
348
349
350 this->SetWindowPos(HWND_TOPMOST, x, y, cx, cy, SWP_SHOWWINDOW);
351
352 m_ShowFlags = dwFlags;
353 m_Shown = true;
354
355 // HACK: The bar needs to be notified of the size AFTER it is shown.
356 // Quick & dirty way of getting it done.
357 BOOL bHandled;
358 _OnSize(WM_SIZE, 0, 0, bHandled);
359
360 UIActivateIO(TRUE, NULL);
361
362 return S_OK;
363 }
364
365 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetIconSize(THIS_ DWORD iIcon)
366 {
367 HRESULT hr;
368 m_IconSize = iIcon;
369
370 // Unknown meaning (set flags? set icon size?)
371 const int CMD = 16;
372 const int CMD_EXEC_OPT = iIcon ? 0 : 2; // seems to work
373
374 hr = IUnknown_QueryServiceExec(m_Client, SID_SMenuBandChild, &CLSID_MenuBand, CMD, CMD_EXEC_OPT, NULL, NULL);
375 if (FAILED_UNEXPECTEDLY(hr))
376 return hr;
377
378 BOOL bHandled;
379 _OnSize(WM_SIZE, 0, 0, bHandled);
380
381 return hr;
382 }
383
384 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetIconSize(THIS_ DWORD* piIcon)
385 {
386 if (piIcon)
387 *piIcon = m_IconSize;
388 return S_OK;
389 }
390
391 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetBitmap(THIS_ HBITMAP hBitmap)
392 {
393 m_Banner = hBitmap;
394
395 BOOL bHandled;
396 _OnSize(WM_SIZE, 0, 0, bHandled);
397
398 return S_OK;
399 }
400
401 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetBitmap(THIS_ HBITMAP* phBitmap)
402 {
403 if (phBitmap)
404 *phBitmap = m_Banner;
405 return S_OK;
406 }
407
408 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetSubMenu(IMenuPopup *pmp, BOOL fSet)
409 {
410 // Called by the MenuBand to assign itself as the logical child of the DeskBar
411
412 if (fSet)
413 {
414 m_SubMenuChild = pmp;
415 }
416 else
417 {
418 if (m_SubMenuChild)
419 {
420 if (SHIsSameObject(pmp, m_SubMenuChild))
421 {
422 m_SubMenuChild = NULL;
423 }
424 }
425 }
426 return S_OK;
427 }
428
429 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnSelect(DWORD dwSelectType)
430 {
431 /* As far as I can tell, the submenu hierarchy looks like this:
432
433 The DeskBar's Child is the Band it contains.
434 The DeskBar's Parent is the SID_SMenuPopup of the Site.
435
436 The Band's Child is the IMenuPopup of the child submenu.
437 The Band's Parent is the SID_SMenuPopup of the Site (the DeskBar).
438
439 When the DeskBar receives a selection event:
440 If it requires closing the window, it will notify the Child (Band) using CancelLevel.
441 If it has to spread upwards (everything but CancelLevel), it will notify the Parent.
442
443 When the Band receives a selection event, this is where it gets fuzzy:
444 In which cases does it call the Parent? Probably not CancelLevel.
445 In which cases does it call the Child?
446 How does it react to calls?
447
448 */
449
450 switch (dwSelectType)
451 {
452 case MPOS_EXECUTE:
453 case MPOS_FULLCANCEL:
454 case MPOS_CANCELLEVEL:
455
456 _CloseBar();
457
458 if (dwSelectType == MPOS_CANCELLEVEL)
459 return S_OK;
460
461 case MPOS_SELECTLEFT:
462 case MPOS_SELECTRIGHT:
463 case MPOS_CHILDTRACKING:
464 if (m_SubMenuParent)
465 return m_SubMenuParent->OnSelect(dwSelectType);
466 break;
467 }
468
469 return S_OK;
470 }
471
472 HRESULT CMenuDeskBar::_CloseBar()
473 {
474 CComPtr<IDeskBarClient> dbc;
475 HRESULT hr;
476
477 m_Shown = false;
478
479 if (m_SubMenuChild)
480 {
481 hr = m_SubMenuChild->OnSelect(MPOS_CANCELLEVEL);
482 if (FAILED_UNEXPECTEDLY(hr))
483 return hr;
484 }
485
486 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &dbc));
487 if (FAILED_UNEXPECTEDLY(hr))
488 return hr;
489
490 hr = dbc->UIActivateDBC(FALSE);
491 if (FAILED_UNEXPECTEDLY(hr))
492 return hr;
493
494 SetWindowPos(m_hWnd, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
495
496 return UIActivateIO(FALSE, NULL);
497 }
498
499 BOOL CMenuDeskBar::_IsSubMenuParent(HWND hwnd)
500 {
501 CComPtr<IMenuPopup> popup = m_SubMenuParent;
502
503 while (popup)
504 {
505 HRESULT hr;
506 CComPtr<IOleWindow> window;
507
508 hr = popup->QueryInterface(IID_PPV_ARG(IOleWindow, &window));
509 if (FAILED_UNEXPECTEDLY(hr))
510 return FALSE;
511
512 HWND parent;
513
514 hr = window->GetWindow(&parent);
515 if (SUCCEEDED(hr) && hwnd == parent)
516 return TRUE;
517
518 popup = NULL;
519 hr = IUnknown_GetSite(window, IID_PPV_ARG(IMenuPopup, &popup));
520 if (FAILED(hr))
521 return FALSE;
522 }
523
524 return FALSE;
525 }
526
527 LRESULT CMenuDeskBar::_OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
528 {
529 if (m_Client)
530 {
531 RECT rc;
532
533 GetClientRect(&rc);
534
535 if (m_Banner != NULL)
536 {
537 BITMAP bm;
538 ::GetObject(m_Banner, sizeof(bm), &bm);
539 rc.left += bm.bmWidth;
540 }
541
542 ::SetWindowPos(m_ClientWindow, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, 0);
543 }
544
545 return 0;
546 }
547
548 LRESULT CMenuDeskBar::_OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
549 {
550 if (!m_Client)
551 return 0;
552
553 CComPtr<IWinEventHandler> winEventHandler;
554 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IWinEventHandler, &winEventHandler));
555 if (FAILED_UNEXPECTEDLY(hr))
556 return 0;
557
558 if (winEventHandler)
559 {
560 LRESULT result;
561 hr = winEventHandler->OnWinEvent(NULL, uMsg, wParam, lParam, &result);
562 if (FAILED_UNEXPECTEDLY(hr))
563 return 0;
564 return result;
565 }
566
567 return 0;
568 }
569
570 LRESULT CMenuDeskBar::_OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
571 {
572 bHandled = FALSE;
573
574 if (m_Banner && !m_IconSize)
575 {
576 BITMAP bm;
577 PAINTSTRUCT ps;
578 HDC hdc = BeginPaint(&ps);
579
580 HDC hdcMem = ::CreateCompatibleDC(hdc);
581 HGDIOBJ hbmOld = ::SelectObject(hdcMem, m_Banner);
582
583 ::GetObject(m_Banner, sizeof(bm), &bm);
584
585 RECT rc;
586 if (!GetClientRect(&rc))
587 WARN("GetClientRect failed\n");
588
589 const int bx = bm.bmWidth;
590 const int by = bm.bmHeight;
591 const int cy = rc.bottom;
592
593 TRACE("Painting banner: %d by %d\n", bm.bmWidth, bm.bmHeight);
594
595 if (!::StretchBlt(hdc, 0, 0, bx, cy - by, hdcMem, 0, 0, bx, 1, SRCCOPY))
596 WARN("StretchBlt failed\n");
597
598 if (!::BitBlt(hdc, 0, cy - by, bx, by, hdcMem, 0, 0, SRCCOPY))
599 WARN("BitBlt failed\n");
600
601 ::SelectObject(hdcMem, hbmOld);
602 ::DeleteDC(hdcMem);
603
604 EndPaint(&ps);
605 }
606
607 return TRUE;
608 }
609
610 LRESULT CMenuDeskBar::_OnActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
611 {
612 if (wParam != 0)
613 return 0;
614
615 // HACK! I just want it to work !!!
616 CComPtr<IDeskBar> db;
617 HRESULT hr = IUnknown_QueryService(m_Client, SID_SMenuBandChild, IID_PPV_ARG(IDeskBar, &db));
618 if (FAILED_UNEXPECTEDLY(hr))
619 return 0;
620
621 CComPtr<IUnknown> punk;
622
623 hr = db->GetClient(&punk);
624 if (FAILED_UNEXPECTEDLY(hr))
625 return 0;
626
627 if (!punk && m_Shown)
628 {
629 if (!_IsSubMenuParent(reinterpret_cast<HWND>(lParam)))
630 {
631 OnSelect(MPOS_FULLCANCEL);
632 }
633 }
634
635 return 0;
636 }
637
638 LRESULT CMenuDeskBar::_OnAppActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
639 {
640 if (wParam == 0)
641 {
642 OnSelect(MPOS_FULLCANCEL);
643 }
644 return 0;
645 }