You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
15428 lines
498 KiB
15428 lines
498 KiB
#include "shellprv.h"
|
|
|
|
#include <regstr.h>
|
|
#include <shellp.h>
|
|
#include <htmlhelp.h>
|
|
#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 <shdispid.h>
|
|
#include <limits.h>
|
|
#include "prop.h"
|
|
#include <mshtmcid.h>
|
|
#include "dvtasks.h"
|
|
#include "category.h"
|
|
#include "ViewState.h"
|
|
#include <initguid.h>
|
|
#include <guids.h>
|
|
#include <CommonControls.h>
|
|
#include "clsobj.h"
|
|
#include <sfview.h>
|
|
#include "defviewp.h"
|
|
#include "shellp.h"
|
|
#include "duiview.h"
|
|
#include "enumidlist.h"
|
|
#include "util.h"
|
|
#include "foldertypes.h"
|
|
#include <dpa.h>
|
|
#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<POS_SORT_INFO>::_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<POS_SORT_INFO> 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<SHCOLUMNID*>(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)
|
|
//
|
|
// <other bits reserved for future use>
|
|
|
|
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: <InfoTip for selected item> 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 <noinherit> 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<LPITEMIDLIST>(_pidl));
|
|
}
|
|
|
|
CBackgroundColInfo::CBackgroundColInfo(LPCITEMIDLIST pidl, UINT uId, UINT uiCol, STRRET& strRet) :
|
|
CBackgroundDefviewInfo(pidl, uId),
|
|
_uiCol(uiCol)
|
|
{
|
|
StrRetToBuf(&strRet, NULL, const_cast<TCHAR*>(_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;
|
|
}
|