#include "stdafx.h" #include // for IDM_PROGRAMS et al #include "regstr.h" #include "rcids.h" #include #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 | WS_CLIPCHILDREN, SPP_PROGLIST, {190, 330}, NULL, NULL}, {WC_MOREPROGRAMS, 0, SPP_MOREPROGRAMS, {190, 30}, NULL, NULL}, {WC_SFTBARHOST, WS_CLIPCHILDREN, 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) || GetSystemMetrics(SM_REMOTECONTROL)) { // 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); } if (dwFlags & MPPF_KEYBOARD) { _EnableKeyboardCues(); } } NotifyWinEvent(EVENT_SYSTEM_MENUPOPUPSTART, _hwnd, OBJID_CLIENT, CHILDID_SELF); // 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(pptl); COMPILETIME_ASSERT(sizeof(RECTL) == sizeof(RECT)); RECT *prcExclude = reinterpret_cast(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 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; ihTheme, 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;iFadeRect(&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) { 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(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; }