4 * Copyright 2014 David Quintana
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.
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.
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
20 #include "shellmenu.h"
22 #include <shlwapi_undoc.h>
24 #include "CMenuDeskBar.h"
26 /* As far as I can tell, the submenu hierarchy looks like this:
28 * The DeskBar's Child is the Band it contains.
29 * The DeskBar's Parent is the SID_SMenuPopup of the Site.
31 * The Band's Child is the IMenuPopup of the child submenu.
32 * The Band's Parent is the SID_SMenuPopup of the Site (the DeskBar).
34 * When the DeskBar receives a selection event:
35 * If it requires closing the window, it will notify the Child (Band) using CancelLevel.
36 * If it has to spread upwards (everything but CancelLevel), it will notify the Parent.
38 * When the Band receives a selection event, this is where it gets fuzzy:
39 * In which cases does it call the Parent? Probably not CancelLevel.
40 * In which cases does it call the Child?
41 * How does it react to calls?
46 WINE_DEFAULT_DEBUG_CHANNEL(CMenuDeskBar
);
49 HRESULT WINAPI
CMenuDeskBar_Constructor(REFIID riid
, LPVOID
*ppv
)
51 return ShellObjectCreator
<CMenuDeskBar
>(riid
, ppv
);
54 CMenuDeskBar::CMenuDeskBar() :
65 CMenuDeskBar::~CMenuDeskBar()
69 LRESULT
CMenuDeskBar::_OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
81 void CMenuDeskBar::OnFinalMessage(HWND
/* hWnd */)
90 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::Initialize(THIS
)
95 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::GetWindow(HWND
*lphwnd
)
103 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::ContextSensitiveHelp(BOOL fEnterMode
)
108 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::OnFocusChangeIS(IUnknown
*punkObj
, BOOL fSetFocus
)
110 return IUnknown_OnFocusChangeIS(m_Client
, punkObj
, fSetFocus
);
113 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::QueryStatus(const GUID
*pguidCmdGroup
, ULONG cCmds
,
114 OLECMD prgCmds
[], OLECMDTEXT
*pCmdText
)
119 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::Exec(const GUID
*pguidCmdGroup
, DWORD nCmdID
,
120 DWORD nCmdexecopt
, VARIANT
*pvaIn
, VARIANT
*pvaOut
)
122 if (IsEqualIID(*pguidCmdGroup
, CGID_MenuDeskBar
))
128 case 3: // load complete
130 case 4: // set font metrics
131 return _AdjustForTheme(nCmdexecopt
);
134 if (IsEqualIID(*pguidCmdGroup
, CGID_Explorer
))
137 else if (IsEqualIID(*pguidCmdGroup
, IID_IDeskBarClient
))
153 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::QueryService(REFGUID guidService
, REFIID riid
, void **ppvObject
)
157 if (IsEqualGUID(guidService
, SID_SMenuPopup
) ||
158 IsEqualGUID(guidService
, SID_SMenuBandParent
) ||
159 IsEqualGUID(guidService
, SID_STopLevelBrowser
))
161 hr
= this->QueryInterface(riid
, ppvObject
);
166 if (IsEqualGUID(guidService
, SID_SMenuBandBottom
) ||
167 IsEqualGUID(guidService
, SID_SMenuBandBottomSelected
) ||
168 IsEqualGUID(guidService
, SID_SMenuBandChild
))
170 if (m_Client
== NULL
)
171 return E_NOINTERFACE
;
173 hr
= IUnknown_QueryService(m_Client
, guidService
, riid
, ppvObject
);
180 return E_NOINTERFACE
;
182 return IUnknown_QueryService(m_Site
, guidService
, riid
, ppvObject
);
185 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::UIActivateIO(BOOL fActivate
, LPMSG lpMsg
)
187 return IUnknown_UIActivateIO(m_Client
, fActivate
, lpMsg
);
190 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::HasFocusIO()
192 return IUnknown_HasFocusIO(m_Client
);
195 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::TranslateAcceleratorIO(LPMSG lpMsg
)
197 return IUnknown_TranslateAcceleratorIO(m_Client
, lpMsg
);
200 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::SetClient(IUnknown
*punkClient
)
202 CComPtr
<IDeskBarClient
> pDeskBandClient
;
207 hr
= m_Client
->QueryInterface(IID_PPV_ARG(IDeskBarClient
, &pDeskBandClient
));
208 if (FAILED_UNEXPECTEDLY(hr
))
211 pDeskBandClient
->SetDeskBarSite(NULL
);
213 pDeskBandClient
= NULL
;
217 if (punkClient
== NULL
)
225 hr
= punkClient
->QueryInterface(IID_PPV_ARG(IUnknown
, &m_Client
));
226 if (FAILED_UNEXPECTEDLY(hr
))
229 hr
= m_Client
->QueryInterface(IID_PPV_ARG(IDeskBarClient
, &pDeskBandClient
));
230 if (FAILED_UNEXPECTEDLY(hr
))
233 hr
= pDeskBandClient
->SetDeskBarSite(static_cast<IDeskBar
*>(this));
234 if (FAILED_UNEXPECTEDLY(hr
))
237 return IUnknown_GetWindow(m_Client
, &m_ClientWindow
);
240 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::GetClient(IUnknown
**ppunkClient
)
242 if (ppunkClient
== NULL
)
248 return m_Client
->QueryInterface(IID_PPV_ARG(IUnknown
, ppunkClient
));
251 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::OnPosRectChangeDB(LPRECT prc
)
259 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::SetSite(IUnknown
*pUnkSite
)
261 // Windows closes the bar if this is called when the bar is shown
266 m_SubMenuParent
= NULL
;
272 IUnknown_QueryService(m_Site
, SID_SMenuPopup
, IID_PPV_ARG(IMenuPopup
, &m_SubMenuParent
));
283 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::GetSite(REFIID riid
, void **ppvSite
)
288 return m_Site
->QueryInterface(riid
, ppvSite
);
291 static void AdjustForExcludeArea(BOOL alignLeft
, BOOL alignTop
, BOOL preferVertical
, PINT px
, PINT py
, INT cx
, INT cy
, RECTL rcExclude
) {
292 RECT rcWindow
= { *px
, *py
, *px
+ cx
, *py
+ cy
};
294 if (rcWindow
.right
> rcExclude
.left
&& rcWindow
.left
< rcExclude
.right
&&
295 rcWindow
.bottom
> rcExclude
.top
&& rcWindow
.top
< rcExclude
.bottom
)
299 if (alignTop
&& rcWindow
.bottom
> rcExclude
.top
)
300 *py
= rcExclude
.top
- cy
;
301 else if (!alignTop
&& rcWindow
.top
< rcExclude
.bottom
)
302 *py
= rcExclude
.bottom
;
303 else if (alignLeft
&& rcWindow
.right
> rcExclude
.left
)
304 *px
= rcExclude
.left
- cx
;
305 else if (!alignLeft
&& rcWindow
.left
< rcExclude
.right
)
306 *px
= rcExclude
.right
;
310 if (alignLeft
&& rcWindow
.right
> rcExclude
.left
)
311 *px
= rcExclude
.left
- cx
;
312 else if (!alignLeft
&& rcWindow
.left
< rcExclude
.right
)
313 *px
= rcExclude
.right
;
314 else if (alignTop
&& rcWindow
.bottom
> rcExclude
.top
)
315 *py
= rcExclude
.top
- cy
;
316 else if (!alignTop
&& rcWindow
.top
< rcExclude
.bottom
)
317 *py
= rcExclude
.bottom
;
322 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::Popup(POINTL
*ppt
, RECTL
*prcExclude
, MP_POPUPFLAGS dwFlags
)
325 CComPtr
<IOleCommandTarget
> oct
;
326 CComPtr
<IInputObject
> io
;
327 CComPtr
<IDeskBand
> band
;
328 CComPtr
<IDeskBarClient
> dbc
;
333 hr
= IUnknown_QueryService(m_Client
, SID_SMenuBandChild
, IID_PPV_ARG(IOleCommandTarget
, &oct
));
334 if (FAILED_UNEXPECTEDLY(hr
))
337 hr
= m_Client
->QueryInterface(IID_PPV_ARG(IDeskBarClient
, &dbc
));
338 if (FAILED_UNEXPECTEDLY(hr
))
341 // Windows calls this, but it appears to be unimplemented?
342 hr
= dbc
->SetModeDBC(1);
343 // Allow it to fail with E_NOTIMPL.
345 // No clue about the arg, using anything != 0
346 hr
= dbc
->UIActivateDBC(TRUE
);
347 if (FAILED_UNEXPECTEDLY(hr
))
351 hr
= dbc
->GetSize(0, &rc
);
352 if (FAILED_UNEXPECTEDLY(hr
))
357 const int CMD_EXEC_OPT
= 0;
359 hr
= IUnknown_QueryServiceExec(m_Client
, SID_SMenuBandChild
, &CLSID_MenuBand
, CMD
, CMD_EXEC_OPT
, NULL
, NULL
);
360 if (FAILED_UNEXPECTEDLY(hr
))
363 ::AdjustWindowRect(&rc
, ::GetWindowLong(m_hWnd
, GWL_STYLE
), FALSE
);
364 ::OffsetRect(&rc
, -rc
.left
, -rc
.top
);
366 if (m_Banner
!= NULL
)
369 ::GetObject(m_Banner
, sizeof(bm
), &bm
);
370 rc
.right
+= bm
.bmWidth
;
374 ::GetWindowRect(GetDesktopWindow(), &rcWorkArea
);
375 int cxWorkArea
= rcWorkArea
.right
- rcWorkArea
.left
;
376 int cyWorkArea
= rcWorkArea
.bottom
- rcWorkArea
.top
;
380 int cx
= rc
.right
- rc
.left
;
381 int cy
= rc
.bottom
- rc
.top
;
383 // TODO: Make alignLeft default to TRUE in LTR systems or whenever necessary.
384 BOOL alignLeft
= FALSE
;
385 BOOL alignTop
= FALSE
;
386 BOOL preferVertical
= FALSE
;
387 switch (dwFlags
& MPPF_POS_MASK
)
391 preferVertical
= TRUE
;
398 preferVertical
= TRUE
;
405 // Try the selected alignment and verify that it doesn't escape the work area.
425 AdjustForExcludeArea(alignLeft
, alignTop
, preferVertical
, &x
, &y
, cx
, cy
, *prcExclude
);
427 // Verify that it doesn't escape the work area, and flip.
430 if (x
< rcWorkArea
.left
&& (ppt
->x
+cx
) <= rcWorkArea
.right
)
434 x
= prcExclude
->right
- ((x
+ cx
) - prcExclude
->left
);
441 if ((ppt
->x
+ cx
) > rcWorkArea
.right
&& x
>= rcWorkArea
.left
)
445 x
= prcExclude
->left
- cx
+ (prcExclude
->right
- x
);
454 if (y
< rcWorkArea
.top
&& (ppt
->y
+ cy
) <= rcWorkArea
.bottom
)
458 y
= prcExclude
->bottom
- ((y
+ cy
) - prcExclude
->top
);
467 if ((ppt
->y
+ cy
) > rcWorkArea
.bottom
&& y
>= rcWorkArea
.top
)
471 y
= prcExclude
->top
- cy
+ (prcExclude
->bottom
- y
);
480 AdjustForExcludeArea(alignLeft
, alignTop
, preferVertical
, &x
, &y
, cx
, cy
, *prcExclude
);
482 if (x
< rcWorkArea
.left
)
488 if (x
+ cx
> rcWorkArea
.right
)
489 x
= rcWorkArea
.right
- cx
;
491 if (y
< rcWorkArea
.top
)
497 if (y
+ cy
> rcWorkArea
.bottom
)
498 y
= rcWorkArea
.bottom
- cy
;
500 int flags
= SWP_SHOWWINDOW
| SWP_NOACTIVATE
;
502 this->SetWindowPos(HWND_TOPMOST
, x
, y
, cx
, cy
, flags
);
506 if (dwFlags
& MPPF_INITIALSELECT
)
507 dwFlags
= (dwFlags
^ MPPF_INITIALSELECT
) | MPPF_FINALSELECT
;
508 else if (dwFlags
& MPPF_FINALSELECT
)
509 dwFlags
= (dwFlags
^ MPPF_FINALSELECT
) | MPPF_INITIALSELECT
;
512 m_ShowFlags
= dwFlags
;
515 // HACK: The bar needs to be notified of the size AFTER it is shown.
516 // Quick & dirty way of getting it done.
518 _OnSize(WM_SIZE
, 0, 0, bHandled
);
520 UIActivateIO(TRUE
, NULL
);
522 if (dwFlags
& (MPPF_INITIALSELECT
| MPPF_FINALSELECT
))
524 const int CMD_SELECT
= 5;
525 int CMD_SELECT_OPTS
= dwFlags
& MPPF_INITIALSELECT
? 0 : -2;
526 IUnknown_QueryServiceExec(m_Client
, SID_SMenuBandChild
, &CLSID_MenuBand
, CMD_SELECT
, CMD_SELECT_OPTS
, NULL
, NULL
);
532 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::SetIconSize(THIS_ DWORD iIcon
)
537 // Unknown meaning (set flags? set icon size?)
539 const int CMD_EXEC_OPT
= iIcon
? 0 : 2; // seems to work
541 hr
= IUnknown_QueryServiceExec(m_Client
, SID_SMenuBandChild
, &CLSID_MenuBand
, CMD
, CMD_EXEC_OPT
, NULL
, NULL
);
542 if (FAILED_UNEXPECTEDLY(hr
))
546 _OnSize(WM_SIZE
, 0, 0, bHandled
);
551 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::GetIconSize(THIS_ DWORD
* piIcon
)
554 *piIcon
= m_IconSize
;
558 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::SetBitmap(THIS_ HBITMAP hBitmap
)
563 _OnSize(WM_SIZE
, 0, 0, bHandled
);
568 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::GetBitmap(THIS_ HBITMAP
* phBitmap
)
571 *phBitmap
= m_Banner
;
575 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::SetSubMenu(IMenuPopup
*pmp
, BOOL fSet
)
577 // Called by the MenuBand to assign itself as the logical child of the DeskBar
581 m_SubMenuChild
= pmp
;
587 if (pmp
== m_SubMenuChild
)
589 m_SubMenuChild
= NULL
;
596 HRESULT STDMETHODCALLTYPE
CMenuDeskBar::OnSelect(DWORD dwSelectType
)
598 CComPtr
<IDeskBar
> safeThis
= this;
599 CComPtr
<IMenuPopup
> oldParent
= m_SubMenuParent
;
601 TRACE("OnSelect dwSelectType=%d\n", this, dwSelectType
);
602 switch (dwSelectType
)
605 case MPOS_FULLCANCEL
:
606 case MPOS_CANCELLEVEL
:
610 if (dwSelectType
== MPOS_CANCELLEVEL
)
613 case MPOS_SELECTLEFT
:
614 case MPOS_SELECTRIGHT
:
615 case MPOS_CHILDTRACKING
:
617 return oldParent
->OnSelect(dwSelectType
);
624 HRESULT
CMenuDeskBar::_CloseBar()
626 CComPtr
<IDeskBarClient
> dbc
;
629 // Ensure that our data isn't destroyed while we are working
630 CComPtr
<IDeskBar
> safeThis
= this;
636 m_SubMenuParent
->SetSubMenu(this, FALSE
);
641 hr
= m_SubMenuChild
->OnSelect(MPOS_CANCELLEVEL
);
642 if (FAILED_UNEXPECTEDLY(hr
))
646 hr
= m_Client
->QueryInterface(IID_PPV_ARG(IDeskBarClient
, &dbc
));
647 if (FAILED_UNEXPECTEDLY(hr
))
650 hr
= dbc
->UIActivateDBC(FALSE
);
651 if (FAILED_UNEXPECTEDLY(hr
))
654 SetWindowPos(NULL
, 0, 0, 0, 0, SWP_HIDEWINDOW
| SWP_NOACTIVATE
| SWP_NOMOVE
);
656 return UIActivateIO(FALSE
, NULL
);
659 BOOL
CMenuDeskBar::_IsSubMenuParent(HWND hwnd
)
661 CComPtr
<IMenuPopup
> popup
= m_SubMenuParent
;
668 hr
= IUnknown_GetWindow(popup
, &parent
);
669 if (FAILED_UNEXPECTEDLY(hr
))
674 hr
= IUnknown_GetSite(popup
, IID_PPV_ARG(IMenuPopup
, &popup
));
682 LRESULT
CMenuDeskBar::_OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
690 if (m_Banner
!= NULL
)
693 ::GetObject(m_Banner
, sizeof(bm
), &bm
);
694 rc
.left
+= bm
.bmWidth
;
697 ::SetWindowPos(m_ClientWindow
, NULL
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, 0);
703 LRESULT
CMenuDeskBar::_OnNotify(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
708 CComPtr
<IWinEventHandler
> winEventHandler
;
709 HRESULT hr
= m_Client
->QueryInterface(IID_PPV_ARG(IWinEventHandler
, &winEventHandler
));
710 if (FAILED_UNEXPECTEDLY(hr
))
716 hr
= winEventHandler
->OnWinEvent(NULL
, uMsg
, wParam
, lParam
, &result
);
717 if (FAILED_UNEXPECTEDLY(hr
))
725 LRESULT
CMenuDeskBar::_OnPaint(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
729 if (m_Banner
&& !m_IconSize
)
733 HDC hdc
= BeginPaint(&ps
);
735 HDC hdcMem
= ::CreateCompatibleDC(hdc
);
736 HGDIOBJ hbmOld
= ::SelectObject(hdcMem
, m_Banner
);
738 ::GetObject(m_Banner
, sizeof(bm
), &bm
);
741 if (!GetClientRect(&rc
))
742 WARN("GetClientRect failed\n");
744 const int bx
= bm
.bmWidth
;
745 const int by
= bm
.bmHeight
;
746 const int cy
= rc
.bottom
;
748 TRACE("Painting banner: %d by %d\n", bm
.bmWidth
, bm
.bmHeight
);
750 if (!::StretchBlt(hdc
, 0, 0, bx
, cy
- by
, hdcMem
, 0, 0, bx
, 1, SRCCOPY
))
751 WARN("StretchBlt failed\n");
753 if (!::BitBlt(hdc
, 0, cy
- by
, bx
, by
, hdcMem
, 0, 0, SRCCOPY
))
754 WARN("BitBlt failed\n");
756 ::SelectObject(hdcMem
, hbmOld
);
765 LRESULT
CMenuDeskBar::_OnActivate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
767 // BUG in ReactOS: WM_ACTIVATE/WA_INACTIVE makes no sense with lParam==hWnd
768 if (LOWORD(wParam
) != 0 || reinterpret_cast<HWND
>(lParam
) == m_hWnd
)
773 // HACK! I just want it to work !!!
774 CComPtr
<IDeskBar
> db
;
775 HRESULT hr
= IUnknown_QueryService(m_Client
, SID_SMenuBandChild
, IID_PPV_ARG(IDeskBar
, &db
));
776 if (FAILED_UNEXPECTEDLY(hr
))
779 CComPtr
<IUnknown
> punk
;
781 hr
= db
->GetClient(&punk
);
782 if (FAILED_UNEXPECTEDLY(hr
))
785 if (!punk
&& m_Shown
)
787 if (!_IsSubMenuParent(reinterpret_cast<HWND
>(lParam
)))
789 OnSelect(MPOS_FULLCANCEL
);
796 LRESULT
CMenuDeskBar::_OnMouseActivate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
798 return MA_NOACTIVATE
;
801 LRESULT
CMenuDeskBar::_OnAppActivate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
804 if (wParam
== 0 && m_Shown
)
806 OnSelect(MPOS_FULLCANCEL
);
812 LRESULT
CMenuDeskBar::_OnWinIniChange(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
814 if (wParam
== SPI_SETFLATMENU
)
815 return _OnNotify(uMsg
, wParam
, lParam
, bHandled
);
820 HRESULT
CMenuDeskBar::_AdjustForTheme(BOOL bFlatStyle
)
822 DWORD style
= bFlatStyle
? WS_BORDER
: WS_CLIPCHILDREN
|WS_DLGFRAME
;
823 DWORD mask
= WS_BORDER
|WS_CLIPCHILDREN
|WS_DLGFRAME
;
824 SHSetWindowBits(m_hWnd
, GWL_STYLE
, mask
, style
);