[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 RECT rcWorkArea;
304 GetWindowRect(GetDesktopWindow(), &rcWorkArea);
305 int waHeight = rcWorkArea.bottom - rcWorkArea.top;
306
307 int x = ppt->x;
308 int y = ppt->y;
309 int cx = rc.right - rc.left;
310 int cy = rc.bottom - rc.top;
311
312 switch (dwFlags & 0xFF000000)
313 {
314 case MPPF_BOTTOM:
315 x = ppt->x;
316 y = ppt->y - rc.bottom;
317 break;
318 case MPPF_RIGHT:
319 x = ppt->x + rc.left;
320 y = ppt->y + rc.top;
321 break;
322 case MPPF_TOP | MPPF_ALIGN_LEFT:
323 x = ppt->x - rc.right;
324 y = ppt->y + rc.top;
325 break;
326 case MPPF_TOP | MPPF_ALIGN_RIGHT:
327 x = ppt->x;
328 y = ppt->y + rc.top;
329 break;
330 }
331
332 if (x + cx > rcWorkArea.right)
333 {
334 // FIXME: Works, but it's oversimplified.
335 x = prcExclude->left - cx;
336 dwFlags = (dwFlags & (~MPPF_TOP)) | MPPF_LEFT;
337 }
338
339 if (y < rcWorkArea.top)
340 {
341 y = rcWorkArea.top;
342 }
343
344 if (cy > waHeight)
345 {
346 cy = waHeight;
347 }
348
349 if (y + cy > rcWorkArea.bottom)
350 {
351 y = rcWorkArea.bottom - cy;
352 }
353
354 this->SetWindowPos(HWND_TOPMOST, x, y, cx, cy, SWP_SHOWWINDOW | SWP_NOACTIVATE);
355
356 m_ShowFlags = dwFlags;
357 m_Shown = true;
358
359 // HACK: The bar needs to be notified of the size AFTER it is shown.
360 // Quick & dirty way of getting it done.
361 BOOL bHandled;
362 _OnSize(WM_SIZE, 0, 0, bHandled);
363
364 UIActivateIO(TRUE, NULL);
365
366 return S_OK;
367 }
368
369 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetIconSize(THIS_ DWORD iIcon)
370 {
371 HRESULT hr;
372 m_IconSize = iIcon;
373
374 // Unknown meaning (set flags? set icon size?)
375 const int CMD = 16;
376 const int CMD_EXEC_OPT = iIcon ? 0 : 2; // seems to work
377
378 hr = IUnknown_QueryServiceExec(m_Client, SID_SMenuBandChild, &CLSID_MenuBand, CMD, CMD_EXEC_OPT, NULL, NULL);
379 if (FAILED_UNEXPECTEDLY(hr))
380 return hr;
381
382 BOOL bHandled;
383 _OnSize(WM_SIZE, 0, 0, bHandled);
384
385 return hr;
386 }
387
388 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetIconSize(THIS_ DWORD* piIcon)
389 {
390 if (piIcon)
391 *piIcon = m_IconSize;
392 return S_OK;
393 }
394
395 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetBitmap(THIS_ HBITMAP hBitmap)
396 {
397 m_Banner = hBitmap;
398
399 BOOL bHandled;
400 _OnSize(WM_SIZE, 0, 0, bHandled);
401
402 return S_OK;
403 }
404
405 HRESULT STDMETHODCALLTYPE CMenuDeskBar::GetBitmap(THIS_ HBITMAP* phBitmap)
406 {
407 if (phBitmap)
408 *phBitmap = m_Banner;
409 return S_OK;
410 }
411
412 HRESULT STDMETHODCALLTYPE CMenuDeskBar::SetSubMenu(IMenuPopup *pmp, BOOL fSet)
413 {
414 // Called by the MenuBand to assign itself as the logical child of the DeskBar
415
416 if (fSet)
417 {
418 m_SubMenuChild = pmp;
419 }
420 else
421 {
422 if (m_SubMenuChild)
423 {
424 if (pmp == m_SubMenuChild)
425 {
426 m_SubMenuChild = NULL;
427 }
428 }
429 }
430 return S_OK;
431 }
432
433 HRESULT STDMETHODCALLTYPE CMenuDeskBar::OnSelect(DWORD dwSelectType)
434 {
435 /* As far as I can tell, the submenu hierarchy looks like this:
436 *
437 * The DeskBar's Child is the Band it contains.
438 * The DeskBar's Parent is the SID_SMenuPopup of the Site.
439 *
440 * The Band's Child is the IMenuPopup of the child submenu.
441 * The Band's Parent is the SID_SMenuPopup of the Site (the DeskBar).
442 *
443 * When the DeskBar receives a selection event:
444 * If it requires closing the window, it will notify the Child (Band) using CancelLevel.
445 * If it has to spread upwards (everything but CancelLevel), it will notify the Parent.
446 *
447 * When the Band receives a selection event, this is where it gets fuzzy:
448 * In which cases does it call the Parent? Probably not CancelLevel.
449 * In which cases does it call the Child?
450 * How does it react to calls?
451 *
452 */
453
454 switch (dwSelectType)
455 {
456 case MPOS_EXECUTE:
457 case MPOS_FULLCANCEL:
458 case MPOS_CANCELLEVEL:
459
460 _CloseBar();
461
462 if (dwSelectType == MPOS_CANCELLEVEL)
463 return S_OK;
464
465 case MPOS_SELECTLEFT:
466 case MPOS_SELECTRIGHT:
467 case MPOS_CHILDTRACKING:
468 if (m_SubMenuParent)
469 return m_SubMenuParent->OnSelect(dwSelectType);
470 break;
471 }
472
473 return S_OK;
474 }
475
476 HRESULT CMenuDeskBar::_CloseBar()
477 {
478 CComPtr<IDeskBarClient> dbc;
479 HRESULT hr;
480
481 m_Shown = false;
482
483 if (m_SubMenuChild)
484 {
485 hr = m_SubMenuChild->OnSelect(MPOS_CANCELLEVEL);
486 if (FAILED_UNEXPECTEDLY(hr))
487 return hr;
488 }
489
490 hr = m_Client->QueryInterface(IID_PPV_ARG(IDeskBarClient, &dbc));
491 if (FAILED_UNEXPECTEDLY(hr))
492 return hr;
493
494 hr = dbc->UIActivateDBC(FALSE);
495 if (FAILED_UNEXPECTEDLY(hr))
496 return hr;
497
498 SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE);
499
500 return UIActivateIO(FALSE, NULL);
501 }
502
503 BOOL CMenuDeskBar::_IsSubMenuParent(HWND hwnd)
504 {
505 CComPtr<IMenuPopup> popup = m_SubMenuParent;
506
507 while (popup)
508 {
509 HRESULT hr;
510 CComPtr<IOleWindow> window;
511
512 hr = popup->QueryInterface(IID_PPV_ARG(IOleWindow, &window));
513 if (FAILED_UNEXPECTEDLY(hr))
514 return FALSE;
515
516 HWND parent;
517
518 hr = window->GetWindow(&parent);
519 if (SUCCEEDED(hr) && hwnd == parent)
520 return TRUE;
521
522 popup = NULL;
523 hr = IUnknown_GetSite(window, IID_PPV_ARG(IMenuPopup, &popup));
524 if (FAILED(hr))
525 return FALSE;
526 }
527
528 return FALSE;
529 }
530
531 LRESULT CMenuDeskBar::_OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
532 {
533 if (m_Client)
534 {
535 RECT rc;
536
537 GetClientRect(&rc);
538
539 if (m_Banner != NULL)
540 {
541 BITMAP bm;
542 ::GetObject(m_Banner, sizeof(bm), &bm);
543 rc.left += bm.bmWidth;
544 }
545
546 ::SetWindowPos(m_ClientWindow, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, 0);
547 }
548
549 return 0;
550 }
551
552 LRESULT CMenuDeskBar::_OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
553 {
554 if (!m_Client)
555 return 0;
556
557 CComPtr<IWinEventHandler> winEventHandler;
558 HRESULT hr = m_Client->QueryInterface(IID_PPV_ARG(IWinEventHandler, &winEventHandler));
559 if (FAILED_UNEXPECTEDLY(hr))
560 return 0;
561
562 if (winEventHandler)
563 {
564 LRESULT result;
565 hr = winEventHandler->OnWinEvent(NULL, uMsg, wParam, lParam, &result);
566 if (FAILED_UNEXPECTEDLY(hr))
567 return 0;
568 return result;
569 }
570
571 return 0;
572 }
573
574 LRESULT CMenuDeskBar::_OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
575 {
576 bHandled = FALSE;
577
578 if (m_Banner && !m_IconSize)
579 {
580 BITMAP bm;
581 PAINTSTRUCT ps;
582 HDC hdc = BeginPaint(&ps);
583
584 HDC hdcMem = ::CreateCompatibleDC(hdc);
585 HGDIOBJ hbmOld = ::SelectObject(hdcMem, m_Banner);
586
587 ::GetObject(m_Banner, sizeof(bm), &bm);
588
589 RECT rc;
590 if (!GetClientRect(&rc))
591 WARN("GetClientRect failed\n");
592
593 const int bx = bm.bmWidth;
594 const int by = bm.bmHeight;
595 const int cy = rc.bottom;
596
597 TRACE("Painting banner: %d by %d\n", bm.bmWidth, bm.bmHeight);
598
599 if (!::StretchBlt(hdc, 0, 0, bx, cy - by, hdcMem, 0, 0, bx, 1, SRCCOPY))
600 WARN("StretchBlt failed\n");
601
602 if (!::BitBlt(hdc, 0, cy - by, bx, by, hdcMem, 0, 0, SRCCOPY))
603 WARN("BitBlt failed\n");
604
605 ::SelectObject(hdcMem, hbmOld);
606 ::DeleteDC(hdcMem);
607
608 EndPaint(&ps);
609 }
610
611 return TRUE;
612 }
613
614 LRESULT CMenuDeskBar::_OnActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
615 {
616 // BUG in ReactOS: WM_ACTIVATE/WA_INACTIVE makes no sense with lParam==hWnd
617 if (LOWORD(wParam) != 0 || reinterpret_cast<HWND>(lParam) == m_hWnd)
618 {
619 return 0;
620 }
621
622 // HACK! I just want it to work !!!
623 CComPtr<IDeskBar> db;
624 HRESULT hr = IUnknown_QueryService(m_Client, SID_SMenuBandChild, IID_PPV_ARG(IDeskBar, &db));
625 if (FAILED_UNEXPECTEDLY(hr))
626 return 0;
627
628 CComPtr<IUnknown> punk;
629
630 hr = db->GetClient(&punk);
631 if (FAILED_UNEXPECTEDLY(hr))
632 return 0;
633
634 if (!punk && m_Shown)
635 {
636 if (!_IsSubMenuParent(reinterpret_cast<HWND>(lParam)))
637 {
638 OnSelect(MPOS_FULLCANCEL);
639 }
640 }
641
642 return 0;
643 }
644
645 LRESULT CMenuDeskBar::_OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
646 {
647 return MA_NOACTIVATE;
648 }
649
650 LRESULT CMenuDeskBar::_OnAppActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
651 {
652 if (wParam == 0 && m_Shown)
653 {
654 OnSelect(MPOS_FULLCANCEL);
655 }
656 return 0;
657 }