2654 lines
74 KiB
C++
2654 lines
74 KiB
C++
#include "stdafx.h"
|
|
#include <startids.h> // for IDM_PROGRAMS et al
|
|
#include "regstr.h"
|
|
#include "rcids.h"
|
|
#include <desktray.h>
|
|
#include "tray.h"
|
|
#include "startmnu.h"
|
|
#include "hostutil.h"
|
|
#include "deskhost.h"
|
|
#include "shdguid.h"
|
|
|
|
#define REGSTR_EXPLORER_ADVANCED REGSTR_PATH_EXPLORER TEXT("\\Advanced")
|
|
|
|
#define TF_DV2HOST 0
|
|
// #define TF_DV2HOST TF_CUSTOM1
|
|
|
|
#define TF_DV2DIALOG 0
|
|
// #define TF_DV2DIALOG TF_CUSTOM1
|
|
|
|
EXTERN_C HINSTANCE hinstCabinet;
|
|
HRESULT StartMenuHost_Create(IMenuPopup** ppmp, IMenuBand** ppmb);
|
|
void RegisterDesktopControlClasses();
|
|
|
|
const WCHAR c_wzStartMenuTheme[] = L"StartMenu";
|
|
|
|
//*****************************************************************
|
|
|
|
CPopupMenu::~CPopupMenu()
|
|
{
|
|
IUnknown_SetSite(_pmp, NULL);
|
|
ATOMICRELEASE(_pmp);
|
|
ATOMICRELEASE(_pmb);
|
|
ATOMICRELEASE(_psm);
|
|
}
|
|
|
|
HRESULT CPopupMenu::Popup(RECT *prcExclude, DWORD dwFlags)
|
|
{
|
|
COMPILETIME_ASSERT(sizeof(RECT) == sizeof(RECTL));
|
|
return _pmp->Popup((POINTL*)prcExclude, (RECTL*)prcExclude, dwFlags);
|
|
}
|
|
|
|
|
|
HRESULT CPopupMenu::Initialize(IShellMenu *psm, IUnknown *punkSite, HWND hwnd)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// We should have been zero-initialized
|
|
ASSERT(_pmp == NULL);
|
|
ASSERT(_pmb == NULL);
|
|
ASSERT(_psm == NULL);
|
|
|
|
hr = CoCreateInstance(CLSID_MenuDeskBar, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IMenuPopup, &_pmp));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IUnknown_SetSite(_pmp, punkSite);
|
|
|
|
IBandSite *pbs;
|
|
hr = CoCreateInstance(CLSID_MenuBandSite, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IBandSite, &pbs));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _pmp->SetClient(pbs);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IDeskBand *pdb;
|
|
if (SUCCEEDED(psm->QueryInterface(IID_PPV_ARG(IDeskBand, &pdb))))
|
|
{
|
|
hr = pbs->AddBand(pdb);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwBandID;
|
|
hr = pbs->EnumBands(0, &dwBandID);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pbs->GetBandObject(dwBandID, IID_PPV_ARG(IMenuBand, &_pmb));
|
|
}
|
|
}
|
|
pdb->Release();
|
|
}
|
|
}
|
|
pbs->Release();
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Failure to set the theme is nonfatal
|
|
IShellMenu2* psm2;
|
|
if (SUCCEEDED(psm->QueryInterface(IID_PPV_ARG(IShellMenu2, &psm2))))
|
|
{
|
|
BOOL fThemed = IsAppThemed();
|
|
psm2->SetTheme(fThemed ? c_wzStartMenuTheme : NULL);
|
|
psm2->SetNoBorder(fThemed ? TRUE : FALSE);
|
|
psm2->Release();
|
|
}
|
|
|
|
// Tell the popup that we are the window to parent UI on
|
|
// This will fail on purpose so don't freak out
|
|
psm->SetMenu(NULL, hwnd, 0);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_psm = psm;
|
|
psm->AddRef();
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CPopupMenu_CreateInstance(IShellMenu *psm,
|
|
IUnknown *punkSite,
|
|
HWND hwnd,
|
|
CPopupMenu **ppmOut)
|
|
{
|
|
HRESULT hr;
|
|
*ppmOut = NULL;
|
|
CPopupMenu *ppm = new CPopupMenu();
|
|
if (ppm)
|
|
{
|
|
hr = ppm->Initialize(psm, punkSite, hwnd);
|
|
if (FAILED(hr))
|
|
{
|
|
ppm->Release();
|
|
}
|
|
else
|
|
{
|
|
*ppmOut = ppm; // transfer ownership to called
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//*****************************************************************
|
|
|
|
const STARTPANELMETRICS g_spmDefault = {
|
|
{380,440},
|
|
{
|
|
{WC_USERPANE, 0, SPP_USERPANE, {380, 40}, NULL, NULL},
|
|
{WC_SFTBARHOST, WS_TABSTOP, SPP_PROGLIST, {190, 330}, NULL, NULL},
|
|
{WC_MOREPROGRAMS, 0, SPP_MOREPROGRAMS, {190, 30}, NULL, NULL},
|
|
{WC_SFTBARHOST, 0, SPP_PLACESLIST, {190, 360}, NULL, NULL},
|
|
{WC_LOGOFF, 0, SPP_LOGOFF, {380, 40}, NULL, NULL},
|
|
}
|
|
};
|
|
|
|
HRESULT
|
|
CDesktopHost::Initialize()
|
|
{
|
|
ASSERT(_hwnd == NULL);
|
|
|
|
//
|
|
// Load some settings.
|
|
//
|
|
_fAutoCascade = SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("Start_AutoCascade"), FALSE, TRUE);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CDesktopHost::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CDesktopHost, IMenuPopup),
|
|
QITABENT(CDesktopHost, IDeskBar), // IMenuPopup derives from IDeskBar
|
|
QITABENTMULTI(CDesktopHost, IOleWindow, IMenuPopup), // IDeskBar derives from IOleWindow
|
|
|
|
QITABENT(CDesktopHost, IMenuBand),
|
|
QITABENT(CDesktopHost, IServiceProvider),
|
|
QITABENT(CDesktopHost, IOleCommandTarget),
|
|
QITABENT(CDesktopHost, IObjectWithSite),
|
|
|
|
QITABENT(CDesktopHost, ITrayPriv), // going away
|
|
QITABENT(CDesktopHost, ITrayPriv2), // going away
|
|
{ 0 },
|
|
};
|
|
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
HRESULT CDesktopHost::SetSite(IUnknown *punkSite)
|
|
{
|
|
CObjectWithSite::SetSite(punkSite);
|
|
if (!_punkSite)
|
|
{
|
|
// This is our cue to break the recursive reference loop
|
|
// The _ppmpPrograms contains multiple backreferences to
|
|
// the CDesktopHost (we are its site, it also references
|
|
// us via CDesktopShellMenuCallback...)
|
|
ATOMICRELEASE(_ppmPrograms);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
CDesktopHost::~CDesktopHost()
|
|
{
|
|
if (_hbmCachedSnapshot)
|
|
{
|
|
DeleteObject(_hbmCachedSnapshot);
|
|
}
|
|
|
|
ATOMICRELEASE(_ppmPrograms);
|
|
ATOMICRELEASE(_ppmTracking);
|
|
|
|
if (_hwnd)
|
|
{
|
|
ASSERT(GetWindowThreadProcessId(_hwnd, NULL) == GetCurrentThreadId());
|
|
DestroyWindow(_hwnd);
|
|
}
|
|
ATOMICRELEASE(_ptFader);
|
|
|
|
}
|
|
|
|
BOOL CDesktopHost::Register()
|
|
{
|
|
_wmDragCancel = RegisterWindowMessage(TEXT("CMBDragCancel"));
|
|
|
|
WNDCLASSEX wndclass;
|
|
|
|
wndclass.cbSize = sizeof(wndclass);
|
|
wndclass.style = CS_DROPSHADOW;
|
|
wndclass.lpfnWndProc = WndProc;
|
|
wndclass.cbClsExtra = 0;
|
|
wndclass.cbWndExtra = 0;
|
|
wndclass.hInstance = hinstCabinet;
|
|
wndclass.hIcon = NULL;
|
|
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wndclass.hbrBackground = GetStockBrush(HOLLOW_BRUSH);
|
|
wndclass.lpszMenuName = NULL;
|
|
wndclass.lpszClassName = WC_DV2;
|
|
wndclass.hIconSm = NULL;
|
|
|
|
return (0 != RegisterClassEx(&wndclass));
|
|
}
|
|
|
|
inline int _ClipCoord(int x, int xMin, int xMax)
|
|
{
|
|
if (x < xMin) x = xMin;
|
|
if (x > xMax) x = xMax;
|
|
return x;
|
|
}
|
|
|
|
//
|
|
// Everybody conspires against us.
|
|
//
|
|
// CTray does not pass us any MPPF_POS_MASK flags to tell us where we
|
|
// need to pop up relative to the point, so there's no point looking
|
|
// at the dwFlags parameter. Which is for the better, I guess, because
|
|
// the MPPF_* flags are not the same as the TPM_* flags. Go figure.
|
|
//
|
|
// And then the designers decided that the Start Menu should pop up
|
|
// in a location different from the location that the standard
|
|
// TrackPopupMenuEx function chooses, so we need a custom positioning
|
|
// algorithm anyway.
|
|
//
|
|
// And finally, the AnimateWindow function takes AW_* flags, which are
|
|
// not the same as TPM_*ANIMATE flags. Go figure. But since we gave up
|
|
// on trying to map IMenuPopup::Popup to TrackPopupMenuEx anyway, we
|
|
// don't have to do any translation here anyway.
|
|
//
|
|
// Returns animation direction.
|
|
//
|
|
void CDesktopHost::_ChoosePopupPosition(POINT *ppt, LPCRECT prcExclude, LPRECT prcWindow)
|
|
{
|
|
//
|
|
// Calculate the monitor BEFORE we adjust the point. Otherwise, we might
|
|
// move the point offscreen. In which case, we will end up pinning the
|
|
// popup to the primary display, which is wron_
|
|
//
|
|
HMONITOR hmon = MonitorFromPoint(*ppt, MONITOR_DEFAULTTONEAREST);
|
|
MONITORINFO minfo;
|
|
minfo.cbSize = sizeof(minfo);
|
|
GetMonitorInfo(hmon, &minfo);
|
|
|
|
// Clip the exclude rectangle to the monitor
|
|
|
|
RECT rcExclude;
|
|
if (prcExclude)
|
|
{
|
|
// We can't use IntersectRect because it turns the rectangle
|
|
// into (0,0,0,0) if the intersection is empty (which can happen if
|
|
// the taskbar is autohide) but we want to glue it to the nearest
|
|
// valid edge.
|
|
rcExclude.left = _ClipCoord(prcExclude->left, minfo.rcMonitor.left, minfo.rcMonitor.right);
|
|
rcExclude.right = _ClipCoord(prcExclude->right, minfo.rcMonitor.left, minfo.rcMonitor.right);
|
|
rcExclude.top = _ClipCoord(prcExclude->top, minfo.rcMonitor.top, minfo.rcMonitor.bottom);
|
|
rcExclude.bottom = _ClipCoord(prcExclude->bottom, minfo.rcMonitor.top, minfo.rcMonitor.bottom);
|
|
}
|
|
else
|
|
{
|
|
rcExclude.left = rcExclude.right = ppt->x;
|
|
rcExclude.top = rcExclude.bottom = ppt->y;
|
|
}
|
|
|
|
_ComputeActualSize(&minfo, &rcExclude);
|
|
|
|
// initialize the height and width from what the layout asked for
|
|
int cy=RECTHEIGHT(_rcActual);
|
|
int cx=RECTWIDTH(_rcActual);
|
|
|
|
ASSERT(cx && cy); // we're in trouble if these are zero
|
|
|
|
int x, y;
|
|
|
|
//
|
|
// First: Determine whether we are going to pop upwards or downwards.
|
|
//
|
|
|
|
BOOL fSide = FALSE;
|
|
|
|
if (rcExclude.top - cy >= minfo.rcMonitor.top)
|
|
{
|
|
// There is room above.
|
|
y = rcExclude.top - cy;
|
|
}
|
|
else if (rcExclude.bottom - cy >= minfo.rcMonitor.top)
|
|
{
|
|
// There is room above if we slide to the side.
|
|
y = rcExclude.bottom - cy;
|
|
fSide = TRUE;
|
|
}
|
|
else if (rcExclude.bottom + cy <= minfo.rcMonitor.bottom)
|
|
{
|
|
// There is room below.
|
|
y = rcExclude.bottom;
|
|
}
|
|
else if (rcExclude.top + cy <= minfo.rcMonitor.bottom)
|
|
{
|
|
// There is room below if we slide to the side.
|
|
y = rcExclude.top;
|
|
fSide = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// We don't fit anywhere. Pin to the appropriate edge of the screen.
|
|
// And we have to go to the side, too.
|
|
fSide = TRUE;
|
|
|
|
if (rcExclude.top - minfo.rcMonitor.top < minfo.rcMonitor.bottom - rcExclude.bottom)
|
|
{
|
|
// Start button at top of screen; pin to top
|
|
y = minfo.rcMonitor.top;
|
|
}
|
|
else
|
|
{
|
|
// Start button at bottom of screen; pin to bottom
|
|
y = minfo.rcMonitor.bottom - cy;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now choose whether we will pop left or right. Try right first.
|
|
//
|
|
|
|
x = fSide ? rcExclude.right : rcExclude.left;
|
|
if (x + cx > minfo.rcMonitor.right)
|
|
{
|
|
// Doesn't fit to the right; pin to the right edge.
|
|
// Notice that we do *not* try to pop left. For some reason,
|
|
// the start menu never pops left.
|
|
|
|
x = minfo.rcMonitor.right - cx;
|
|
}
|
|
|
|
SetRect(prcWindow, x, y, x+cx, y+cy);
|
|
}
|
|
|
|
int GetDesiredHeight(HWND hwndHost, SMPANEDATA *psmpd)
|
|
{
|
|
SMNGETMINSIZE nmgms = {0};
|
|
nmgms.hdr.hwndFrom = hwndHost;
|
|
nmgms.hdr.code = SMN_GETMINSIZE;
|
|
nmgms.siz = psmpd->size;
|
|
|
|
SendMessage(psmpd->hwnd, WM_NOTIFY, nmgms.hdr.idFrom, (LPARAM)&nmgms);
|
|
|
|
return nmgms.siz.cy;
|
|
}
|
|
|
|
//
|
|
// Query each item to see if it has any size requirements.
|
|
// Position all the items at their final locations.
|
|
//
|
|
void CDesktopHost::_ComputeActualSize(MONITORINFO *pminfo, LPCRECT prcExclude)
|
|
{
|
|
// Compute the maximum permissible space above/below the Start Menu.
|
|
// Designers don't want the Start Menu to slide horizontally; it must
|
|
// fit entirely above or below.
|
|
|
|
int cxMax = RECTWIDTH(pminfo->rcWork);
|
|
int cyMax = max(prcExclude->top - pminfo->rcMonitor.top,
|
|
pminfo->rcMonitor.bottom - prcExclude->bottom);
|
|
|
|
// Start at the minimum size and grow as necesary
|
|
_rcActual = _rcDesired;
|
|
|
|
// Ask the windows if they wants any adjustments
|
|
int iMFUHeight = GetDesiredHeight(_hwnd, &_spm.panes[SMPANETYPE_MFU]);
|
|
int iPlacesHeight = GetDesiredHeight(_hwnd, &_spm.panes[SMPANETYPE_PLACES]);
|
|
int iMoreProgHeight = _spm.panes[SMPANETYPE_MOREPROG].size.cy;
|
|
|
|
// Figure out the maximum size for each pane
|
|
int cyPlacesMax = cyMax - (_spm.panes[SMPANETYPE_USER].size.cy + _spm.panes[SMPANETYPE_LOGOFF].size.cy);
|
|
int cyMFUMax = cyPlacesMax - _spm.panes[SMPANETYPE_MOREPROG].size.cy;
|
|
|
|
|
|
TraceMsg(TF_DV2HOST, "MFU Desired Height=%d(cur=%d,max=%d), Places Desired Height=%d(cur=%d,max=%d)",
|
|
iMFUHeight, _spm.panes[SMPANETYPE_MFU].size.cy, cyMFUMax,
|
|
iPlacesHeight, _spm.panes[SMPANETYPE_PLACES].size.cy, cyPlacesMax);
|
|
|
|
// Clip each pane to its max - the smaller of (The largest possible or The largest we want to be)
|
|
_fClipped = FALSE;
|
|
if (iMFUHeight > cyMFUMax)
|
|
{
|
|
iMFUHeight = cyMFUMax;
|
|
_fClipped = TRUE;
|
|
}
|
|
|
|
if (iPlacesHeight > cyPlacesMax)
|
|
{
|
|
iPlacesHeight = cyPlacesMax;
|
|
_fClipped = TRUE;
|
|
}
|
|
|
|
// ensure that places == mfu + moreprog by growing the smaller of the two.
|
|
if (iPlacesHeight > iMFUHeight + iMoreProgHeight)
|
|
iMFUHeight = iPlacesHeight - iMoreProgHeight;
|
|
else
|
|
iPlacesHeight = iMFUHeight + iMoreProgHeight;
|
|
|
|
//
|
|
// move the actual windows
|
|
// See diagram of layout in deskhost.h for the hardcoded assumptions here.
|
|
// this could be made more flexible/variable, but we want to lock in this layout
|
|
//
|
|
|
|
// helper variables...
|
|
DWORD dwUserBottomEdge = _spm.panes[SMPANETYPE_USER].size.cy;
|
|
DWORD dwMFURightEdge = _spm.panes[SMPANETYPE_MFU].size.cx;
|
|
DWORD dwMFUBottomEdge = dwUserBottomEdge + iMFUHeight;
|
|
DWORD dwMoreProgBottomEdge = dwMFUBottomEdge + iMoreProgHeight;
|
|
|
|
// set the size of the overall pane
|
|
_rcActual.right = _spm.panes[SMPANETYPE_USER].size.cx;
|
|
_rcActual.bottom = dwMoreProgBottomEdge + _spm.panes[SMPANETYPE_LOGOFF].size.cy;
|
|
|
|
HDWP hdwp = BeginDeferWindowPos(5);
|
|
const DWORD dwSWPFlags = SWP_NOACTIVATE | SWP_NOZORDER;
|
|
DeferWindowPos(hdwp, _spm.panes[SMPANETYPE_USER].hwnd, NULL, 0, 0, _rcActual.right, dwUserBottomEdge, dwSWPFlags);
|
|
DeferWindowPos(hdwp, _spm.panes[SMPANETYPE_MFU].hwnd, NULL, 0, dwUserBottomEdge, dwMFURightEdge, iMFUHeight, dwSWPFlags);
|
|
DeferWindowPos(hdwp, _spm.panes[SMPANETYPE_MOREPROG].hwnd, NULL,0, dwMFUBottomEdge, dwMFURightEdge, iMoreProgHeight, dwSWPFlags);
|
|
DeferWindowPos(hdwp, _spm.panes[SMPANETYPE_PLACES].hwnd, NULL, dwMFURightEdge, dwUserBottomEdge, _rcActual.right-dwMFURightEdge, iPlacesHeight, dwSWPFlags);
|
|
DeferWindowPos(hdwp, _spm.panes[SMPANETYPE_LOGOFF].hwnd, NULL, 0, dwMoreProgBottomEdge, _rcActual.right, _spm.panes[SMPANETYPE_LOGOFF].size.cy, dwSWPFlags);
|
|
EndDeferWindowPos(hdwp);
|
|
}
|
|
|
|
HWND CDesktopHost::_Create()
|
|
{
|
|
TCHAR szTitle[MAX_PATH];
|
|
|
|
LoadString(hinstCabinet, IDS_STARTMENU, szTitle, MAX_PATH);
|
|
|
|
Register();
|
|
|
|
// Must load metrics early to determine whether we are themed or not
|
|
LoadPanelMetrics();
|
|
|
|
DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
|
|
if (IS_BIDI_LOCALIZED_SYSTEM())
|
|
{
|
|
dwExStyle |= WS_EX_LAYOUTRTL;
|
|
}
|
|
|
|
DWORD dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; // We will make it visible as part of animation
|
|
if (!_hTheme)
|
|
{
|
|
// Normally the theme provides the border effects, but if there is
|
|
// no theme then we have to do it ourselves.
|
|
dwStyle |= WS_DLGFRAME;
|
|
}
|
|
|
|
_hwnd = CreateWindowEx(
|
|
dwExStyle,
|
|
WC_DV2,
|
|
szTitle,
|
|
dwStyle,
|
|
0, 0,
|
|
0, 0,
|
|
v_hwndTray,
|
|
NULL,
|
|
hinstCabinet,
|
|
this);
|
|
|
|
v_hwndStartPane = _hwnd;
|
|
|
|
return _hwnd;
|
|
}
|
|
|
|
void CDesktopHost::_ReapplyRegion()
|
|
{
|
|
SMNMAPPLYREGION ar;
|
|
|
|
// If we fail to create a rectangular region, then remove the region
|
|
// entirely so we don't carry the old (bad) region around.
|
|
// Yes it means you get ugly black corners, but it's better than
|
|
// clipping away huge chunks of the Start Menu!
|
|
|
|
ar.hrgn = CreateRectRgn(0, 0, _sizWindowPrev.cx, _sizWindowPrev.cy);
|
|
if (ar.hrgn)
|
|
{
|
|
// Let all the clients take a bite out of it
|
|
ar.hdr.hwndFrom = _hwnd;
|
|
ar.hdr.idFrom = 0;
|
|
ar.hdr.code = SMN_APPLYREGION;
|
|
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&ar, SPM_SEND | SPM_ONELEVEL);
|
|
}
|
|
|
|
if (!SetWindowRgn(_hwnd, ar.hrgn, FALSE))
|
|
{
|
|
// SetWindowRgn takes ownership on success
|
|
// On failure we need to free it ourselves
|
|
if (ar.hrgn)
|
|
{
|
|
DeleteObject(ar.hrgn);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// We need to use PrintWindow because WM_PRINT messes up RTL.
|
|
// PrintWindow requires that the window be visible.
|
|
// Making the window visible causes the shadow to appear.
|
|
// We don't want the shadow to appear until we are ready.
|
|
// So we have to do a lot of goofy style mangling to suppress the
|
|
// shadow until we're ready.
|
|
//
|
|
BOOL ShowCachedWindow(HWND hwnd, SIZE sizWindow, HBITMAP hbmpSnapshot, BOOL fRepaint)
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
if (hbmpSnapshot)
|
|
{
|
|
// Turn off the shadow so it won't get triggered by our SetWindowPos
|
|
DWORD dwClassStylePrev = GetClassLong(hwnd, GCL_STYLE);
|
|
SetClassLong(hwnd, GCL_STYLE, dwClassStylePrev & ~CS_DROPSHADOW);
|
|
|
|
// Show the window and tell it not to repaint; we'll do that
|
|
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER |
|
|
SWP_NOREDRAW | SWP_SHOWWINDOW);
|
|
|
|
// Turn the shadow back on
|
|
SetClassLong(hwnd, GCL_STYLE, dwClassStylePrev);
|
|
|
|
// Disable WS_CLIPCHILDREN because we need to draw over the kids for our BLT
|
|
DWORD dwStylePrev = SHSetWindowBits(hwnd, GWL_STYLE, WS_CLIPCHILDREN, 0);
|
|
|
|
HDC hdcWindow = GetDCEx(hwnd, NULL, DCX_WINDOW | DCX_CACHE);
|
|
if (hdcWindow)
|
|
{
|
|
HDC hdcMem = CreateCompatibleDC(hdcWindow);
|
|
if (hdcMem)
|
|
{
|
|
HBITMAP hbmPrev = (HBITMAP)SelectObject(hdcMem, hbmpSnapshot);
|
|
|
|
// PrintWindow only if fRepaint says it's necessary
|
|
if (!fRepaint || PrintWindow(hwnd, hdcMem, 0))
|
|
{
|
|
// Do this horrible dance because sometimes GDI takes a long
|
|
// time to do a BitBlt so you end up seeing the shadow for
|
|
// a half second before the bits show up.
|
|
//
|
|
// So show the bits first, then show the shadow.
|
|
|
|
if (BitBlt(hdcWindow, 0, 0, sizWindow.cx, sizWindow.cy, hdcMem, 0, 0, SRCCOPY))
|
|
{
|
|
// Tell USER to attach the shadow
|
|
// Do this by hiding the window and then showing it
|
|
// again, but do it in this goofy way to avoid flicker.
|
|
// (If we used ShowWindow(SW_HIDE), then the window
|
|
// underneath us would repaint pointlessly.)
|
|
|
|
SHSetWindowBits(hwnd, GWL_STYLE, WS_VISIBLE, 0);
|
|
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER |
|
|
SWP_NOREDRAW | SWP_SHOWWINDOW);
|
|
|
|
// Validate the window now that we've drawn it
|
|
RedrawWindow(hwnd, NULL, NULL, RDW_NOERASE | RDW_NOFRAME |
|
|
RDW_NOINTERNALPAINT | RDW_VALIDATE);
|
|
fSuccess = TRUE;
|
|
}
|
|
}
|
|
|
|
SelectObject(hdcMem, hbmPrev);
|
|
DeleteDC(hdcMem);
|
|
}
|
|
ReleaseDC(hwnd, hdcWindow);
|
|
}
|
|
|
|
SetWindowLong(hwnd, GWL_STYLE, dwStylePrev);
|
|
}
|
|
|
|
if (!fSuccess)
|
|
{
|
|
// re-hide the window so USER knows it's all invalid again
|
|
ShowWindow(hwnd, SW_HIDE);
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CDesktopHost::_TryShowBuffered()
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
BOOL fRepaint = FALSE;
|
|
|
|
if (!_hbmCachedSnapshot)
|
|
{
|
|
HDC hdcWindow = GetDCEx(_hwnd, NULL, DCX_WINDOW | DCX_CACHE);
|
|
if (hdcWindow)
|
|
{
|
|
_hbmCachedSnapshot = CreateCompatibleBitmap(hdcWindow, _sizWindowPrev.cx, _sizWindowPrev.cy);
|
|
fRepaint = TRUE;
|
|
ReleaseDC(_hwnd, hdcWindow);
|
|
}
|
|
}
|
|
if (_hbmCachedSnapshot)
|
|
{
|
|
fSuccess = ShowCachedWindow(_hwnd, _sizWindowPrev, _hbmCachedSnapshot, fRepaint);
|
|
if (!fSuccess)
|
|
{
|
|
DeleteObject(_hbmCachedSnapshot);
|
|
_hbmCachedSnapshot = NULL;
|
|
}
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
LRESULT CDesktopHost::OnNeedRepaint()
|
|
{
|
|
if (_hwnd && _hbmCachedSnapshot)
|
|
{
|
|
// This will force a repaint the next time the window is shown
|
|
DeleteObject(_hbmCachedSnapshot);
|
|
_hbmCachedSnapshot = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CDesktopHost::_Popup(POINT *ppt, RECT *prcExclude, DWORD dwFlags)
|
|
{
|
|
if (_hwnd)
|
|
{
|
|
RECT rcWindow;
|
|
_ChoosePopupPosition(ppt, prcExclude, &rcWindow);
|
|
SIZE sizWindow = { RECTWIDTH(rcWindow), RECTHEIGHT(rcWindow) };
|
|
|
|
MoveWindow(_hwnd, rcWindow.left, rcWindow.top,
|
|
sizWindow.cx, sizWindow.cy, TRUE);
|
|
|
|
if (sizWindow.cx != _sizWindowPrev.cx ||
|
|
sizWindow.cy != _sizWindowPrev.cy)
|
|
{
|
|
_sizWindowPrev = sizWindow;
|
|
_ReapplyRegion();
|
|
// We need to repaint since our size has changed
|
|
OnNeedRepaint();
|
|
}
|
|
|
|
// If the user toggles the tray between topmost and nontopmost
|
|
// our own topmostness can get messed up, so re-assert it here.
|
|
SetWindowZorder(_hwnd, HWND_TOPMOST);
|
|
|
|
if (GetSystemMetrics(SM_REMOTESESSION))
|
|
{
|
|
// If running remotely, then don't cache the Start Menu
|
|
// or double-buffer. Show the keyboard cues accurately
|
|
// from the start (to avoid flicker).
|
|
|
|
SendMessage(_hwnd, WM_CHANGEUISTATE, UIS_INITIALIZE, 0);
|
|
if (dwFlags & MPPF_KEYBOARD)
|
|
{
|
|
_EnableKeyboardCues();
|
|
}
|
|
ShowWindow(_hwnd, SW_SHOW);
|
|
}
|
|
else
|
|
{
|
|
// If running locally, then force keyboard cues off so our
|
|
// cached bitmap won't have underlines. Then draw the
|
|
// Start Menu, then turn on keyboard cues if necessary.
|
|
|
|
SendMessage(_hwnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_SET, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0);
|
|
|
|
if (!_TryShowBuffered())
|
|
{
|
|
ShowWindow(_hwnd, SW_SHOW);
|
|
}
|
|
|
|
NotifyWinEvent(EVENT_SYSTEM_MENUPOPUPSTART, _hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
|
|
if (dwFlags & MPPF_KEYBOARD)
|
|
{
|
|
_EnableKeyboardCues();
|
|
}
|
|
}
|
|
|
|
// Tell tray that the start pane is active, so it knows to eat
|
|
// mouse clicks on the Start Button.
|
|
Tray_SetStartPaneActive(TRUE);
|
|
|
|
_fOpen = TRUE;
|
|
_fMenuBlocked = FALSE;
|
|
_fMouseEntered = FALSE;
|
|
_fOfferedNewApps = FALSE;
|
|
|
|
_MaybeOfferNewApps();
|
|
_MaybeShowClipBalloon();
|
|
|
|
// Tell all our child windows it's time to maybe revalidate
|
|
NMHDR nm = { _hwnd, 0, SMN_POSTPOPUP };
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nm, SPM_SEND | SPM_ONELEVEL);
|
|
|
|
ExplorerPlaySound(TEXT("MenuPopup"));
|
|
|
|
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
HRESULT CDesktopHost::Popup(POINTL *pptl, RECTL *prclExclude, DWORD dwFlags)
|
|
{
|
|
COMPILETIME_ASSERT(sizeof(POINTL) == sizeof(POINT));
|
|
POINT *ppt = reinterpret_cast<POINT*>(pptl);
|
|
|
|
COMPILETIME_ASSERT(sizeof(RECTL) == sizeof(RECT));
|
|
RECT *prcExclude = reinterpret_cast<RECT*>(prclExclude);
|
|
|
|
if (_hwnd == NULL)
|
|
{
|
|
_hwnd = _Create();
|
|
}
|
|
|
|
return _Popup(ppt, prcExclude, dwFlags);
|
|
}
|
|
|
|
LRESULT CDesktopHost::OnHaveNewItems(NMHDR *pnm)
|
|
{
|
|
PSMNMHAVENEWITEMS phni = (PSMNMHAVENEWITEMS)pnm;
|
|
|
|
_hwndNewHandler = pnm->hwndFrom;
|
|
|
|
// We have a new "new app" list, so tell the cached Programs menu
|
|
// its cache is no longer valid and it should re-query us
|
|
// so we can color the new apps appropriately.
|
|
|
|
if (_ppmPrograms)
|
|
{
|
|
_ppmPrograms->Invalidate();
|
|
}
|
|
|
|
//
|
|
// Were any apps in the list installed since the last time the
|
|
// user acknowledged a new app?
|
|
//
|
|
|
|
FILETIME ftBalloon = { 0, 0 }; // assume never
|
|
DWORD dwSize = sizeof(ftBalloon);
|
|
|
|
SHRegGetUSValue(DV2_REGPATH, DV2_NEWAPP_BALLOON_TIME, NULL,
|
|
&ftBalloon, &dwSize, FALSE, NULL, 0);
|
|
|
|
if (CompareFileTime(&ftBalloon, &phni->ftNewestApp) < 0)
|
|
{
|
|
_iOfferNewApps = NEWAPP_OFFER_COUNT;
|
|
_MaybeOfferNewApps();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void CDesktopHost::_MaybeOfferNewApps()
|
|
{
|
|
// Display the balloon tip only once per pop-open,
|
|
// and only if there are new apps to offer
|
|
// and only if we're actually visible
|
|
if (_fOfferedNewApps || !_iOfferNewApps || !IsWindowVisible(_hwnd) ||
|
|
!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, REGSTR_VAL_DV2_NOTIFYNEW, FALSE, TRUE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_fOfferedNewApps = TRUE;
|
|
_iOfferNewApps--;
|
|
|
|
SMNMBOOL nmb = { { _hwnd, 0, SMN_SHOWNEWAPPSTIP }, TRUE };
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nmb, SPM_SEND | SPM_ONELEVEL);
|
|
}
|
|
|
|
void CDesktopHost::OnSeenNewItems()
|
|
{
|
|
_iOfferNewApps = 0; // Do not offer More Programs balloon tip again
|
|
|
|
// Remember the time the user acknowledged the balloon so we only
|
|
// offer the balloon if there is an app installed after this point.
|
|
|
|
FILETIME ftNow;
|
|
GetSystemTimeAsFileTime(&ftNow);
|
|
SHRegSetUSValue(DV2_REGPATH, DV2_NEWAPP_BALLOON_TIME, REG_BINARY,
|
|
&ftNow, sizeof(ftNow), SHREGSET_FORCE_HKCU);
|
|
}
|
|
|
|
|
|
void CDesktopHost::_MaybeShowClipBalloon()
|
|
{
|
|
if (_fClipped && !_fWarnedClipped)
|
|
{
|
|
_fWarnedClipped = TRUE;
|
|
|
|
RECT rc;
|
|
GetWindowRect(_spm.panes[SMPANETYPE_MFU].hwnd, &rc); // show the clipped ballon pointing to the bottom of the MFU
|
|
|
|
_hwndClipBalloon = CreateBalloonTip(_hwnd,
|
|
(rc.right+rc.left)/2, rc.bottom,
|
|
NULL,
|
|
IDS_STARTPANE_CLIPPED_TITLE,
|
|
IDS_STARTPANE_CLIPPED_TEXT);
|
|
if (_hwndClipBalloon)
|
|
{
|
|
SetProp(_hwndClipBalloon, PROP_DV2_BALLOONTIP, DV2_BALLOONTIP_CLIP);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDesktopHost::OnContextMenu(LPARAM lParam)
|
|
{
|
|
if (!IsRestrictedOrUserSetting(HKEY_CURRENT_USER, REST_NOTRAYCONTEXTMENU, TEXT("Advanced"), TEXT("TaskbarContextMenu"), ROUS_KEYALLOWS | ROUS_DEFAULTALLOW))
|
|
{
|
|
HMENU hmenu = SHLoadMenuPopup(hinstCabinet, MENU_STARTPANECONTEXT);
|
|
if (hmenu)
|
|
{
|
|
POINT pt;
|
|
if (IS_WM_CONTEXTMENU_KEYBOARD(lParam))
|
|
{
|
|
pt.x = pt.y = 0;
|
|
MapWindowPoints(_hwnd, HWND_DESKTOP, &pt, 1);
|
|
}
|
|
else
|
|
{
|
|
pt.x = GET_X_LPARAM(lParam);
|
|
pt.y = GET_Y_LPARAM(lParam);
|
|
}
|
|
|
|
int idCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
|
pt.x, pt.y, _hwnd, NULL);
|
|
if (idCmd == IDSYSPOPUP_STARTMENUPROP)
|
|
{
|
|
DesktopHost_Dismiss(_hwnd);
|
|
Tray_DoProperties(TPF_STARTMENUPAGE);
|
|
}
|
|
DestroyMenu(hmenu);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CDesktopHost::_ShouldIgnoreFocusChange(HWND hwndFocusRecipient)
|
|
{
|
|
// Ignore focus changes when a popup menu is up
|
|
if (_ppmTracking)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// If a focus change from a special balloon, this means that the
|
|
// user is clicking a tooltip. So dismiss the ballon and not the Start Menu.
|
|
HANDLE hProp = GetProp(hwndFocusRecipient, PROP_DV2_BALLOONTIP);
|
|
if (hProp)
|
|
{
|
|
SendMessage(hwndFocusRecipient, TTM_POP, 0, 0);
|
|
if (hProp == DV2_BALLOONTIP_MOREPROG)
|
|
{
|
|
OnSeenNewItems();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// Otherwise, dismiss ourselves
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
HRESULT CDesktopHost::TranslatePopupMenuMessage(MSG *pmsg, LRESULT *plres)
|
|
{
|
|
BOOL fDismissOnlyPopup = FALSE;
|
|
|
|
// If the user drags an item off of a popup menu, the popup menu
|
|
// will autodismiss itself. If the user is over our window, then
|
|
// we only want it to dismiss up to our level.
|
|
|
|
// (under low memory conditions, _wmDragCancel might be WM_NULL)
|
|
if (pmsg->message == _wmDragCancel && pmsg->message != WM_NULL)
|
|
{
|
|
RECT rc;
|
|
POINT pt;
|
|
if (GetWindowRect(_hwnd, &rc) &&
|
|
GetCursorPos(&pt) &&
|
|
PtInRect(&rc, pt))
|
|
{
|
|
fDismissOnlyPopup = TRUE;
|
|
}
|
|
}
|
|
|
|
if (fDismissOnlyPopup)
|
|
_fDismissOnlyPopup++;
|
|
|
|
HRESULT hr = _ppmTracking->TranslateMenuMessage(pmsg, plres);
|
|
|
|
if (fDismissOnlyPopup)
|
|
_fDismissOnlyPopup--;
|
|
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CALLBACK CDesktopHost::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CDesktopHost *pdh = (CDesktopHost *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
LPCREATESTRUCT pcs;
|
|
|
|
if (pdh && pdh->_ppmTracking)
|
|
{
|
|
MSG msg = { hwnd, uMsg, wParam, lParam };
|
|
LRESULT lres;
|
|
if (pdh->TranslatePopupMenuMessage(&msg, &lres) == S_OK)
|
|
{
|
|
return lres;
|
|
}
|
|
wParam = msg.wParam;
|
|
lParam = msg.lParam;
|
|
}
|
|
|
|
switch(uMsg)
|
|
{
|
|
case WM_NCCREATE:
|
|
pcs = (LPCREATESTRUCT)lParam;
|
|
pdh = (CDesktopHost *)pcs->lpCreateParams;
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pdh);
|
|
break;
|
|
|
|
case WM_CREATE:
|
|
pdh->OnCreate(hwnd);
|
|
break;
|
|
|
|
case WM_ACTIVATEAPP:
|
|
if (!wParam)
|
|
{
|
|
DesktopHost_Dismiss(hwnd);
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATE:
|
|
if (pdh)
|
|
{
|
|
if (LOWORD(wParam) == WA_INACTIVE)
|
|
{
|
|
pdh->_SaveChildFocus();
|
|
HWND hwndAncestor = GetAncestor((HWND) lParam, GA_ROOTOWNER);
|
|
if (hwnd != hwndAncestor &&
|
|
!(hwndAncestor == v_hwndTray && pdh->_ShouldIgnoreFocusChange((HWND)lParam)) &&
|
|
!pdh->_ppmTracking)
|
|
// Losing focus to somebody unrelated to us = dismiss
|
|
{
|
|
#ifdef FULL_DEBUG
|
|
if (! (GetAsyncKeyState(VK_SHIFT) <0) )
|
|
#endif
|
|
DesktopHost_Dismiss(hwnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pdh->_RestoreChildFocus();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
pdh->OnDestroy();
|
|
break;
|
|
|
|
case WM_SHOWWINDOW:
|
|
/*
|
|
* If hiding the window, save the focus for restoration later.
|
|
*/
|
|
if (!wParam)
|
|
{
|
|
pdh->_SaveChildFocus();
|
|
}
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
pdh->OnSetFocus((HWND)wParam);
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
pdh->OnPaint((HDC)wParam, TRUE);
|
|
return TRUE;
|
|
|
|
#if 0
|
|
// currently, the host doesn't do anything on WM_PAINT
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc;
|
|
|
|
if(hdc = BeginPaint(hwnd, &ps))
|
|
{
|
|
pdh->OnPaint(hdc, FALSE);
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
}
|
|
|
|
break;
|
|
#endif
|
|
|
|
case WM_NOTIFY:
|
|
{
|
|
LPNMHDR pnm = (LPNMHDR)lParam;
|
|
switch (pnm->code)
|
|
{
|
|
case SMN_HAVENEWITEMS:
|
|
return pdh->OnHaveNewItems(pnm);
|
|
case SMN_COMMANDINVOKED:
|
|
return pdh->OnCommandInvoked(pnm);
|
|
case SMN_FILTEROPTIONS:
|
|
return pdh->OnFilterOptions(pnm);
|
|
|
|
case SMN_NEEDREPAINT:
|
|
return pdh->OnNeedRepaint();
|
|
|
|
case SMN_TRACKSHELLMENU:
|
|
pdh->OnTrackShellMenu(pnm);
|
|
return 0;
|
|
|
|
case SMN_BLOCKMENUMODE:
|
|
pdh->_fMenuBlocked = ((SMNMBOOL*)pnm)->f;
|
|
break;
|
|
case SMN_SEENNEWITEMS:
|
|
pdh->OnSeenNewItems();
|
|
break;
|
|
|
|
case SMN_CANCELSHELLMENU:
|
|
pdh->_DismissTrackShellMenu();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_CONTEXTMENU:
|
|
pdh->OnContextMenu(lParam);
|
|
return 0; // do not bubble up
|
|
|
|
case WM_SETTINGCHANGE:
|
|
if ((wParam == SPI_ICONVERTICALSPACING) ||
|
|
((wParam == 0) && (lParam != 0) && (StrCmpIC((LPCTSTR)lParam, TEXT("Policy")) == 0)))
|
|
{
|
|
// A change in icon vertical spacing is how the themes control
|
|
// panel tells us that it changed the Large Icons setting (!)
|
|
::PostMessage(v_hwndTray, SBM_REBUILDMENU, 0, 0);
|
|
}
|
|
|
|
// Toss our cached bitmap because the user may have changed something
|
|
// that affects our appearance (e.g., toggled keyboard cues)
|
|
pdh->OnNeedRepaint();
|
|
|
|
SHPropagateMessage(hwnd, uMsg, wParam, lParam, SPM_SEND | SPM_ONELEVEL); // forward to kids
|
|
break;
|
|
|
|
|
|
case WM_DISPLAYCHANGE:
|
|
case WM_SYSCOLORCHANGE:
|
|
// Toss our cached bitmap because these settings may affect our
|
|
// appearance (e.g., color changes)
|
|
pdh->OnNeedRepaint();
|
|
|
|
SHPropagateMessage(hwnd, uMsg, wParam, lParam, SPM_SEND | SPM_ONELEVEL); // forward to kids
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
switch (wParam)
|
|
{
|
|
case IDT_MENUCHANGESEL:
|
|
pdh->_OnMenuChangeSel();
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case DHM_DISMISS:
|
|
pdh->_OnDismiss((BOOL)wParam);
|
|
break;
|
|
|
|
// Alt+F4 dismisses the window, but doesn't destroy it
|
|
case WM_CLOSE:
|
|
pdh->_OnDismiss(FALSE);
|
|
return 0;
|
|
|
|
case WM_SYSCOMMAND:
|
|
switch (wParam & ~0xF) // must ignore bottom 4 bits
|
|
{
|
|
case SC_SCREENSAVE:
|
|
DesktopHost_Dismiss(hwnd);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//
|
|
// If the user executes something or cancels out, we dismiss ourselves.
|
|
//
|
|
HRESULT CDesktopHost::OnSelect(DWORD dwSelectType)
|
|
{
|
|
HRESULT hr = E_NOTIMPL;
|
|
|
|
switch (dwSelectType)
|
|
{
|
|
case MPOS_EXECUTE:
|
|
case MPOS_CANCELLEVEL:
|
|
_DismissMenuPopup();
|
|
hr = S_OK;
|
|
break;
|
|
|
|
case MPOS_FULLCANCEL:
|
|
if (!_fDismissOnlyPopup)
|
|
{
|
|
_DismissMenuPopup();
|
|
}
|
|
// Don't _CleanupTrackShellMenu yet; wait for
|
|
// _smTracking.IsMenuMessage() to return E_FAIL
|
|
// because it might have some modal UI up
|
|
hr = S_OK;
|
|
break;
|
|
|
|
case MPOS_SELECTLEFT:
|
|
_DismissTrackShellMenu();
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CDesktopHost::_DismissTrackShellMenu()
|
|
{
|
|
if (_ppmTracking)
|
|
{
|
|
_fDismissOnlyPopup++;
|
|
_ppmTracking->OnSelect(MPOS_FULLCANCEL);
|
|
_fDismissOnlyPopup--;
|
|
}
|
|
}
|
|
|
|
void CDesktopHost::_CleanupTrackShellMenu()
|
|
{
|
|
ATOMICRELEASE(_ppmTracking);
|
|
_hwndTracking = NULL;
|
|
_hwndAltTracking = NULL;
|
|
KillTimer(_hwnd, IDT_MENUCHANGESEL);
|
|
|
|
NMHDR nm = { _hwnd, 0, SMN_SHELLMENUDISMISSED };
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nm, SPM_SEND | SPM_ONELEVEL);
|
|
}
|
|
|
|
void CDesktopHost::_DismissMenuPopup()
|
|
{
|
|
DesktopHost_Dismiss(_hwnd);
|
|
}
|
|
|
|
//
|
|
// The PMs want custom keyboard navigation behavior on the Start Panel,
|
|
// so we have to do it all manually.
|
|
//
|
|
BOOL CDesktopHost::_IsDialogMessage(MSG *pmsg)
|
|
{
|
|
//
|
|
// If this is a keyboard message or a mouse click, then remove the
|
|
// Start Menu lock.
|
|
if ((pmsg->message >= WM_KEYFIRST && pmsg->message <= WM_KEYLAST) ||
|
|
pmsg->message == WM_LBUTTONDOWN ||
|
|
pmsg->message == WM_RBUTTONDOWN)
|
|
{
|
|
Tray_UnlockStartPane();
|
|
}
|
|
|
|
//
|
|
// If the menu isn't even open or if menu mode is blocked, then
|
|
// do not mess with the message.
|
|
//
|
|
if (!_fOpen || _fMenuBlocked) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Tapping the ALT key dismisses menus.
|
|
//
|
|
if (pmsg->message == WM_SYSKEYDOWN && pmsg->wParam == VK_MENU)
|
|
{
|
|
DesktopHost_Dismiss(_hwnd);
|
|
// For accessibility purposes, dismissing the
|
|
// Start Menu should place focus on the Start Button.
|
|
SetFocus(c_tray._hwndStart);
|
|
return TRUE;
|
|
}
|
|
|
|
if (SHIsChildOrSelf(_hwnd, pmsg->hwnd) != S_OK) {
|
|
//
|
|
// If this is an uncaptured mouse move message, then eat it.
|
|
// That's what menus do -- they eat mouse moves.
|
|
// Let clicks go through, however, so the user
|
|
// can click away to dismiss the menu and activate
|
|
// whatever they clicked on.
|
|
if (!GetCapture() && pmsg->message == WM_MOUSEMOVE) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Destination window must be a grandchild of us. The child is the
|
|
// host control; the grandchild is the real control. Note also that
|
|
// we do not attempt to modify the behavior of great-grandchildren,
|
|
// because that would mess up inplace editing (which creates an
|
|
// edit control as a child of the listview).
|
|
|
|
HWND hwndTarget = GetParent(pmsg->hwnd);
|
|
if (hwndTarget != NULL && GetParent(hwndTarget) != _hwnd)
|
|
{
|
|
hwndTarget = NULL;
|
|
}
|
|
|
|
//
|
|
// Intercept mouse messages so we can do mouse hot tracking goo.
|
|
// (But not if a client has blocked menu mode because it has gone
|
|
// into some modal state.)
|
|
//
|
|
switch (pmsg->message) {
|
|
case WM_MOUSEMOVE:
|
|
_FilterMouseMove(pmsg, hwndTarget);
|
|
break;
|
|
|
|
case WM_MOUSELEAVE:
|
|
_FilterMouseLeave(pmsg, hwndTarget);
|
|
break;
|
|
|
|
case WM_MOUSEHOVER:
|
|
_FilterMouseHover(pmsg, hwndTarget);
|
|
break;
|
|
|
|
}
|
|
|
|
//
|
|
// Keyboard messages require a valid target.
|
|
//
|
|
if (hwndTarget == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Okay, hwndTarget is the host control that understands our
|
|
// wacky notification messages.
|
|
//
|
|
|
|
switch (pmsg->message)
|
|
{
|
|
case WM_KEYDOWN:
|
|
_EnableKeyboardCues();
|
|
|
|
switch (pmsg->wParam)
|
|
{
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
return _DlgNavigateArrow(hwndTarget, pmsg);
|
|
|
|
case VK_ESCAPE:
|
|
case VK_CANCEL:
|
|
DesktopHost_Dismiss(_hwnd);
|
|
// For accessibility purposes, hitting ESC to dismiss the
|
|
// Start Menu should place focus on the Start Button.
|
|
SetFocus(c_tray._hwndStart);
|
|
return TRUE;
|
|
|
|
case VK_RETURN:
|
|
_FindChildItem(hwndTarget, NULL, SMNDM_INVOKECURRENTITEM | SMNDM_KEYBOARD);
|
|
return TRUE;
|
|
|
|
// Eat space
|
|
case VK_SPACE:
|
|
return TRUE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
|
|
// Must dispatch there here so Tray's TranslateAccelerator won't see them
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SYSKEYUP:
|
|
case WM_SYSCHAR:
|
|
DispatchMessage(pmsg);
|
|
return TRUE;
|
|
|
|
case WM_CHAR:
|
|
return _DlgNavigateChar(hwndTarget, pmsg);
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT CDesktopHost::_FindChildItem(HWND hwnd, SMNDIALOGMESSAGE *pnmdm, UINT smndm)
|
|
{
|
|
SMNDIALOGMESSAGE nmdm;
|
|
if (!pnmdm)
|
|
{
|
|
pnmdm = &nmdm;
|
|
}
|
|
|
|
pnmdm->hdr.hwndFrom = _hwnd;
|
|
pnmdm->hdr.idFrom = 0;
|
|
pnmdm->hdr.code = SMN_FINDITEM;
|
|
pnmdm->flags = smndm;
|
|
|
|
LRESULT lres = ::SendMessage(hwnd, WM_NOTIFY, 0, (LPARAM)pnmdm);
|
|
|
|
if (lres && (smndm & SMNDM_SELECT))
|
|
{
|
|
SetFocus(::GetWindow(hwnd, GW_CHILD));
|
|
}
|
|
|
|
return lres;
|
|
}
|
|
|
|
void CDesktopHost::_EnableKeyboardCues()
|
|
{
|
|
SendMessage(_hwnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0);
|
|
}
|
|
|
|
|
|
//
|
|
// _DlgFindItem does the grunt work of walking the group/tab order
|
|
// looking for an item.
|
|
//
|
|
// hwndStart = window after which to start searching
|
|
// pnmdm = structure to receive results
|
|
// smndm = flags for _FindChildItem call
|
|
// GetNextDlgItem = GetNextDlgTabItem or GetNextDlgGroupItem
|
|
// fl = flags (DFI_*)
|
|
//
|
|
// DFI_INCLUDESTARTLAST: Include hwndStart at the end of the search.
|
|
// Otherwise do not search in hwndStart.
|
|
//
|
|
// Returns the found window, or NULL.
|
|
//
|
|
|
|
#define DFI_FORWARDS 0x0000
|
|
#define DFI_BACKWARDS 0x0001
|
|
|
|
#define DFI_INCLUDESTARTLAST 0x0002
|
|
|
|
HWND CDesktopHost::_DlgFindItem(
|
|
HWND hwndStart, SMNDIALOGMESSAGE *pnmdm, UINT smndm,
|
|
GETNEXTDLGITEM GetNextDlgItem, UINT fl)
|
|
{
|
|
HWND hwndT = hwndStart;
|
|
int iLoopCount = 0;
|
|
|
|
while ((hwndT = GetNextDlgItem(_hwnd, hwndT, fl & DFI_BACKWARDS)) != NULL)
|
|
{
|
|
if (!(fl & DFI_INCLUDESTARTLAST) && hwndT == hwndStart)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (_FindChildItem(hwndT, pnmdm, smndm))
|
|
{
|
|
return hwndT;
|
|
}
|
|
|
|
if (hwndT == hwndStart)
|
|
{
|
|
ASSERT(fl & DFI_INCLUDESTARTLAST);
|
|
return NULL;
|
|
}
|
|
|
|
if (++iLoopCount > 10)
|
|
{
|
|
// If this assert fires, it means that the controls aren't
|
|
// playing nice with WS_TABSTOP and WS_GROUP and we got stuck.
|
|
ASSERT(iLoopCount < 10);
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CDesktopHost::_DlgNavigateArrow(HWND hwndStart, MSG *pmsg)
|
|
{
|
|
HWND hwndT;
|
|
SMNDIALOGMESSAGE nmdm;
|
|
MSG msg;
|
|
nmdm.pmsg = pmsg; // other fields will be filled in by _FindChildItem
|
|
|
|
TraceMsg(TF_DV2DIALOG, "idm.arrow(%04x)", pmsg->wParam);
|
|
|
|
// If RTL, then flip the left and right arrows
|
|
UINT vk = (UINT)pmsg->wParam;
|
|
BOOL fRTL = GetWindowLong(_hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL;
|
|
if (fRTL)
|
|
{
|
|
if (vk == VK_LEFT) vk = VK_RIGHT;
|
|
else if (vk == VK_RIGHT) vk = VK_LEFT;
|
|
// Put the flipped arrows into the MSG structure so clients don't
|
|
// have to know anything about RTL.
|
|
msg = *pmsg;
|
|
nmdm.pmsg = &msg;
|
|
msg.wParam = vk;
|
|
}
|
|
BOOL fBackwards = vk == VK_LEFT || vk == VK_UP;
|
|
BOOL fVerticalKey = vk == VK_UP || vk == VK_DOWN;
|
|
|
|
|
|
//
|
|
// First see if the navigation can be handled by the control natively.
|
|
// We have to let the control get first crack because it might want to
|
|
// override default behavior (e.g., open a menu when VK_RIGHT is pressed
|
|
// instead of moving to the right).
|
|
//
|
|
|
|
//
|
|
// Holding the shift key while hitting the Right [RTL:Left] arrow
|
|
// suppresses the attempt to cascade.
|
|
//
|
|
|
|
DWORD dwTryCascade = 0;
|
|
if (vk == VK_RIGHT && GetKeyState(VK_SHIFT) >= 0)
|
|
{
|
|
dwTryCascade |= SMNDM_TRYCASCADE;
|
|
}
|
|
|
|
if (_FindChildItem(hwndStart, &nmdm, dwTryCascade | SMNDM_FINDNEXTARROW | SMNDM_SELECT | SMNDM_KEYBOARD))
|
|
{
|
|
// That was easy
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If the arrow key is in alignment with the control's orientation,
|
|
// then walk through the other controls in the group until we find
|
|
// one that contains an item, or until we loop back.
|
|
//
|
|
|
|
ASSERT(nmdm.flags & (SMNDM_VERTICAL | SMNDM_HORIZONTAL));
|
|
|
|
// Save this because subsequent callbacks will wipe it out.
|
|
DWORD dwDirection = nmdm.flags;
|
|
|
|
//
|
|
// Up/Down arrow always do prev/next. Left/right arrow will
|
|
// work if we are in a horizontal control.
|
|
//
|
|
if (fVerticalKey || (dwDirection & SMNDM_HORIZONTAL))
|
|
{
|
|
// Search for next/prev control in group.
|
|
|
|
UINT smndm = fBackwards ? SMNDM_FINDLAST : SMNDM_FINDFIRST;
|
|
UINT fl = fBackwards ? DFI_BACKWARDS : DFI_FORWARDS;
|
|
|
|
hwndT = _DlgFindItem(hwndStart, &nmdm,
|
|
smndm | SMNDM_SELECT | SMNDM_KEYBOARD,
|
|
GetNextDlgGroupItem,
|
|
fl | DFI_INCLUDESTARTLAST);
|
|
|
|
// Always return TRUE to eat the message
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Navigate to next column or row. Look for controls that intersect
|
|
// the x (or y) coordinate of the current item and ask them to select
|
|
// the nearest available item.
|
|
//
|
|
// Note that in this loop we do not want to let the starting point
|
|
// try again because it already told us that the navigation key was
|
|
// trying to leave the starting point.
|
|
//
|
|
|
|
//
|
|
// Note: For RTL compatibility, we must map rectangles.
|
|
//
|
|
RECT rcSrc = { nmdm.pt.x, nmdm.pt.y, nmdm.pt.x, nmdm.pt.y };
|
|
MapWindowRect(hwndStart, HWND_DESKTOP, &rcSrc);
|
|
hwndT = hwndStart;
|
|
|
|
while ((hwndT = GetNextDlgGroupItem(_hwnd, hwndT, fBackwards)) != NULL &&
|
|
hwndT != hwndStart)
|
|
{
|
|
// Does this window intersect in the desired direction?
|
|
RECT rcT;
|
|
BOOL fIntersect;
|
|
|
|
GetWindowRect(hwndT, &rcT);
|
|
if (dwDirection & SMNDM_VERTICAL)
|
|
{
|
|
rcSrc.left = rcSrc.right = fRTL ? rcT.right : rcT.left;
|
|
fIntersect = rcSrc.top >= rcT.top && rcSrc.top < rcT.bottom;
|
|
}
|
|
else
|
|
{
|
|
rcSrc.top = rcSrc.bottom = rcT.top;
|
|
fIntersect = rcSrc.left >= rcT.left && rcSrc.left < rcT.right;
|
|
}
|
|
|
|
if (fIntersect)
|
|
{
|
|
rcT = rcSrc;
|
|
MapWindowRect(HWND_DESKTOP, hwndT, &rcT);
|
|
nmdm.pt.x = rcT.left;
|
|
nmdm.pt.y = rcT.top;
|
|
if (_FindChildItem(hwndT, &nmdm,
|
|
SMNDM_FINDNEAREST | SMNDM_SELECT | SMNDM_KEYBOARD))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always return TRUE to eat the message
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Find the next/prev tabstop and tell it to select its first item.
|
|
// Keep doing this until we run out of controls or we find a control
|
|
// that is nonempty.
|
|
//
|
|
|
|
HWND CDesktopHost::_FindNextDlgChar(HWND hwndStart, SMNDIALOGMESSAGE *pnmdm, UINT smndm)
|
|
{
|
|
//
|
|
// See if there is a match in the hwndStart control.
|
|
//
|
|
if (_FindChildItem(hwndStart, pnmdm, SMNDM_FINDNEXTMATCH | SMNDM_KEYBOARD | smndm))
|
|
{
|
|
return hwndStart;
|
|
}
|
|
|
|
//
|
|
// Oh well, look for some other control, possibly wrapping back around
|
|
// to the start.
|
|
//
|
|
return _DlgFindItem(hwndStart, pnmdm,
|
|
SMNDM_FINDFIRSTMATCH | SMNDM_KEYBOARD | smndm,
|
|
GetNextDlgGroupItem,
|
|
DFI_FORWARDS | DFI_INCLUDESTARTLAST);
|
|
|
|
}
|
|
|
|
//
|
|
// Find the next item that begins with the typed letter and
|
|
// invoke it if it is unique.
|
|
//
|
|
BOOL CDesktopHost::_DlgNavigateChar(HWND hwndStart, MSG *pmsg)
|
|
{
|
|
SMNDIALOGMESSAGE nmdm;
|
|
nmdm.pmsg = pmsg; // other fields will be filled in by _FindChildItem
|
|
|
|
//
|
|
// See if there is a match in the hwndStart control.
|
|
//
|
|
HWND hwndFound = _FindNextDlgChar(hwndStart, &nmdm, SMNDM_SELECT);
|
|
if (hwndFound)
|
|
{
|
|
LRESULT idFound = nmdm.itemID;
|
|
|
|
//
|
|
// See if there is another match for this character.
|
|
// We are only looking, so don't pass SMNDM_SELECT.
|
|
//
|
|
HWND hwndFound2 = _FindNextDlgChar(hwndFound, &nmdm, 0);
|
|
if (hwndFound2 == hwndFound && nmdm.itemID == idFound)
|
|
{
|
|
//
|
|
// There is only one item that begins with this character.
|
|
// Invoke it!
|
|
//
|
|
UpdateWindow(_hwnd);
|
|
_FindChildItem(hwndFound2, &nmdm, SMNDM_INVOKECURRENTITEM | SMNDM_KEYBOARD);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CDesktopHost::_FilterMouseMove(MSG *pmsg, HWND hwndTarget)
|
|
{
|
|
|
|
if (!_fMouseEntered) {
|
|
_fMouseEntered = TRUE;
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(tme);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = pmsg->hwnd;
|
|
TrackMouseEvent(&tme);
|
|
}
|
|
|
|
//
|
|
// If the mouse is in the same place as last time, then ignore it.
|
|
// We can get spurious "no-motion" messages when the user is
|
|
// keyboard navigating.
|
|
//
|
|
if (_hwndLastMouse == pmsg->hwnd &&
|
|
_lParamLastMouse == pmsg->lParam)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_hwndLastMouse = pmsg->hwnd;
|
|
_lParamLastMouse = pmsg->lParam;
|
|
|
|
//
|
|
// See if the target window can hit-test this item successfully.
|
|
//
|
|
LRESULT lres;
|
|
if (hwndTarget)
|
|
{
|
|
SMNDIALOGMESSAGE nmdm;
|
|
nmdm.pt.x = GET_X_LPARAM(pmsg->lParam);
|
|
nmdm.pt.y = GET_Y_LPARAM(pmsg->lParam);
|
|
lres = _FindChildItem(hwndTarget, &nmdm, SMNDM_HITTEST | SMNDM_SELECT);
|
|
}
|
|
else
|
|
{
|
|
lres = 0; // No target, so no hit-test
|
|
}
|
|
|
|
if (!lres)
|
|
{
|
|
_RemoveSelection();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We selected a guy. Turn on the hover timer so we can
|
|
// do the auto-open thingie.
|
|
//
|
|
if (_fAutoCascade)
|
|
{
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(tme);
|
|
tme.dwFlags = TME_HOVER;
|
|
tme.hwndTrack = pmsg->hwnd;
|
|
if (!SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &tme.dwHoverTime, 0))
|
|
{
|
|
tme.dwHoverTime = HOVER_DEFAULT;
|
|
}
|
|
TrackMouseEvent(&tme);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CDesktopHost::_FilterMouseLeave(MSG *pmsg, HWND hwndTarget)
|
|
{
|
|
_fMouseEntered = FALSE;
|
|
_hwndLastMouse = NULL;
|
|
|
|
// If we got a WM_MOUSELEAVE due to a menu popping up, don't
|
|
// give up the focus since it really didn't leave yet.
|
|
if (!_ppmTracking)
|
|
{
|
|
_RemoveSelection();
|
|
}
|
|
}
|
|
|
|
void CDesktopHost::_FilterMouseHover(MSG *pmsg, HWND hwndTarget)
|
|
{
|
|
_FindChildItem(hwndTarget, NULL, SMNDM_OPENCASCADE);
|
|
}
|
|
|
|
//
|
|
// Remove the menu selection and put it in the "dead space" above
|
|
// the first visible item.
|
|
//
|
|
void CDesktopHost::_RemoveSelection()
|
|
{
|
|
// Put focus on first valid child control
|
|
// The real control is the grandchild
|
|
HWND hwndChild = GetNextDlgTabItem(_hwnd, NULL, FALSE);
|
|
if (hwndChild)
|
|
{
|
|
// The inner ::GetWindow will always succeed
|
|
// because all our controls contain inner windows
|
|
// (and if they failed to create their inner window,
|
|
// they would've failed their WM_CREATE message)
|
|
HWND hwndInner = ::GetWindow(hwndChild, GW_CHILD);
|
|
SetFocus(hwndInner);
|
|
|
|
//
|
|
// Now lie to the control and make it think it lost
|
|
// focus. This will cause the selection to clear.
|
|
//
|
|
NMHDR hdr;
|
|
hdr.hwndFrom = hwndInner;
|
|
hdr.idFrom = GetDlgCtrlID(hwndInner);
|
|
hdr.code = NM_KILLFOCUS;
|
|
::SendMessage(hwndChild, WM_NOTIFY, hdr.idFrom, (LPARAM)&hdr);
|
|
}
|
|
}
|
|
|
|
HRESULT CDesktopHost::IsMenuMessage(MSG *pmsg)
|
|
{
|
|
if (_hwnd)
|
|
{
|
|
if (_ppmTracking)
|
|
{
|
|
HRESULT hr = _ppmTracking->IsMenuMessage(pmsg);
|
|
if (hr == E_FAIL)
|
|
{
|
|
_CleanupTrackShellMenu();
|
|
hr = S_FALSE;
|
|
}
|
|
if (hr == S_OK)
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (_IsDialogMessage(pmsg))
|
|
{
|
|
return S_OK; // message handled
|
|
}
|
|
else
|
|
{
|
|
return S_FALSE; // message not handled
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return E_FAIL; // Menu is gone
|
|
}
|
|
}
|
|
|
|
HRESULT CDesktopHost::TranslateMenuMessage(MSG *pmsg, LRESULT *plres)
|
|
{
|
|
if (_ppmTracking)
|
|
{
|
|
return _ppmTracking->TranslateMenuMessage(pmsg, plres);
|
|
}
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// IServiceProvider::QueryService
|
|
STDMETHODIMP CDesktopHost::QueryService(REFGUID guidService, REFIID riid, void ** ppvObject)
|
|
{
|
|
if(IsEqualGUID(guidService,SID_SMenuPopup))
|
|
return QueryInterface(riid,ppvObject);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
// *** IOleCommandTarget ***
|
|
STDMETHODIMP CDesktopHost::QueryStatus (const GUID * pguidCmdGroup,
|
|
ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CDesktopHost::Exec (const GUID * pguidCmdGroup,
|
|
DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
|
|
{
|
|
if (IsEqualGUID(CLSID_MenuBand,*pguidCmdGroup))
|
|
{
|
|
switch (nCmdID)
|
|
{
|
|
case MBANDCID_REFRESH:
|
|
{
|
|
// There was a session or WM_DEVICECHANGE, we need to refresh our logoff options
|
|
NMHDR nm = { _hwnd, 0, SMN_REFRESHLOGOFF};
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nm, SPM_SEND | SPM_ONELEVEL);
|
|
OnNeedRepaint();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
// ITrayPriv2::ModifySMInfo
|
|
HRESULT CDesktopHost::ModifySMInfo(IN LPSMDATA psmd, IN OUT SMINFO *psminfo)
|
|
{
|
|
if (_hwndNewHandler)
|
|
{
|
|
SMNMMODIFYSMINFO nmsmi;
|
|
nmsmi.hdr.hwndFrom = _hwnd;
|
|
nmsmi.hdr.idFrom = 0;
|
|
nmsmi.hdr.code = SMN_MODIFYSMINFO;
|
|
nmsmi.psmd = psmd;
|
|
nmsmi.psminfo = psminfo;
|
|
SendMessage(_hwndNewHandler, WM_NOTIFY, 0, (LPARAM)&nmsmi);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL CDesktopHost::AddWin32Controls()
|
|
{
|
|
RegisterDesktopControlClasses();
|
|
|
|
// we create the controls with an arbitrary size, since we won't know how big we are until we pop up...
|
|
|
|
// Note that we do NOT set WS_EX_CONTROLPARENT because we want the
|
|
// dialog manager to think that our child controls are the interesting
|
|
// objects, not the inner grandchildren.
|
|
//
|
|
// Setting the control ID equal to the internal index number is just
|
|
// for the benefit of the test automation tools.
|
|
|
|
for (int i=0; i<ARRAYSIZE(_spm.panes); i++)
|
|
{
|
|
_spm.panes[i].hwnd = CreateWindowExW(0, _spm.panes[i].pszClassName,
|
|
NULL,
|
|
_spm.panes[i].dwStyle | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
|
|
0, 0, _spm.panes[i].size.cx, _spm.panes[i].size.cy,
|
|
_hwnd, IntToPtr_(HMENU, i), NULL,
|
|
&_spm.panes[i]);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CDesktopHost::OnPaint(HDC hdc, BOOL bBackground)
|
|
{
|
|
}
|
|
|
|
void CDesktopHost::_ReadPaneSizeFromTheme(SMPANEDATA *psmpd)
|
|
{
|
|
RECT rc;
|
|
if (SUCCEEDED(GetThemeRect(psmpd->hTheme, psmpd->iPartId, 0, TMT_DEFAULTPANESIZE, &rc)))
|
|
{
|
|
// semi-hack to take care of the fact that if one the start panel parts is missing a property,
|
|
// themes will use the next level up (to the panel itself)
|
|
if ((rc.bottom != _spm.sizPanel.cy) || (rc.right != _spm.sizPanel.cx))
|
|
{
|
|
psmpd->size.cx = RECTWIDTH(rc);
|
|
psmpd->size.cy = RECTHEIGHT(rc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemapSizeForHighDPI(SIZE *psiz)
|
|
{
|
|
static int iLPX, iLPY;
|
|
|
|
if (!iLPX || !iLPY)
|
|
{
|
|
HDC hdc = GetDC(NULL);
|
|
iLPX = GetDeviceCaps(hdc, LOGPIXELSX);
|
|
iLPY = GetDeviceCaps(hdc, LOGPIXELSY);
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
|
|
// 96 DPI is small fonts, so scale based on the multiple of that.
|
|
psiz->cx = (psiz->cx * iLPX)/96;
|
|
psiz->cy = (psiz->cy * iLPY)/96;
|
|
}
|
|
|
|
|
|
|
|
void CDesktopHost::LoadResourceInt(UINT ids, LONG *pl)
|
|
{
|
|
TCHAR sz[64];
|
|
if (LoadString(hinstCabinet, ids, sz, ARRAYSIZE(sz)))
|
|
{
|
|
int i = StrToInt(sz);
|
|
if (i)
|
|
{
|
|
*pl = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDesktopHost::LoadPanelMetrics()
|
|
{
|
|
// initialize our copy of the panel metrics from the default...
|
|
_spm = g_spmDefault;
|
|
|
|
// Adjust for localization
|
|
LoadResourceInt(IDS_STARTPANE_TOTALHEIGHT, &_spm.sizPanel.cy);
|
|
LoadResourceInt(IDS_STARTPANE_TOTALWIDTH, &_spm.sizPanel.cx);
|
|
LoadResourceInt(IDS_STARTPANE_USERHEIGHT, &_spm.panes[SMPANETYPE_USER].size.cy);
|
|
LoadResourceInt(IDS_STARTPANE_MOREPROGHEIGHT,&_spm.panes[SMPANETYPE_MOREPROG].size.cy);
|
|
LoadResourceInt(IDS_STARTPANE_LOGOFFHEIGHT, &_spm.panes[SMPANETYPE_LOGOFF].size.cy);
|
|
|
|
// wacky raymondc logic to scale using the values in g_spmDefault as relative ratio's
|
|
// Now apply those numbers; widths are easy
|
|
int i;
|
|
for (i = 0; i < ARRAYSIZE(_spm.panes); i++)
|
|
{
|
|
_spm.panes[i].size.cx = MulDiv(g_spmDefault.panes[i].size.cx,
|
|
_spm.sizPanel.cx,
|
|
g_spmDefault.sizPanel.cx);
|
|
}
|
|
|
|
// Places gets all height not eaten by User and Logoff
|
|
_spm.panes[SMPANETYPE_PLACES].size.cy = _spm.sizPanel.cy
|
|
- _spm.panes[SMPANETYPE_USER].size.cy
|
|
- _spm.panes[SMPANETYPE_LOGOFF].size.cy;
|
|
|
|
// MFU gets Places minus More Programs
|
|
_spm.panes[SMPANETYPE_MFU].size.cy = _spm.panes[SMPANETYPE_PLACES].size.cy
|
|
- _spm.panes[SMPANETYPE_MOREPROG].size.cy;
|
|
|
|
// End of adjustments for localization
|
|
|
|
// load the theme file (which shouldn't be loaded yet)
|
|
ASSERT(!_hTheme);
|
|
// only try to use themes if our color depth is greater than 8bpp.
|
|
if (SHGetCurColorRes() > 8)
|
|
_hTheme = OpenThemeData(_hwnd, STARTPANELTHEME);
|
|
|
|
if (_hTheme)
|
|
{
|
|
// if we fail reading the size from the theme, it will fall back to the defaul size....
|
|
|
|
RECT rcT;
|
|
if (SUCCEEDED(GetThemeRect(_hTheme, 0, 0, TMT_DEFAULTPANESIZE, &rcT))) // the overall pane
|
|
{
|
|
_spm.sizPanel.cx = RECTWIDTH(rcT);
|
|
_spm.sizPanel.cy = RECTHEIGHT(rcT);
|
|
for (int i=0;i<ARRAYSIZE(_spm.panes);i++)
|
|
{
|
|
_spm.panes[i].hTheme = _hTheme;
|
|
_ReadPaneSizeFromTheme(&_spm.panes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ASSERT that the layout matches up somewhat...
|
|
ASSERT(_spm.sizPanel.cx == _spm.panes[SMPANETYPE_USER].size.cx);
|
|
ASSERT(_spm.sizPanel.cx == _spm.panes[SMPANETYPE_MFU].size.cx + _spm.panes[SMPANETYPE_PLACES].size.cx );
|
|
ASSERT(_spm.sizPanel.cx == _spm.panes[SMPANETYPE_LOGOFF].size.cx);
|
|
ASSERT(_spm.panes[SMPANETYPE_MOREPROG].size.cx == _spm.panes[SMPANETYPE_MFU].size.cx);
|
|
TraceMsg(TF_DV2HOST, "sizPanel.cy = %d, user = %d, MFU =%d, moreprog=%d, logoff=%d",
|
|
_spm.sizPanel.cy, _spm.panes[SMPANETYPE_USER].size.cy, _spm.panes[SMPANETYPE_MFU].size.cy,
|
|
_spm.panes[SMPANETYPE_MOREPROG].size.cy, _spm.panes[SMPANETYPE_LOGOFF].size.cy);
|
|
|
|
ASSERT(_spm.sizPanel.cy == _spm.panes[SMPANETYPE_USER].size.cy + _spm.panes[SMPANETYPE_MFU].size.cy + _spm.panes[SMPANETYPE_MOREPROG].size.cy + _spm.panes[SMPANETYPE_LOGOFF].size.cy);
|
|
|
|
// one final pass to adjust everything for DPI
|
|
// note that things may not match up exactly after this due to rounding, but _ComputeActualSize can deal
|
|
RemapSizeForHighDPI(&_spm.sizPanel);
|
|
for (int i=0;i<ARRAYSIZE(_spm.panes);i++)
|
|
{
|
|
RemapSizeForHighDPI(&_spm.panes[i].size);
|
|
}
|
|
|
|
SetRect(&_rcDesired, 0, 0, _spm.sizPanel.cx, _spm.sizPanel.cy);
|
|
}
|
|
|
|
void CDesktopHost::OnCreate(HWND hwnd)
|
|
{
|
|
_hwnd = hwnd;
|
|
TraceMsg(TF_DV2HOST, "Entering CDesktopHost::OnCreate");
|
|
|
|
// Add the controls and background images
|
|
AddWin32Controls();
|
|
}
|
|
|
|
void CDesktopHost::OnDestroy()
|
|
{
|
|
_hwnd = NULL;
|
|
if (_hTheme)
|
|
{
|
|
CloseThemeData(_hTheme);
|
|
_hTheme = NULL;
|
|
}
|
|
}
|
|
|
|
void CDesktopHost::OnSetFocus(HWND hwndLose)
|
|
{
|
|
if (!_RestoreChildFocus())
|
|
{
|
|
_RemoveSelection();
|
|
}
|
|
}
|
|
|
|
LRESULT CDesktopHost::OnCommandInvoked(NMHDR *pnm)
|
|
{
|
|
// Invoking a command indicates explicit user activity
|
|
Tray_UnlockStartPane();
|
|
|
|
PSMNMCOMMANDINVOKED pci = (PSMNMCOMMANDINVOKED)pnm;
|
|
|
|
ExplorerPlaySound(TEXT("MenuCommand"));
|
|
BOOL fFade = FALSE;
|
|
if (SystemParametersInfo(SPI_GETSELECTIONFADE, 0, &fFade, 0) && fFade)
|
|
{
|
|
if (!_ptFader)
|
|
{
|
|
CoCreateInstance(CLSID_FadeTask, NULL, CLSCTX_INPROC, IID_PPV_ARG(IFadeTask, &_ptFader));
|
|
}
|
|
if (_ptFader)
|
|
{
|
|
_ptFader->FadeRect(&pci->rcItem);
|
|
}
|
|
}
|
|
|
|
return OnSelect(MPOS_EXECUTE);
|
|
}
|
|
|
|
LRESULT CDesktopHost::OnFilterOptions(NMHDR *pnm)
|
|
{
|
|
PSMNFILTEROPTIONS popt = (PSMNFILTEROPTIONS)pnm;
|
|
|
|
if ((popt->smnop & SMNOP_LOGOFF) &&
|
|
!_ShowStartMenuLogoff())
|
|
{
|
|
popt->smnop &= ~SMNOP_LOGOFF;
|
|
}
|
|
|
|
if ((popt->smnop & SMNOP_TURNOFF) &&
|
|
!_ShowStartMenuShutdown())
|
|
{
|
|
popt->smnop &= ~SMNOP_TURNOFF;
|
|
}
|
|
|
|
if ((popt->smnop & SMNOP_DISCONNECT) &&
|
|
!_ShowStartMenuDisconnect())
|
|
{
|
|
popt->smnop &= ~SMNOP_DISCONNECT;
|
|
}
|
|
|
|
if ((popt->smnop & SMNOP_EJECT) &&
|
|
!_ShowStartMenuEject())
|
|
{
|
|
popt->smnop &= ~SMNOP_EJECT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CDesktopHost::OnTrackShellMenu(NMHDR *pnm)
|
|
{
|
|
// Opening a menu indicates explicit user activity
|
|
Tray_UnlockStartPane();
|
|
|
|
PSMNTRACKSHELLMENU ptsm = CONTAINING_RECORD(pnm, SMNTRACKSHELLMENU, hdr);
|
|
HRESULT hr;
|
|
|
|
_hwndTracking = ptsm->hdr.hwndFrom;
|
|
_itemTracking = ptsm->itemID;
|
|
_hwndAltTracking = NULL;
|
|
_itemAltTracking = 0;
|
|
|
|
//
|
|
// Decide which direction we need to pop.
|
|
//
|
|
DWORD dwFlags;
|
|
if (GetWindowLong(_hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
|
|
{
|
|
dwFlags = MPPF_LEFT;
|
|
}
|
|
else
|
|
{
|
|
dwFlags = MPPF_RIGHT;
|
|
}
|
|
|
|
// Don't _CleanupTrackShellMenu because that will undo some of the
|
|
// work we've already done and make the client think that the popup
|
|
// they requested got dismissed.
|
|
|
|
//
|
|
// ISSUE raymondc: actually this abandons the trackpopupmenu that
|
|
// may already be in progress - its mouse UI gets messed up as a result.
|
|
//
|
|
ATOMICRELEASE(_ppmTracking);
|
|
|
|
if (_hwndTracking == _spm.panes[SMPANETYPE_MOREPROG].hwnd)
|
|
{
|
|
if (_ppmPrograms && _ppmPrograms->IsSame(ptsm->psm))
|
|
{
|
|
// It's already in our cache, woo-hoo!
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
ATOMICRELEASE(_ppmPrograms);
|
|
_SubclassTrackShellMenu(ptsm->psm);
|
|
hr = CPopupMenu_CreateInstance(ptsm->psm, GetUnknown(), _hwnd, &_ppmPrograms);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_ppmTracking = _ppmPrograms;
|
|
_ppmTracking->AddRef();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_SubclassTrackShellMenu(ptsm->psm);
|
|
hr = CPopupMenu_CreateInstance(ptsm->psm, GetUnknown(), _hwnd, &_ppmTracking);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _ppmTracking->Popup(&ptsm->rcExclude, ptsm->dwFlags | dwFlags);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// In addition to freeing any partially-allocated objects,
|
|
// this also sends a SMN_SHELLMENUDISMISSED so the client
|
|
// knows to remove the highlight from the item being cascaded
|
|
_CleanupTrackShellMenu();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
HRESULT CDesktopHost::_MenuMouseFilter(LPSMDATA psmd, BOOL fRemove, LPMSG pmsg)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
SMNDIALOGMESSAGE nmdm;
|
|
|
|
enum {
|
|
WHERE_IGNORE, // ignore this message
|
|
WHERE_OUTSIDE, // outside the Start Menu entirely
|
|
WHERE_DEADSPOT, // a dead spot on the Start Menu
|
|
WHERE_ONSELF, // over the item that initiated the popup
|
|
WHERE_ONOTHER, // over some other item in the Start Menu
|
|
} uiWhere;
|
|
|
|
//
|
|
// Figure out where the mouse is.
|
|
//
|
|
// Note: ChildWindowFromPointEx searches only immediate
|
|
// children; it does not search grandchildren. Fortunately, that's
|
|
// exactly what we want...
|
|
//
|
|
|
|
HWND hwndTarget = NULL;
|
|
|
|
if (fRemove)
|
|
{
|
|
if (psmd->punk)
|
|
{
|
|
// Inside a menuband - mouse has left our window
|
|
uiWhere = WHERE_OUTSIDE;
|
|
}
|
|
else
|
|
{
|
|
POINT pt = { GET_X_LPARAM(pmsg->lParam), GET_Y_LPARAM(pmsg->lParam) };
|
|
ScreenToClient(_hwnd, &pt);
|
|
|
|
hwndTarget = ChildWindowFromPointEx(_hwnd, pt, CWP_SKIPINVISIBLE);
|
|
if (hwndTarget == _hwnd)
|
|
{
|
|
uiWhere = WHERE_DEADSPOT;
|
|
}
|
|
else if (hwndTarget)
|
|
{
|
|
LRESULT lres;
|
|
nmdm.pt = pt;
|
|
HWND hwndChild = ::GetWindow(hwndTarget, GW_CHILD);
|
|
MapWindowPoints(_hwnd, hwndChild, &nmdm.pt, 1);
|
|
lres = _FindChildItem(hwndTarget, &nmdm, SMNDM_HITTEST | SMNDM_SELECT);
|
|
if (lres)
|
|
{
|
|
// Mouse is over something; is it over the current item?
|
|
|
|
if (nmdm.itemID == _itemTracking &&
|
|
hwndTarget == _hwndTracking)
|
|
{
|
|
uiWhere = WHERE_ONSELF;
|
|
}
|
|
else
|
|
{
|
|
uiWhere = WHERE_ONOTHER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uiWhere = WHERE_DEADSPOT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ChildWindowFromPoint failed - user has left the Start Menu
|
|
uiWhere = WHERE_OUTSIDE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ignore PM_NOREMOVE messages; we'll pay attention to them when
|
|
// they are PM_REMOVE'd.
|
|
uiWhere = WHERE_IGNORE;
|
|
}
|
|
|
|
//
|
|
// Now do appropriate stuff depending on where the mouse is.
|
|
//
|
|
switch (uiWhere)
|
|
{
|
|
case WHERE_IGNORE:
|
|
break;
|
|
|
|
case WHERE_OUTSIDE:
|
|
//
|
|
// If you've left the menu entirely, then we return the menu to
|
|
// its original state, which is to say, as if you are hovering
|
|
// over the item that caused the popup to open in the first place.
|
|
// as being in a dead zone.
|
|
//
|
|
// FALL THROUGH
|
|
goto L_WHERE_ONSELF_HOVER;
|
|
|
|
case WHERE_DEADSPOT:
|
|
// To avoid annoying flicker as the user wanders over dead spots,
|
|
// we ignore mouse motion over them (but dismiss if they click
|
|
// in a dead spot).
|
|
if (pmsg->message == WM_LBUTTONDOWN ||
|
|
pmsg->message == WM_RBUTTONDOWN)
|
|
{
|
|
// Must explicitly dismiss; if we let it fall through to the
|
|
// default handler, then it will dismiss for us, causing the
|
|
// entire Start Menu to go away instead of just the tracking
|
|
// part.
|
|
_DismissTrackShellMenu();
|
|
hr = S_OK;
|
|
}
|
|
break;
|
|
|
|
case WHERE_ONSELF:
|
|
if (pmsg->message == WM_LBUTTONDOWN ||
|
|
pmsg->message == WM_RBUTTONDOWN)
|
|
{
|
|
_DismissTrackShellMenu();
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
L_WHERE_ONSELF_HOVER:
|
|
_hwndAltTracking = NULL;
|
|
_itemAltTracking = 0;
|
|
nmdm.itemID = _itemTracking;
|
|
_FindChildItem(_hwndTracking, &nmdm, SMNDM_FINDITEMID | SMNDM_SELECT);
|
|
KillTimer(_hwnd, IDT_MENUCHANGESEL);
|
|
}
|
|
break;
|
|
|
|
case WHERE_ONOTHER:
|
|
if (pmsg->message == WM_LBUTTONDOWN ||
|
|
pmsg->message == WM_RBUTTONDOWN)
|
|
{
|
|
_DismissTrackShellMenu();
|
|
hr = S_OK;
|
|
}
|
|
else if (hwndTarget == _hwndAltTracking && nmdm.itemID == _itemAltTracking)
|
|
{
|
|
// Don't restart the timer if the user wiggles the mouse
|
|
// within a single item
|
|
}
|
|
else
|
|
{
|
|
_hwndAltTracking = hwndTarget;
|
|
_itemAltTracking = nmdm.itemID;
|
|
|
|
DWORD dwHoverTime;
|
|
if (!SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &dwHoverTime, 0))
|
|
{
|
|
dwHoverTime = 0;
|
|
}
|
|
SetTimer(_hwnd, IDT_MENUCHANGESEL, dwHoverTime, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void CDesktopHost::_OnMenuChangeSel()
|
|
{
|
|
KillTimer(_hwnd, IDT_MENUCHANGESEL);
|
|
_DismissTrackShellMenu();
|
|
}
|
|
|
|
void CDesktopHost::_SaveChildFocus()
|
|
{
|
|
if (!_hwndChildFocus)
|
|
{
|
|
HWND hwndFocus = GetFocus();
|
|
if (hwndFocus && IsChild(_hwnd, hwndFocus))
|
|
{
|
|
_hwndChildFocus = hwndFocus;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns non-NULL if focus was successfully restored
|
|
HWND CDesktopHost::_RestoreChildFocus()
|
|
{
|
|
HWND hwndRet = NULL;
|
|
if (IsWindow(_hwndChildFocus))
|
|
{
|
|
HWND hwndT = _hwndChildFocus;
|
|
_hwndChildFocus = NULL;
|
|
hwndRet = SetFocus(hwndT);
|
|
}
|
|
return hwndRet;
|
|
}
|
|
|
|
|
|
void CDesktopHost::_DestroyClipBalloon()
|
|
{
|
|
if (_hwndClipBalloon)
|
|
{
|
|
DestroyWindow(_hwndClipBalloon);
|
|
_hwndClipBalloon = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CDesktopHost::_OnDismiss(BOOL bDestroy)
|
|
{
|
|
// Break the recursion loop: Call IMenuPopup::OnSelect only if the
|
|
// window was previously visible.
|
|
_fOpen = FALSE;
|
|
if (ShowWindow(_hwnd, SW_HIDE))
|
|
{
|
|
if (_ppmTracking)
|
|
{
|
|
_ppmTracking->OnSelect(MPOS_FULLCANCEL);
|
|
}
|
|
|
|
OnSelect(MPOS_FULLCANCEL);
|
|
|
|
NMHDR nm = { _hwnd, 0, SMN_DISMISS };
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nm, SPM_SEND | SPM_ONELEVEL);
|
|
|
|
_DestroyClipBalloon();
|
|
|
|
// Allow clicking on Start button to pop the menu immediately
|
|
Tray_SetStartPaneActive(FALSE);
|
|
|
|
// Don't try to preserve child focus across popups
|
|
_hwndChildFocus = NULL;
|
|
|
|
Tray_OnStartMenuDismissed();
|
|
|
|
NotifyWinEvent(EVENT_SYSTEM_MENUPOPUPEND, _hwnd, OBJID_CLIENT, CHILDID_SELF);
|
|
}
|
|
if (bDestroy)
|
|
{
|
|
v_hwndStartPane = NULL;
|
|
ASSERT(GetWindowThreadProcessId(_hwnd, NULL) == GetCurrentThreadId());
|
|
DestroyWindow(_hwnd);
|
|
}
|
|
}
|
|
|
|
HRESULT CDesktopHost::Build()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (_hwnd == NULL)
|
|
{
|
|
_hwnd = _Create();
|
|
|
|
if (_hwnd)
|
|
{
|
|
// Tell all our child windows it's time to reinitialize
|
|
NMHDR nm = { _hwnd, 0, SMN_INITIALUPDATE };
|
|
SHPropagateMessage(_hwnd, WM_NOTIFY, 0, (LPARAM)&nm, SPM_SEND | SPM_ONELEVEL);
|
|
}
|
|
}
|
|
|
|
if (_hwnd == NULL)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//*****************************************************************
|
|
//
|
|
// CDeskHostShellMenuCallback
|
|
//
|
|
// Create a wrapper IShellMenuCallback that picks off mouse
|
|
// messages.
|
|
//
|
|
class CDeskHostShellMenuCallback
|
|
: public CUnknown
|
|
, public IShellMenuCallback
|
|
, public IServiceProvider
|
|
, public CObjectWithSite
|
|
{
|
|
friend class CDesktopHost;
|
|
|
|
public:
|
|
// *** IUnknown ***
|
|
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj);
|
|
STDMETHODIMP_(ULONG) AddRef(void) { return CUnknown::AddRef(); }
|
|
STDMETHODIMP_(ULONG) Release(void) { return CUnknown::Release(); }
|
|
|
|
// *** IShellMenuCallback ***
|
|
STDMETHODIMP CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
// *** IObjectWithSite ***
|
|
STDMETHODIMP SetSite(IUnknown *punkSite);
|
|
|
|
// *** IServiceProvider ***
|
|
STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void ** ppvObject);
|
|
|
|
private:
|
|
CDeskHostShellMenuCallback(CDesktopHost *pdh)
|
|
{
|
|
_pdh = pdh; _pdh->AddRef();
|
|
}
|
|
|
|
~CDeskHostShellMenuCallback()
|
|
{
|
|
ATOMICRELEASE(_pdh);
|
|
IUnknown_SetSite(_psmcPrev, NULL);
|
|
ATOMICRELEASE(_psmcPrev);
|
|
}
|
|
|
|
IShellMenuCallback *_psmcPrev;
|
|
CDesktopHost *_pdh;
|
|
};
|
|
|
|
HRESULT CDeskHostShellMenuCallback::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CDeskHostShellMenuCallback, IShellMenuCallback),
|
|
QITABENT(CDeskHostShellMenuCallback, IObjectWithSite),
|
|
QITABENT(CDeskHostShellMenuCallback, IServiceProvider),
|
|
{ 0 },
|
|
};
|
|
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
BOOL FeatureEnabled(LPTSTR pszFeature)
|
|
{
|
|
return SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, pszFeature,
|
|
FALSE, // Don't ignore HKCU
|
|
FALSE); // Disable this cool feature.
|
|
}
|
|
|
|
|
|
HRESULT CDeskHostShellMenuCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case SMC_MOUSEFILTER:
|
|
if (_pdh)
|
|
return _pdh->_MenuMouseFilter(psmd, (BOOL)wParam, (MSG*)lParam);
|
|
|
|
case SMC_GETSFINFOTIP:
|
|
if (!FeatureEnabled(TEXT("ShowInfoTip")))
|
|
return E_FAIL; // E_FAIL means don't show. S_FALSE means show default
|
|
break;
|
|
|
|
}
|
|
|
|
if (_psmcPrev)
|
|
return _psmcPrev->CallbackSM(psmd, uMsg, wParam, lParam);
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
HRESULT CDeskHostShellMenuCallback::SetSite(IUnknown *punkSite)
|
|
{
|
|
CObjectWithSite::SetSite(punkSite);
|
|
// Each time our site changes, reassert ourselves as the site of
|
|
// the inner object so he can try a new QueryService.
|
|
IUnknown_SetSite(_psmcPrev, this->GetUnknown());
|
|
|
|
// If the game is over, break our backreference
|
|
if (!punkSite)
|
|
{
|
|
ATOMICRELEASE(_pdh);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CDeskHostShellMenuCallback::QueryService(REFGUID guidService, REFIID riid, void ** ppvObject)
|
|
{
|
|
return IUnknown_QueryService(_punkSite, guidService, riid, ppvObject);
|
|
}
|
|
|
|
void CDesktopHost::_SubclassTrackShellMenu(IShellMenu *psm)
|
|
{
|
|
CDeskHostShellMenuCallback *psmc = new CDeskHostShellMenuCallback(this);
|
|
if (psmc)
|
|
{
|
|
UINT uId, uIdAncestor;
|
|
DWORD dwFlags;
|
|
if (SUCCEEDED(psm->GetMenuInfo(&psmc->_psmcPrev, &uId, &uIdAncestor, &dwFlags)))
|
|
{
|
|
psm->Initialize(psmc, uId, uIdAncestor, dwFlags);
|
|
}
|
|
psmc->Release();
|
|
}
|
|
}
|
|
|
|
STDAPI DesktopV2_Build(void *pvStartPane)
|
|
{
|
|
HRESULT hr = E_POINTER;
|
|
if (pvStartPane)
|
|
{
|
|
hr = reinterpret_cast<CDesktopHost *>(pvStartPane)->Build();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
STDAPI DesktopV2_Create(
|
|
IMenuPopup **ppmp, IMenuBand **ppmb, void **ppvStartPane)
|
|
{
|
|
*ppmp = NULL;
|
|
*ppmb = NULL;
|
|
|
|
HRESULT hr;
|
|
CDesktopHost *pdh = new CDesktopHost;
|
|
if (pdh)
|
|
{
|
|
*ppvStartPane = pdh;
|
|
hr = pdh->Initialize();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pdh->QueryInterface(IID_PPV_ARG(IMenuPopup, ppmp));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pdh->QueryInterface(IID_PPV_ARG(IMenuBand, ppmb));
|
|
}
|
|
}
|
|
pdh->GetUnknown()->Release();
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ATOMICRELEASE(*ppmp);
|
|
ATOMICRELEASE(*ppmb);
|
|
ppvStartPane = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HBITMAP CreateMirroredBitmap( HBITMAP hbmOrig)
|
|
{
|
|
HDC hdc, hdcMem1, hdcMem2;
|
|
HBITMAP hbm = NULL, hOld_bm1, hOld_bm2;
|
|
BITMAP bm;
|
|
int IncOne = 0;
|
|
|
|
if (!hbmOrig)
|
|
return NULL;
|
|
|
|
if (!GetObject(hbmOrig, sizeof(BITMAP), &bm))
|
|
return NULL;
|
|
|
|
// Grab the screen DC
|
|
hdc = GetDC(NULL);
|
|
|
|
if (hdc)
|
|
{
|
|
hdcMem1 = CreateCompatibleDC(hdc);
|
|
|
|
if (!hdcMem1)
|
|
{
|
|
ReleaseDC(NULL, hdc);
|
|
return NULL;
|
|
}
|
|
|
|
hdcMem2 = CreateCompatibleDC(hdc);
|
|
if (!hdcMem2)
|
|
{
|
|
DeleteDC(hdcMem1);
|
|
ReleaseDC(NULL, hdc);
|
|
return NULL;
|
|
}
|
|
|
|
hbm = CreateCompatibleBitmap(hdc, bm.bmWidth, bm.bmHeight);
|
|
|
|
if (!hbm)
|
|
{
|
|
ReleaseDC(NULL, hdc);
|
|
DeleteDC(hdcMem1);
|
|
DeleteDC(hdcMem2);
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Flip the bitmap
|
|
//
|
|
hOld_bm1 = (HBITMAP)SelectObject(hdcMem1, hbmOrig);
|
|
hOld_bm2 = (HBITMAP)SelectObject(hdcMem2 , hbm );
|
|
|
|
SET_DC_RTL_MIRRORED(hdcMem2);
|
|
BitBlt(hdcMem2, IncOne, 0, bm.bmWidth, bm.bmHeight, hdcMem1, 0, 0, SRCCOPY);
|
|
|
|
SelectObject(hdcMem1, hOld_bm1 );
|
|
SelectObject(hdcMem1, hOld_bm2 );
|
|
|
|
DeleteDC(hdcMem1);
|
|
DeleteDC(hdcMem2);
|
|
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
|
|
return hbm;
|
|
}
|