#include "shellprv.h" #include #include #include #include "ole2dup.h" #include "ids.h" #include "defview.h" #include "lvutil.h" #include "idlcomm.h" #include "filetbl.h" #include "undo.h" #include "cnctnpt.h" #include "ovrlaymn.h" #include "_security.h" #include "unicpp\dutil.h" #include "uemapp.h" #include "unicpp\deskhtm.h" #include "unicpp\dcomp.h" #include "datautil.h" #include "defvphst.h" #include #include #include "prop.h" #include #include "dvtasks.h" #include "category.h" #include "ViewState.h" #include #include #include #include "clsobj.h" #include #include "defviewp.h" #include "shellp.h" #include "duiview.h" #include "enumidlist.h" #include "util.h" #include "foldertypes.h" #include #include "views.h" #include "defcm.h" #include "contextmenu.h" // a "default" view to trick the browser into letting us delay viewmode selection // {6C6720F7-4B22-4CAA-82D6-502BB6F85A9A} DEFINE_GUID(VID_DefaultView, 0x6C6720F7L, 0x4B22, 0x4CAA, 0x82, 0xD6, 0x50, 0x2B, 0xB6, 0xF8, 0x5A, 0x9A); void DisableActiveDesktop(); STDAPI_(void) CFSFolder_UpdateIcon(IShellFolder *psf, LPCITEMIDLIST pidl); STDAPI_(void) SetPositionItemsPoints(IFolderView* psfv, LPCITEMIDLIST* apidl, UINT cidl, IDataObject* pdtobj, POINT* ptDrag); void UpdateGridSizes(BOOL fDesktop, HWND hwndListview, int nWorkAreas, LPRECT prcWork, BOOL fMinimizeGutterSpace); #define ID_LISTVIEW 1 #define ID_STATIC 2 extern BOOL g_fDraggingOverSource; #define IsDefaultState(_dvHead) ((_dvHead).dvState.lParamSort == 0 && \ (_dvHead).dvState.iDirection == 1 && \ (_dvHead).dvState.iLastColumnClick == -1 && \ (_dvHead).ptScroll.x == 0 && (_dvHead).ptScroll.y == 0) HMODULE g_hmodNTSHRUI = NULL; typedef struct { POINT pt; ITEMIDLIST idl; } DVITEM; // // Note that it returns NULL, if iItem is -1. // // determine if color is light or dark #define COLORISLIGHT(clr) ((5*GetGValue((clr)) + 2*GetRValue((clr)) + GetBValue((clr))) > 8*128) void EnableCombinedView(CDefView *pdsv, BOOL fEnable); BOOL IsBarricadeGloballyOff(); VARIANT_BOOL GetBarricadeStatus(LPCTSTR pszValueName); BOOL GetBarricadeValueNameFromPidl(LPCITEMIDLIST pidl, LPTSTR pszValueName, UINT cch); HRESULT SetBarricadeStatus(LPCTSTR pszValueName, VARIANT_BOOL bShowBarricade); // Command Strings // !! warning. Some ContextMenu handlers do not do a case-insensitive // check of the command so keep the case the same everywhere TCHAR const c_szCut[] = TEXT("cut"); TCHAR const c_szCopy[] = TEXT("copy"); TCHAR const c_szLink[] = TEXT("link"); TCHAR const c_szProperties[] = TEXT("properties"); TCHAR const c_szPaste[] = TEXT("paste"); TCHAR const c_szPasteLink[] = TEXT("pastelink"); TCHAR const c_szRename[] = TEXT("rename"); TCHAR const c_szDelete[] = TEXT("delete"); TCHAR const c_szNewFolder[] = TEXT(CMDSTR_NEWFOLDERA); char const c_szDeleteA[] = "delete"; char const c_szNewFolderA[] = CMDSTR_NEWFOLDERA; char const c_szPrintA[] = "print"; WCHAR const c_szPrintW[] = L"print"; DWORD CDefView::_Attributes(LPCITEMIDLIST pidl, DWORD dwAttribs) { return SHGetAttributes(_pshf, pidl, dwAttribs); } // IDefViewSafety HRESULT CDefView::IsSafePage() { HRESULT hr = E_ACCESSDENIED; WCHAR wszCurrentMoniker[MAX_PATH]; if (SUCCEEDED(_cFrame._GetCurrentWebViewMoniker(wszCurrentMoniker, ARRAYSIZE(wszCurrentMoniker)))) { // Some previous versions of the OS put a "FILE://" in front of the template name and some didn't. // SHRegisterValidateTemplate can't handle this prefix, so skip past it. LPWSTR pszMoniker = wszCurrentMoniker; if (!StrNCmpI(pszMoniker, L"FILE://", 7)) pszMoniker += 7; hr = SHRegisterValidateTemplate(pszMoniker, SHRVT_VALIDATE | SHRVT_PROMPTUSER | SHRVT_REGISTERIFPROMPTOK); } return hr; } // IDVGetEnum HRESULT CDefView::SetEnumReadyCallback(PFDVENUMREADYBALLBACK pfn, void *pvData) { _pfnEnumReadyCallback = pfn; _pvEnumCallbackData = pvData; return S_OK; } BOOL FilterOnAttributes(DWORD dwAttributes, DWORD grfEnumFlags) { if (dwAttributes & SFGAO_FOLDER) { if (!(grfEnumFlags & SHCONTF_FOLDERS)) return FALSE; // item is folder but client does not want folders } else if (!(grfEnumFlags & SHCONTF_NONFOLDERS)) { return FALSE; // item is file, but client only wants folders } if (!(grfEnumFlags & SHCONTF_INCLUDEHIDDEN) && (dwAttributes & SFGAO_HIDDEN)) return FALSE; // item is hidden by client wants non hidden return TRUE; } HRESULT CDefView::CreateEnumIDListFromContents(LPCITEMIDLIST pidlFolder, DWORD grfEnumFlags, IEnumIDList **ppenum) { HRESULT hr = E_FAIL; LPITEMIDLIST pidlView = _GetViewPidl(); if (pidlView) { if (ILIsEqual(pidlFolder, pidlView) && (grfEnumFlags & _GetEnumFlags()) == grfEnumFlags) { LPCITEMIDLIST *apidl; UINT cItems; hr = _GetItemObjects(&apidl, SVGIO_ALLVIEW, &cItems); if (SUCCEEDED(hr)) { for (UINT i = 0; i < cItems; i++) { if (!FilterOnAttributes(_Attributes(apidl[i], SFGAO_FOLDER | SFGAO_HIDDEN), grfEnumFlags)) { apidl[i] = apidl[cItems - 1]; cItems--; i--; } } hr = CreateIEnumIDListOnIDLists(apidl, cItems, ppenum); LocalFree(apidl); } } ILFree(pidlView); } return hr; } HRESULT CDefView::_OnDefaultCommand() { return _pcdb ? _pcdb->OnDefaultCommand(_psvOuter ? _psvOuter : this) : E_NOTIMPL; } HRESULT CDefView::_OnStateChange(UINT code) { return _pcdb ? _pcdb->OnStateChange(_psvOuter ? _psvOuter : this, code) : E_NOTIMPL; } HRESULT CDefView::_IncludeObject(LPCITEMIDLIST pidl) { if (_pcdb) return _pcdb->IncludeObject(_psvOuter ? _psvOuter : this, pidl); else { IFolderFilter *psff = _cCallback.GetISFF(); return psff ? psff->ShouldShow(_pshf, NULL, pidl) : S_OK; } } HRESULT CDefView::CallCB(UINT uMsg, WPARAM wParam, LPARAM lParam) { return _cCallback.CallCB(uMsg, wParam, lParam); } // fires dispatch events to clients (address bar, webview, etc). // this translates return values of false into "ERROR_CANCELLED" HRESULT CDefView::_FireEvent(DISPID dispid) { HRESULT hr; VARIANT varResult = {0}; SHINVOKEPARAMS inv = {0}; inv.dispidMember = dispid; inv.piid = &IID_NULL; inv.wFlags = DISPATCH_METHOD; inv.pvarResult = &varResult; if (SUCCEEDED(IUnknown_CPContainerInvokeIndirect(_pauto, DIID_DShellFolderViewEvents, &inv))) { if ((VT_BOOL == varResult.vt) && (VARIANT_FALSE == varResult.boolVal)) { hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } else { hr = S_OK; } VariantClear(&varResult); } else hr = S_FALSE; return hr; } BOOL CDefView::_IsPositionedView() { return !_fGroupView && ((_fs.ViewMode == FVM_ICON) || (_fs.ViewMode == FVM_SMALLICON) || (_fs.ViewMode == FVM_TILE) || (_fs.ViewMode == FVM_THUMBNAIL) || (_fs.ViewMode == FVM_THUMBSTRIP)); } // reposition the selected items in a listview by dx, dy void CDefView::_MoveSelectedItems(int dx, int dy, BOOL fAbsolute) { SendMessage(_hwndListview, WM_SETREDRAW, FALSE, 0); for (int i = ListView_GetNextItem(_hwndListview, -1, LVNI_SELECTED); i >= 0; i = ListView_GetNextItem(_hwndListview, i, LVNI_SELECTED)) { if (fAbsolute) { _SetItemPosition(i, dx, dy); } else { POINT pt; ListView_GetItemPosition(_hwndListview, i, &pt); pt.x += dx; pt.y += dy; _SetItemPosition(i, pt.x, pt.y); } } SendMessage(_hwndListview, WM_SETREDRAW, TRUE, 0); } void CDefView::_SameViewMoveIcons() { POINT ptDrop; BOOL fAbsolute = FALSE; // We'll use the insert mark rect (if available) to determine a drop point if (_GetInsertPoint(&ptDrop)) fAbsolute = TRUE; // Move all items to this point. else { ptDrop = _ptDrop; ptDrop.x -= _ptDragAnchor.x; ptDrop.y -= _ptDragAnchor.y; LVUtil_ClientToLV(_hwndListview, &ptDrop); } ASSERT(_IsPositionedView()); _MoveSelectedItems(ptDrop.x, ptDrop.y, fAbsolute); } // // This function checks if the current HTML wallpaper is the default // wallpaper and returns TRUE if so. If the wallpaper is the default wallpaper, // it reads the colors from the registry. If the colors are missing, then it // supplies the default colors. // BOOL CDefView::_GetColorsFromHTMLdoc(COLORREF *pclrTextBk, COLORREF *pclrHotlight) { // make sure the HTML document has reached ready-state interactive COLORREF clrBackground; BOOL bRet = SUCCEEDED(_cFrame._GetHTMLBackgroundColor(&clrBackground)); if (bRet) { // The following are the standard colors supported on desktop const COLORREF c_VgaColorTable[] = { 0x000000, // Black 0x000080, 0x0000FF, 0x008000, 0x008080, 0x00FF00, // Green 0x00FFFF, // Yellow 0x800000, 0x800080, 0x808000, 0x808080, 0xF0CAA6, 0xF0FBFF, 0xFF0000, // Blue 0xFF00FF, // Magenta 0xFFFF00, // cobalt 0xFFFFFF // White }; // Check if the given background color is a standard color. // If not, use the system background (COLOR_BACKGROUND). *pclrTextBk = GetSysColor(COLOR_BACKGROUND); // default for (int i = 0; i < ARRAYSIZE(c_VgaColorTable); i++) { if (c_VgaColorTable[i] == clrBackground) { *pclrTextBk = clrBackground; // standard, so use it break; } } if (COLORISLIGHT(*pclrTextBk)) *pclrHotlight = 0x000000; //Black as hightlight color! else *pclrHotlight = 0xFFFFFF; //White as highlight color! } return bRet; } // Set the colors for the folder - taking care if it's the desktop. void CDefView::_SetFolderColors() { COLORREF clrText, clrTextBk, clrWindow; // Is this view for the desktop? if (_IsDesktop()) { COLORREF clrHotlight; Shell_SysColorChange(); // If we show HTML wallpaper, then get the appropriate colors too! if (_fCombinedView && _GetColorsFromHTMLdoc(&clrTextBk, &clrHotlight)) { // Set the Hotlight color! ListView_SetHotlightColor(_hwndListview, clrHotlight); } else { // Yep. // Clear the background color of the desktop to make it // properly handle transparency. clrTextBk = GetSysColor(COLOR_BACKGROUND); //Reset the Hotlight color sothat the system color can be used. ListView_SetHotlightColor(_hwndListview, CLR_DEFAULT); } // set a text color that will show up over desktop color if (COLORISLIGHT(clrTextBk)) clrText = 0x000000; // black else clrText = 0xFFFFFF; // white clrWindow = CLR_NONE; // Assume transparent // // if there is no wallpaper or pattern we can use // a solid color for the ListView. otherwise we // need to use a transparent ListView, this is much // slower so dont do it unless we need to. // // Don't do this optimization if USER is going to paint // some magic text on the desktop, such as // // "FailSafe" (SM_CLEANBOOT) // "Debug" (SM_DEBUG) // "Build ####" (REGSTR_PATH_DESKTOP\PaintDesktopVersion) // "Evaluation Version" // // too bad there is no SPI_GETWALLPAPER, we need to read // from WIN.INI. // TCHAR szWallpaper[128], szPattern[128]; DWORD dwPaintVersion = 0; szWallpaper[0] = 0; szPattern[0] = 0; HKEY hkey; if (RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_DESKTOP, 0, KEY_QUERY_VALUE, &hkey) == 0) { UINT cb = sizeof(szWallpaper); SHQueryValueEx(hkey, TEXT("Wallpaper"), NULL, NULL, (LPBYTE)szWallpaper, (ULONG*)&cb); cb = sizeof(szPattern); SHQueryValueEx(hkey, TEXT("Pattern"), NULL, NULL, (LPBYTE)szPattern, (ULONG*)&cb); cb = sizeof(dwPaintVersion); SHQueryValueEx(hkey, TEXT("PaintDesktopVersion"), NULL, NULL, (LPBYTE)&dwPaintVersion, (ULONG*)&cb); // Other external criteria for painting the version // // - This is a beta version (has an expiration date) // - A test certificate is installed // if (dwPaintVersion == 0 && IsOS(OS_WIN2000ORGREATER)) { #define REGSTR_PATH_LM_ROOTCERTIFICATES \ TEXT("SOFTWARE\\Microsoft\\SystemCertificates\\Root\\Certificates") #define REGSTR_PATH_GPO_ROOTCERTIFICATES \ TEXT("SOFTWARE\\Policies\\Microsoft\\SystemCertificates\\Root\\Certificates") #define REGSTR_KEY_TESTCERTIFICATE \ TEXT("2BD63D28D7BCD0E251195AEB519243C13142EBC3") dwPaintVersion = (0 != USER_SHARED_DATA->SystemExpirationDate.QuadPart) || SHRegSubKeyExists(HKEY_LOCAL_MACHINE, REGSTR_PATH_LM_ROOTCERTIFICATES TEXT("\\") REGSTR_KEY_TESTCERTIFICATE) || SHRegSubKeyExists(HKEY_LOCAL_MACHINE, REGSTR_PATH_GPO_ROOTCERTIFICATES TEXT("\\") REGSTR_KEY_TESTCERTIFICATE) || SHRegSubKeyExists(HKEY_CURRENT_USER, REGSTR_PATH_GPO_ROOTCERTIFICATES TEXT("\\") REGSTR_KEY_TESTCERTIFICATE); } RegCloseKey(hkey); } if (_fCombinedView || (GetSystemMetrics(SM_CLEANBOOT) == 0 && GetSystemMetrics(SM_DEBUG) == 0 && !dwPaintVersion && (!_fHasDeskWallPaper) && (szWallpaper[0] == 0 || szWallpaper[0] == TEXT('(')) && (szPattern[0] == 0 || szPattern[0] == TEXT('(')))) { clrWindow = GetSysColor(COLOR_BACKGROUND); } } else { // Nope. clrWindow = GetSysColor(COLOR_WINDOW); clrTextBk = clrWindow; clrText = GetSysColor(COLOR_WINDOWTEXT); if (_fs.fFlags & FWF_TRANSPARENT) { IWebBrowser2 *pwb; if (SUCCEEDED(IUnknown_QueryService(_psb, SID_SContainerDispatch, IID_PPV_ARG(IWebBrowser2, &pwb)))) { IDispatch *pdisp; if (SUCCEEDED(pwb->get_Parent(&pdisp))) { IUnknown_HTMLBackgroundColor(pdisp, &clrWindow); pdisp->Release(); } pwb->Release(); } } } if (!_fClassic && ISVALIDCOLOR(_crCustomColors[CRID_CUSTOMTEXTBACKGROUND])) clrTextBk = _crCustomColors[CRID_CUSTOMTEXTBACKGROUND]; if (!_fClassic && ISVALIDCOLOR(_crCustomColors[CRID_CUSTOMTEXT])) clrText = _crCustomColors[CRID_CUSTOMTEXT]; BOOL bChange = FALSE; if (clrWindow != ListView_GetBkColor(_hwndListview)) bChange = ListView_SetBkColor(_hwndListview, clrWindow); if (clrTextBk != ListView_GetTextBkColor(_hwndListview)) bChange = ListView_SetTextBkColor(_hwndListview, clrTextBk); if (clrText != ListView_GetTextColor(_hwndListview)) bChange = ListView_SetTextColor(_hwndListview, clrText); if (bChange) InvalidateRect(_hwndListview, NULL, TRUE); } #define ViewRequiresColumns(x) ((x) == FVM_DETAILS || (x) == FVM_TILE) DWORD CDefView::_LVStyleFromView() { DWORD dwStyle; if (_IsDesktop()) { dwStyle = LVS_NOSCROLL | LVS_ALIGNLEFT; } else { dwStyle = LVS_SHOWSELALWAYS; // make sure selection is visible } // dwStyle |= _UxGetView(); // The listview view is no longer set using the window style, so the call to the // view mapping code has been commented out. // APPCOMPAT: This may be an issue, if apps are depending the exstyle bits on the listview hwnd // in defview. If so, we can set them, but we must take care to exclude any bits outside the 2bit // "view range" in the extended style (namely, tile view) if (_IsAutoArrange()) dwStyle |= LVS_AUTOARRANGE; if (_fs.fFlags & FWF_SINGLESEL) dwStyle |= LVS_SINGLESEL; if (_fs.fFlags & FWF_ALIGNLEFT) dwStyle |= LVS_ALIGNLEFT; if (_fs.fFlags & FWF_NOSCROLL) dwStyle |= LVS_NOSCROLL; return dwStyle; } DWORD CDefView::_LVExStyleFromView() { DWORD dwLVExStyle = 0; if (_fs.fFlags & FWF_SNAPTOGRID) dwLVExStyle |= LVS_EX_SNAPTOGRID; if (_fs.fFlags & FWF_CHECKSELECT) dwLVExStyle |= LVS_EX_CHECKBOXES|LVS_EX_SIMPLESELECT; return dwLVExStyle; } HRESULT CDefView::_GetDetailsHelper(int i, DETAILSINFO *pdi) { HRESULT hr = E_NOTIMPL; if (_pshf2) { hr = _pshf2->GetDetailsOf(pdi->pidl, i, (SHELLDETAILS *)&pdi->fmt); } if (FAILED(hr)) // Don't make NSEs impl all of IShellFolder2 { if (_psd) { // HACK: pdi->fmt is the same layout as SHELLDETAILS hr = _psd->GetDetailsOf(pdi->pidl, i, (SHELLDETAILS *)&pdi->fmt); } else if (HasCB()) { hr = CallCB(SFVM_GETDETAILSOF, i, (LPARAM)pdi); } } return hr; } // Determine if the given defview state struct has valid // state info. If is doesn't, this function massages the // values so it does. UINT CDefView::_GetHeaderCount() { UINT cCols = 0; HWND hwndHead = ListView_GetHeader(_hwndListview); if (hwndHead) { cCols = Header_GetItemCount(hwndHead); } return cCols; } void CDefView::AddColumns() { // so we do this once if (_bLoadedColumns) return; _bLoadedColumns = TRUE; // I also use this as a flag for whether to free pColHdr // // Calculate a reasonable size to initialize the column width to. _cxChar = GetControlCharWidth(_hwndListview); // Check whether there is any column enumerator (ShellDetails or callback) if (_psd || _pshf2 || HasCB()) { // Some shell extensions return S_OK and NULL pstmCols. IStream *pstmCols = NULL; if (SUCCEEDED(CallCB(SFVM_GETCOLSAVESTREAM, STGM_READ, (LPARAM)&pstmCols)) && pstmCols) { _vs.LoadColumns(this, pstmCols); pstmCols->Release(); } // Verify that this has been initialized. This may not be if there was no state stream. _vs.InitializeColumns(this); for (UINT i = 0; i < _vs.GetColumnCount(); ++i) { if (_IsColumnInListView(i)) { UINT iVisible = _RealToVisibleCol(i); LV_COLUMN col; col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; col.fmt = _vs.GetColumnFormat(i); // If column width is not specified in the desktop.ini....... col.cx = _vs.GetColumnWidth(iVisible, _vs.GetColumnCharCount(i) * _cxChar); col.pszText = _vs.GetColumnName(i); col.cchTextMax = MAX_COLUMN_NAME_LEN; col.iSubItem = i; if (col.fmt & LVCFMT_COL_HAS_IMAGES) { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_SUBITEMIMAGES, LVS_EX_SUBITEMIMAGES); col.fmt &= ~LVCFMT_COL_HAS_IMAGES; } ListView_InsertColumn(_hwndListview, iVisible, &col); } } // Set the header control to have zero margin around bitmaps, for the sort arrows Header_SetBitmapMargin(ListView_GetHeader(_hwndListview), 0); ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP, LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP); //We added columns; so, just sync the Column order. _vs.SyncColumnOrder(this, TRUE); } // use real numbers, not visible int cCols = (int)_vs.GetColumnCount(); if (_vs._iLastColumnClick >= cCols) { _vs.InitWithDefaults(this); if (_vs._iLastColumnClick >= cCols || _vs._lParamSort >= cCols) { // our defaults won't work on this view.... // hard code these defaults _vs._lParamSort = 0; _vs._iDirection = 1; _vs._iLastColumnClick = -1; } } } void CDefView::InitSelectionMode() { _dwSelectionMode = 0; if (_fs.fFlags & FWF_SINGLECLICKACTIVATE) { _dwSelectionMode = LVS_EX_TRACKSELECT | LVS_EX_ONECLICKACTIVATE; } else if (!_fClassic) { SHELLSTATE ss; SHGetSetSettings(&ss, SSF_DOUBLECLICKINWEBVIEW, FALSE); if (!ss.fDoubleClickInWebView) _dwSelectionMode = LVS_EX_TRACKSELECT | LVS_EX_ONECLICKACTIVATE; } } void CDefView::_UpdateSelectionMode() { InitSelectionMode(); ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_TRACKSELECT | LVS_EX_ONECLICKACTIVATE | LVS_EX_TWOCLICKACTIVATE, _dwSelectionMode); } DWORD _GetUnderlineStyles() { DWORD dwUnderline = ICON_IE; // Read the icon underline settings. DWORD cb = sizeof(dwUnderline); SHRegGetUSValue(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"), TEXT("IconUnderline"), NULL, &dwUnderline, &cb, FALSE, &dwUnderline, cb); // If it says to use the IE link settings, read them in. if (dwUnderline == ICON_IE) { dwUnderline = ICON_YES; TCHAR szUnderline[8]; cb = sizeof(szUnderline); SHRegGetUSValue(TEXT("Software\\Microsoft\\Internet Explorer\\Main"), TEXT("Anchor Underline"), NULL, szUnderline, &cb, FALSE, szUnderline, cb); // Convert the string to an ICON_ value. if (!lstrcmpi(szUnderline, TEXT("hover"))) dwUnderline = ICON_HOVER; else if (!lstrcmpi(szUnderline, TEXT("no"))) dwUnderline = ICON_NO; else dwUnderline = ICON_YES; } // Convert the ICON_ value into an LVS_EX value. DWORD dwExStyle; switch (dwUnderline) { case ICON_NO: dwExStyle = 0; break; case ICON_HOVER: dwExStyle = LVS_EX_UNDERLINEHOT; break; case ICON_YES: dwExStyle = LVS_EX_UNDERLINEHOT | LVS_EX_UNDERLINECOLD; break; } return dwExStyle; } void CDefView::_UpdateUnderlines() { // Set the new LVS_EX_UNDERLINE flags. ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_UNDERLINEHOT | LVS_EX_UNDERLINECOLD, _GetUnderlineStyles()); } void CDefView::_SetSysImageList() { HIMAGELIST himlLarge, himlSmall; Shell_GetImageLists(&himlLarge, &himlSmall); ListView_SetImageList(_hwndListview, himlLarge, LVSIL_NORMAL); ListView_SetImageList(_hwndListview, himlSmall, LVSIL_SMALL); } void CDefView::_SetTileview() { IImageList* piml; if (SUCCEEDED(SHGetImageList(SHIL_EXTRALARGE, IID_PPV_ARG(IImageList, &piml)))) { ListView_SetImageList(_hwndListview, IImageListToHIMAGELIST(piml), LVSIL_NORMAL); piml->Release(); } } LRESULT CDefView::_OnCreate(HWND hWnd) { _hwndView = hWnd; _hmenuCur = NULL; _uState = SVUIA_DEACTIVATE; _hAccel = LoadAccelerators(HINST_THISDLL, MAKEINTRESOURCE(ACCEL_DEFVIEW)); // Note that we are going to get a WM_SIZE message soon, which will // place this window correctly // Map the ViewMode to the proper listview style DWORD dwStyle = _LVStyleFromView() | LVS_EDITLABELS; DWORD dwExStyle = 0; // If the parent window is mirrored then the treeview window will inheret the mirroring flag // And we need the reading order to be Left to right, which is the right to left in the mirrored mode. if (IS_WINDOW_RTL_MIRRORED(hWnd)) { // This means left to right reading order because this window will be mirrored. dwExStyle |= WS_EX_RTLREADING; } // don't set this as in webview this is normally off, having this // set causes a 3d edge to flash on in a refresh if (!_ShouldShowWebView() && !_IsDesktop() && !(_fs.fFlags & FWF_NOCLIENTEDGE)) { dwExStyle |= WS_EX_CLIENTEDGE; } if (_IsOwnerData()) dwStyle |= LVS_OWNERDATA; _hwndListview = CreateWindowEx(dwExStyle, WC_LISTVIEW, TEXT("FolderView"), // MSAA name dwStyle | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_SHAREIMAGELISTS, 0, 0, 0, 0, hWnd, (HMENU)ID_LISTVIEW, HINST_THISDLL, NULL); if (_hwndListview) { // Set up non-viewmode-dependant listview information here. // Other flags are set up in _SwitchToViewFVM DWORD dwLVExStyle = _LVExStyleFromView() | LVS_EX_INFOTIP | LVS_EX_LABELTIP; if (_IsDesktop()) { if (GetNumberOfMonitors() > 1) dwLVExStyle |= LVS_EX_MULTIWORKAREAS; } else { dwLVExStyle |= LVS_EX_DOUBLEBUFFER; // Enable double buffering for all but desktop for affects } // turn on infotips -- window was just created, so all LVS_EX bits are off ListView_SetExtendedListViewStyle(_hwndListview, dwLVExStyle); // Get the proper RTL bits to pass on to our child windows _fmt = 0; // Be sure that the OS is supporting the flags DATE_LTRREADING and DATE_RTLREADING if (g_bBiDiPlatform) { // Get the date format reading order LCID locale = GetUserDefaultLCID(); if ((PRIMARYLANGID(LANGIDFROMLCID(locale)) == LANG_ARABIC)) { // Get the real list view windows ExStyle. // [msadek]; we shouldn't check for either WS_EX_RTLREADING OR RTL_MIRRORED_WINDOW // on localized builds we have both of them to display dirve letters,..etc correctly // on enabled builds we have none of them. let's check on RTL_MIRRORED_WINDOW only if (GetWindowLong(_hwndListview, GWL_EXSTYLE) & RTL_MIRRORED_WINDOW) _fmt = LVCFMT_RIGHT_TO_LEFT; else _fmt = LVCFMT_LEFT_TO_RIGHT; } } // Get hwndInfotip (the control for all listview infotips). HWND hwndInfotip = ListView_GetToolTips(_hwndListview); if (hwndInfotip) { // make the tooltip window to be topmost window (set the TTS_TOPMOST style bit for the tooltip) SetWindowPos(hwndInfotip, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // Initialize hwndInfotip. _InitInfotipControl(hwndInfotip); } _UpdateUnderlines(); // IShellDetails for old callers, new guys use IShellFolder2 ASSERT(_psd == NULL); _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IShellDetails, &_psd)); // App compat - some apps need columns loaded first thing if (SHGetAppCompatFlags(ACF_LOADCOLUMNHANDLER) & ACF_LOADCOLUMNHANDLER) { AddColumns(); } _SetFolderColors(); } // Create _hwndInfotip (the control for all non-listview infotips). _hwndInfotip = _CreateInfotipControl(hWnd); if (_hwndInfotip) { // Initialize _hwndInfotip. _InitInfotipControl(_hwndInfotip); } return _hwndListview ? 0 : -1; // 0 is success, -1 is failure from WM_CREATE } HWND CDefView::_CreateInfotipControl(HWND hwndParent) { // hwndInfotip is currently expected to be destroyed by destruction of // the parent hwnd (hwndParent). Thus, hwndParent should not be NULL. ASSERT(hwndParent != NULL); // Sanity check. // Create hwndInfotip. return ::CreateWindowEx( IS_WINDOW_RTL_MIRRORED(hwndParent) || IS_BIDI_LOCALIZED_SYSTEM() ? WS_EX_LAYOUTRTL : 0, TOOLTIPS_CLASS, NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, NULL, g_hinst, NULL); } void CDefView::_InitInfotipControl(HWND hwndInfotip) { ASSERT(hwndInfotip); // Set the length of time the pointer must remain stationary within a tool's // bounding rectangle before the ToolTip window appears to 2 times the default. INT iTime = ::SendMessage(hwndInfotip, TTM_GETDELAYTIME, TTDT_INITIAL, 0); ::SendMessage(hwndInfotip, TTM_SETDELAYTIME, TTDT_INITIAL, (LPARAM)(INT)MAKELONG(iTime * 2, 0)); // Set the length of time a ToolTip window remains visible if the pointer // is stationary within a tool's bounding rectangle to a very large value. ::SendMessage(hwndInfotip, TTM_SETDELAYTIME, TTDT_AUTOPOP, (LPARAM)(INT)MAKELONG(MAXSHORT, 0)); } // "Auto" AutoArrange means re-position if we are in a positioned view // and the listview is not in auto-arrange mode. we do this to re-layout // the icons in cases where that makes sense HRESULT CDefView::_AutoAutoArrange(DWORD dwReserved) { if (!_fUserPositionedItems && _IsPositionedView() && !(GetWindowStyle(_hwndListview) & LVS_AUTOARRANGE)) { ListView_Arrange(_hwndListview, LVA_DEFAULT); } return S_OK; } LRESULT CDefView::WndSize(HWND hWnd) { RECT rc; // We need to dismiss "name edit" mode, if we are in. _DismissEdit(); // Get the client size. GetClientRect(hWnd, &rc); // Set the Static to be the Client size. if (_hwndStatic) { MoveWindow(_hwndStatic, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, TRUE); HWND hAnimate = ::GetWindow (_hwndStatic, GW_CHILD); if (hAnimate) { MoveWindow(hAnimate, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, TRUE); } RedrawWindow(_hwndStatic, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN); } // Set all windows to their new rectangles. _cFrame.SetRect(&rc); // Don't resize _hwndListview if a DefViewOC is using it. // // If we're waiting for a Web View (!_fCanActivateNow), then it // doesn't make sense to resize the _hwndListview -- just extra // work, right? But in the non-WebView case, it's this first // resize which sets the listview size, and then there are no // more. Unfortunately, the first resize comes in when the // _hwndListview is created, which is *before* _fCanActivateNow // can possibly be set. if (!_fGetWindowLV && !_pDUIView) { SetWindowPos(_hwndListview, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOACTIVATE); OnResizeListView(); } if (_pDUIView) { _pDUIView->SetSize (&rc); _AutoAutoArrange(0); } CallCB(SFVM_SIZE, 0, 0); return 1; } UINT _GetMenuIDFromViewMode(UINT uViewMode) { ASSERTMSG(FVM_FIRST <= uViewMode && uViewMode <= FVM_LAST, "_GetMenuIDFromViewMode received unknown uViewMode"); return SFVIDM_VIEW_FIRSTVIEW + uViewMode - FVM_FIRST; } void CDefView::CheckToolbar() { if (SHGetAppCompatFlags(ACF_WIN95DEFVIEW) & ACF_WIN95DEFVIEW) { int idCmdCurView = _GetMenuIDFromViewMode(_fs.ViewMode); // preserve win95 behavior for dumb corel apps for (int idCmd = SFVIDM_VIEW_ICON; idCmd <= SFVIDM_VIEW_DETAILS; idCmd++) { _psb->SendControlMsg( FCW_TOOLBAR, TB_CHECKBUTTON, idCmd, (LPARAM)(idCmd == idCmdCurView), NULL); } } } void CDefView::OnListViewDelete(int iItem, LPITEMIDLIST pidlToFree, BOOL fCallCB) { LPCITEMIDLIST pidlReal = _GetPIDLParam((LPARAM)pidlToFree, iItem); if (fCallCB) { CallCB(SFVM_DELETEITEM, 0, (LPARAM)pidlReal); } ILFree(pidlToFree); // NULL in owner data case } // NOTE: many keys are handled as accelerators void CDefView::HandleKeyDown(LV_KEYDOWN *pnmhdr) { // REVIEW: these are things not handled by accelerators, see if we can // make them all based on accelerators switch (pnmhdr->wVKey) { case VK_ESCAPE: if (_bHaveCutStuff) OleSetClipboard(NULL); break; } } // This function checks to see if we are in virtual mode or not. If we are in // virtual mode, we always need to ask our folder we are viewing for the item and // not the listview. LPCITEMIDLIST CDefView::_GetPIDL(int i) { if (_IsOwnerData()) { LPCITEMIDLIST pidl = NULL; CallCB(SFVM_GETITEMIDLIST, i, (LPARAM)&pidl); return pidl; } return (LPCITEMIDLIST)LVUtil_GetLParam(_hwndListview, i); } LPCITEMIDLIST CDefView::_GetPIDLParam(LPARAM lParam, int i) { return lParam ? (LPCITEMIDLIST)lParam : _GetPIDL(i); } // returns an array of LPCITEMIDLIST for objects in the view (selected or all) // the "focused" item is always in array entry 0. this array contains poitners to pidls // owned stored in the listview, so YOU SHOULD NOT FREE THEM OR MESS WITH THEM IN ANYWAY. // this also implies the lifetime of this array must be shorter than the listview // data it points to. that is if the view changes under you you are hosed. // // Notes: this function returns LP*C*ITEMIDLIST. The caller is not // supposed alter or delete them. Their lifetime are very short (until the // list view is modified). typedef struct { LPCITEMIDLIST pidl; POINT pt; int iItem; } POS_SORT_INFO; // standard compare returns // -1 1 < 2 // 0 1 = 2 // 1 1 > 2 // // NOTE: in the RTL_MIRRORED_WINDOW case the coords are reversed for us int _CmpTopToBottomLeftToRight(POS_SORT_INFO *psi1, POS_SORT_INFO *psi2, LPARAM lParam) { int iCmp = psi1->pt.y - psi2->pt.y; if (0 == iCmp) { iCmp = psi1->pt.x - psi2->pt.x; } return iCmp; } int _CmpLeftToRightTopToBottom(POS_SORT_INFO *psi1, POS_SORT_INFO *psi2, LPARAM lParam) { int iCmp = psi1->pt.x - psi2->pt.x; if (0 == iCmp) { iCmp = psi1->pt.y - psi2->pt.y; } return iCmp; } CDPA::_PFNDPACOMPARE _GetSortFunction(HWND hwndListview) { if (GetWindowStyle(hwndListview) & LVS_ALIGNLEFT) { return _CmpLeftToRightTopToBottom; // desktop LV_VIEW_ICON case } else { UINT uViewMode = ListView_GetView(hwndListview); switch (uViewMode) { case LV_VIEW_DETAILS: case LV_VIEW_LIST: return _CmpLeftToRightTopToBottom; case LV_VIEW_TILE: case LV_VIEW_ICON: default: return _CmpTopToBottomLeftToRight; } } } UINT CDefView::_GetItemArray(LPCITEMIDLIST apidl[], UINT capidl, UINT uWhat) { UINT cItems = 0; if ((uWhat & SVGIO_TYPE_MASK) == SVGIO_SELECTION) { cItems = ListView_GetSelectedCount(_hwndListview); } else if ((uWhat & SVGIO_TYPE_MASK) == SVGIO_CHECKED) { int iItem = ListView_GetItemCount(_hwndListview) - 1; for (; iItem >= 0; iItem--) { if (ListView_GetCheckState(_hwndListview, iItem)) cItems++; } } else { cItems = ListView_GetItemCount(_hwndListview); } if (apidl) { UINT uType = (SVGIO_SELECTION == (uWhat & SVGIO_TYPE_MASK)) ? LVNI_SELECTED : LVNI_ALL; BOOL bArrayFilled = FALSE; // gets set on success of the sort code path // optimize the 1 case, the sort below is not needed if (!(SVGIO_FLAG_VIEWORDER & uWhat) && (capidl > 1)) { CDPA dpaItemInfo; // pick a grow size of capidl so that we get a single alloc // when we add the first item to the array if (dpaItemInfo.Create(capidl)) { POS_SORT_INFO *ppsi = new POS_SORT_INFO[capidl]; if (ppsi) { UINT iDPAIndex = 0; for (int iListView = ListView_GetNextItem(_hwndListview, -1, uType); (iListView >= 0) && (iDPAIndex < capidl); iListView = ListView_GetNextItem(_hwndListview, iListView, uType)) { // if we want checked then it must be checked, otherwise just return (or skip) if ((SVGIO_CHECKED != (uWhat & SVGIO_TYPE_MASK)) || ListView_GetCheckState(_hwndListview, iListView)) { ppsi[iDPAIndex].pidl = _GetPIDL(iListView); ppsi[iDPAIndex].iItem = iListView; ListView_GetItemPosition(_hwndListview, iListView, &ppsi[iDPAIndex].pt); // this may fail, but we catch that case below dpaItemInfo.SetPtr(iDPAIndex, &ppsi[iDPAIndex]); iDPAIndex++; } } // make sure the DPA got all of the items, if not // we fall through to the unsorted case if (dpaItemInfo.GetPtrCount() == capidl) { dpaItemInfo.Sort(_GetSortFunction(_hwndListview), 0); int iFirstItem = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED); // compute the start index in the dpa based on iFirstItem. this is to // rotate the array so that iFirstItem is first in the list for (iDPAIndex = 0; iDPAIndex < (UINT)dpaItemInfo.GetPtrCount(); iDPAIndex++) { if (dpaItemInfo.FastGetPtr(iDPAIndex)->iItem == iFirstItem) { break; // iDPAIndex setup for loop below } } for (int i = 0; i < dpaItemInfo.GetPtrCount(); i++, iDPAIndex++) { if (iDPAIndex >= (UINT)dpaItemInfo.GetPtrCount()) iDPAIndex = 0; // wrap back to zero apidl[i] = dpaItemInfo.FastGetPtr(iDPAIndex)->pidl; } bArrayFilled = TRUE; // we have the results we want delete [] ppsi; } } dpaItemInfo.Destroy(); } } if (!bArrayFilled) { UINT i = 0; for (int iListView = ListView_GetNextItem(_hwndListview, -1, uType); (iListView >= 0) && (i < capidl); iListView = ListView_GetNextItem(_hwndListview, iListView, uType)) { // if we want checked then it must be checked, otherwise just return (or skip) if ((SVGIO_CHECKED != (uWhat & SVGIO_TYPE_MASK)) || ListView_GetCheckState(_hwndListview, iListView)) { apidl[i++] = _GetPIDL(iListView); } } } } return cItems; } // // get the array of IDList from the selection and calls // IShellFolder::GetUIObjectOf member to get the specified UI object // interface. // HRESULT CDefView::_GetUIObjectFromItem(REFIID riid, void **ppv, UINT uWhat, BOOL fSetPoints) { LPCITEMIDLIST *apidl; UINT cItems; HRESULT hr; if (SVGIO_SELECTION == (uWhat & SVGIO_TYPE_MASK)) { hr = GetSelectedObjects(&apidl, &cItems); } else { hr = _GetItemObjects(&apidl, uWhat, &cItems); } if (SUCCEEDED(hr)) { if (cItems) { hr = _pshf->GetUIObjectOf(_hwndMain, cItems, apidl, riid, 0, ppv); if (SUCCEEDED(hr) && (IID_IDataObject == riid) && fSetPoints) { _SetPoints(cItems, apidl, (IDataObject *)*ppv); } LocalFree((HLOCAL)apidl); } else hr = E_INVALIDARG; } return hr; } // If the browser has a Tree then we want to use explore. UINT CDefView::_GetExplorerFlag() { return IsExplorerBrowser(_psb) ? CMF_EXPLORE : 0; } // creates a selection object out of the current selection. IShellItemArray* CDefView::_CreateSelectionShellItemArray(void) { IShellItemArray *pSelectionObj = NULL; LPCITEMIDLIST *apidl; UINT cItems; if (SUCCEEDED(_GetItemObjects(&apidl, SVGIO_SELECTION | SVGIO_FLAG_VIEWORDER, &cItems)) && cItems) { SHCreateShellItemArray(NULL, _pshf, cItems, apidl, &pSelectionObj); LocalFree(apidl); } return pSelectionObj; } DWORD CDefView::_AttributesFromSel(DWORD dwAttributesNeeded) { // If this gets hit then chances are it's a performance problem... // if (_fSelectionChangePending) { TraceMsg(TF_WARNING, "Potential perf badness: may be asking for attributes during OnLVNUpdateItem!"); if (_pSelectionShellItemArray) ATOMICRELEASE(_pSelectionShellItemArray); _pSelectionShellItemArray = _CreateSelectionShellItemArray(); } DWORD dwAttributes = 0; if (_pSelectionShellItemArray) { _pSelectionShellItemArray->GetAttributes(SIATTRIBFLAGS_APPCOMPAT, dwAttributesNeeded, &dwAttributes); } return dwAttributes; } // IContextMenuSite: // Defview's context menu implementation isn't very clean. As a temporary step towards // cleaning it up (CONTEXT and BACK_CONTEXT are intermingled), use the new DOCONTEXTMENUPOPUP range // HRESULT CDefView::DoContextMenuPopup(IUnknown* punkCM, UINT fFlags, POINT pt) { return _DoContextMenuPopup(punkCM, fFlags, pt, FALSE); } HRESULT CDefView::_DoContextMenuPopup(IUnknown* punk, UINT fFlags, POINT pt, BOOL fListviewItem) { IContextMenu* pcm; HRESULT hr = punk->QueryInterface(IID_PPV_ARG(IContextMenu, &pcm)); if (SUCCEEDED(hr)) { hr = E_OUTOFMEMORY; HMENU hmContext = CreatePopupMenu(); if (hmContext) { fFlags |= _GetExplorerFlag(); if (0 > GetKeyState(VK_SHIFT)) fFlags |= CMF_EXTENDEDVERBS; IContextMenu3* pcm3; if (SUCCEEDED(pcm->QueryInterface(IID_PPV_ARG(IContextMenu3, &pcm3)))) { fFlags |= CMF_ICM3; pcm3->Release(); } // Give the context menu a site if it doesn't have one already IUnknown* punkSite; if (SUCCEEDED(IUnknown_GetSite(pcm, IID_PPV_ARG(IUnknown, &punkSite)))) { punkSite->Release(); } else { IUnknown_SetSite(pcm, SAFECAST(this, IShellView2*)); } hr = pcm->QueryContextMenu(hmContext, 0, SFVIDM_BACK_CONTEXT_FIRST, SFVIDM_BACK_CONTEXT_LAST, fFlags); if (SUCCEEDED(hr)) { // Must preinitialize to NULL; Adaptec Easy CD Creator 3.5 does not // null out the pointer on failure. ICommDlgBrowser2 *pcdb2 = NULL; _psb->QueryInterface(IID_PPV_ARG(ICommDlgBrowser2, &pcdb2)); // If this is the common dialog browser, we need to make the // default command "Select" so that double-clicking (which is // open in common dialog) makes sense. if (_IsCommonDialog()) { // make sure this is an item if (fListviewItem) { HMENU hmSelect = SHLoadPopupMenu(HINST_THISDLL, POPUP_COMMDLG_POPUPMERGE); // If we have a pointer to the ICommDlgBrowser2 interface // query if this interface wants to change the text of the // default verb. This interface is needed in the common print // dialog to change the default text from 'Select' to 'Print'. if (pcdb2) { WCHAR szTextW[MAX_PATH] = {0}; if (pcdb2->GetDefaultMenuText(this, szTextW, ARRAYSIZE(szTextW)) == S_OK) { MENUITEMINFO mi = {0}; mi.cbSize = sizeof(mi); mi.fMask = MIIM_TYPE; mi.fType = MFT_STRING; mi.dwTypeData = szTextW; SetMenuItemInfo(hmSelect, 0, MF_BYPOSITION, &mi); } } // NOTE: Since commdlg always eats the default command, // we don't care what id we assign hmSelect, as long as it // doesn't conflict with any other context menu id. // SFVIDM_CONTEXT_FIRST-1 won't conflict with anyone. Shell_MergeMenus(hmContext, hmSelect, 0, (UINT)(SFVIDM_BACK_CONTEXT_FIRST-1), (UINT)-1, MM_ADDSEPARATOR); SetMenuDefaultItem(hmContext, 0, MF_BYPOSITION); DestroyMenu(hmSelect); } } _SHPrettyMenu(hmContext); // If this is the common dialog browser 2, we need inform it // the context menu is has started. This notifiction is use in // the common print dialog on NT which hosts the printers folder. // Common dialog want to relselect the printer object if the user // selected the context menu from the background. if (pcdb2) { pcdb2->Notify(this, CDB2N_CONTEXTMENU_START); } // To reduce some menu message forwarding, throw away _pcmFile if we have one // (Since we can't have a TrackPopupMenu and a File menu open at the same time) IUnknown_SetSite(_pcmFile, NULL); ATOMICRELEASE(_pcmFile); // stash pcm in _pcmContextMenuPopup so we can forward menu messages ASSERT(NULL==_pcmContextMenuPopup); _pcmContextMenuPopup = pcm; _pcmContextMenuPopup->AddRef(); int idDefault = GetMenuDefaultItem(hmContext, MF_BYCOMMAND, 0); int idCmd = TrackPopupMenu(hmContext, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN, pt.x, pt.y, 0, _hwndView, NULL); ATOMICRELEASE(_pcmContextMenuPopup); if ((idCmd == idDefault) && _OnDefaultCommand() == S_OK) { // commdlg browser ate the default command } else if (idCmd == 0) { // No item selected } else if (idCmd >= SFVIDM_BACK_CONTEXT_FIRST && idCmd <= SFVIDM_BACK_CONTEXT_LAST) { idCmd -= SFVIDM_BACK_CONTEXT_FIRST; // We need to special case the rename command (just in case a legacy contextmenu impl relied on this behavior) TCHAR szCommandString[64]; ContextMenu_GetCommandStringVerb(pcm, idCmd, szCommandString, ARRAYSIZE(szCommandString)); if (lstrcmpi(szCommandString, c_szRename) == 0) { DoRename(); } else { CMINVOKECOMMANDINFOEX ici = { 0 }; ici.cbSize = sizeof(ici); ici.hwnd = _hwndMain; ici.lpVerb = IntToPtr_(LPCSTR, idCmd); ici.nShow = SW_NORMAL; ici.ptInvoke = pt; ici.fMask |= CMIC_MASK_PTINVOKE | CMIC_MASK_FLAG_LOG_USAGE; // record if shift or control was being held down SetICIKeyModifiers(&ici.fMask); _InvokeContextMenu(pcm, &ici); } } else { RIPMSG(FALSE, "CDefView::DoContextMenuPopup - Some IContextMenu inserted an ID out of our range. Ignoring."); } // If this is the common dialog browser 2, we need inform it // the context menu is done. This notifiction is use in // the common print dialog on NT which hosts the printers folder. // Common dialog want to relselect the printer object if the user // selected the context menu from the background. if (pcdb2) { pcdb2->Notify(this, CDB2N_CONTEXTMENU_DONE); pcdb2->Release(); } } DestroyMenu(hmContext); } // Always remove the site even if we didn't set it -- once used, the IContextMenu is dead. IUnknown_SetSite(pcm, NULL); pcm->Release(); } return hr; } void CDefView::ContextMenu(DWORD dwPos) { int iItem; UINT fFlags = 0; POINT pt; if (SHRestricted(REST_NOVIEWCONTEXTMENU)) { return; } // if shell32's global copy of the stopwatch mode is not init'd yet, init it now. if (g_dwStopWatchMode == 0xffffffff) g_dwStopWatchMode = StopWatchMode(); if (g_dwStopWatchMode) StopWatch_Start(SWID_MENU, TEXT("Defview ContextMenu Start"), SPMODE_SHELL | SPMODE_DEBUGOUT); if (IsWindowVisible(_hwndListview) && (IsChildOrSelf(_hwndListview, GetFocus()) == S_OK)) { // Find the selected item iItem = ListView_GetNextItem(_hwndListview, -1, LVNI_SELECTED); } else { iItem = -1; } if (dwPos == (DWORD) -1) { if (iItem != -1) { RECT rc; int iItemFocus = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED|LVNI_SELECTED); if (iItemFocus == -1) iItemFocus = iItem; // // Note that LV_GetItemRect returns it in client coordinate! // ListView_GetItemRect(_hwndListview, iItemFocus, &rc, LVIR_ICON); pt.x = (rc.left + rc.right) / 2; pt.y = (rc.top + rc.bottom) / 2; } else { pt.x = pt.y = 0; } MapWindowPoints(_hwndListview, HWND_DESKTOP, &pt, 1); } else { pt.x = GET_X_LPARAM(dwPos); pt.y = GET_Y_LPARAM(dwPos); } IContextMenu* pcm; LPARAM uemEvent; if (iItem == -1) { DECLAREWAITCURSOR; SetWaitCursor(); // use the background context menu wrapper GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARG(IContextMenu, &pcm)); ResetWaitCursor(); // set the max range for these, so that they are unaffected... uemEvent = _IsDesktop() ? UIBL_CTXTDESKBKGND : UIBL_CTXTDEFBKGND; } else { fFlags |= CMF_CANRENAME; // One or more items are selected, let the folder add menuitems. _CreateSelectionContextMenu(IID_PPV_ARG(IContextMenu, &pcm)); uemEvent = _IsDesktop() ? UIBL_CTXTDESKITEM : UIBL_CTXTDEFITEM; } UEMFireEvent(&UEMIID_SHELL, UEME_INSTRBROWSER, UEMF_INSTRUMENT, UIBW_UICONTEXT, uemEvent); if (g_dwStopWatchMode) StopWatch_Stop(SWID_MENU, TEXT("Defview ContextMenu Stop (!SafeToDefaultVerb)"), SPMODE_SHELL | SPMODE_DEBUGOUT); if (IsSafeToDefaultVerb() && pcm) { _DoContextMenuPopup(pcm, fFlags, pt, iItem != -1); } ATOMICRELEASE(pcm); } BOOL CDefView::_GetItemSpacing(ITEMSPACING *pis) { DWORD dwSize = ListView_GetItemSpacing(_hwndListview, TRUE); pis->cxSmall = GET_X_LPARAM(dwSize); pis->cySmall = GET_Y_LPARAM(dwSize); dwSize = ListView_GetItemSpacing(_hwndListview, FALSE); pis->cxLarge = GET_X_LPARAM(dwSize); pis->cyLarge = GET_Y_LPARAM(dwSize); return _fs.ViewMode != FVM_ICON; } BOOL _DidDropOnRecycleBin(IDataObject *pdtobj) { CLSID clsid; return SUCCEEDED(DataObj_GetDropTarget(pdtobj, &clsid)) && IsEqualCLSID(clsid, CLSID_RecycleBin); } void CDefView::_SetPoints(UINT cidl, LPCITEMIDLIST *apidl, IDataObject *pdtobj) { POINT pt; GetDragPoint(&pt); ::SetPositionItemsPoints(SAFECAST(this, IFolderView*), apidl, cidl, pdtobj, &pt); } LRESULT CDefView::_OnBeginDrag(NM_LISTVIEW * pnm) { POINT ptOffset = pnm->ptAction; // hwndLV client coords // This DefView is used as a drag source so we need to see if it's // is hosted by something that can disguise the action. if (S_OK != _ZoneCheck(PUAF_NOUI, URLACTION_SHELL_WEBVIEW_VERB)) { // This DefView is hosted in HTML, so we need to turn off the // ability of this defview from being a drag source. return 0; } _OnDelayedSelectionChange(); if (FAILED(_FireEvent(DISPID_BEGINDRAG))) // script canceles dragging return 0; DWORD dwEffect = _AttributesFromSel(SFGAO_CANDELETE | DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY); // Turn on DROPEFFECT_MOVE for any deleteable item // (this is so the item can be dragged to the recycle bin) if (SFGAO_CANDELETE & dwEffect) { dwEffect |= DROPEFFECT_MOVE; } // Mask out all attribute bits that aren't also DROPEFFECT bits: dwEffect &= (DROPEFFECT_LINK | DROPEFFECT_MOVE | DROPEFFECT_COPY); // Somebody began dragging in our window, so store that fact _bDragSource = TRUE; // save away the anchor point _ptDragAnchor = pnm->ptAction; LVUtil_ClientToLV(_hwndListview, &_ptDragAnchor); ClientToScreen(_hwndListview, &ptOffset); // now in screen // can't use _pdoSelection here since we need fSetPoints IDataObject *pdtobj; if (SUCCEEDED(_GetUIObjectFromItem(IID_PPV_ARG(IDataObject, &pdtobj), SVGIO_SELECTION, TRUE))) { // Give the source a chance to alter the drop effect. CallCB(SFVM_ALTERDROPEFFECT, (WPARAM)&dwEffect, (LPARAM)pdtobj); if (DAD_SetDragImageFromWindow(_hwndListview, &ptOffset, pdtobj)) { if (DRAGDROP_S_DROP == SHDoDragDrop(_hwndMain, pdtobj, NULL, dwEffect, &dwEffect)) { if (S_OK != CallCB(SFVM_DIDDRAGDROP, (WPARAM)dwEffect, (LPARAM)pdtobj)) { // the return of DROPEFFECT_MOVE tells us we need to delete the data // see if we need to do that now... // NOTE: we can't trust the dwEffect return result from DoDragDrop() because // some apps (adobe photoshop) return this when you drag a file on them that // they intend to open. so we invented the "PreformedEffect" as a way to // know what the real value is, that is why we test both of these. if ((DROPEFFECT_MOVE == dwEffect) && (DROPEFFECT_MOVE == DataObj_GetDWORD(pdtobj, g_cfPerformedDropEffect, DROPEFFECT_NONE))) { // enable UI for the recycle bin case (the data will be lost // as the recycle bin really can't recycle stuff that is not files) UINT uFlags = _DidDropOnRecycleBin(pdtobj) ? 0 : CMIC_MASK_FLAG_NO_UI; SHInvokeCommandOnDataObject(_hwndMain, NULL, pdtobj, uFlags,c_szDeleteA); } } } // // We need to clear the dragged image only if we still have the drag context. // DAD_SetDragImage((HIMAGELIST)-1, NULL); } pdtobj->Release(); } _bDragSource = FALSE; // All done dragging return 0; } void CDefView::_FocusOnSomething(void) { int iFocus = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED); if (iFocus == -1) { if (ListView_GetItemCount(_hwndListview) > 0) { // set the focus on the first item. ListView_SetItemState(_hwndListview, 0, LVIS_FOCUSED, LVIS_FOCUSED); } } } HRESULT CDefView::_InvokeContextMenu(IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici) { TCHAR szWorkingDir[MAX_PATH]; CHAR szWorkingDirAnsi[MAX_PATH]; if (SUCCEEDED(CallCB(SFVM_GETWORKINGDIR, ARRAYSIZE(szWorkingDir), (LPARAM)szWorkingDir))) { // Fill in both the ansi working dir and the unicode one // since we don't know who's gonna be processing this thing. SHUnicodeToAnsi(szWorkingDir, szWorkingDirAnsi, ARRAYSIZE(szWorkingDirAnsi)); pici->lpDirectory = szWorkingDirAnsi; pici->lpDirectoryW = szWorkingDir; pici->fMask |= CMIC_MASK_UNICODE; } // In case the ptInvoke field was not already set for us, guess where // that could be. (dli) maybe should let the caller set all points if (!(pici->fMask & CMIC_MASK_PTINVOKE)) { if (GetCursorPos(&pici->ptInvoke)) pici->fMask |= CMIC_MASK_PTINVOKE; } pici->fMask |= CMIC_MASK_ASYNCOK; _OnDelayedSelectionChange(); HRESULT hr = _FireEvent(DISPID_VERBINVOKED); if (SUCCEEDED(hr)) hr = pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)pici); return hr; } DWORD CDefView::_GetNeededSecurityAction(void) { DWORD dwUrlAction = 0; // this blanks out everything that has SFGAO_FOLDER -- what about zip/cab? // this check is used in context menu ops to see if the default verb is // trusted -- for the zip/cab case, we DO trust the default verb since we // expose them as folders throughout the rest of the shell. if (!(SFGAO_FOLDER & _AttributesFromSel(SFGAO_FOLDER))) { // If we are hosted by Trident, Zone Check Action. // this is how we detect if we are hosted in an IFRAME or OBJECT tag IUnknown *punk; if (SUCCEEDED(_psb->QueryInterface(IID_IIsWebBrowserSB, (void **)&punk))) { dwUrlAction = URLACTION_SHELL_VERB; punk->Release(); } else if (_fGetWindowLV) { // If we are using WebView, Zone Check Action. dwUrlAction = URLACTION_SHELL_WEBVIEW_VERB; } } return dwUrlAction; } HRESULT CDefView::_ZoneCheck(DWORD dwFlags, DWORD dwAllowAction) { HRESULT hr = S_OK; DWORD dwUrlAction = _GetNeededSecurityAction(); if (dwUrlAction && (dwUrlAction != dwAllowAction)) { // First check if our parent wants to generate our context (Zone/URL). IInternetHostSecurityManager *pihsm; hr = IUnknown_QueryService(_psb, IID_IInternetHostSecurityManager, IID_PPV_ARG(IInternetHostSecurityManager, &pihsm)); if (FAILED(hr) && _cFrame._pDocView) { // Yes, so if we are in WebView mode, check the instance of Trident that is // displaying the WebView content, because that content could discuise the DefView // and make the user unknowingly do something bad. hr = IUnknown_QueryService(_cFrame._pDocView, IID_IInternetHostSecurityManager, IID_PPV_ARG(IInternetHostSecurityManager, &pihsm)); } if (SUCCEEDED(hr)) { // This is the prefered way to do the zone check. hr = ZoneCheckHost(pihsm, dwUrlAction, dwFlags | PUAF_FORCEUI_FOREGROUND); pihsm->Release(); } else { // No, we were not able to get the interface. So fall back to zone checking the // URL that comes from the pidl we are at. TCHAR szPathSource[MAX_PATH]; if (_GetPath(szPathSource)) { // Try to get a IInternetSecurityMgrSite so our UI will be modal. IInternetSecurityMgrSite *pisms; if (SUCCEEDED(IUnknown_QueryService(_psb, SID_STopLevelBrowser, IID_PPV_ARG(IInternetSecurityMgrSite, &pisms)))) { // TODO: Have this object support IInternetSecurityMgrSite in case our parent doesn't provide one. // Make that code support ::GetWindow() and ::EnableModless() or we won't get the modal behavior // needed for VB and AOL. hr = ZoneCheckUrl(szPathSource, dwUrlAction, dwFlags | PUAF_ISFILE | PUAF_FORCEUI_FOREGROUND, pisms); pisms->Release(); } } } } return hr; } BOOL CDefView::IsSafeToDefaultVerb(void) { return S_OK == _ZoneCheck(PUAF_WARN_IF_DENIED, 0); } HRESULT CDefView::_InvokeContextMenuVerb(IContextMenu* pcm, LPCSTR pszVerb, UINT uKeyFlags, DWORD dwCMMask) { DECLAREWAITCURSOR; SetWaitCursor(); CMINVOKECOMMANDINFOEX ici = {0}; ici.cbSize = sizeof(ici); ici.hwnd = _hwndMain; ici.nShow = SW_NORMAL; ici.fMask = dwCMMask; // Get the point where the double click is invoked. GetMsgPos(&ici.ptInvoke); ici.fMask |= CMIC_MASK_PTINVOKE; // record if shift or control was being held down SetICIKeyModifiers(&ici.fMask); IUnknown_SetSite(pcm, SAFECAST(this, IOleCommandTarget *)); // Security note: we assume all non default verbs safe HRESULT hr; if (pszVerb || (IsSafeToDefaultVerb() && SUCCEEDED(_FireEvent(DISPID_DEFAULTVERBINVOKED)))) { WCHAR szVerbW[128]; if (pszVerb) { ici.lpVerb = pszVerb; SHAnsiToUnicode(pszVerb, szVerbW, ARRAYSIZE(szVerbW)); ici.lpVerbW = szVerbW; ici.fMask |= CMIC_MASK_UNICODE; } HMENU hmenu = CreatePopupMenu(); if (hmenu) { UINT fFlags = _GetExplorerFlag(); if (NULL == pszVerb) fFlags |= CMF_DEFAULTONLY; // optmization // SHIFT + dbl click does a Explore by default if (uKeyFlags & LVKF_SHIFT) fFlags |= CMF_EXPLORE; pcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, fFlags); if (pszVerb) hr = S_OK; else { UINT idCmd = GetMenuDefaultItem(hmenu, MF_BYCOMMAND, GMDI_GOINTOPOPUPS); if (idCmd == -1) { hr = E_FAIL; } else { ici.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - CONTEXTMENU_IDCMD_FIRST); hr = S_OK; } } if (SUCCEEDED(hr)) { // need to reset it so that user won't blow off the app starting cursor // also so that if we won't leave the wait cursor up when we're not waiting // (like in a prop sheet or something that has a message loop ResetWaitCursor(); hcursor_wait_cursor_save = NULL; hr = _InvokeContextMenu(pcm, &ici); } DestroyMenu(hmenu); } else { hr = E_OUTOFMEMORY; } } else { hr = E_ACCESSDENIED; } IUnknown_SetSite(pcm, NULL); if (hcursor_wait_cursor_save) ResetWaitCursor(); return hr; } HRESULT CDefView::_InvokeContextMenuVerbOnSelection(LPCSTR pszVerb, UINT uKeyFlags, DWORD dwCMMask) { if (NULL == pszVerb) { if (_IsDesktop()) UEMFireEvent(&UEMIID_SHELL, UEME_UISCUT, UEMF_XEVENT, -1, (LPARAM)-1); if (S_OK == _OnDefaultCommand()) { return S_FALSE; /* commdlg browser ate the message */ } if (uKeyFlags & LVKF_ALT) pszVerb = "properties"; } // Dealing with context menus can be slow DECLAREWAITCURSOR; SetWaitCursor(); IContextMenu *pcmSel; HRESULT hr = _CreateSelectionContextMenu(IID_PPV_ARG(IContextMenu, &pcmSel)); if (SUCCEEDED(hr)) _LogDesktopLinksAndRegitems(); ResetWaitCursor(); // undo the cursor since the below _Invoke needs to control cursor shape if (SUCCEEDED(hr)) { hr = _InvokeContextMenuVerb(pcmSel, pszVerb, uKeyFlags, dwCMMask); pcmSel->Release(); } return hr; } // // We want to keep track of which desktop regitems and links the user is using. // This lets the desktop cleaner app know which ones can safely be // cleaned up. // // Be careful - there are many race conditions... You have to do the // GetSelectedObjects before any InvokeCommands are done, because the // InvokeCommand calls might change the selection state. But you also // have to use the result of GetSelectedObjects immediately, because // it returns pidls that are owned by the defview, and if a filesys // notify comes in, you might end up with pidls that have been freed. // // So we just do all the work up front, before actually invoking anything. // This does mean that if the invoke fails, we still log the usage, // but that seems like a small price to pay. // void CDefView::_LogDesktopLinksAndRegitems() { if (_IsDesktop()) { LPCITEMIDLIST *apidl; UINT cItems; if (SUCCEEDED(GetSelectedObjects(&apidl, &cItems)) && apidl) { for (UINT i = 0; i < cItems; i++) { TCHAR szDisplayName[GUIDSTR_MAX+2]; // +2 for leading "::" if (SUCCEEDED(DisplayNameOf(_pshf, apidl[i], SHGDN_INFOLDER | SHGDN_FORPARSING, szDisplayName, ARRAYSIZE(szDisplayName)))) { if (_Attributes(apidl[i], SFGAO_LINK)) { // its a link UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)szDisplayName); } else if (IsRegItemName(szDisplayName, NULL)) { // it's a regitem UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)szDisplayName); } } } LocalFree((HLOCAL)apidl); } } } void CDefView::_UpdateColData(CBackgroundColInfo *pbgci) { UINT iItem = ListView_MapIDToIndex(_hwndListview, pbgci->GetId()); if (iItem != -1) { UINT uiCol = pbgci->GetColumn(); if (_IsColumnInListView(uiCol)) { UINT iVisCol = _RealToVisibleCol(uiCol); ListView_SetItemText(_hwndListview, iItem, iVisCol, (LPTSTR)pbgci->GetText()); } } delete pbgci; } void CDefView::_UpdateIcon(LPITEMIDLIST pidl, UINT iIcon) { int i = _FindItem(pidl, NULL, FALSE, FALSE); if (i >= 0) { LV_ITEM item = {0}; item.mask = LVIF_IMAGE; item.iItem = i; item.iImage = iIcon; ListView_SetItem(_hwndListview, &item); } ILFree(pidl); } void CDefView::_UpdateOverlay(int iList, int iOverlay) { ASSERT (iList >= 0); if (_IsOwnerData()) { // In the ownerdata case, tell the owner that the overlay changed CallCB(SFVM_SETICONOVERLAY, iList, iOverlay); ListView_RedrawItems(_hwndListview, iList, iList); } else { ListView_SetItemState(_hwndListview, iList, INDEXTOOVERLAYMASK(iOverlay), LVIS_OVERLAYMASK); } } HRESULT CDefView::_GetIconAsync(LPCITEMIDLIST pidl, int *piIcon, BOOL fCanWait) { HRESULT hr; // if we are not an owner-data view then try to extract asynchronously UINT flags = (_IsOwnerData() ? 0 : GIL_ASYNC); if (GIL_ASYNC & flags) { hr = SHMapIDListToImageListIndexAsync(_pScheduler, _pshf, pidl, flags, _AsyncIconTaskCallback, this, NULL, piIcon, NULL); if (SUCCEEDED(hr)) { return S_OK; // indicate that we got the real icon } else if (hr == E_PENDING) { hr = S_FALSE; // the icon index we have is a placeholder } } else { hr = SHGetIconFromPIDL(_pshf, _psi, pidl, flags, piIcon); } return hr; } void CDefView::_AsyncIconTaskCallback(LPCITEMIDLIST pidl, void *pvData, void *pvHint, INT iIconIndex, INT iOpenIconIndex) { CDefView *pdv = (CDefView *)pvData; ASSERT(pdv); if (pdv) { LPITEMIDLIST pidlClone = ILClone(pidl); if (pidlClone && !PostMessage(pdv->_hwndView, WM_DSV_UPDATEICON, (WPARAM)pidlClone, (LPARAM)iIconIndex)) ILFree(pidlClone); } } HRESULT CDefView::_GetOverlayIndexAsync(LPCITEMIDLIST pidl, int iList) { IRunnableTask * pTask; HRESULT hr = CIconOverlayTask_CreateInstance(this, pidl, iList, &pTask); if (SUCCEEDED(hr)) { _AddTask(pTask, TOID_DVIconOverlay, 0, TASK_PRIORITY_GET_ICON, ADDTASK_ATEND); pTask->Release(); } return hr; } // // Returns: if the cursor is over a listview item, its index; otherwise, -1. // int CDefView::_HitTest(const POINT *ppt, BOOL fIgnoreEdge) { LV_HITTESTINFO info; if (!_IsListviewVisible()) return -1; info.pt = *ppt; int iRet = ListView_HitTest(_hwndListview, &info); if (-1 != iRet && fIgnoreEdge) { // If we're in one of these large image area modes, and the caller says // it's okay to ignore "edge" hits, then pretend the user is over nothing. // Tile mode only ignores the left edge of the icon, since the right edge // is all text (and usually shorter than the tile width anyway). if (_IsTileMode() && (info.flags & LVHT_ONLEFTSIDEOFICON)) iRet = -1; else if (_IsImageMode() && (info.flags & (LVHT_ONLEFTSIDEOFICON|LVHT_ONRIGHTSIDEOFICON))) iRet = -1; } return iRet; } void CDefView::_OnGetInfoTip(NMLVGETINFOTIP *plvn) { if (!SHShowInfotips()) return; LPCITEMIDLIST pidl = _GetPIDL(plvn->iItem); if (pidl) { ATOMICRELEASE(_pBackgroundInfoTip); // Release the previous value, if any HRESULT hr = E_FAIL; _pBackgroundInfoTip = new CBackgroundInfoTip(&hr, plvn); if (_pBackgroundInfoTip && SUCCEEDED(hr)) { LPITEMIDLIST pidlFolder = _GetViewPidl(); if (pidlFolder) { CStatusBarAndInfoTipTask *pTask; hr = CStatusBarAndInfoTipTask_CreateInstance(pidlFolder, pidl, 0, 0, _pBackgroundInfoTip, _hwndView, _pScheduler, &pTask); if (SUCCEEDED(hr)) { if (_pScheduler) { // make sure there are no other background infotip tasks going on... _pScheduler->RemoveTasks(TOID_DVBackgroundInfoTip, ITSAT_DEFAULT_LPARAM, FALSE); } _AddTask(pTask, TOID_DVBackgroundInfoTip, 0, TASK_PRIORITY_INFOTIP, ADDTASK_ATEND); pTask->Release(); } ILFree(pidlFolder); } } } // Do not show a tip while the processing is happening in the background plvn->pszText[0] = 0; } HRESULT CDefView::_OnViewWindowActive() { IShellView *psv = _psvOuter ? _psvOuter : SAFECAST(this, IShellView*); return _psb->OnViewWindowActive(psv); } // CLR_NONE is a special value that never matches a valid RGB COLORREF g_crAltColor = CLR_NONE; // uninitialized magic value COLORREF g_crAltEncryptedColor = CLR_NONE; // uninitialized magic value DWORD GetRegColor(COLORREF clrDefault, LPCTSTR pszName, COLORREF *pValue) { // Fetch the alternate color (for compression) if supplied. if (*pValue == CLR_NONE) // initialized yet? { DWORD cbData = sizeof(*pValue); if (FAILED(SKGetValue(SHELLKEY_HKCU_EXPLORER, NULL, pszName, NULL, pValue, &cbData))) { *pValue = clrDefault; // default value } } return *pValue; } LRESULT CDefView::_GetDisplayInfo(LV_DISPINFO *plvdi) { LPCITEMIDLIST pidl = _GetPIDLParam(plvdi->item.lParam, plvdi->item.iItem); if (pidl && (plvdi->item.mask & (DEFVIEW_LISTCALLBACK_FLAGS))) { ASSERT(IsValidPIDL(pidl)); ASSERT(plvdi->item.iSubItem != 0 ? ViewRequiresColumns(_fs.ViewMode) : TRUE); LV_ITEM item = {0}; item.mask = plvdi->item.mask & (DEFVIEW_LISTCALLBACK_FLAGS); item.iItem = plvdi->item.iItem; item.iImage = plvdi->item.iImage = -1; // for iSubItem != 0 case if ((plvdi->item.iSubItem == 0) && (item.mask & LVIF_IMAGE)) { // If the folder supports IShellIconOverlay then only need to ask for ghosted, else // we need to do the old stuff... DWORD uFlags = _Attributes(pidl, _psio ? SFGAO_GHOSTED : SFGAO_LINK | SFGAO_SHARE | SFGAO_GHOSTED); // set the mask item.mask |= LVIF_STATE; plvdi->item.mask |= LVIF_STATE; item.stateMask = LVIS_OVERLAYMASK; // Pick the right overlay icon. The order is significant. item.state = 0; if (_psio) { int iOverlayIndex = SFV_ICONOVERLAY_UNSET; if (_IsOwnerData()) { // Note: we are passing SFV_ICONOVERLAY_DEFAULT here because // some owners do not respond to SFVM_GETICONOVERLAY might return // iOverlayIndex unchanged and it will get iOverlayIndex = SFV_ICONOVERLAY_DEFAULT; CallCB(SFVM_GETICONOVERLAY, plvdi->item.iItem, (LPARAM)&iOverlayIndex); if (iOverlayIndex > 0) { item.stateMask |= LVIS_OVERLAYMASK; item.state |= INDEXTOOVERLAYMASK(iOverlayIndex); } } if (iOverlayIndex == SFV_ICONOVERLAY_UNSET) { iOverlayIndex = OI_ASYNC; HRESULT hr = _psio->GetOverlayIndex(pidl, &iOverlayIndex); if (E_PENDING == hr) _GetOverlayIndexAsync(pidl, item.iItem); else if (S_OK == hr) { ASSERT(iOverlayIndex >= 0); ASSERT(iOverlayIndex < MAX_OVERLAY_IMAGES); // In the owner data case, tell the owner we got an Overlay index if (_IsOwnerData()) CallCB(SFVM_SETICONOVERLAY, item.iItem, iOverlayIndex); item.state = INDEXTOOVERLAYMASK(iOverlayIndex); } } } else { if (uFlags & SFGAO_LINK) { item.state = INDEXTOOVERLAYMASK(II_LINK - II_OVERLAYFIRST + 1); } else if (uFlags & SFGAO_SHARE) { item.state = INDEXTOOVERLAYMASK(II_SHARE - II_OVERLAYFIRST + 1); } } if (uFlags & SFGAO_GHOSTED) { item.stateMask |= LVIS_CUT; item.state |= LVIS_CUT; } else { item.stateMask |= LVIS_CUT; item.state &= ~LVIS_CUT; } plvdi->item.stateMask = item.stateMask; plvdi->item.state = item.state; // Get the image if (_IsOwnerData() && !_IsImageMode()) { CallCB(SFVM_GETITEMICONINDEX, plvdi->item.iItem, (LPARAM)&item.iImage); } if (item.iImage == -1) { if (_IsImageMode()) { // Check if the item is visible. If it is not, then the image was // probably asked for by the thumbnail read ahead task, in which case, we set a // different priority. if (ListView_IsItemVisible(_hwndListview, item.iItem)) { if (S_OK != ExtractItem((UINT*)&item.iImage, item.iItem, pidl, TRUE, FALSE, PRIORITY_P5)) { _CacheDefaultThumbnail(pidl, &item.iImage); } } else { // Likely from read ahead task. ExtractItem((UINT*)&item.iImage, item.iItem, pidl, TRUE, FALSE, PRIORITY_READAHEAD_EXTRACT); } } else _GetIconAsync(pidl, &item.iImage, TRUE); } plvdi->item.iImage = item.iImage; } if (item.mask & LVIF_TEXT) { if (plvdi->item.cchTextMax) *plvdi->item.pszText = 0; // Note that we do something different for index 0 = NAME if (plvdi->item.iSubItem == 0) { DisplayNameOf(_pshf, pidl, SHGDN_INFOLDER, plvdi->item.pszText, plvdi->item.cchTextMax); } else { // on the first slow column complete all of the other columns (assumed to be slow) // now so we get good caching from the col handlers UINT iReal = _VisibleToRealCol(plvdi->item.iSubItem); if (_vs.GetColumnState(iReal) & SHCOLSTATE_SLOW) { UINT cCols = _vs.GetColumnCount(); for (UINT iVisCol = plvdi->item.iSubItem; iReal < cCols; iReal++) { if (_IsColumnInListView(iReal)) { ASSERT(_vs.GetColumnState(iReal) & SHCOLSTATE_SLOW); UINT uId = ListView_MapIndexToID(_hwndListview, plvdi->item.iItem); // in the async case set the text to nothing (NULL). this will // prevent another call to ListView_GetItemText() from invoking us ListView_SetItemText(_hwndListview, plvdi->item.iItem, iVisCol++, NULL); IRunnableTask *pTask; if (SUCCEEDED(CExtendedColumnTask_CreateInstance(this, pidl, uId, _fmt, iReal, &pTask))) { _AddTask(pTask, TOID_DVBackgroundEnum, 0, TASK_PRIORITY_FILE_PROPS, ADDTASK_ATEND); pTask->Release(); } } } return 0; // bail! } DETAILSINFO di; di.pidl = pidl; di.fmt = _fmt; di.iImage = -1; // Assume for now no image... if (SUCCEEDED(_GetDetailsHelper(iReal, &di))) { StrRetToBuf(&di.str, pidl, plvdi->item.pszText, plvdi->item.cchTextMax); if ((di.iImage != -1) && (plvdi->item.mask & LVIF_IMAGE)) { plvdi->item.iImage = di.iImage; } } } } if ((item.mask & LVIF_GROUPID) && _fGroupView) { plvdi->item.mask |= LVIF_GROUPID; plvdi->item.iGroupId = _GetGroupForItem(plvdi->item.iItem, pidl); } if (item.mask & LVIF_COLUMNS) { if (_fScrolling) { // Ignore any column requests if we're currently scrolling. However, don't // return zero for the number of columns, return I_COLUMNSCALLBACK instead, because // we do still want listview to call us back to ask for them if it is every displaying // this guy while we're not scrolling. plvdi->item.cColumns = I_COLUMNSCALLBACK; plvdi->item.puColumns = NULL; _fRequestedTileDuringScroll = TRUE; } else { if (_IsOwnerData()) { AddColumns(); if (plvdi->item.cColumns > 1) { // hack special case for the find folder if (_MapSCIDToColumn(&SCID_DIRECTORY, &plvdi->item.puColumns[0])) plvdi->item.cColumns = 1; } } else { BOOL fGotColumns = FALSE; // Start a task to extract the important columns for this item. LPCITEMIDLIST pidl = _GetPIDL(plvdi->item.iItem); if (pidl) { plvdi->item.cColumns = TILEVIEWLINES; if (SUCCEEDED(_PeekColumnsCache(NULL, 0, pidl, plvdi->item.puColumns, &plvdi->item.cColumns))) { // Make sure columns are loaded AddColumns(); _FixupColumnsForTileview(plvdi->item.puColumns, plvdi->item.cColumns); fGotColumns = TRUE; } else { IRunnableTask *pTask; UINT uId = ListView_MapIndexToID(_hwndListview, plvdi->item.iItem); if (SUCCEEDED(CFileTypePropertiesTask_CreateInstance(this, pidl, TILEVIEWLINES, uId, &pTask))) //pidl gets cloned { _AddTask(pTask, TOID_DVFileTypeProperties, 0, TASK_PRIORITY_FILE_PROPS, ADDTASK_ATEND); pTask->Release(); } } } if (!fGotColumns) { plvdi->item.cColumns = 0; plvdi->item.puColumns = NULL; } } } } if (plvdi->item.iSubItem == 0) plvdi->item.mask |= LVIF_DI_SETITEM; // always store the name } return 0; } int CALLBACK GroupCompare(int iGroup1, int iGroup2, void *pvData) { ICategorizer* pcat = (ICategorizer*)pvData; HRESULT hr = pcat->CompareCategory(CATSORT_DEFAULT, (DWORD)iGroup1, (DWORD)iGroup2); return ShortFromResult(hr); } void CDefView::_OnCategoryTaskAdd() { _fInBackgroundGrouping = TRUE; } void CDefView::_OnCategoryTaskDone() { _fInBackgroundGrouping = FALSE; LONG cTasksCompleteLocal; ENTERCRITICAL; { _fGroupingMsgInFlight = FALSE; cTasksCompleteLocal = _cTasksCompleted; _cTasksCompleted = 0; // swap the DPAs HDPA hdpaTemp = _hdpaGroupingListBackup; _hdpaGroupingListBackup = _hdpaGroupingListActive; _hdpaGroupingListActive = hdpaTemp; } LEAVECRITICAL; LONG cEntries = DPA_GetPtrCount(_hdpaGroupingListBackup); // now process everything in _hdpaGroupingListEmpty for (int i = 0; i < cEntries; i++) { CBackgroundGroupInfo* pbggi = (CBackgroundGroupInfo*)DPA_GetPtr(_hdpaGroupingListBackup, i); if (pbggi) { if (pbggi->VerifyGroupExists(_hwndListview, _pcat)) { int iItem = ListView_MapIDToIndex(_hwndListview, pbggi->GetId()); if (iItem != -1) { LVITEM lvi = {0}; lvi.mask = LVIF_GROUPID; lvi.iGroupId = pbggi->GetGroupId(); lvi.iItem = iItem; ListView_SetItem(_hwndListview, &lvi); } } delete pbggi; } } DPA_DeleteAllPtrs(_hdpaGroupingListBackup); if (_pidlSelectAndPosition) { POINT pt = {0}; // Don't care: Groups don't have a position SelectAndPositionItem(_pidlSelectAndPosition, _uSelectAndPositionFlags, &pt); Pidl_Set(&_pidlSelectAndPosition, NULL); _uSelectAndPositionFlags = 0; } for (LONG i = 0; i < cTasksCompleteLocal; i++) { _ShowSearchUI(FALSE); _GlobeAnimation(FALSE); } } DWORD CDefView::_GetGroupForItem(int iItem, LPCITEMIDLIST pidl) { DWORD dwGroup = I_GROUPIDNONE; if (_fGroupView) { if (_fSlowGroup) { UINT uId = ListView_MapIndexToID(_hwndListview, iItem); IRunnableTask* pTask; if (SUCCEEDED(CCategoryTask_Create(this, pidl, uId, &pTask))) { // Need to get the globe/search stuff kicked off while within the CreateViewWindow2 call, // so do it here instead of a posted message in the above constructor _OnCategoryTaskAdd(); _AddTask(pTask, TOID_DVBackgroundGroup, 0, TASK_PRIORITY_GROUP, ADDTASK_ATEND); pTask->Release(); } } else { _pcat->GetCategory(1, (LPCITEMIDLIST*)&pidl, &dwGroup); if (!ListView_HasGroup(_hwndListview, dwGroup)) { CATEGORY_INFO ci; _pcat->GetCategoryInfo(dwGroup, &ci); LVINSERTGROUPSORTED igrp; igrp.pfnGroupCompare = GroupCompare; igrp.pvData = (void *)_pcat; igrp.lvGroup.cbSize = sizeof(LVGROUP); igrp.lvGroup.mask = LVGF_HEADER | LVGF_GROUPID; igrp.lvGroup.pszHeader= ci.wszName; igrp.lvGroup.iGroupId = (int)dwGroup; ListView_InsertGroupSorted(_hwndListview, &igrp); } } } return dwGroup; } BOOL CDefView::_EnsureSCIDCache() { BOOL bRet = FALSE; if (_hdsaSCIDCache) { bRet = TRUE; } else if (_pshf2) { _hdsaSCIDCache = DSA_Create(sizeof(SHCOLUMNID), 30); if (_hdsaSCIDCache) { SHCOLUMNID scid; for (UINT iCol = 0; SUCCEEDED(_pshf2->MapColumnToSCID(iCol, &scid)); iCol++) { // ignore failure, just means we can't find the thing DSA_AppendItem(_hdsaSCIDCache, &scid); } bRet = TRUE; } } return bRet; } BOOL CDefView::_MapSCIDToColumn(const SHCOLUMNID *pscid, UINT *pnColumn) { BOOL bRet = FALSE; *pnColumn = 0; if (_EnsureSCIDCache()) { UINT cCol = DSA_GetItemCount(_hdsaSCIDCache); for (UINT iCol = 0; iCol < cCol; iCol++) { SHCOLUMNID scid; DSA_GetItem(_hdsaSCIDCache, iCol, &scid); if (IsEqualSCID(*pscid, scid)) { *pnColumn = iCol; bRet = TRUE; break; } } } return bRet; } HRESULT CDefView::_GetPropertyUI(IPropertyUI **pppui) { if (!_ppui) SHCoCreateInstance(NULL, &CLSID_PropertiesUI, NULL, IID_PPV_ARG(IPropertyUI, &_ppui)); return _ppui ? _ppui->QueryInterface(IID_PPV_ARG(IPropertyUI, pppui)) : E_NOTIMPL; } HRESULT CDefView::_PeekColumnsCache(PTSTR pszPath, UINT cchPath, LPCITEMIDLIST pidl, UINT rguColumns[], UINT *pcColumns) { TCHAR szPath[MAX_PATH]; if (pszPath == NULL) { pszPath = szPath; cchPath = ARRAYSIZE(szPath); } // NOTE - need to replace this with GetDetailsEx(SCID_CANONICALTYPE) to support // caching properly. then we dont need to sniff attributes or the name in order to get // a nice caching index. HRESULT hr = DisplayNameOf(_pshf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, pszPath, cchPath); if (SUCCEEDED(hr)) { LPCWSTR pszExt = _Attributes(pidl, SFGAO_FOLDER) ? NULL : PathFindExtension(pszPath); hr = E_FAIL; // Check file table cache: ENTERCRITICAL; SHCOLUMNID *pscidCached; UINT cSCIDCached = pszExt ? LookupFileSCIDs(pszExt, &pscidCached) : 0; //Handle no extension case by not looking up in cache LEAVECRITICAL; if (cSCIDCached) // Found the SCIDs cache in the file table { UINT nFilled = 0; // Found it... we don't need to check the registry for (UINT nSCID = 0; nSCID < cSCIDCached && nFilled < *pcColumns; nSCID++) { if (_MapSCIDToColumn(&pscidCached[nSCID], &rguColumns[nFilled])) nFilled++; } *pcColumns = nFilled; LocalFree(pscidCached); hr = S_OK; } } return hr; } // Get the important columns for this guy, based on file extension // pidl: The pidl of the item in question // puColumns[]: The array which will get filled with important column indicies // pcColumns IN: specifies how big rguColumns[] is. OUT: specified how many slots got filled. HRESULT CDefView::_GetImportantColumns(LPCITEMIDLIST pidl, UINT rguColumns[], UINT *pcColumns) { TCHAR szPath[MAX_PATH]; // We need to ensure that the columns are loaded here if (!_bLoadedColumns) { DWORD_PTR lRes = 0; if (!SendMessageTimeout(_hwndView, WM_DSV_ENSURE_COLUMNS_LOADED, 0, 0, SMTO_NORMAL, 5000, &lRes) || lRes == 0) return E_FAIL; } HRESULT hr = _PeekColumnsCache(szPath, ARRAYSIZE(szPath), pidl, rguColumns, pcColumns); if (FAILED(hr)) { IQueryAssociations *pqa; hr = _pshf->GetUIObjectOf(_hwndMain, 1, &pidl, IID_PPV_ARG_NULL(IQueryAssociations, &pqa)); if (SUCCEEDED(hr)) { IPropertyUI *ppui; hr = _GetPropertyUI(&ppui); if (SUCCEEDED(hr)) { TCHAR szProps[INFOTIPSIZE]; DWORD cchOut = ARRAYSIZE(szProps); hr = pqa->GetString(0, ASSOCSTR_TILEINFO, NULL, szProps, &cchOut); if (SUCCEEDED(hr)) { UINT cNumColumns = 0; // # of items in rguColumns UINT cSCID = 0; // # of items in rgscid SHCOLUMNID rgscid[64]; // reasonable upper bound ULONG chEaten = 0; // loop variable ParsePropertyName updates this while ((cSCID < ARRAYSIZE(rgscid)) && SUCCEEDED(ppui->ParsePropertyName(szProps, &rgscid[cSCID].fmtid, &rgscid[cSCID].pid, &chEaten))) { // Map SCID to a column (while there are more column slots) if ((cNumColumns < *pcColumns) && _MapSCIDToColumn(&rgscid[cSCID], &rguColumns[cNumColumns])) { cNumColumns++; cSCID++; } } *pcColumns = cNumColumns; LPCWSTR pszExt = _Attributes(pidl, SFGAO_FOLDER) ? NULL : PathFindExtension(szPath); if (pszExt) { // cache for future use, except if there's no extension (cache key) ENTERCRITICAL; AddFileSCIDs(pszExt, rgscid, cSCID); LEAVECRITICAL; } } ppui->Release(); } pqa->Release(); } } return hr; } void CDefView::_FixupColumnsForTileview(UINT *rguColumns, UINT cColumns) { // Make sure these columns are added to listview (ie. visible). // And then map the columns in rguColumns from real columns to visible columns for (UINT i = 0; i < cColumns; i++) { _AddTileColumn(rguColumns[i]); } // Now, also add the sorted by column, if it hasn't been added yet. if (!_fSetTileViewSortedCol) { _fSetTileViewSortedCol = TRUE; // It's ok if we don't actually set it. It's the thought that counts. if (_vs._lParamSort != -1) { _AddTileColumn(_vs._lParamSort); // And set it selected, if we're not in groupview if (!_fGroupView) { ListView_SetSelectedColumn(_hwndListview, _RealToVisibleCol(_vs._lParamSort)); } } } // This must be done after all the _AddTileColumns, or else the visible col #'s will be off. for (UINT i = 0; i < cColumns; i++) { rguColumns[i] = _RealToVisibleCol(rguColumns[i]); } } void CDefView::_SetImportantColumns(CBackgroundTileInfo *pbgTileInfo) { UINT cColumns = pbgTileInfo->GetColumnCount(); UINT *rguColumns = pbgTileInfo->GetColumns(); LVTILEINFO ti = {0}; ti.cbSize = sizeof(ti); ti.cColumns = cColumns; ti.puColumns = rguColumns; ti.iItem = ListView_MapIDToIndex(_hwndListview, pbgTileInfo->GetId()); if (ti.iItem != -1) { _FixupColumnsForTileview(rguColumns, cColumns); // have the listview store the per item tile info that we have computed ListView_SetTileInfo(_hwndListview, &ti); } delete pbgTileInfo; } // Ensures if we're in tileview, that the tileviewinfo is set. void CDefView::_SetView(UINT fvm) { // Update our internal state _fs.ViewMode = fvm; // Map the ViewMode into a listview mode DWORD iView = LV_VIEW_ICON; // Now switch the listview switch (fvm) { case FVM_ICON: case FVM_SMALLICON: case FVM_THUMBNAIL: case FVM_THUMBSTRIP: iView = LV_VIEW_ICON; break; case FVM_LIST: iView = LV_VIEW_LIST; break; case FVM_TILE: iView = LV_VIEW_TILE; break; case FVM_DETAILS: iView = LV_VIEW_DETAILS; break; default: ASSERTMSG(FALSE, "_SetView got an invalid ViewMode!"); break; } if (iView == LV_VIEW_TILE) { RECT rcLabelMargin = {1, 1, 1, 1}; // This gives us some room around the label, so the focus rect doesn't clip part of the text LVTILEVIEWINFO lvtvi = {0}; lvtvi.cbSize = sizeof(lvtvi); lvtvi.dwMask = LVTVIM_TILESIZE | LVTVIM_COLUMNS | LVTVIM_LABELMARGIN; lvtvi.dwFlags = LVTVIF_AUTOSIZE; lvtvi.cLines = TILEVIEWLINES; lvtvi.rcLabelMargin = rcLabelMargin; ListView_SetTileViewInfo(_hwndListview, &lvtvi); } ListView_SetView(_hwndListview, iView); _FireEvent(DISPID_VIEWMODECHANGED); } // rename the selection based on the new name for the renamed item // this makes it easy to rename groups of files to a common base name HRESULT CDefView::_DoBulkRename(LPCITEMIDLIST pidlNewName) { LPCITEMIDLIST *apidl; UINT cItems; HRESULT hr = _GetItemObjects(&apidl, SVGIO_SELECTION, &cItems); if (SUCCEEDED(hr)) { if (cItems > 1) // only interesting if more than 1 { TCHAR szBase[MAX_PATH]; // seed file name used to generate other names hr = DisplayNameOf(_pshf, pidlNewName, SHGDN_INFOLDER | SHGDN_FORPARSING, szBase, ARRAYSIZE(szBase)); if (SUCCEEDED(hr)) { if (!SHGetAttributes(_pshf, pidlNewName, SFGAO_FOLDER)) PathRemoveExtension(szBase); // remove the extension, if it is a file UINT cBase = 1; // one based counter, start at "File (1)" // if input contains (#) use that as the sequence # base LPWSTR psz = StrChr(szBase, TEXT('(')); if (psz) { cBase = StrToInt(psz + 1) + 1; // start at this in sequence *psz = 0; // remove the (#) from the base name } PathRemoveBlanks(szBase); // clean away leading/trailing blanks // start at 1, skipping the focused item, renaming all others in the array for (UINT i = 1; (i < cItems) && SUCCEEDED(hr); i++) { TCHAR szOld[MAX_PATH]; hr = DisplayNameOf(_pshf, apidl[i], SHGDN_INFOLDER | SHGDN_FORPARSING, szOld, ARRAYSIZE(szOld)); if (SUCCEEDED(hr)) { // Clone the pidl since isf->SetNameOf can result in synchronous update item // that can free the ListView owned apidl[i]. LPITEMIDLIST pidlOldName = ILClone(apidl[i]); if (pidlOldName) { // if the new name we produce conflicts with a name that // already exists we will retry up to 100 times for (UINT cRetry = 0; cRetry < 100; cRetry++) { WCHAR szName[MAX_PATH]; wnsprintf(szName, ARRAYSIZE(szName), TEXT("%s (%d)%s"), szBase, cBase, PathFindExtension(szOld)); hr = _pshf->SetNameOf(NULL, pidlOldName, szName, SHGDN_INFOLDER | SHGDN_FORPARSING, NULL); if (SUCCEEDED(hr)) { // force sync change notify update to make sure // all renames come through (avoid UPDATEDIR) SHChangeNotifyHandleEvents(); cBase++; break; // did this one successfully } else if (HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) == hr || HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) == hr) { cBase++; hr = S_OK; // and keep trying } else { break; // other error, exit } } ILFree(pidlOldName); } } } } } LocalFree(apidl); } return hr; } LRESULT CDefView::_OnLVNotify(NM_LISTVIEW *plvn) { switch (plvn->hdr.code) { case NM_KILLFOCUS: // force update on inactive to not ruin save bits _OnStateChange(CDBOSC_KILLFOCUS); if (GetForegroundWindow() != _hwndMain) UpdateWindow(_hwndListview); _fHasListViewFocus = FALSE; _EnableDisableTBButtons(); break; case NM_SETFOCUS: { if (!_fDestroying) { if (_cFrame.IsWebView()) // Do OLE stuff { UIActivate(SVUIA_ACTIVATE_FOCUS); } else { // We should call IShellBrowser::OnViewWindowActive() before // calling its InsertMenus(). _OnViewWindowActive(); _OnStateChange(CDBOSC_SETFOCUS); OnActivate(SVUIA_ACTIVATE_FOCUS); _FocusOnSomething(); _UpdateStatusBar(FALSE); } _fHasListViewFocus = TRUE; _EnableDisableTBButtons(); } break; } case NM_RCLICK: // on the shift+right-click case we want to deselect everything and select just our item if it is // not already selected. if we dont do this, then listview gets confused (because he thinks // shift means extend selection, but in the right click case it dosent!) and will bring up the // context menu for whatever is currently selected instead of what the user just right clicked on. if ((GetKeyState(VK_SHIFT) < 0) && (plvn->iItem >= 0) && !(ListView_GetItemState(_hwndListview, plvn->iItem, LVIS_SELECTED) & LVIS_SELECTED)) { // clear any currently slected items ListView_SetItemState(_hwndListview, -1, 0, LVIS_SELECTED); // select the guy that was just right-clicked on ListView_SetItemState(_hwndListview, plvn->iItem, LVIS_SELECTED, LVIS_SELECTED); } break; case LVN_ENDSCROLL: { // This means we're scrolling. Ignore requests for LVIF_COLUMNS while we're // scrolling to speed things up. // We don't want to ignore requests for LVIF_COLUMNS when we're owner data, because // owner data listviews always callback for info on what to display. (The result would // be already-present tileinfo vanishing while scrolling, since we'd be ignoring requests // for what to display) if ((_fs.ViewMode == FVM_TILE) && !_IsOwnerData()) { SetTimer(_hwndView, DV_IDTIMER_SCROLL_TIMEOUT, 250, NULL); if (!_fScrolling) { _fScrolling = TRUE; // We don't reset this on every LVN_ENDSCROLL - only if this is the first time // we've scrolled since a stable (non-scrolling) state _fRequestedTileDuringScroll = FALSE; } } } break; case LVN_GETINFOTIP: _OnGetInfoTip((NMLVGETINFOTIP *)plvn); break; case LVN_ITEMACTIVATE: if (!_fDisabled) { //in win95 if user left clicks on one click activate icon and then right //clicks on it (within double click time interval), the icon is launched //and context menu appears on top of it -- it does not disappear. //furthermore the context menu cannot be destroyed but stays on top of //any window and items on it are not accessible. to avoid this //send cancel mode to itself to destroy context before the icon is //launched if (_hwndView) SendMessage(_hwndView, WM_CANCELMODE, 0, 0); _InvokeContextMenuVerbOnSelection(NULL, ((NMITEMACTIVATE *)plvn)->uKeyFlags, CMIC_MASK_FLAG_LOG_USAGE); } break; case NM_CUSTOMDRAW: { LPNMLVCUSTOMDRAW pcd = (LPNMLVCUSTOMDRAW)plvn; switch (pcd->nmcd.dwDrawStage) { case CDDS_PREPAINT: { return _fShowCompColor ? CDRF_NOTIFYITEMDRAW : CDRF_DODEFAULT; } case CDDS_ITEMPREPAINT: { LRESULT lres = CDRF_DODEFAULT; LPCITEMIDLIST pidl = _GetPIDLParam(pcd->nmcd.lItemlParam, (int)pcd->nmcd.dwItemSpec); if (pidl) { DWORD dwAttribs = _Attributes(pidl, SFGAO_COMPRESSED | SFGAO_ENCRYPTED); // only one or the other, can never be both if (dwAttribs & SFGAO_COMPRESSED) { // default value of Blue pcd->clrText = GetRegColor(RGB(0, 0, 255), TEXT("AltColor"), &g_crAltColor); } else if (dwAttribs & SFGAO_ENCRYPTED) { // default value Luna Mid Green pcd->clrText = GetRegColor(RGB(19, 146, 13), TEXT("AltEncryptionColor"), &g_crAltEncryptedColor); } } if (_IsImageMode() && pcd->nmcd.hdc && (_dwRecClrDepth <= 8)) { HPALETTE hpal = NULL; if (SUCCEEDED(_GetBrowserPalette(&hpal))) { // Since we are a child of the browser, we should always take a back seat to thier palette selection _hpalOld = SelectPalette(pcd->nmcd.hdc, hpal, TRUE); RealizePalette(pcd->nmcd.hdc); lres |= CDRF_NOTIFYPOSTPAINT; } } return lres; } case CDDS_ITEMPOSTPAINT: if (_IsImageMode() && _hpalOld && pcd->nmcd.hdc) { SelectPalette(pcd->nmcd.hdc, _hpalOld, TRUE); _hpalOld = NULL; } break; } } return CDRF_DODEFAULT; case LVN_BEGINDRAG: case LVN_BEGINRDRAG: if (_fDisabled) return FALSE; /* commdlg doesn't want user dragging */ return _OnBeginDrag(plvn); case LVN_ITEMCHANGING: if (_fDisabled) return TRUE; break; // Something changed in the listview. Delete any data that // we might have cached away. case LVN_ITEMCHANGED: if (plvn->uChanged & LVIF_STATE) { if (!_fIgnoreItemChanged) { // The rest only cares about SELCHANGE messages (avoid LVIS_DRAGSELECT, etc) if ((plvn->uNewState ^ plvn->uOldState) & (LVIS_SELECTED | LVIS_FOCUSED)) { //if we are the drag source then dont send selection change message if (!_bDragSource) { _OnStateChange(CDBOSC_SELCHANGE); } OnLVSelectionChange(plvn); } else if ((plvn->uNewState ^ plvn->uOldState) & (LVIS_STATEIMAGEMASK)) { if (!_bDragSource) { _OnStateChange(CDBOSC_STATECHANGE); } } } } break; // owner data state changed: e.g. search results case LVN_ODSTATECHANGED: { NM_ODSTATECHANGE *pnm = (NM_ODSTATECHANGE *)plvn; // for now handle only selection changes if ((pnm->uOldState ^ pnm->uNewState) & (LVIS_SELECTED | LVIS_FOCUSED)) { _OnLVSelectionChange(-1, pnm->uOldState, pnm->uNewState, 0); } } break; case LVN_DELETEITEM: OnListViewDelete(plvn->iItem, (LPITEMIDLIST)plvn->lParam, TRUE); break; case LVN_COLUMNCLICK: // allow clicking on columns to set the sort order if (_fGroupView) { BOOL fAllowArrange = TRUE; UINT iRealColumn = _VisibleToRealCol(plvn->iSubItem); SHCOLUMNID scid; if (SUCCEEDED(_pshf2->MapColumnToSCID(iRealColumn, &scid))) { ICategoryProvider* pcp = NULL; if (SUCCEEDED(_pshf->CreateViewObject(NULL, IID_PPV_ARG(ICategoryProvider, &pcp)))) { // returns S_FALSE to remove. if (S_FALSE == pcp->CanCategorizeOnSCID(&scid)) { fAllowArrange = FALSE; } } } if (fAllowArrange) _ArrangeBy(iRealColumn + SFVIDM_GROUPSFIRST); } else if (_pshf2 || _psd || HasCB()) { LPARAM lParamSort = _vs._lParamSort; LONG iLastColumnClick = _vs._iLastColumnClick, iLastSortDirection = _vs._iDirection; // push sort state // Folder doesn't know which columns are on or off, so communication with folder uses real col #s UINT iRealColumn = _VisibleToRealCol(plvn->iSubItem); // seeral ways to do this... each can defer to the // ultimate default that is defview calling itself. HRESULT hr = S_FALSE; if (_psd) hr = _psd->ColumnClick(iRealColumn); if (hr != S_OK) hr = CallCB(SFVM_COLUMNCLICK, iRealColumn, 0); if (hr != S_OK) hr = Rearrange(iRealColumn); // Allows iLastColumnClick to stay valid during the above calls if (SUCCEEDED(hr)) _vs._iLastColumnClick = iRealColumn; else { // We failed somewhere so pop the sort state. _vs._iDirection = iLastSortDirection; _vs._iLastColumnClick = (int)_vs._lParamSort; _vs._lParamSort = lParamSort ; _SetSortFeedback(); _vs._iLastColumnClick = iLastColumnClick; } } break; case LVN_KEYDOWN: HandleKeyDown(((LV_KEYDOWN *)plvn)); break; #define plvdi ((LV_DISPINFO *)plvn) case LVN_BEGINLABELEDIT: { LPCITEMIDLIST pidl = _GetPIDLParam(plvdi->item.lParam, plvdi->item.iItem); if (!pidl || !_Attributes(pidl, SFGAO_CANRENAME)) { MessageBeep(0); return TRUE; // Don't allow label edit } _fInLabelEdit = TRUE; HWND hwndEdit = ListView_GetEditControl(_hwndListview); if (hwndEdit) { int cchMax = 0; CallCB(SFVM_GETCCHMAX, (WPARAM)pidl, (LPARAM)&cchMax); if (cchMax) { ASSERT(cchMax < 1024); SendMessage(hwndEdit, EM_LIMITTEXT, cchMax, 0); } TCHAR szName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(_pshf, pidl, SHGDN_INFOLDER | SHGDN_FOREDITING, szName, ARRAYSIZE(szName)))) { SetWindowText(hwndEdit, szName); } SHLimitInputEdit(hwndEdit, _pshf); } } break; case LVN_ENDLABELEDIT: _fInLabelEdit = FALSE; if (plvdi->item.pszText) { LPCITEMIDLIST pidl = _GetPIDLParam(plvdi->item.lParam, plvdi->item.iItem); if (pidl) { // this set site is questionable as folder should not have any state // associated with the view. but this is needed for FTP so it can // do an EnableModless for it's UI IUnknown_SetSite(_pshf, SAFECAST(this, IOleCommandTarget *)); // Clone the pidl since isf->SetNameOf can result in a synchronous update item that // will free the listview owned pidl. LPITEMIDLIST pidlOldName = ILClone(pidl); if (pidlOldName) { LPITEMIDLIST pidlNewName = NULL; // paranoid about bad SetNameOf() impls if (SUCCEEDED(_pshf->SetNameOf(_hwndMain, pidlOldName, plvdi->item.pszText, SHGDN_INFOLDER, &pidlNewName))) { ASSERT(NULL != pidlNewName); // folders need to implement this if (pidlNewName) { _DoBulkRename(pidlNewName); ILFree(pidlNewName); } SHChangeNotifyHandleEvents(); _OnStateChange(CDBOSC_RENAME); } else { SendMessage(_hwndListview, LVM_EDITLABEL, plvdi->item.iItem, (LPARAM)plvdi->item.pszText); } ILFree(pidlOldName); } IUnknown_SetSite(_pshf, NULL); } } else { // The user canceled. so return TRUE to let things like the mouse // click be processed. return TRUE; } break; case LVN_GETDISPINFO: return _GetDisplayInfo(plvdi); case LVN_ODFINDITEM: // We are owner data so we need to find the item for the user... { int iItem = -1; if (SUCCEEDED(CallCB(SFVM_ODFINDITEM, (WPARAM)&iItem, (LPARAM)plvn))) return iItem; return -1; // Not Found } case LVN_ODCACHEHINT: // Just a hint we don't care about return values CallCB(SFVM_ODCACHEHINT, 0, (LPARAM)plvn); break; case LVN_GETEMPTYTEXT: if (HasCB()) { if ((plvdi->item.mask & LVIF_TEXT) && SUCCEEDED(CallCB(SFVM_GETEMPTYTEXT, (WPARAM)(plvdi->item.cchTextMax), (LPARAM)(plvdi->item.pszText)))) return TRUE; } break; } #undef lpdi #undef plvdi return 0; } // FEATURE -- implement enabling/disabling of other toolbar buttons. We can enable/disable // based on the current selection, but the problem is that some of the buttons work // for other guys when defview doesn't have focus. Specifically, cut/copy/paste work // for the folders pane. If we're going to enable/disable these buttons based on the // selection, then we'll need to have a mechanism that lets the active band (such as // folders) also have a say about the button state. That is too much work right now. static const UINT c_BtnCmds[] = { SFVIDM_EDIT_COPYTO, SFVIDM_EDIT_MOVETO, #ifdef ENABLEDISABLEBUTTONS SFVIDM_EDIT_COPY, SFVIDM_EDIT_CUT, #endif }; static const DWORD c_BtnAttr[] = { SFGAO_CANCOPY, SFGAO_CANMOVE, #ifdef ENABLEDISABLEBUTTONS SFGAO_CANCOPY, SFGAO_CANMOVE, #endif }; #define SFGAO_RELEVANT (SFGAO_CANCOPY | SFGAO_CANMOVE) // Description: // Called by toolbar infrastructure to determine whether to display a given // toolbar button in the "enabled" or "disabled" state. // // Return: // TRUE display toolbar button in enabled state // FALSE display toolbar button in disabled state // BOOL CDefView::_ShouldEnableToolbarButton(UINT uiCmd, DWORD dwAttr, int iIndex) { COMPILETIME_ASSERT(sizeof(c_BtnCmds) == sizeof(c_BtnAttr)); BOOL bEnable; switch (uiCmd) { case SFVIDM_VIEW_VIEWMENU: bEnable = !_fBarrierDisplayed; break; default: { DWORD dwBtnAttr; if (iIndex != -1) { // Caller was nice and figured out dest index for us dwBtnAttr = c_BtnAttr[iIndex]; } else { // Look for the command ourselves dwBtnAttr = SHSearchMapInt((int*)c_BtnCmds, (int*)c_BtnAttr, ARRAYSIZE(c_BtnCmds), uiCmd); if (dwBtnAttr == -1) { // We don't care about this button, just enable it. return TRUE; } } // Disable any button we care about while listview is inactive. bEnable = BOOLIFY(dwAttr & dwBtnAttr) && _fHasListViewFocus; break; } } return bEnable; } // As a perf enhancement, we cache the attributes of the currently selected // files/folders in a FS view only. This is to avoid n^2 traversals of the // selected items as we select/unselect them. These cached attributes // should not be used for anything other than determining toolbar button // states and should be revisited if we add toolbar buttons that care about // much more than the attributes used by Move to & Copy to. BOOL CDefView::_GetCachedToolbarSelectionAttrs(ULONG *pdwAttr) { BOOL fResult = FALSE; CLSID clsid; HRESULT hr = IUnknown_GetClassID(_pshf, &clsid); if (SUCCEEDED(hr) && IsEqualGUID(CLSID_ShellFSFolder, clsid)) { UINT iCount; if (SUCCEEDED(GetSelectedCount(&iCount)) && (iCount > 0) && (_uCachedSelCount > 0)) { *pdwAttr = _uCachedSelAttrs; fResult = TRUE; } } return fResult; } void CDefView::_SetCachedToolbarSelectionAttrs(ULONG dwAttrs) { if (SUCCEEDED(GetSelectedCount(&_uCachedSelCount))) _uCachedSelAttrs = dwAttrs; else _uCachedSelCount = 0; } void CDefView::_EnableDisableTBButtons() { if (!IsEqualGUID(_clsid, GUID_NULL)) { IExplorerToolbar *piet; if (SUCCEEDED(IUnknown_QueryService(_psb, SID_SExplorerToolbar, IID_PPV_ARG(IExplorerToolbar, &piet)))) { ULONG dwAttr; if (!_GetCachedToolbarSelectionAttrs(&dwAttr)) dwAttr = _AttributesFromSel(SFGAO_RELEVANT); for (int i = 0; i < ARRAYSIZE(c_BtnCmds); i++) _EnableToolbarButton(piet, c_BtnCmds[i], _ShouldEnableToolbarButton(c_BtnCmds[i], dwAttr, i)); _SetCachedToolbarSelectionAttrs(dwAttr); piet->Release(); } } } // Description: // Enables or disables a specified button on the toolbar. // void CDefView::EnableToolbarButton(UINT uiCmd, BOOL bEnable) { if (!IsEqualGUID(_clsid, GUID_NULL)) { IExplorerToolbar *piet; if (SUCCEEDED(IUnknown_QueryService(_psb, SID_SExplorerToolbar, IID_PPV_ARG(IExplorerToolbar, &piet)))) { _EnableToolbarButton(piet, uiCmd, bEnable); piet->Release(); } } } // Description: // Enables or disables a specified button on the toolbar. // // Note: // This is an _internal_ method only. // External calls should use EnableToolbarButton(). // Caller is responsible for ensuring this object uses IExplorerToolbar mechanism. // void CDefView::_EnableToolbarButton(IExplorerToolbar *piet, UINT uiCmd, BOOL bEnable) { ASSERT(!IsEqualGUID(_clsid, GUID_NULL)); // Required or piet cannot be valid. ASSERT(piet); // Required or we're not using IExplorerToolbar mechanism. UINT uiState; if (SUCCEEDED(piet->GetState(&_clsid, uiCmd, &uiState))) { if (bEnable) uiState |= TBSTATE_ENABLED; else uiState &= ~TBSTATE_ENABLED; piet->SetState(&_clsid, uiCmd, uiState); } } void CDefView::_OnContentsChanged() { // use a timer to delay sending a gazillion content change messages to automation. // todo: see what duiview has to do with this stuff. // only fire event if someone is listening if (_pauto || _pDUIView) { // delay for 100ms SetTimer(_hwndView, DV_IDTIMER_NOTIFY_AUTOMATION_CONTENTSCHANGED, 100, NULL); } if (!_pDUIView) { _fRcvdContentsChangeBeforeDuiViewCreated = TRUE; } } void CDefView::_OnDelayedContentsChanged() { KillTimer(_hwndView, DV_IDTIMER_NOTIFY_AUTOMATION_CONTENTSCHANGED); // update dui, would be better if there were different handlers in CDUIView // but go through selection changed for now. ATOMICRELEASE(_pSelectionShellItemArray); _pSelectionShellItemArray = _CreateSelectionShellItemArray(); if (_pDUIView) { if (_fBarrierDisplayed != _QueryBarricadeState()) { // // Yet another DUI special-case. // If the barrier state has changed, we need to // tell DUIView about it so that the DUI right-pane // content is reconstructed. This is required to make // Control Panel update it's right-pane content when // webview is turned on/off. // _fBarrierDisplayed = !_fBarrierDisplayed; _pDUIView->EnableBarrier (_fBarrierDisplayed); } _pDUIView->OnContentsChange(_pSelectionShellItemArray); } _FireEvent(DISPID_CONTENTSCHANGED); } // WARNING: don't add any code here that is expensive in anyway! // we get many many of these notifies and if we slow this routine down // we mess select all and large selection perf. // // you can add expensive code to the WM_DSV_SENDSELECTIONCHANGED handler _OnSelectionChanged, // that happens after all of the sel change notifies go through. // // or you can add really expensive code to the double-click-timeout delayed _OnDelayedSelectionChange. // void CDefView::OnLVSelectionChange(NM_LISTVIEW *plvn) { _OnLVSelectionChange(plvn->iItem, plvn->uOldState, plvn->uNewState, plvn->lParam); } void CDefView::_OnLVSelectionChange(int iItem, UINT uOldState, UINT uNewState, LPARAM lParam) { // Do selection changed stuff on a selection change only if ((uOldState ^ uNewState) & LVIS_SELECTED) { // Tell the defview client that the selection may have changed SFVM_SELCHANGE_DATA dvsci; dvsci.uNewState = uNewState; dvsci.uOldState = uOldState; dvsci.lParamItem = lParam; CallCB(SFVM_SELCHANGE, MAKEWPARAM(SFVIDM_CLIENT_FIRST, iItem), (LPARAM)&dvsci); } // Notify the dispach that the focus changed.. _PostSelectionChangedMessage(uOldState ^ uNewState); } void CDefView::_PostSelectionChangedMessage(UINT uSelectionStateChanged) { if (!_fSelectionChangePending) { _uSelectionStateChanged = uSelectionStateChanged; // RACE CONDITION FIX (edwardp & buzzr) // It is imperative to set _fSelectionChangePending _before_ posting // WM_DSV_SENDSELECTIONCHANGED. Otherwise, a race condition ensues // whereby we could handle the message via _OnSelectionChanged() // whose first line sets _fSelectionChangePending = FALSE before we // have set it to TRUE here. This means _fSelectionChangePending // will never again be set to FALSE (since the this thread will be // rescheduled, set it to TRUE, and the action of clearing it will // already be past). This was happening with 100% reproducability // with our background CGetCommandStateTask for WIA devices. The // symptom most noticeable was that the DUI pane (task lists and // details) no longer updated with each selection change. _fSelectionChangePending = TRUE; PostMessage(_hwndView, WM_DSV_SENDSELECTIONCHANGED, 0, 0); } else { _uSelectionStateChanged |= uSelectionStateChanged; } } void CDefView::_OnSelectionChanged() // handles WM_DSV_SENDSELECTIONCHANGED { _fSelectionChangePending = FALSE; // release this first so code we call doesn't think we're "pending" any more if (_uSelectionStateChanged & LVIS_SELECTED) { // Get and cache the data object for the current selection ATOMICRELEASE(_pSelectionShellItemArray); _pSelectionShellItemArray = _CreateSelectionShellItemArray(); // Update DUIView if (_pDUIView) _pDUIView->OnSelectionChange(_pSelectionShellItemArray); _UpdateStatusBar(FALSE); _EnableDisableTBButtons(); } // Only fire selection change events if someone is listening // and if the selection changed event was not caused by going into Edit mode (why?) if (_pauto && !_fInLabelEdit) { // Send out the selection changed notification to the automation after a delay. if (!_bAutoSelChangeTimerSet) { _bAutoSelChangeTimerSet = TRUE; _uAutoSelChangeState = _uSelectionStateChanged; } else { _uAutoSelChangeState |= _uSelectionStateChanged; } // But not too long, since parts of our UI update when they receive this event. // (Update the timer every time to keep delaying it during rapid selection change events) SetTimer(_hwndView, DV_IDTIMER_NOTIFY_AUTOMATION_SELCHANGE, GetDoubleClickTime()/2, NULL); } } void CDefView::_OnDelayedSelectionChange() // handles DV_IDTIMER_NOTIFY_AUTOMATION_SELCHANGE { if (_bAutoSelChangeTimerSet) { KillTimer(_hwndView, DV_IDTIMER_NOTIFY_AUTOMATION_SELCHANGE); if (_uAutoSelChangeState & LVIS_SELECTED) _FireEvent(DISPID_SELECTIONCHANGED); if (_uAutoSelChangeState & LVIS_FOCUSED) _FireEvent(DISPID_FOCUSCHANGED); _bAutoSelChangeTimerSet = FALSE; } } void CDefView::_PostNoItemStateChangedMessage() { if (_pauto && !_fNoItemStateChangePending) { PostMessage(_hwndView, WM_DSV_SENDNOITEMSTATECHANGED, 0, 0); _fNoItemStateChangePending = TRUE; } } void CDefView::_OnNoItemStateChanged() { _FireEvent(DISPID_NOITEMSTATE_CHANGED); _fNoItemStateChangePending = FALSE; } void CDefView::_PostEnumDoneMessage() { PostMessage(_hwndView, WM_DSV_FILELISTENUMDONE, 0, 0); } void CDefView::_PostFillDoneMessage() { _ShowSearchUI(TRUE); PostMessage(_hwndView, WM_DSV_FILELISTFILLDONE, 0, 0); } void CDefView::_OnEnumDoneMessage() { if (_pauto) _FireEvent(DISPID_FILELISTENUMDONE); if (_pfnEnumReadyCallback) _pfnEnumReadyCallback(_pvEnumCallbackData); } #define IN_VIEW_BMP 0x8000 #define EXT_VIEW_GOES_HERE 0x4000 #define PRIVATE_TB_FLAGS (IN_VIEW_BMP | EXT_VIEW_GOES_HERE) #define IN_STD_BMP 0x0000 LRESULT CDefView::_OnNotify(NMHDR *pnm) { switch (pnm->idFrom) { case ID_LISTVIEW: return _OnLVNotify((NM_LISTVIEW *)pnm); case FCIDM_TOOLBAR: return _TBNotify(pnm); default: switch (pnm->code) { case TTN_NEEDTEXT: #define ptt ((LPTOOLTIPTEXT)pnm) _GetToolTipText(ptt->hdr.idFrom, ptt->szText, ARRAYSIZE(ptt->szText)); #undef ptt break; case NM_RCLICK: if (GetParent(pnm->hwndFrom) == _hwndListview) { POINT p; GetMsgPos(&p); _DoColumnsMenu(p.x, p.y); return 1; // To keep normal context menu from appearing } } } return 0; } // ask the folder for the default column state DWORD CDefView::_DefaultColumnState(UINT iCol) { DWORD dwState; if (_pshf2) { if (FAILED(_pshf2->GetDefaultColumnState(iCol, &dwState))) { dwState = SHCOLSTATE_ONBYDEFAULT; // deal with E_NOTIMPL GetDefaultColumState implementations } } else { dwState = SHCOLSTATE_ONBYDEFAULT; } return dwState; } // SHCOLSTATE_ONBYDEFAULT // // columns that are turn on for this view (are displayed in the UI) BOOL CDefView::_IsDetailsColumn(UINT iCol) { return (_vs.GetColumnState(iCol) & SHCOLSTATE_ONBYDEFAULT) ? TRUE : FALSE; } BOOL CDefView::_IsColumnInListView(UINT iCol) { return ((_vs.GetColumnState(iCol) & SHCOLSTATE_ONBYDEFAULT) || (_vs.GetTransientColumnState(iCol) & SHTRANSCOLSTATE_TILEVIEWCOLUMN)) ? TRUE : FALSE; } BOOL CDefView::_IsTileViewColumn(UINT iCol) { return (_vs.GetTransientColumnState(iCol) & SHTRANSCOLSTATE_TILEVIEWCOLUMN) ? TRUE : FALSE; } // SHCOLSTATE_HIDDEN // // columns that should not be displayed in the UI, but are exposed from // the psf2->GetDetailsEx(). this is a way to have programtic access to properties // that don't show up in details view BOOL CDefView::_IsColumnHidden(UINT uCol) { return (_vs.GetColumnState(uCol) & SHCOLSTATE_HIDDEN) ? TRUE : FALSE; } #define COL_CM_MAXITEMS 25 // how many item show up in context menu before more ... is inserted HRESULT CDefView::AddColumnsToMenu(HMENU hm, DWORD dwBase) { BOOL bNeedMoreMenu = FALSE; HRESULT hr = E_FAIL; if (_vs._hdsaColumns) { AppendMenu(hm, MF_STRING | MF_CHECKED | MF_GRAYED, dwBase, _vs.GetColumnName(0)); for (UINT i = 1; i < min(COL_CM_MAXITEMS, _vs.GetColumnCount()); i++) { DWORD dwFlags = _vs.GetColumnState(i); if (!(dwFlags & SHCOLSTATE_HIDDEN)) { if (dwFlags & SHCOLSTATE_SECONDARYUI) bNeedMoreMenu = TRUE; else AppendMenu(hm, MF_STRING | (dwFlags & SHCOLSTATE_ONBYDEFAULT) ? MF_CHECKED : 0, dwBase + i, _vs.GetColumnName(i)); } } if (bNeedMoreMenu || (_vs.GetColumnCount() > COL_CM_MAXITEMS)) { TCHAR szMore[MAX_COLUMN_NAME_LEN]; LoadString(HINST_THISDLL, IDS_COL_CM_MORE, szMore, ARRAYSIZE(szMore)); AppendMenu(hm, MF_SEPARATOR, 0, NULL); AppendMenu(hm, MF_STRING, SFVIDM_VIEW_COLSETTINGS, szMore); } hr = S_OK; } return hr; } UINT CDefView::_RealToVisibleCol(UINT iReal) { ASSERT(_bLoadedColumns && _vs.GetColumnCount()); int iVisible = -1; // start here to get zero based result int cMax = min(_vs.GetColumnCount() - 1, iReal); for (int i = 0; i <= cMax; i++) { if (_IsColumnInListView(i)) { iVisible++; } } ASSERT(-1 != iVisible); return iVisible; } // map listview (zero based) column indexes // indexs (zero based) UINT CDefView::_VisibleToRealCol(UINT iVisible) { ASSERT(_bLoadedColumns && _vs.GetColumnCount()); for (UINT i = 0, cVisibleSeen = 0; i < _vs.GetColumnCount(); i++) { if (_IsColumnInListView(i)) { if (cVisibleSeen == iVisible) { return i; } cVisibleSeen++; } } ASSERT(0); // should never get a vis col not in the real return 0; } void CDefView::_AddTileColumn(UINT uCol) { if (_IsColumnInListView(uCol)) { // All we need to do is make sure it's marked as a tile column _vs.SetTransientColumnState(uCol, SHTRANSCOLSTATE_TILEVIEWCOLUMN, SHTRANSCOLSTATE_TILEVIEWCOLUMN); return; } _vs.SetTransientColumnState(uCol, SHTRANSCOLSTATE_TILEVIEWCOLUMN, SHTRANSCOLSTATE_TILEVIEWCOLUMN); // Now that we set the transient state, we can get the new visible column index // for this guy, and add it to the listview. UINT uColVis = _RealToVisibleCol(uCol); _AddColumnToListView(uCol, uColVis); // We now need to reset the tile info for each item. We can make an optimization: // if this column was added at the end (i.e. biggest visible column), it won't affect // any of the current tiles, so we don't need to do this. Passing -1 gives us the // largest visible index. if (_RealToVisibleCol(-1) != uColVis) { _ResetTileInfo(uColVis, TRUE); } } // Remove all columns that were added because of tileview (unless they were also // added for other reasons). // Note: This should only be called when leaving tileview, since we do not reset the // items' tileinfo. void CDefView::_RemoveTileColumns() { for (UINT uCol = 0; uCol < _vs.GetColumnCount(); uCol++) { if (_IsTileViewColumn(uCol)) { // First nuke the tile bit. UINT uColVis = _RealToVisibleCol(uCol); _vs.SetTransientColumnState(uCol, SHTRANSCOLSTATE_TILEVIEWCOLUMN, 0); // Then go ahead and remove it from listview if it wasn't a details column if (!_IsDetailsColumn(uCol)) { ListView_DeleteColumn(_hwndListview, uColVis); } } } } // This method resets the tileinfo for each item in the listview, based on which // visible column we just added or removed. // uColVis = the visible column that was added or removed. // Note: This must be called prior to there being any tileinfo in the listview containing // a reference to this new column. void CDefView::_ResetTileInfo(UINT uColVis, BOOL bAdded) { if (!_IsOwnerData()) { UINT rguColumns[TILEVIEWLINES]; for (int i = 0; i < ListView_GetItemCount(_hwndListview); i++) { UINT uColBoundary = uColVis; LVITEM lvi; lvi.mask = LVIF_COLUMNS | LVIF_NORECOMPUTE; lvi.iSubItem = 0; lvi.iItem = i; lvi.cColumns = ARRAYSIZE(rguColumns); lvi.puColumns = rguColumns; if (!ListView_GetItem(_hwndListview, &lvi)) continue; if ((lvi.cColumns == 0) || (lvi.cColumns == I_COLUMNSCALLBACK)) { continue; } ASSERT(lvi.cColumns <= ARRAYSIZE(rguColumns)); // If for some reason listview has more, there's a problem // guard just in case if (lvi.cColumns > ARRAYSIZE(rguColumns)) { lvi.cColumns = ARRAYSIZE(rguColumns); } UINT *puColumn = lvi.puColumns; BOOL bChange = FALSE; // Adjust the column numbers as needed: up for added, down for removed. int iIncDec = bAdded ? 1 : -1; if (!bAdded) { // What is this doing? If we've added a column X, we need to adjust columns // from X on up. If we've removed a column X, we need to adjust columns from // X+1 on up. So basically, instead of doing (*puColumn > uColBoundary), we're // doing (*puColumn >= (uColBoundary+1)). So we can do the same ">=" expression // whether or not bAdded, avoiding an if check in the loop. uColBoundary++; } for (UINT uCol = 0; uCol < lvi.cColumns; uCol++, puColumn++) { if (*puColumn >= uColBoundary) { (*puColumn) = (UINT)(iIncDec + (int)(*puColumn)); // Inc or dec. bChange = TRUE; } } if (bChange) // If there were any changes, set the ti back. { LVTILEINFO ti; ti.cbSize = sizeof(ti); ti.iItem = lvi.iItem; ti.cColumns = lvi.cColumns; ti.puColumns = lvi.puColumns; ListView_SetTileInfo(_hwndListview, &ti); } } } } // Called when leaving tileview, this "cleans the slate" so that we reload the // columns properly when re-entering tileview at a later time. void CDefView::_RemoveTileInfo() { if (!_IsOwnerData()) { for (int i = 0; i < ListView_GetItemCount(_hwndListview); i++) { LVTILEINFO ti = {0}; ti.cbSize = sizeof(ti); ti.iItem = i; ti.cColumns = I_COLUMNSCALLBACK; ListView_SetTileInfo(_hwndListview, &ti); } } } // uCol is a real column number, not visible column number // This method toggles the SHCOLSTATE_ONBYDEFAULT bit of the column, // and adds or removes the column as necessary. BOOL CDefView::_HandleColumnToggle(UINT uCol, BOOL bRefresh) { BOOL fWasOn = _IsColumnInListView(uCol); // if its off now, we are adding it BOOL fWasDetailsColumn = _IsDetailsColumn(uCol); UINT uColVisOld = _RealToVisibleCol(uCol); _vs.SetColumnState(uCol, SHCOLSTATE_ONBYDEFAULT, fWasDetailsColumn ? 0 : SHCOLSTATE_ONBYDEFAULT); BOOL fIsOn = _IsColumnInListView(uCol); // This could == fWasOn if it's a tileview column UINT uColVis = _RealToVisibleCol(uCol); if (fIsOn != fWasOn) { if (!fWasOn) { _AddColumnToListView(uCol, uColVis); if (_fs.ViewMode == FVM_TILE) { _ResetTileInfo(uColVis, TRUE); } } else { _vs.RemoveColumn(uColVisOld); ListView_DeleteColumn(_hwndListview, uColVisOld); if (_fs.ViewMode == FVM_TILE) { _ResetTileInfo(uColVisOld, FALSE); } if (_vs._lParamSort == (int) uCol) { UINT iNewVis = _VisibleToRealCol(0); Rearrange(iNewVis); } if (ListView_GetSelectedColumn(_hwndListview) == (UINT)uCol) ListView_SetSelectedColumn(_hwndListview, -1); } } if (bRefresh) { ListView_RedrawItems(_hwndListview, 0, 0x7fff); InvalidateRect(_hwndListview, NULL, TRUE); UpdateWindow(_hwndListview); } return TRUE; } // uCol = Real column number. uColVis = add it as this visible column. void CDefView::_AddColumnToListView(UINT uCol, UINT uColVis) { LV_COLUMN col = {0}; // Adding a column col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; col.fmt = _vs.GetColumnFormat(uCol); col.cx = _vs.GetColumnCharCount(uCol) * _cxChar; // Use default width col.pszText = _vs.GetColumnName(uCol); col.cchTextMax = MAX_COLUMN_NAME_LEN; col.iSubItem = uCol; // not vis // This is all odd... Find Files uses this, but i think it should be LVCFMT_COL_IMAGE if (col.fmt & LVCFMT_COL_HAS_IMAGES) { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_SUBITEMIMAGES, LVS_EX_SUBITEMIMAGES); col.fmt &= ~LVCFMT_COL_HAS_IMAGES; } if (-1 != ListView_InsertColumn(_hwndListview, uColVis, &col)) { // now add it to our DSA _vs.AppendColumn(uColVis, (USHORT) col.cx, uColVis); if (!_fGroupView && (_vs._lParamSort == (int)uCol)) { ListView_SetSelectedColumn(_hwndListview, uColVis); } } } void SetHeaderSort(HWND hwndHead, int iCol, UINT sortFlags) { HDITEM hdi = {HDI_FORMAT}; Header_GetItem(hwndHead, iCol, &hdi); hdi.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN); hdi.fmt |= sortFlags; Header_SetItem(hwndHead, iCol, &hdi); } void CDefView::_SetSortFeedback() { HWND hwndHead = ListView_GetHeader(_hwndListview); // the _IsOwnerData() is bad. this keeps search from getting sort UI feedback. // to fix this implement a mode where the sort has not been determined and thus we don't // display any sort feedback. regular folders could use this too as after items have // been added the view is not really sorted if (!hwndHead || _IsOwnerData()) return; BOOL fRemoveBitmapFromLastHeader = TRUE; int iColLast = _RealToVisibleCol(_vs._iLastColumnClick); int iCol = _RealToVisibleCol((UINT)_vs._lParamSort); if (_fGroupView) { SetHeaderSort(hwndHead, iCol, 0); } else { ListView_SetSelectedColumn(_hwndListview, iCol); SetHeaderSort(hwndHead, iCol, _vs._iDirection > 0 ? HDF_SORTUP : HDF_SORTDOWN); // Only remove the bitmap if the last header is not the one we are currently sorting by if (iColLast == iCol) fRemoveBitmapFromLastHeader = FALSE; } if (fRemoveBitmapFromLastHeader && iColLast != -1) { SetHeaderSort(hwndHead, iColLast, 0); } } // use the folder to compare two items, falling back if the lParam is not understood by // that folder. // 99/05/18 #341468 vtan: If the first comparison fails it may be because // lParamSort is not understood by IShellFolder::CompareIDs (perhaps it's // an extended column that might not be installed any more) // In this case get the default comparison method // and use that. If that fails use 0 which should hopefully not fail. If // the 0 case fails we are toast with an assert. HRESULT CDefView::_CompareIDsFallback(LPARAM lParam, LPCITEMIDLIST p1, LPCITEMIDLIST p2) { HRESULT hr = _pshf->CompareIDs(lParam, p1, p2); if (FAILED(hr)) { LPARAM lParamSort; _vs.GetDefaults(this, &lParamSort, NULL, NULL); hr = _pshf->CompareIDs(lParamSort | (SHCIDS_ALLFIELDS & lParam), p1, p2); if (FAILED(hr)) { // even that did not work, fall back to zero based compare (pluse the all fields flag) hr = _pshf->CompareIDs((SHCIDS_ALLFIELDS & lParam), p1, p2); } } return hr; } // compare two items, taking into account the sort direction int CDefView::_CompareIDsDirection(LPARAM lParam, LPCITEMIDLIST p1, LPCITEMIDLIST p2) { ASSERT(_vs._iDirection != 0); HRESULT hr = _CompareIDsFallback(lParam, (LPITEMIDLIST)p1, (LPITEMIDLIST)p2); return ShortFromResult(hr) * _vs._iDirection; } // p1 and p2 are pointers to the lv_item's LPARAM, which is currently the pidl int CALLBACK CDefView::_Compare(void *p1, void *p2, LPARAM lParam) { CDefView *pdv = (CDefView *)lParam; return pdv->_CompareIDsDirection(pdv->_vs._lParamSort, (LPITEMIDLIST)p1, (LPITEMIDLIST)p2); } typedef struct { VARIANT var; BOOL fIsFolder; } VARIANT_AND_FOLDER; typedef struct { VARIANT_AND_FOLDER *pvars; SHCOLUMNID scid; CDefView *pdv; } VARIANT_SORT_INFO; int CALLBACK _CompareVariantCallback(LPARAM dw1, LPARAM dw2, LPARAM lParam) { VARIANT_SORT_INFO *psi = (VARIANT_SORT_INFO *)lParam; int iRet = 0; // Always put the folders first if (psi->pvars[dw1].fIsFolder) { if (!psi->pvars[dw2].fIsFolder) iRet = -1; } else if (psi->pvars[dw2].fIsFolder) { iRet = 1; } if (0 == iRet) { iRet = CompareVariants(psi->pvars[dw1].var, psi->pvars[dw2].var); } return iRet * psi->pdv->_vs._iDirection; } #define LV_NOFROZENITEM -1 HRESULT CDefView::_Sort(void) { HRESULT hr = CallCB(SFVM_ARRANGE, 0, _vs._lParamSort); if (FAILED(hr)) { hr = HRESULT_FROM_WIN32(ERROR_CAN_NOT_COMPLETE); int iIndexRecycleBin = LV_NOFROZENITEM; POINT ptRecycleBin; _SetSortFeedback(); // For desktop, we need to freeze the recycle bin position before we arrage other icons. if (_fPositionRecycleBin) { iIndexRecycleBin = _FreezeRecycleBin(&ptRecycleBin); _fPositionRecycleBin = FALSE; } // This is semi-bogus for defview to care whether the column is extended or not. // We could have modified the ISF::CompareIDs() to handle extended columns, but // then it would only have the pidls, and would have to re-extract any data, so // its much faster if we separate out the extended columns, and take advantage // of listview's caching abilities. DWORD dwState = _DefaultColumnState((UINT)_vs._lParamSort); SHCOLUMNID scid; HRESULT hrMapColumn = E_FAIL; if (_pshf2) hrMapColumn = _pshf2->MapColumnToSCID((UINT)_vs._lParamSort, &scid); // SHCOLSTATE_PREFER_VARCMP tells us that the folder's CompareIDs() // produces the same result as comparing the variants. this is an optimization // for folders who's CompareIDs() are slow (bit bucket) if (_IsOwnerData() || (dwState & (SHCOLSTATE_EXTENDED | SHCOLSTATE_PREFER_VARCMP))) { if (_GetBackgroundTaskCount(TOID_DVBackgroundEnum) == 0) { int cItems = ListView_GetItemCount(_hwndListview); if (cItems) { VARIANT_SORT_INFO vsi; BOOL fOkToProceed = TRUE; if ((UINT)_vs._lParamSort == 0) { vsi.scid = SCID_NAME; } else if (SUCCEEDED(hrMapColumn)) { vsi.scid = scid; } else { fOkToProceed = FALSE; hr = hrMapColumn; } if (fOkToProceed) { vsi.pvars = new VARIANT_AND_FOLDER[cItems]; if (vsi.pvars) { vsi.pdv = this; for (int i = 0; i < cItems; i++) { LPCITEMIDLIST pidl = _GetPIDL(i); if (pidl) { DWORD dwAttrib = SHGetAttributes(_pshf, pidl, SFGAO_FOLDER); vsi.pvars[i].fIsFolder = dwAttrib & SFGAO_FOLDER; if ((UINT)_vs._lParamSort == 0) // This is the NAME column { WCHAR szName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(_pshf, pidl, SHGDN_INFOLDER | SHGDN_NORMAL, szName, ARRAYSIZE(szName)))) { InitVariantFromStr(&vsi.pvars[i].var, szName); } } else { _pshf2->GetDetailsEx(pidl, &vsi.scid, &vsi.pvars[i].var); } } } hr = CallCB(SFVM_SORTLISTDATA, (LPARAM)_CompareVariantCallback, (LPARAM)&vsi); // dont send a LVM_SORTITEMS to an ownerdraw or comctl32 will rip if (FAILED(hr) && !_IsOwnerData() && ListView_SortItemsEx(_hwndListview, _CompareVariantCallback, (LPARAM)&vsi)) hr = S_OK; for (int i = 0; i < cItems; i++) { VariantClear(&vsi.pvars[i].var); } delete [] vsi.pvars; } } } } } else { ASSERT(!_IsOwnerData()) // dont send a LVM_SORTITEMS to an ownerdraw or comctl32 will rip if (ListView_SortItems(_hwndListview, _Compare, (LPARAM)this)) hr = S_OK; } //If we froze recycle-bin earlier, now is the time to put it in it's default position. if (iIndexRecycleBin != LV_NOFROZENITEM) _SetRecycleBinInDefaultPosition(&ptRecycleBin); } return hr; } // this should NOT check for whether the item is already in the listview // if it does, we'll have some serious performance problems int CDefView::_AddObject(LPITEMIDLIST pidl) // takes ownership of pidl. { int iItem = -1; // Check the commdlg hook to see if we should include this // object. if ((S_OK == _IncludeObject(pidl)) && (S_FALSE != CallCB(SFVM_INSERTITEM, 0, (LPARAM)pidl))) { LV_ITEM item = {0}; item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_COLUMNS; item.iItem = INT_MAX; // add at end item.iImage = I_IMAGECALLBACK; item.pszText = LPSTR_TEXTCALLBACK; item.lParam = (LPARAM)pidl; // Takes pidl ownership. item.cColumns = I_COLUMNSCALLBACK; // REVIEW: why not fill this in like the _UpdateObject call? That would fix the problem where GroupBy doesn't keep the "Searching UI" going... iItem = ListView_InsertItem(_hwndListview, &item); if (iItem < 0) { ILFree(pidl); } else if (_bBkFilling) { _pEnumTask->_AddToPending(pidl); } _OnContentsChanged(); if (iItem == 0) { _PostNoItemStateChangedMessage(); } } else { ILFree(pidl); } return iItem; } // Find an item in the view int CDefView::_FindItem(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlFound, BOOL fSamePtr, BOOL fForwards) { RIP(ILFindLastID(pidl) == pidl); int cItems = ListView_GetItemCount(_hwndListview); if (_iLastFind >= cItems) _iLastFind = 0; int iItem = _iLastFind; if (SUCCEEDED(CallCB(SFVM_INDEXOFITEMIDLIST, (WPARAM)&iItem, (LPARAM)pidl))) { if (ppidlFound) *ppidlFound = (LPITEMIDLIST)_GetPIDL(iItem); // cast as caller knows how to free this } else { iItem = -1; // assume failure for (int cCounter = 0, i = _iLastFind; cCounter < cItems; cCounter++) { LPCITEMIDLIST pidlT = _GetPIDL(i); ASSERT(pidlT); if (pidlT) { if ((pidlT == pidl) || (!fSamePtr && (0 == ResultFromShort(_pshf->CompareIDs(0, pidl, pidlT))))) { if (ppidlFound) *ppidlFound = (LPITEMIDLIST)pidlT; // cast as callers know how to free _iLastFind = iItem = i; // success // TraceMsg(TF_DEFVIEW, "####FIND CACHE RESULT --- %s by %d", cCounter < iItem ? TEXT("WIN") : TEXT("LOSE"), iItem - cCounter); break; } } if (fForwards) { i = (i+1)%cItems; } else { i = (i > 0)?(i - 1):(cItems-1); } } if (-1 == iItem) { _iLastFind = 0; // didn't find it, reset this for next time } } return iItem; } int CDefView::_FindItemHint(LPCITEMIDLIST pidl, int iItem) { _iLastFind = iItem; return _FindItem(pidl, NULL, FALSE, FALSE); } // Function to process the SFVM_REMOVEOBJECT message, by searching // through the list for a match of the pidl. If a match is found, the // item is removed from the list and the index number is returned, else // -1 is returned. int CDefView::_RemoveObject(LPCITEMIDLIST pidl, BOOL fSamePtr) { int i = 0; // Docfind will pass in a null pointer to tell us that it wants // to refresh the window by deleting all of the items from it. if (pidl == NULL) { CallCB(SFVM_DELETEITEM, 0, 0); // view callback notify ListView_DeleteAllItems(_hwndListview); _PostNoItemStateChangedMessage(); _OnContentsChanged(); } else { // Non null go look for item. i = _FindItem(pidl, NULL, fSamePtr); if (i >= 0) { RECT rc; UINT uState = ListView_GetItemState(_hwndListview, i, LVIS_ALL); if (uState & LVIS_FOCUSED) ListView_GetItemRect(_hwndListview, i, &rc, LVIR_ICON); if (_bBkFilling) _pEnumTask->_DeleteFromPending(pidl); // removes the pointer from the pending list. ListView_DeleteItem(_hwndListview, i); // we deleted the focused item.. replace the focus to the nearest item. if (uState & LVIS_FOCUSED) { int iFocus = i; if (_IsPositionedView() || _fGroupView) { LV_FINDINFO lvfi = {0}; lvfi.flags = LVFI_NEARESTXY; lvfi.pt.x = rc.left; lvfi.pt.y = rc.top; iFocus = ListView_FindItem(_hwndListview, -1, &lvfi); } else { if (ListView_GetItemCount(_hwndListview) >= iFocus) iFocus--; } if (iFocus != -1) { ListView_SetItemState(_hwndListview, iFocus, LVIS_FOCUSED, LVIS_FOCUSED); ListView_EnsureVisible(_hwndListview, iFocus, FALSE); } else { // RAID 372130 // Notify image preview control to update its image (to // nothing). The image preview control uses focus change // events to track when it should update the image it is // displaying. When it receives a focus change event, it // queries the listview to see which item has focus, then // displays that item in the image preview window. When // the last item in the listview is deleted, we need to // fire a focus change event to the image preview control // even though the focus has not changed to another item. // This way, the image preview control realizes there is // nothing with focus, and correctly displays as empty. if (_fs.ViewMode == FVM_THUMBSTRIP) _ThumbstripSendImagePreviewFocusChangeEvent(); } } // Notify automation if the listview is now empty UINT uCount = 0; GetObjectCount(&uCount); if (!uCount) { _PostNoItemStateChangedMessage(); } _OnContentsChanged(); } } return i; } // search the list for a match of the first pidl. If a match is found, // the item is updated to the second pidl... int CDefView::_UpdateObject(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew) { LPITEMIDLIST pidlOldToFree; int i = _FindItem(pidlOld, &pidlOldToFree, FALSE); if (i >= 0) { if (_IsOwnerData()) { if (SUCCEEDED(CallCB(SFVM_SETITEMIDLIST, i, (LPARAM)pidlNew))) { // Invalidate the rectangle so we update the item... RECT rc; ListView_GetItemRect(_hwndListview, i, &rc, LVIR_BOUNDS); InvalidateRect(_hwndListview, &rc, FALSE); ListView_Update(_hwndListview, i); _OnContentsChanged(); } else { i = -1; // we failed, try to cleanup and bail. } } else { LPITEMIDLIST pidlNewClone = ILClone(pidlNew); if (pidlNewClone) { LV_ITEM item = {0}; // We found the item so lets now update it in the // the view. item.mask = LVIF_PARAM | LVIF_TEXT | LVIF_IMAGE; item.iItem = i; item.pszText = LPSTR_TEXTCALLBACK; item.iImage = I_IMAGECALLBACK; item.lParam = (LPARAM)pidlNewClone; // if selected, deselect it UINT uState = ListView_GetItemState(_hwndListview, i, LVIS_FOCUSED|LVIS_SELECTED); if (uState & (LVIS_FOCUSED|LVIS_SELECTED)) { _OnLVSelectionChange(i, uState, 0, (LPARAM)pidlOldToFree); } // remove the item. CallCB(SFVM_DELETEITEM, 0, (LPARAM)pidlOldToFree); // now insert it with a new pidl CallCB(SFVM_INSERTITEM, 0, (LPARAM)pidlNewClone); // if it was selected, select it again if (uState & (LVIS_FOCUSED|LVIS_SELECTED)) { _OnLVSelectionChange(i, 0, uState, (LPARAM)pidlNewClone); } if (_fGroupView) { item.mask |= LVIF_GROUPID; item.iGroupId = (int)_GetGroupForItem(item.iItem, pidlNewClone); } ListView_SetItem(_hwndListview, &item); if (_bBkFilling) { _pEnumTask->_DeleteFromPending(pidlOld); _pEnumTask->_AddToPending(pidlNewClone); } int cCols = _GetHeaderCount(); for (item.iSubItem++; item.iSubItem < cCols; item.iSubItem++) { ListView_SetItemText(_hwndListview, item.iItem, item.iSubItem, LPSTR_TEXTCALLBACK); } // // Warning!!! Only free pidlOldToFree *after* calling ListView_SetItem. ListView_SetItem // can call back asking for image info on the old pidl! // // Now delete the item but don't call the callback since we did that already. OnListViewDelete(i, pidlOldToFree, FALSE); _OnContentsChanged(); } else { i = -1; } } } return i; } // // invalidates all items with the given image index. // // or update all items if iImage == -1 // void CDefView::_UpdateImage(int iImage) { // -1 means update all // reset the imagelists incase the size has changed, and do // a full update. if (iImage == -1) { if (_IsImageMode()) { _RemoveThumbviewTasks(); _pImageCache->Flush(TRUE); _SetThumbview(); } else if (_IsTileMode()) { _SetTileview(); } else { _SetSysImageList(); } _ReloadContent(); } else { // get a dc so we can optimize for visible/not visible cases HDC hdcLV = GetDC(_hwndListview); // scan the listview updating any items which match LV_ITEM item = {0}; int cItems = ListView_GetItemCount(_hwndListview); for (item.iItem = 0; item.iItem < cItems; item.iItem++) { item.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_NORECOMPUTE; ListView_GetItem(_hwndListview, &item); int iImageOld = item.iImage; if (item.iImage == iImage) // this filters I_IMAGECALLBACK for us { RECT rc; LPCITEMIDLIST pidl = _GetPIDLParam(item.lParam, item.iItem); CFSFolder_UpdateIcon(_pshf, pidl); // // if the item is visible then we don't want to flicker so just // kick off an async extract. if the item is not visible then // leave it for later by slamming in I_IMAGECALLBACK. // item.iImage = I_IMAGECALLBACK; if (!_IsImageMode() && ListView_GetItemRect(_hwndListview, item.iItem, &rc, LVIR_ICON) && RectVisible(hdcLV, &rc)) { int iImageNew; HRESULT hr = _GetIconAsync(pidl, &iImageNew, FALSE); if (hr == S_FALSE) continue; if (SUCCEEDED(hr)) { if (iImageNew == iImageOld) { ListView_RedrawItems(_hwndListview, item.iItem, item.iItem); continue; } item.iImage = iImageNew; } } item.mask = LVIF_IMAGE; item.iSubItem = 0; ListView_SetItem(_hwndListview, &item); } } ReleaseDC(_hwndListview, hdcLV); } } // Function to process the SFVM_REFRESHOBJECT message, by searching // through the list for a match of the first pidl. If a match is found, // the item is redrawn. int CDefView::_RefreshObject(LPITEMIDLIST *ppidl) { int i = _FindItem(ppidl[0], NULL, FALSE); if (i >= 0) ListView_RedrawItems(_hwndListview, i, i); return i; } HRESULT CDefView::_GetItemObjects(LPCITEMIDLIST **ppidl, UINT uWhat, UINT *pcItems) { *pcItems = _GetItemArray(NULL, 0, uWhat); if (ppidl) { *ppidl = NULL; if (*pcItems) { *ppidl = (LPCITEMIDLIST *)LocalAlloc(LPTR, sizeof(*ppidl) * (*pcItems)); if (*ppidl) _GetItemArray(*ppidl, *pcItems, uWhat); else return E_OUTOFMEMORY; } } return S_OK; } void CDefView::_SetItemPosition(int i, int x, int y) { ListView_SetItemPosition32(_hwndListview, i, x, y); _fUserPositionedItems = TRUE; } void CDefView::_SetItemPos(LPSFV_SETITEMPOS psip) { int i = _FindItem(psip->pidl, NULL, FALSE); if (i >= 0) { _SetItemPosition(i, psip->pt.x, psip->pt.y); } } // "View State" here refers to column information and icon positions BOOL CDefView::GetViewState() { BOOL bRet = FALSE; IPropertyBag* ppb; if (SUCCEEDED(IUnknown_QueryServicePropertyBag(_psb, SHGVSPB_FOLDER, IID_PPV_ARG(IPropertyBag, &ppb)))) { DWORD dw; // Check if we've saved state before (first check) or if we may want to // try upgrading some settings if we haven't saved state before (second check) if (SUCCEEDED(SHPropertyBag_ReadDWORD(ppb, VS_PROPSTR_MODE, &dw)) || SUCCEEDED(SHPropertyBag_ReadDWORD(ppb, VS_PROPSTR_FFLAGS, &dw))) { bRet = SUCCEEDED(_vs.LoadFromPropertyBag(this, ppb)); } else { IStream* pstm; if (SUCCEEDED(_LoadGlobalViewState(&pstm))) { _vs.LoadFromStream(this, pstm); bRet = TRUE; pstm->Release(); } } ppb->Release(); } else { // 99/02/05 #226140 vtan: Try to get the view state stream // from ShellBrowser. If that fails then look for a global // view state stream that is stored when the user clicks on // the "Like Current Folder" in the View tab of folder settings. // IShellBrowser::GetViewStateStream() match the dwDefRevCount // of the cabinet state to make sure that it's valid. IStream *pstm; if (SUCCEEDED(_psb->GetViewStateStream(STGM_READ, &pstm)) || SUCCEEDED(_LoadGlobalViewState(&pstm))) { _vs.LoadFromStream(this, pstm); pstm->Release(); bRet = TRUE; } } return bRet; } void CDefView::_UpdateEnumerationFlags() { SHELLSTATE ss; SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS | SSF_SHOWCOMPCOLOR, FALSE); _fShowAllObjects = ss.fShowAllObjects; // Don't allow compression coloring on the desktop proper _fShowCompColor = _IsDesktop() ? FALSE : ss.fShowCompColor; } // starts and stops the spinning Globe animation // indicating that we are in the process of navigating to // a directory void CDefView::_GlobeAnimation(BOOL fStartSpinning, BOOL fForceStop) { if (_fGlobeCanSpin) { DWORD dwCmdID = 0; if (fStartSpinning) { if (_crefGlobeSpin++ == 0) { dwCmdID = CBRANDIDM_STARTGLOBEANIMATION; } } else { ASSERT(_crefGlobeSpin > 0); if (fForceStop || (--_crefGlobeSpin == 0)) { dwCmdID = CBRANDIDM_STOPGLOBEANIMATION; // our navigation is over, never spin again _fGlobeCanSpin = FALSE; } } if (dwCmdID) { IUnknown_QueryServiceExec(_psb, SID_SBrandBand, &CGID_BrandCmdGroup, dwCmdID, 0, NULL, NULL); } } } LRESULT SearchingUIWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case GET_WM_CTLCOLOR_MSG(CTLCOLOR_STATIC): SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, uMsg), GetSysColor(COLOR_WINDOW)); return (LRESULT)GetSysColorBrush(COLOR_WINDOW); default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } void CDefView::_ShowSearchUI(BOOL fStartSearchWindow) { if (_fAllowSearchingWindow || _crefSearchWindow) // once started, make sure our refcount finishes { if (fStartSearchWindow) { if (_crefSearchWindow++ == 0) { // The static window could already exist during a refresh if (!_hwndStatic) { _hwndStatic = SHCreateWorkerWindowW((WNDPROC)SearchingUIWndProc, _hwndView, 0, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, NULL, NULL); if (_hwndStatic) { HWND hAnimate = CreateWindowEx(0, ANIMATE_CLASS, c_szNULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | ACS_TRANSPARENT | ACS_AUTOPLAY | ACS_CENTER, 0, 0, 0, 0, _hwndStatic, (HMENU)ID_STATIC, HINST_THISDLL, NULL); if (hAnimate) { RECT rc; GetClientRect(_hwndView, &rc); // Move this window to the top so the user sees the "looking" icon // We are in a "normal" view. We need to do this always or the // Flashlight doesn't appear. It tested safe with WebView on. SetWindowPos(_hwndStatic, HWND_TOP, 0, 0, rc.right, rc.bottom, 0); SetWindowPos(hAnimate, HWND_TOP, 0, 0, rc.right, rc.bottom, SWP_NOZORDER); _OnMoveWindowToTop(_hwndStatic); SetTimer(_hwndView, DV_IDTIMER_START_ANI, 2000, NULL); // 2 second timer } } } ShowHideListView(); } } else { if (0 == _crefSearchWindow) // if _ShowSearchUI(FALSE) gets called before _ShowSearchUI(TRUE) { _fAllowSearchingWindow = FALSE; } else if (--_crefSearchWindow == 0) { _fAllowSearchingWindow = FALSE; ShowHideListView(); } } } } // this is only called from within SHCNE_* don't put up ui on the enum error. void CDefView::_FullViewUpdate(BOOL fUpdateItem) { if (fUpdateItem) _ReloadContent(); // the folder we're looking at has changed else FillObjectsShowHide(FALSE); // our contents have changed } void CDefView::_ShowControl(UINT idControl, int idCmd) { IBrowserService *pbs; if (SUCCEEDED(_psb->QueryInterface(IID_PPV_ARG(IBrowserService, &pbs)))) { pbs->ShowControlWindow(idControl, idCmd); pbs->Release(); } } // This function does three things: // 1 - Alter the size of the parent to best fit around the items we have. // 2 - Set the default icon view mode // 3 - Make sure the correct toolbars are showing // void CDefView::_BestFit() { // Only bestfit once if (_fs.fFlags & FWF_BESTFITWINDOW) { _fs.fFlags &= ~FWF_BESTFITWINDOW; // Make sure the correct toolbars are showing the first time this folder is displayed // int iITbar = SBSC_HIDE; int iStdBar = SBSC_HIDE; switch (_uDefToolbar) { case HIWORD(TBIF_INTERNETBAR): iITbar = SBSC_SHOW; goto ShowToolbar; case HIWORD(TBIF_STANDARDTOOLBAR): iStdBar = SBSC_SHOW; goto ShowToolbar; case HIWORD(TBIF_NOTOOLBAR): ShowToolbar: _ShowControl(FCW_INTERNETBAR, iITbar); _ShowControl(FCW_TOOLBAR, iStdBar); break; } } } void CDefView::_ClearPostedMsgs(HWND hwnd) { MSG msg; while (PeekMessage(&msg, hwnd, WM_DSV_UPDATEICON, WM_DSV_UPDATEICON, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_UPDATEICON after WM_DESTROY!!!"); LPITEMIDLIST pidl = (LPITEMIDLIST) msg.wParam; ILFree(pidl); } } while (PeekMessage(&msg, hwnd, WM_DSV_UPDATECOLDATA, WM_DSV_UPDATECOLDATA, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_UPDATECOLDATA after WM_DESTROY!!!"); delete (CBackgroundColInfo*)msg.lParam; } } while (PeekMessage(&msg, hwnd, WM_DSV_DELAYSTATUSBARUPDATE, WM_DSV_DELAYSTATUSBARUPDATE, PM_REMOVE)) { if (msg.hwnd == hwnd) { LocalFree((void *)msg.lParam); } } while (PeekMessage(&msg, hwnd, WM_DSV_SETIMPORTANTCOLUMNS, WM_DSV_SETIMPORTANTCOLUMNS, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_SETIMPORTANTCOLUMNS after WM_DESTROY!!!"); delete (CBackgroundTileInfo*)msg.lParam; } } while (PeekMessage(&msg, hwnd, WM_DSV_SETITEMGROUP, WM_DSV_SETITEMGROUP, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_SETITEMGROUP after WM_DESTROY!!!"); delete (CBackgroundGroupInfo*)msg.lParam; } } while (PeekMessage(&msg, hwnd, WM_DSV_UPDATETHUMBNAIL, WM_DSV_UPDATETHUMBNAIL, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_UPDATETHUMBNAIL after WM_DESTROY!!!"); _CleanupUpdateThumbnail((DSV_UPDATETHUMBNAIL*)msg.lParam); } } while (PeekMessage(&msg, hwnd, WM_DSV_POSTCREATEINFOTIP, WM_DSV_POSTCREATEINFOTIP, PM_REMOVE)) { // PeekMessage(hwnd) can return messages posted to CHILDREN of this hwnd... // Verify that the message was really for us. // if (msg.hwnd == hwnd) { TraceMsg(TF_DEFVIEW, "DefView: WM_DSV_POSTCREATEINFOTIP after WM_DESTROY!!!"); _OnPostCreateInfotipCleanup((TOOLINFO *)msg.wParam); } } } void CDefView::_CallRefresh(BOOL fPreRefresh) { if (fPreRefresh) { IUnknown_Exec(_pshf, NULL, OLECMDID_REFRESH, 0, NULL, NULL); } CallCB(SFVM_REFRESH, fPreRefresh, 0); } void CDefView::FillDone() { SendMessage(_hwndListview, WM_SETREDRAW, (WPARAM)FALSE, 0); _fListviewRedraw = TRUE; AddRef(); // hold a ref to ourself while in this function. _fAllowSearchingWindow = FALSE; _PostFillDoneMessage(); if (_bBkFilling) _OnStopBackgroundEnum(); HRESULT hr = _pEnumTask->FillObjectsDoneToView(); _pEnumTask->Release(); _pEnumTask = NULL; if (SUCCEEDED(hr)) { // Clear our error state, if we were in one _fEnumFailed = FALSE; if (_fSyncOnFillDone) { _vs.Sync(this, TRUE); _fSyncOnFillDone = FALSE; } ShowHideListView(); // set the focus on the first item. _FocusOnSomething(); _DoThumbnailReadAhead(); } else { // The fill objects failed for some reason, go into error mode TraceMsg(TF_WARNING, "::FillObjects failed to enumerate for some reason"); _fEnumFailed = TRUE; ShowHideListView(); } // Tell the defview client that this window has been refreshed _CallRefresh(FALSE); _OnContentsChanged(); _UpdateStatusBar(TRUE); _PostEnumDoneMessage(); Release(); SendMessage(_hwndListview, WM_SETREDRAW, (WPARAM)TRUE, 0); _fListviewRedraw = FALSE; } HRESULT CDefView::_OnStartBackgroundEnum() { _GlobeAnimation(TRUE); _ShowSearchUI(TRUE); _bBkFilling = TRUE; return S_OK; } HRESULT CDefView::_OnStopBackgroundEnum() { _bBkFilling = FALSE; _GlobeAnimation(FALSE); _ShowSearchUI(FALSE); return S_OK; } HRESULT CDefView::_OnBackgroundEnumDone() { FillDone(); _UpdateStatusBar(FALSE); CallCB(SFVM_BACKGROUNDENUMDONE, 0, 0); return S_OK; } HRESULT EmptyBkgrndThread(IShellTaskScheduler *pScheduler) { HRESULT hr = S_OK; if (pScheduler) { // empty the queue and wait until it is empty..... hr = pScheduler->RemoveTasks(TOID_NULL, ITSAT_DEFAULT_LPARAM, TRUE); } return hr; } DWORD CDefView::_GetEnumFlags() { // Setup the enum flags. DWORD grfEnumFlags = SHCONTF_NONFOLDERS; if (_fShowAllObjects) grfEnumFlags |= SHCONTF_INCLUDEHIDDEN; //Is this View in Common Dialog if (!(grfEnumFlags & SHCONTF_INCLUDEHIDDEN)) { // Ask Common dialog if its wants to show all files ICommDlgBrowser2 *pcdb2; if (SUCCEEDED(_psb->QueryInterface(IID_PPV_ARG(ICommDlgBrowser2, &pcdb2)))) { DWORD dwFlags = 0; pcdb2->GetViewFlags(&dwFlags); if (dwFlags & CDB2GVF_SHOWALLFILES) grfEnumFlags |= SHCONTF_INCLUDEHIDDEN; pcdb2->Release(); } } if (!(_fs.fFlags & FWF_NOSUBFOLDERS)) grfEnumFlags |= SHCONTF_FOLDERS; return grfEnumFlags; } HRESULT CDefView::FillObjectsShowHide(BOOL fInteractive) { HRESULT hr = S_OK; DECLAREWAITCURSOR; SetWaitCursor(); // This is a potentially long operation // To get here we're either not enumerating at all, // or we are enumerating on the background thread, // or we got re-entered ASSERT((!_pEnumTask&&!_bBkFilling) || (_pEnumTask)); if (_pEnumTask) { if (fInteractive) { // This is in response to the user pressing F5, // assume the current enumeration will be valid hr = S_FALSE; } else if (!_bBkFilling) { // We're not on the background but we have a _pEnumTask, this means // that we got re-entered during the below call to FillObjectsToDPA. // Assume the current enumeration attempt will be valid hr = S_FALSE; } else { if (_pScheduler) { // An UPDATEDIR or equivalent happened, anything already enumerated could be bad. // Tell the current enumeration task to give up _pScheduler->RemoveTasks(TOID_DVBackgroundEnum, ITSAT_DEFAULT_LPARAM, FALSE); _pScheduler->RemoveTasks(TOID_DVBackgroundGroup, ITSAT_DEFAULT_LPARAM, TRUE); } ASSERT(_bBkFilling); _OnStopBackgroundEnum(); _pEnumTask->Release(); _pEnumTask = NULL; } } if (S_OK==hr) { _pEnumTask = new CDefviewEnumTask(this, ++_dwEnumId); if (_pEnumTask) { // Note: It is possible for us to get re-entered during FillObjectsToDPA, // since we pass our HWND to the enumerator. _pEnumTask->FillObjectsToDPA(fInteractive); hr = _pEnumTask->FillObjectsDPAToDone(); } else { _fEnumFailed = TRUE; ShowHideListView(); hr = E_OUTOFMEMORY; } } ResetWaitCursor(); return hr; } // This implementation uses following assumptions. // (1) The IShellFolder uses CDefFolderMenu. // (2) The CDefFolderMenu always add the folder at the top. #define EC_SELECTION 0 #define EC_BACKGROUND 1 #define EC_EITHER 3 HRESULT CDefView::_ExplorerCommand(UINT idFCIDM) { HRESULT hr = E_FAIL; static struct { UINT idmFC; UINT fBackground; LPCTSTR pszVerb; } const c_idMap[] = { { SFVIDM_FILE_RENAME, EC_SELECTION, c_szRename }, { SFVIDM_FILE_DELETE, EC_SELECTION, c_szDelete }, { SFVIDM_FILE_PROPERTIES, EC_EITHER, c_szProperties }, { SFVIDM_EDIT_COPY, EC_SELECTION, c_szCopy }, { SFVIDM_EDIT_CUT, EC_SELECTION, c_szCut }, { SFVIDM_FILE_LINK, EC_SELECTION, c_szLink }, { SFVIDM_EDIT_PASTE, EC_BACKGROUND, c_szPaste }, { SFVIDM_EDIT_PASTELINK, EC_BACKGROUND, c_szPasteLink }, }; for (int i = 0; i < ARRAYSIZE(c_idMap); i++) { if (c_idMap[i].idmFC == idFCIDM) { IContextMenu *pcm; if (c_idMap[i].fBackground == EC_BACKGROUND) { hr = _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IContextMenu, &pcm)); } else { hr = _CreateSelectionContextMenu(IID_PPV_ARG(IContextMenu, &pcm)); if (FAILED(hr) && (c_idMap[i].fBackground == EC_EITHER) && !ListView_GetSelectedCount(_hwndListview)) { hr = _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IContextMenu, &pcm)); } } if (SUCCEEDED(hr)) { CMINVOKECOMMANDINFOEX ici = {0}; ici.cbSize = sizeof(ici); ici.hwnd = _hwndMain; ici.nShow = SW_NORMAL; // record if shift or control was being held down SetICIKeyModifiers(&ici.fMask); // Fill in both the ansi verb and the unicode verb since we // don't know who is going to be processing this thing. CHAR szVerbAnsi[40]; SHUnicodeToAnsi(c_idMap[i].pszVerb, szVerbAnsi, ARRAYSIZE(szVerbAnsi)); ici.lpVerb = szVerbAnsi; ici.lpVerbW = c_idMap[i].pszVerb; ici.fMask |= CMIC_MASK_UNICODE; HMENU hmenu = CreatePopupMenu(); if (hmenu) { IUnknown_SetSite(pcm, SAFECAST(this, IOleCommandTarget *)); pcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, 0); _bContextMenuMode = TRUE; hr = _InvokeContextMenu(pcm, &ici); _bContextMenuMode = FALSE; DestroyMenu(hmenu); IUnknown_SetSite(pcm, NULL); } pcm->Release(); } else { // keys are pressed when there is no selection. MessageBeep(0); } break; } ASSERT(i < ARRAYSIZE(c_idMap)); } return hr; } STDAPI_(BOOL) Def_IsPasteAvailable(IDropTarget *pdtgt, DWORD *pdwEffect); BOOL CDefView::_AllowCommand(UINT uID) { DWORD dwAttribsIn; DWORD dwEffect; switch (uID) { case SFVIDM_EDIT_PASTE: return Def_IsPasteAvailable(_pdtgtBack, &dwEffect); case SFVIDM_EDIT_PASTELINK: Def_IsPasteAvailable(_pdtgtBack, &dwEffect); return dwEffect & DROPEFFECT_LINK; case SFVIDM_EDIT_COPY: dwAttribsIn = SFGAO_CANCOPY; break; case SFVIDM_EDIT_CUT: dwAttribsIn = SFGAO_CANMOVE; break; case SFVIDM_FILE_DELETE: dwAttribsIn = SFGAO_CANDELETE; break; case SFVIDM_FILE_LINK: dwAttribsIn = SFGAO_CANLINK; break; case SFVIDM_FILE_PROPERTIES: dwAttribsIn = SFGAO_HASPROPSHEET; break; default: ASSERT(FALSE); return FALSE; } return _AttributesFromSel(dwAttribsIn) & dwAttribsIn; } // return copy of pidl of folder we're viewing LPITEMIDLIST CDefView::_GetViewPidl() { LPITEMIDLIST pidl; if (SHGetIDListFromUnk(_pshf, &pidl) != S_OK) // S_FALSE is success by empty { if (SUCCEEDED(CallCB(SFVM_THISIDLIST, 0, (LPARAM)&pidl))) { ASSERT(pidl); } else if (_SetupNotifyData() && _pidlMonitor) { pidl = ILClone(_pidlMonitor); } } return pidl; } inline BOOL CDefView::_ItemsDeferred() { return _hdsaSelect != NULL; } BOOL CDefView::_IsListviewVisible() { return _fListViewShown; } inline BOOL CDefView::_IsOwnerData() { return _fs.fFlags & FWF_OWNERDATA; } inline BOOL CDefView::_IsCommonDialog() { return NULL != _pcdb; } BOOL CDefView::_IsDesktop() { return _fs.fFlags & FWF_DESKTOP; } BOOL CDefView::_IsViewDesktop() { BOOL bDesktop = FALSE; LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { bDesktop = ILIsEmpty(pidl); ILFree(pidl); } return bDesktop; } // access to the current views name ala IShellFolder::GetDisplayNameOf() HRESULT CDefView::_GetNameAndFlags(UINT gdnFlags, LPTSTR pszPath, UINT cch, DWORD *pdwFlags) { *pszPath = 0; HRESULT hr; LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { hr = SHGetNameAndFlags(pidl, gdnFlags, pszPath, cch, pdwFlags); ILFree(pidl); } else hr = E_OUTOFMEMORY; return hr; } // returns TRUE if the current view is a file system folder, returns the path BOOL CDefView::_GetPath(LPTSTR pszPath) { *pszPath = 0; LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { SHGetPathFromIDList(pidl, pszPath); ILFree(pidl); } return *pszPath != 0; } EXTERN_C TCHAR const c_szHtmlWindowsHlp[] = TEXT("windows.chm"); // web view background colors, click mode, etc have changed // void CDefView::_UpdateListviewColors() { // First clear out our state for (int i = 0; i < ARRAYSIZE(_crCustomColors); i++) _crCustomColors[i] = CLR_MYINVALID; // Then read the registry/desktop.ini LPCTSTR pszLegacyWatermark = NULL; SFVM_CUSTOMVIEWINFO_DATA cvi = {0}; if (SUCCEEDED(CallCB(SFVM_GETCUSTOMVIEWINFO, (WPARAM)0, (LPARAM)&cvi))) { if (!_IsCommonDialog() && !_IsDesktop()) { // Set up the listview image, if any if (*cvi.szIconAreaImage) { pszLegacyWatermark = cvi.szIconAreaImage; } // change the differing stuff // if (!_fClassic) { for (i = 0; i < ARRAYSIZE(_crCustomColors); i++) { COLORREF cr = cvi.crCustomColors[i]; if (ISVALIDCOLOR(cr)) { _crCustomColors[i] = PALETTERGB(0, 0, 0) | cr; } } // if there was an image specified but no custom text background, // set to CLR_NONE so the listview text is transparent // get combined view custom colors if (!ISVALIDCOLOR(_crCustomColors[CRID_CUSTOMTEXTBACKGROUND]) && cvi.szIconAreaImage[0]) { _crCustomColors[CRID_CUSTOMTEXTBACKGROUND] = CLR_NONE; } } } } _SetLegacyWatermark(pszLegacyWatermark); _SetFolderColors(); _UpdateSelectionMode(); } BOOL CDefView::HasCurrentViewWindowFocus() { BOOL fRet = false; HWND hwndCurrentFocus = GetFocus(); if (hwndCurrentFocus) { fRet = (SHIsChildOrSelf(_hwndListview, hwndCurrentFocus) == S_OK); } return fRet; } HWND CDefView::ViewWindowSetFocus() { SetFocus(_hwndListview); if (!_IsDesktop()) { _cFrame._uState = SVUIA_ACTIVATE_FOCUS; } return _hwndListview; } HRESULT CDefView::_GetSFVMViewState(UINT uViewMode, SFVM_VIEW_DATA* pvi) { HRESULT hr = CallCB(SFVM_GETVIEWDATA, (WPARAM)uViewMode, (LPARAM)pvi); if (FAILED(hr)) { pvi->dwOptions = SFVMQVI_NORMAL; } return hr; } HRESULT CDefView::_GetSFVMViewInfoTemplate(UINT uViewMode, SFVM_WEBVIEW_TEMPLATE_DATA* pvit) { return CallCB(SFVM_GETWEBVIEW_TEMPLATE, (WPARAM)uViewMode, (LPARAM)pvit); } HRESULT CDefView::_GetWebViewMoniker(LPWSTR pszMoniker, DWORD cchMoniker) { SFVM_WEBVIEW_TEMPLATE_DATA vit; if (SUCCEEDED(_GetSFVMViewInfoTemplate(_fs.ViewMode, &vit))) { StrCpyN(pszMoniker, vit.szWebView, cchMoniker); } else { pszMoniker[0] = L'\0'; } return *pszMoniker ? S_OK : E_FAIL; } // Show or hide Web View content // // This does not affect the View Mode of the listview (it does tweak desktop listview for _fCombinedView stuff) // // fShow==TRUE -> hr is success/fail of showing web view // fShow==FALSE -> hr is E_FAIL (nobody looks at return code of turning web view off) // HRESULT CDefView::_SwitchToWebView(BOOL fShow) { HRESULT hr = E_FAIL; // Cache the focus/select state across this transition BOOL bSetFocusRequired = HasCurrentViewWindowFocus(); if (fShow) { // For now, the desktop is always a combined view... if (_IsDesktop()) { BOOL fCombinedViewOld = (BOOL)_fCombinedView; SHELLSTATE ss; SHGetSetSettings(&ss, SSF_HIDEICONS | SSF_DESKTOPHTML | SSF_STARTPANELON, FALSE); // Does the user want desktop in HyperText view? if (ss.fDesktopHTML) _fCombinedView = TRUE; if (ss.fHideIcons) _fs.fFlags |= FWF_NOICONS; else _fs.fFlags &= ~FWF_NOICONS; if (_fCombinedView && !fCombinedViewOld) { EnableCombinedView(this, TRUE); ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_REGIONAL, LVS_EX_REGIONAL); _SetFolderColors(); } } WCHAR wszMoniker[MAX_PATH]; hr = _GetWebViewMoniker(wszMoniker, ARRAYSIZE(wszMoniker)); if (SUCCEEDED(hr)) { if (_IsDesktop()) { IActiveDesktopP *piadp; if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ActiveDesktop, NULL, IID_PPV_ARG(IActiveDesktopP, &piadp)))) { piadp->EnsureUpdateHTML(); piadp->Release(); } hr = _cFrame.ShowWebView(wszMoniker); } else if (SHRestricted(REST_REVERTWEBVIEWSECURITY)) { hr = _cFrame.ShowWebView(wszMoniker); } else if (!_fUserRejectedWebViewTemplate) { WCHAR szTemplate[MAX_PATH]; DWORD cchTemplate = ARRAYSIZE(szTemplate); if (PathIsURL(wszMoniker)) { hr = PathCreateFromUrl(wszMoniker, szTemplate, &cchTemplate, 0); } else { StringCchCopy(szTemplate, ARRAYSIZE(szTemplate), wszMoniker); } if (SUCCEEDED(hr)) { DWORD dwFlags = SHRVT_VALIDATE | SHRVT_ALLOW_INTRANET; if (SHRestricted(REST_ALLOWUNHASHEDWEBVIEW)) { dwFlags |= SHRVT_PROMPTUSER | SHRVT_REGISTERIFPROMPTOK; } hr = SHRegisterValidateTemplate(szTemplate, dwFlags); if (SUCCEEDED(hr)) { hr = _cFrame.ShowWebView(wszMoniker); } else { _fUserRejectedWebViewTemplate = TRUE; } } } } if (FAILED(hr)) { fShow = FALSE; } else { RECT rcClient; // Make sure the new view is the correct size GetClientRect(_hwndView, &rcClient); _cFrame.SetRect(&rcClient); ShowHideListView(); } } if (!fShow) { _cFrame.HideWebView(); // If we were combined, then get the listview out of region mode and // reset the color scheme. Also, turn off the combined bit. if (_fCombinedView) { _fCombinedView = FALSE; ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_REGIONAL, 0); EnableCombinedView(this, FALSE); _SetFolderColors(); } } // restore focus/select state -- if we switched to web view it will put much of // this into a "pending" state until the listview is re-shown inside the web content // if (bSetFocusRequired) { CallCB(SFVM_SETFOCUS, 0, 0); ViewWindowSetFocus(); } CheckToolbar(); _EnableDisableTBButtons(); // make sure that the listview settings get refreshed anyway (back image) _UpdateListviewColors(); return hr; } void CDefView::_RemoveThumbviewTasks() { if (_pScheduler) { _pScheduler->RemoveTasks(TOID_ExtractImageTask, ITSAT_DEFAULT_LPARAM, FALSE); _pScheduler->RemoveTasks(TOID_CheckCacheTask, ITSAT_DEFAULT_LPARAM, FALSE); _pScheduler->RemoveTasks(TOID_ReadAheadHandler, ITSAT_DEFAULT_LPARAM, FALSE); _fReadAhead = FALSE; } } // // This function checkes to see if the list view needs to be shown; then shows it. // If it needs to be hidden, hides it! You must call this function every time // you change a bit of state that could change the show/hide state of listview. // // Let me repeat that: call this function EVERY TIME you change state that // affects our show/hide. // HRESULT CDefView::ShowHideListView() { // NOTE: this is where most of the flicker bugs come from -- showing the // listview too early. This is touchy code, so be careful when you change it. // And plese document all changes for future generations. Thanks. // // Standard "is listview shown" check // // If our view hasn't been UIActivate()d yet, then we are waiting until // the IShellBrowser selects us as the active view. // // App compat for above UIActivate() change: // Adaptec Easy CD Creator never calls IShellView::UIActivate. // They got away with it because UIActivate didn't used to do much, // but now we use UIActivate to decide when to show our icons. They forget // to call it and the icons never show up. // So if we are in Win95 Defview compatibility mode, then // go ahead and show the icons now. The app gets flicker, but at least // the icons show up at all. // // Don't show the listview if we're told to not show it, or we see an error during enum. // // If we're enumerating in the background, don't show // // Potential problem: We used to defer SelectPendingSelectedItems until: // "_fListViewShown && (_cFrame._dwConnectionCookie /*&& !_cFrame._fReadyStateInteractiveProcessed*/)" // Selecting before readystatedone may pose a problem, but I don't see how it could // be a problem unless showing the view early is a problem as well, which this code didn't check. // if ((!_cFrame.IsWebView() || _fGetWindowLV || _fCombinedView) // we think icons should be visible && (_uState != SVUIA_DEACTIVATE || (SHGetAppCompatFlags(ACF_WIN95DEFVIEW) & ACF_WIN95DEFVIEW)) // async defview means we don't show before we transition out of DEACTIVE && !(BOOLIFY(_fs.fFlags & FWF_NOICONS)) // check if we've been told to not show icons && !_fEnumFailed // failed enumeration wants _hwndView to show through, not _hwndListview && !(_crefSearchWindow && _hwndStatic) // keep the listview hidden while we show the "searching" window ) { // Make sure we do each transition only once - we do more than just show the window if (!_fListViewShown) { _fListViewShown = TRUE; // Bring this to the top while showing it to avoid a second paint when // _hwndStatic is destroyed (listview has optimizations when hidden, // and it will repaint when once shown even if though it may be obscured) // SetWindowPos(_hwndListview, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW); _OnMoveWindowToTop(_hwndListview); // Remove _hwndStatic after listview is moved to top to avoid a re-paint if (_hwndStatic) { DestroyWindow(_hwndStatic); _hwndStatic = NULL; } // if we need to select items, do it now that the window is shown SelectPendingSelectedItems(); } } else { if (_fListViewShown) { _fListViewShown = FALSE; ShowWindow(_hwndListview, SW_HIDE); } // If FWF_NOICONS is set and the enumertion went to the background thread we need // to make sure that we turn of the searchui. if (BOOLIFY(_fs.fFlags & FWF_NOICONS) && _hwndStatic && 0 == _crefSearchWindow) { DestroyWindow(_hwndStatic); _hwndStatic = NULL; } } return S_OK; } IShellItemArray* CDefView::_GetFolderAsShellItemArray() { if (!_pFolderShellItemArray && _pshfParent && _pidlRelative) { SHCreateShellItemArray(NULL, _pshfParent, 1, (LPCITEMIDLIST *)&_pidlRelative, &_pFolderShellItemArray); } return _pFolderShellItemArray; } // if the attributes dwAttribMask for pdo exactly match dwAttribValue, this item should be enabled HRESULT CDefView::_CheckAttribs(IShellItemArray *psiItemArray, DWORD dwAttribMask, DWORD dwAttribValue, UISTATE* puisState) { DWORD dwAttrib = 0; HRESULT hr; if (NULL == psiItemArray) { psiItemArray = _GetFolderAsShellItemArray(); } if (psiItemArray) { hr = psiItemArray->GetAttributes(SIATTRIBFLAGS_APPCOMPAT, dwAttribMask, &dwAttrib); if (FAILED(hr)) dwAttrib = 0; } else hr = S_OK; *puisState = (dwAttribValue == dwAttrib) ? UIS_ENABLED : UIS_HIDDEN; return hr; } HRESULT CDefView::_CanWrite(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_CheckAttribs(psiItemArray, SFGAO_READONLY|SFGAO_STORAGE, SFGAO_STORAGE, puisState); } HRESULT CDefView::_CanRename(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_CheckAttribs(psiItemArray, SFGAO_CANRENAME, SFGAO_CANRENAME, puisState); } HRESULT CDefView::_CanMove(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_CheckAttribs(psiItemArray, SFGAO_CANMOVE, SFGAO_CANMOVE, puisState); } HRESULT CDefView::_CanCopy(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_CheckAttribs(psiItemArray,SFGAO_CANCOPY, SFGAO_CANCOPY, puisState); } HRESULT CDefView::_CanPublish(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_HIDDEN; if ((pThis->_wvLayout.dwLayout & SFVMWVL_NOPUBLISH) || SHRestricted(REST_NOPUBLISHWIZARD)) { // bail out early with UIS_HIDDEN, we dont show the verb return S_OK; } // Iterate first 10 items because that is what old code did before // switching to IShellItemArray. Since the attribs that are // being requested for are already being cached in the ShellItemArray // may as well always ask for all. if (psiItemArray) { IEnumShellItems *pEnumShellItems; if (SUCCEEDED(psiItemArray->EnumItems(&pEnumShellItems))) { IShellItem *pShellItem; DWORD dwIterationCount = 0; BOOL fHide = FALSE, fHasStreams = FALSE, fHasStorages = FALSE; while (!fHide && (dwIterationCount < 10) && (S_OK == pEnumShellItems->Next(1, &pShellItem, NULL))) { SFGAOF dwAttribs = SFGAO_STORAGE | SFGAO_STREAM; HRESULT hrAttribs = pShellItem->GetAttributes(dwAttribs, &dwAttribs); pShellItem->Release(); pShellItem = NULL; // null to catch if we use it again. if (SUCCEEDED(hrAttribs)) { if (!(dwAttribs & (SFGAO_STORAGE | SFGAO_STREAM))) { // if this item doesn't have either storage or stream, hide the task. fHide = TRUE; } else if (dwAttribs & SFGAO_STREAM) { // if we have a folder and files, hide the task. fHide = fHasStorages; fHasStreams = TRUE; } else if (dwAttribs & SFGAO_STORAGE) { // if we have multiple folders or a folder and files, hide the task. fHide = fHasStorages || fHasStreams; fHasStorages = TRUE; } } ++dwIterationCount; } if (!fHide) *puisState = UIS_ENABLED; pEnumShellItems->Release(); } } else { // if nothing is selected, enable the task if the current folder is a storage. LPITEMIDLIST pidl = pThis->_GetViewPidl(); if (pidl) { if (SHGetAttributes(NULL, pidl, SFGAO_STORAGE)) { *puisState = UIS_ENABLED; } ILFree(pidl); } } return S_OK; } // Note - _DoesStaticMenuHaveVerb only checks the first pidl in the data object for now // So only use it for single-selections // -DSheldon BOOL CDefView::_DoesStaticMenuHaveVerb(IShellItemArray *psiItemArray, LPCWSTR pszVerb) { BOOL fHasVerb = FALSE; IShellItem *pshItem; // get first shellItem in the array. if (SUCCEEDED(psiItemArray->GetItemAt(0,&pshItem))) { IQueryAssociations* pqa; if (SUCCEEDED(pshItem->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARG(IQueryAssociations, &pqa)))) { DWORD cch = 0; fHasVerb = SUCCEEDED(pqa->GetString(0, ASSOCSTR_COMMAND, pszVerb, NULL, &cch)); pqa->Release(); } pshItem->Release(); } return fHasVerb; } HRESULT CDefView::_GetFullPathNameAt(IShellItemArray *psiItemArray,DWORD dwIndex,LPOLESTR *ppszPath) { HRESULT hr = E_FAIL; IShellItem *pShellItem; if (NULL == psiItemArray || NULL == ppszPath) { ASSERT(psiItemArray); ASSERT(ppszPath); return E_INVALIDARG; } // get the path of the first item in the ShellArray. hr = psiItemArray->GetItemAt(dwIndex,&pShellItem); if (SUCCEEDED(hr)) { hr = pShellItem->GetDisplayName(SIGDN_FILESYSPATH,ppszPath); pShellItem->Release(); } return hr; } HRESULT CDefView::_CanShare(IUnknown* pv,IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { HRESULT hr = E_FAIL; CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_DISABLED; if (!psiItemArray) { psiItemArray = pThis->_GetFolderAsShellItemArray(); } if (psiItemArray) { #ifdef DEBUG // Sanity check. DWORD dwNumItems; ASSERT(S_OK == psiItemArray->GetCount(&dwNumItems)); ASSERT(1 == dwNumItems); #endif IShellItem *psi; hr = psiItemArray->GetItemAt(0, &psi); if (SUCCEEDED(hr)) { // Retrieve pidl. LPITEMIDLIST pidl; hr = SHGetIDListFromUnk(psi, &pidl); if (SUCCEEDED(hr)) { // Retrieve path and attributes. WCHAR szPath[MAX_PATH]; DWORD dwAttributes = SFGAO_LINK; hr = SHGetNameAndFlags(pidl, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), &dwAttributes); if (SUCCEEDED(hr) && !(dwAttributes & SFGAO_LINK) && !PathIsRemote(szPath)) { if (!g_hmodNTSHRUI) { g_hmodNTSHRUI = LoadLibrary(L"ntshrui.dll"); } if (g_hmodNTSHRUI) { PFNCANSHAREFOLDERW pfnCanShareFolder = (PFNCANSHAREFOLDERW)GetProcAddress(g_hmodNTSHRUI, "CanShareFolderW"); if (pfnCanShareFolder) { *puisState = (S_OK == pfnCanShareFolder(szPath)) ? UIS_ENABLED : UIS_DISABLED; } } } ILFree(pidl); } psi->Release(); } } return hr; } HRESULT CDefView::_CanEmail(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { DWORD dwAttributes = 0; // Prevent people from attempting to e-mail non-filesystem objects. // Attempting to attach such objects to an e-mail message fails. // An example of this type of failure is attempting to e-mail // connectoids in the "Network Connections" folder. if (psiItemArray) { psiItemArray->GetAttributes(SIATTRIBFLAGS_APPCOMPAT, SFGAO_FILESYSTEM, &dwAttributes); } *puisState = dwAttributes & SFGAO_FILESYSTEM ? UIS_ENABLED : UIS_DISABLED; return S_OK; } HRESULT CDefView::_CanPrint(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { if (!(((CDefView*)(void*)pv)->_wvLayout.dwLayout & SFVMWVL_NOPRINT)) return _HasPrintVerb(pv, psiItemArray, fOkToBeSlow, puisState); *puisState = UIS_HIDDEN; return S_OK; } HRESULT CDefView::_HasPrintVerb(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; if (!psiItemArray) { psiItemArray = pThis->_GetFolderAsShellItemArray(); } BOOL fHasPrint = _DoesStaticMenuHaveVerb(psiItemArray,c_szPrintW); *puisState = (fHasPrint) ? UIS_ENABLED : UIS_HIDDEN; return S_OK; } HRESULT CDefView::_CanDelete(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_CheckAttribs(psiItemArray, SFGAO_CANDELETE, SFGAO_CANDELETE, puisState); } // determines if defview is hosted over the system drive root or not BOOL CDefView::_IsSystemDrive(void) { TCHAR szPath[MAX_PATH]; TCHAR szSystemDrive[4]; BOOL bResult = FALSE; if (SUCCEEDED(_GetPath(szPath))) { SHExpandEnvironmentStrings (TEXT("%SystemDrive%\\"), szSystemDrive, ARRAYSIZE(szSystemDrive)); if (!lstrcmpi(szPath, szSystemDrive)) { bResult = TRUE; } } return bResult; } HRESULT CDefView::_CanViewDrives(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_DISABLED; if (pThis->_wvContent.dwFlags & SFVMWVF_BARRICADE) { if (pThis->_fBarrierDisplayed) { if (pThis->_IsSystemDrive()) { *puisState = UIS_ENABLED; } } } return S_OK; } HRESULT CDefView::_CanHideDrives(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_DISABLED; if (pThis->_wvContent.dwFlags & SFVMWVF_BARRICADE) { if (!pThis->_fBarrierDisplayed) { if (pThis->_IsSystemDrive()) { *puisState = UIS_ENABLED; } } } return S_OK; } HRESULT CDefView::_CanViewFolder(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_DISABLED; if (pThis->_wvContent.dwFlags & SFVMWVF_BARRICADE) { if (pThis->_fBarrierDisplayed) { if (!pThis->_IsSystemDrive()) { *puisState = UIS_ENABLED; } } } return S_OK; } HRESULT CDefView::_CanHideFolder(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { CDefView* pThis = (CDefView*)(void*)pv; *puisState = UIS_DISABLED; if (pThis->_wvContent.dwFlags & SFVMWVF_BARRICADE) { if (!pThis->_fBarrierDisplayed) { if (!pThis->_IsSystemDrive()) { *puisState = UIS_ENABLED; } } } return S_OK; } HRESULT CDefView::_HasPreviousVersions(IUnknown* pv, IShellItemArray *psiItemArray, BOOL fOkToBeSlow, UISTATE* puisState) { HRESULT hr = S_OK; *puisState = UIS_HIDDEN; if (!psiItemArray) { CDefView* pThis = (CDefView*)(void*)pv; psiItemArray = pThis->_GetFolderAsShellItemArray(); } if (NULL != psiItemArray) { #ifdef DEBUG // Sanity check. DWORD dwNumItems; ASSERT(S_OK == psiItemArray->GetCount(&dwNumItems)); ASSERT(1 == dwNumItems); #endif BOOL bHasShadowCopy = FALSE; // This returns E_PENDING if the answer is unknown // and fOkToBeSlow is FALSE hr = HavePreviousVersionsAt(psiItemArray, 0, fOkToBeSlow, &bHasShadowCopy); if (S_OK == hr && bHasShadowCopy) { *puisState = UIS_ENABLED; } } return hr; } HRESULT CDefView::_DoVerb(IShellItemArray *psiItemArray, LPCSTR pszVerbA) { HRESULT hr = E_FAIL; if (NULL== psiItemArray) { IContextMenu* pcm; hr = GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARG(IContextMenu, &pcm)); if (SUCCEEDED(hr)) { hr = _InvokeContextMenuVerb(pcm, pszVerbA, 0, CMIC_MASK_FLAG_LOG_USAGE); pcm->Release(); } } else { ASSERT(psiItemArray == _pSelectionShellItemArray); hr = _InvokeContextMenuVerbOnSelection(pszVerbA, 0, CMIC_MASK_FLAG_LOG_USAGE); } return hr; } HRESULT CDefView::_DoDropOnClsid(REFCLSID clsidDrop, IDataObject* pdo) { HRESULT hr = E_FAIL; IDataObject *pdoFree = NULL; if (!pdo) { IShellItemArray *pFolder = _GetFolderAsShellItemArray(); if (pFolder) { hr = pFolder->BindToHandler(NULL, BHID_DataObject, IID_PPV_ARG(IDataObject, &pdoFree)); if (SUCCEEDED(hr)) { pdo = pdoFree; } else { pdoFree = NULL; } } } if (pdo) { hr = SHSimulateDropOnClsid(clsidDrop, SAFECAST(this, IOleCommandTarget *), pdo); } ATOMICRELEASE(pdoFree); // may be NULL return hr; } HRESULT CDefView::_OnNewFolder(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_DoVerb(psiItemArray,c_szNewFolderA); } HRESULT CDefView::_OnRename(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->DoRename(); } HRESULT CDefView::_OnMove(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_DoMoveOrCopyTo(CLSID_MoveToMenu, psiItemArray); } HRESULT CDefView::_OnCopy(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_DoMoveOrCopyTo(CLSID_CopyToMenu, psiItemArray); } HRESULT CDefView::_OnPublish(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { HRESULT hr = S_OK; IDataObject *pdo = NULL; CDefView* pThis = (CDefView*)(void*)pv; if (psiItemArray) { hr = psiItemArray->BindToHandler(NULL, BHID_DataObject, IID_PPV_ARG(IDataObject, &pdo)); } if (SUCCEEDED(hr)) { hr = pThis->_DoDropOnClsid(CLSID_PublishDropTarget, pdo); } ATOMICRELEASE(pdo); // may be NULL return hr; } HRESULT CDefView::_OnShare(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { HRESULT hr = E_FAIL; CDefView* pThis = (CDefView*)(void*)pv; if (!psiItemArray) { psiItemArray = pThis->_GetFolderAsShellItemArray(); } if (NULL != psiItemArray) { LPOLESTR pszPath; hr = pThis->_GetFullPathNameAt(psiItemArray, 0, &pszPath); if (SUCCEEDED(hr)) { if (!g_hmodNTSHRUI) { g_hmodNTSHRUI = LoadLibrary(L"ntshrui.dll"); } if (g_hmodNTSHRUI) { PFNSHOWSHAREFOLDERUIW pfnShowShareFolderUI = (PFNSHOWSHAREFOLDERUIW) GetProcAddress(g_hmodNTSHRUI, "ShowShareFolderUIW"); if (pfnShowShareFolderUI) { pfnShowShareFolderUI(pThis->_hwndMain, pszPath); } } CoTaskMemFree(pszPath); } } return hr; } HRESULT CDefView::_OnEmail(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { HRESULT hr = E_FAIL; IDataObject *pdo = NULL; if (psiItemArray) { hr = psiItemArray->BindToHandler(NULL,BHID_DataObject,IID_PPV_ARG(IDataObject,&pdo)); } if (SUCCEEDED(hr)) { CDefView* pThis = (CDefView*)(void*)pv; BOOL bNoFilesFoundToEmail = TRUE; INamespaceWalk *pnsw; hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(INamespaceWalk, &pnsw)); if (SUCCEEDED(hr)) { // Note: // To mirror the behaviour of the selection context menu's "Send To-> // Mail Recipient", don't traverse links, mail the link file itself. hr = pnsw->Walk(pdo, NSWF_DONT_TRAVERSE_LINKS, 0, NULL); if (SUCCEEDED(hr)) { UINT cItems; LPITEMIDLIST *ppidls; hr = pnsw->GetIDArrayResult(&cItems, &ppidls); if (SUCCEEDED(hr)) { if (cItems) { IDataObject* pdoWalk; hr = SHCreateFileDataObject(&c_idlDesktop, cItems, (LPCITEMIDLIST *)ppidls, NULL, (IDataObject **)&pdoWalk); if (SUCCEEDED(hr)) { hr = pThis->_DoDropOnClsid(CLSID_MailRecipient, pdoWalk); bNoFilesFoundToEmail = FALSE; pdoWalk->Release(); } } FreeIDListArray(ppidls, cItems); } } pnsw->Release(); } if (bNoFilesFoundToEmail) { // No items found to e-mail (selected folders contained no files). ShellMessageBox( HINST_THISDLL, pThis->_hwndMain, MAKEINTRESOURCE(IDS_NOFILESTOEMAIL), NULL, MB_OK | MB_ICONERROR); } pdo->Release(); } return hr; } HRESULT CDefView::_OnPrint(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_DoVerb(psiItemArray,c_szPrintA); } HRESULT CDefView::_OnDelete(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->_DoVerb(psiItemArray,c_szDeleteA); } HRESULT CDefView::RemoveBarricade (void) { LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { TCHAR szValueName[MAX_PATH]; if (GetBarricadeValueNameFromPidl(pidl, szValueName, ARRAYSIZE(szValueName))) { SetBarricadeStatus (szValueName, VARIANT_FALSE); } ILFree(pidl); } // Restore "View" menu commands which were stripped. RecreateMenus(); // Enable "View Menu" button on the toolbar. EnableToolbarButton(SFVIDM_VIEW_VIEWMENU, TRUE); _fBarrierDisplayed = FALSE; return _pDUIView->EnableBarrier(FALSE); } HRESULT CDefView::_OnView(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return pThis->RemoveBarricade(); } HRESULT CDefView::_OnHide(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; LPITEMIDLIST pidl = pThis->_GetViewPidl(); if (pidl) { TCHAR szValueName[MAX_PATH]; if (GetBarricadeValueNameFromPidl(pidl, szValueName, ARRAYSIZE(szValueName))) { SetBarricadeStatus(szValueName, VARIANT_TRUE); } ILFree(pidl); } // Disable "View Menu" button on the toolbar. pThis->EnableToolbarButton(SFVIDM_VIEW_VIEWMENU, FALSE); pThis->_fBarrierDisplayed = TRUE; return pThis->_pDUIView->EnableBarrier(TRUE); } HRESULT CDefView::_OnAddRemovePrograms(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { HCURSOR hcOld = SetCursor(LoadCursor(NULL, IDC_WAIT)); SHRunControlPanel(L"appwiz.cpl", NULL); SetCursor(hcOld); return S_OK; } HRESULT CDefView::_OnSearchFiles(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; return IUnknown_ShowBrowserBar (pThis->_psb, CLSID_FileSearchBand, TRUE); } HRESULT CDefView::_OnPreviousVersions(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc) { CDefView* pThis = (CDefView*)(void*)pv; if (!psiItemArray) { psiItemArray = pThis->_GetFolderAsShellItemArray(); } ShowPreviousVersionsAt(psiItemArray, 0, pThis->_hwndMain); return S_OK; } const WVTASKITEM c_DefviewBlockadeTaskHeader = WVTI_HEADER_ENTRY(L"shell32.dll", IDS_HEADER_DEFVIEW_BLOCKADE, IDS_HEADER_DEFVIEW_BLOCKADE, IDS_HEADER_DEFVIEW_BLOCKADE, IDS_HEADER_DEFVIEW_BLOCKADE, IDS_HEADER_DEFVIEW_BLOCKADE_TT); const WVTASKITEM c_DefviewBlockadeTasks[] = { WVTI_ENTRY_ALL(UICID_ViewContents, L"shell32.dll", IDS_TASK_DEFVIEW_VIEWCONTENTS_DRIVE, IDS_TASK_DEFVIEW_VIEWCONTENTS_DRIVE_TT, IDI_STSPROGS, CDefView::_CanViewDrives, CDefView::_OnView), WVTI_ENTRY_ALL(UICID_HideContents, L"shell32.dll", IDS_TASK_DEFVIEW_HIDECONTENTS_DRIVE, IDS_TASK_DEFVIEW_HIDECONTENTS_DRIVE_TT, IDI_STSPROGS, CDefView::_CanHideDrives, CDefView::_OnHide), WVTI_ENTRY_ALL(UICID_ViewContents, L"shell32.dll", IDS_TASK_DEFVIEW_VIEWCONTENTS_FOLDER, IDS_TASK_DEFVIEW_VIEWCONTENTS_FOLDER_TT,IDI_STSPROGS, CDefView::_CanViewFolder, CDefView::_OnView), WVTI_ENTRY_ALL(UICID_HideContents, L"shell32.dll", IDS_TASK_DEFVIEW_HIDECONTENTS_FOLDER, IDS_TASK_DEFVIEW_HIDECONTENTS_FOLDER_TT,IDI_STSPROGS, CDefView::_CanHideFolder, CDefView::_OnHide), WVTI_ENTRY_ALL(UICID_AddRemovePrograms, L"shell32.dll", IDS_TASK_ARP, IDS_TASK_ARP_TT, IDI_CPCAT_ARP, NULL, CDefView::_OnAddRemovePrograms), WVTI_ENTRY_ALL(UICID_SearchFiles, L"shell32.dll", IDS_TASK_SEARCHFORFILES, IDS_TASK_SEARCHFORFILES_TT, IDI_STFIND, NULL, CDefView::_OnSearchFiles), }; const WVTASKITEM c_DefviewFileFolderTasksHeaders = WVTI_HEADER(L"shell32.dll", IDS_HEADER_FILEFOLDER, IDS_HEADER_FILEFOLDER_TT); const WVTASKITEM c_DefviewItemFolderTasksHeaders = WVTI_HEADER(L"shell32.dll", IDS_HEADER_ITEMFOLDER, IDS_HEADER_ITEMFOLDER_TT); const WVTASKITEM c_DefviewFileFolderTasks[] = { WVTI_ENTRY_NOSELECTION(UICID_NewFolder, L"shell32.dll", IDS_TASK_CURFOLDER_NEWFOLDER, IDS_TASK_CURFOLDER_NEWFOLDER_TT, IDI_TASK_NEWFOLDER, CDefView::_CanWrite, CDefView::_OnNewFolder), WVTI_ENTRY_TITLE(UICID_Rename, L"shell32.dll", IDS_TASK_RENAME_FILE, IDS_TASK_RENAME_FOLDER, 0, IDS_TASK_RENAME_FILE_TT, IDI_TASK_RENAME, CDefView::_CanRename, CDefView::_OnRename), WVTI_ENTRY_TITLE(UICID_Move, L"shell32.dll", IDS_TASK_MOVE_FILE, IDS_TASK_MOVE_FOLDER, IDS_TASK_MOVE_ITEMS, IDS_TASK_MOVE_TT, IDI_TASK_MOVE, CDefView::_CanMove, CDefView::_OnMove), WVTI_ENTRY_ALL_TITLE(UICID_Copy, L"shell32.dll", 0, IDS_TASK_COPY_FILE, IDS_TASK_COPY_FOLDER, IDS_TASK_COPY_ITEMS, IDS_TASK_COPY_TT, IDI_TASK_COPY, CDefView::_CanCopy, CDefView::_OnCopy), WVTI_ENTRY_ALL_TITLE(UICID_Publish, L"shell32.dll", IDS_TASK_PUBLISH_FOLDER, IDS_TASK_PUBLISH_FILE,IDS_TASK_PUBLISH_FOLDER, IDS_TASK_PUBLISH_ITEMS, IDS_TASK_PUBLISH_TT, IDI_TASK_PUBLISH, CDefView::_CanPublish, CDefView::_OnPublish), WVTI_ENTRY_ALL_TITLE(UICID_Share, L"shell32.dll", IDS_TASK_SHARE_FOLDER, 0, IDS_TASK_SHARE_FOLDER, 0, IDS_TASK_SHARE_TT, IDI_TASK_SHARE, CDefView::_CanShare, CDefView::_OnShare), WVTI_ENTRY_TITLE(UICID_Email, L"shell32.dll", IDS_TASK_EMAIL_FILE, IDS_TASK_EMAIL_FOLDER, IDS_TASK_EMAIL_ITEMS, IDS_TASK_EMAIL_TT, IDI_TASK_EMAILFILE, CDefView::_CanEmail, CDefView::_OnEmail), WVTI_ENTRY_TITLE(UICID_Print, L"shell32.dll", IDS_TASK_PRINT_FILE, 0, 0, IDS_TASK_PRINT_TT, IDI_TASK_PRINT, CDefView::_CanPrint, CDefView::_OnPrint), WVTI_ENTRY_TITLE(UICID_Delete, L"shell32.dll", IDS_TASK_DELETE_FILE, IDS_TASK_DELETE_FOLDER, IDS_TASK_DELETE_ITEMS, IDS_TASK_DELETE_TT, IDI_TASK_DELETE, CDefView::_CanDelete, CDefView::_OnDelete), WVTI_ENTRY_ALL_TITLE(UICID_PreviousVersions, L"shell32.dll", IDS_TASK_SHADOW, IDS_TASK_SHADOW, IDS_TASK_SHADOW, 0, IDS_TASK_SHADOW_TT, IDI_TASK_SHADOW, CDefView::_HasPreviousVersions, CDefView::_OnPreviousVersions), }; const size_t c_cDefviewFileFolderTasks = ARRAYSIZE(c_DefviewFileFolderTasks); const WVTASKITEM c_DefviewItemFolderTasks[] = { WVTI_ENTRY_NOSELECTION(UICID_NewFolder, L"shell32.dll", IDS_TASK_CURFOLDER_NEWFOLDER, IDS_TASK_CURFOLDER_NEWFOLDER_TT, IDI_TASK_NEWFOLDER, CDefView::_CanWrite, CDefView::_OnNewFolder), WVTI_ENTRY_TITLE(UICID_Rename, L"shell32.dll", IDS_TASK_RENAME_ITEM, IDS_TASK_RENAME_FOLDER, 0, IDS_TASK_RENAME_ITEM_TT, IDI_TASK_RENAME, CDefView::_CanRename, CDefView::_OnRename), WVTI_ENTRY_TITLE(UICID_Move, L"shell32.dll", IDS_TASK_MOVE_ITEM, IDS_TASK_MOVE_FOLDER, IDS_TASK_MOVE_ITEMS, IDS_TASK_MOVE_TT, IDI_TASK_MOVE, CDefView::_CanMove, CDefView::_OnMove), WVTI_ENTRY_ALL_TITLE(UICID_Copy, L"shell32.dll", 0, IDS_TASK_COPY_ITEM, IDS_TASK_COPY_FOLDER, IDS_TASK_COPY_ITEMS, IDS_TASK_COPY_TT, IDI_TASK_COPY, CDefView::_CanCopy, CDefView::_OnCopy), WVTI_ENTRY_ALL_TITLE(UICID_Share, L"shell32.dll", IDS_TASK_SHARE_FOLDER, 0, IDS_TASK_SHARE_FOLDER, 0, IDS_TASK_SHARE_TT, IDI_TASK_SHARE, CDefView::_CanShare, CDefView::_OnShare), WVTI_ENTRY_TITLE(UICID_Delete, L"shell32.dll", IDS_TASK_DELETE_ITEM, IDS_TASK_DELETE_FOLDER, IDS_TASK_DELETE_ITEMS, IDS_TASK_DELETE_TT, IDI_TASK_DELETE, CDefView::_CanDelete, CDefView::_OnDelete), }; const size_t c_cDefviewItemFolderTasks = ARRAYSIZE(c_DefviewItemFolderTasks); const WVTASKITEM c_DefviewOtherPlaces = WVTI_HEADER_ENTRY(L"shell32.dll", IDS_HEADER_OTHER_PLACES, IDS_HEADER_OTHER_PLACES, IDS_HEADER_OTHER_PLACES, IDS_HEADER_OTHER_PLACES, IDS_HEADER_OTHER_PLACES_TT); const WVTASKITEM c_DefviewDetails = WVTI_HEADER_ENTRY(L"shell32.dll", IDS_HEADER_DETAILS, IDS_HEADER_DETAILS, IDS_HEADER_DETAILS, IDS_HEADER_DETAILS, IDS_HEADER_DETAILS_TT); const WVTASKITEM* CDefView::_FindTaskItem(REFGUID guidCanonicalName) { const BOOL bFileFolderTasks = _wvLayout.dwLayout & SFVMWVL_FILES; const WVTASKITEM *paTasks = bFileFolderTasks ? c_DefviewFileFolderTasks : c_DefviewItemFolderTasks; const size_t cTasks = bFileFolderTasks ? c_cDefviewFileFolderTasks : c_cDefviewItemFolderTasks; for (size_t i = 0; i < cTasks; i++) if (IsEqualGUID(*(paTasks[i].pguidCanonicalName), guidCanonicalName)) return &paTasks[i]; return NULL; } HRESULT CDefView::get_Name(REFGUID guidCanonicalName, IShellItemArray *psiItemArray, LPWSTR *ppszName) { const WVTASKITEM* pTask = _FindTaskItem(guidCanonicalName); if (pTask) return CWVTASKITEM::get_Name(pTask, psiItemArray, ppszName); return E_FAIL; } HRESULT CDefView::get_Icon(REFGUID guidCanonicalName, IShellItemArray *psiItemArray, LPWSTR *ppszIcon) { const WVTASKITEM* pTask = _FindTaskItem(guidCanonicalName); if (pTask) return CWVTASKITEM::get_Icon(pTask, psiItemArray, ppszIcon); return E_FAIL; } HRESULT CDefView::get_Tooltip(REFGUID guidCanonicalName, IShellItemArray *psiItemArray, LPWSTR *ppszInfotip) { const WVTASKITEM* pTask = _FindTaskItem(guidCanonicalName); if (pTask) return CWVTASKITEM::get_Tooltip(pTask, psiItemArray, ppszInfotip); return E_FAIL; } HRESULT CDefView::get_State(REFGUID guidCanonicalName, IShellItemArray *psiItemArray, UISTATE* puisState) { const WVTASKITEM* pTask = _FindTaskItem(guidCanonicalName); if (pTask) return CWVTASKITEM::get_State(pTask, SAFECAST(this, IShellView2*), psiItemArray, TRUE, puisState); return E_FAIL; } HRESULT CDefView::Invoke(REFGUID guidCanonicalName, IShellItemArray *psiItemArray, IBindCtx *pbc) { const WVTASKITEM* pTask = _FindTaskItem(guidCanonicalName); if (pTask) return CWVTASKITEM::Invoke(pTask, SAFECAST(this, IShellView2*), psiItemArray, pbc); return E_FAIL; } HRESULT CDefView::_GetDefaultWebviewContent(BOOL bFileFolderTasks) { if (!_wvTasks.penumSpecialTasks) { if (_wvContent.dwFlags & SFVMWVF_BARRICADE) { // defview provides a default penumSpecialTasks for barricaded folders Create_IUIElement(&c_DefviewBlockadeTaskHeader, &(_wvContent.pSpecialTaskHeader)); Create_IEnumUICommand((IUnknown*)(void*)this, c_DefviewBlockadeTasks, ARRAYSIZE(c_DefviewBlockadeTasks), &(_wvTasks.penumSpecialTasks)); } } if (!_wvTasks.penumFolderTasks) { if (_wvContent.pFolderTaskHeader) _wvContent.pFolderTaskHeader->Release(); Create_IUIElement(bFileFolderTasks ? &c_DefviewFileFolderTasksHeaders : &c_DefviewItemFolderTasksHeaders, &(_wvContent.pFolderTaskHeader)); Create_IEnumUICommand( (IUnknown*)(void*)this, bFileFolderTasks ? c_DefviewFileFolderTasks : c_DefviewItemFolderTasks, bFileFolderTasks ? c_cDefviewFileFolderTasks : c_cDefviewItemFolderTasks, &(_wvTasks.penumFolderTasks)); } if (!_wvContent.penumOtherPlaces) { LPCTSTR rgCSIDLs[] = { MAKEINTRESOURCE(CSIDL_PERSONAL), MAKEINTRESOURCE(CSIDL_COMMON_DOCUMENTS), MAKEINTRESOURCE(CSIDL_NETWORK) }; LPITEMIDLIST pidl = _GetViewPidl(); CreateIEnumIDListOnCSIDLs(pidl, rgCSIDLs, ARRAYSIZE(rgCSIDLs), &_wvContent.penumOtherPlaces); if (pidl) ILFree(pidl); } ASSERT(NULL==_pOtherPlacesHeader); Create_IUIElement(&c_DefviewOtherPlaces, &_pOtherPlacesHeader); ASSERT(NULL==_pDetailsHeader); Create_IUIElement(&c_DefviewDetails, &_pDetailsHeader); return S_OK; } void CDefView::_FreeWebViewContentData() { ATOMICRELEASE(_wvContent.pSpecialTaskHeader); ATOMICRELEASE(_wvContent.pFolderTaskHeader); ATOMICRELEASE(_wvContent.penumOtherPlaces); ATOMICRELEASE(_wvTasks.penumSpecialTasks); ATOMICRELEASE(_wvTasks.penumFolderTasks); ATOMICRELEASE(_pOtherPlacesHeader); ATOMICRELEASE(_pDetailsHeader); _fQueryWebViewData = FALSE; _wvLayout.dwLayout = -1; // an invalid value } BOOL CDefView::_QueryBarricadeState() { BOOL bResult = FALSE; LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { // // Control panel is a special case. // The barricade is used to represent 'category view' which can // be turned on/off by the user. We must always ask control panel // if it's barricade is on or off. // BOOL bIsControlPanel = FALSE; LPITEMIDLIST pidlControlPanel; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_CONTROLS, &pidlControlPanel))) { bIsControlPanel = ILIsEqual(pidl, pidlControlPanel); ILFree (pidlControlPanel); } if (bIsControlPanel) { SFVM_WEBVIEW_CONTENT_DATA wvc; if (SUCCEEDED(CallCB(SFVM_GETWEBVIEWCONTENT, 0, (LPARAM)&wvc))) { // // Control Panel doesn't provide all the standard // webview content so it's doing nothing more than setting // the dwFlags member. Assert to ensure this doesn't // change in the future without us knowing about it. // ASSERT(NULL == wvc.pIntroText); ASSERT(NULL == wvc.pSpecialTaskHeader); ASSERT(NULL == wvc.pFolderTaskHeader); ASSERT(NULL == wvc.penumOtherPlaces); bResult = (0 != (SFVMWVF_BARRICADE & wvc.dwFlags)); } } else if (_wvContent.dwFlags & SFVMWVF_BARRICADE) { if (!IsBarricadeGloballyOff()) { TCHAR szValueName[MAX_PATH]; if (GetBarricadeValueNameFromPidl(pidl, szValueName, ARRAYSIZE(szValueName))) { if (VARIANT_TRUE == GetBarricadeStatus(szValueName)) { bResult = TRUE; } } } } ILFree(pidl); } return bResult; } void CDefView::_ShowLegacyWatermark() { BOOL fShowLegacyWatermark = TRUE; LVBKIMAGE lvbki = {0}; if (_pszLegacyWatermark) { lvbki.ulFlags = LVBKIF_SOURCE_URL | LVBKIF_STYLE_TILE; lvbki.pszImage = _pszLegacyWatermark; } else { // this code path is used to clear the watermark lvbki.ulFlags = LVBKIF_TYPE_WATERMARK; // if we're turning off the legacy watermark, we may have to turn on the theme one if (_idThemeWatermark && _pDUIView) { fShowLegacyWatermark = FALSE; } } if (fShowLegacyWatermark) ListView_SetBkImage(_hwndListview, &lvbki); else _ShowThemeWatermark(); } void CDefView::_ShowThemeWatermark() { BOOL fShowLegacyWatermark = TRUE; if (_idThemeWatermark && _pDUIView) { HINSTANCE hinstTheme = _pDUIView->_GetThemeHinst(); LVBKIMAGE lvbki = {0}; lvbki.ulFlags = LVBKIF_TYPE_WATERMARK; lvbki.hbm = DUILoadBitmap(hinstTheme, _idThemeWatermark, LR_DEFAULTCOLOR); if (lvbki.hbm) { // If the window color doesn't match the background color of the watermark, // then we'll hide the watermark. HDC hDC = CreateCompatibleDC(NULL); if (hDC) { HBITMAP hOldBitmap; hOldBitmap = (HBITMAP)SelectObject (hDC, lvbki.hbm); if (GetPixel(hDC, 0, 0) != GetSysColor(COLOR_WINDOW)) { _idThemeWatermark = 0; } SelectObject (hDC, hOldBitmap); DeleteDC (hDC); } if (_idThemeWatermark && ListView_SetBkImage(_hwndListview, &lvbki)) { fShowLegacyWatermark = FALSE; } else { DeleteObject(lvbki.hbm); } } if (fShowLegacyWatermark) _idThemeWatermark = 0; // something failed, pretend we don't have one } // usually this will just hide the previous watermark if (fShowLegacyWatermark) { _ShowLegacyWatermark(); } } void CDefView::_SetThemeWatermark() { UINT idThemeWatermark = 0; if (_pDUIView) { const WVTHEME* pwvTheme = _pDUIView->GetThemeInfo(); if (pwvTheme && pwvTheme->idListviewWatermark) { HINSTANCE hinstTheme = _pDUIView->_GetThemeHinst(); if (HINST_THISDLL != hinstTheme) { // Only add the watermark if the machine is fast enough... if (SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("ListviewWatermark"), FALSE, // Don't ignore HKCU FALSE)) // Assume not fast enough { idThemeWatermark = pwvTheme->idListviewWatermark; } } } } if (idThemeWatermark != _idThemeWatermark) { _idThemeWatermark = idThemeWatermark; // Since DUI Document view isn't themed, legacy watermarks have precedence there. // Might as well have them take precedence for My Pictures too... if (!_pszLegacyWatermark) { _ShowThemeWatermark(); } } } void CDefView::_SetLegacyWatermark(LPCTSTR pszLegacyWatermark) { Str_SetPtr(&_pszLegacyWatermark, pszLegacyWatermark); _ShowLegacyWatermark(); } HRESULT CDefView::_TryShowWebView(UINT fvmNew, UINT fvmOld) { SFVM_WEBVIEW_LAYOUT_DATA sfvmwvld = {0}; HRESULT hr = E_FAIL; BOOL fShowDUI = FALSE; // The desktop IShellFolder doesn't know if it's in-frame or in the real desktop, // so only ask it for new DUIView if we're not the actual desktop. if (!_IsDesktop()) { // Supporting SFVM_GETWEBVIEWLAYOUT means the folder wants our new // DUI View and they support SFVM_GETWEBVIEWCONTENT // hr = CallCB(SFVM_GETWEBVIEWLAYOUT, (WPARAM)fvmNew, (LPARAM)&sfvmwvld); fShowDUI = SUCCEEDED(hr); } // This folder doesn't specify the new DUIView, try the old WebView stuff if (!fShowDUI) { WCHAR wszMoniker[MAX_PATH]; hr = _GetWebViewMoniker(wszMoniker, ARRAYSIZE(wszMoniker)); if (SUCCEEDED(hr)) { if(_pDUIView) //Hide it only if we are switching from DUI _TryHideWebView(); // just in case we're switching from DUI to Web View (can happen when customizing) if (wszMoniker[0]) { hr = _SwitchToWebView(TRUE); } } // Okay, we don't have Web View, use the default DUI View if (FAILED(hr)) { sfvmwvld.dwLayout = SFVMWVL_NORMAL; fShowDUI = TRUE; } } if (fShowDUI) { hr = S_OK; _cFrame.HideWebView(); // just in case we're switching from Web View to DUI View (can happen when customizing) if (sfvmwvld.dwLayout != _wvLayout.dwLayout) { if (!_fQueryWebViewData) // instead of this we could allow per-layout tasks... { CallCB(SFVM_GETWEBVIEWTHEME, 0, (LPARAM)&_wvTheme); // _FreeWebViewContentData(); if we have per-layout tasks... if (FAILED(CallCB(SFVM_GETWEBVIEWCONTENT, 0, (LPARAM)&_wvContent))) { ZeroMemory(&_wvContent, sizeof(_wvContent)); } if (0 == (SFVMWVF_ENUMTASKS & _wvContent.dwFlags)) { // // View wants standard task sections. // Non-standard task sections are enumerated in duiview. // if (FAILED(CallCB(SFVM_GETWEBVIEWTASKS, 0, (LPARAM)&_wvTasks))) { ZeroMemory(&_wvTasks, sizeof(_wvTasks)); } _GetDefaultWebviewContent(sfvmwvld.dwLayout & SFVMWVL_FILES); } _fQueryWebViewData = TRUE; } CopyMemory(&_wvLayout, &sfvmwvld, sizeof(_wvLayout)); _wvLayout.punkPreview = NULL; if (_pDUIView) { _pDUIView->EnablePreview(sfvmwvld.punkPreview); } else { _pDUIView = Create_CDUIView(this); if (_pDUIView) { _fBarrierDisplayed = _QueryBarricadeState(); if (SUCCEEDED(_pDUIView->Initialize(_fBarrierDisplayed, sfvmwvld.punkPreview))) { if (((SFVMWVF_ENUMTASKS | SFVMWVF_CONTENTSCHANGE) & _wvContent.dwFlags) && _fRcvdContentsChangeBeforeDuiViewCreated) { // // If the webview provider dynamically enumerates // tasks or wants to be refreshed when contents change, // (i.e. Control Panel), AND we received a 'contents change' // before DUI View was created, initiate a 'contents change' now. // Otherwise, such providers will not receive a 'contents change' // and thus will not display their dynamic webview content. // _OnContentsChanged(); } } else { _pDUIView->Release(); _pDUIView = NULL; } } } } else { // except potentially refresh if we need to add/remove our DUI Details minipreview if (_pDUIView && (_IsImageMode(fvmNew) != _IsImageMode(fvmOld))) _pDUIView->OnSelectionChange(_pSelectionShellItemArray); } ATOMICRELEASE(sfvmwvld.punkPreview); } _SetThemeWatermark(); return hr; } HRESULT CDefView::_TryHideWebView() { if (_pDUIView) { _pDUIView->DetachListview(); // so we detach and re-parent the listview synchronously // // Ensure DUser has shut down and handled all DUser messages // before we release our ref on CDUIView. // _pDUIView->UnInitializeDirectUI(); _pDUIView->Release(); _pDUIView = NULL; // * necessary * because this is used internally as a state (must be BEFORE WndSize() below) _wvLayout.dwLayout = -1; // an invalid value _fListViewShown = FALSE; // CDUIView::DetachListview() does a SW_HIDE on the listview WndSize(_hwndView); // resize _hwndView to account for DUI disappearing (otherwise // it will still be the smaller size expecting DUI to be drawn // next to it) ShowHideListView(); } else { _SwitchToWebView(FALSE); } return S_OK; } // we are switching the listview view mode in this function, not dorking with web view content. // HRESULT CDefView::_SwitchToViewFVM(UINT fvmNew, UINT uiType) { HRESULT hr = S_OK; UINT fvmOld = _fs.ViewMode; ASSERT(_hwndListview); HWND hwndCurrentFocus = GetFocus(); BOOL bSetFocusRequired = HasCurrentViewWindowFocus(); if (SWITCHTOVIEW_WEBVIEWONLY != uiType) { // if we haven't loaded the columns yet, do that now // Don't pre-load the columns for TileView, we are delaying the load on purpose for perf reasons. if (fvmNew == FVM_DETAILS) { AddColumns(); _SetSortFeedback(); } else if (fvmNew == FVM_THUMBSTRIP) { // Thumbstrip makes no sense in non-webview, fall back to thumbnail if (!_ShouldShowWebView()) { fvmNew = FVM_THUMBNAIL; } } // Combined view only applies to large icon view if (_fCombinedView && fvmNew != FVM_ICON) { _fCombinedView = FALSE; ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_REGIONAL, 0); _SetFolderColors(); } // First we turn OFF view specific stuff that is no longer needed switch (fvmOld) { case FVM_THUMBSTRIP: if (FVM_THUMBSTRIP != fvmNew) { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_SINGLEROW, 0); // we may have forced thumbstrip to auto-arrange, undo that if so if (!(_fs.fFlags & FWF_AUTOARRANGE)) { SHSetWindowBits(_hwndListview, GWL_STYLE, LVS_AUTOARRANGE, 0); } } // fall through case FVM_THUMBNAIL: if (!_IsImageMode(fvmNew)) { _ResetThumbview(); // Since we are switching from thumbnail view, remove any thumbnail extraction tasks _RemoveThumbviewTasks(); if (_fs.fFlags & FWF_OWNERDATA) { InvalidateRect(_hwndListview, NULL, TRUE); } else { ListView_InvalidateImageIndexes(_hwndListview); } } break; case FVM_TILE: if (!_IsTileMode(fvmNew)) { if (_pScheduler) _pScheduler->RemoveTasks(TOID_DVFileTypeProperties, ITSAT_DEFAULT_LPARAM, TRUE); // Remove the columns that // were pulled in because of tileview. _RemoveTileColumns(); } break; } _SetView(fvmNew); // we can now switch the listview around // Now that'we no longer in tileview, we can reset the tileinfo. If we were to do it // prior to changing the view, then listview would start asking us for the tileinformation // for each item again, and we'd pull in the tile columns again. if (fvmOld == FVM_TILE) { _RemoveTileInfo(); } // Third, turn ON view specific stuff // switch (fvmNew) { case FVM_THUMBSTRIP: if (FVM_THUMBSTRIP!=fvmOld) { ListView_SetExtendedListViewStyleEx(_hwndListview, LVS_EX_SINGLEROW, LVS_EX_SINGLEROW); // thumbstrip can not be in group view if (_fGroupView) _ToggleGrouping(); // thumbstrip is always in auto-arrange if (!(_fs.fFlags & FWF_AUTOARRANGE)) { _ClearItemPositions(); SHSetWindowBits(_hwndListview, GWL_STYLE, LVS_AUTOARRANGE, LVS_AUTOARRANGE); } } // fall through case FVM_THUMBNAIL: if (!_IsImageMode(fvmOld)) { if (GetKeyState(VK_SHIFT) < 0) { _fs.fFlags ^= FWF_HIDEFILENAMES; // toggle } _SetThumbview(); _DoThumbnailReadAhead(); RECT rc = {1, 3, 4, 4}; ListView_SetViewMargins(_hwndListview, &rc); } break; case FVM_TILE: if (!_IsTileMode(fvmOld)) { _SetTileview(); RECT rc = {3, 4, 4, 1}; ListView_SetViewMargins(_hwndListview, &rc); } break; default: _SetSysImageList(); { RECT rc = {1, 3, 4, 0}; ListView_SetViewMargins(_hwndListview, &rc); } break; } } if (SWITCHTOVIEW_NOWEBVIEW != uiType) { // New to Whistler: a view mode transition may also entail a web view template change if (_ShouldShowWebView()) { _TryShowWebView(fvmNew, fvmOld); _AutoAutoArrange(0); hr = S_OK; // we don't care about failure since we still get icons } else { _TryHideWebView(); } } if (SWITCHTOVIEW_WEBVIEWONLY != uiType) { ShowHideListView(); _AutoAutoArrange(0); if (bSetFocusRequired) { // _hwndListview is the current view window. Let's set focus to it. CallCB(SFVM_SETFOCUS, 0, 0); ViewWindowSetFocus(); // notify image preview control to update its image if (fvmNew == FVM_THUMBSTRIP) _ThumbstripSendImagePreviewFocusChangeEvent(); } else { SetFocus(hwndCurrentFocus); } CheckToolbar(); // update menus, i.e. add Choose Columns to the view menu if Details view is selected // or remove it otherwise RecreateMenus(); _EnableDisableTBButtons(); } return hr; } // Description: // Notify image preview control to update its image. The image preview // control uses focus change events to track when it should update the image // it is displaying. When it receives a focus change event, it queries the // listview to see which item has focus, then displays that item in the // image preview window. When nothing in the listview has focus (such as // when it has no items), the image preview window displays as empty. // // This method fires the "focus changed" event which is picked up by the // image preview control, and causes it to update the image it's displaying. // void CDefView::_ThumbstripSendImagePreviewFocusChangeEvent() { ASSERT(_fs.ViewMode == FVM_THUMBSTRIP); _FireEvent(DISPID_FOCUSCHANGED); } int CDefView::CheckCurrentViewMenuItem(HMENU hmenu) { int iCurViewMenuItem = _GetMenuIDFromViewMode(_fs.ViewMode); CheckMenuRadioItem(hmenu, SFVIDM_VIEW_FIRSTVIEW, SFVIDM_VIEW_LASTVIEW, iCurViewMenuItem, MF_BYCOMMAND | MF_CHECKED); return iCurViewMenuItem; } const UINT c_aiNonCustomizableFolders[] = { CSIDL_WINDOWS, CSIDL_SYSTEM, CSIDL_SYSTEMX86, CSIDL_PROGRAM_FILES, CSIDL_PROGRAM_FILESX86, CSIDL_PERSONAL, CSIDL_MYDOCUMENTS, CSIDL_MYMUSIC, CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_COMMON_DOCUMENTS, CSIDL_COMMON_MUSIC, CSIDL_COMMON_PICTURES, CSIDL_COMMON_VIDEO }; // since we moved to the property bag this check is fast; we don't probe to see if we can create desktop.ini // or anything. BOOL IsCustomizable(LPCITEMIDLIST pidlFolder) { BOOL fCustomizable = FALSE; if (!SHRestricted(REST_NOCUSTOMIZETHISFOLDER) && !SHRestricted(REST_CLASSICSHELL)) { // Check if this is a file system folder. // customization requires the folder being a regular file system // folder. FILESYSTEMANCESTOR is the key bit here #define SFGAO_CUST_BITS (SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR) ULONG rgfFolderAttr = SFGAO_CUST_BITS; TCHAR szPath[MAX_PATH]; if (SUCCEEDED(SHGetNameAndFlags(pidlFolder, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), &rgfFolderAttr)) && (SFGAO_CUST_BITS == (rgfFolderAttr & SFGAO_CUST_BITS))) { if (!PathIsOneOf(szPath, c_aiNonCustomizableFolders, ARRAYSIZE(c_aiNonCustomizableFolders)) && (!PathIsRoot(szPath) || PathIsUNCServerShare(szPath)) && !SHRestricted(REST_NOCUSTOMIZEWEBVIEW)) { IPropertyBag *ppb; if (SUCCEEDED(SHGetViewStatePropertyBag(pidlFolder, VS_BAGSTR_EXPLORER, SHGVSPB_PERUSER | SHGVSPB_PERFOLDER, IID_PPV_ARG(IPropertyBag, &ppb)))) { fCustomizable = TRUE; ppb->Release(); } } } } return fCustomizable; } // wrapper around IsCustomizable to save some state, plus some defview-specific logic. BOOL CDefView::_CachedIsCustomizable() { if (_IsDesktop() || _IsViewDesktop() || _IsCommonDialog()) { _iCustomizable = NOT_CUSTOMIZABLE; } if (_iCustomizable == DONTKNOW_IF_CUSTOMIZABLE) { LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { _iCustomizable = IsCustomizable(pidl) ? YES_CUSTOMIZABLE : NOT_CUSTOMIZABLE; ILFree(pidl); } } return (_iCustomizable != NOT_CUSTOMIZABLE); } BOOL CDefView::_InvokeCustomization() { BOOL fRet = FALSE; if (!_CachedIsCustomizable()) { //If not customizable, put up this error message! ShellMessageBox(HINST_THISDLL, _hwndMain, MAKEINTRESOURCE(IDS_NOTCUSTOMIZABLE), NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND); return FALSE; // ...and bail out! } //Save the view state first. SaveViewState(); LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { TCHAR szSheetName[25]; LoadString(HINST_THISDLL, IDS_CUSTOMIZE, szSheetName, ARRAYSIZE(szSheetName)); SHELLEXECUTEINFO sei = { SIZEOF(sei), SEE_MASK_INVOKEIDLIST, // fMask _hwndMain, // hwnd c_szProperties, // lpVerb NULL, // lpFile szSheetName, // lpParameters NULL, // lpDirectory SW_SHOWNORMAL, // nShow NULL, // hInstApp pidl, // lpIDList NULL, // lpClass 0, // hkeyClass 0, // dwHotKey NULL // hIcon }; // only invoking properties verb fRet = ShellExecuteEx(&sei); ILFree(pidl); } return fRet; } struct { UINT uiSfvidm; DWORD dwOlecmdid; } const c_CmdTable[] = { { SFVIDM_EDIT_CUT, OLECMDID_CUT }, { SFVIDM_EDIT_COPY, OLECMDID_COPY }, { SFVIDM_EDIT_PASTE, OLECMDID_PASTE }, { SFVIDM_FILE_DELETE, OLECMDID_DELETE }, { SFVIDM_FILE_PROPERTIES, OLECMDID_PROPERTIES }, }; DWORD OlecmdidFromSfvidm(UINT uiSfvidm) { DWORD dwOlecmdid = 0; for (int i = 0; i < ARRAYSIZE(c_CmdTable); i++) { if (c_CmdTable[i].uiSfvidm == uiSfvidm) { dwOlecmdid = c_CmdTable[i].dwOlecmdid; break; } } return dwOlecmdid; } void HideIE4DesktopChannelBar() { HWND hwndChannelBar; //Check if the channel bar is currently running. If so, turn it off! if ((hwndChannelBar = FindWindowEx(GetShellWindow(), NULL, TEXT("BaseBar"), TEXT("ChanApp"))) || (hwndChannelBar = FindWindowEx(NULL, NULL, TEXT("BaseBar"), TEXT("ChanApp")))) // can be a toplevel window { //Close the channel bar. PostMessage(hwndChannelBar, WM_CLOSE, 0, 0); } } // Wrapper around _SwitchToWebView to do desktop-specific stuff LRESULT CDefView::_SwitchDesktopHTML(BOOL fShow) { LRESULT lRes; if (fShow) { // Do this early to give the desktop a chance to regenerate it's webview template _CallRefresh(TRUE); lRes = SUCCEEDED(_SwitchToWebView(TRUE)); if (lRes) { HideIE4DesktopChannelBar(); } } else { _SwitchToWebView(FALSE); CoFreeUnusedLibraries(); lRes = TRUE; } return lRes; } void CDefView::_DoColumnsMenu(int x, int y) // X and Y are screen coordinates { HMENU hmenu = CreatePopupMenu(); if (hmenu) { AddColumnsToMenu(hmenu, SFVIDM_COLUMN_FIRST); int item = TrackPopupMenu(hmenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, x, y, 0, _hwndListview, NULL); DestroyMenu(hmenu); // validate item first if (item == SFVIDM_VIEW_COLSETTINGS) { CColumnDlg ccd(this); AddColumns(); ccd.ShowDialog(_hwndMain); } else if (item > SFVIDM_COLUMN_FIRST) { _HandleColumnToggle(item - SFVIDM_COLUMN_FIRST, TRUE); } } } BOOL CDefView::_ArrangeBy(UINT idCmd) { int iColumn = idCmd - SFVIDM_GROUPSFIRST; BOOL fAllowToggle = TRUE; // We want to enter group by if We already have a group, or if this is an extended grouping if ((_fGroupView || InRange(idCmd, SFVIDM_GROUPSEXTENDEDFIRST, SFVIDM_GROUPSEXTENDEDLAST)) && !(_fs.ViewMode == FVM_LIST)) { _GroupBy(idCmd); iColumn = 0; // Arrange by name, when grouping fAllowToggle = FALSE; // Always arrange in ascending order } return S_OK == _OnRearrange(iColumn, fAllowToggle); } BOOL CDefView::_InitArrangeMenu(HMENU hmInit) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_SUBMENU; GetMenuItemInfo(hmInit, SFVIDM_MENU_ARRANGE, MF_BYCOMMAND, &mii); HMENU hmenuCtx = mii.hSubMenu; if (hmenuCtx) { int idToCheck = -1; AddColumns(); UINT cVisible = _RealToVisibleCol(-1) + 1; // count ICategoryProvider* pcp = NULL; _pshf->CreateViewObject(NULL, IID_PPV_ARG(ICategoryProvider, &pcp)); while (1) { MENUITEMINFO miiSep = {0}; miiSep.cbSize = sizeof(mii); miiSep.fMask = MIIM_ID | MIIM_TYPE; miiSep.wID = -1; if (!GetMenuItemInfo(hmenuCtx, 0, MF_BYPOSITION, &miiSep) || miiSep.wID == SFVIDM_GROUPSEP) { break; } DeleteMenu(hmenuCtx, 0, MF_BYPOSITION); } UINT iInsert = 0; for (UINT i = 0; i < cVisible; i++) { BOOL fAddItem = TRUE; UINT iReal = _VisibleToRealCol(i); if (_IsDetailsColumn(iReal)) { // See if the category Provider wants to exclude this column when groupview is enabled if (pcp && _fGroupView) { SHCOLUMNID scid; if (SUCCEEDED(_pshf2->MapColumnToSCID(iReal, &scid))) { // returns S_FALSE to remove. fAddItem = (S_OK == pcp->CanCategorizeOnSCID(&scid)); } } if (fAddItem) { WCHAR wszName[MAX_COLUMN_NAME_LEN]; BOOL bpuiName = FALSE; IPropertyUI *ppui; // Attempt to retrieve mnemonic name from IPropertyUI interface. if (_pshf2 && SUCCEEDED(_GetPropertyUI(&ppui))) { SHCOLUMNID scid; if (SUCCEEDED(_pshf2->MapColumnToSCID(iReal, &scid))) { bpuiName = SUCCEEDED(ppui->GetDisplayName(scid.fmtid, scid.pid, PUIFNF_MNEMONIC, wszName, ARRAYSIZE(wszName))); } ppui->Release(); } MENUITEMINFO miiItem = {0}; miiItem.cbSize = sizeof(mii); miiItem.fMask = MIIM_ID | MIIM_TYPE; miiItem.fType = MFT_STRING; miiItem.wID = iReal + SFVIDM_GROUPSFIRST; miiItem.dwTypeData = bpuiName ? wszName : _vs.GetColumnName(iReal); InsertMenuItem(hmenuCtx, iInsert++, TRUE, &miiItem); } } } _InitExtendedGroups(pcp, hmenuCtx, iInsert, &idToCheck); // Only do the Bullets if we're in auto arrange mode or if we are in details. if (_IsAutoArrange() || _fGroupView || _fs.ViewMode == FVM_DETAILS) { if (idToCheck == -1) { // Since we're not going to have more than 4million columns, this case should suffice idToCheck = (int)_vs._lParamSort + SFVIDM_GROUPSFIRST; if (_fGroupView && !(_fs.ViewMode == FVM_LIST)) { idToCheck = MapSCIDToColumn(_pshf2, &_vs._scidDetails) + SFVIDM_GROUPSFIRST; } } CheckMenuRadioItem(hmenuCtx, SFVIDM_GROUPSFIRST, SFVIDM_GROUPSEXTENDEDLAST, idToCheck, MF_BYCOMMAND | MF_CHECKED); } if (pcp) pcp->Release(); } DWORD dwGroupEnableFlags = MF_GRAYED; if (_pshf2 && // Needs to implement IShellFolder2 !_IsViewDesktop() && // Doesn't work on the desktop !(_fs.ViewMode == FVM_LIST) && // Doesn't work in 'List' View !(_fs.ViewMode == FVM_THUMBSTRIP) &&// Doesn't work in 'ThumbStrip' View !(_fs.fFlags & FWF_OWNERDATA)) // Doesn't work for ownerdata lists (search) { dwGroupEnableFlags = MF_ENABLED; CheckMenuItem(hmenuCtx, SFVIDM_GROUPBY, MF_BYCOMMAND | (_fGroupView?MF_CHECKED:0)); } EnableMenuItem(hmenuCtx, SFVIDM_GROUPBY, MF_BYCOMMAND | dwGroupEnableFlags); _SHPrettyMenu(hmenuCtx); return TRUE; } BOOL CDefView::_InitExtendedGroups(ICategoryProvider* pcp, HMENU hmenuCtx, int iIndex, int* piIdToCheck) { if (!pcp) return FALSE; *piIdToCheck = -1; if (_hdaCategories == NULL) { _hdaCategories = DSA_Create(sizeof(GUID), 5); if (_hdaCategories) { IEnumGUID* penum; if (SUCCEEDED(pcp->EnumCategories(&penum))) { GUID guidCat; while (S_OK == penum->Next(1, &guidCat, NULL)) { DSA_AppendItem(_hdaCategories, (void*)&guidCat); } penum->Release(); } } } if (_hdaCategories) { int id = SFVIDM_GROUPSEXTENDEDFIRST; TCHAR szName[MAX_PATH]; TCHAR szCurrentName[MAX_PATH]; WCHAR wszName[MAX_PATH]; GUID* pguidCat; szCurrentName[0] = 0; if (_pcat) { _pcat->GetDescription(szCurrentName, ARRAYSIZE(szCurrentName)); } MENUITEMINFO mii = {0}; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_ID | MIIM_TYPE; mii.fType = MFT_SEPARATOR; mii.wID = -1; InsertMenuItem(hmenuCtx, iIndex, TRUE, &mii); iIndex++; int cCategories = DSA_GetItemCount(_hdaCategories); for (int i = 0; i < cCategories; i++) { pguidCat = (GUID*)DSA_GetItemPtr(_hdaCategories, i); if (SUCCEEDED(pcp->GetCategoryName(pguidCat, wszName, ARRAYSIZE(wszName)))) { SHUnicodeToTChar(wszName, szName, ARRAYSIZE(szName)); MENUITEMINFO mii = {0}; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; mii.fType = MFT_STRING; mii.dwItemData = (DWORD_PTR)pguidCat; mii.wID = id; mii.dwTypeData = szName; mii.cch = ARRAYSIZE(szName); InsertMenuItem(hmenuCtx, iIndex, TRUE, &mii); if (lstrcmpi(szCurrentName, szName) == 0) { *piIdToCheck = id; } id++; iIndex++; } } } return TRUE; } BOOL CDefView::_CategorizeOnSCID(const SHCOLUMNID* pscid) { BOOL fRet = FALSE; _fSlowGroup = FALSE; if (IsEqualSCID(*pscid, SCID_NAME)) { if (SUCCEEDED(CAlphaCategorizer_Create(_pshf2, IID_PPV_ARG(ICategorizer, &_pcat)))) { _vs._guidGroupID = CLSID_AlphabeticalCategorizer; fRet = TRUE; } } else if (IsEqualSCID(*pscid, SCID_SIZE)) { if (SUCCEEDED(CSizeCategorizer_Create(_pshf2, IID_PPV_ARG(ICategorizer, &_pcat)))) { _vs._guidGroupID = CLSID_SizeCategorizer; fRet = TRUE; } } else if (IsEqualSCID(*pscid, SCID_WRITETIME) || IsEqualSCID(*pscid, SCID_CREATETIME) || IsEqualSCID(*pscid, SCID_ACCESSTIME) || IsEqualSCID(*pscid, SCID_DATEDELETED)) { if (SUCCEEDED(CTimeCategorizer_Create(_pshf2, pscid, IID_PPV_ARG(ICategorizer, &_pcat)))) { _vs._guidGroupID = CLSID_TimeCategorizer; fRet = TRUE; } } else { _fSlowGroup = TRUE; if (SUCCEEDED(CDetailCategorizer_Create(*pscid, _pshf2, IID_PPV_ARG(ICategorizer, &_pcat)))) { _vs._guidGroupID = CLSID_DetailCategorizer; fRet = TRUE; } } if (fRet) { _vs._scidDetails = *pscid; } return fRet; } // slow groups have an architecture problem, after 5000 items in the view // the message queue overflows from groupdone messages and its all bad. // this ends up hanging the static flashlight window around because of resulting // refcount issues. // the only view that both defaults to a slow group and could have 5000 items is the // cd burning folder. lou says its too late to change the interface now to let the // categorizer decide if its slow or not, so just special case it here. // everything works if its a fast group (and its actually fast anyway). BOOL CDefView::_IsSlowGroup(const GUID *pguid) { BOOL fSlow = TRUE; if (IsEqualGUID(*pguid, CLSID_MergedCategorizer)) { fSlow = FALSE; } // room to grow if we need to special case others return fSlow; } BOOL CDefView::_CategorizeOnGUID(const GUID* pguid, const SHCOLUMNID* pscid) { BOOL fRet = FALSE; if (_pshf2) { _fGroupView = FALSE; // Just in case the create fails if (_pScheduler) _pScheduler->RemoveTasks(TOID_DVBackgroundGroup, ITSAT_DEFAULT_LPARAM, TRUE); ATOMICRELEASE(_pcat); ListView_RemoveAllGroups(_hwndListview); ICategoryProvider* pcp; if (SUCCEEDED(_pshf->CreateViewObject(NULL, IID_PPV_ARG(ICategoryProvider, &pcp)))) { GUID guidGroup = *pguid; if (pscid && S_OK != pcp->GetCategoryForSCID(const_cast(pscid), &guidGroup)) { fRet = _CategorizeOnSCID(pscid); } else { _fSlowGroup = _IsSlowGroup(&guidGroup); if (SUCCEEDED(pcp->CreateCategory(&guidGroup, IID_PPV_ARG(ICategorizer, &_pcat)))) { _vs._guidGroupID = guidGroup; if (pscid) { _vs._scidDetails = *pscid; } else { ZeroMemory(&_vs._scidDetails, sizeof(_vs._scidDetails)); } fRet = TRUE; } } pcp->Release(); } else { if (pscid) fRet = _CategorizeOnSCID(pscid); } } if (fRet) { _ClearItemPositions(); _fGroupView = TRUE; SHSetWindowBits(_hwndListview, GWL_STYLE, LVS_AUTOARRANGE, LVS_AUTOARRANGE); // We're enabling groupview, so turn off the selected column // (this will make it so tiles do not show the selected column as their first column) ListView_SetSelectedColumn(_hwndListview, -1); if (_fSlowGroup) _fAllowSearchingWindow = TRUE; ListView_EnableGroupView(_hwndListview, TRUE); } return fRet; } void CDefView::_GroupBy(int iColumn) { _fGroupView = FALSE; // Just in case the create fails if (_pshf2) { if (InRange(iColumn, SFVIDM_GROUPSEXTENDEDFIRST, SFVIDM_GROUPSEXTENDEDLAST)) { int iIndex = iColumn - SFVIDM_GROUPSEXTENDEDFIRST; GUID* pguid = (GUID*)DSA_GetItemPtr(_hdaCategories, iIndex); if (pguid) { _CategorizeOnGUID(pguid, NULL); } } else { SHCOLUMNID scid; iColumn -= SFVIDM_GROUPSFIRST; if (SUCCEEDED(_pshf2->MapColumnToSCID(iColumn, &scid))) { _CategorizeOnGUID(&CLSID_DetailCategorizer, &scid); } } // Make sure the arrows on details view look right... _SetSortFeedback(); } } void CDefView::_ToggleGrouping() { if (_fGroupView) { _fGroupView = FALSE; if (_pScheduler) _pScheduler->RemoveTasks(TOID_DVBackgroundGroup, ITSAT_DEFAULT_LPARAM, TRUE); ListView_EnableGroupView(_hwndListview, FALSE); ListView_RemoveAllGroups(_hwndListview); ListView_SetSelectedColumn(_hwndListview, _vs._lParamSort); _SetSortFeedback(); _OnRearrange(_vs._lParamSort, FALSE); } else if (FVM_THUMBSTRIP != _fs.ViewMode) // Thumbstrip can never go into groupby mode { // If we have a categorizer, then we can just reenable grouping. if (_pcat) { _fGroupView = TRUE; ListView_EnableGroupView(_hwndListview, TRUE); ListView_SetSelectedColumn(_hwndListview, -1); _SetSortFeedback(); } else { // If we don't, then we need to go get one. _GroupBy((int)_vs._lParamSort + SFVIDM_GROUPSFIRST); } } } LRESULT CDefView::_OnDefviewEditCommand(UINT uID) { // if we are in label edit mode, don't allowany of the buttons...... if (_fInLabelEdit) { MessageBeep(0); return 1; } if (_AllowCommand(uID)) { HRESULT hr = _ExplorerCommand(uID); if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_CANCELLED))) { MessageBeep(0); } return 1; } else { return 0; } } HRESULT CDefView::_DoMoveOrCopyTo(REFCLSID clsid, IShellItemArray *psiItemArray) { IDataObject *pdo = NULL; IContextMenu *pcm; HRESULT hr = E_FAIL; if (!psiItemArray) { psiItemArray = _GetFolderAsShellItemArray(); } if (psiItemArray) { hr = psiItemArray->BindToHandler(NULL,BHID_DataObject,IID_PPV_ARG(IDataObject, &pdo)); } if (SUCCEEDED(hr)) { hr = SHCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(IContextMenu, &pcm)); if (SUCCEEDED(hr)) { IUnknown_SetSite(pcm, SAFECAST(this, IDropTarget *)); // Needed to go modal during UI IShellExtInit* psei; hr = pcm->QueryInterface(IID_PPV_ARG(IShellExtInit, &psei)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlFolder = _GetViewPidl(); if (pidlFolder) { psei->Initialize(pidlFolder, pdo, NULL); ILFree(pidlFolder); } CMINVOKECOMMANDINFO ici = {0}; ici.hwnd = _hwndMain; hr = pcm->InvokeCommand(&ici); psei->Release(); } IUnknown_SetSite(pcm, NULL); pcm->Release(); } pdo->Release(); } return hr; } LRESULT CDefView::_OnCommand(IContextMenu *pcmToInvoke, WPARAM wParam, LPARAM lParam) { UINT uID = GET_WM_COMMAND_ID(wParam, lParam); if (InRange(uID, SFVIDM_GROUPSFIRST, SFVIDM_GROUPSEXTENDEDLAST)) { _ArrangeBy(uID); return 1; } else if (InRange(uID, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST)) { UINT uCMBias = SFVIDM_CONTEXT_FIRST; if (_pcmFile) { IContextMenu* pcmToInvoke = _pcmFile; pcmToInvoke->AddRef(); // We need to special case the rename command TCHAR szCommandString[64]; ContextMenu_GetCommandStringVerb(pcmToInvoke, uID - SFVIDM_CONTEXT_FIRST, szCommandString, ARRAYSIZE(szCommandString)); if (lstrcmpi(szCommandString, c_szRename) == 0) { DoRename(); } else { CMINVOKECOMMANDINFOEX ici = { 0 }; ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX); ici.hwnd = _hwndMain; ici.lpVerb = (LPSTR)MAKEINTRESOURCE(uID - SFVIDM_CONTEXT_FIRST); ici.nShow = SW_NORMAL; ici.fMask = CMIC_MASK_FLAG_LOG_USAGE; int iItemSelect = ListView_GetNextItem(_hwndListview, -1, LVNI_SELECTED); if (iItemSelect != -1) { RECT rcItem; ListView_GetItemRect(_hwndListview, iItemSelect, &rcItem, LVIR_BOUNDS); MapWindowPoints(_hwndListview, HWND_DESKTOP, (POINT *)&rcItem, 2); ici.ptInvoke.x = (rcItem.left + rcItem.right) / 2; ici.ptInvoke.y = (rcItem.top + rcItem.bottom) / 2; ici.fMask |= CMIC_MASK_PTINVOKE; } // record if shift or control was being held down SetICIKeyModifiers(&ici.fMask); _InvokeContextMenu(pcmToInvoke, &ici); } //Since we are releaseing our only hold on the context menu, release the site. IUnknown_SetSite(pcmToInvoke, NULL); pcmToInvoke->Release(); // undo our gaurd ref ATOMICRELEASE(_pcmFile); // once used, it can't be used again } return 0; } #ifdef DEBUG else if (InRange(uID, SFVIDM_BACK_CONTEXT_FIRST, SFVIDM_BACK_CONTEXT_LAST)) { RIPMSG(FALSE, "_OnCommand should not get this context menu invoke..."); } #endif else if (InRange(uID, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST) && HasCB()) { // view callback range CallCB(SFVM_INVOKECOMMAND, uID - SFVIDM_CLIENT_FIRST, 0); return 0; } // First check for commands that always go to this defview switch (uID) { case SFVIDM_GROUPBY: _ToggleGrouping(); break; case SFVIDM_EDIT_UNDO: // if we are in label edit mode, don't allowany of the buttons...... if (_fInLabelEdit) { MessageBeep(0); return 0; } Undo(_hwndMain); break; case SFVIDM_VIEW_COLSETTINGS: { CColumnDlg ccd(this); AddColumns(); ccd.ShowDialog(_hwndMain); break; } case SFVIDM_VIEW_VIEWMENU: { // if we are in label edit mode, don't allow any of the buttons...... if (_fInLabelEdit) { MessageBeep(0); return 0; } LPCDFVCMDDATA pcd = (LPCDFVCMDDATA)lParam; if (pcd && pcd->pva && pcd->pva->byref) { LPRECT prect = (LPRECT)pcd->pva->byref; IContextMenu* pcm; if (SUCCEEDED(_Create_BackgrndHMENU(TRUE, IID_PPV_ARG(IContextMenu, &pcm)))) { POINT pt = { prect->left, prect->bottom}; DoContextMenuPopup(pcm, 0, pt); pcm->Release(); } } } break; case SFVIDM_VIEW_TILE: // // AppCompat: Pre WinXP 0x702E used to be SFVIDM_VIEW_VIEWMENU, now it's SFVIDM_VIEW_TILE. // Corel apps send 0x702E to get the ViewMenu on the SaveAs dialogs. Of course that no // longer works since 0x702E switches them to TileMode. Luckily SFVIDM_VIEW_VIEWMENU has // a non-NULL lParam while SFVIDM_VIEW_TILE always has a NULL lParam so we can tell the // two apart. So when Corel sends a 0x702E with a non-NULL lParam they mean SFVIDM_VIEW_VIEWMENU // and when they send a 0x702E with a NULL lParam they mean SFVIDM_VIEW_TILE. // COMPILETIME_ASSERT(SFVIDM_VIEW_TILE == 0x702E); //see above app compat comments. if (lParam && (SHGetAppCompatFlags(ACF_WIN95DEFVIEW) & ACF_WIN95DEFVIEW)) { return _OnCommand(pcmToInvoke, SFVIDM_VIEW_VIEWMENU, lParam); // change this into a SFVIDM_VIEW_VIEWMENU } // Fall through ... case SFVIDM_VIEW_ICON: case SFVIDM_VIEW_SMALLICON: case SFVIDM_VIEW_THUMBNAIL: case SFVIDM_VIEW_THUMBSTRIP: case SFVIDM_VIEW_LIST: case SFVIDM_VIEW_DETAILS: COMPILETIME_ASSERT(FVM_ICON == (SFVIDM_VIEW_ICON-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_SMALLICON == (SFVIDM_VIEW_SMALLICON-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_THUMBNAIL == (SFVIDM_VIEW_THUMBNAIL-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_THUMBSTRIP == (SFVIDM_VIEW_THUMBSTRIP-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_LIST == (SFVIDM_VIEW_LIST-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_TILE == (SFVIDM_VIEW_TILE-SFVIDM_VIEW_FIRST)); COMPILETIME_ASSERT(FVM_DETAILS == (SFVIDM_VIEW_DETAILS-SFVIDM_VIEW_FIRST)); SetCurrentViewMode(uID - SFVIDM_VIEW_FIRST); break; case SFVIDM_DESKTOPHTML_WEBCONTENT: { // we have removed this button, but we need to keep this for message for other things BOOL bHasVisibleNonLocalPicture = FALSE; SHELLSTATE ss; SHGetSetSettings(&ss, SSF_DESKTOPHTML, FALSE); // Get the setting ss.fDesktopHTML = !ss.fDesktopHTML; // Toggle the state if (ss.fDesktopHTML && !IsICWCompleted()) { IActiveDesktop *pIAD; if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ActiveDesktop, NULL, IID_PPV_ARG(IActiveDesktop, &pIAD)))) { bHasVisibleNonLocalPicture = (DisableUndisplayableComponents(pIAD) != 0); pIAD->Release(); } } if (!bHasVisibleNonLocalPicture) { SHELLSTATE ss2; SHGetSetSettings(&ss, SSF_DESKTOPHTML, TRUE); // Write back the new // Now read back the current setting - only call _SwitchDesktopHTML if the current // setting and the one we just set agree. If they don't that means someone changed // the setting during the above call and we shouldn't do any more work or our state // will get messed up. SHGetSetSettings(&ss2, SSF_DESKTOPHTML, FALSE); if (ss.fDesktopHTML == ss2.fDesktopHTML) { _SwitchDesktopHTML(BOOLIFY(ss.fDesktopHTML)); } } } break; case SFVIDM_DESKTOPHTML_ICONS: case SFVIDM_ARRANGE_DISPLAYICONS: // (buzzr) I'm leaving SFVIDM_ARRANGE_DISPLAYICONS { // for backwards compat. It used to be a SHELLSTATE ss; // menu entry on POPUP_SFV_BACKGROUND. DWORD dwValue; // Toggle the cached state _fs.fFlags ^= FWF_NOICONS; ss.fHideIcons = ((_fs.fFlags & FWF_NOICONS) != 0); dwValue = ss.fHideIcons ? 1 : 0; // Since this value is currrently stored under the "advanced" reg tree we need // to explicitly write to the registry or the value won't persist properly via // SHGetSetSettings. SHSetValue(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), TEXT("HideIcons"), REG_DWORD, &dwValue, sizeof(dwValue)); // Finally set the ShellState and perform the action! SHGetSetSettings(&ss, SSF_HIDEICONS, TRUE); // Since this SFVIDM_ comes from the menu, we better already be active (or // this SW_SHOW could make us visible before we want to be seen). ASSERT(_uState != SVUIA_DEACTIVATE); ActiveDesktop_ApplyChanges(); ShowHideListView(); } break; case SFVIDM_DESKTOPHTML_LOCK: { DWORD dwFlags = GetDesktopFlags(); dwFlags ^= COMPONENTS_LOCKED; SetDesktopFlags(COMPONENTS_LOCKED, dwFlags); ActiveDesktop_ApplyChanges(); } break; case SFVIDM_DESKTOPHTML_WIZARD: { // launch desktop cleanup wizard SHRunDLLThread(NULL, TEXT("fldrclnr.dll,Wizard_RunDLL all"), SW_SHOWNORMAL); } break; case SFVIDM_EDIT_COPYTO: case SFVIDM_EDIT_MOVETO: { // if we are in label edit mode, don't allowany of the buttons...... if (_fInLabelEdit) { MessageBeep(0); return 0; } if (_pSelectionShellItemArray) { _DoMoveOrCopyTo(((uID == SFVIDM_EDIT_COPYTO) ? CLSID_CopyToMenu : CLSID_MoveToMenu), _pSelectionShellItemArray); } } break; case SFVIDM_FILE_PROPERTIES: if (SHRestricted(REST_NOVIEWCONTEXTMENU)) break; // else fall through... case SFVIDM_EDIT_PASTE: case SFVIDM_EDIT_PASTELINK: case SFVIDM_EDIT_COPY: case SFVIDM_EDIT_CUT: case SFVIDM_FILE_LINK: case SFVIDM_FILE_DELETE: if (!_OnDefviewEditCommand(uID)) { // REVIEW: this looks like a hack. // there's got to be a cleaner way of doing this... // LPDFVCMDDATA pcd = (LPDFVCMDDATA)lParam; // Try translating the SFVIDM value into a standard // OLECMDID value, so that the caller can try applying // it to a different object. // doh if (!IsBadWritePtr(pcd, sizeof(*pcd))) { pcd->nCmdIDTranslated = OlecmdidFromSfvidm(uID); } } break; case SFVIDM_TOOL_OPTIONS: if (!SHRestricted(REST_NOFOLDEROPTIONS)) { IUnknown_Exec(_psb, &CGID_Explorer, SBCMDID_OPTIONS, 0, NULL, NULL); } break; #ifdef DEBUG case SFVIDM_DEBUG_WEBVIEW: _cFrame._ShowWebViewContent(); break; #endif // DEBUG case SFVIDM_HELP_TOPIC: // Don't call WinHelp when we are in the common dialog. if (!_IsCommonDialog()) { // Use a callback to see if the namespace has requested a different help file name and/or topic SFVM_HELPTOPIC_DATA htd; HWND hwndDesktop = GetDesktopWindow(); SHTCharToUnicode(c_szHtmlWindowsHlp, htd.wszHelpFile, ARRAYSIZE(htd.wszHelpFile)); htd.wszHelpTopic[0] = 0; if (SUCCEEDED(CallCB(SFVM_GETHELPTOPIC, 0, (LPARAM)&htd))) { if (URL_SCHEME_MSHELP == GetUrlSchemeW(htd.wszHelpTopic)) { // // Callback specified an HSS help URL. // SHELLEXECUTEINFOW sei = {0}; sei.cbSize = sizeof(sei); sei.lpFile = htd.wszHelpTopic; sei.hwnd = hwndDesktop; sei.nShow = SW_NORMAL; // executing help topic ShellExecuteExW(&sei); } else { HtmlHelp(hwndDesktop, htd.wszHelpFile, HH_HELP_FINDER, htd.wszHelpTopic[0] ? (DWORD_PTR)htd.wszHelpTopic : 0); } } else { // ask the shell dispatch object to display Help for us IShellDispatch *psd; if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_Shell, NULL, IID_PPV_ARG(IShellDispatch, &psd)))) { psd->Help(); psd->Release(); } } } break; case SFVIDM_VIEW_CUSTOMWIZARD: _InvokeCustomization(); break; case SFVIDM_MISC_HARDREFRESH: _fAllowSearchingWindow = TRUE; _FreeWebViewContentData(); _ReloadContent(TRUE); // have to enumerate before _GetDefaultViewMode() will be accurate SetCurrentViewMode(_GetDefaultViewMode()); // even if fvm is the same, it will update webview if it changed Refresh(); break; case SFVIDM_MISC_SETWEBVIEW: SetCurrentViewMode(_fs.ViewMode); // re-setting the fvm updates everything (turning web view off can switch from Thumbstrip to Thumbnail!) Refresh(); // we want to refresh when we switch turn webview on/off, since some icons appear/disappear on the transition break; case SFVIDM_MISC_REFRESH: _fAllowSearchingWindow = TRUE; Refresh(); break; default: // check for commands that need to be sent to the active object switch (uID) { case SFVIDM_ARRANGE_AUTO: _fs.fFlags ^= FWF_AUTOARRANGE; // toggle _ClearItemPositions(); SHSetWindowBits(_hwndListview, GWL_STYLE, LVS_AUTOARRANGE, _IsAutoArrange() ? LVS_AUTOARRANGE : 0); break; case SFVIDM_ARRANGE_GRID: ListView_Arrange(_hwndListview, LVA_SNAPTOGRID); break; case SFVIDM_ARRANGE_AUTOGRID: { _fs.fFlags ^= FWF_SNAPTOGRID; DWORD dwLVFlags = ListView_GetExtendedListViewStyle(_hwndListview); dwLVFlags ^= LVS_EX_SNAPTOGRID; ListView_SetExtendedListViewStyle(_hwndListview, dwLVFlags); //if this is desktop, we need to change the icon spacing. UpdateGridSizes(_IsDesktop(), _hwndListview, 0, NULL, BOOLIFY(dwLVFlags & LVS_EX_SNAPTOGRID)); // if ActiveDesktop on, need to refresh, otherwise, can just arrange SHELLSTATE ss = {0}; SHGetSetSettings( &ss, SSF_DESKTOPHTML, FALSE); if (ss.fDesktopHTML) { Refresh(); } else { if ((dwLVFlags & LVS_EX_SNAPTOGRID)) { ListView_Arrange(_hwndListview, LVA_SNAPTOGRID); } } } break; default: // Normal view, we know what to do switch (uID) { case SFVIDM_SELECT_ALL: { DECLAREWAITCURSOR; if (CallCB(SFVM_SELECTALL, 0, 0) != S_FALSE) { SetWaitCursor(); SetFocus(_hwndListview); ListView_SetItemState(_hwndListview, -1, LVIS_SELECTED, LVIS_SELECTED); // make the first item in the view the focused guy ListView_SetItemState(_hwndListview, 0, LVIS_FOCUSED, LVIS_FOCUSED); ResetWaitCursor(); } break; } case SFVIDM_DESELECT_ALL: ListView_SetItemState(_hwndListview, -1, 0, LVIS_SELECTED); break; case SFVIDM_SELECT_INVERT: { DECLAREWAITCURSOR; SetWaitCursor(); SetFocus(_hwndListview); int iItem = -1; while ((iItem = ListView_GetNextItem(_hwndListview, iItem, 0)) != -1) { // flip the selection bit on each item UINT flag = ListView_GetItemState(_hwndListview, iItem, LVIS_SELECTED); flag ^= LVNI_SELECTED; ListView_SetItemState(_hwndListview, iItem, flag, LVIS_SELECTED); } ResetWaitCursor(); break; } case SFVIDM_FILE_RENAME: DoRename(); break; default: return 1; } } } return 0; } LPITEMIDLIST CDefView::_ObjectExists(LPCITEMIDLIST pidl, BOOL fGlobal) { LPITEMIDLIST pidlReal = NULL; // 365069 - global events also come through here - ZekeL - 16-APR-2001 // this means that that the pidl may not be one level. if its deeper // then for us this item doesnt exist. this enforces our assert if (pidl && !ILIsEmpty(pidl) && (!fGlobal || ILIsEmpty(_ILNext(pidl)))) { ASSERTMSG(ILFindLastID(pidl) == pidl, "defview doesnt expect recursive notification"); SHGetRealIDL(_pshf, pidl, &pidlReal); } return pidlReal; } void CDefView::_OnRename(LPCITEMIDLIST* ppidl) { if (_pidlMonitor) { if (!ILIsParent(_pidlMonitor, ppidl[0], TRUE)) { // move to this folder _OnFSNotify(SHCNE_CREATE, &ppidl[1]); } else if (!ILIsParent(_pidlMonitor, ppidl[1], TRUE)) { // move from this folder _OnFSNotify(SHCNE_DELETE, &ppidl[0]); } else { // rename within this folder // _pidlMonitor is guaranteed to be immediate parent of both pidls so ILFindLastID is okay. LPCITEMIDLIST pidlOld = ILFindLastID(ppidl[0]); LPITEMIDLIST pidlNew = _ObjectExists(ILFindLastID(ppidl[1]), FALSE); if (pidlNew) { _UpdateObject(pidlOld, pidlNew); ILFree(pidlNew); } } } } // // SFVM_UPDATESTATUSBAR return values: // // failure code = Callback did not do anything, we must do it all // // Otherwise, the GetScode(hr) is a bitmask describing what the app // wants us to do. // // 0 - App wants us to do nothing (S_OK) - message handled completely // 1 - App wants us to set the default text (but not initialize) // // void CDefView::_UpdateStatusBar(BOOL fInitialize) { HRESULT hr; // We have to clear the contents here since some clients (like the ftp client) return S_OK from // the callback but do not set the text of the bar HWND hwndStatus; if (_psb && SUCCEEDED(_psb->GetControlWindow(FCW_STATUS, &hwndStatus)) && hwndStatus) { _fBackgroundStatusTextValid = FALSE; SendMessage(hwndStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)_TEXT("")); } if (_bBkFilling || FAILED(hr = CallCB(SFVM_UPDATESTATUSBAR, fInitialize, 0))) { // Client wants us to do everything _DoStatusBar(fInitialize); } else if (hr & SFVUSB_INITED) { // Client wants us to do text but not initialize _DoStatusBar(FALSE); } } // Returns TRUE iff we are supposed to show Web View content on this view. // For the most part it follows SSF_WEBVIEW for normal folders and SSF_DESKTOPHTML for the desktop // BOOL CDefView::_ShouldShowWebView() { // No webview for common dialogs if (_IsCommonDialog()) { return FALSE; } // No webview in cleanboot mode if (GetSystemMetrics(SM_CLEANBOOT)) return FALSE; BOOL bForceWebViewOn; if (SUCCEEDED(CallCB(SFVM_FORCEWEBVIEW, (WPARAM)&bForceWebViewOn, 0))) { return bForceWebViewOn; } // Quattro Pro (QPW) doesn't know how SHChangeNotify works, // so when they want to refresh My Computer, they create an IShellView, // invoke its CreateViewWindow(), invoke its Refresh(), then DestroyWindow // the window and release the view. The IShellBrowser they pass // to CreateViewWindow is allocated on the stack (!), and they expect // that their Release() be the last one. Creating an async view keeps // the object alive, so when the view is complete, we try to talk to the // IShellBrowser and fault because it's already gone. // // The Zip Archives (from Aeco Systems) is another messed up App. // They neither implement IPersistFolder2 (so we can't get their pidl) nor // set the pidl to the shellfolderviewcb object. They don't implement // IShellFolder2 either. Webview is practically useless for them. // // Adaptec Easy CD Creator 3.5 is in the same boat. // SHELLSTATE ss; SHGetSetSettings(&ss, SSF_WIN95CLASSIC | SSF_DESKTOPHTML | SSF_WEBVIEW, FALSE); // If the "no web view" flag is set (potential WebOC case) then return false; if (_fs.fFlags & FWF_NOWEBVIEW) return FALSE; if (_IsDesktop()) { return ss.fDesktopHTML; } else { return ss.fWebView && !(SHGetAppCompatFlags(ACF_OLDCREATEVIEWWND) & ACF_OLDCREATEVIEWWND) && !(SHGetObjectCompatFlags(_pshf, NULL) & OBJCOMPATF_NO_WEBVIEW); } } // takes ownership of pidlNew since _AddObject takes ownership. void CDefView::_AddOrUpdateItem(LPCITEMIDLIST pidlOld, LPITEMIDLIST pidlNew) { if (_FindItem(pidlOld, NULL, FALSE) != -1) { _UpdateObject(pidlOld, pidlNew); ILFree(pidlNew); } else { // check if the shellfolder says this new guy shouldn't be enumerated. if (!_Attributes(pidlNew, SFGAO_NONENUMERATED)) { _AddObject(pidlNew); // takes pidl ownership. } else { ILFree(pidlNew); } } } #define FSNDEBUG // WM_DSV_FSNOTIFY message LRESULT CDefView::_OnFSNotify(LONG lNotification, LPCITEMIDLIST* ppidl) { LPITEMIDLIST pidl; LPCITEMIDLIST pidlItem; // // Note that renames between directories are changed to // create/delete pairs by SHChangeNotify. // #ifdef DEBUG #ifdef FSNDEBUG TCHAR szPath[MAX_PATH]; TraceMsg(TF_DEFVIEW, "CDefView::_OnFSNotify, hwnd = %d lEvent = %d", _hwndView, lNotification); switch (lNotification) { case SHCNE_RENAMEITEM: case SHCNE_RENAMEFOLDER: // two pidls SHGetPathFromIDList(ppidl[0], szPath); TraceMsg(TF_DEFVIEW, "CDefView::_OnFSNotify: hwnd %d, %s", _hwndView, szPath); SHGetPathFromIDList(ppidl[1], szPath); TraceMsg(TF_DEFVIEW, "CDefView::_OnFSNotify: hwnd %d, %s", _hwndView, szPath); break; case SHCNE_CREATE: case SHCNE_DELETE: case SHCNE_MKDIR: case SHCNE_RMDIR: case SHCNE_MEDIAINSERTED: case SHCNE_MEDIAREMOVED: case SHCNE_DRIVEREMOVED: case SHCNE_DRIVEADD: case SHCNE_NETSHARE: case SHCNE_NETUNSHARE: case SHCNE_ATTRIBUTES: case SHCNE_UPDATEDIR: case SHCNE_UPDATEITEM: case SHCNE_SERVERDISCONNECT: case SHCNE_DRIVEADDGUI: case SHCNE_EXTENDED_EVENT: // one pidl SHGetPathFromIDList(ppidl[0], szPath); TraceMsg(TF_DEFVIEW, "CDefView::_OnFSNotify: hwnd %d, %s", _hwndView, szPath); break; case SHCNE_UPDATEIMAGE: // DWORD wrapped inside a pidl TraceMsg(TF_DEFVIEW, "CDefView::_OnFSNotify: hwnd %d, %08x", _hwndView, ((LPSHChangeDWORDAsIDList)ppidl[0])->dwItem1); break; case SHCNE_ASSOCCHANGED: // No parameters break; } #endif #endif // we may be registered for notifications on pidls that are different from // the one returned by _GetViewPidl (ftp folder). switch (lNotification) { case SHCNE_DRIVEADD: case SHCNE_CREATE: case SHCNE_MKDIR: pidlItem = _pidlMonitor ? ILFindChild(_pidlMonitor, ppidl[0]) : NULL; pidl = _ObjectExists(pidlItem, FALSE); if (pidl) { _AddOrUpdateItem(pidlItem, pidl); } break; case SHCNE_DRIVEREMOVED: case SHCNE_DELETE: case SHCNE_RMDIR: pidlItem = _pidlMonitor ? ILFindChild(_pidlMonitor, ppidl[0]) : NULL; if (pidlItem) { ASSERTMSG(ILFindLastID(pidlItem) == pidlItem, "defview doesnt expect recursive notification"); _RemoveObject((LPITEMIDLIST)pidlItem, FALSE); } break; case SHCNE_RENAMEITEM: case SHCNE_RENAMEFOLDER: _OnRename(ppidl); break; case SHCNE_UPDATEIMAGE: // the system image cache is changing // ppidl[0] is a IDLIST of image indexs that have changed if (ppidl && ppidl[1]) { // this event is generated instead of a normal UPDATEIMAGE so that we can handle the // cross process case.... // handle the notification int iImage = SHHandleUpdateImage(ppidl[1]); if (iImage != -1) { _UpdateImage(iImage); } } else if (ppidl && ppidl[0]) { int iImage = *(int UNALIGNED *)((BYTE *)ppidl[0] + 2); _UpdateImage(iImage); } break; case SHCNE_ASSOCCHANGED: // For this one we will call refresh as we may need to reextract // the icons and the like. Later we can optimize this somewhat if // we can detect which ones changed and only update those. _ReloadContent(); break; case SHCNE_ATTRIBUTES: // these all mean the same thing case SHCNE_MEDIAINSERTED: case SHCNE_MEDIAREMOVED: case SHCNE_NETUNSHARE: case SHCNE_NETSHARE: case SHCNE_UPDATEITEM: if (ppidl) { LPCITEMIDLIST pidlOld = _pidlMonitor ? ILFindChild(_pidlMonitor, ppidl[0]) : NULL; LPITEMIDLIST pidlNew = _ObjectExists(pidlOld, SHCNE_GLOBALEVENTS & lNotification); if (pidlNew) { _AddOrUpdateItem(pidlOld, pidlNew); } else { // If we do not have any subobjects and the passed in pidl is the same as // this views pidl then refresh all the items. LPITEMIDLIST pidlView = _GetViewPidl(); if (pidlView) { if (ILIsEqual(ppidl[0], pidlView)) { _FullViewUpdate(SHCNE_UPDATEITEM == lNotification); } ILFree(pidlView); } } } else // ppidl == NULL means update all items (re-enum them) { _FullViewUpdate(SHCNE_UPDATEITEM == lNotification); } break; case SHCNE_FREESPACE: TCHAR szPath[MAX_PATH]; if (_GetPath(szPath)) { int idDrive = PathGetDriveNumber(szPath); if (idDrive != -1) { DWORD dwChangedDrives = *(DWORD UNALIGNED *)((BYTE *)ppidl[0] + 2); if (((1 << idDrive) & dwChangedDrives)) { _UpdateStatusBar(TRUE); } } } break; default: TraceMsg(TF_DEFVIEW, "DefView: unknown FSNotify %08lX, doing full update", lNotification); _FullViewUpdate(FALSE); break; } _UpdateStatusBar(FALSE); return 0; } // called when some of our objects get put on the clipboard LRESULT CDefView::_OnSetClipboard(BOOL bMove) { if (bMove) // move { // mark all selected items as being "cut" int i = -1; while ((i = ListView_GetNextItem(_hwndListview, i, LVIS_SELECTED)) != -1) { ListView_SetItemState(_hwndListview, i, LVIS_CUT, LVIS_CUT); _bHaveCutStuff = TRUE; } // join the clipboard viewer chain so we will know when to // "uncut" our selected items. if (_bHaveCutStuff) { ASSERT(!_bClipViewer); ASSERT(_hwndNextViewer == NULL); _hwndNextViewer = SetClipboardViewer(_hwndView); _bClipViewer = TRUE; } } return 0; } // called when the clipboard get changed, clear any items in the "cut" state // LRESULT CDefView::_OnClipboardChange() { // // if we dont have any cut stuff we dont care. // if (!_bHaveCutStuff) return 0; ASSERT(_bClipViewer); _RestoreAllGhostedFileView(); _bHaveCutStuff = FALSE; // // unhook from the clipboard viewer chain. // ChangeClipboardChain(_hwndView, _hwndNextViewer); _bClipViewer = FALSE; _hwndNextViewer = NULL; return 0; } // // Note: this function returns the point in Listview Coordinate // space. So any hit testing done with this needs to be converted // back to Client coordinate space... BOOL CDefView::_GetDropPoint(POINT *ppt) { // Check whether we already have gotten the drop anchor (before any // menu processing) if (_bDropAnchor) { // We'll use the insert mark rect (if available) to determine a drop point if (!_GetInsertPoint(ppt)) { *ppt = _ptDrop; // Otherwise use _ptDrop LVUtil_ClientToLV(_hwndListview, ppt); } } else if (_bMouseMenu) { *ppt = _ptDragAnchor; return TRUE; } else { // We need the most up-to-date cursor information, since this // may be called during a drop, and the last time the current // thread called GetMessage was about 10 minutes ago GetCursorPos(ppt); LVUtil_ScreenToLV(_hwndListview, ppt); } return _bDropAnchor; } // This uses the listview's insertmark to determinie an insert point // Returns FALSE if a point could not be determined, TRUE otherwise // The coordinates returned are in listview coordinate space. BOOL CDefView::_GetInsertPoint(POINT *ppt) { if (_IsAutoArrange() || (_fs.fFlags & FWF_SNAPTOGRID)) { RECT rcInsert; if (ListView_GetInsertMarkRect(_hwndListview, &rcInsert)) { LONG dwStyle = GetWindowLong(_hwndListview, GWL_STYLE); BOOL fHorizontal = (_fs.fFlags & FWF_ALIGNLEFT); if (fHorizontal) { ppt->x = (rcInsert.right + rcInsert.left) / 2; // Drop in middle of insertmark rect ppt->y = rcInsert.top; } else { ppt->x = rcInsert.left; ppt->y = (rcInsert.bottom + rcInsert.top) / 2; // Drop in middle of insertmark rect } return TRUE; } } return FALSE; } BOOL CDefView::_GetDragPoint(POINT *ppt) { BOOL fSource = _bDragSource || _bMouseMenu; if (fSource) { // if anchor from mouse activity *ppt = _ptDragAnchor; } else { // if anchor from keyboard activity... use the focused item int i = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED); if (i != -1) { ListView_GetItemPosition(_hwndListview, i, ppt); } else { ppt->x = ppt->y = 0; } } return fSource; } void CDefView::_PaintErrMsg(HWND hWnd) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // if we're in an error state, make sure we're not in webview if (_cFrame.IsWebView()) { _SwitchToWebView(FALSE); } RECT rc; GetClientRect(hWnd, &rc); DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_RECT | BF_SOFT | BF_ADJUST | BF_MIDDLE); EndPaint(hWnd, &ps); } // // The default status bar looks like this: // // No items selected: "nn object(s)" nn = total objects in folder // One item selected: if item supports InfoTip // Else: "nn object(s) selected" nn = num selected objects // // void CDefView::_DoStatusBar(BOOL fInitialize) { HWND hwndStatus; if (_psb && SUCCEEDED(_psb->GetControlWindow(FCW_STATUS, &hwndStatus)) && hwndStatus) { // Some of the failure cases do not null hwnd... UINT uMsg = IDS_FSSTATUSSELECTED; if (fInitialize) { int ciParts[] = {-1}; SendMessage(hwndStatus, SB_SETPARTS, ARRAYSIZE(ciParts), (LPARAM)ciParts); } if (_bBkFilling && ListView_GetSelectedCount(_hwndListview) == 0) { _fBackgroundStatusTextValid = FALSE; LPWSTR pszStatus = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(IDS_FSSTATUSSEARCHING)); // We are not checking if the alloc succeeded in ShellConstructMessageString since both // SendMessage and LocalFree can take NULL as inputs. SendMessage(hwndStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)pszStatus); LocalFree((void *)pszStatus); } else { LPCITEMIDLIST *apidl = NULL; int nMsgParam = ListView_GetSelectedCount(_hwndListview); switch (nMsgParam) { case 0: // No objects selected; show total item count nMsgParam = ListView_GetItemCount(_hwndListview); uMsg = IDS_FSSTATUSBASE; break; case 1: UINT cItems; GetSelectedObjects(&apidl, &cItems); break; } LPITEMIDLIST pidlFolder = _GetViewPidl(); if (pidlFolder) { CStatusBarAndInfoTipTask *pTask; if (SUCCEEDED(CStatusBarAndInfoTipTask_CreateInstance(pidlFolder, apidl ? *apidl : NULL, uMsg, nMsgParam, NULL, _hwndView, _pScheduler, &pTask))) { if (_pScheduler) { // make sure there are no other status bar background tasks going on... _pScheduler->RemoveTasks(TOID_DVBackgroundStatusBar, ITSAT_DEFAULT_LPARAM, FALSE); } _fBackgroundStatusTextValid = TRUE; _AddTask(pTask, TOID_DVBackgroundStatusBar, 0, TASK_PRIORITY_INFOTIP, ADDTASK_ATEND); pTask->Release(); } ILFree(pidlFolder); } if (apidl) LocalFree(apidl); } } } void CDefView::_OnWinIniChangeDesktop(WPARAM wParam, LPCTSTR pszSection) { if (pszSection) { if (!lstrcmpi(pszSection, TEXT("ToggleDesktop"))) { _OnCommand(NULL, SFVIDM_DESKTOPHTML_WEBCONTENT, 0); } else if (!lstrcmpi(pszSection, TEXT("RefreshDesktop"))) { if (FAILED(Refresh())) { SHELLSTATE ss; //Refresh failed because the new template didn't exist //Toggle the Registry settings back to Icons-only mode! ss.fDesktopHTML = FALSE; SHGetSetSettings(&ss, SSF_DESKTOPHTML, TRUE); // Write back the new } } else if (!lstrcmpi(pszSection, TEXT("BufferedRefresh"))) { //See if we have already started a timer to refresh if (!_fRefreshBuffered) { TraceMsg(TF_DEFVIEW, "A Buffered refresh starts the timer"); SetTimer(_hwndView, DV_IDTIMER_BUFFERED_REFRESH, 5000, NULL); // 5 sec _fRefreshBuffered = TRUE; } else //If refresh is already buffered, don't do anything! { TraceMsg(TF_DEFVIEW, "A buffered refresh occured while another is pending"); } } else { if (wParam == SPI_SETDESKWALLPAPER || wParam == SPI_SETDESKPATTERN) { _SetFolderColors(); } } } else { switch(wParam) { case SPI_SETDESKWALLPAPER: case SPI_SETDESKPATTERN: _SetFolderColors(); break; case SPI_ICONHORIZONTALSPACING: case SPI_ICONVERTICALSPACING: if (_IsDesktop()) { DWORD dwLVExStyle = ListView_GetExtendedListViewStyle(_hwndListview); UpdateGridSizes(TRUE, _hwndListview, 0, NULL, BOOLIFY(dwLVExStyle & LVS_EX_SNAPTOGRID)); } break; } } } void CDefView::_OnWinIniChange(WPARAM wParam, LPCTSTR pszSection) { if ((wParam == SPI_GETICONTITLELOGFONT) || ((wParam == 0) && pszSection && !lstrcmpi(pszSection, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\IconUnderline")))) { _UpdateUnderlines(); } if (pszSection && !lstrcmpi(pszSection, TEXT("VisualEffects"))) { Refresh(); } // Why all this code? It's a rare event -- just kick off a refresh... if (!wParam || (pszSection && !lstrcmpi(pszSection, TEXT("intl")))) { // has the time format changed while we're in details mode? if (ViewRequiresColumns(_fs.ViewMode) && !_IsOwnerData()) { InvalidateRect(_hwndListview, NULL, TRUE); // 99/04/13 #320903 vtan: If the date format has changed then iterate // the entire list looking for extended columns of type date and // resetting them to LPSTR_TEXTCALLBACK effectively dumping the cache. // For performance improvement it's possible to collect an array of // visible columns and reset that array. It will still involve TWO // for loops. int iItemCount = ListView_GetItemCount(_hwndListview); for (int iItem = 0; iItem < iItemCount; ++iItem) { for (UINT uiRealColumn = 0; uiRealColumn < _vs.GetColumnCount(); ++uiRealColumn) { DWORD dwFlags = _vs.GetColumnState(uiRealColumn); if (((dwFlags & SHCOLSTATE_EXTENDED) != 0) && ((dwFlags & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)) { UINT uiVisibleColumn = _RealToVisibleCol(uiRealColumn); ListView_SetItemText(_hwndListview, iItem, uiVisibleColumn, LPSTR_TEXTCALLBACK); } } } } } // // we may need to rebuild the icon cache. // if (wParam == SPI_SETICONMETRICS || wParam == SPI_SETNONCLIENTMETRICS) { if (_IsImageMode()) { _SetThumbview(); } else if (_IsTileMode()) { _SetTileview(); } else { _SetSysImageList(); } } // // we need to invalidate the cursor cache // if (wParam == SPI_SETCURSORS) { DAD_InvalidateCursors(); } if ((wParam == SPI_SETMENUANIMATION) && _pDUIView) { _pDUIView->ManageAnimations(FALSE); } if (!wParam && !pszSection && _pDUIView) { if (_fBarrierDisplayed != _QueryBarricadeState()) { _fBarrierDisplayed = !_fBarrierDisplayed; _pDUIView->EnableBarrier (_fBarrierDisplayed); } } if (_IsDesktop()) { _OnWinIniChangeDesktop(wParam, pszSection); } } void CDefView::_SetDefaultViewSettings() { // only do this if we've actually shown the view... // (ie, there's no _hwndStatic) // and we're not the desktop // and we're not an exstended view // and we are not in an explorer (tree pane on) if (!_hwndStatic && !_IsDesktop() && !IsExplorerBrowser(_psb)) { SHELLSTATE ss; ss.lParamSort = (LONG)_vs._lParamSort; ss.iSortDirection = _vs._iDirection; SHGetSetSettings(&ss, SSF_SORTCOLUMNS, TRUE); } } HWND CDefView::GetChildViewWindow() { if (_cFrame.IsWebView()) return _cFrame.GetExtendedViewWindow(); return _hwndListview; } void CDefView::_SetFocus() { // if it's a combined view then we need to give focus to listview if (!_fCombinedView && _cFrame.IsWebView() && !_fActivateLV) { _OnViewWindowActive(); if (_cFrame._pOleObj) { MSG msg = {_hwndView, WM_KEYDOWN, VK_TAB, 0xf0001}; // HACKHACK!!! MUST set state here! idealy shbrowse should call // UIActivate on the view but that breaks dochost stuff. // if we did not set the state here, trident would call // CSFVSite::ActivateMe that would not forward the call to obj::UIActivate // and therefore nothing would get focus (actually trident would have it // but it would not be visible). Note that this behavior happens only // second time around, i.e. on init UIActivate is called and everything // works fine, but if we tab from address bar onto the view, that's when // the stuff gets broken. OnActivate(SVUIA_ACTIVATE_FOCUS); _cFrame._UIActivateIO(TRUE, &msg); } } else { CallCB(SFVM_SETFOCUS, 0, 0); if (_hwndListview) SetFocus(_hwndListview); if (!_IsDesktop()) { _cFrame._uState = SVUIA_ACTIVATE_FOCUS; } } } LRESULT CALLBACK CDefView::s_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lres = 0; CDefView * pThis; ULONG_PTR cookie = 0; if (WM_NCCREATE == uMsg) { pThis = (CDefView*)((LPCREATESTRUCT)lParam)->lpCreateParams; if (pThis) { pThis->AddRef(); SetWindowLongPtr(hWnd, 0, (LONG_PTR)pThis); } } else { pThis = (CDefView*)GetWindowLongPtr(hWnd, 0); } // FUSION: When defview calls out to 3rd party code we want it to use // the process default context. This means that the 3rd party code will get // v5 in the explorer process. However, if shell32 is hosted in a v6 process, // then the 3rd party code will still get v6. // Future enhancements to this codepath may include using the fusion manifest // tab which basically surplants the activat(null) in the following // codepath. This disables the automatic activation from user32 for the duration // of this wndproc, essentially doing this null push. ActivateActCtx(NULL, &cookie); // we need to use a __try{}__finally{} block here to make sure that we de-activate // the activation context. Fusion pushes and pops these off a stack and if we leave one // around we will basically infect this thread with the wrong context. __try { if (pThis) { lres = pThis->WndProc(hWnd, uMsg, wParam, lParam); } else { lres = DefWindowProc(hWnd, uMsg, wParam, lParam); } } __finally { if (cookie != 0) { DeactivateActCtx(0, cookie); } } return lres; } BOOL CDefView::_OnAppCommand(UINT cmd, UINT uDevice, DWORD dwKeys) { BOOL bHandled = FALSE; switch (cmd) { case APPCOMMAND_MEDIA_PLAY_PAUSE: if (S_OK == _InvokeContextMenuVerbOnSelection("play", 0, 0)) bHandled = TRUE; break; } return bHandled; } HRESULT CDefView::_ForwardMenuMessages(DWORD dwID, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult, BOOL* pfHandled) { if (InRange(dwID, SFVIDM_BACK_CONTEXT_FIRST, SFVIDM_BACK_CONTEXT_LAST)) { if (pfHandled) *pfHandled = TRUE; return SHForwardContextMenuMsg(_pcmContextMenuPopup, uMsg, wParam, lParam, plResult, TRUE); } else if (InRange(dwID, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST)) { if (pfHandled) *pfHandled = TRUE; return SHForwardContextMenuMsg(_pcmFile, uMsg, wParam, lParam, plResult, TRUE); } if (pfHandled) *pfHandled = FALSE; return E_FAIL; } LRESULT CDefView::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT l; DWORD dwID; switch (uMsg) { // IShellBrowser forwards these to the IShellView. // Dochost also forwards them down to the IOleObject, so we should do it too... case WM_SYSCOLORCHANGE: { HDITEM hdi = {HDI_FORMAT, 0, NULL, NULL, 0, 0, 0, 0, 0}; HWND hwndHead = ListView_GetHeader(_hwndListview); // We only want to update the sort arrows if they are already present. if (hwndHead) { Header_GetItem(hwndHead, _vs._lParamSort, &hdi); if (hdi.fmt & HDF_BITMAP) _SetSortFeedback(); } // fall through } case WM_WININICHANGE: _sizeThumbnail.cx = -1; // fall through case WM_ENTERSIZEMOVE: case WM_EXITSIZEMOVE: case WM_FONTCHANGE: if (_cFrame.IsWebView()) { HWND hwndExt = _cFrame.GetExtendedViewWindow(); if (hwndExt) { SendMessage(hwndExt, uMsg, wParam, lParam); } } break; } switch (uMsg) { case WM_DESTROY: if (GetKeyState(VK_CONTROL) < 0) _SetDefaultViewSettings(); // Dont need our web view data any more _FreeWebViewContentData(); // We don't flush these on WM_EXITMENULOOP any more, so do it here IUnknown_SetSite(_pcmFile, NULL); ATOMICRELEASE(_pcmFile); EmptyBkgrndThread(_pScheduler); ATOMICRELEASE(_pScheduler); // do this after our task scheduler is gone, since one of it's // items may be on the background task scheduler (or DUI may be // talking to it on the background) and it may need it's site chain. IUnknown_SetSite(_cCallback.GetSFVCB(), NULL); if (_pDiskCache) { // at this point we assume that we have no lock, _pDiskCache->Close(NULL); ATOMICRELEASE(_pDiskCache); } // Depending on when it is closed we may have an outstanding post // to us about the rest of the fill data which we should try to // process in order to keep from leaking stuff... // logically hWnd == _hwndView, but we already zeroed // _hwndView so use hWnd _ClearPostedMsgs(hWnd); // // remove ourself as a clipboard viewer // if (_bClipViewer) { ChangeClipboardChain(hWnd, _hwndNextViewer); _bClipViewer = FALSE; _hwndNextViewer = NULL; } if (_uRegister) { ULONG uRegister = _uRegister; _uRegister = 0; SHChangeNotifyDeregister(uRegister); } ATOMICRELEASE(_psd); ATOMICRELEASE(_pdtgtBack); if (_hwndListview) { if (_IsDesktop()) // only the desktop can have a combined view (e.g. Active Desktop) { EnableCombinedView(this, FALSE); } if (_bRegisteredDragDrop) RevokeDragDrop(_hwndListview); } SetAutomationObject(NULL); // cleanup refs we may be holding if (IsWindow(_hwndInfotip)) { DestroyWindow(_hwndInfotip); _hwndInfotip = NULL; } break; case WM_CREATE: return _OnCreate(hWnd); case WM_DSV_DELAYED_DESTROYWND: DestroyWindow(hWnd); break; case WM_NCDESTROY: _hwndView = NULL; SetWindowLongPtr(hWnd, 0, 0); // get rid of extra junk in the icon cache IconCacheFlush(FALSE); if (_pDUIView) { // // We must uninitialize DUser prior to releasing // _pDUIView so that all DUser gadgets are properly destroyed. // We used to call DirectUI::UnInitThread() in the CDUIView dtor. // However, since both CDefView and the various 'task' DUI // elements maintain a ref to CDUIView, we got into scenarios where // one of the 'task' elements held the final ref to CDUIView. That // resulted in the destruction of that 'task' element causing // uninitialization of DUser in the middle of a DUser call stack. // That's bad. // Uninitializing DUser here causes DUser to handle all pending // messages and destroy all it's gadgets on it's own terms. // _pDUIView->UnInitializeDirectUI(); _pDUIView->Release(); _pDUIView = NULL; } // release our reference generated during WM_NCCREATE in static wndproc Release(); break; case WM_ENABLE: _fDisabled = !wParam; break; case WM_ERASEBKGND: { COLORREF cr = ListView_GetBkColor(_hwndListview); if (cr == CLR_NONE) return SendMessage(_hwndMain, uMsg, wParam, lParam); //Turning On EraseBkgnd. This is required so as to avoid the //painting issue - when the listview is not visible and //invalidation occurs. HBRUSH hbr = CreateSolidBrush(cr); RECT rc; GetClientRect(hWnd, &rc); FillRect((HDC)wParam, &rc, hbr); DeleteObject(hbr); } // We want to reduce flash return 1; case WM_PAINT: if (_fEnumFailed) _PaintErrMsg(hWnd); else goto DoDefWndProc; break; case WM_LBUTTONUP: if (_fEnumFailed) PostMessage(hWnd, WM_KEYDOWN, (WPARAM)VK_F5, 0); else goto DoDefWndProc; break; case WM_SETFOCUS: if (!_fDestroying) // Ignore if we are destroying _hwndView. { _SetFocus(); } break; case WM_MOUSEACTIVATE: // // this keeps our window from coming to the front on button down // instead, we activate the window on the up click // if (LOWORD(lParam) != HTCLIENT) goto DoDefWndProc; LV_HITTESTINFO lvhti; GetCursorPos(&lvhti.pt); ScreenToClient(_hwndListview, &lvhti.pt); ListView_HitTest(_hwndListview, &lvhti); if (lvhti.iItem != -1 && lvhti.flags & LVHT_ONITEM) return MA_NOACTIVATE; else return MA_ACTIVATE; case WM_ACTIVATE: // force update on inactive to not ruin save bits if (wParam == WA_INACTIVE) UpdateWindow(_hwndListview); // if active view created, call active object to allow it to visualize activation. if (_cFrame._pActive) _cFrame._pActive->OnFrameWindowActivate((BOOL)wParam); break; case WM_SIZE: return WndSize(hWnd); case WM_NOTIFY: { #ifdef DEBUG // DefView_OnNotify sometimes destroys the pnm, so we need to save // the code while we can. (E.g., common dialog single-click activate. // LVN_ITEMACTIVATE causes us to dismiss the common dialog, which // does a DestroyViewWindow, which destroys the ListView // which destroys the NMHDR!) UINT code = ((NMHDR *)lParam)->code; #endif AddRef(); // just in case l = _OnNotify((NMHDR *)lParam); Release(); // release return l; } case WM_CONTEXTMENU: if (!_fDisabled) { if (lParam != (LPARAM) -1) { _bMouseMenu = TRUE; _ptDragAnchor.x = GET_X_LPARAM(lParam); _ptDragAnchor.y = GET_Y_LPARAM(lParam); LVUtil_ScreenToLV(_hwndListview, &_ptDragAnchor); } // Note: in deview inside a defview we can have problems of the // parent destroying us when we change views, so we better addref/release // around this... AddRef(); _bContextMenuMode = TRUE; ContextMenu((DWORD) lParam); _bContextMenuMode = FALSE; _bMouseMenu = FALSE; Release(); } break; case WM_COMMAND: return _OnCommand(NULL, wParam, lParam); case WM_APPCOMMAND: if (!_OnAppCommand(GET_APPCOMMAND_LPARAM(lParam), GET_DEVICE_LPARAM(lParam), GET_KEYSTATE_LPARAM(lParam))) goto DoDefWndProc; break; case WM_DSV_DISABLEACTIVEDESKTOP: DisableActiveDesktop(); break; case WM_DSV_DELAYWINDOWCREATE: CallCB(SFVM_DELAYWINDOWCREATE, (WPARAM)_hwndView, 0); break; case WM_DSV_BACKGROUNDENUMDONE: // Make sure this notify is from our enumeration task (it could be from a previous one) if (lParam == _dwEnumId) _OnBackgroundEnumDone(); break; case WM_DSV_GROUPINGDONE: _OnCategoryTaskDone(); break; case WM_DSV_FILELISTENUMDONE: _OnEnumDoneMessage(); break; case WM_DSV_FILELISTFILLDONE: _ShowSearchUI(FALSE); break; case WM_DSV_UPDATETHUMBNAIL: { DSV_UPDATETHUMBNAIL* putn = (DSV_UPDATETHUMBNAIL*)lParam; if (_IsImageMode()) // some messages may come in after the view mode is changed. { _UpdateThumbnail(putn->iItem, putn->iImage, putn->pidl); } _CleanupUpdateThumbnail(putn); } break; case WM_DSV_POSTCREATEINFOTIP: _OnPostCreateInfotip((TOOLINFO *)wParam, lParam); break; case WM_DSV_FSNOTIFY: { LPITEMIDLIST *ppidl; LONG lEvent; LPSHChangeNotificationLock pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent); if (pshcnl) { if (_fDisabled || (CallCB(SFVM_FSNOTIFY, (WPARAM)ppidl, (LPARAM)lEvent) == S_FALSE)) { lParam = 0; } else { lParam = _OnFSNotify(lEvent, (LPCITEMIDLIST*)ppidl); } SHChangeNotification_Unlock(pshcnl); } } return lParam; // the background thread's callback will post this message to us // when it has finished extracting a icon in the background. // // wParam is PIDL // lParam is iIconIndex case WM_DSV_UPDATEICON: _UpdateIcon((LPITEMIDLIST)wParam, (UINT)lParam); break; case WM_DSV_UPDATECOLDATA: _UpdateColData((CBackgroundColInfo*)lParam); break; case WM_DSV_UPDATEOVERLAY: _UpdateOverlay((int)wParam, (int)lParam); break; case WM_DSV_SETIMPORTANTCOLUMNS: _SetImportantColumns((CBackgroundTileInfo*)lParam); break; case WM_DSV_SHOWDRAGIMAGE: return DAD_ShowDragImage((BOOL)lParam); case WM_DSV_DELAYSTATUSBARUPDATE: { HWND hwndStatus; LPWSTR pszStatus = (LPWSTR)lParam; if (_fBackgroundStatusTextValid) { _fBackgroundStatusTextValid = FALSE; // Now prepare the text and post it to the status bar window. _psb->GetControlWindow(FCW_STATUS, &hwndStatus); if (hwndStatus) { SendMessage(hwndStatus, SB_SETTEXT, (WPARAM)0, (LPARAM)pszStatus); } } LocalFree((void *)pszStatus); } break; case WM_DSV_DELAYINFOTIP: if ((CBackgroundInfoTip *)wParam == _pBackgroundInfoTip && _pBackgroundInfoTip->_fReady) { LRESULT lRet = SendMessage(_hwndListview, LVM_SETINFOTIP, NULL, (LPARAM)&_pBackgroundInfoTip->_lvSetInfoTip); ATOMICRELEASE(_pBackgroundInfoTip); return lRet; } break; case WM_DSV_ENSURE_COLUMNS_LOADED: if (!_fDestroying) { AddColumns(); return 1; } break; case GET_WM_CTLCOLOR_MSG(CTLCOLOR_STATIC): SetBkColor(GET_WM_CTLCOLOR_HDC(wParam, lParam, uMsg), GetSysColor(COLOR_WINDOW)); return (LRESULT)GetSysColorBrush(COLOR_WINDOW); case WM_DRAWCLIPBOARD: if (_hwndNextViewer != NULL) SendMessage(_hwndNextViewer, uMsg, wParam, lParam); if (_bClipViewer) return _OnClipboardChange(); break; case WM_CHANGECBCHAIN: if ((HWND)wParam == _hwndNextViewer) { _hwndNextViewer = (HWND)lParam; return TRUE; } if (_hwndNextViewer != NULL) return SendMessage(_hwndNextViewer, uMsg, wParam, lParam); break; case WM_WININICHANGE: _OnWinIniChange(wParam, (LPCTSTR)lParam); SendMessage(_hwndListview, uMsg, wParam, lParam); break; case WM_THEMECHANGED: PostMessage(_hwndView, WM_COMMAND, (WPARAM)SFVIDM_MISC_REFRESH, 0); break; case WM_SHELLNOTIFY: #define SHELLNOTIFY_SETDESKWALLPAPER 0x0004 if (wParam == SHELLNOTIFY_SETDESKWALLPAPER) { if (_IsDesktop()) { _fHasDeskWallPaper = (lParam != 0); _SetFolderColors(); } } break; // What we would like out of these menu messages: // WM_ENTERMENULOOP // WM_INITMENUPOPUP // for File.Edit.View...: handle ourselves (merge in _pcmFile etc) and forward to IShellFolderViewCB for init // for submenus or context menus: forward to whatever IContextMenu owns the popup // WM_INITMENUPOPUP for next menu, etc // WM_EXITMENULOOP // PostMessage(WM_DSV_MENUTERM) // WM_COMMAND comes in, if a menu item was selected // Forward to the correct object to handle // WM_DSV_MENUTERM // clean up File.Edit.View... (release _pcmFile etc), and forward to IShellFolderViewCB for cleanup // // From previous comments here, it sounds like we don't get proper WM_ENTERMENULOOP / WM_EXITMENULOOP. // I suspect this is a behavior change since Win95. (This probably happened when we changed // the browser's HMENU to our own custom menu bar implementation way back in IE4...) // // Previous code also posted WM_DSV_MENUTERM *twice* -- another relic from the Edit menu days... // // If we try to clean up on WM_EXITMENULOOP, then we'll free _pcmFile etc when // the File menu closes. This caused us problems when we tried to merge _pcmFile // into the Edit menu. (We should have used _pcmEdit and cleaned up on WM_UNINITMENUPOPUP.) // This is no longer a problem for defview, but it is a problem for the IShellFolderViewCB // which can merge into any of File.Edit.View... menus. (In fact, no code in the source tree // does anything on SFVM_EXITMENULOOP.) // // We could free up _pcmFile early (when the File menu goes away) if we want, // but there doesn't seem to be any harm in letting it sit around. // So rip out this unused WM_EXITMENULOOP/WM_DSVMENUTERM/_OnMenuTermination code. // case WM_INITMENU: _OnInitMenu(); break; case WM_INITMENUPOPUP: _OnInitMenuPopup((HMENU)wParam, LOWORD(lParam), HIWORD(lParam)); break; case WM_TIMER: KillTimer(hWnd, (UINT) wParam); // Ignore if we're in the middle of destroying the window if (_fDestroying) break; if (DV_IDTIMER_START_ANI == wParam) { if (_hwndStatic) { WCHAR szName[128]; HINSTANCE hinst; // used only by camera namespace; they have a const string < 128 ch if (S_OK != CallCB(SFVM_GETANIMATION, (WPARAM)&hinst, (LPARAM)szName)) { hinst = g_hinst; StrCpyNW(szName, L"#150", ARRAYSIZE(szName)); } HWND hAnimate = ::GetWindow (_hwndStatic, GW_CHILD); if (hAnimate) { // Animate_OpenEx() except we want the W version always SendMessage(hAnimate, ACM_OPENW, (WPARAM)hinst, (LPARAM)szName); } } } else if (DV_IDTIMER_BUFFERED_REFRESH == wParam) { if (_fRefreshBuffered) { _fRefreshBuffered = FALSE; PostMessage(_hwndView, WM_KEYDOWN, (WPARAM)VK_F5, 0); TraceMsg(TF_DEFVIEW, "Buffered Refresh timer causes actual refresh"); } } else if (DV_IDTIMER_NOTIFY_AUTOMATION_SELCHANGE == wParam) { _OnDelayedSelectionChange(); } else if (DV_IDTIMER_NOTIFY_AUTOMATION_CONTENTSCHANGED == wParam) { _OnDelayedContentsChanged(); } else if (DV_IDTIMER_DISKCACHE == wParam) { DWORD dwMode; if (_pDiskCache->GetMode(&dwMode) == S_OK && _pDiskCache->IsLocked() == S_FALSE) { // two seconds since last access, close the cache. _pDiskCache->Close(NULL); } if (_GetBackgroundTaskCount(TOID_NULL) == 0) { // there is nothing in the queue pending, so quit listening... KillTimer(hWnd, DV_IDTIMER_DISKCACHE); } break; } else if (DV_IDTIMER_SCROLL_TIMEOUT == wParam) { // Scroll timer expired. TraceMsg(TF_DEFVIEW, "SCROLL TIMEOUT"); _fScrolling = FALSE; // Now we send a paint to listview, so it will send us more requests for tileinformation // that we ignored during scrolling. if (_fRequestedTileDuringScroll) { InvalidateRect(_hwndListview, NULL, FALSE); } } else { ASSERT(FALSE); // nobody is handling this timer id! } break; case WM_SETCURSOR: if (_hwndStatic) { SetCursor(LoadCursor(NULL, IDC_WAIT)); return TRUE; } goto DoDefWndProc; case WM_DRAWITEM: #define lpdis ((LPDRAWITEMSTRUCT)lParam) dwID = lpdis->itemID; if (lpdis->CtlType != ODT_MENU) return 0; if (InRange(lpdis->itemID, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST) && HasCB()) { CallCB(SFVM_DRAWITEM, SFVIDM_CLIENT_FIRST, lParam); return 1; } else { LRESULT lResult = 0; _ForwardMenuMessages(dwID, uMsg, wParam, lParam, &lResult, NULL); return lResult; } #undef lpdis case WM_MEASUREITEM: #define lpmis ((LPMEASUREITEMSTRUCT)lParam) dwID = lpmis->itemID; if (lpmis->CtlType != ODT_MENU) return 0; if (InRange(lpmis->itemID, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST) && HasCB()) { CallCB(SFVM_MEASUREITEM, SFVIDM_CLIENT_FIRST, lParam); return 1; } else { LRESULT lResult = 0; _ForwardMenuMessages(dwID, uMsg, wParam, lParam, &lResult, NULL); return lResult; } case WM_MENUCHAR: if (_pcmFile) { LRESULT lResult; HRESULT hr = SHForwardContextMenuMsg(_pcmFile, uMsg, wParam, lParam, &lResult, FALSE); if (hr == S_OK) return lResult; } if (_pcmContextMenuPopup) { LRESULT lResult; HRESULT hr = SHForwardContextMenuMsg(_pcmContextMenuPopup, uMsg, wParam, lParam, &lResult, FALSE); if (hr == S_OK) return lResult; } return MAKELONG(0, MNC_IGNORE); // there are two possible ways to put help texts in the // status bar, (1) processing WM_MENUSELECT or (2) handling MenuHelp // messages. (1) is compatible with OLE, but (2) is required anyway // for tooltips. // case WM_MENUSELECT: _OnMenuSelect(GET_WM_MENUSELECT_CMD(wParam, lParam), GET_WM_MENUSELECT_FLAGS(wParam, lParam), GET_WM_MENUSELECT_HMENU(wParam, lParam)); break; case WM_SYSCOLORCHANGE: _SetFolderColors(); SendMessage(_hwndListview, uMsg, wParam, lParam); _rgbBackColor = CLR_INVALID; break; case SVM_SELECTITEM: SelectItem((LPCITEMIDLIST)lParam, (int) wParam); break; case SVM_SELECTANDPOSITIONITEM: { SFM_SAP * psap = (SFM_SAP*)lParam; for (UINT i = 0; i < wParam; psap++, i++) SelectAndPositionItem(psap->pidl, psap->uSelectFlags, psap->fMove ? &psap->pt : NULL); break; } case WM_PALETTECHANGED: if (_IsImageMode()) { InvalidateRect(_hwndListview, NULL, FALSE); return TRUE; } // else Fall Through case WM_QUERYNEWPALETTE: if (_IsImageMode()) { return FALSE; // Let Browser handle palette management } else { HWND hwndT = GetChildViewWindow(); if (!hwndT) goto DoDefWndProc; return SendMessage(hwndT, uMsg, wParam, lParam); } case WM_DSV_REARRANGELISTVIEW: _ShowAndActivate(); break; case WM_DSV_SENDSELECTIONCHANGED: _OnSelectionChanged(); break; case WM_DSV_SENDNOITEMSTATECHANGED: _OnNoItemStateChanged(); break; case WM_DSV_DESKHTML_CHANGES: if (_IsDesktop()) { IADesktopP2 *piadp2; if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ActiveDesktop, NULL, IID_PPV_ARG(IADesktopP2, &piadp2)))) { IActiveDesktopP *piadpp; // 98/11/23 #254482 vtan: When making changes using dynamic // HTML don't forget to update the "desktop.htt" file so // that it's in sync with the registry BEFORE using DHTML. if (SUCCEEDED(piadp2->QueryInterface(IID_PPV_ARG(IActiveDesktopP, &piadpp)))) { piadpp->EnsureUpdateHTML(); // ignore result piadpp->Release(); } piadp2->MakeDynamicChanges(_cFrame._pOleObj); piadp2->Release(); } } break; // Toggling the New Start Menu on/off causes My Computer, etc. // desktop icons to dynamically hide/show themselves. case WM_DSV_STARTPAGE_TURNONOFF: _ReloadContent(FALSE); break; case WM_DSV_ADJUSTRECYCLEBINPOSITION: { // We need to move the recycle bin to it's default position. POINT ptRecycleBin; int iIndexRecycleBin = _FreezeRecycleBin(&ptRecycleBin); if (iIndexRecycleBin != LV_NOFROZENITEM) _SetRecycleBinInDefaultPosition(&ptRecycleBin); } break; default: DoDefWndProc: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } // don't test the result as this will fail on the second call void CDefView::_RegisterWindow(void) { WNDCLASS wc = {0}; // don't want vredraw and hredraw because that causes horrible // flicker expecially with full drag wc.style = CS_PARENTDC; wc.lpfnWndProc = CDefView::s_WndProc; wc.cbWndExtra = sizeof(CDefView *); wc.hInstance = HINST_THISDLL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = TEXT("SHELLDLL_DefView"); RegisterClass(&wc); } CDefView::~CDefView() { _uState = SVUIA_DEACTIVATE; // Sanity check. ASSERT(_tlistPendingInfotips.GetHeadPosition() == NULL); DebugMsg(TF_LIFE, TEXT("dtor CDefView %x"), this); // // Just in case, there is a left over. // _dvdt.LeaveAndReleaseData(); // // We need to give it a chance to clean up. // CallCB(SFVM_PRERELEASE, 0, 0); DestroyViewWindow(); ATOMICRELEASE(_pSelectionShellItemArray); ATOMICRELEASE(_pFolderShellItemArray); ATOMICRELEASE(_pScheduler); // // We should release _psb after _pshf (for docfindx) // ATOMICRELEASE(_pshf); ATOMICRELEASE(_pshf2); ATOMICRELEASE(_pshfParent); ATOMICRELEASE(_pshf2Parent); ILFree(_pidlRelative); ATOMICRELEASE(_psi); ATOMICRELEASE(_psio); ATOMICRELEASE(_pcdb); ATOMICRELEASE(_psb); ATOMICRELEASE(_psd); IUnknown_SetSite(_pcmFile, NULL); ATOMICRELEASE(_pcmFile); ATOMICRELEASE(_pcat); ATOMICRELEASE(_pImageCache); ATOMICRELEASE(_pDiskCache); DSA_Destroy(_hdaCategories); DSA_Destroy(_hdsaSCIDCache); // NOTE we dont release psvOuter // it has a ref on us if (_pbtn) LocalFree(_pbtn); // // Cleanup _dvdt // _dvdt.ReleaseDataObject(); _dvdt.ReleaseCurrentDropTarget(); _ClearPendingSelectedItems(); ATOMICRELEASE(_pauto); ATOMICRELEASE(_padvise); if (_hmenuCur) { DestroyMenu(_hmenuCur); } ATOMICRELEASE(_pBackgroundInfoTip); ATOMICRELEASE(_ppui); if (_pidlSelectAndPosition) ILFree(_pidlSelectAndPosition); Str_SetPtr(&_pszLegacyWatermark, NULL); if (_hdpaGroupingListActive) DPA_Destroy(_hdpaGroupingListActive); if (_hdpaGroupingListBackup) DPA_Destroy(_hdpaGroupingListBackup); } HRESULT CDefView::_AddTask(IRunnableTask *pTask, REFTASKOWNERID rTID, DWORD_PTR lParam, DWORD dwPriority, DWORD grfFlags) { HRESULT hr = E_FAIL; if (_pScheduler) { if (grfFlags & ADDTASK_ONLYONCE) { hr = _pScheduler->MoveTask(rTID, lParam, dwPriority, (grfFlags & ADDTASK_ATFRONT ? ITSSFLAG_TASK_PLACEINFRONT : ITSSFLAG_TASK_PLACEINBACK)); } if (hr != S_OK) // If we didn't move it, add it { hr = _pScheduler->AddTask2(pTask, rTID, lParam, dwPriority, (grfFlags & ADDTASK_ATFRONT ? ITSSFLAG_TASK_PLACEINFRONT : ITSSFLAG_TASK_PLACEINBACK)); } } return hr; } // Get the number of running tasks of the indicated task ID. UINT CDefView::_GetBackgroundTaskCount(REFTASKOWNERID rtid) { return _pScheduler ? _pScheduler->CountTasks(rtid) : 0; } const TBBUTTON c_tbDefView[] = { { VIEW_MOVETO | IN_VIEW_BMP, SFVIDM_EDIT_MOVETO, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { VIEW_COPYTO | IN_VIEW_BMP, SFVIDM_EDIT_COPYTO, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_DELETE | IN_STD_BMP, SFVIDM_FILE_DELETE, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_UNDO | IN_STD_BMP, SFVIDM_EDIT_UNDO, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { 0, 0, TBSTATE_ENABLED, BTNS_SEP, {0,0}, 0, -1 }, { VIEW_VIEWMENU | IN_VIEW_BMP, SFVIDM_VIEW_VIEWMENU, TBSTATE_ENABLED, BTNS_WHOLEDROPDOWN, {0,0}, 0, -1}, // hidden buttons (off by default, available only via customize dialog) { STD_PROPERTIES | IN_STD_BMP, SFVIDM_FILE_PROPERTIES, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_CUT | IN_STD_BMP, SFVIDM_EDIT_CUT, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_COPY | IN_STD_BMP, SFVIDM_EDIT_COPY, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_PASTE | IN_STD_BMP, SFVIDM_EDIT_PASTE, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { VIEW_OPTIONS | IN_VIEW_BMP, SFVIDM_TOOL_OPTIONS, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, }; const TBBUTTON c_tbDefViewWebView[] = { //{ 0, 0, TBSTATE_ENABLED, BTNS_SEP, {0,0}, 0, -1 }, { VIEW_VIEWMENU | IN_VIEW_BMP, SFVIDM_VIEW_VIEWMENU, TBSTATE_ENABLED, BTNS_WHOLEDROPDOWN, {0,0}, 0, -1}, // hidden buttons (off by default, available only via customize dialog) { VIEW_MOVETO | IN_VIEW_BMP, SFVIDM_EDIT_MOVETO, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { VIEW_COPYTO | IN_VIEW_BMP, SFVIDM_EDIT_COPYTO, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_DELETE | IN_STD_BMP, SFVIDM_FILE_DELETE, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_UNDO | IN_STD_BMP, SFVIDM_EDIT_UNDO, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_PROPERTIES | IN_STD_BMP, SFVIDM_FILE_PROPERTIES, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_CUT | IN_STD_BMP, SFVIDM_EDIT_CUT, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_COPY | IN_STD_BMP, SFVIDM_EDIT_COPY, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_PASTE | IN_STD_BMP, SFVIDM_EDIT_PASTE, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { VIEW_OPTIONS | IN_VIEW_BMP, SFVIDM_TOOL_OPTIONS, TBSTATE_HIDDEN | TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, }; // win95 defview toolbar, used for corel apphack const TBBUTTON c_tbDefView95[] = { { STD_CUT | IN_STD_BMP, SFVIDM_EDIT_CUT, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_COPY | IN_STD_BMP, SFVIDM_EDIT_COPY, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_PASTE | IN_STD_BMP, SFVIDM_EDIT_PASTE, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { 0, 0, TBSTATE_ENABLED, BTNS_SEP, {0,0}, 0, -1 }, { STD_UNDO | IN_STD_BMP, SFVIDM_EDIT_UNDO, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { 0, 0, TBSTATE_ENABLED, BTNS_SEP, {0,0}, 0, -1 }, { STD_DELETE | IN_STD_BMP, SFVIDM_FILE_DELETE, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { STD_PROPERTIES | IN_STD_BMP, SFVIDM_FILE_PROPERTIES, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1}, { 0, 0, TBSTATE_ENABLED, BTNS_SEP, {0,0}, 0, -1 }, // the bitmap indexes here are relative to the view bitmap { VIEW_LARGEICONS | IN_VIEW_BMP, SFVIDM_VIEW_ICON, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1 }, { VIEW_SMALLICONS | IN_VIEW_BMP, SFVIDM_VIEW_SMALLICON, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1 }, { VIEW_LIST | IN_VIEW_BMP, SFVIDM_VIEW_LIST, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1 }, { VIEW_DETAILS | IN_VIEW_BMP, SFVIDM_VIEW_DETAILS, TBSTATE_ENABLED, BTNS_BUTTON, {0,0}, 0, -1 }, }; LRESULT CDefView::_TBNotify(NMHDR *pnm) { LPTBNOTIFY ptbn = (LPTBNOTIFY)pnm; switch (pnm->code) { case TBN_BEGINDRAG: _OnMenuSelect(ptbn->iItem, 0, 0); break; } return 0; } BOOL CDefView::_MergeIExplorerToolbar(UINT cExtButtons) { BOOL fRet = FALSE; IExplorerToolbar *piet; if (SUCCEEDED(IUnknown_QueryService(_psb, SID_SExplorerToolbar, IID_PPV_ARG(IExplorerToolbar, &piet)))) { BOOL fGotClsid = TRUE; DWORD dwFlags = 0; if (cExtButtons == 0) { // This shf has no buttons to merge in; use the standard defview // clsid so that the shf shares standard toolbar customization. _clsid = CGID_DefViewFrame; } else if (SUCCEEDED(IUnknown_GetClassID(_pshf, &_clsid))) { // This shf has buttons to merge in; use its clsid // so that this shf gets separate customization persistence. // The shf might expect us to provide room for two lines of // text (since that was the default in IE4). dwFlags |= VBF_TWOLINESTEXT; } else { // This shf has buttons to merge in but doesn't implement // IPersist::GetClassID; so we can't use IExplorerToolbar mechanism. fGotClsid = FALSE; } if (fGotClsid) { HRESULT hr = piet->SetCommandTarget((IUnknown *)SAFECAST(this, IOleCommandTarget *), &_clsid, dwFlags); if (SUCCEEDED(hr)) { // If hr == S_FALSE, another defview merged in its buttons under the // same clsid, and they're still there. So no need to call AddButtons. if (hr != S_FALSE) hr = piet->AddButtons(&_clsid, _cButtons, _pbtn); if (SUCCEEDED(hr)) { fRet = TRUE; } } } piet->Release(); } return fRet; } int _FirstHiddenButton(TBBUTTON* ptbn, int cButtons) { for (int i = 0; i < cButtons; i++) { if (ptbn[i].fsState & TBSTATE_HIDDEN) break; } return i; } void CDefView::_CopyDefViewButton(PTBBUTTON ptbbDest, PTBBUTTON ptbbSrc) { *ptbbDest = *ptbbSrc; if (!(ptbbDest->fsStyle & BTNS_SEP)) { // Fix up bitmap offset depending on whether this is a "view" bitmap or a "standard" bitmap if (ptbbDest->iBitmap & IN_VIEW_BMP) ptbbDest->iBitmap = (int)((ptbbDest->iBitmap & ~PRIVATE_TB_FLAGS) + _iViewBMOffset); else ptbbDest->iBitmap = (int)(ptbbDest->iBitmap + _iStdBMOffset); } } // // Here's the deal with _GetButtons // // DefView has some buttons, and its callback client may have some buttons. // // Some of defview's buttons are visible on the toolbar by default, and some only show // up if you customize the toolbar. // // We specify which buttons are hidden by default by marking them with TBSTATE_HIDDEN in // the declaration of c_tbDefView. We assume all such buttons are in a continuous block at // the end of c_tbDefView. // // We return in ppbtn a pointer to an array of all the buttons, including those not shown // by default. We put the buttons not shown by default at the end of this array. We pass // back in pcButtons the count of visible buttons, and in pcTotalButtons the count of visible // and hidden buttons. // // The int return value is the number of client buttons in the array. // int CDefView::_GetButtons(PTBBUTTON* ppbtn, LPINT pcButtons, LPINT pcTotalButtons) { int cVisibleBtns = 0; // count of visible defview + client buttons TBINFO tbinfo; tbinfo.uFlags = TBIF_APPEND; tbinfo.cbuttons = 0; // Does the client want to prepend/append a toolbar? CallCB(SFVM_GETBUTTONINFO, 0, (LPARAM)&tbinfo); _uDefToolbar = HIWORD(tbinfo.uFlags); tbinfo.uFlags &= 0xffff; // tbDefView needs to be big enough to hold either c_tbDefView or c_tbDefView95 COMPILETIME_ASSERT(ARRAYSIZE(c_tbDefView95) >= ARRAYSIZE(c_tbDefView)); COMPILETIME_ASSERT(ARRAYSIZE(c_tbDefView95) >= ARRAYSIZE(c_tbDefViewWebView)); TBBUTTON tbDefView[ARRAYSIZE(c_tbDefView95)]; int cDefViewBtns; // total count of defview buttons if (SHGetAppCompatFlags(ACF_WIN95DEFVIEW) & ACF_WIN95DEFVIEW) { memcpy(tbDefView, c_tbDefView95, sizeof(TBBUTTON) * ARRAYSIZE(c_tbDefView95)); cDefViewBtns = ARRAYSIZE(c_tbDefView95); } else if (_cFrame.IsWebView() || _pDUIView) { memcpy(tbDefView, c_tbDefViewWebView, sizeof(TBBUTTON) * ARRAYSIZE(c_tbDefViewWebView)); cDefViewBtns = ARRAYSIZE(c_tbDefViewWebView); } else { memcpy(tbDefView, c_tbDefView, sizeof(TBBUTTON) * ARRAYSIZE(c_tbDefView)); cDefViewBtns = ARRAYSIZE(c_tbDefView); } int cVisibleDefViewBtns = _FirstHiddenButton(tbDefView, cDefViewBtns); // count of visible defview buttons TBBUTTON *pbtn = (TBBUTTON *)LocalAlloc(LPTR, (cDefViewBtns + tbinfo.cbuttons) * sizeof(*pbtn)); if (pbtn) { int iStart = 0; cVisibleBtns = tbinfo.cbuttons + cVisibleDefViewBtns; // Have the client fill in its buttons switch (tbinfo.uFlags) { case TBIF_PREPEND: CallCB(SFVM_GETBUTTONS, MAKEWPARAM(SFVIDM_CLIENT_FIRST, tbinfo.cbuttons), (LPARAM)pbtn); iStart = tbinfo.cbuttons; break; case TBIF_APPEND: CallCB(SFVM_GETBUTTONS, MAKEWPARAM(SFVIDM_CLIENT_FIRST, tbinfo.cbuttons), (LPARAM)&pbtn[cVisibleDefViewBtns]); iStart = 0; break; case TBIF_REPLACE: CallCB(SFVM_GETBUTTONS, MAKEWPARAM(SFVIDM_CLIENT_FIRST, tbinfo.cbuttons), (LPARAM)pbtn); cVisibleBtns = tbinfo.cbuttons; cVisibleDefViewBtns = 0; break; default: RIPMSG(0, "View callback passed an invalid TBINFO flag"); break; } // Fill in visible defview buttons for (int i = 0; i < cVisibleDefViewBtns; i++) { // Visible defview button block gets added at iStart _CopyDefViewButton(&pbtn[i + iStart], &tbDefView[i]); } // Fill in hidden defview buttons for (i = cVisibleDefViewBtns; i < cDefViewBtns; i++) { // Hidden defview button block gets added after visible & client buttons _CopyDefViewButton(&pbtn[i + tbinfo.cbuttons], &tbDefView[i]); // If this rips a visible button got mixed in with the hidden block ASSERT(pbtn[i + tbinfo.cbuttons].fsState & TBSTATE_HIDDEN); // Rip off the hidden bit pbtn[i + tbinfo.cbuttons].fsState &= ~TBSTATE_HIDDEN; } } ASSERT(ppbtn); ASSERT(pcButtons); ASSERT(pcTotalButtons); *ppbtn = pbtn; *pcButtons = cVisibleBtns; *pcTotalButtons = tbinfo.cbuttons + cDefViewBtns; return tbinfo.cbuttons; } void CDefView::MergeToolBar(BOOL bCanRestore) { TBADDBITMAP ab; ab.hInst = HINST_COMMCTRL; // hinstCommctrl ab.nID = IDB_STD_SMALL_COLOR; // std bitmaps _psb->SendControlMsg(FCW_TOOLBAR, TB_ADDBITMAP, 8, (LPARAM)&ab, &_iStdBMOffset); ab.nID = IDB_VIEW_SMALL_COLOR; // std view bitmaps _psb->SendControlMsg(FCW_TOOLBAR, TB_ADDBITMAP, 8, (LPARAM)&ab, &_iViewBMOffset); if (_pbtn) LocalFree(_pbtn); int cExtButtons = _GetButtons(&_pbtn, &_cButtons, &_cTotalButtons); if (_pbtn && !_MergeIExplorerToolbar(cExtButtons)) { // if we're able to do the new IExplorerToolbar merge method, great... // if not, we use the old style _psb->SetToolbarItems(_pbtn, _cButtons, FCT_MERGE); CDefView::CheckToolbar(); } } STDMETHODIMP CDefView::GetWindow(HWND *phwnd) { *phwnd = _hwndView; return S_OK; } STDMETHODIMP CDefView::ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; } STDMETHODIMP CDefView::EnableModeless(BOOL fEnable) { // We have no modeless window to be enabled/disabled return S_OK; } HRESULT CDefView::_ReloadListviewContent() { // HACK: We always call IsShared with fUpdateCache=FALSE for performance. // However, we need to update the cache when the user explicitly tell // us to "Refresh". This is not the ideal place to put this code, but // we have no other choice. TCHAR szPathAny[MAX_PATH]; _UpdateSelectionMode(); // finish any pending edits SendMessage(_hwndListview, LVM_EDITLABEL, (WPARAM)-1, 0); GetWindowsDirectory(szPathAny, ARRAYSIZE(szPathAny)); IsShared(szPathAny, TRUE); // HACK: strange way to notify folder that we're refreshing ULONG rgf = SFGAO_VALIDATE; _pshf->GetAttributesOf(0, NULL, &rgf); // // if a item is selected, make sure it gets nuked from the icon // cache, this is a last resort type thing, select a item and // hit F5 to fix all your problems. // int iItem = ListView_GetNextItem(_hwndListview, -1, LVNI_SELECTED); if (iItem != -1) CFSFolder_UpdateIcon(_pshf, _GetPIDL(iItem)); // We should not save the selection if doing refresh. _ClearPendingSelectedItems(); // 01/05/21 #399284: Don't save/restore the state and nuke objects if there's a background process using them if(!_bBkFilling) { // First we have to save all the icon positions, so they will be restored // properly during the FillObjectsShowHide SaveViewState(); // 99/04/07 #309965 vtan: Persist the view state (above). Make sure // our internal representation is the same as the one on the disk // by dumping our cache and reloading the information. GetViewState(); // To make it look like the refesh is doing something, clear // all the icons from the view before we start enumerating. _RemoveObject(NULL, FALSE); _fSyncOnFillDone = TRUE; // apply the just-saved view state when we finish enumeration } return FillObjectsShowHide(TRUE); } HRESULT CDefView::_ReloadContent(BOOL fForce) { if (_bReEntrantReload) { return S_FALSE; } _bReEntrantReload = TRUE; HRESULT hrExtView = S_OK; HRESULT hrNormalView = S_OK; SHELLSTATE ss; // Tell the defview client that this window is about to be refreshed _CallRefresh(TRUE); // make sure that the CommandIds and the Uids match by recreating the menus RecreateMenus(); // If the global SSF_WIN95CLASSIC state changed, we need to muck with the UI. SHGetSetSettings(&ss, SSF_WIN95CLASSIC, FALSE); // Show webview and pane again if we are forced OR the view has changed. if (fForce || (BOOLIFY(ss.fWin95Classic) != BOOLIFY(_fClassic))) { _fClassic = ss.fWin95Classic; _UpdateListviewColors(); } if (_ShouldShowWebView()) { // We need to save the icon positions before we refresh the view. SaveViewState(); if (_pDUIView) { hrExtView = _pDUIView->Refresh(); } else { _TryShowWebView(_fs.ViewMode, _fs.ViewMode); } } else { _TryHideWebView(); // make sure it's off } // We want to preserve the earlier error if any hrNormalView = _ReloadListviewContent(); _bReEntrantReload = FALSE; return FAILED(hrExtView) ? hrExtView : hrNormalView; } STDMETHODIMP CDefView::Refresh() { // See if some refreshes were buffered if (_fRefreshBuffered) { //Since we are refreshing it right now. Kill the timer. TraceMsg(TF_DEFVIEW, "Buffered Refresh Timer Killed by regular Refresh"); KillTimer(_hwndView, DV_IDTIMER_BUFFERED_REFRESH); _fRefreshBuffered = FALSE; } // If desktop is in modal state, do not attempt to refresh. // If we do, we endup destroying Trident object when it is in modal state. if (_IsDesktop() && _fDesktopModal) { // Remember that we could not refresh the desktop because it was in // a modal state. _fDesktopRefreshPending = TRUE; return S_OK; } // make sure we have the latest SHRefreshSettings(); _UpdateRegFlags(); if (_IsDesktop()) { SHELLSTATE ss = {0}; SHGetSetSettings(&ss, SSF_DESKTOPHTML, FALSE); // The following code is not needed because _ReloadContent() takes care of switching to // web-view. // _SwitchDesktopHTML(BOOLIFY(ss.fDesktopHTML)); if (ss.fDesktopHTML) { // For backward compatibility, hide the desktop channel bar. HideIE4DesktopChannelBar(); // ActiveDesktop is not part of shdocvw's browser session count // so when we refresh, we must tell wininet to reset the session // count otherwise we will not hit the net. MyInternetSetOption(NULL, INTERNET_OPTION_RESET_URLCACHE_SESSION, NULL, 0); } } return _ReloadContent(TRUE); } STDMETHODIMP CDefView::CreateViewWindow(IShellView *psvPrevious, LPCFOLDERSETTINGS pfs, IShellBrowser *psb, RECT *prc, HWND *phWnd) { SV2CVW2_PARAMS cParams = {0}; cParams.cbSize = sizeof(SV2CVW2_PARAMS); cParams.psvPrev = psvPrevious; cParams.pfs = pfs; cParams.psbOwner = psb; cParams.prcView = prc; HRESULT hr = CreateViewWindow2(&cParams); *phWnd = cParams.hwndView; if (SUCCEEDED(hr) && (SHGetAppCompatFlags(ACF_OLDCREATEVIEWWND) & ACF_OLDCREATEVIEWWND)) { // // CreateViewWindow was documented as returning S_OK on success, // but IE4 changed the function to return S_FALSE if the defview // was created async. // // PowerDesk relies on the old behavior. // So does Quattro Pro. // hr = S_OK; } return hr; } STDMETHODIMP CDefView::HandleRename(LPCITEMIDLIST pidl) { HRESULT hr = E_FAIL; // Gross, but if no PIDL passed in use the GetObject(-2) hack to get the selected object... // Don't need to free as it wsa not cloned... if (!pidl) { GetObject((LPITEMIDLIST*)&pidl, (UINT)-2); } else { RIP(ILFindLastID(pidl) == pidl); if (ILFindLastID(pidl) != pidl) { return E_INVALIDARG; } } hr = SelectAndPositionItem(pidl, SVSI_SELECT, NULL); if (SUCCEEDED(hr)) hr = SelectAndPositionItem(pidl, SVSI_EDIT, NULL); return hr; } // IViewObject HRESULT CDefView::GetColorSet(DWORD dwAspect, LONG lindex, void *pvAspect, DVTARGETDEVICE *ptd, HDC hicTargetDev, LOGPALETTE **ppColorSet) { if (_cFrame.IsWebView() && _cFrame._pvoActive) { return _cFrame._pvoActive->GetColorSet(dwAspect, lindex, pvAspect, ptd, hicTargetDev, ppColorSet); } if (ppColorSet) *ppColorSet = NULL; return E_FAIL; } HRESULT CDefView::Freeze(DWORD, LONG, void *, DWORD *pdwFreeze) { return E_NOTIMPL; } HRESULT CDefView::Unfreeze(DWORD) { return E_NOTIMPL; } HRESULT CDefView::SetAdvise(DWORD dwAspect, DWORD advf, IAdviseSink *pSink) { if (dwAspect != DVASPECT_CONTENT) return DV_E_DVASPECT; if (advf & ~(ADVF_PRIMEFIRST | ADVF_ONLYONCE)) return E_INVALIDARG; if (pSink != _padvise) { ATOMICRELEASE(_padvise); _padvise = pSink; if (_padvise) _padvise->AddRef(); } if (_padvise) { _advise_aspect = dwAspect; _advise_advf = advf; if (advf & ADVF_PRIMEFIRST) PropagateOnViewChange(dwAspect, -1); } else _advise_aspect = _advise_advf = 0; return S_OK; } HRESULT CDefView::GetAdvise(DWORD *pdwAspect, DWORD *padvf, IAdviseSink **ppSink) { if (pdwAspect) *pdwAspect = _advise_aspect; if (padvf) *padvf = _advise_advf; if (ppSink) { if (_padvise) _padvise->AddRef(); *ppSink = _padvise; } return S_OK; } HRESULT CDefView::Draw(DWORD, LONG, void *, DVTARGETDEVICE *, HDC, HDC, const RECTL *, const RECTL *, BOOL (*)(ULONG_PTR), ULONG_PTR) { return E_NOTIMPL; } void CDefView::PropagateOnViewChange(DWORD dwAspect, LONG lindex) { dwAspect &= _advise_aspect; if (dwAspect && _padvise) { IAdviseSink *pSink = _padvise; IUnknown *punkRelease; if (_advise_advf & ADVF_ONLYONCE) { punkRelease = pSink; _padvise = NULL; _advise_aspect = _advise_advf = 0; } else punkRelease = NULL; pSink->OnViewChange(dwAspect, lindex); ATOMICRELEASE(punkRelease); } } void CDefView::PropagateOnClose() { // // we aren't closing ourselves, just somebody under us... // ...reflect this up the chain as a view change. // if (_padvise) PropagateOnViewChange(_advise_aspect, -1); } UINT CDefView::_ValidateViewMode(UINT uViewMode) { UINT uViewModeDefault = FVM_ICON; if (uViewMode >= FVM_FIRST && uViewMode <= FVM_LAST) { uViewModeDefault = uViewMode; #ifdef DEBUG if (!_ViewSupported(uViewMode)) { // Whoa! the default is excluded? Ignore it. TraceMsg(TF_WARNING, "Bug in IShellFolderViewCB client: returned a default viewmode that is excluded"); } #endif } else { TraceMsg(TF_WARNING, "Bug in IShellFolderViewCB client: returned invalid viewmode"); } return uViewModeDefault; } UINT CDefView::_GetDefaultViewMode() { UINT uViewMode = (_IsDesktop() || !IsOS(OS_SERVERADMINUI)) ? FVM_ICON : FVM_DETAILS; CallCB(SFVM_DEFVIEWMODE, 0, (LPARAM)&uViewMode); return _ValidateViewMode(uViewMode); } void CDefView::_GetDeferredViewSettings(UINT* puViewMode) { SFVM_DEFERRED_VIEW_SETTINGS sdvsSettings; ZeroMemory(&sdvsSettings, sizeof(sdvsSettings)); if (SUCCEEDED(CallCB(SFVM_GETDEFERREDVIEWSETTINGS, 0, (LPARAM)&sdvsSettings))) { _vs._lParamSort = sdvsSettings.uSortCol; _vs._iDirection = sdvsSettings.iSortDirection >= 0 ? 1 : -1; *puViewMode = _ValidateViewMode(sdvsSettings.fvm); _fs.fFlags = (_fs.fFlags & ~FWF_AUTOARRANGE) | (sdvsSettings.fFlags & FWF_AUTOARRANGE); SHSetWindowBits(_hwndListview, GWL_STYLE, LVS_AUTOARRANGE, _IsAutoArrange() ? LVS_AUTOARRANGE : 0); if (sdvsSettings.fGroupView && (*puViewMode != FVM_THUMBSTRIP)) { SHCOLUMNID scid; if SUCCEEDED(_pshf2->MapColumnToSCID(sdvsSettings.uSortCol, &scid)) { _CategorizeOnGUID(&CLSID_DetailCategorizer, &scid); } } } else { *puViewMode = _GetDefaultViewMode(); } } BOOL CDefView::_ViewSupported(UINT uView) { SFVM_VIEW_DATA vi; _GetSFVMViewState(uView, &vi); BOOL fIncludeView; if (vi.dwOptions == SFVMQVI_INCLUDE) fIncludeView = TRUE; else if (vi.dwOptions == SFVMQVI_EXCLUDE) fIncludeView = FALSE; else fIncludeView = uView != FVM_THUMBSTRIP; // by default, everything is included except FVM_THUMBSTRIP return fIncludeView; } STDMETHODIMP CDefView::GetView(SHELLVIEWID* pvid, ULONG uView) { HRESULT hr; if ((int)uView >= 0) { // start with the first supported view UINT fvm = FVM_FIRST; while (fvm <= FVM_LAST && !_ViewSupported(fvm)) fvm++; // find fvm associated with index uView for (ULONG i = 0; fvm <= FVM_LAST && i < uView; fvm++, i++) { // skip unsupported views while (fvm <= FVM_LAST && !_ViewSupported(fvm)) fvm++; } if (fvm <= FVM_LAST) { hr = SVIDFromViewMode((FOLDERVIEWMODE)fvm, pvid); } else if (i == uView) { // enumerate the "default view" so the browser doesn't throw it out later *pvid = VID_DefaultView; hr = S_OK; } else { hr = E_INVALIDARG; } } else { // We're being asked about specific view info: switch (uView) { case SV2GV_CURRENTVIEW: hr = SVIDFromViewMode((FOLDERVIEWMODE)_fs.ViewMode, pvid); break; case SV2GV_DEFAULTVIEW: // tell the browser "default" so we can pick the right one later on *pvid = VID_DefaultView; hr = S_OK; break; default: hr = E_INVALIDARG; break; } } return hr; } // For Folder Advanced Options flags that we check often, it's better // to cache the values as flags. Update them here. void CDefView::_UpdateRegFlags() { DWORD dwValue, cbSize = sizeof(dwValue); if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), TEXT("ClassicViewState"), NULL, &dwValue, &cbSize) && dwValue) { _fWin95ViewState = TRUE; } else { _fWin95ViewState = FALSE; } } BOOL CDefView::_SetupNotifyData() { if (!_pidlMonitor && !_lFSEvents) { LPCITEMIDLIST pidl = NULL; LONG lEvents = 0; if (SUCCEEDED(CallCB(SFVM_GETNOTIFY, (WPARAM)&pidl, (LPARAM)&lEvents))) { _pidlMonitor = pidl; _lFSEvents = lEvents; } } return _pidlMonitor || _lFSEvents; } void CDefView::_ShowViewEarly() { // Show the window early (what old code did) SetWindowPos(_hwndView, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW); _OnMoveWindowToTop(_hwndView); UpdateWindow(_hwndView); } BOOL LV_FindWorkArea(RECT rcWorkAreas[], int nWorkAreas, POINT *ppt, int *piWorkArea) { for (int iWork = 0; iWork < nWorkAreas; iWork++) { if (PtInRect(&rcWorkAreas[iWork], *ppt)) { *piWorkArea = iWork; return TRUE; } } *piWorkArea = 0; // default case is the primary work area return FALSE; } void CDefView::_ClearItemPositions() { _fUserPositionedItems = FALSE; _vs.ClearPositionData(); } // // This function finds the Recycle bin icon and freezes it. It also freezes the bottom right corner // slot sothat no icon can occupy it. // int CDefView::_FreezeRecycleBin(POINT *ppt) { int iIndexRecycleBin = -1; if (_IsDesktop()) { LPITEMIDLIST pidlRecycleBin; if (SUCCEEDED(SHGetFolderLocation(NULL, CSIDL_BITBUCKET, NULL, 0, &pidlRecycleBin))) { //Find the index of the recycle bin in the listview. iIndexRecycleBin = _FindItem(pidlRecycleBin, NULL, FALSE); if (iIndexRecycleBin >= 0) //If we don't find recycle bin, we don't have anything to do! { //Freeze the recycle item (prevent it from moving) ListView_SetFrozenItem(_hwndListview, TRUE, iIndexRecycleBin); RECT rcItem; ListView_GetItemRect(_hwndListview, iIndexRecycleBin, &rcItem, LVIR_SELECTBOUNDS); //Get the ViewRect. RECT rcViewRect; int nWorkAreas = 0; //Get the number of work-areas ListView_GetNumberOfWorkAreas(_hwndListview, &nWorkAreas); if (nWorkAreas > 1) { ASSERT(nWorkAreas <= LV_MAX_WORKAREAS); if (nWorkAreas <= LV_MAX_WORKAREAS) // just make sure in retail { RECT rcWorkAreas[LV_MAX_WORKAREAS]; int iCurWorkArea = 0; //Get all the work areas! ListView_GetWorkAreas(_hwndListview, nWorkAreas, rcWorkAreas); //Find which work area the Recycle-bin currently lies. LV_FindWorkArea(rcWorkAreas, nWorkAreas, (LPPOINT)(&rcItem.left), &iCurWorkArea); CopyRect(&rcViewRect, &rcWorkAreas[iCurWorkArea]); } } else { ListView_GetViewRect(_hwndListview, &rcViewRect); } //Calculate the bottom-right corner of this slot POINT ptRecycleBin; ptRecycleBin.x = rcViewRect.right; ptRecycleBin.y = rcViewRect.bottom; //Freeze this slot sothat no other icon can occupy this. ListView_SetFrozenSlot(_hwndListview, TRUE, &ptRecycleBin); RECT rcIcon; ListView_GetItemRect(_hwndListview, iIndexRecycleBin, &rcIcon, LVIR_ICON); ppt->x = rcViewRect.right - RECTWIDTH(rcIcon) - (RECTWIDTH(rcItem) - RECTWIDTH(rcIcon))/2; ppt->y = rcViewRect.bottom - RECTHEIGHT(rcItem); } ILFree(pidlRecycleBin); } } return iIndexRecycleBin; } // // This function moves the RecycleBin item to the given location and then unfreezes the item and // the frozen slot. // void CDefView::_SetRecycleBinInDefaultPosition(POINT *ppt) { // If a sorting has happened since an item was frozen, the index of that item would have changed. // So, get the index of the recycle bin here. int iIndexRecycleBin = ListView_GetFrozenItem(_hwndListview); if (iIndexRecycleBin != LV_NOFROZENITEM) { //Move the recycle-bin icon to it's default position _SetItemPosition(iIndexRecycleBin, ppt->x, ppt->y); //Unfreeze the slot ListView_SetFrozenSlot(_hwndListview, FALSE, NULL); //FALSE ==> Unfreeze! //Unfreeze the recycle bin ListView_SetFrozenItem(_hwndListview, FALSE, 0); //FALSE ==> Unfreeze! //Since we repositioned recyclebin earlier, we need to save it in the registry. //Do we need this? // SaveViewState(); } } STDMETHODIMP CDefView::CreateViewWindow2(LPSV2CVW2_PARAMS pParams) { if (g_dwProfileCAP & 0x00000001) StopCAP(); if (pParams->cbSize < sizeof(SV2CVW2_PARAMS)) return E_INVALIDARG; pParams->hwndView = NULL; _RegisterWindow(); if (_hwndView || !pParams->psbOwner) return E_UNEXPECTED; DECLAREWAITCURSOR; SetWaitCursor(); // Need to leave this code as is. Previously, we had changed it to // pParams->psbOwner->QueryInterface(IID_PPV_ARG(IShellBrowser, &_psb)); // However, this breaks Corel Quattro Pro 8 in their filesave dialog. // They pass in some sort of dummy "stub" IShellBrowser. QI'ing it for IShellBrowser // will do nothing, and thus _psb will remain null, and we crash. Restoring it to // the old way, _psb will be their "stub", but still valid, IShellBrowser. // Look for other comments for "Quattro Pro" in this file to see why they pass // in this stub. // (do this before doing the GetWindowRect) _psb = pParams->psbOwner; _psb->AddRef(); ASSERT(_psb); // much of our code assumes this to be valid w/o checking #ifdef _X86_ // Verify that the CHijaakObjectWithSite is properly laid out COMPILETIME_ASSERT(FIELD_OFFSET(CDefView, _psfHijaak) + sizeof(_psfHijaak) == FIELD_OFFSET(CDefView, _psb)); #endif _fGlobeCanSpin = TRUE; _GlobeAnimation(TRUE); HRESULT hr; SHELLSTATE ss; // we will need these bits later on SHGetSetSettings(&ss, SSF_WIN95CLASSIC | SSF_DESKTOPHTML | SSF_WEBVIEW | SSF_STARTPANELON, FALSE); _pshf->QueryInterface(IID_PPV_ARG(IShellIcon, &_psi)); _pshf->QueryInterface(IID_PPV_ARG(IShellIconOverlay, &_psio)); pParams->psbOwner->QueryInterface(IID_PPV_ARG(ICommDlgBrowser, &_pcdb)); // listview starts out in large icon mode, we will switch to the proper view shortly _fs.ViewMode = FVM_ICON; // refetch FWF_ after browser supplied versions stomped our copy _fs.fFlags = pParams->pfs->fFlags & ~FWF_OWNERDATA; CallCB(SFVM_FOLDERSETTINGSFLAGS, 0, (LPARAM)&_fs.fFlags); // pvid takes precedence over pfs->ViewMode UINT fvm = pParams->pfs->ViewMode; if (pParams->pvid) { if (IsEqualIID(*pParams->pvid, VID_DefaultView)) fvm = FVM_LAST + 1; // not a real view -- we will pick after enumeration else ViewModeFromSVID(pParams->pvid, (FOLDERVIEWMODE *)&fvm); } // This should never fail _psb->GetWindow(&_hwndMain); ASSERT(IsWindow(_hwndMain)); CallCB(SFVM_HWNDMAIN, 0, (LPARAM)_hwndMain); // We need to restore the column widths and icon positions before showing the window if (!GetViewState()) { // Icon positions are not available; Therefore, it is a clean install // and we need to position recycle bin if this is Desktop. _fPositionRecycleBin = BOOLIFY(_IsDesktop()); } _fSyncOnFillDone = TRUE; // apply the just-loaded view state when we finish enumeration // if there was a previous view that we know about, update our column state if (_fWin95ViewState && pParams->psvPrev) { _vs.InitFromPreviousView(pParams->psvPrev); } _pEnumTask = new CDefviewEnumTask(this, ++_dwEnumId); if (_pEnumTask && CreateWindowEx(IS_WINDOW_RTL_MIRRORED(_hwndMain) ? dwExStyleRTLMirrorWnd : 0, TEXT("SHELLDLL_DefView"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_TABSTOP, pParams->prcView->left, pParams->prcView->top, pParams->prcView->right - pParams->prcView->left, pParams->prcView->bottom - pParams->prcView->top, _hwndMain, NULL, HINST_THISDLL, this)) { // See if they want to overwrite the selection object if (_fs.fFlags & FWF_OWNERDATA) { // Only used in owner data. ILVRange *plvr = NULL; CallCB(SFVM_GETODRANGEOBJECT, LVSR_SELECTION, (LPARAM)&plvr); if (plvr) { ListView_SetLVRangeObject(_hwndListview, LVSR_SELECTION, plvr); plvr->Release(); // We assume the lv will hold onto it... } plvr = NULL; CallCB(SFVM_GETODRANGEOBJECT, LVSR_CUT, (LPARAM)&plvr); if (plvr) { ListView_SetLVRangeObject(_hwndListview, LVSR_CUT, plvr); plvr->Release(); // We assume the lv will hold onto it... } } // This needs to be done before calling _BestFit (used to be in _FillObjects) // so that the parent can handle size changes effectively. pParams->hwndView = _hwndView; // Since ::FillObjects can take a while we force a paint now // before any items are added so we don't see the gray background of // the explorer window for a long time. // // We used to do this after determining "async-ness" of the view, which // required us to pick the webview template. We want to postpone that // decision so force the repaint in the same scenarios that we otherwise // would have (non-webview or desktop). // // Make an educated guess here, if we get it wrong, we fix it up below. // if (!_ShouldShowWebView() || _IsDesktop()) { _ShowViewEarly(); } // Try and fill the listview synchronously with view creation. // _fAllowSearchingWindow = TRUE; hr = _pEnumTask->FillObjectsToDPA(TRUE); if (SUCCEEDED(hr)) { // Setting the view mode has to happen after SFVM_ENUMERATEDITEMS // NOTE: this also AddColumns() if the new view requires them if (FVM_LAST + 1 == fvm) _GetDeferredViewSettings(&fvm); // Don't call SetCurrentViewMode since it clears position data and we may have read in // position data via GetViewState but haven't used it yet. Call _SwitchToViewFVM directly. hr = _SwitchToViewFVM(fvm, SWITCHTOVIEW_NOWEBVIEW); // The following bits depend on the result of _SwitchToViewFVM. // It returns the value from turning on web view, // this is used to determine async defview behavior (so we have // an answer to the SHDVID_CANACTIVATENOW question the browser // will soon ask us). // // Note: Desktop synchronous, even for web view // if (SUCCEEDED(hr) && _IsDesktop()) hr = S_OK; _fCanActivateNow = (S_OK == hr); // S_FALSE implies async waiting for ReadyStateInteractive _fIsAsyncDefView = !BOOLIFY(_fCanActivateNow); // needed in a separate bit since _fCanActivateNow changes // This has to happen after _SwitchToViewFVM so it can calculate // the correct size of the window _BestFit(); // Tell the defview client that this windows has been initialized // Note that this must come before _pEnumTask->FillObjectsDPAToDone() so that the status bar displays // (Disk Free space xxGB) correctly in explorer view. CallCB(SFVM_WINDOWCREATED, (WPARAM)_hwndView, 0); // // If this is desktop, we need to calc and upgrade the grid sizes. // (This is needed because the SnapToGrid may be ON and the default grid size // will result in large gutter space on the edges). // if (_IsDesktop()) { DWORD dwLVExStyle = ListView_GetExtendedListViewStyle(_hwndListview); // //Since the work areas are NOT yet set for the desktop's listview (because this is too early //in it's creation, we pass just one work area and the view rect as work area here.) // UpdateGridSizes(TRUE, _hwndListview, 1, pParams->prcView, BOOLIFY(dwLVExStyle & LVS_EX_SNAPTOGRID)); } // Doing this after _BestFit means we dont need to auto-auto arrange _pEnumTask->FillObjectsDPAToDone(); // splitting this function call in half means that we won't call WebView with contents changed for initial population _SwitchToViewFVM(fvm, SWITCHTOVIEW_WEBVIEWONLY); // If we're activating now, make sure we did the synchronous thing up above... // (If not, do it now -- otherwise defview may never be shown) if (_fCanActivateNow && !(!_ShouldShowWebView() || _IsDesktop())) { _ShowViewEarly(); } if (_IsDesktop()) { HideIE4DesktopChannelBar(); } // turn on proper background and colors _fClassic = ss.fWin95Classic; _UpdateListviewColors(); // this needs to be done after the enumeration if (_SetupNotifyData()) { SHChangeNotifyEntry fsne = {0}; if (FAILED(CallCB(SFVM_QUERYFSNOTIFY, 0, (LPARAM)&fsne))) { // Reset entry fsne.pidl = _pidlMonitor; fsne.fRecursive = FALSE; } int iSources = (_lFSEvents & SHCNE_DISKEVENTS) ? SHCNRF_ShellLevel | SHCNRF_InterruptLevel : SHCNRF_ShellLevel; LONG lEvents = _lFSEvents | SHCNE_UPDATEIMAGE | SHCNE_UPDATEDIR; _uRegister = SHChangeNotifyRegister(_hwndView, SHCNRF_NewDelivery | iSources, lEvents, WM_DSV_FSNOTIFY, 1, &fsne); } // We do the toolbar before the menu bar to avoid flash if (!_IsDesktop()) MergeToolBar(TRUE); // Note: it's okay for the CreateViewObject(&_pdtgtBack) to fail ASSERT(_pdtgtBack == NULL); _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IDropTarget, &_pdtgtBack)); // we don't really need to register drag drop when in the shell because // our frame does it for us. we still need it here for comdlg and other // hosts.. but for the desktop, let the desktpo frame take care of this // so that they can do webbar d/d creation if (!_IsDesktop()) { THR(RegisterDragDrop(_hwndListview, SAFECAST(this, IDropTarget*))); _bRegisteredDragDrop = TRUE; } ASSERT(SUCCEEDED(hr)) PostMessage(_hwndView, WM_DSV_DELAYWINDOWCREATE, 0, 0); if (SUCCEEDED(CallCB(SFVM_QUERYCOPYHOOK, 0, 0))) AddCopyHook(); if (SUCCEEDED(_GetIPersistHistoryObject(NULL))) { IBrowserService *pbs; if (SUCCEEDED(_psb->QueryInterface(IID_PPV_ARG(IBrowserService, &pbs)))) { IOleObject *pole; IStream *pstm; IBindCtx *pbc; pbs->GetHistoryObject(&pole, &pstm, &pbc); if (pole) { IUnknown_SetSite(pole, SAFECAST(this, IShellView2*)); // Set the back pointer. if (pstm) { IPersistHistory *pph; if (SUCCEEDED(pole->QueryInterface(IID_PPV_ARG(IPersistHistory, &pph)))) { pph->LoadHistory(pstm, pbc); pph->Release(); } pstm->Release(); } IUnknown_SetSite(pole, NULL); // just to be safe... if (pbc) pbc->Release(); pole->Release(); } pbs->Release(); } } if (_psb && !_dwProffered) { // Proffer DVGetEnum service: this connects CDefView with the tree control for // optimized navigation. IUnknown_ProfferService(_psb, SID_SFolderView, SAFECAST(this, IServiceProvider *), &_dwProffered); // Failure here does not require special handling } } else { // Cleanup - enum failed. DestroyViewWindow(); } } else { hr = E_OUTOFMEMORY; } _GlobeAnimation(FALSE); ResetWaitCursor(); return hr; } struct SCHEDULER_AND_HWND { IShellTaskScheduler *pScheduler; HWND hwnd; }; STDMETHODIMP CDefView::DestroyViewWindow() { if (_fDestroying) return S_OK; if (_psb && _dwProffered) { // Revoke DVGetEnum service IUnknown_ProfferService(_psb, SID_SFolderView, NULL, &_dwProffered); // Failure here does not require special handling } // Make sure that we stop the spinning globe before going away. _GlobeAnimation(FALSE, TRUE); _fDestroying = TRUE; // 99/04/16 #326158 vtan: Loop thru the headers looking for // stray HBITMAPs which need to be DeleteObject'd. Don't bother // setting it back the header is about to be dumped. // NOTE: Make sure this gets executed BEFORE the view gets // dumped below in DestoryViewWindow(). if (IsWindow(_hwndListview)) { HWND hwndHeader = ListView_GetHeader(_hwndListview); if (IsWindow(hwndHeader)) { int iHeaderCount = Header_GetItemCount(hwndHeader); for (int i = 0; i < iHeaderCount; ++i) { HDITEM hdi = {0}; hdi.mask = HDI_BITMAP; Header_GetItem(hwndHeader, i, &hdi); if (hdi.hbm != NULL) TBOOL(DeleteObject(hdi.hbm)); } } } _cFrame.HideWebView(); // // Just in case... // OnDeactivate(); if (IsWindow(_hwndView)) { // // This is a bit lazy implementation, but minimum code. // RemoveCopyHook(); // Tell the defview client that this window will be destroyed CallCB(SFVM_WINDOWDESTROY, (WPARAM)_hwndView, 0); } if (IsWindow(_hwndView)) { if (_pScheduler) { // empty the queue but do NOT wait until it is empty..... _pScheduler->RemoveTasks(TOID_NULL, ITSAT_DEFAULT_LPARAM, FALSE); // If there is still a task going, then kill our window later, as to not // block the UI thread. #ifdef DEBUG // Stress the feature in debug mode if (1) #else if (_GetBackgroundTaskCount(TOID_NULL) > 0) #endif { ShowWindow(_hwndView, SW_HIDE); // We are NOT passing 'this' defview pointer to the background thread // because we do not want the destructor of defview to be called on any // thread other than the one it was created on. SCHEDULER_AND_HWND *pData = (SCHEDULER_AND_HWND *)LocalAlloc(LPTR, sizeof(*pData)); if (pData) { _pScheduler->AddRef(); pData->pScheduler = _pScheduler; pData->hwnd = _hwndView; // We need to keep Browseui loaded because we depend on the CShellTaskScheduler // to be still around when our background task executes. Browseui can be unloaded by COM when // we CoUninit from this thread. if (SHQueueUserWorkItem(CDefView::BackgroundDestroyWindow, pData, 0, NULL, NULL, "browseui.dll", 0)) goto exit; else { LocalFree(pData); _pScheduler->Release(); } } } } DestroyWindow(_hwndView); } exit: return S_OK; } DWORD CDefView::BackgroundDestroyWindow(void *pvData) { SCHEDULER_AND_HWND *pData = (SCHEDULER_AND_HWND *)pvData; // Note: the window coud have been already destroyed before we get here // in the case where the frame gets closed down. if (IsWindow(pData->hwnd)) { // Remove all tasks EmptyBkgrndThread(pData->pScheduler); // We need to release before we post to ensure that browseui doesn't get unloaded from under us (pScheduler is // in browseui.dll). Browseui can get unloaded when we uninitialize OLE's MTA, even if there are still refs on the DLL. pData->pScheduler->Release(); PostMessage(pData->hwnd, WM_DSV_DELAYED_DESTROYWND, 0, 0); } else { pData->pScheduler->Release(); } LocalFree(pData); return 0; } void CDefView::_MergeViewMenu(HMENU hmenuViewParent, HMENU hmenuMerge) { HMENU hmenuView = _GetMenuFromID(hmenuViewParent, FCIDM_MENU_VIEW); if (hmenuView) { #ifdef DEBUG DWORD dwValue; DWORD cbSize = sizeof(dwValue); if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"), TEXT("DebugWebView"), NULL, &dwValue, &cbSize) && dwValue) { MENUITEMINFO mi = {0}; mi.cbSize = sizeof(mi); mi.fMask = MIIM_TYPE|MIIM_ID; mi.fType = MFT_STRING; mi.dwTypeData = TEXT("Show WebView Content"); mi.wID = SFVIDM_DEBUG_WEBVIEW; InsertMenuItem(hmenuMerge, -1, MF_BYPOSITION, &mi); } #endif // // Find the "options" separator in the view menu. // int index = MenuIndexFromID(hmenuView, FCIDM_MENU_VIEW_SEP_OPTIONS); // // Here, index is the index of he "optoins" separator if it has; // otherwise, it is -1. // // Add the separator above (in addition to existing one if any). InsertMenu(hmenuView, index, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); // Then merge our menu between two separators (or right below if only one). if (index != -1) { index++; } Shell_MergeMenus(hmenuView, hmenuMerge, (UINT)index, 0, (UINT)-1, MM_SUBMENUSHAVEIDS); } } void CDefView::_SetUpMenus(UINT uState) { // // If this is desktop, don't bother creating menu // if (!_IsDesktop()) { OnDeactivate(); ASSERT(_hmenuCur == NULL); HMENU hMenu = CreateMenu(); if (hMenu) { HMENU hMergeMenu; OLEMENUGROUPWIDTHS mwidth = { { 0, 0, 0, 0, 0, 0 } }; _hmenuCur = hMenu; _psb->InsertMenusSB(hMenu, &mwidth); if (uState == SVUIA_ACTIVATE_FOCUS) { hMergeMenu = LoadMenu(HINST_THISDLL, MAKEINTRESOURCE(POPUP_SFV_MAINMERGE)); if (hMergeMenu) { // NOTE: hard coded references to offsets in this menu Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_FILE), GetSubMenu(hMergeMenu, 0), (UINT)0, 0, (UINT)-1, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS | MM_DONTREMOVESEPS); Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_EDIT), GetSubMenu(hMergeMenu, 1), (UINT)-1, 0, (UINT)-1, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS | MM_DONTREMOVESEPS); _MergeViewMenu(hMenu, GetSubMenu(hMergeMenu, 2)); Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_HELP), GetSubMenu(hMergeMenu, 3), (UINT)0, 0, (UINT)-1, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS); DestroyMenu(hMergeMenu); } } else { hMergeMenu = LoadMenu(HINST_THISDLL, MAKEINTRESOURCE(POPUP_SFV_MAINMERGENF)); if (hMergeMenu) { // NOTE: hard coded references to offsets in this menu // top half of edit menu Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_EDIT), GetSubMenu(hMergeMenu, 0), (UINT)0, 0, (UINT)-1, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS); // bottom half of edit menu Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_EDIT), GetSubMenu(hMergeMenu, 1), (UINT)-1, 0, (UINT)-1, MM_SUBMENUSHAVEIDS); // view menu _MergeViewMenu(hMenu, GetSubMenu(hMergeMenu, 2)); Shell_MergeMenus(_GetMenuFromID(hMenu, FCIDM_MENU_HELP), GetSubMenu(hMergeMenu, 3), (UINT)0, 0, (UINT)-1, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS); DestroyMenu(hMergeMenu); } } // Allow the client to merge its own menus UINT indexClient = GetMenuItemCount(hMenu)-1; QCMINFO info = { hMenu, indexClient, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST }; CallCB(SFVM_MERGEMENU, 0, (LPARAM)&info); _psb->SetMenuSB(hMenu, NULL, _hwndView); } } } // set up the menus based on our activation state // BOOL CDefView::OnActivate(UINT uState) { if (_uState != uState) { _SetUpMenus(uState); _uState = uState; } return TRUE; } BOOL CDefView::OnDeactivate() { if (_hmenuCur || (_uState != SVUIA_DEACTIVATE)) { if (!_IsDesktop()) { ASSERT(_hmenuCur); CallCB(SFVM_UNMERGEMENU, 0, (LPARAM)_hmenuCur); _psb->SetMenuSB(NULL, NULL, NULL); _psb->RemoveMenusSB(_hmenuCur); DestroyMenu(_hmenuCur); _hmenuCur = NULL; } _uState = SVUIA_DEACTIVATE; } return TRUE; } void CDefView::_OnMoveWindowToTop(HWND hwnd) { // // Let the browser know that this has happened // VARIANT var; var.vt = VT_INT_PTR; var.byref = hwnd; IUnknown_Exec(_psb, &CGID_Explorer, SBCMDID_ONVIEWMOVETOTOP, 0, &var, NULL); } // // This function activates the view window. Note that activating it // will not change the focus (while setting the focus will activate it). // STDMETHODIMP CDefView::UIActivate(UINT uState) { if (SVUIA_DEACTIVATE == uState) { OnDeactivate(); ASSERT(_hmenuCur==NULL); } else { if (_fIsAsyncDefView) { // Need to show the defview window for the Async Case only. Showing // it earlier causes repaint problems(Bug 275266). Showing the window // here for the Sync case also causes problems - when the client // creates a Synchronous Defview and then hides it later which gets // lost with this SetWindowPos (Bug 355392). SetWindowPos(_hwndView, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW); UpdateWindow(_hwndView); _OnMoveWindowToTop(_hwndView); } if (uState == SVUIA_ACTIVATE_NOFOCUS) { // we lost focus // if in web view and we have valid ole obj (just being paranoid) if (!_fCombinedView && _cFrame.IsWebView() && _cFrame._pOleObj) { _cFrame._UIActivateIO(FALSE, NULL); } } // We may be waiting for ReadyState_Interactive. If requested, // we should activate before then... // // When we boot, the desktop paints ugly white screen for several // seconds before it shows the HTML content. This is because: the // following code switches the oleobj even before it reaches readystate // interactive. For desktop, we skip this here. When the new object // reaches readystate interactive, we will show it! if (!_IsDesktop()) { _cFrame._SwitchToNewOleObj(); // NOTE: The browser IP/UI-activates us when we become the // current active view! We want to resize and show windows // at that time. But if we're still waiting for _fCanActivateNow // (ie, ReadyStateInteractive), then we need to cache this request // and do it later. NOTE: if Trident caches the focus (done w/ TAB) // then we don't need to do anything here... // if (uState == SVUIA_ACTIVATE_FOCUS) { _SetFocus(); // _SetFocus can set _uState without causing our menu to // get created and merged. Clear it here so that OnActivate does the // right thing. if (!_hmenuCur) _uState = SVUIA_DEACTIVATE; } } // else we are the desktop; do we also need to steal focus? else if (uState == SVUIA_ACTIVATE_FOCUS) { HWND hwnd = GetFocus(); if (SHIsChildOrSelf(_hwndView, hwnd) != S_OK) _SetFocus(); } // OnActivate must follow _SetFocus OnActivate(uState); ShowHideListView(); ASSERT(_IsDesktop() || _hmenuCur); _cFrame._UpdateZonesStatusPane(NULL); } return S_OK; } STDMETHODIMP CDefView::GetCurrentInfo(LPFOLDERSETTINGS pfs) { *pfs = _fs; return S_OK; } BOOL IsBackSpace(const MSG *pMsg) { return pMsg && (pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_BACK); } extern int IsVK_TABCycler(MSG *pMsg); //*** // NOTES // try ListView->TA first // then if that fails try WebView->TA iff it has focus. // then if that fails and it's a TAB we do WebView->UIAct STDMETHODIMP CDefView::TranslateAccelerator(LPMSG pmsg) { // 1st, try ListView if (_fInLabelEdit) { // the second clause stops us passing mouse key clicks to the toolbar if we are in label edit mode... if (WM_KEYDOWN == pmsg->message || WM_KEYUP == pmsg->message) { // process this msg so the exploer does not get to translate TranslateMessage(pmsg); DispatchMessage(pmsg); return S_OK; // we handled it } else return S_FALSE; } // If we are in classic mode and if it's a tab and the listview doesn't have focus already, receive the tab. else if (IsVK_TABCycler(pmsg) && !(_cFrame.IsWebView() || _pDUIView) && (GetFocus() != _hwndListview)) { _SetFocus(); return S_OK; } if (GetFocus() == _hwndListview) { if (::TranslateAccelerator(_hwndView, _hAccel, pmsg)) { // we know we have a normal view, therefore this is // the right translate accelerator to use, otherwise the // common dialogs will fail to get any accelerated keys. return S_OK; } else if (WM_KEYDOWN == pmsg->message || WM_SYSKEYDOWN == pmsg->message) { // MSHTML eats these keys for frameset scrolling, but we // want to get them to our wndproc . . . translate 'em ourself // switch (pmsg->wParam) { case VK_LEFT: case VK_RIGHT: // only go through here if alt is not down. // don't intercept all alt combinations because // alt-enter means something // this is for alt-left/right compat with IE if (GetAsyncKeyState(VK_MENU) < 0) break; // fall through case VK_UP: case VK_DOWN: case VK_HOME: case VK_END: case VK_PRIOR: case VK_NEXT: case VK_RETURN: case VK_F10: TranslateMessage(pmsg); DispatchMessage(pmsg); return S_OK; } } } // 1.5th, before we pass it down, see whether shell browser handles it. // we do this to make sure that webview has the same accelerator semantics // no matter what view(s) are active. // note that this is arguably inconsistent w/ the 'pass it to whoever has // focus'. // // however *don't* do this if: // - we're in a dialog (in which case the buttons should come 1st) // (comdlg's shellbrowser xxx::TA impl is broken it always does S_OK) // - it's a TAB (which is always checked last) // - it's a BACKSPACE (we should give the currently active object the first chance). // However, in this case, we should call TranslateAcceleratorSB() AFTER we've tried // calling TranslateAccelerator() on the currently active control (_pActive) in // _cFrame->OnTranslateAccelerator(). // // note: if you muck w/ this code careful not to regress the following: // - ie41:62140: mnemonics broken after folder selected in organize favs // - ie41:62419: TAB activates addr and menu if folder selected in explorer if (!_IsCommonDialog() && !IsVK_TABCycler(pmsg) && !IsBackSpace(pmsg)) if (S_OK == _psb->TranslateAcceleratorSB(pmsg, 0)) return S_OK; BOOL bTabOffLastTridentStop = FALSE; BOOL bHadIOFocus = (_cFrame._HasFocusIO() == S_OK); // Cache this here before the _cFrame.OnTA() call below // 2nd, try WebView if it's active if (IsVK_TABCycler(pmsg) && _pDUIView) { if (_pDUIView->Navigate(GetAsyncKeyState(VK_SHIFT) >= 0)) return S_OK; } if (_cFrame.IsWebView() && (S_OK == _cFrame.OnTranslateAccelerator(pmsg, &bTabOffLastTridentStop))) { return S_OK; } // We've given _pActive->TranslateAccelerator() the first shot in // _cFrame.OnTranslateAccelerator, but it failed. Let's try the shell browser. if (IsBackSpace(pmsg) && (S_OK == _psb->TranslateAcceleratorSB(pmsg, 0))) return S_OK; // 3rd, ??? if (::TranslateAccelerator(_hwndView, _hAccel, pmsg)) return S_OK; // 4th, if it's a TAB, cycle to next guy // hack: we fake a bunch of the TAB-activation handshaking if (IsVK_TABCycler(pmsg) && _cFrame.IsWebView()) { HRESULT hr; BOOL fBack = (GetAsyncKeyState(VK_SHIFT) < 0); if (!bHadIOFocus && bTabOffLastTridentStop) { // We were at the last tab stop in trident when the browser called defview->TA(). // When we called TA() on trident above, it must've told us that we are tabbing // off the last tab stop (bTabOffLastTridentStop). This will leave us not setting focus // on anything. But, we have to set focus to something. We can do this by calling TA() // on trident again, which will set focus on the first tab stop again. return _cFrame.OnTranslateAccelerator(pmsg, &bTabOffLastTridentStop); } else if (_cFrame._HasFocusIO() == S_OK) { // ExtView has focus, and doesn't want the TAB. // this means we're TABing off of it. // no matter what, deactivate it (since we're TABing off). // if the view is next in the TAB order, (pseudo-)activate it, // and return S_OK since we've handled it. // o.w. return S_OK so our parent will activate whoever's next // in the TAB order. hr = _cFrame._UIActivateIO(FALSE, NULL); ASSERT(hr == S_OK); // in web view listview already has focus so don't give it again // that's not the case with desktop if (fBack && _IsDesktop()) { SetFocus(_hwndListview); return S_OK; } return S_FALSE; } else { if (!fBack) { hr = _cFrame._UIActivateIO(TRUE, pmsg); ASSERT(hr == S_OK || hr == S_FALSE); return hr; } } } return S_FALSE; } // Description: // Regenerates the CDefView's menus. Used for regaining any menu items // which may have been stripped via DeleteMenu(), as occurs for various // particular view states. // // Example: Transitioning to a barricaded view automatically strips out // a number of commands from the "View" menu which are not appropriate // for the barricaded view. Thus, on the transition back out of the // barricaded view, the menus must be recreated in order to regain // any/all the menu items stripped (this is not to say a number of // them may not be stripped again if we're just transitioning to // another view which doesn't want them in there!). // void CDefView::RecreateMenus() { UINT uState = _uState; _SetUpMenus(uState); // Note _SetupMenus() calls OnDeactivate() _uState = uState; // which sets _uState to SVUIA_DEACTIVATE. } void CDefView::InitViewMenu(HMENU hmInit) { // Initialize view menu accordingly... if (_fBarrierDisplayed) _InitViewMenuWhenBarrierDisplayed(hmInit); else _InitViewMenuWhenBarrierNotDisplayed(hmInit); // Remove any extraneous menu separators arising from initialization. _SHPrettyMenu(hmInit); } // Description: // Used to initialize the entries of the "View" menu and its associated // submenus whenever a soft barrier is being displayed. // // Note: // This method is also employed when "Category View" is being used in // browsing the Control Panel. // void CDefView::_InitViewMenuWhenBarrierDisplayed(HMENU hmenuView) { // If "list view" is not visible (i.e. we're in Category View in the // Control Panel, or we're looking at a barricaded folder), we strip // out the following stuff from the View menu: // // Filmstrip // Thumbnails // Tiles // Icons // List // Details // ------------------- // Arrange Icons By -> // ------------------- // Choose Details... // Customize This Folder... // Remove menu entries. DeleteMenu(hmenuView, SFVIDM_VIEW_THUMBSTRIP, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_THUMBNAIL, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_TILE, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_ICON, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_LIST, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_DETAILS, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_MENU_ARRANGE, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_COLSETTINGS, MF_BYCOMMAND); DeleteMenu(hmenuView, SFVIDM_VIEW_CUSTOMWIZARD, MF_BYCOMMAND); } // Description: // Used to initialize the entries of the "View" menu and its associated // submenus whenever a soft barrier is not being displayed. // void CDefView::_InitViewMenuWhenBarrierNotDisplayed(HMENU hmenuView) { DWORD dwListViewFlags = ListView_GetExtendedListViewStyle(_hwndListview); UINT uEnabled = (MF_ENABLED | MF_BYCOMMAND); UINT uDisabled = (MF_GRAYED | MF_BYCOMMAND); UINT uChecked = (MF_CHECKED | MF_BYCOMMAND); UINT uUnchecked = (MF_UNCHECKED | MF_BYCOMMAND); UINT uAAEnable; // Auto Arrange UINT uAACheck; UINT uAGrEnable; // Align to Grid UINT uAGrCheck; // Initialize "view" menu entries. _InitViewMenuViewsWhenBarrierNotDisplayed(hmenuView); // Initialize "Arrange Icons By ->" submenu. _InitArrangeMenu(hmenuView); // Determine and set appropriate enable state for "Auto Arrange" and "Align to Grid". if (_IsPositionedView() && _IsListviewVisible() && !(_fs.ViewMode == FVM_THUMBSTRIP)) uAAEnable = uAGrEnable = uEnabled; else uAAEnable = uAGrEnable = uDisabled; EnableMenuItem(hmenuView, SFVIDM_ARRANGE_AUTO, uAAEnable); EnableMenuItem(hmenuView, SFVIDM_ARRANGE_AUTOGRID, uAGrEnable); // Determine and set appropriate check state for "Auto Arrange" and "Align to Grid". uAACheck = (((uAAEnable == uEnabled) || _fGroupView || (_fs.ViewMode == FVM_THUMBSTRIP)) && _IsAutoArrange()) ? uChecked : uUnchecked; uAGrCheck = (((uAGrEnable == uEnabled) || _fGroupView) && (dwListViewFlags & LVS_EX_SNAPTOGRID)) ? uChecked : uUnchecked; CheckMenuItem(hmenuView, SFVIDM_ARRANGE_AUTO, uAACheck); CheckMenuItem(hmenuView, SFVIDM_ARRANGE_AUTOGRID, uAGrCheck); // If icons are not being shown (such as can be set on the // desktop), disable ALL icon-arrangement related commands. if (!_IsListviewVisible()) { HMENU hArrangeSubMenu; UINT uID; int i = 0; // Retrieve "Arrange Icons By ->" submenu. hArrangeSubMenu = GetSubMenu(hmenuView, 2); // Iterate and disable until we get to "Show Icons". while (1) { uID = GetMenuItemID(hArrangeSubMenu, i); if ((uID == SFVIDM_DESKTOPHTML_ICONS) || (uID == (UINT)-1)) break; else EnableMenuItem(hArrangeSubMenu, i, MF_GRAYED | MF_BYPOSITION); i++; } } else if (!_ShouldShowWebView()) { // If Web View is off, then thumbstrip will never work... DeleteMenu(hmenuView, SFVIDM_VIEW_THUMBSTRIP, MF_BYCOMMAND); } // Remove "Customize This Folder..." if folder is not customizable. if (!_CachedIsCustomizable()) { // The Folder Option "Classic style" and the shell restriction WIN95CLASSIC // should be the same. (Per ChristoB, otherwise admin's never understand what // the restriction means.) Since we want this to change DEFAULTs, and still // allow the user to turn on Web View, we don't remove the customize wizard here. int iIndex = MenuIndexFromID(hmenuView, SFVIDM_VIEW_CUSTOMWIZARD); if (iIndex != -1) { DeleteMenu(hmenuView, iIndex + 1, MF_BYPOSITION); // Remove Menu seperator DeleteMenu(hmenuView, iIndex, MF_BYPOSITION); // Remove Customize } } } // Description: // Initializes the "view" entries on a view menu. This involves stripping // out any "view" entries for unsupported views, and additionally checking // of the appropriate "view" entry for the current view. // // Note: // This method should not be called if a soft barrier is being displayed. // Remember that in this case there is no concept of a view, so why // would someone be attempting to initialize "view" menu entries. // void CDefView::_InitViewMenuViewsWhenBarrierNotDisplayed(HMENU hmenuView) { ASSERT(!_fBarrierDisplayed); // Remove menu entries for unsupported views. for (UINT fvm = FVM_FIRST; fvm <= FVM_LAST; fvm++) if (!_ViewSupported(fvm)) DeleteMenu(hmenuView, SFVIDM_VIEW_FIRSTVIEW + fvm - FVM_FIRST, MF_BYCOMMAND); // "Check" menu entry for current view. CheckCurrentViewMenuItem(hmenuView); } void CDefView::_GetCBText(UINT_PTR id, UINT uMsgT, UINT uMsgA, UINT uMsgW, LPTSTR psz, UINT cch) { *psz = 0; WCHAR szW[MAX_PATH]; if (SUCCEEDED(CallCB(uMsgW, MAKEWPARAM(id - SFVIDM_CLIENT_FIRST, ARRAYSIZE(szW)), (LPARAM)szW))) SHUnicodeToTChar(szW, psz, cch); else { char szA[MAX_PATH]; if (SUCCEEDED(CallCB(uMsgA, MAKEWPARAM(id - SFVIDM_CLIENT_FIRST, ARRAYSIZE(szA)), (LPARAM)szA))) SHAnsiToTChar(szA, psz, cch); else CallCB(uMsgT, MAKEWPARAM(id - SFVIDM_CLIENT_FIRST, cch), (LPARAM)psz); } } void CDefView::_GetMenuHelpText(UINT_PTR id, LPTSTR pszText, UINT cchText) { *pszText = 0; if ((InRange(id, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST) && _pcmFile) || (InRange(id, SFVIDM_BACK_CONTEXT_FIRST, SFVIDM_BACK_CONTEXT_LAST) && _pcmContextMenuPopup)) { UINT uCMBias = SFVIDM_CONTEXT_FIRST; IContextMenu *pcmSel = NULL; if (InRange(id, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST)) { pcmSel = _pcmFile; uCMBias = SFVIDM_CONTEXT_FIRST; } else if (InRange(id, SFVIDM_BACK_CONTEXT_FIRST, SFVIDM_BACK_CONTEXT_LAST)) { pcmSel = _pcmContextMenuPopup; uCMBias = SFVIDM_BACK_CONTEXT_FIRST; } // First try to get the stardard help string pcmSel->GetCommandString(id - uCMBias, GCS_HELPTEXT, NULL, (LPSTR)pszText, cchText); if (*pszText == 0) { // If we didn't get anything, try to grab the ansi version CHAR szText[MAX_PATH]; szText[0] = 0; // Don't start with garbage in case of failure... pcmSel->GetCommandString(id - uCMBias, GCS_HELPTEXTA, NULL, szText, ARRAYSIZE(szText)); SHAnsiToUnicode(szText, pszText, cchText); } } else if (InRange(id, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST) && HasCB()) { _GetCBText(id, SFVM_GETHELPTEXT, SFVM_GETHELPTEXTA, SFVM_GETHELPTEXTW, pszText, cchText); } else if (InRange(id, SFVIDM_GROUPSFIRST, SFVIDM_GROUPSLAST)) { TCHAR sz[MAX_PATH]; int idHelp = _fGroupView?IDS_GROUPBY_HELPTEXT:IDS_ARRANGEBY_HELPTEXT; LoadString(HINST_THISDLL, idHelp, sz, ARRAYSIZE(sz)); wnsprintf(pszText, cchText, sz, _vs.GetColumnName((UINT)id - SFVIDM_GROUPSFIRST)); } else if (InRange(id, SFVIDM_GROUPSEXTENDEDFIRST, SFVIDM_GROUPSEXTENDEDLAST)) { // Can't think of anything descriptive } else if (InRange(id, SFVIDM_FIRST, SFVIDM_LAST)) { if ((id == SFVIDM_EDIT_UNDO) && IsUndoAvailable()) { GetUndoText(pszText, cchText, UNDO_STATUSTEXT); } else { UINT idHelp = (UINT)id + SFVIDS_MH_FIRST; // Unfortunatly, this starts to hit other ranges, so I'm just hard coding this one instead of // using the table. If you add more, we need another table method of associating ids and help strings if (id == SFVIDM_GROUPBY) idHelp = IDS_GROUPBYITEM_HELPTEXT; LoadString(HINST_THISDLL, idHelp, pszText, cchText); } } } void CDefView::_GetToolTipText(UINT_PTR id, LPTSTR pszText, UINT cchText) { *pszText = 0; if (InRange(id, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST) && HasCB()) { _GetCBText(id, SFVM_GETTOOLTIPTEXT, SFVM_GETTOOLTIPTEXTA, SFVM_GETTOOLTIPTEXTW, pszText, cchText); } else if (InRange(id, SFVIDM_FIRST, SFVIDM_LAST)) { if (id == SFVIDM_EDIT_UNDO) { if (IsUndoAvailable()) { GetUndoText(pszText, cchText, UNDO_MENUTEXT); return; } } LoadString(HINST_THISDLL, (UINT)(IDS_TT_SFVIDM_FIRST + id), pszText, cchText); } else { // REVIEW: This might be an assert situation: missing tooltip info... TraceMsg(TF_WARNING, "_GetToolTipText: tip request for unknown object"); } } LRESULT CDefView::_OnMenuSelect(UINT id, UINT mf, HMENU hmenu) { TCHAR szHelpText[80 + 2*MAX_PATH]; // Lots of stack! // If we dismissed the edit restore our status bar... if (!hmenu && LOWORD(mf)==0xffff) { _psb->SendControlMsg(FCW_STATUS, SB_SIMPLE, 0, 0, NULL); return 0; } if (mf & (MF_SYSMENU | MF_SEPARATOR)) return 0; szHelpText[0] = 0; // in case of failures below if (mf & MF_POPUP) { MENUITEMINFO miiSubMenu; miiSubMenu.cbSize = sizeof(MENUITEMINFO); miiSubMenu.fMask = MIIM_ID; miiSubMenu.cch = 0; // just in case if (!GetMenuItemInfo(hmenu, id, TRUE, &miiSubMenu)) return 0; // Change the parameters to simulate a "normal" menu item id = miiSubMenu.wID; mf &= ~MF_POPUP; } _GetMenuHelpText(id, szHelpText, ARRAYSIZE(szHelpText)); _fBackgroundStatusTextValid = FALSE; _psb->SendControlMsg(FCW_STATUS, SB_SETTEXT, SBT_NOBORDERS | 255, (LPARAM)szHelpText, NULL); _psb->SendControlMsg(FCW_STATUS, SB_SIMPLE, 1, 0, NULL); return 0; } // // This function dismisses the name edit mode if there is any. // // REVIEW: Moving the focus away from the edit window will // dismiss the name edit mode. Should we introduce // a LV_DISMISSEDIT instead? // void CDefView::_DismissEdit() { if (_uState == SVUIA_ACTIVATE_FOCUS) { ListView_CancelEditLabel(_hwndListview); } } void CDefView::_OnInitMenu() { // We need to dismiss the edit mode if it is any. _DismissEdit(); } void _RemoveContextMenuItems(HMENU hmInit) { int i; for (i = GetMenuItemCount(hmInit) - 1; i >= 0; --i) { MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_ID | MIIM_ID; mii.cch = 0; // just in case if (GetMenuItemInfo(hmInit, i, TRUE, &mii)) { if (InRange(mii.wID, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST)) { TraceMsg(TF_DEFVIEW, "_RemoveContextMenuItems: setting bDeleteItems at %d, %d", i, mii.wID); //bDeleteItems = TRUE; DeleteMenu(hmInit, i, MF_BYPOSITION); } } } } BOOL HasClientItems(HMENU hmenu) { int cItems = GetMenuItemCount(hmenu); for (int i = 0; i < cItems; i++) { UINT id = GetMenuItemID(hmenu, i); if (InRange(id, SFVIDM_CLIENT_FIRST, SFVIDM_CLIENT_LAST)) return TRUE; } return FALSE; } LRESULT CDefView::_OnInitMenuPopup(HMENU hmInit, int nIndex, BOOL fSystemMenu) { if (_hmenuCur) { // This old code makes sure we only switch on the wID for one of our top-level windows // The id shouldn't be re-used, so this probably isn't needed. But it doesn't hurt... // MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_SUBMENU | MIIM_ID; if (GetMenuItemInfo(_hmenuCur, nIndex, TRUE, &mii) && mii.hSubMenu == hmInit) { switch (mii.wID) { case FCIDM_MENU_FILE: // PERF note: we could avoid the rip-down-and-re-build our File menu // if we have a _pcmFile and the _uState is the same as last // time and the selection is identical to last time. // First of all, clean up our last _pcmFile usage: // remove all the menu items we've added // remove the named separators for defcm _RemoveContextMenuItems(hmInit); SHUnprepareMenuForDefcm(hmInit, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST); IUnknown_SetSite(_pcmFile, NULL); ATOMICRELEASE(_pcmFile); // Second, handle the focus/nofocus menus if (_uState == SVUIA_ACTIVATE_FOCUS) { // Enable/disable our menuitems in the "File" pulldown. Def_InitFileCommands(_AttributesFromSel(SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANLINK | SFGAO_HASPROPSHEET), hmInit, SFVIDM_FIRST, FALSE); // Collect our new _pcmFile context menu IContextMenu* pcmSel = NULL; _CreateSelectionContextMenu(IID_PPV_ARG(IContextMenu, &pcmSel)); IContextMenu* pcmBack = NULL; _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IContextMenu, &pcmBack)); IContextMenu* rgpcm[] = { pcmSel, pcmBack }; Create_ContextMenuOnContextMenuArray(rgpcm, ARRAYSIZE(rgpcm), IID_PPV_ARG(IContextMenu, &_pcmFile)); if (pcmSel) pcmSel->Release(); if (pcmBack) pcmBack->Release(); } else if (_uState == SVUIA_ACTIVATE_NOFOCUS) { _pshf->CreateViewObject(_hwndMain, IID_PPV_ARG(IContextMenu, &_pcmFile)); } // Third, merge in the context menu items { HRESULT hrPrepare = SHPrepareMenuForDefcm(hmInit, 0, 0, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST); if (_pcmFile) { IUnknown_SetSite(_pcmFile, SAFECAST(this, IShellView2*)); _pcmFile->QueryContextMenu(hmInit, 0, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST, CMF_DVFILE | CMF_NODEFAULT); } SHPrettyMenuForDefcm(hmInit, 0, SFVIDM_CONTEXT_FIRST, SFVIDM_CONTEXT_LAST, hrPrepare); } break; case FCIDM_MENU_EDIT: // Enable/disable menuitems in the "Edit" pulldown. Def_InitEditCommands(_AttributesFromSel(SFGAO_CANCOPY | SFGAO_CANMOVE), hmInit, SFVIDM_FIRST, _pdtgtBack, 0); _SHPrettyMenu(hmInit); break; case FCIDM_MENU_VIEW: InitViewMenu(hmInit); break; } } } // Check for a context menu's popup: // assume the first item in the menu identifies the range BOOL fHandled; _ForwardMenuMessages(GetMenuItemID(hmInit, 0), WM_INITMENUPOPUP, (WPARAM)hmInit, MAKELPARAM(nIndex, fSystemMenu), NULL, &fHandled); // Maybe this is the callback's menu then? if (!fHandled && _hmenuCur && HasCB() && HasClientItems(hmInit)) { CallCB(SFVM_INITMENUPOPUP, MAKEWPARAM(SFVIDM_CLIENT_FIRST, nIndex), (LPARAM)hmInit); } return 0; } // IShellView::AddPropertySheetPages STDMETHODIMP CDefView::AddPropertySheetPages(DWORD dwRes, LPFNADDPROPSHEETPAGE lpfn, LPARAM lParam) { SFVM_PROPPAGE_DATA data; ASSERT(IS_VALID_CODE_PTR(lpfn, FNADDPROPSHEETPAGE)); data.dwReserved = dwRes; data.pfn = lpfn; data.lParam = lParam; // Call the callback to add pages CallCB(SFVM_ADDPROPERTYPAGES, 0, (LPARAM)&data); return S_OK; } STDMETHODIMP CDefView::SaveViewState() { HRESULT hr; IPropertyBag* ppb; hr = IUnknown_QueryServicePropertyBag(_psb, SHGVSPB_FOLDER, IID_PPV_ARG(IPropertyBag, &ppb)); if (SUCCEEDED(hr)) { hr = _vs.SaveToPropertyBag(this, ppb); ppb->Release(); } else { IStream *pstm; hr = _psb->GetViewStateStream(STGM_WRITE, &pstm); if (SUCCEEDED(hr)) { hr = _vs.SaveToStream(this, pstm); pstm->Release(); } else { // There are cases where we may not save out the complete view state // but we do want to save out the column information (like Docfind...) if (SUCCEEDED(CallCB(SFVM_GETCOLSAVESTREAM, STGM_READ, (LPARAM)&pstm))) { hr = _vs.SaveColumns(this, pstm); pstm->Release(); } } } return hr; } // 99/02/05 #226140 vtan: Function used to get the storage // stream for the default view state of the current DefView. // Typically this will be CLSID_ShellFSFolder but can be // others. HRESULT CDefView::_GetStorageStream (DWORD grfMode, IStream* *ppIStream) { *ppIStream = NULL; CLSID clsid; HRESULT hr = IUnknown_GetClassID(_pshf, &clsid); if (SUCCEEDED(hr)) { TCHAR szCLSID[64]; // enough for the CLSID if (IsEqualGUID(CLSID_MyDocuments, clsid)) clsid = CLSID_ShellFSFolder; TINT(SHStringFromGUID(clsid, szCLSID, ARRAYSIZE(szCLSID))); *ppIStream = OpenRegStream(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER TEXT("\\Streams\\Defaults"), szCLSID, grfMode); if (*ppIStream == NULL) hr = E_FAIL; } return hr; } // 99/02/05 #226140 vtan: Function called from DefView's // implementation of IOleCommandTarget::Exec() which is // invoked from CShellBrowser2::SetAsDefFolderSettings(). HRESULT CDefView::_SaveGlobalViewState(void) { IStream *pstm; HRESULT hr = _GetStorageStream(STGM_WRITE, &pstm); if (SUCCEEDED(hr)) { hr = _vs.SaveToStream(this, pstm); if (SUCCEEDED(hr)) { hr = (ERROR_SUCCESS == SHDeleteKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\ShellNoRoam\\Bags"))) ? S_OK : E_FAIL; } pstm->Release(); } return hr; } // 99/02/05 #226140 vtan: Function called from // GetViewState to get the default view state // for this class. HRESULT CDefView::_LoadGlobalViewState(IStream* *ppIStream) { return _GetStorageStream(STGM_READ, ppIStream); } // 99/02/09 #226140 vtan: Function used to reset the // global view states stored by deleting the key // that stores all of them. HRESULT CDefView::_ResetGlobalViewState(void) { SHDeleteKey(HKEY_CURRENT_USER, REGSTR_PATH_EXPLORER TEXT("\\Streams\\Defaults")); LONG lRetVal = SHDeleteKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\ShellNoRoam\\Bags")); return (ERROR_SUCCESS == lRetVal) ? S_OK : E_FAIL; } void CDefView::_RestoreAllGhostedFileView() { ListView_SetItemState(_hwndListview, -1, 0, LVIS_CUT); UINT c = ListView_GetItemCount(_hwndListview); for (UINT i = 0; i < c; i++) { if (_Attributes(_GetPIDL(i), SFGAO_GHOSTED)) ListView_SetItemState(_hwndListview, i, LVIS_CUT, LVIS_CUT); } } HRESULT CDefView::SelectAndPositionItem(LPCITEMIDLIST pidlItem, UINT uFlags, POINT *ppt) { HRESULT hr; if (NULL == pidlItem) hr = _SelectAndPosition(-1, uFlags, ppt); else if (ILFindLastID(pidlItem) == pidlItem) { if (_fInBackgroundGrouping) { Pidl_Set(&_pidlSelectAndPosition, pidlItem); _uSelectAndPositionFlags = uFlags; hr = S_OK; } else { int iItem = _FindItem(pidlItem, NULL, FALSE); if (iItem != -1) hr = _SelectAndPosition(iItem, uFlags, ppt); else hr = S_OK; } } else { RIP(ILFindLastID(pidlItem) == pidlItem); hr = E_INVALIDARG; } return hr; } HRESULT CDefView::_SelectAndPosition(int iItem, UINT uFlags, POINT *ppt) { HRESULT hr = S_OK; // assume all is good // See if we should first deselect everything else if (-1 == iItem) { if (uFlags == SVSI_DESELECTOTHERS) { ListView_SetItemState(_hwndListview, -1, 0, LVIS_SELECTED); _RestoreAllGhostedFileView(); } else { hr = E_INVALIDARG; // I only know how to deselect everything } } else { if (_pDUIView) { _fBarrierDisplayed = FALSE; _pDUIView->EnableBarrier(FALSE); } if (uFlags & SVSI_TRANSLATEPT) { //The caller is asking us to take this point and convert it from screen Coords // to the Client of the Listview. LVUtil_ScreenToLV(_hwndListview, ppt); } // set the position first so that the ensure visible scrolls to // the new position if (ppt) { _SetItemPosition(iItem, ppt->x, ppt->y); } else if ((SVSI_POSITIONITEM & uFlags) && _bMouseMenu && _IsPositionedView()) { _SetItemPosition(iItem, _ptDragAnchor.x, _ptDragAnchor.y); } if ((uFlags & SVSI_EDIT) == SVSI_EDIT) { // Grab focus if the listview (or any of it's children) don't already have focus HWND hwndFocus = GetFocus(); if (SHIsChildOrSelf(_hwndListview, hwndFocus) != S_OK) SetFocus(_hwndListview); ListView_EditLabel(_hwndListview, iItem); } else { // change the item state if (!(uFlags & SVSI_NOSTATECHANGE)) { UINT stateMask = LVIS_SELECTED; UINT state = (uFlags & SVSI_SELECT) ? LVIS_SELECTED : 0; if (uFlags & SVSI_FOCUSED) { state |= LVIS_FOCUSED; stateMask |= LVIS_FOCUSED; } // See if we should first deselect everything else if (uFlags & SVSI_DESELECTOTHERS) { ListView_SetItemState(_hwndListview, -1, 0, LVIS_SELECTED); _RestoreAllGhostedFileView(); } ListView_SetItemState(_hwndListview, iItem, state, stateMask); } if (uFlags & SVSI_ENSUREVISIBLE) ListView_EnsureVisible(_hwndListview, iItem, FALSE); // we should only set focus when SVUIA_ACTIVATE_FOCUS // bug fixing that might break find target code if (uFlags & SVSI_FOCUSED) SetFocus(_hwndListview); if (uFlags & SVSI_SELECTIONMARK) ListView_SetSelectionMark(_hwndListview, iItem); // if this is a check select view then set the state of that item accordingly if (_fs.fFlags & FWF_CHECKSELECT) ListView_SetCheckState(_hwndListview, iItem, (uFlags & SVSI_CHECK)); } } return hr; } STDMETHODIMP CDefView::SelectItem(int iItem, DWORD uFlags) { return _SelectAndPosition(iItem, uFlags, NULL); } typedef struct { LPITEMIDLIST pidl; UINT uFlagsSelect; } DELAY_SEL_ITEM; STDMETHODIMP CDefView::SelectItem(LPCITEMIDLIST pidlItem, UINT uFlags) { // if the listview isn't shown, there's nothing to select yet. // Likewise if we are in the process of being created we should defer. if (!_IsListviewVisible()) { if (!_hdsaSelect) { _hdsaSelect = DSA_Create(sizeof(DELAY_SEL_ITEM), 4); if (!_hdsaSelect) return E_OUTOFMEMORY; } HRESULT hr = E_OUTOFMEMORY; DELAY_SEL_ITEM dvdsi; dvdsi.pidl = ILClone(pidlItem); if (dvdsi.pidl) { dvdsi.uFlagsSelect = uFlags; if (DSA_AppendItem(_hdsaSelect, &dvdsi) == DSA_ERR) ILFree(dvdsi.pidl); else hr = S_OK; } return hr; } return SelectAndPositionItem(pidlItem, uFlags, NULL); } // IFolderView STDMETHODIMP CDefView::GetCurrentViewMode(UINT *pViewMode) { *pViewMode = _fs.ViewMode; return S_OK; } STDMETHODIMP CDefView::SetCurrentViewMode(UINT uViewMode) { ASSERT(FVM_FIRST <= uViewMode && uViewMode <= FVM_LAST); if (uViewMode != _vs._ViewMode) _ClearItemPositions(); return _SwitchToViewFVM(uViewMode); } STDMETHODIMP CDefView::GetFolder(REFIID riid, void **ppv) { if (_pshf) return _pshf->QueryInterface(riid, ppv); *ppv = NULL; return E_NOINTERFACE; } STDMETHODIMP CDefView::Item(int iItemIndex, LPITEMIDLIST *ppidl) { HRESULT hr = E_FAIL; LPCITEMIDLIST pidl = _GetPIDL(iItemIndex); if (pidl) { hr = SHILClone(pidl, ppidl); } return hr; } STDMETHODIMP CDefView::ItemCount(UINT uFlags, int *pcItems) { *pcItems = _GetItemArray(NULL, NULL, uFlags); return S_OK; } HRESULT CDefView::_EnumThings(UINT uWhat, IEnumIDList **ppenum) { *ppenum = NULL; LPCITEMIDLIST *apidl; UINT cItems; HRESULT hr = _GetItemObjects(&apidl, uWhat, &cItems); if (SUCCEEDED(hr)) { hr = CreateIEnumIDListOnIDLists(apidl, cItems, ppenum); LocalFree(apidl); } return hr; } STDMETHODIMP CDefView::Items(UINT uWhat, REFIID riid, void **ppv) { HRESULT hr = E_NOINTERFACE; if (IID_IEnumIDList == riid) { hr = _EnumThings(uWhat, (IEnumIDList**)ppv); } else if (IID_IDataObject == riid) { if ((uWhat & SVGIO_TYPE_MASK) == SVGIO_SELECTION) { if (_pSelectionShellItemArray) { hr = _pSelectionShellItemArray->BindToHandler(NULL, BHID_DataObject, riid, ppv); } } else { hr = _GetUIObjectFromItem(riid, ppv, uWhat, FALSE); } } return hr; } // inverse of ::SelectItem(..., SVSI_SELECTIONMARK) STDMETHODIMP CDefView::GetSelectionMarkedItem(int *piItem) { *piItem = ListView_GetSelectionMark(_hwndListview); return (-1 == *piItem) ? S_FALSE : S_OK; } STDMETHODIMP CDefView::GetFocusedItem(int *piItem) { *piItem = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED); return (-1 == *piItem) ? S_FALSE : S_OK; } BOOL CDefView::_GetItemPosition(LPCITEMIDLIST pidl, POINT *ppt) { int i = _FindItem(pidl, NULL, FALSE); if (i != -1) return ListView_GetItemPosition(_hwndListview, i, ppt); return FALSE; } STDMETHODIMP CDefView::GetItemPosition(LPCITEMIDLIST pidl, POINT *ppt) { return _GetItemPosition(pidl, ppt) ? S_OK : E_FAIL; } STDMETHODIMP CDefView::GetSpacing(POINT* ppt) { if (ppt) { if (_fs.ViewMode != FVM_TILE) { BOOL fSmall; switch (_fs.ViewMode) { case FVM_SMALLICON: case FVM_LIST: case FVM_DETAILS: fSmall = TRUE; break; case FVM_ICON: case FVM_THUMBNAIL: case FVM_THUMBSTRIP: default: fSmall = FALSE; break; } DWORD dwSize = ListView_GetItemSpacing(_hwndListview, fSmall); ppt->x = GET_X_LPARAM(dwSize); ppt->y = GET_Y_LPARAM(dwSize); } else { LVTILEVIEWINFO tvi; tvi.cbSize = sizeof(tvi); tvi.dwMask = LVTVIM_TILESIZE; if (ListView_GetTileViewInfo(_hwndListview, &tvi)) { ppt->x = tvi.sizeTile.cx; ppt->y = tvi.sizeTile.cy; } else { // guess. ppt->x = 216; ppt->y = 56; } } } return _IsPositionedView() ? S_OK : S_FALSE; } STDMETHODIMP CDefView::GetDefaultSpacing(POINT* ppt) { ASSERT(ppt); if (_fs.ViewMode != FVM_THUMBNAIL && _fs.ViewMode != FVM_THUMBSTRIP && _fs.ViewMode != FVM_TILE) { DWORD dwSize = ListView_GetItemSpacing(_hwndListview, FALSE); ppt->x = GET_X_LPARAM(dwSize); ppt->y = GET_Y_LPARAM(dwSize); } else { // Bug #163528 (edwardp 8/15/00) Should get this data from comctl. ppt->x = GetSystemMetrics(SM_CXICONSPACING); ppt->y = GetSystemMetrics(SM_CYICONSPACING); } return S_OK; } // IShellFolderView STDMETHODIMP CDefView::GetAutoArrange() { return _IsAutoArrange() ? S_OK : S_FALSE; } void CDefView::_ClearPendingSelectedItems() { if (_hdsaSelect) { HDSA hdsa = _hdsaSelect; _hdsaSelect = NULL; int cItems = DSA_GetItemCount(hdsa); for (int i = 0; i < cItems; i++) { DELAY_SEL_ITEM *pdvdsi = (DELAY_SEL_ITEM*)DSA_GetItemPtr(hdsa, i); if (pdvdsi) ILFree(pdvdsi->pidl); } DSA_Destroy(hdsa); } } // Call this whenever the state changes such that SelectItem (above) void CDefView::SelectPendingSelectedItems() { ASSERT(_IsListviewVisible()); if (_hdsaSelect) { // // Listview quirk: If the following conditions are met.. // // 1. WM_SETREDRAW(FALSE) or ShowWindow(SW_HIDE) // 2. Listview has never painted yet // 3. LVS_LIST // // then ListView_LGetRects doesn't work. And consequently, // everything that relies on known item rectangles (e.g., // LVM_ENSUREVISIBLE, sent by below SelectItem call) doesn't work. // // (1) ShowHideListView did a ShowWindow(SW_SHOW), but // FillDone does a WM_SETREDRAW(FALSE). // check _fListviewRedraw to see if condition (1) is met // // (2) We just showed the listview, if it's the first time, // then Condition (2) has been met // // But wait, there's also a listview bug where SetWindowPos // doesn't trigger it into thinking that the window is visible. // So you have to send a manual WM_SHOWWINDOW, too. // // So if we detect that condition (3) is also met, we temporarily // enable redraw (thereby cancelling condition 1), tell listview // "No really, you're visible" -- this tickles it into computing // column stuff -- then turn redraw back off. // if (_fListviewRedraw && (GetWindowStyle(_hwndListview) & LVS_TYPEMASK) == LVS_LIST) { // Evil hack (fix comctl32.dll v6.0 someday NTRAID#182448) SendMessage(_hwndListview, WM_SETREDRAW, (WPARAM)TRUE, 0); SendMessage(_hwndListview, WM_SHOWWINDOW, TRUE, 0); SendMessage(_hwndListview, WM_SETREDRAW, (WPARAM)FALSE, 0); } // End of listview hack workaround HDSA hdsa = _hdsaSelect; _hdsaSelect = NULL; int cItems = DSA_GetItemCount(hdsa); for (int i = 0; i < cItems; i++) { DELAY_SEL_ITEM *pdvdsi = (DELAY_SEL_ITEM*)DSA_GetItemPtr(hdsa, i); if (pdvdsi) { SelectItem(pdvdsi->pidl, pdvdsi->uFlagsSelect); ILFree(pdvdsi->pidl); } } DSA_Destroy(hdsa); } } HRESULT CDefView::_GetIPersistHistoryObject(IPersistHistory **ppph) { // See to see if specific folder wants to handle it... HRESULT hr = CallCB(SFVM_GETIPERSISTHISTORY, 0, (LPARAM)ppph); if (FAILED(hr)) { // Here we can decide if we want to default should be to always save // the default defview stuff or not. For now we will assume that we do if (ppph) { CDefViewPersistHistory *pdvph = new CDefViewPersistHistory(); if (pdvph) { hr = pdvph->QueryInterface(IID_PPV_ARG(IPersistHistory, ppph)); pdvph->Release(); } else { *ppph = NULL; hr = E_OUTOFMEMORY; } } else hr = S_FALSE; // still succeeds but can detect on other side if desired... } return hr; } STDMETHODIMP CDefView::GetItemObject(UINT uWhat, REFIID riid, void **ppv) { HRESULT hr = E_NOINTERFACE; *ppv = NULL; switch (uWhat & SVGIO_TYPE_MASK) { case SVGIO_BACKGROUND: if (IsEqualIID(riid, IID_IContextMenu) || IsEqualIID(riid, IID_IContextMenu2) || IsEqualIID(riid, IID_IContextMenu3)) { hr = _CBackgrndMenu_CreateInstance(riid, ppv); } else if (IsEqualIID(riid, IID_IDispatch) || IsEqualIID(riid, IID_IDefViewScript)) { if (!_pauto) { // try to create an Instance of the Shell dispatch for folder views... IDispatch *pdisp; if (SUCCEEDED(SHExtCoCreateInstance(NULL, &CLSID_ShellFolderView, NULL, IID_PPV_ARG(IDispatch, &pdisp)))) { SetAutomationObject(pdisp); // we hold a ref here ASSERT(_pauto); // the above grabbed this pdisp->Release(); } } // return the IDispath interface. if (_pauto) hr = _pauto->QueryInterface(riid, ppv); } else if (IsEqualIID(riid, IID_IPersistHistory)) { // See if the folder wants a chance at this. The main // case for this is the search results windows. hr = _GetIPersistHistoryObject((IPersistHistory**)ppv); if (SUCCEEDED(hr)) { IUnknown_SetSite((IUnknown*)*ppv, SAFECAST(this, IShellView2*)); } } else if (_cFrame.IsWebView() && _cFrame._pOleObj) { hr = _cFrame._pOleObj->QueryInterface(riid, ppv); } break; case SVGIO_ALLVIEW: if (_hwndStatic) { DECLAREWAITCURSOR; SetWaitCursor(); do { // If _hwndStatic is around, we must be filling the // view in a background thread, so we will peek for // messages to it (so SendMessages will get through) // and dispatch only _hwndStatic messages so we get the // animation effect. // Note there is no timeout, so this could take // a while on a slow link, but there really isn't // much else I can do MSG msg; // Since _hwndStatic can only be destroyed on a WM_DSV_BACKGROUNDENUMDONE // message, we should never get a RIP // We also need to allow WM_DSV_FILELISTFILLDONE since it can destroy _hwndStatic if (PeekMessage(&msg, _hwndView, WM_DSV_BACKGROUNDENUMDONE, WM_DSV_BACKGROUNDENUMDONE, PM_REMOVE) || PeekMessage(&msg, _hwndView, WM_DSV_FILELISTFILLDONE, WM_DSV_FILELISTFILLDONE, PM_REMOVE) || PeekMessage(&msg, _hwndView, WM_DSV_GROUPINGDONE, WM_DSV_GROUPINGDONE, PM_REMOVE) || PeekMessage(&msg, _hwndStatic, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } while (_hwndStatic); ResetWaitCursor(); } // Fall through case SVGIO_SELECTION: hr = _GetUIObjectFromItem(riid, ppv, uWhat, TRUE); break; } return hr; } HRESULT CDefView::PreCreateInfotip(HWND hwndContaining, UINT_PTR uToolID, LPRECT prectTool) { ASSERT(hwndContaining != NULL); ASSERT(_FindPendingInfotip(hwndContaining, uToolID, NULL, FALSE) == S_FALSE); PENDING_INFOTIP *ppi = new PENDING_INFOTIP; HRESULT hr; if (ppi) { ppi->hwndContaining = hwndContaining; ppi->uToolID = uToolID; ppi->rectTool = *prectTool; if (_tlistPendingInfotips.AddTail(ppi)) { hr = S_OK; } else { hr = E_OUTOFMEMORY; delete ppi; } } else { hr = E_OUTOFMEMORY; } return hr; } HRESULT CDefView::PostCreateInfotip(HWND hwndContaining, UINT_PTR uToolID, HINSTANCE hinst, UINT_PTR uInfotipID, LPARAM lParam) { ASSERT(hwndContaining != NULL); TOOLINFO *pti = new TOOLINFO; HRESULT hr; if (pti) { pti->cbSize = sizeof(TOOLINFO); pti->uFlags = 0; pti->hwnd = hwndContaining; pti->uId = uToolID; //pti->rect = initialized in _OnPostCreateInfotip() pti->hinst = hinst; pti->lpszText = (LPWSTR)uInfotipID; pti->lParam = lParam; hr = PostMessage(_hwndView, WM_DSV_POSTCREATEINFOTIP, (WPARAM)pti, lParam) ? S_OK : E_FAIL; if (FAILED(hr)) { delete pti; } } else { hr = E_OUTOFMEMORY; } return hr; } HRESULT CDefView::PostCreateInfotip(HWND hwndContaining, UINT_PTR uToolID, LPCWSTR pwszInfotip, LPARAM lParam) { HRESULT hr = SHStrDup(pwszInfotip, (LPWSTR *)&pwszInfotip); if (SUCCEEDED(hr)) { hr = PostCreateInfotip(hwndContaining, uToolID, NULL, (UINT_PTR)pwszInfotip, lParam); if (FAILED(hr)) { CoTaskMemFree((LPVOID)pwszInfotip); } } return hr; } HRESULT CDefView::_OnPostCreateInfotip(TOOLINFO *pti, LPARAM lParam) { HRESULT hr = _FindPendingInfotip(pti->hwnd, pti->uId, &pti->rect, TRUE); if (hr == S_OK) { hr = SendMessage(_hwndInfotip, TTM_ADDTOOL, 0, (LPARAM)pti) ? S_OK : E_FAIL; } _OnPostCreateInfotipCleanup(pti); return hr; } HRESULT CDefView::_OnPostCreateInfotipCleanup(TOOLINFO *pti) { if (!pti->hinst) CoTaskMemFree(pti->lpszText); delete pti; return S_OK; } HRESULT CDefView::_FindPendingInfotip(HWND hwndContaining, UINT_PTR uToolID, LPRECT prectTool, BOOL bRemoveAndDestroy) { CLISTPOS posNext = _tlistPendingInfotips.GetHeadPosition(); CLISTPOS posCurrent; PENDING_INFOTIP *ppi; HRESULT hr = S_FALSE; while (posNext) { posCurrent = posNext; ppi = _tlistPendingInfotips.GetNext(posNext); if (ppi->hwndContaining == hwndContaining && ppi->uToolID == uToolID) { if (bRemoveAndDestroy) { if (prectTool) { // Use prectTool as out param. *prectTool = ppi->rectTool; } _tlistPendingInfotips.RemoveAt(posCurrent); delete ppi; } else { if (prectTool) { // Use prectTool as in param. ppi->rectTool = *prectTool; } } hr = S_OK; break; } } // Post Contition -- callers expect only S_OK or S_FALSE. ASSERT(hr == S_OK || hr == S_FALSE); return hr; } HRESULT CDefView::CreateInfotip(HWND hwndContaining, UINT_PTR uToolID, LPRECT prectTool, HINSTANCE hinst, UINT_PTR uInfotipID, LPARAM lParam) { ASSERT(hwndContaining != NULL); // CreateInfotip() is not for use with PreCreateInfotip()/PostCreateInfotip(). ASSERT(_FindPendingInfotip(hwndContaining, uToolID, NULL, FALSE) == S_FALSE); TOOLINFO ti; ti.cbSize = sizeof(TOOLINFO); ti.uFlags = 0; ti.hwnd = hwndContaining; ti.uId = uToolID; ti.rect = *prectTool; ti.hinst = hinst; ti.lpszText = (LPWSTR)uInfotipID; ti.lParam = lParam; return SendMessage(_hwndInfotip, TTM_ADDTOOL, 0, (LPARAM)&ti) ? S_OK : E_FAIL; } HRESULT CDefView::CreateInfotip(HWND hwndContaining, UINT_PTR uToolID, LPRECT prectTool, LPCWSTR pwszInfotip, LPARAM lParam) { return CreateInfotip(hwndContaining, uToolID, prectTool, NULL, (UINT_PTR)pwszInfotip, lParam); } HRESULT CDefView::DestroyInfotip(HWND hwndContaining, UINT_PTR uToolID) { ASSERT(hwndContaining != NULL); if (_FindPendingInfotip(hwndContaining, uToolID, NULL, TRUE) == S_FALSE) { TOOLINFO ti = { 0 }; ti.cbSize = sizeof(ti); ti.hwnd = hwndContaining; ti.uId = uToolID; SendMessage(_hwndInfotip, TTM_DELTOOL, 0, (LPARAM)&ti); } return S_OK; } // Note: // Coordinates in prectTool must be relative to the hwnd in hwndContaining. // HRESULT CDefView::RepositionInfotip(HWND hwndContaining, UINT_PTR uToolID, LPRECT prectTool) { if (_FindPendingInfotip(hwndContaining, uToolID, prectTool, FALSE) == S_FALSE) { TOOLINFO ti = { 0 }; ti.cbSize = sizeof(ti); ti.hwnd = hwndContaining; ti.uId = uToolID; ti.rect = *prectTool; SendMessage(_hwndInfotip, TTM_NEWTOOLRECT, 0, (LPARAM)&ti); } return S_OK; } HRESULT CDefView::RelayInfotipMessage(HWND hwndFrom, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr; if (_hwndInfotip) { MSG msg; msg.hwnd = hwndFrom; msg.message = uMsg; msg.wParam = wParam; msg.lParam = lParam; SendMessage(_hwndInfotip, TTM_RELAYEVENT, 0, (LPARAM)&msg); hr = S_OK; } else { hr = E_FAIL; } return hr; } STDMETHODIMP CDefView::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CDefView, IShellView2), // IID_IShellView2 QITABENTMULTI(CDefView, IShellView, IShellView2), // IID_IShellView QITABENT(CDefView, IViewObject), // IID_IViewObject QITABENT(CDefView, IDropTarget), // IID_IDropTarget QITABENT(CDefView, IShellFolderView), // IID_IShellFolderView QITABENT(CDefView, IFolderView), // IID_IFolderView QITABENT(CDefView, IOleCommandTarget), // IID_IOleCommandTarget QITABENT(CDefView, IServiceProvider), // IID_IServiceProvider QITABENT(CDefView, IDefViewFrame3), // IID_IDefViewFrame QITABENT(CDefView, IDefViewFrame), // IID_IDefViewFrame QITABENT(CDefView, IDocViewSite), // IID_IDocViewSite QITABENT(CDefView, IInternetSecurityMgrSite), // IID_IInternetSecurityMgrSite QITABENT(CDefView, IObjectWithSite), // IID_IObjectWithSite QITABENT(CDefView, IPersistIDList), // IID_IPersistIDList QITABENT(CDefView, IDVGetEnum), // IID_IDVGetEnum QITABENT(CDefView, IContextMenuSite), // IID_IContextMenuSite QITABENT(CDefView, IDefViewSafety), // IID_IDefViewSafety QITABENT(CDefView, IUICommandTarget), // IID_IUICommandTarget { 0 } }; HRESULT hr = QISearch(this, qit, riid, ppvObj); if (FAILED(hr)) { // special case this one as it simply casts this... if (IsEqualIID(riid, IID_CDefView)) { *ppvObj = (void *)this; AddRef(); hr = S_OK; } } return hr; } STDMETHODIMP_(ULONG) CDefView::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CDefView::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } //=========================================================================== // Constructor of CDefView class //=========================================================================== CDefView::CDefView(IShellFolder *psf, IShellFolderViewCB *psfvcb, IShellView *psvOuter) : _cRef(1), _cCallback(psfvcb) { psf->QueryInterface(IID_PPV_ARG(IShellFolder, &_pshf)); psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &_pshf2)); LPITEMIDLIST pidlFull = _GetViewPidl(); if (pidlFull) { LPCITEMIDLIST pidlRelative; if (SUCCEEDED(SHBindToFolderIDListParent(NULL, pidlFull, IID_PPV_ARG(IShellFolder, &_pshfParent), &pidlRelative))) { _pidlRelative = ILClone(pidlRelative); _pshfParent->QueryInterface(IID_PPV_ARG(IShellFolder2, &_pshf2Parent)); } ILFree(pidlFull); } CallCB(SFVM_FOLDERSETTINGSFLAGS, 0, (LPARAM)&_fs.fFlags); _vs.InitWithDefaults(this); _rgbBackColor = CLR_INVALID; _sizeThumbnail.cx = -1; // non init state _iIncrementCat = 1; _wvLayout.dwLayout = -1; // an invalid value // NOTE we dont AddRef() psvOuter // it has a ref on us _psvOuter = psvOuter; // the client needs this info to be able to do anything with us, // so set it REALLY early on in the creation process IUnknown_SetSite(_cCallback.GetSFVCB(), SAFECAST(this, IShellFolderView*)); for (int i = 0; i < ARRAYSIZE(_crCustomColors); i++) _crCustomColors[i] = CLR_MYINVALID; _UpdateRegFlags(); IDLData_InitializeClipboardFormats(); if (SUCCEEDED(CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTaskScheduler2, &_pScheduler)))) { // init a set a 60 second timeout _pScheduler->Status(ITSSFLAG_KILL_ON_DESTROY, DEFVIEW_THREAD_IDLE_TIMEOUT); } // Catch unexpected STACK allocations which would break us. ASSERT(_hwndInfotip == NULL); } STDMETHODIMP CDefView::Init() { HRESULT hr; _hdpaGroupingListActive = DPA_Create(16); _hdpaGroupingListBackup = DPA_Create(16); if (_hdpaGroupingListActive && _hdpaGroupingListBackup) { hr = S_OK; } else { hr = E_OUTOFMEMORY; } return hr; } STDAPI SHCreateShellFolderView(const SFV_CREATE* pcsfv, IShellView ** ppsv) { *ppsv = NULL; HRESULT hr = E_INVALIDARG; if (pcsfv && sizeof(*pcsfv) == pcsfv->cbSize) { CDefView *pdsv = new CDefView(pcsfv->pshf, pcsfv->psfvcb, pcsfv->psvOuter); if (pdsv) { hr = pdsv->Init(); if (SUCCEEDED(hr)) { *ppsv = pdsv; } else { pdsv->Release(); } } } return hr; } void CDVDropTarget::LeaveAndReleaseData() { DragLeave(); } void CDVDropTarget::ReleaseDataObject() { ATOMICRELEASE(_pdtobj); } void CDVDropTarget::ReleaseCurrentDropTarget() { CDefView *pdv = IToClass(CDefView, _dvdt, this); if (_pdtgtCur) { _pdtgtCur->DragLeave(); ATOMICRELEASE(_pdtgtCur); } pdv->_itemCur = -2; // WARNING: Never touch pdv->itemOver in this function. } HRESULT CDVDropTarget::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { CDefView *pdv = IToClass(CDefView, _dvdt, this); IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); // Don't allow a drop from our webview content to ourself! _fIgnoreSource = FALSE; IOleCommandTarget* pct; if (pdv->_cFrame.IsWebView() && SUCCEEDED(pdv->_cFrame.GetCommandTarget(&pct))) { VARIANTARG v = {0}; if (SUCCEEDED(pct->Exec(&CGID_ShellDocView, SHDVID_ISDRAGSOURCE, 0, NULL, &v))) { pct->Release(); if (v.lVal) { *pdwEffect = DROPEFFECT_NONE; _fIgnoreSource = TRUE; return S_OK; } } } g_fDraggingOverSource = FALSE; _grfKeyState = grfKeyState; ASSERT(_pdtgtCur == NULL); // don't really need to do this, but this sets the target state ReleaseCurrentDropTarget(); _itemOver = -2; // // In case of Desktop, we should not lock the enter screen. // HWND hwndLock = pdv->_IsDesktop() ? pdv->_hwndView : pdv->_hwndMain; GetWindowRect(hwndLock, &_rcLockWindow); DAD_DragEnterEx3(hwndLock, ptl, pdtobj); DAD_InitScrollData(&_asd); _ptLast.x = _ptLast.y = 0x7fffffff; // put bogus value to force redraw return S_OK; } #define DVAE_BEFORE 0x01 #define DVAE_AFTER 0x02 // this MUST set pdwEffect to 0 or DROPEFFECT_MOVE if it's a default drag drop // in the same window void CDefView::_AlterEffect(DWORD grfKeyState, DWORD *pdwEffect, UINT uFlags) { g_fDraggingOverSource = FALSE; if (_IsDropOnSource(NULL)) { if (_IsPositionedView()) { // If this is default drag & drop, enable move. if (uFlags & DVAE_AFTER) { if ((grfKeyState & (MK_LBUTTON | MK_CONTROL | MK_SHIFT | MK_ALT)) == MK_LBUTTON) { *pdwEffect = DROPEFFECT_MOVE; g_fDraggingOverSource = TRUE; } else if (grfKeyState & MK_RBUTTON) { *pdwEffect |= DROPEFFECT_MOVE; } } } else { if (uFlags & DVAE_BEFORE) { // No. Disable move. *pdwEffect &= ~DROPEFFECT_MOVE; // default drag & drop, disable all. if ((grfKeyState & (MK_LBUTTON | MK_CONTROL | MK_SHIFT | MK_ALT)) == MK_LBUTTON) { *pdwEffect = 0; } } } } } HRESULT CDVDropTarget::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect) { CDefView *pdv = IToClass(CDefView, _dvdt, this); HRESULT hr = S_OK; DWORD dwEffectScroll = 0; DWORD dwEffectOut = 0; DWORD dwEffectOutToCache; BOOL fSameImage = FALSE; if (_fIgnoreSource) { // for parity with win2k behavior, we need to bail out from DragOver // if we hit the SHDVID_ISDRAGSOURCE in DragEnter. // this is so when you have a stretched background in active desktop and // show desktop icons is off, when you drag the background image around // you'll get DROPEFFECT_NONE instead of a bad DROPEFFECT_COPY. *pdwEffect = DROPEFFECT_NONE; return S_OK; } POINT pt = {ptl.x, ptl.y}; // in screen coords RECT rc; GetWindowRect(pdv->_hwndListview, &rc); BOOL fInRect = PtInRect(&rc, pt); ScreenToClient(pdv->_hwndListview, &pt); // now in client // assume coords of our window match listview if (DAD_AutoScroll(pdv->_hwndListview, &_asd, &pt)) dwEffectScroll = DROPEFFECT_SCROLL; // hilight an item, or unhilight all items (DropTarget returns -1) int itemNew = fInRect ? pdv->_HitTest(&pt, TRUE) : -1; // If we are dragging over on a different item, get its IDropTarget // interface or adjust itemNew to -1. if (_itemOver != itemNew) { IDropTarget *pdtgtNew = NULL; _dwLastTime = GetTickCount(); // keep track for auto-expanding the tree _itemOver = itemNew; // Avoid dropping onto drag source objects. if ((itemNew != -1) && pdv->_bDragSource) { UINT uState = ListView_GetItemState(pdv->_hwndListview, itemNew, LVIS_SELECTED); if (uState & LVIS_SELECTED) itemNew = -1; } // If we are dragging over an item, try to get its IDropTarget. if (itemNew != -1) { // We are dragging over an item. LPCITEMIDLIST apidl[1] = { pdv->_GetPIDL(itemNew) }; if (apidl[0]) { pdv->_pshf->GetUIObjectOf(pdv->_hwndMain, 1, apidl, IID_PPV_ARG_NULL(IDropTarget, &pdtgtNew)); ASSERT(itemNew != pdv->_itemCur); // MUST not be the same } if (pdtgtNew == NULL) { // If the item is not a drop target, don't hightlight it // treat it as transparent. itemNew = -1; } } // If the new target is different from the current one, switch it. if (pdv->_itemCur != itemNew) { // Release previous drop target, if any. ReleaseCurrentDropTarget(); ASSERT(_pdtgtCur==NULL); // Update pdv->_itemCur which indicates the current target. // (Note that it might be different from _itemOver). pdv->_itemCur = itemNew; // If we are dragging over the background or over non-sink item, // get the drop target for the folder. if (itemNew == -1) { // We are dragging over the background, this can be NULL ASSERT(pdtgtNew == NULL); _pdtgtCur = pdv->_pdtgtBack; if (_pdtgtCur) _pdtgtCur->AddRef(); } else { ASSERT(pdtgtNew); _pdtgtCur = pdtgtNew; } // Hilight the sink item (itemNew != -1) or unhilight all (-1). LVUtil_DragSelectItem(pdv->_hwndListview, itemNew); // Call IDropTarget::DragEnter of the target object. if (_pdtgtCur) { // pdwEffect is in/out parameter. dwEffectOut = *pdwEffect; // pdwEffect in // Special case if we are dragging within a source window pdv->_AlterEffect(grfKeyState, &dwEffectOut, DVAE_BEFORE); hr = _pdtgtCur->DragEnter(_pdtobj, grfKeyState, ptl, &dwEffectOut); pdv->_AlterEffect(grfKeyState, &dwEffectOut, DVAE_AFTER); } else { ASSERT(dwEffectOut==0); pdv->_AlterEffect(grfKeyState, &dwEffectOut, DVAE_BEFORE | DVAE_AFTER); } TraceMsg(TF_DEFVIEW, "CDV::DragOver dwEIn=%x, dwEOut=%x", *pdwEffect, dwEffectOut); } else { ASSERT(pdtgtNew == NULL); // It must be NULL goto NoChange; } // Every time we're over a new item, record this information so we can handle the insertmark. _fItemOverNotADropTarget = (itemNew == -1); } else { NoChange: if (_itemOver != -1) { DWORD dwNow = GetTickCount(); if ((dwNow - _dwLastTime) >= 1000) { _dwLastTime = dwNow; // DAD_ShowDragImage(FALSE); // OpenItem(pdv, _itemOver); // DAD_ShowDragImage(TRUE); } } // // No change in the selection. We assume that *pdwEffect stays // the same during the same drag-loop as long as the key state doesn't change. // if ((_grfKeyState != grfKeyState) && _pdtgtCur) { // Note that pdwEffect is in/out parameter. dwEffectOut = *pdwEffect; // pdwEffect in // Special case if we are dragging within a source window pdv->_AlterEffect(grfKeyState, &dwEffectOut, DVAE_BEFORE); hr = _pdtgtCur->DragOver(grfKeyState, ptl, &dwEffectOut); pdv->_AlterEffect(grfKeyState, &dwEffectOut, DVAE_AFTER); } else { // Same item and same key state. Use the previous dwEffectOut. dwEffectOut = _dwEffectOut; fSameImage = TRUE; hr = S_OK; } } // Cache the calculated dwEffectOut (BEFORE making local modifications below). dwEffectOutToCache = dwEffectOut; // Activate/deactivate insertmark, if appropriate. LVINSERTMARK lvim = { sizeof(LVINSERTMARK), 0, -1, 0 }; if (_fItemOverNotADropTarget) { // Only do the insertion mark stuff if we're in a view mode that makes sense for these: if (pdv->_IsAutoArrange() || (pdv->_fs.fFlags & FWF_SNAPTOGRID)) { ListView_InsertMarkHitTest(pdv->_hwndListview, &pt, &lvim); if (pdv->_bDragSource && pdv->_IsAutoArrange() && (lvim.iItem == -1)) { // a "move" drop here won't do anything so set the effect appropriately if (dwEffectOut & DROPEFFECT_MOVE) { // fall back to "copy" drop effect (if supported) if (*pdwEffect & DROPEFFECT_COPY) { dwEffectOut |= DROPEFFECT_COPY; } // fall back to "link" drop effect (if supported) else if (*pdwEffect & DROPEFFECT_LINK) { dwEffectOut |= DROPEFFECT_LINK; } // fall back to no drop effect dwEffectOut &= ~DROPEFFECT_MOVE; } // NOTE: a DROPEFFECT_MOVE still comes through the ::Drop for a left-drop... // we might want to remember that we're exclududing move (_bDragSourceDropOnDragItem) } } } ListView_SetInsertMark(pdv->_hwndListview, &lvim); _grfKeyState = grfKeyState; // store these for the next Drop _dwEffectOut = dwEffectOutToCache; // and DragOver // OLE does not call IDropTarget::Drop if we return something // valid. We force OLE call it by returning DROPEFFECT_SCROLL. if (g_fDraggingOverSource) dwEffectScroll = DROPEFFECT_SCROLL; *pdwEffect = dwEffectOut | dwEffectScroll; // pdwEffect out if (!(fSameImage && pt.x == _ptLast.x && pt.y == _ptLast.y)) { HWND hwndLock = pdv->_IsDesktop() ? pdv->_hwndView : pdv->_hwndMain; DAD_DragMoveEx(hwndLock, ptl); _ptLast.x = ptl.x; _ptLast.y = ptl.y; } return hr; } HRESULT CDVDropTarget::DragLeave() { CDefView *pdv = IToClass(CDefView, _dvdt, this); // // Make it possible to call it more than necessary. // if (_pdtobj) { TraceMsg(TF_DEFVIEW, "CDVDropTarget::DragLeave"); ReleaseCurrentDropTarget(); _itemOver = -2; ReleaseDataObject(); DAD_DragLeave(); LVUtil_DragSelectItem(pdv->_hwndListview, -1); } g_fDraggingOverSource = FALSE; ASSERT(_pdtgtCur == NULL); ASSERT(_pdtobj == NULL); LVINSERTMARK lvim = { sizeof(LVINSERTMARK), 0, -1, 0 }; // clear insert mark (-1) ListView_SetInsertMark(pdv->_hwndListview, &lvim); return S_OK; } HRESULT CDVDropTarget::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { CDefView *pdv = IToClass(CDefView, _dvdt, this); IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); pdv->_ptDrop.x = pt.x; pdv->_ptDrop.y = pt.y; ScreenToClient(pdv->_hwndListview, &pdv->_ptDrop); // // handle moves within the same window here. // depend on _AlterEffect forcing in DROPEFFECT_MOVE and only // dropeffect move when drag in same window // // Notes: We need to use _grfKeyState instead of grfKeyState // to see if the left mouse was used or not during dragging. // pdv->_AlterEffect(_grfKeyState, pdwEffect, DVAE_BEFORE | DVAE_AFTER); if ((_grfKeyState & MK_LBUTTON) && (*pdwEffect == DROPEFFECT_MOVE) && (pdv->_IsDropOnSource(NULL))) { // This means we are left-dropping on ourselves, so we just move // the icons. DAD_DragLeave(); pdv->_SameViewMoveIcons(); SetForegroundWindow(pdv->_hwndMain); ASSERT(pdv->_bDropAnchor == FALSE); *pdwEffect = 0; // the underlying objects didn't 'move' anywhere ReleaseCurrentDropTarget(); } else if (_pdtgtCur) { // use this local because if pdtgtCur::Drop does a UnlockWindow // then hits an error and needs to put up a dialog, // we could get re-entered and clobber the defview's pdtgtCur IDropTarget *pdtgtCur = _pdtgtCur; _pdtgtCur = NULL; // // HACK ALERT!!!! // // If we don't call LVUtil_DragEnd here, we'll be able to leave // dragged icons visible when the menu is displayed. However, because // we are calling IDropTarget::Drop() which may create some modeless // dialog box or something, we can not ensure the locked state of // the list view -- LockWindowUpdate() can lock only one window at // a time. Therefore, we skip this call only if the pdtgtCur // is a subclass of CIDLDropTarget, assuming its Drop calls // CDefView::DragEnd (or CIDLDropTarget_DragDropMenu) appropriately. // pdv->_bDropAnchor = TRUE; if (!DoesDropTargetSupportDAD(pdtgtCur)) { // This will hide the dragged image. DAD_DragLeave(); // reset the drag image list so that the user // can start another drag&drop while we are in this // Drop() member function call. DAD_SetDragImage(NULL, NULL); } // Special case if we are dragging within a source window pdv->_AlterEffect(grfKeyState, pdwEffect, DVAE_BEFORE | DVAE_AFTER); IUnknown_SetSite(pdtgtCur, SAFECAST(pdv, IShellView2*)); pdtgtCur->Drop(pdtobj, grfKeyState, pt, pdwEffect); IUnknown_SetSite(pdtgtCur, NULL); pdtgtCur->Release(); DAD_DragLeave(); pdv->_bDropAnchor = FALSE; } else { // We come here if Drop is called without DragMove (with DragEnter). *pdwEffect = 0; } DragLeave(); // DoDragDrop does not call DragLeave() after Drop() return S_OK; } BOOL CDefView::_IsBkDropTarget(IDropTarget *pdtg) { BOOL fRet = FALSE; if (_bContextMenuMode) { if (ListView_GetSelectedCount(_hwndListview) == 0) { fRet = TRUE; } } POINT pt; if (!fRet) { if (_GetInsertPoint(&pt)) // If there is an insert point, then the background is the drop target. return TRUE; if (_GetDropPoint(&pt)) { // The Drop point is returned in internal listview coordinates // space, so we need to convert it back to client space // before we call this function... LVUtil_LVToClient(_hwndListview, &pt); if (_HitTest(&pt) == -1) { fRet = TRUE; } } } return fRet; } // IShellFolderView::Rearrange STDMETHODIMP CDefView::Rearrange(LPARAM lParamSort) { return _OnRearrange(lParamSort, TRUE); } // end user initiated arrange (click on col header, etc) HRESULT CDefView::_OnRearrange(LPARAM lParamSort, BOOL fAllowToggle) { DECLAREWAITCURSOR; _vs._iLastColumnClick = (int) _vs._lParamSort; _vs._lParamSort = lParamSort; // toggle the direction of the sort if on the same column if (fAllowToggle && !_IsPositionedView() && _vs._iLastColumnClick == (int) lParamSort) _vs._iDirection = -_vs._iDirection; else _vs._iDirection = 1; SetWaitCursor(); HRESULT hr = _Sort(); // reset to the state that no items have been moved if currently in a positioned mode // so auto-arraning works. if (_IsPositionedView()) { _ClearItemPositions(); } ResetWaitCursor(); return hr; } STDMETHODIMP CDefView::ArrangeGrid() { _OnCommand(NULL, GET_WM_COMMAND_MPS(SFVIDM_ARRANGE_GRID, 0, 0)); return S_OK; } STDMETHODIMP CDefView::AutoArrange() { _OnCommand(NULL, GET_WM_COMMAND_MPS(SFVIDM_ARRANGE_AUTO, 0, 0)); return S_OK; } STDMETHODIMP CDefView::GetArrangeParam(LPARAM *plParamSort) { *plParamSort = _vs._lParamSort; return S_OK; } STDMETHODIMP CDefView::AddObject(LPITEMIDLIST pidl, UINT *puItem) { LPITEMIDLIST pidlCopy = ILClone(pidl); if (pidlCopy) { *puItem = _AddObject(pidlCopy); // takes pidl ownership. } else { *puItem = (UINT)-1; } // must cast to "int" because UINTs are never negative so we would // otherwise never be able to detect failure return (int)*puItem >= 0 ? S_OK : E_OUTOFMEMORY; } STDMETHODIMP CDefView::GetObjectCount(UINT *puCount) { *puCount = ListView_GetItemCount(_hwndListview); return S_OK; } STDMETHODIMP CDefView::SetObjectCount(UINT uCount, UINT dwFlags) { // Mask over to the flags that map directly accross DWORD dw = dwFlags & SFVSOC_NOSCROLL; UINT uCountOld = 0; GetObjectCount(&uCountOld); if ((dwFlags & SFVSOC_INVALIDATE_ALL) == 0) dw |= LVSICF_NOINVALIDATEALL; // gross transform HRESULT hr = (HRESULT)SendMessage(_hwndListview, LVM_SETITEMCOUNT, (WPARAM)uCount, (LPARAM)dw); // Notify automation if we're going from 0 to 1 or more items if (!uCountOld && uCount) { _PostNoItemStateChangedMessage(); } return hr; } STDMETHODIMP CDefView::GetObject(LPITEMIDLIST *ppidl, UINT uItem) { // Worse hack, if -42 then return our own pidl... if (uItem == (UINT)-42) { *ppidl = (LPITEMIDLIST)_pidlMonitor; return *ppidl ? S_OK : E_UNEXPECTED; } // Hack, if item is -2, this implies return the focused item if (uItem == (UINT)-2) uItem = ListView_GetNextItem(_hwndListview, -1, LVNI_FOCUSED); *ppidl = (LPITEMIDLIST)_GetPIDL(uItem); // cast due to bad interface def return *ppidl ? S_OK : E_UNEXPECTED; } STDMETHODIMP CDefView::RemoveObject(LPITEMIDLIST pidl, UINT *puItem) { *puItem = _RemoveObject(pidl, FALSE); // must cast to "int" because UINTs are never negative so we would // otherwise never be able to detect failure return (int)*puItem >= 0 ? S_OK : E_INVALIDARG; } STDMETHODIMP CDefView::UpdateObject(LPITEMIDLIST pidlOld, LPITEMIDLIST pidlNew, UINT *puItem) { *puItem = _UpdateObject(pidlOld, pidlNew); return (int)(*puItem) >= 0 ? S_OK : E_INVALIDARG; } STDMETHODIMP CDefView::RefreshObject(LPITEMIDLIST pidl, UINT *puItem) { *puItem = _RefreshObject(&pidl); // must cast to "int" because UINTs are never negative so we would // otherwise never be able to detect failure return (int)*puItem >= 0 ? S_OK : E_INVALIDARG; } STDMETHODIMP CDefView::SetRedraw(BOOL bRedraw) { SendMessage(_hwndListview, WM_SETREDRAW, (WPARAM)bRedraw, 0); return S_OK; } STDMETHODIMP CDefView::GetSelectedObjects(LPCITEMIDLIST **pppidl, UINT *puItems) { return _GetItemObjects(pppidl, SVGIO_SELECTION, puItems); } STDMETHODIMP CDefView::GetSelectedCount(UINT *puSelected) { *puSelected = ListView_GetSelectedCount(_hwndListview); return S_OK; } BOOL CDefView::_IsDropOnSource(IDropTarget *pdtgt) { // context menu paste (_bMouseMenu shows context menu, cut stuff shows source) if (_bMouseMenu && _bHaveCutStuff) { int iItem = ListView_GetNextItem(_hwndListview, -1, LVNI_SELECTED); if (iItem == -1) return TRUE; } if (_itemCur != -1 || !_bDragSource) { // We did not drag onto the background of the source return FALSE; } return TRUE; } STDMETHODIMP CDefView::IsDropOnSource(IDropTarget *pDropTarget) { return _IsDropOnSource(pDropTarget) ? S_OK : S_FALSE; } STDMETHODIMP CDefView::MoveIcons(IDataObject *pdtobj) { return E_NOTIMPL; } STDMETHODIMP CDefView::GetDropPoint(POINT *ppt) { return _GetDropPoint(ppt) ? S_OK : S_FALSE; } STDMETHODIMP CDefView::GetDragPoint(POINT *ppt) { return _GetDragPoint(ppt) ? S_OK : S_FALSE; } STDMETHODIMP CDefView::SetItemPos(LPCITEMIDLIST pidl, POINT *ppt) { SFV_SETITEMPOS sip; sip.pidl = pidl; sip.pt = *ppt; _SetItemPos(&sip); return S_OK; } STDMETHODIMP CDefView::IsBkDropTarget(IDropTarget *pDropTarget) { return _IsBkDropTarget(pDropTarget) ? S_OK : S_FALSE; } STDMETHODIMP CDefView::SetClipboard(BOOL bMove) { _OnSetClipboard(bMove); // do this always, even if not current active view return S_OK; } // defcm.cpp asks us to setup the points of the currently selected objects // into the data object on Copy/Cut commands STDMETHODIMP CDefView::SetPoints(IDataObject *pdtobj) { LPCITEMIDLIST *apidl; UINT cItems; HRESULT hr = GetSelectedObjects(&apidl, &cItems); if (SUCCEEDED(hr) && cItems) { _SetPoints(cItems, apidl, pdtobj); LocalFree((HLOCAL)apidl); } return hr; } STDMETHODIMP CDefView::GetItemSpacing(ITEMSPACING *pSpacing) { return _GetItemSpacing(pSpacing) ? S_OK : S_FALSE; } STDMETHODIMP CDefView::SetCallback(IShellFolderViewCB* pNewCB, IShellFolderViewCB** ppOldCB) { *ppOldCB = NULL; return _cCallback.SetCallback(pNewCB, ppOldCB); } const UINT c_rgiSelectFlags[][2] = { { SFVS_SELECT_ALLITEMS, SFVIDM_SELECT_ALL }, { SFVS_SELECT_NONE, SFVIDM_DESELECT_ALL }, { SFVS_SELECT_INVERT, SFVIDM_SELECT_INVERT } }; STDMETHODIMP CDefView::Select(UINT dwFlags) { // translate the flag into the menu ID for (int i = 0; i < ARRAYSIZE(c_rgiSelectFlags); i++) { if (c_rgiSelectFlags[i][0] == dwFlags) { return (HRESULT)_OnCommand(NULL, c_rgiSelectFlags[i][1], 0); } } return E_INVALIDARG; } STDMETHODIMP CDefView::QuerySupport(UINT * pdwSupport) { // *pdwSupport is an in/out param, we leave the out == in return S_OK; // DefView supports all the operations... } STDMETHODIMP CDefView::SetAutomationObject(IDispatch *pdisp) { // release back pointers IUnknown_SetOwner(_pauto, NULL); IUnknown_SetSite(_pauto, NULL); IUnknown_Set((IUnknown **)&_pauto, pdisp); // hold or free _pauto // this connects the automation object to our view, so it can implement // stuff like "SelectedItems" IUnknown_SetOwner(_pauto, SAFECAST(this, IShellFolderView *)); // use the browser as the site so OM related QueryService calls will find // the browser above us as the place to do security checks instead of defivew // this is stuff that depends on the zone of the caller as the security check IUnknown_SetSite(_pauto, _psb); return S_OK; } STDMETHODIMP CDefView::SelectAndPositionItems(UINT cidl, LPCITEMIDLIST* apidl, POINT* apt, DWORD dwFlags) { for (UINT i = 0; i < cidl; i++) SelectAndPositionItem(apidl[i], dwFlags, apt ? &apt[i] : NULL); return S_OK; } // -------------- auto scroll stuff -------------- BOOL _AddTimeSample(AUTO_SCROLL_DATA *pad, const POINT *ppt, DWORD dwTime) { pad->pts[pad->iNextSample] = *ppt; pad->dwTimes[pad->iNextSample] = dwTime; pad->iNextSample++; if (pad->iNextSample == ARRAYSIZE(pad->pts)) pad->bFull = TRUE; pad->iNextSample = pad->iNextSample % ARRAYSIZE(pad->pts); return pad->bFull; } #ifdef DEBUG // for debugging, verify we have good averages DWORD g_time = 0; int g_distance = 0; #endif int _CurrentVelocity(AUTO_SCROLL_DATA *pad) { int i, iStart, iNext; int dx, dy, distance; DWORD time; ASSERT(pad->bFull); distance = 0; time = 1; // avoid div by zero i = iStart = pad->iNextSample % ARRAYSIZE(pad->pts); do { iNext = (i + 1) % ARRAYSIZE(pad->pts); dx = abs(pad->pts[i].x - pad->pts[iNext].x); dy = abs(pad->pts[i].y - pad->pts[iNext].y); distance += (dx + dy); time += abs(pad->dwTimes[i] - pad->dwTimes[iNext]); i = iNext; } while (i != iStart); #ifdef DEBUG g_time = time; g_distance = distance; #endif // scale this so we don't loose accuracy return (distance * 1024) / time; } // NOTE: this is duplicated in shell32.dll // // checks to see if we are at the end position of a scroll bar // to avoid scrolling when not needed (avoid flashing) // // in: // code SB_VERT or SB_HORZ // bDown FALSE is up or left // TRUE is down or right BOOL CanScroll(HWND hwnd, int code, BOOL bDown) { SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = (SIF_RANGE | SIF_PAGE | SIF_POS); GetScrollInfo(hwnd, code, &si); if (bDown) { if (si.nPage) si.nMax -= si.nPage - 1; return si.nPos < si.nMax; } else { return si.nPos > si.nMin; } } #define DSD_NONE 0x0000 #define DSD_UP 0x0001 #define DSD_DOWN 0x0002 #define DSD_LEFT 0x0004 #define DSD_RIGHT 0x0008 DWORD DAD_DragScrollDirection(HWND hwnd, const POINT *ppt) { RECT rcOuter, rc; DWORD dwDSD = DSD_NONE; DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE); #define g_cxVScroll GetSystemMetrics(SM_CXVSCROLL) #define g_cyHScroll GetSystemMetrics(SM_CYHSCROLL) GetClientRect(hwnd, &rc); if (dwStyle & WS_HSCROLL) rc.bottom -= g_cyHScroll; if (dwStyle & WS_VSCROLL) rc.right -= g_cxVScroll; // the explorer forwards us drag/drop things outside of our client area // so we need to explictly test for that before we do things // rcOuter = rc; InflateRect(&rcOuter, g_cxSmIcon, g_cySmIcon); InflateRect(&rc, -g_cxIcon, -g_cyIcon); if (!PtInRect(&rc, *ppt) && PtInRect(&rcOuter, *ppt)) { // Yep - can we scroll? if (dwStyle & WS_HSCROLL) { if (ppt->x < rc.left) { if (CanScroll(hwnd, SB_HORZ, FALSE)) dwDSD |= DSD_LEFT; } else if (ppt->x > rc.right) { if (CanScroll(hwnd, SB_HORZ, TRUE)) dwDSD |= DSD_RIGHT; } } if (dwStyle & WS_VSCROLL) { if (ppt->y < rc.top) { if (CanScroll(hwnd, SB_VERT, FALSE)) dwDSD |= DSD_UP; } else if (ppt->y > rc.bottom) { if (CanScroll(hwnd, SB_VERT, TRUE)) dwDSD |= DSD_DOWN; } } } return dwDSD; } #define SCROLL_FREQUENCY (GetDoubleClickTime()/2) // 1 line scroll every 1/4 second #define MIN_SCROLL_VELOCITY 20 // scaled mouse velocity BOOL WINAPI DAD_AutoScroll(HWND hwnd, AUTO_SCROLL_DATA *pad, const POINT *pptNow) { // first time we've been called, init our state int v; DWORD dwTimeNow = GetTickCount(); DWORD dwDSD = DAD_DragScrollDirection(hwnd, pptNow); if (!_AddTimeSample(pad, pptNow, dwTimeNow)) return dwDSD; v = _CurrentVelocity(pad); if (v <= MIN_SCROLL_VELOCITY) { // Nope, do some scrolling. if ((dwTimeNow - pad->dwLastScroll) < SCROLL_FREQUENCY) dwDSD = 0; if (dwDSD & DSD_UP) { DAD_ShowDragImage(FALSE); FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEUP, 1, SendMessage); } else if (dwDSD & DSD_DOWN) { DAD_ShowDragImage(FALSE); FORWARD_WM_VSCROLL(hwnd, NULL, SB_LINEDOWN, 1, SendMessage); } if (dwDSD & DSD_LEFT) { DAD_ShowDragImage(FALSE); FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEUP, 1, SendMessage); } else if (dwDSD & DSD_RIGHT) { DAD_ShowDragImage(FALSE); FORWARD_WM_HSCROLL(hwnd, NULL, SB_LINEDOWN, 1, SendMessage); } DAD_ShowDragImage(TRUE); if (dwDSD) { TraceMsg(TF_DEFVIEW, "v=%d", v); pad->dwLastScroll = dwTimeNow; } } return dwDSD; // bits set if in scroll region } // warning: global data holding COM objects that may span apartment boundaries // be very careful HDSA g_hdsaDefViewCopyHook = NULL; typedef struct { HWND hwndView; CDefView *pdv; } DVCOPYHOOK; void CDefView::AddCopyHook() { ENTERCRITICAL; if (!g_hdsaDefViewCopyHook) { g_hdsaDefViewCopyHook = DSA_Create(sizeof(DVCOPYHOOK), 4); TraceMsg(TF_DEFVIEW, "AddCopyHook creating the dsa"); } if (g_hdsaDefViewCopyHook) { DVCOPYHOOK dvch = { _hwndView, this }; ASSERT(dvch.hwndView); if (DSA_AppendItem(g_hdsaDefViewCopyHook, &dvch)!=-1) { AddRef(); TraceMsg(TF_DEFVIEW, "AddCopyHook successfully added (total=%d)", DSA_GetItemCount(g_hdsaDefViewCopyHook)); } } LEAVECRITICAL; } int CDefView::FindCopyHook(BOOL fRemoveInvalid) { ASSERTCRITICAL; if (g_hdsaDefViewCopyHook) { int item = DSA_GetItemCount(g_hdsaDefViewCopyHook); while (--item >= 0) { const DVCOPYHOOK *pdvch = (const DVCOPYHOOK *)DSA_GetItemPtr(g_hdsaDefViewCopyHook, item); if (pdvch) { if (fRemoveInvalid) { if (!IsWindow(pdvch->hwndView)) { TraceMsg(TF_WARNING, "FindCopyHook: found a invalid element, removing..."); DSA_DeleteItem(g_hdsaDefViewCopyHook, item); continue; } } if ((pdvch->hwndView == _hwndView) && (pdvch->pdv == this)) { return item; } } else { ASSERT(0); } } } return -1; // not found } void CDefView::RemoveCopyHook() { IShellView *psv = NULL; ENTERCRITICAL; if (g_hdsaDefViewCopyHook) { int item = FindCopyHook(TRUE); if (item != -1) { DVCOPYHOOK *pdvch = (DVCOPYHOOK *)DSA_GetItemPtr(g_hdsaDefViewCopyHook, item); psv = pdvch->pdv; TraceMsg(TF_DEFVIEW, "RemoveCopyHook removing an element"); DSA_DeleteItem(g_hdsaDefViewCopyHook, item); // // If this is the last guy, destroy it. // if (DSA_GetItemCount(g_hdsaDefViewCopyHook) == 0) { TraceMsg(TF_DEFVIEW, "RemoveCopyHook destroying hdsa (no element)"); DSA_Destroy(g_hdsaDefViewCopyHook); g_hdsaDefViewCopyHook = NULL; } } } LEAVECRITICAL; // // Release it outside the critical section. // ATOMICRELEASE(psv); } STDAPI_(UINT) DefView_CopyHook(const COPYHOOKINFO *pchi) { UINT idRet = IDYES; if (g_hdsaDefViewCopyHook==NULL) { return idRet; } for (int item = 0; ; item++) { DVCOPYHOOK dvch = { NULL, NULL }; // We should minimize this critical section (and must not // call pfnCallBack which may popup UI!). ENTERCRITICAL; if (g_hdsaDefViewCopyHook && DSA_GetItem(g_hdsaDefViewCopyHook, item, &dvch)) { dvch.pdv->AddRef(); } LEAVECRITICAL; if (dvch.pdv) { if (IsWindow(dvch.hwndView)) { HRESULT hr = dvch.pdv->CallCB(SFVM_NOTIFYCOPYHOOK, 0, (LPARAM)pchi); ATOMICRELEASE(dvch.pdv); if (SUCCEEDED(hr) && (hr != S_OK)) { idRet = HRESULT_CODE(hr); ASSERT(idRet==IDYES || idRet==IDCANCEL || idRet==IDNO); break; } item++; } else { TraceMsg(TF_DEFVIEW, "DefView_CopyHook list has an invalid element"); ATOMICRELEASE(dvch.pdv); } } else { break; // no more item. } } return idRet; } // IOleCommandTarget stuff - just forward to the webview STDMETHODIMP CDefView::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext) { HRESULT hr = OLECMDERR_E_UNKNOWNGROUP; BOOL fQSCalled = FALSE; if (_cFrame.IsWebView()) { IOleCommandTarget* pct; if (SUCCEEDED(_cFrame.GetCommandTarget(&pct))) { hr = pct->QueryStatus(pguidCmdGroup, cCmds, rgCmds, pcmdtext); fQSCalled = SUCCEEDED(hr); pct->Release(); } } if (pguidCmdGroup == NULL) { if (rgCmds == NULL) return E_INVALIDARG; for (UINT i = 0; i < cCmds; i++) { // ONLY say that we support the stuff we support in ::OnExec switch (rgCmds[i].cmdID) { case OLECMDID_REFRESH: rgCmds[i].cmdf = OLECMDF_ENABLED; break; default: // don't disable if the webview has already answered if (!fQSCalled) { rgCmds[i].cmdf = 0; } break; } } } else if (IsEqualGUID(_clsid, *pguidCmdGroup)) { if (pcmdtext) { switch (pcmdtext->cmdtextf) { case OLECMDTEXTF_NAME: // It's a query for the button tooltip text. ASSERT(cCmds == 1); _GetToolTipText(rgCmds[0].cmdID, pcmdtext->rgwz, pcmdtext->cwBuf); // ensure NULL termination pcmdtext->rgwz[pcmdtext->cwBuf - 1] = 0; pcmdtext->cwActual = lstrlenW(pcmdtext->rgwz); hr = S_OK; break; default: hr = E_FAIL; break; } } else { DWORD dwAttr = _AttributesFromSel(SFGAO_RELEVANT); for (UINT i = 0; i < cCmds; i++) { if (_ShouldEnableToolbarButton(rgCmds[i].cmdID, dwAttr, -1)) rgCmds[i].cmdf = OLECMDF_ENABLED; else rgCmds[i].cmdf = 0; } hr = S_OK; } } return hr; } STDMETHODIMP CDefView::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { HRESULT hr; // Hold a ref to ourselves on Exec. In the camera name space if the view context menu is up when the camera // is unplugged, explorer faults because the view is torn down and the context menu exec tries to unwind // after defview is gone. This holds a ref on defview while in exec so defview doesn't dissappear. // AddRef(); hr = _Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvarargIn, pvarargOut); Release(); return hr; } HRESULT CDefView::_Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut) { HRESULT hr = OLECMDERR_E_UNKNOWNGROUP; if (pguidCmdGroup == NULL) { switch (nCmdID) { case OLECMDID_REFRESH: _fAllowSearchingWindow = TRUE; // this exec typically comes from a user action (F5, Refresh) if (FAILED(_ReloadContent())) { //This invalidation deletes the WebView and also avoid //unpainted areas in ListView areas whose paint messages //are eaten by the visible WebView InvalidateRect(_hwndView, NULL, TRUE); } hr = S_OK; break; } } else if (IsEqualGUID(CGID_DefView, *pguidCmdGroup)) { switch (nCmdID) { case DVID_SETASDEFAULT: // 99/02/05 #226140 vtan: Exec command issued from // CShellBrowser2::_SaveDefViewDefaultFolderSettings() // when user clicks "Like Current Folder" in folder // options "View" tab. ASSERTMSG(nCmdexecopt == OLECMDEXECOPT_DODEFAULT, "nCmdexecopt must be OLECMDEXECOPT_DODEFAULT"); ASSERTMSG(pvarargIn == NULL, "pvarargIn must be NULL"); ASSERTMSG(pvarargOut == NULL, "pvarargOut must be NULL"); hr = _SaveGlobalViewState(); break; case DVID_RESETDEFAULT: // 99/02/05 #226140 vtan: Exec command issued from // CShellBrowser2::_ResetDefViewDefaultFolderSettings() // when user clicks "Reset All Folders" in folder // options "View" tab. ASSERTMSG(nCmdexecopt == OLECMDEXECOPT_DODEFAULT, "nCmdexecopt must be OLECMDEXECOPT_DODEFAULT"); ASSERTMSG(pvarargIn == NULL, "pvarargIn must be NULL"); ASSERTMSG(pvarargOut == NULL, "pvarargOut must be NULL"); hr = _ResetGlobalViewState(); break; default: break; } } else if (IsEqualGUID(CGID_ShellDocView, *pguidCmdGroup)) { switch (nCmdID) { case SHDVID_CANACTIVATENOW: return _fCanActivateNow ? S_OK : S_FALSE; // NOTE: for a long time IOleCommandTarget was implemented // BUT it wasn't in the QI! At this late stage of the game // I'll be paranoid and not forward everything down to the // webview. We'll just pick off CANACTIVATENOW... // default: return OLECMDERR_E_UNKNOWNGROUP; } } else if (IsEqualGUID(CGID_Explorer, *pguidCmdGroup)) { switch (nCmdID) { case SBCMDID_GETPANE: V_I4(pvarargOut) = PANE_NONE; CallCB(SFVM_GETPANE, nCmdexecopt, (LPARAM)&V_I4(pvarargOut)); return S_OK; case SBCMDID_MIXEDZONE: if (pvarargOut) return _cFrame._GetCurrentZone(NULL, pvarargOut); break; default: break; } } else if (IsEqualGUID(IID_IExplorerToolbar, *pguidCmdGroup)) { // handle the ones coming FROM itbar: switch (nCmdID) { case ETCMDID_GETBUTTONS: pvarargOut->vt = VT_BYREF; pvarargOut->byref = (void *)_pbtn; *pvarargIn->plVal = _cTotalButtons; return S_OK; case ETCMDID_RELOADBUTTONS: MergeToolBar(TRUE); return S_OK; } } else if (IsEqualGUID(_clsid, *pguidCmdGroup)) { UEMFireEvent(&UEMIID_BROWSER, UEME_UITOOLBAR, UEMF_XEVENT, UIG_OTHER, nCmdID); DFVCMDDATA cd; cd.pva = pvarargIn; cd.hwnd = _hwndMain; cd.nCmdIDTranslated = 0; _OnCommand(NULL, nCmdID, (LPARAM)&cd); } // no need to pass OLECMDID_REFRESH on to the webview, as we // just nuked and replaced the webview above -- a super refresh of sorts. if (_cFrame.IsWebView() && hr != S_OK) { // Do not pass IDM_PARSECOMPLETE back to MSHTML. This will cause them to load mshtmled.dll // unecessarily for webview which is a significant performance hit. if (!(pguidCmdGroup && IsEqualGUID(CGID_MSHTML, *pguidCmdGroup) && (nCmdID == IDM_PARSECOMPLETE))) { IOleCommandTarget* pct; if (SUCCEEDED(_cFrame.GetCommandTarget(&pct))) { hr = pct->Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvarargIn, pvarargOut); pct->Release(); } } } return hr; } void CDefView::_ShowAndActivate() { // Can't call SetFocus because it rips focus away from such nice // UI elements like the TREE pane... // UIActivate will steal focus only if _uState is SVUIA_ACTIVATE_FOCUS UIActivate(_uState); } // IDefViewFrame (available only through QueryService from sfvext!) // HRESULT CDefView::GetShellFolder(IShellFolder **ppsf) { *ppsf = _pshf; if (*ppsf) _pshf->AddRef(); return *ppsf ? S_OK : E_FAIL; } // IDefViewFrame3 // HRESULT CDefView::GetWindowLV(HWND * phwnd) { if (!_IsDesktop()) { if (!_fGetWindowLV) { _fGetWindowLV = TRUE; // Caller will call ShowHideListView for us *phwnd = _hwndListview; } TraceMsg(TF_DEFVIEW, "GetWindowLV - TAKEN"); return S_OK; } else { *phwnd = NULL; return E_FAIL; } } HRESULT CDefView::OnResizeListView() { _AutoAutoArrange(0); return S_OK; } HRESULT CDefView::ReleaseWindowLV() { _fGetWindowLV = FALSE; WndSize(_hwndView); // Make sure we resize _hwndListview ShowHideListView(); return S_OK; } HRESULT CDefView::DoRename() { return HandleRename(NULL); } // IServiceProvider STDMETHODIMP CDefView::QueryService(REFGUID guidService, REFIID riid, void **ppv) { HRESULT hr = E_FAIL; *ppv = NULL; if (guidService == SID_DefView) // private service ID { // DefViewOCs request this interface if (riid != IID_IDefViewFrame || !_IsDesktop()) hr = QueryInterface(riid, ppv); } else if (guidService == SID_ShellTaskScheduler) { if (_pScheduler) { hr = _pScheduler->QueryInterface(riid, ppv); } } else if ((guidService == SID_SContextMenuSite) || (guidService == SID_SFolderView)) // documented service ID { hr = QueryInterface(riid, ppv); } else if (guidService == SID_ShellFolderViewCB) // access to the view callback object { IShellFolderViewCB * psfvcb = _cCallback.GetSFVCB(); if (psfvcb) hr = psfvcb->QueryInterface(riid, ppv); } else if (guidService == SID_WebViewObject) { if (_cFrame.IsWebView()) { if (_cFrame._pOleObj) { // // We hit this codepath while navigating away (while saving history), // so there should not be any pending _cFrame._pOleObjNew as this // view is going to be destroyed. // ASSERTMSG(!_cFrame._pOleObjNew, "Ambiguous Oleobj while peristing trident history in webview"); hr = _cFrame._pOleObj->QueryInterface(riid, ppv); } else if (_cFrame._pOleObjNew) { // // We hit this codepath if we are navigating to the view (while loading history), // we have not yet called _cFrame._SwitchToNewOleObj(), so we'll use // the pending oleobj as CDefViewPersistHistory::LoadHistory() // expects to get the right IPersistHistory interface from it. // hr = _cFrame._pOleObjNew->QueryInterface(riid, ppv); } } } else if (guidService == SID_SProgressUI) { // return a new instance of the progress dialog to the caller hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, riid, ppv); } else if (_psb) { hr = IUnknown_QueryService(_psb, guidService, riid, ppv); // send up the to the browser } else { hr = IUnknown_QueryService(_punkSite, guidService, riid, ppv); // or our site } return hr; } STDMETHODIMP CDefView::OnSetTitle(VARIANTARG *pvTitle) { return E_NOTIMPL; } BOOL CDefView::_LoadCategory(GUID *pguidGroupID) { BOOL fRet = FALSE; LPITEMIDLIST pidl = _GetViewPidl(); if (pidl) { IPropertyBag *ppb; if (SUCCEEDED(IUnknown_QueryServicePropertyBag(_psb, SHGVSPB_FOLDER, IID_PPV_ARG(IPropertyBag, &ppb)))) { fRet = SUCCEEDED(SHPropertyBag_ReadGUID(ppb, TEXT("Categorize"), pguidGroupID)); ppb->Release(); } ILFree(pidl); } return fRet; } void SHGetThumbnailSize(SIZE *psize) { psize->cx = psize->cy = 96; DWORD dw = 0, cb = sizeof(dw); SHRegGetUSValue(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"), TEXT("ThumbnailSize"), NULL, &dw, &cb, FALSE, NULL, 0); if (dw >= 32 && dw <= 256) // constrain to reason { psize->cx = psize->cy = (int)dw; } } void SHGetThumbnailSizeForThumbsDB(SIZE *psize) { SHGetThumbnailSize(psize); // Due to tnail.cpp restriction buffer sizes, we can only go to 120 (since that's all we've tested at) if (psize->cx > 120) psize->cx = psize->cy = 120; } void CDefView::_GetThumbnailSize(SIZE *psize) { if (-1 == _sizeThumbnail.cx) { SHGetThumbnailSize(&_sizeThumbnail); } *psize = _sizeThumbnail; } #ifdef _X86_ //************ // // More of the Hijaak Hack // // We return no attributes (specifically, Hijaak looks for // SFGAO_FILESYSTEM) and Hijaak will say, "Whoa, I don't know // how to patch this guy; I'll leave it alone." STDAPI FakeHijaak_GetAttributesOf(void *_this, UINT cidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut) { *rgfInOut = 0; // Move along, nothing to see here return S_OK; } const struct FakeHijaakFolderVtbl { FARPROC Dummy[9]; FARPROC GetAttributesOf; } c_FakeHijaakFolderVtbl = { { 0 }, (FARPROC)FakeHijaak_GetAttributesOf }; const LPVOID c_FakeHijaakFolder = (const LPVOID)&c_FakeHijaakFolderVtbl; // // End of the Hijaak Hack // //************ #endif // _X86_ CBackgroundDefviewInfo::CBackgroundDefviewInfo(LPCITEMIDLIST pidl, UINT uId) : _pidl(pidl), _uId(uId) { } CBackgroundDefviewInfo::~CBackgroundDefviewInfo (void) { ILFree(const_cast(_pidl)); } CBackgroundColInfo::CBackgroundColInfo(LPCITEMIDLIST pidl, UINT uId, UINT uiCol, STRRET& strRet) : CBackgroundDefviewInfo(pidl, uId), _uiCol(uiCol) { StrRetToBuf(&strRet, NULL, const_cast(_szText), ARRAYSIZE(_szText)); } CBackgroundColInfo::~CBackgroundColInfo(void) { } // Takes ownership of pidl, copies rguColumns. CBackgroundTileInfo::CBackgroundTileInfo(LPCITEMIDLIST pidl, UINT uId, UINT rguColumns[], UINT cColumns) : CBackgroundDefviewInfo(pidl, uId), _cColumns(cColumns) { ASSERT(cColumns <= ARRAYSIZE(_rguColumns)); for (UINT i = 0; (i < cColumns) && (i < ARRAYSIZE(_rguColumns)); i++) _rguColumns[i] = rguColumns[i]; } CBackgroundTileInfo::~CBackgroundTileInfo(void) { } // Helper function that scales the given size by some percentage where the percentage // is defined in the resources for the localizers to adjust as approp. Range is 0 to 30% larger INT ScaleSizeBasedUponLocalization (INT iSize) { TCHAR szPercentageIncrease[3]; INT iReturnValue = iSize; INT iPercentageIncrease; if (iSize > 0) { if (LoadString(HINST_THISDLL, IDS_SIZE_INCREASE_PERCENTAGE, szPercentageIncrease, ARRAYSIZE(szPercentageIncrease))) { iPercentageIncrease = StrToInt(szPercentageIncrease); if (iPercentageIncrease > 0) { if (iPercentageIncrease > 30) { iPercentageIncrease = 30; } iReturnValue += ((iPercentageIncrease * iSize) / 100); } } } return iReturnValue; } CBackgroundGroupInfo::CBackgroundGroupInfo (LPCITEMIDLIST pidl, UINT uId, DWORD dwGroupId): CBackgroundDefviewInfo(pidl, uId), _dwGroupId(dwGroupId) { } BOOL CBackgroundGroupInfo::VerifyGroupExists(HWND hwnd, ICategorizer* pcat) { if (!pcat) return FALSE; if (!ListView_HasGroup(hwnd, _dwGroupId)) { CATEGORY_INFO ci; pcat->GetCategoryInfo(_dwGroupId, &ci); LVINSERTGROUPSORTED igrp; igrp.pfnGroupCompare = GroupCompare; igrp.pvData = (void *)pcat; igrp.lvGroup.cbSize = sizeof(LVGROUP); igrp.lvGroup.mask = LVGF_HEADER | LVGF_GROUPID; igrp.lvGroup.pszHeader= ci.wszName; igrp.lvGroup.iGroupId = (int)_dwGroupId; ListView_InsertGroupSorted(hwnd, &igrp); } return TRUE; }