#include "shellprv.h" #include #include "datautil.h" #include "ids.h" #include "defview.h" #include "_security.h" #include "shitemid.h" #include "idlcomm.h" #include "bitbuck.h" #include "bookmk.h" #include "filefldr.h" #include "brfcase.h" #include "copy.h" #include "filetbl.h" #define TF_DRAGDROP 0x04000000 typedef struct { HWND hwnd; DWORD dwFlags; POINTL pt; CHAR szUrl[INTERNET_MAX_URL_LENGTH]; } ADDTODESKTOP; DWORD CALLBACK AddToActiveDesktopThreadProc(void *pv) { ADDTODESKTOP* pToAD = (ADDTODESKTOP*)pv; CHAR szFilePath[MAX_PATH]; DWORD cchFilePath = SIZECHARS(szFilePath); BOOL fAddComp = TRUE; if (SUCCEEDED(PathCreateFromUrlA(pToAD->szUrl, szFilePath, &cchFilePath, 0))) { fAddComp = FALSE; TCHAR szPath[MAX_PATH]; SHAnsiToTChar(szFilePath, szPath, ARRAYSIZE(szPath)); // If the Url is in the Temp directory if (PathIsTemporary(szPath)) { if (IDYES == ShellMessageBox(g_hinst, pToAD->hwnd, MAKEINTRESOURCE(IDS_REASONS_URLINTEMPDIR), MAKEINTRESOURCE(IDS_AD_NAME), MB_YESNO | MB_ICONQUESTION)) { TCHAR szFilter[64], szTitle[64]; TCHAR szFilename[MAX_PATH]; LPTSTR psz; LoadString(g_hinst, IDS_ALLFILESFILTER, szFilter, ARRAYSIZE(szFilter)); LoadString(g_hinst, IDS_SAVEAS, szTitle, ARRAYSIZE(szTitle)); psz = szFilter; //Strip out the # and make them Nulls for SaveAs Dialog while (*psz) { if (*psz == (WCHAR)('#')) *psz = (WCHAR)('\0'); psz++; } if (SUCCEEDED(StringCchCopy(szFilename, ARRAYSIZE(szFilename), PathFindFileName(szPath)))) { OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = pToAD->hwnd; ofn.hInstance = g_hinst; ofn.lpstrFilter = szFilter; ofn.lpstrFile = szFilename; ofn.nMaxFile = ARRAYSIZE(szFilename); ofn.lpstrTitle = szTitle; ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { SHFILEOPSTRUCT sfo = { 0 }; szPath[lstrlen(szPath) + 1] = 0; ofn.lpstrFile[lstrlen(ofn.lpstrFile) + 1] = 0; sfo.hwnd = pToAD->hwnd; sfo.wFunc = FO_COPY; sfo.pFrom = szPath; sfo.pTo = ofn.lpstrFile; cchFilePath = SIZECHARS(szPath); if (SHFileOperation(&sfo) == 0 && SUCCEEDED(UrlCreateFromPath(szPath, szPath, &cchFilePath, 0))) { SHTCharToAnsi(szPath, pToAD->szUrl, ARRAYSIZE(pToAD->szUrl)); fAddComp = TRUE; } } } } } } if (fAddComp) CreateDesktopComponents(pToAD->szUrl, NULL, pToAD->hwnd, pToAD->dwFlags, pToAD->pt.x, pToAD->pt.y); LocalFree((HLOCAL)pToAD); return 0; } typedef struct { DWORD dwDefEffect; IDataObject *pdtobj; POINTL pt; DWORD * pdwEffect; HKEY rghk[MAX_ASSOC_KEYS]; DWORD ck; HMENU hmenu; UINT idCmd; DWORD grfKeyState; } FSDRAGDROPMENUPARAM; typedef struct { HMENU hMenu; UINT uCopyPos; UINT uMovePos; UINT uLinkPos; } FSMENUINFO; class CFSDropTarget : CObjectWithSite, public IDropTarget { public: // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IDropTarget STDMETHODIMP DragEnter(IDataObject* pdtobj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect); STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect); STDMETHODIMP DragLeave(); STDMETHODIMP Drop(IDataObject* pdtobj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect); CFSDropTarget(CFSFolder *pFolder, HWND hwnd); protected: virtual ~CFSDropTarget(); BOOL _IsBriefcaseTarget() { return IsEqualCLSID(_pFolder->_clsidBind, CLSID_BriefcaseFolder); }; BOOL _IsDesktopFolder() { return _GetIDList() && ILIsEmpty(_GetIDList()); }; HRESULT _FilterFileContents(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterFileContentsOLEHack(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterDeskCompHDROP(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterSneakernetBriefcase(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterBriefcase(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterHDROP(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterHIDA(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterDeskImage(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterDeskComp(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterOlePackage(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterOleObj(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); HRESULT _FilterOleLink(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo); DWORD _FilesystemAdjustedDefaultEffect(DWORD dwCurEffectAvail); HRESULT _GetPath(LPTSTR pszPath, int cchPath); LPCITEMIDLIST _GetIDList(); DWORD _LimitDefaultEffect(DWORD dwDefEffect, DWORD dwEffectsAllowed); DWORD _GetDefaultEffect(DWORD grfKeyState, DWORD dwCurEffectAvail, DWORD dwAllEffectAvail, DWORD dwOrigDefEffect); DWORD _DetermineEffects(DWORD grfKeyState, DWORD *pdwEffectInOut, HMENU hmenu); DWORD _EffectFromFolder(); typedef struct { CFSDropTarget *pThis; IStream *pstmDataObj; IStream *pstmFolderView; } DROPTHREADPARAMS; static void _FreeThreadParams(DROPTHREADPARAMS *pdtp); static DWORD CALLBACK _DoDropThreadProc(void *pv); void _DoDrop(IDataObject *pdtobj, IFolderView* pfv); static void _AddVerbs(DWORD* pdwEffects, DWORD dwEffectAvail, DWORD dwDefEffect, UINT idCopy, UINT idMove, UINT idLink, DWORD dwForceEffect, FSMENUINFO* pfsMenuInfo); void _FixUpDefaultItem(HMENU hmenu, DWORD dwDefEffect); HRESULT _DragDropMenu(FSDRAGDROPMENUPARAM *pddm); HRESULT _CreatePackage(IDataObject *pdtobj); HRESULT _CreateURLDeskComp(IDataObject *pdtobj, POINTL pt); HRESULT _CreateDeskCompImage(IDataObject *pdtobj, POINTL pt); void _GetStateFromSite(); BOOL _IsFromSneakernetBriefcase(); BOOL _IsFromSameBriefcase(); void _MoveCopy(IDataObject *pdtobj, IFolderView* pfv, HDROP hDrop); void _MoveSelectIcons(IDataObject *pdtobj, IFolderView* pfv, void *hNameMap, LPCTSTR pszFiles, BOOL fMove, HDROP hDrop); LONG _cRef; CFSFolder *_pFolder; HWND _hwnd; // EVIL: used as a site and UI host UINT _idCmd; DWORD _grfKeyStateLast; // for previous DragOver/Enter IDataObject *_pdtobj; // used durring Dragover() and DoDrop(), don't use on background thread DWORD _dwEffectLastReturned; // stashed effect that's returned by base class's dragover DWORD _dwEffect; DWORD _dwData; // DTID_* DWORD _dwEffectPreferred; // if dwData & DTID_PREFERREDEFFECT DWORD _dwEffectFolder; // folder desktop.ini preferred effect BOOL _fSameHwnd; // the drag source and target are the same folder BOOL _fDragDrop; // BOOL _fUseExactDropPoint; // Don't transform the drop point. The target knows exactly where it wants things. BOOL _fBkDropTarget; POINT _ptDrop; IFolderView* _pfv; typedef struct { FORMATETC fmte; HRESULT (CFSDropTarget::*pfnGetDragDropInfo)( IN FORMATETC* pfmte, IN DWORD grfKeyFlags, IN DWORD dwEffectsAvail, IN OUT DWORD* pdwEffectsUsed, OUT DWORD* pdwDefaultEffect, IN OUT FSMENUINFO* pfsMenuInfo); CLIPFORMAT *pcfInit; } _DATA_HANDLER; // HACKHACK: C++ doesn't let you initialize statics inside a class // definition, and also doesn't let you specify an empty // size (i.e., rg_data_handlers[]) inside a class definition // either, so we have to have this bogus NUM_DATA_HANDLERS // symbol that must manually be kept in sync. enum { NUM_DATA_HANDLERS = 16 }; static _DATA_HANDLER rg_data_handlers[NUM_DATA_HANDLERS]; static void _Init_rg_data_handlers(); private: friend HRESULT CFSDropTarget_CreateInstance(CFSFolder* pFolder, HWND hwnd, IDropTarget** ppdt); }; CFSDropTarget::CFSDropTarget(CFSFolder *pFolder, HWND hwnd) : _cRef(1), _hwnd(hwnd), _pFolder(pFolder), _dwEffectFolder(-1) { ASSERT(0 == _grfKeyStateLast); ASSERT(NULL == _pdtobj); ASSERT(0 == _dwEffectLastReturned); ASSERT(0 == _dwData); ASSERT(0 == _dwEffectPreferred); _pFolder->AddRef(); } CFSDropTarget::~CFSDropTarget() { AssertMsg(_pdtobj == NULL, TEXT("didn't get matching DragLeave, fix that bug")); ATOMICRELEASE(_pdtobj); ATOMICRELEASE(_pfv); _pFolder->Release(); } STDAPI CFSDropTarget_CreateInstance(CFSFolder* pFolder, HWND hwnd, IDropTarget** ppdt) { *ppdt = new CFSDropTarget(pFolder, hwnd); return *ppdt ? S_OK : E_OUTOFMEMORY; } HRESULT CFSDropTarget::QueryInterface(REFIID riid, void** ppvObj) { static const QITAB qit[] = { QITABENT(CFSDropTarget, IDropTarget), QITABENT(CFSDropTarget, IObjectWithSite), QITABENTMULTI2(CFSDropTarget, IID_IDropTargetWithDADSupport, IDropTarget), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CFSDropTarget::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CFSDropTarget::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } void CFSDropTarget::_FreeThreadParams(DROPTHREADPARAMS *pdtp) { pdtp->pThis->Release(); ATOMICRELEASE(pdtp->pstmDataObj); ATOMICRELEASE(pdtp->pstmFolderView); LocalFree(pdtp); } // compute DTID_ bit flags from the data object to make format testing easier for // DragOver() and Drop() code STDAPI GetClipFormatFlags(IDataObject *pdtobj, DWORD *pdwData, DWORD *pdwEffectPreferred) { *pdwData = 0; *pdwEffectPreferred = 0; if (pdtobj) { IEnumFORMATETC *penum; if (SUCCEEDED(pdtobj->EnumFormatEtc(DATADIR_GET, &penum))) { FORMATETC fmte; ULONG celt; while (S_OK == penum->Next(1, &fmte, &celt)) { if (fmte.cfFormat == CF_HDROP && (fmte.tymed & TYMED_HGLOBAL)) *pdwData |= DTID_HDROP; if (fmte.cfFormat == g_cfHIDA && (fmte.tymed & TYMED_HGLOBAL)) *pdwData |= DTID_HIDA; if (fmte.cfFormat == g_cfNetResource && (fmte.tymed & TYMED_HGLOBAL)) *pdwData |= DTID_NETRES; if (fmte.cfFormat == g_cfEmbeddedObject && (fmte.tymed & TYMED_ISTORAGE)) *pdwData |= DTID_EMBEDDEDOBJECT; if (fmte.cfFormat == g_cfFileContents && (fmte.tymed & (TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE))) *pdwData |= DTID_CONTENTS; if (fmte.cfFormat == g_cfFileGroupDescriptorA && (fmte.tymed & TYMED_HGLOBAL)) *pdwData |= DTID_FDESCA; if (fmte.cfFormat == g_cfFileGroupDescriptorW && (fmte.tymed & TYMED_HGLOBAL)) *pdwData |= DTID_FDESCW; if ((fmte.cfFormat == g_cfPreferredDropEffect) && (fmte.tymed & TYMED_HGLOBAL) && (DROPEFFECT_NONE != (*pdwEffectPreferred = DataObj_GetDWORD(pdtobj, g_cfPreferredDropEffect, DROPEFFECT_NONE)))) { *pdwData |= DTID_PREFERREDEFFECT; } #ifdef DEBUG TCHAR szFormat[MAX_PATH]; if (GetClipboardFormatName(fmte.cfFormat, szFormat, ARRAYSIZE(szFormat))) { TraceMsg(TF_DRAGDROP, "CFSDropTarget - cf %s, tymed %d", szFormat, fmte.tymed); } else { TraceMsg(TF_DRAGDROP, "CFSDropTarget - cf %d, tymed %d", fmte.cfFormat, fmte.tymed); } #endif // DEBUG SHFree(fmte.ptd); } penum->Release(); } // // HACK: // Win95 always did the GetData below which can be quite expensive if // the data is a directory structure on an ftp server etc. // dont check for FD_LINKUI if the data object has a preferred effect // if ((*pdwData & (DTID_PREFERREDEFFECT | DTID_CONTENTS)) == DTID_CONTENTS) { if (*pdwData & DTID_FDESCA) { FORMATETC fmteRead = {g_cfFileGroupDescriptorA, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (S_OK == pdtobj->GetData(&fmteRead, &medium)) { FILEGROUPDESCRIPTORA * pfgd = (FILEGROUPDESCRIPTORA *)GlobalLock(medium.hGlobal); if (pfgd) { if (pfgd->cItems >= 1) { if (pfgd->fgd[0].dwFlags & FD_LINKUI) *pdwData |= DTID_FD_LINKUI; } GlobalUnlock(medium.hGlobal); } ReleaseStgMedium(&medium); } } else if (*pdwData & DTID_FDESCW) { FORMATETC fmteRead = {g_cfFileGroupDescriptorW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (S_OK == pdtobj->GetData(&fmteRead, &medium)) { FILEGROUPDESCRIPTORW * pfgd = (FILEGROUPDESCRIPTORW *)GlobalLock(medium.hGlobal); if (pfgd) { if (pfgd->cItems >= 1) { if (pfgd->fgd[0].dwFlags & FD_LINKUI) *pdwData |= DTID_FD_LINKUI; } GlobalUnlock(medium.hGlobal); } ReleaseStgMedium(&medium); } } } if (S_OK == OleQueryCreateFromData(pdtobj)) *pdwData |= DTID_OLEOBJ; if (S_OK == OleQueryLinkFromData(pdtobj)) *pdwData |= DTID_OLELINK; } return S_OK; // for now always succeeds } STDMETHODIMP CFSDropTarget::DragEnter(IDataObject* pdtobj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { ASSERT(NULL == _pdtobj); // req DragDrop protocol, someone forgot to call DragLeave // init our registerd data formats IDLData_InitializeClipboardFormats(); _grfKeyStateLast = grfKeyState; IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); GetClipFormatFlags(_pdtobj, &_dwData, &_dwEffectPreferred); *pdwEffect = _dwEffectLastReturned = _DetermineEffects(grfKeyState, pdwEffect, NULL); return S_OK; } STDMETHODIMP CFSDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { if (_grfKeyStateLast != grfKeyState) { _grfKeyStateLast = grfKeyState; *pdwEffect = _dwEffectLastReturned = _DetermineEffects(grfKeyState, pdwEffect, NULL); } else { *pdwEffect = _dwEffectLastReturned; } return S_OK; } STDMETHODIMP CFSDropTarget::DragLeave() { IUnknown_Set((IUnknown **)&_pdtobj, NULL); return S_OK; } // init data from our site that we will need in processing the drop void CFSDropTarget::_GetStateFromSite() { IShellFolderView* psfv; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IShellFolderView, &psfv)))) { _fSameHwnd = S_OK == psfv->IsDropOnSource((IDropTarget*)this); _fDragDrop = S_OK == psfv->GetDropPoint(&_ptDrop); _fBkDropTarget = S_OK == psfv->IsBkDropTarget(NULL); psfv->QueryInterface(IID_PPV_ARG(IFolderView, &_pfv)); psfv->Release(); } } STDMETHODIMP CFSDropTarget::Drop(IDataObject* pdtobj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { // OLE may give us a different data object (fully marshalled) // from the one we've got on DragEnter (this is not the case on Win2k, this is a nop) IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); _GetStateFromSite(); // note, that on the drop the mouse buttons are not down so the grfKeyState // is not what we saw on the DragOver/DragEnter, thus we need to cache // the grfKeyState to detect left vs right drag // // ASSERT(this->grfKeyStateLast == grfKeyState); HMENU hmenu = SHLoadPopupMenu(HINST_THISDLL, POPUP_TEMPLATEDD); DWORD dwDefEffect = _DetermineEffects(grfKeyState, pdwEffect, hmenu); if (DROPEFFECT_NONE == dwDefEffect) { *pdwEffect = DROPEFFECT_NONE; DAD_SetDragImage(NULL, NULL); IUnknown_Set((IUnknown **)&_pdtobj, NULL); return S_OK; } TCHAR szPath[MAX_PATH]; _GetPath(szPath, ARRAYSIZE(szPath)); // this doesn't actually do the menu if (grfKeyState MK_LBUTTON) FSDRAGDROPMENUPARAM ddm; ddm.dwDefEffect = dwDefEffect; ddm.pdtobj = pdtobj; ddm.pt = pt; ddm.pdwEffect = pdwEffect; ddm.ck = SHGetAssocKeysForIDList(_GetIDList(), ddm.rghk, ARRAYSIZE(ddm.rghk)); ddm.hmenu = hmenu; ddm.grfKeyState = grfKeyState; HRESULT hr = _DragDropMenu(&ddm); SHRegCloseKeys(ddm.rghk, ddm.ck); DestroyMenu(hmenu); if (hr == S_FALSE) { // let callers know where this is about to go // SHScrap cares because it needs to close the file so we can copy/move it DataObj_SetDropTarget(pdtobj, &CLSID_ShellFSFolder); switch (ddm.idCmd) { case DDIDM_CONTENTS_DESKCOMP: hr = CreateDesktopComponents(NULL, pdtobj, _hwnd, 0, ddm.pt.x, ddm.pt.y); break; case DDIDM_CONTENTS_DESKURL: hr = _CreateURLDeskComp(pdtobj, ddm.pt); break; case DDIDM_CONTENTS_DESKIMG: hr = _CreateDeskCompImage(pdtobj, ddm.pt); break; case DDIDM_CONTENTS_COPY: case DDIDM_CONTENTS_MOVE: case DDIDM_CONTENTS_LINK: hr = CFSFolder_AsyncCreateFileFromClip(_hwnd, szPath, pdtobj, pt, pdwEffect, _fBkDropTarget); break; case DDIDM_SCRAP_COPY: case DDIDM_SCRAP_MOVE: case DDIDM_DOCLINK: hr = SHCreateBookMark(_hwnd, szPath, pdtobj, pt, pdwEffect); break; case DDIDM_OBJECT_COPY: case DDIDM_OBJECT_MOVE: hr = _CreatePackage(pdtobj); if (E_UNEXPECTED == hr) { // _CreatePackage() can only expand certain types of packages // back into files. For example, it doesn't handle CMDLINK files. // // If _CreatePackage() didn't recognize the stream format, we fall // back to SHCreateBookMark(), which should create a scrap: hr = SHCreateBookMark(_hwnd, szPath, pdtobj, pt, pdwEffect); } break; case DDIDM_COPY: case DDIDM_SYNCCOPY: case DDIDM_SYNCCOPYTYPE: case DDIDM_MOVE: case DDIDM_LINK: _dwEffect = *pdwEffect; _idCmd = ddm.idCmd; if (DataObj_CanGoAsync(pdtobj) || DataObj_GoAsyncForCompat(pdtobj)) { // create another thread to avoid blocking the source thread. DROPTHREADPARAMS *pdtp; hr = SHLocalAlloc(sizeof(*pdtp), &pdtp); if (SUCCEEDED(hr)) { pdtp->pThis = this; pdtp->pThis->AddRef(); CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pdtobj, &pdtp->pstmDataObj); CoMarshalInterThreadInterfaceInStream(IID_IFolderView, _pfv, &pdtp->pstmFolderView); if (SHCreateThread(_DoDropThreadProc, pdtp, CTF_COINIT, NULL)) { hr = S_OK; } else { _FreeThreadParams(pdtp); hr = E_OUTOFMEMORY; } } } else { _DoDrop(pdtobj, _pfv); // synchronously } // in these CF_HDROP cases "Move" is always an optimized move, we delete the // source. make sure we don't return DROPEFFECT_MOVE so the source does not // try to do this too... // even if we have not done anything yet since we may have // kicked of a thread to do this DataObj_SetDWORD(pdtobj, g_cfLogicalPerformedDropEffect, *pdwEffect); if (DROPEFFECT_MOVE == *pdwEffect) *pdwEffect = DROPEFFECT_NONE; break; } } IUnknown_Set((IUnknown **)&_pdtobj, NULL); // don't use this any more if (FAILED(hr)) *pdwEffect = DROPEFFECT_NONE; ASSERT(*pdwEffect==DROPEFFECT_COPY || *pdwEffect==DROPEFFECT_LINK || *pdwEffect==DROPEFFECT_MOVE || *pdwEffect==DROPEFFECT_NONE); return hr; } void CFSDropTarget::_AddVerbs(DWORD* pdwEffects, DWORD dwEffectAvail, DWORD dwDefEffect, UINT idCopy, UINT idMove, UINT idLink, DWORD dwForceEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); MENUITEMINFO mii; TCHAR szCmd[MAX_PATH]; if (NULL != pfsMenuInfo) { mii.cbSize = sizeof(mii); mii.dwTypeData = szCmd; mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA; mii.fType = MFT_STRING; } if ((DROPEFFECT_COPY == (DROPEFFECT_COPY & dwEffectAvail)) && ((0 == (*pdwEffects & DROPEFFECT_COPY)) || (dwForceEffect & DROPEFFECT_COPY))) { ASSERT(0 != idCopy); if (NULL != pfsMenuInfo) { LoadString(HINST_THISDLL, idCopy + IDS_DD_FIRST, szCmd, ARRAYSIZE(szCmd)); mii.fState = MFS_ENABLED | ((DROPEFFECT_COPY == dwDefEffect) ? MFS_DEFAULT : 0); mii.wID = idCopy; mii.dwItemData = DROPEFFECT_COPY; InsertMenuItem(pfsMenuInfo->hMenu, pfsMenuInfo->uCopyPos, TRUE, &mii); pfsMenuInfo->uCopyPos++; pfsMenuInfo->uMovePos++; pfsMenuInfo->uLinkPos++; } } if ((DROPEFFECT_MOVE == (DROPEFFECT_MOVE & dwEffectAvail)) && ((0 == (*pdwEffects & DROPEFFECT_MOVE)) || (dwForceEffect & DROPEFFECT_MOVE))) { ASSERT(0 != idMove); if (NULL != pfsMenuInfo) { LoadString(HINST_THISDLL, idMove + IDS_DD_FIRST, szCmd, ARRAYSIZE(szCmd)); mii.fState = MFS_ENABLED | ((DROPEFFECT_MOVE == dwDefEffect) ? MFS_DEFAULT : 0); mii.wID = idMove; mii.dwItemData = DROPEFFECT_MOVE; InsertMenuItem(pfsMenuInfo->hMenu, pfsMenuInfo->uMovePos, TRUE, &mii); pfsMenuInfo->uMovePos++; pfsMenuInfo->uLinkPos++; } } if ((DROPEFFECT_LINK == (DROPEFFECT_LINK & dwEffectAvail)) && ((0 == (*pdwEffects & DROPEFFECT_LINK)) || (dwForceEffect & DROPEFFECT_LINK))) { ASSERT(0 != idLink); if (NULL != pfsMenuInfo) { LoadString(HINST_THISDLL, idLink + IDS_DD_FIRST, szCmd, ARRAYSIZE(szCmd)); mii.fState = MFS_ENABLED | ((DROPEFFECT_LINK == dwDefEffect) ? MFS_DEFAULT : 0); mii.wID = idLink; mii.dwItemData = DROPEFFECT_LINK; InsertMenuItem(pfsMenuInfo->hMenu, pfsMenuInfo->uLinkPos, TRUE, &mii); pfsMenuInfo->uLinkPos++; } } *pdwEffects |= dwEffectAvail; } // determine the default drop effect (move/copy/link) from the file type // // HKCR\.cda "DefaultDropEffect" = 4 // DROPEFFECT_LINK DWORD EffectFromFileType(IDataObject *pdtobj) { DWORD dwDefEffect = DROPEFFECT_NONE; // 0 LPITEMIDLIST pidl; if (SUCCEEDED(PidlFromDataObject(pdtobj, &pidl))) { IQueryAssociations *pqa; if (SUCCEEDED(SHGetAssociations(pidl, (void **)&pqa))) { DWORD cb = sizeof(dwDefEffect); pqa->GetData(0, ASSOCDATA_VALUE, L"DefaultDropEffect", &dwDefEffect, &cb); pqa->Release(); } ILFree(pidl); } return dwDefEffect; } // compute the default effect based on // the allowed effects // the keyboard state, // the preferred effect that might be in the data object // and previously computed default effect (if the above yields nothing) DWORD CFSDropTarget::_GetDefaultEffect(DWORD grfKeyState, DWORD dwCurEffectAvail, DWORD dwAllEffectAvail, DWORD dwOrigDefEffect) { DWORD dwDefEffect = 0; // // keyboard, (explicit user input) gets first crack // switch (grfKeyState & (MK_CONTROL | MK_SHIFT | MK_ALT)) { case MK_CONTROL: dwDefEffect = DROPEFFECT_COPY; break; case MK_SHIFT: dwDefEffect = DROPEFFECT_MOVE; break; case MK_SHIFT | MK_CONTROL: case MK_ALT: dwDefEffect = DROPEFFECT_LINK; break; default: // no modifier keys case // if the data object contains a preferred drop effect, try to use it DWORD dwPreferred = DataObj_GetDWORD(_pdtobj, g_cfPreferredDropEffect, DROPEFFECT_NONE) & dwAllEffectAvail; if (DROPEFFECT_NONE == dwPreferred) { dwPreferred = EffectFromFileType(_pdtobj) & dwAllEffectAvail; } if (dwPreferred) { if (dwPreferred & DROPEFFECT_MOVE) { dwDefEffect = DROPEFFECT_MOVE; } else if (dwPreferred & DROPEFFECT_COPY) { dwDefEffect = DROPEFFECT_COPY; } else if (dwPreferred & DROPEFFECT_LINK) { dwDefEffect = DROPEFFECT_LINK; } } else { dwDefEffect = dwOrigDefEffect; } break; } return dwDefEffect & dwCurEffectAvail; } HRESULT CFSDropTarget::_FilterDeskCompHDROP(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (!PolicyNoActiveDesktop() && !SHRestricted(REST_NOADDDESKCOMP) && _IsDesktopFolder()) { hr = IsDeskCompHDrop(_pdtobj); if (S_OK == hr) { DWORD dwDefEffect = 0; DWORD dwEffectAdd = dwEffectsAvail & DROPEFFECT_LINK; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_LINK); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, 0, 0, DDIDM_CONTENTS_DESKCOMP, DROPEFFECT_LINK, // force add the DDIDM_CONTENTS_DESKCOMP verb pfsMenuInfo); } } return hr; } // see if a PIDL is scoped by a briefcaes BOOL IsBriefcaseOrChild(LPCITEMIDLIST pidlIn) { BOOL bRet = FALSE; LPITEMIDLIST pidl = ILClone(pidlIn); if (pidl) { do { CLSID clsid; if (SUCCEEDED(GetCLSIDFromIDList(pidl, &clsid)) && IsEqualCLSID(clsid, CLSID_Briefcase)) { bRet = TRUE; // it is a briefcase break; } } while (ILRemoveLastID(pidl)); ILFree(pidl); } return bRet; } // returns true if the data object represents items in a sneakernet briefcase // (briefcase on removable media) BOOL CFSDropTarget::_IsFromSneakernetBriefcase() { BOOL bRet = FALSE; // assume no if (!_IsBriefcaseTarget()) { STGMEDIUM medium = {0}; LPIDA pida = DataObj_GetHIDA(_pdtobj, &medium); if (pida) { LPCITEMIDLIST pidlFolder = HIDA_GetPIDLFolder(pida); TCHAR szSource[MAX_PATH]; if (SHGetPathFromIDList(pidlFolder, szSource)) { // is source on removable device? if (!PathIsUNC(szSource) && IsRemovableDrive(DRIVEID(szSource))) { TCHAR szTarget[MAX_PATH]; _GetPath(szTarget, ARRAYSIZE(szTarget)); // is the target fixed media? if (PathIsUNC(szTarget) || !IsRemovableDrive(DRIVEID(szTarget))) { bRet = IsBriefcaseOrChild(pidlFolder); } } } HIDA_ReleaseStgMedium(pida, &medium); } } return bRet; } // TRUE if any folders are in hdrop BOOL DroppingAnyFolders(HDROP hDrop) { TCHAR szPath[MAX_PATH]; for (UINT i = 0; DragQueryFile(hDrop, i, szPath, ARRAYSIZE(szPath)); i++) { if (PathIsDirectory(szPath)) return TRUE; } return FALSE; } // sneakernet case: // dragging a file/folder from a briefcase on removable media. we special case this // and use this as a chance to connect up this target folder with the content of the briefcase HRESULT CFSDropTarget::_FilterSneakernetBriefcase(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); if (_IsFromSneakernetBriefcase()) { // Yes; show the non-default briefcase cm STGMEDIUM medium = {0}; FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { DWORD dwDefEffect = 0; DWORD dwEffectAdd = DROPEFFECT_COPY & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_COPY); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_SYNCCOPY, 0, 0, 0, pfsMenuInfo); // Call _AddVerbs() again to force "Sync Copy of Type" as a 2nd DROPEFFECT_COPY verb: if ((DROPEFFECT_COPY & dwEffectsAvail) && DroppingAnyFolders((HDROP)medium.hGlobal)) { _AddVerbs(pdwEffects, DROPEFFECT_COPY, 0, DDIDM_SYNCCOPYTYPE, 0, 0, DROPEFFECT_COPY, pfsMenuInfo); } ReleaseStgMedium(&medium); } } return S_OK; } // returns true if the data object represents items from the same briefcase // as this drop target BOOL CFSDropTarget::_IsFromSameBriefcase() { BOOL bRet = FALSE; STGMEDIUM medium; FORMATETC fmteBrief = {g_cfBriefObj, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; // Yes; are they from the same briefcase as the target? if (SUCCEEDED(_pdtobj->GetData(&fmteBrief, &medium))) { BriefObj *pbo = (BriefObj *)GlobalLock(medium.hGlobal); TCHAR szBriefPath[MAX_PATH], szPath[MAX_PATH]; if (SUCCEEDED(StringCchCopy(szBriefPath, ARRAYSIZE(szBriefPath), BOBriefcasePath(pbo)))) { if (SUCCEEDED(StringCchCopy(szPath, ARRAYSIZE(szPath), BOFileList(pbo)))) // first file in list { TCHAR szPathTgt[MAX_PATH]; _GetPath(szPathTgt, ARRAYSIZE(szPathTgt)); int cch = PathCommonPrefix(szPath, szPathTgt, NULL); bRet = (0 < cch) && (lstrlen(szBriefPath) <= cch); } } GlobalUnlock(medium.hGlobal); ReleaseStgMedium(&medium); } return bRet; } // briefcase drop target specific handling gets computed here HRESULT CFSDropTarget::_FilterBriefcase(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { if (_IsBriefcaseTarget() && !_IsFromSameBriefcase()) { STGMEDIUM medium = {0}; FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { DWORD dwDefEffect = DROPEFFECT_COPY; DWORD dwEffectAdd = DROPEFFECT_COPY & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_COPY); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_SYNCCOPY, 0, 0, 0, pfsMenuInfo); // Call _AddVerbs() again to force "Sync Copy of Type" as a 2nd DROPEFFECT_COPY verb: if ((DROPEFFECT_COPY & dwEffectsAvail) && DroppingAnyFolders((HDROP)medium.hGlobal)) { _AddVerbs(pdwEffects, DROPEFFECT_COPY, 0, DDIDM_SYNCCOPYTYPE, 0, 0, DROPEFFECT_COPY, pfsMenuInfo); } ReleaseStgMedium(&medium); } } return S_OK; } HRESULT CFSDropTarget::_FilterHDROP(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); DWORD dwDefEffect = 0; DWORD dwEffectAdd = dwEffectsAvail & (DROPEFFECT_COPY | DROPEFFECT_MOVE); if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, _FilesystemAdjustedDefaultEffect(dwEffectAdd)); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_COPY, DDIDM_MOVE, 0, 0, pfsMenuInfo); return S_OK; } HRESULT CFSDropTarget::_FilterFileContents(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); if ((_dwData & (DTID_CONTENTS | DTID_FDESCA)) == (DTID_CONTENTS | DTID_FDESCA) || (_dwData & (DTID_CONTENTS | DTID_FDESCW)) == (DTID_CONTENTS | DTID_FDESCW)) { DWORD dwEffectAdd, dwSuggestedEffect; // // HACK: if there is a preferred drop effect and no HIDA // then just take the preferred effect as the available effects // this is because we didn't actually check the FD_LINKUI bit // back when we assembled dwData! (performance) // if ((_dwData & (DTID_PREFERREDEFFECT | DTID_HIDA)) == DTID_PREFERREDEFFECT) { dwEffectAdd = _dwEffectPreferred; dwSuggestedEffect = _dwEffectPreferred; } else if (_dwData & DTID_FD_LINKUI) { dwEffectAdd = DROPEFFECT_LINK; dwSuggestedEffect = DROPEFFECT_LINK; } else { dwEffectAdd = DROPEFFECT_COPY | DROPEFFECT_MOVE; dwSuggestedEffect = DROPEFFECT_COPY; } dwEffectAdd &= dwEffectsAvail; DWORD dwDefEffect = 0; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, dwSuggestedEffect); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_CONTENTS_COPY, DDIDM_CONTENTS_MOVE, DDIDM_CONTENTS_LINK, 0, pfsMenuInfo); } return S_OK; } // // Old versions of OLE have a bug where if two FORMATETCs use the same // CLIPFORMAT, then only the first one makes it to the IEnumFORMATETC, // even if the other parameters (such as DVASPECT) are different. // // This causes us problems because those other DVASPECTs might be useful. // So if we see a FileContents with the wrong DVASPECT, sniff at the // object to see if maybe it also contains a copy with the correct DVASPECT. // // This bug was fixed in 1996 on the NT side, but the Win9x side was // not fixed. The Win9x OLE team was disbanded before the fix could // be propagated. So we get to work around this OLE bug forever. // HRESULT CFSDropTarget::_FilterFileContentsOLEHack(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { FORMATETC fmte = *pfmte; fmte.dwAspect = DVASPECT_CONTENT; // // Whoa, this test is so (intentionally) backwards it isn't funny. // // We want to see whether there is a DVASPECT_CONTENT available in // the real object. So we first ask the object if it has a // DVASPECT_CONTENT format already. If the answer is yes, then we // **skip** this FORMATETC, because it will be found (or has already // been found) by our big EnumFORMATETC loop. // // If the answer is NO, then maybe we're hitting an OLE bug. // (They cache the list of available formats, but the bug is that // their cache is broken.) Bypass the cache by actually getting the // data. If it works, then run with it. Otherwise, I guess OLE wasn't // kidding. // // Note that we do not GetData() unconditionally -- bad for perf. // Only call GetData() after all the easy tests have failed. // HRESULT hr = _pdtobj->QueryGetData(&fmte); if (hr == DV_E_FORMATETC) { // Maybe we are hitting the OLE bug. Try harder. STGMEDIUM stgm = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &stgm))) { // Yup. OLE lied to us. ReleaseStgMedium(&stgm); hr = _FilterFileContents(&fmte, grfKeyFlags, dwEffectsAvail, pdwEffects, pdwDefaultEffect, pfsMenuInfo); } else { // Whaddya know, OLE was telling the truth. Do nothing with this // format. hr = S_OK; } } else { // Either QueryGetData() failed in some bizarre way // (in which case we ignore the problem) or the QueryGetData // succeeded, in which case we ignore this FORMATETC since // the big enumeration will find (or has already found) the // DVASPECT_CONTENT. hr = S_OK; } return hr; } HRESULT CFSDropTarget::_FilterHIDA(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); DWORD dwDefEffect = 0; DWORD dwEffectAdd = DROPEFFECT_LINK & dwEffectsAvail; // NOTE: we only add a HIDA default effect if HDROP isn't going to add a default // effect. This preserves shell behavior with file system data objects without // requiring us to change the enumerator order in CIDLDataObj. When we do change // the enumerator order, we can remove this special case: if (pdwDefaultEffect && ((0 == (_dwData & DTID_HDROP)) || (0 == _GetDefaultEffect(grfKeyFlags, dwEffectsAvail & (DROPEFFECT_COPY | DROPEFFECT_MOVE), dwEffectsAvail, _FilesystemAdjustedDefaultEffect(dwEffectsAvail & (DROPEFFECT_COPY | DROPEFFECT_MOVE)))))) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_LINK); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, 0, 0, DDIDM_LINK, 0, pfsMenuInfo); return S_OK; } // {F20DA720-C02F-11CE-927B-0800095AE340} const GUID CLSID_CPackage = {0xF20DA720L, 0xC02F, 0x11CE, 0x92, 0x7B, 0x08, 0x00, 0x09, 0x5A, 0xE3, 0x40}; // old packager guid... // {0003000C-0000-0000-C000-000000000046} const GUID CLSID_OldPackage = {0x0003000CL, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}; HRESULT CFSDropTarget::_FilterOlePackage(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (pdwDefaultEffect) { *pdwDefaultEffect = 0; } FORMATETC fmte = {g_cfObjectDescriptor, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { // we've got an object descriptor OBJECTDESCRIPTOR* pOD = (OBJECTDESCRIPTOR*) GlobalLock(medium.hGlobal); if (pOD) { if (IsEqualCLSID(CLSID_OldPackage, pOD->clsid) || IsEqualCLSID(CLSID_CPackage, pOD->clsid)) { // This is a package - proceed DWORD dwDefEffect = 0; DWORD dwEffectAdd = (DROPEFFECT_COPY | DROPEFFECT_MOVE) & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_COPY); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_OBJECT_COPY, DDIDM_OBJECT_MOVE, 0, 0, pfsMenuInfo); hr = S_OK; } GlobalUnlock(medium.hGlobal); } ReleaseStgMedium(&medium); } return hr; } // REARCHITECT: // This code has lots of problems. We need to fix this the text time we touch this code // outside of ship mode. TO FIX: // 1. Use SHAnsiToUnicode(CP_UTF8, ) to convert pszHTML to unicode. This will allow international // paths to work. // 2. Obey the selected range. // 3. Use MSHTML to get the image. You can have trident parse the HTML via IHTMLTxtRange::pasteHTML. // MS HTML has a special collection of images. Ask for the first image in that collection, or // the first image in that collection within the selected range. (#1 isn't needed with this) BOOL ExtractImageURLFromCFHTML(IN LPSTR pszHTML, IN SIZE_T cbHTMLSize, OUT LPSTR szImg, IN DWORD cchSize) { BOOL fSucceeded = FALSE; // To avoid going nuts, only look at the first 64K of the HTML. // (Important on Win64 because StrCpyNA doesn't support more than 4GB.) if (cbHTMLSize > 0xFFFF) cbHTMLSize = 0xFFFF; // NT #391669: pszHTML isn't terminated, so terminate it now. LPSTR pszCopiedHTML = (LPSTR) LocalAlloc(LPTR, cbHTMLSize + 1); if (pszCopiedHTML) { if (SUCCEEDED(StringCchCopyA(pszCopiedHTML, (int)(cbHTMLSize + 1), pszHTML))) { //DANGER WILL ROBINSON: // HTML is comming in as UFT-8 encoded. Neither Unicode or Ansi, // We've got to do something.... I'm going to party on it as if it were // Ansi. This code will choke on escape sequences..... //Find the base URL //Locate //Read the should be the Image URL //Determine if it's an absolute or relative URL //If relative, append to BASE url. You may need to lop off from the // last delimiter to the end of the string. //Pull out the SourceURL LPSTR pszTemp; LPSTR pszBase = StrStrIA(pszCopiedHTML,"SourceURL:"); // Point to the char after : if (pszBase) { pszBase += sizeof("SourceURL:")-1; //Since each line can be terminated by a CR, CR/LF or LF check each case... pszTemp = StrChrA(pszBase,'\n'); if (!pszTemp) pszTemp = StrChrA(pszBase,'\r'); if (pszTemp) *pszTemp = '\0'; pszTemp++; } else pszTemp = pszCopiedHTML; //Pull out the Img Src LPSTR pszImgSrc = StrStrIA(pszTemp,"IMG"); if (pszImgSrc != NULL) { pszImgSrc = StrStrIA(pszImgSrc,"SRC"); if (pszImgSrc != NULL) { LPSTR pszImgSrcOrig = pszImgSrc; pszImgSrc = StrChrA(pszImgSrc,'\"'); if (pszImgSrc) { pszImgSrc++; // Skip over the quote at the beginning of the src path. pszTemp = StrChrA(pszImgSrc,'\"'); // Find the end of the path. } else { LPSTR pszTemp1; LPSTR pszTemp2; pszImgSrc = StrChrA(pszImgSrcOrig,'='); if (pszImgSrc) { pszImgSrc++; // Skip past the equals to the first char in the path. // Someday we may need to handle spaces between '=' and the path. pszTemp1 = StrChrA(pszImgSrc,' '); // Since the path doesn't have quotes around it, assume a space will terminate it. pszTemp2 = StrChrA(pszImgSrc,'>'); // Since the path doesn't have quotes around it, assume a space will terminate it. pszTemp = pszTemp1; // Assume quote terminates path. if (!pszTemp1) pszTemp = pszTemp2; // Use '>' if quote not found. if (pszTemp1 && pszTemp2 && (pszTemp2 < pszTemp1)) pszTemp = pszTemp2; // Change to having '>' terminate path if both exist and it comes first. } } if (pszImgSrc && pszTemp) { *pszTemp = '\0'; // Terminate path. //At this point, I've reduced the 2 important strings. Now see if I need to //Join them. //If this fails, then we don't have a full URL, Only a relative. if (!UrlIsA(pszImgSrc, URLIS_URL) && pszBase) { if (SUCCEEDED(UrlCombineA(pszBase, pszImgSrc, szImg, &cchSize, 0))) { fSucceeded = TRUE; } } else { if (lstrlenA(pszImgSrc) <= (int)cchSize) { StringCchCopyA(szImg, cchSize, pszImgSrc); fSucceeded = TRUE; } } } } } } LocalFree(pszCopiedHTML); } return fSucceeded; } HRESULT CFSDropTarget::_FilterDeskImage(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (pdwDefaultEffect) { *pdwDefaultEffect = 0; } if (!PolicyNoActiveDesktop() && !SHRestricted(REST_NOADDDESKCOMP) && _IsDesktopFolder()) { FORMATETC fmte = {g_cfHTML, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { //DANGER WILL ROBINSON: //HTML is UTF-8, a mostly ANSI cross of ANSI and Unicode. Play with // it as is it were ANSI. Find a way to escape the sequences... CHAR *pszData = (CHAR*) GlobalLock(medium.hGlobal); if (pszData) { CHAR szUrl[MAX_URL_STRING]; if (ExtractImageURLFromCFHTML(pszData, GlobalSize(medium.hGlobal), szUrl, ARRAYSIZE(szUrl))) { // The HTML contains an image tag - carry on... DWORD dwDefEffect = 0; DWORD dwEffectAdd = DROPEFFECT_LINK; // NOTE: ignoring dwEffectsAvail! if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail | DROPEFFECT_LINK, DROPEFFECT_LINK); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, 0, 0, DDIDM_CONTENTS_DESKIMG, 0, pfsMenuInfo); hr = S_OK; } GlobalUnlock(medium.hGlobal); } ReleaseStgMedium(&medium); } } return hr; } HRESULT CFSDropTarget::_FilterDeskComp(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (pdwDefaultEffect) { *pdwDefaultEffect = 0; } if (!PolicyNoActiveDesktop() && !SHRestricted(REST_NOADDDESKCOMP) && _IsDesktopFolder()) { FORMATETC fmte = {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { // DANGER WILL ROBINSON: // HTML is UTF-8, a mostly ANSI cross of ANSI and Unicode. Play with // it as is it were ANSI. Find a way to escape the sequences... CHAR *pszData = (CHAR*) GlobalLock(medium.hGlobal); if (pszData) { int nScheme = GetUrlSchemeA(pszData); if ((nScheme != URL_SCHEME_INVALID) && (nScheme != URL_SCHEME_FTP)) { // This is an internet scheme - carry on... DWORD dwDefEffect = 0; DWORD dwEffectAdd = DROPEFFECT_LINK & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_LINK); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, 0, 0, DDIDM_CONTENTS_DESKURL, DROPEFFECT_LINK, // force add this verb pfsMenuInfo); hr = S_OK; } GlobalUnlock(medium.hGlobal); } ReleaseStgMedium(&medium); } } return hr; } HRESULT CFSDropTarget::_FilterOleObj(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (_dwData & DTID_OLEOBJ) { DWORD dwDefEffect = 0; DWORD dwEffectAdd = (DROPEFFECT_COPY | DROPEFFECT_MOVE) & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_COPY); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, DDIDM_SCRAP_COPY, DDIDM_SCRAP_MOVE, 0, 0, pfsMenuInfo); hr = S_OK; } return hr; } HRESULT CFSDropTarget::_FilterOleLink(FORMATETC* pfmte, DWORD grfKeyFlags, DWORD dwEffectsAvail, DWORD* pdwEffects, DWORD* pdwDefaultEffect, FSMENUINFO* pfsMenuInfo) { ASSERT(pdwEffects); HRESULT hr = S_FALSE; if (_dwData & DTID_OLELINK) { DWORD dwDefEffect = 0; DWORD dwEffectAdd = DROPEFFECT_LINK & dwEffectsAvail; if (pdwDefaultEffect) { dwDefEffect = _GetDefaultEffect(grfKeyFlags, dwEffectAdd, dwEffectsAvail, DROPEFFECT_LINK); *pdwDefaultEffect = dwDefEffect; } _AddVerbs(pdwEffects, dwEffectAdd, dwDefEffect, 0, 0, DDIDM_DOCLINK, 0, pfsMenuInfo); hr = S_OK; } return hr; } HRESULT CFSDropTarget::_CreateURLDeskComp(IDataObject *pdtobj, POINTL pt) { // This code should only be entered if DDIDM_CONTENTS_DESKURL was added to the menu, // and it has these checks: ASSERT(!PolicyNoActiveDesktop() && !SHRestricted(REST_NOADDDESKCOMP) && _IsDesktopFolder()); STGMEDIUM medium = {0}; FORMATETC fmte = {g_cfShellURL, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; HRESULT hr = pdtobj->GetData(&fmte, &medium); if (SUCCEEDED(hr)) { //DANGER WILL ROBINSON: //HTML is UTF-8, a mostly ANSI cross of ANSI and Unicode. Play with // it as is it were ANSI. Find a way to escape the sequences... CHAR *pszData = (CHAR*) GlobalLock(medium.hGlobal); if (pszData) { int nScheme = GetUrlSchemeA(pszData); if ((nScheme != URL_SCHEME_INVALID) && (nScheme != URL_SCHEME_FTP)) { // This is an internet scheme - URL hr = CreateDesktopComponents(pszData, NULL, _hwnd, DESKCOMP_URL, pt.x, pt.y); } GlobalUnlock(medium.hGlobal); } else { hr = E_FAIL; } ReleaseStgMedium(&medium); } return hr; } HRESULT CFSDropTarget::_CreateDeskCompImage(IDataObject *pdtobj, POINTL pt) { ASSERT(!PolicyNoActiveDesktop() && !SHRestricted(REST_NOADDDESKCOMP) && _IsDesktopFolder()); FORMATETC fmte = {g_cfHTML, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; HRESULT hr = pdtobj->GetData(&fmte, &medium); if (SUCCEEDED(hr)) { //DANGER WILL ROBINSON: //HTML is UTF-8, a mostly ANSI cross of ANSI and Unicode. Play with // it as is it were ANSI. Find a way to escape the sequences... CHAR *pszData = (CHAR*) GlobalLock(medium.hGlobal); if (pszData) { CHAR szUrl[MAX_URL_STRING]; if (ExtractImageURLFromCFHTML(pszData, GlobalSize(medium.hGlobal), szUrl, ARRAYSIZE(szUrl))) { // The HTML contains an image tag - carry on... ADDTODESKTOP *pToAD; hr = SHLocalAlloc(sizeof(*pToAD), &pToAD); if (SUCCEEDED(hr)) { pToAD->hwnd = _hwnd; if (SUCCEEDED(StringCchCopyA(pToAD->szUrl, ARRAYSIZE(pToAD->szUrl), szUrl))) { pToAD->dwFlags = DESKCOMP_IMAGE; pToAD->pt = pt; if (SHCreateThread(AddToActiveDesktopThreadProc, pToAD, CTF_COINIT, NULL)) { hr = S_OK; } else { LocalFree(pToAD); hr = E_OUTOFMEMORY; } } else { LocalFree(pToAD); hr = E_FAIL; } } } else { hr = E_FAIL; } GlobalUnlock(medium.hGlobal); } else hr = E_FAIL; ReleaseStgMedium(&medium); } return hr; } // // read byte by byte until we hit the null terminating char // return: the number of bytes read // HRESULT StringReadFromStream(IStream* pstm, LPSTR pszBuf, UINT cchBuf) { UINT cch = 0; do { *pszBuf = 0; pstm->Read(pszBuf, sizeof(CHAR), NULL); cch++; } while (*pszBuf++ && cch <= cchBuf); return cch; } HRESULT CopyStreamToFile(IStream* pstmSrc, LPCTSTR pszFile, ULONGLONG ullFileSize) { IStream *pstmFile; HRESULT hr = SHCreateStreamOnFile(pszFile, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstmFile); if (SUCCEEDED(hr)) { hr = CopyStreamUI(pstmSrc, pstmFile, NULL, ullFileSize); pstmFile->Release(); } return hr; } HRESULT CFSDropTarget::_CreatePackage(IDataObject *pdtobj) { ILockBytes* pLockBytes; HRESULT hr = CreateILockBytesOnHGlobal(NULL, TRUE, &pLockBytes); if (SUCCEEDED(hr)) { STGMEDIUM medium; medium.tymed = TYMED_ISTORAGE; hr = StgCreateDocfileOnILockBytes(pLockBytes, STGM_DIRECT | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &medium.pstg); if (SUCCEEDED(hr)) { FORMATETC fmte = {g_cfEmbeddedObject, NULL, DVASPECT_CONTENT, -1, TYMED_ISTORAGE}; hr = pdtobj->GetDataHere(&fmte, &medium); if (SUCCEEDED(hr)) { IStream* pstm; #ifdef DEBUG STATSTG stat; if (SUCCEEDED(medium.pstg->Stat(&stat, STATFLAG_NONAME))) { ASSERT(IsEqualCLSID(CLSID_OldPackage, stat.clsid) || IsEqualCLSID(CLSID_CPackage, stat.clsid)); } #endif // DEBUG #define PACKAGER_ICON 2 #define PACKAGER_CONTENTS L"\001Ole10Native" #define PACKAGER_EMBED_TYPE 3 hr = medium.pstg->OpenStream(PACKAGER_CONTENTS, 0, STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pstm); if (SUCCEEDED(hr)) { DWORD dw; WORD w; CHAR szName[MAX_PATH]; CHAR szTemp[MAX_PATH]; if (SUCCEEDED(pstm->Read(&dw, sizeof(dw), NULL)) && // pkg size SUCCEEDED(pstm->Read(&w, sizeof(w), NULL)) && // pkg appearance (PACKAGER_ICON == w) && SUCCEEDED(StringReadFromStream(pstm, szName, ARRAYSIZE(szName))) && SUCCEEDED(StringReadFromStream(pstm, szTemp, ARRAYSIZE(szTemp))) && // icon path SUCCEEDED(pstm->Read(&w, sizeof(w), NULL)) && // icon index SUCCEEDED(pstm->Read(&w, sizeof(w), NULL)) && // panetype (PACKAGER_EMBED_TYPE == w) && SUCCEEDED(pstm->Read(&dw, sizeof(dw), NULL)) && // filename size SUCCEEDED(pstm->Read(szTemp, min(dw, sizeof(szTemp)), NULL)) && // filename SUCCEEDED(pstm->Read(&dw, sizeof(dw), NULL))) // get file size { // The rest of the stream is the file contents TCHAR szPath[MAX_PATH], szBase[MAX_PATH], szDest[MAX_PATH]; _GetPath(szPath, ARRAYSIZE(szPath)); SHAnsiToTChar(szName, szBase, ARRAYSIZE(szBase)); PathAppend(szPath, szBase); if (PathYetAnotherMakeUniqueName(szDest, szPath, NULL, szBase)) { TraceMsg(TF_GENERAL, "CFSIDLDropTarget pkg: %s", szDest); hr = CopyStreamToFile(pstm, szDest, dw); if (SUCCEEDED(hr)) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szDest, NULL); if (_fBkDropTarget && _hwnd) { PositionFileFromDrop(_hwnd, szDest, NULL); } } } else { hr = E_FAIL; } } else { hr = E_UNEXPECTED; } pstm->Release(); } } medium.pstg->Release(); } pLockBytes->Release(); } return hr; } HRESULT CFSDropTarget::_GetPath(LPTSTR pszPath, int cchPath) { return _pFolder->_GetPath(pszPath, cchPath); } LPCITEMIDLIST CFSDropTarget::_GetIDList() { return _pFolder->_GetIDList(); } DWORD CFSDropTarget::_EffectFromFolder() { if (-1 == _dwEffectFolder) { _dwEffectFolder = DROPEFFECT_NONE; // re-set to nothing (0) TCHAR szPath[MAX_PATH]; // add a simple pathisroot check here to prevent it from hitting the disk (mostly floppy) // when we want the dropeffect probe to be fast (sendto, hovering over drives in view). // its not likely that we'll want to modify the root's drop effect, and this still allows // dropeffect modification on floppy subfolders. if (SUCCEEDED(_GetPath(szPath, ARRAYSIZE(szPath))) && !PathIsRoot(szPath) && PathAppend(szPath, TEXT("desktop.ini"))) { _dwEffectFolder = GetPrivateProfileInt(STRINI_CLASSINFO, TEXT("DefaultDropEffect"), 0, szPath); } } return _dwEffectFolder; } BOOL AllRegisteredPrograms(HDROP hDrop) { TCHAR szPath[MAX_PATH]; for (UINT i = 0; DragQueryFile(hDrop, i, szPath, ARRAYSIZE(szPath)); i++) { if (!PathIsRegisteredProgram(szPath)) return FALSE; } return TRUE; } BOOL IsBriefcaseRoot(IDataObject *pdtobj) { BOOL bRet = FALSE; STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { // Is there a briefcase root in this pdtobj? IShellFolder2 *psf; if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder2, HIDA_GetPIDLFolder(pida), &psf)))) { for (UINT i = 0; i < pida->cidl; i++) { CLSID clsid; bRet = SUCCEEDED(GetItemCLSID(psf, IDA_GetIDListPtr(pida, i), &clsid)) && IsEqualCLSID(clsid, CLSID_Briefcase); if (bRet) break; } psf->Release(); } HIDA_ReleaseStgMedium(pida, &medium); } return bRet; } // // the "default effect" defines what will be choosen out of the allowed effects // // If the data object does NOT contain HDROP -> "none" // else if the source data object has a default drop effect folder list (maybe based on sub folderness) // else if the source is root or registered progam -> "link" // else if this is within a volume -> "move" // else if this is a briefcase -> "move" // else -> "copy" // DWORD CFSDropTarget::_FilesystemAdjustedDefaultEffect(DWORD dwCurEffectAvail) { DWORD dwDefEffect = DROPEFFECT_NONE; FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM medium = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmte, &medium))) { TCHAR szPath[MAX_PATH]; DragQueryFile((HDROP) medium.hGlobal, 0, szPath, ARRAYSIZE(szPath)); // focused item // DROPEFFECTFOLDERLIST allows the source of the data // to specify the desired drop effect for items under // certain parts of the name space. // // cd-burning does this to avoid the default move/copy computation // that would kick in for cross volume CD burning/staging area transfers FORMATETC fmteDropFolders = {g_cfDropEffectFolderList, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM mediumDropFolders = {0}; if (SUCCEEDED(_pdtobj->GetData(&fmteDropFolders, &mediumDropFolders))) { DROPEFFECTFOLDERLIST *pdefl = (DROPEFFECTFOLDERLIST*)GlobalLock(mediumDropFolders.hGlobal); if (pdefl) { // get the default effect from the list -- in the staging area case this is DROPEFFECT_COPY // so its a copy even if the staging area and source are on the same volume. dwDefEffect = pdefl->dwDefaultDropEffect; for (INT i = 0; i < pdefl->cFolders; i++) { // some folders are excluded, for example if you move a file from one part of the staging // area to another we override (to DROPEFFECT_MOVE in this case). if (PathIsEqualOrSubFolder(pdefl->aFolders[i].wszPath, szPath)) { dwDefEffect = pdefl->aFolders[i].dwDropEffect; break; } } GlobalUnlock(pdefl); } ReleaseStgMedium(&mediumDropFolders); } if (DROPEFFECT_NONE == dwDefEffect) { dwDefEffect = _EffectFromFolder(); } // If we didn't get a drop effect (==0) then lets fall back to the old checks if (DROPEFFECT_NONE == dwDefEffect) { TCHAR szFolder[MAX_PATH]; _GetPath(szFolder, ARRAYSIZE(szFolder)); // drive/UNC roots and installed programs get link if (PathIsRoot(szPath) || AllRegisteredPrograms((HDROP)medium.hGlobal)) { dwDefEffect = DROPEFFECT_LINK; } else if (PathIsSameRoot(szPath, szFolder)) { dwDefEffect = DROPEFFECT_MOVE; } else if (IsBriefcaseRoot(_pdtobj)) { // briefcase default to move even accross volumes dwDefEffect = DROPEFFECT_MOVE; } else { dwDefEffect = DROPEFFECT_COPY; } } ReleaseStgMedium(&medium); } else if (SUCCEEDED(_pdtobj->QueryGetData(&fmte))) { // but QueryGetData() succeeds! // this means this data object has HDROP but can't // provide it until it is dropped. Let's assume we are copying. dwDefEffect = DROPEFFECT_COPY; } // Switch default verb if the dwCurEffectAvail hint suggests that we picked an // unavailable effect (this code applies to MOVE and COPY only): dwCurEffectAvail &= (DROPEFFECT_MOVE | DROPEFFECT_COPY); if ((DROPEFFECT_MOVE == dwDefEffect) && (DROPEFFECT_COPY == dwCurEffectAvail)) { // If we were going to return MOVE, and only COPY is available, return COPY: dwDefEffect = DROPEFFECT_COPY; } else if ((DROPEFFECT_COPY == dwDefEffect) && (DROPEFFECT_MOVE == dwCurEffectAvail)) { // If we were going to return COPY, and only MOVE is available, return MOVE: dwDefEffect = DROPEFFECT_MOVE; } return dwDefEffect; } // // make sure that the default effect is among the allowed effects // DWORD CFSDropTarget::_LimitDefaultEffect(DWORD dwDefEffect, DWORD dwEffectsAllowed) { if (dwDefEffect & dwEffectsAllowed) return dwDefEffect; if (dwEffectsAllowed & DROPEFFECT_COPY) return DROPEFFECT_COPY; if (dwEffectsAllowed & DROPEFFECT_MOVE) return DROPEFFECT_MOVE; if (dwEffectsAllowed & DROPEFFECT_LINK) return DROPEFFECT_LINK; return DROPEFFECT_NONE; } // Handy abbreviation #define TYMED_ALLCONTENT (TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ISTORAGE) // Use FSDH for registered clipboard formats (anything of the form g_cf*) // Use _FSDH for predefined clipboard formats (like CF_HDROP or 0) // Generate the _DATA_HANDLER array #define FSDH(pfn, cf, dva, tymed) { { 0, NULL, dva, -1, tymed }, pfn, &cf } #define _FSDH(pfn, cf, dva, tymed) { { (CLIPFORMAT)cf, NULL, dva, -1, tymed }, pfn, NULL } // NOTE: the order is important (particularly for multiple entries with the same FORMATETC) CFSDropTarget::_DATA_HANDLER CFSDropTarget::rg_data_handlers[NUM_DATA_HANDLERS] = { FSDH(_FilterFileContents, g_cfFileGroupDescriptorW, DVASPECT_CONTENT, TYMED_HGLOBAL), FSDH(_FilterFileContentsOLEHack, g_cfFileGroupDescriptorW, DVASPECT_LINK, TYMED_HGLOBAL), FSDH(_FilterFileContents, g_cfFileGroupDescriptorA, DVASPECT_CONTENT, TYMED_HGLOBAL), FSDH(_FilterFileContentsOLEHack, g_cfFileGroupDescriptorA, DVASPECT_LINK, TYMED_HGLOBAL), FSDH(_FilterFileContents, g_cfFileContents, DVASPECT_CONTENT, TYMED_ALLCONTENT), FSDH(_FilterFileContentsOLEHack, g_cfFileContents, DVASPECT_LINK, TYMED_ALLCONTENT), _FSDH(_FilterBriefcase, CF_HDROP, DVASPECT_CONTENT, TYMED_HGLOBAL), _FSDH(_FilterSneakernetBriefcase, CF_HDROP, DVASPECT_CONTENT, TYMED_HGLOBAL), _FSDH(_FilterHDROP, CF_HDROP, DVASPECT_CONTENT, TYMED_HGLOBAL), _FSDH(_FilterDeskCompHDROP, CF_HDROP, DVASPECT_CONTENT, TYMED_HGLOBAL), FSDH(_FilterHIDA, g_cfHIDA, DVASPECT_CONTENT, TYMED_HGLOBAL), FSDH(_FilterOlePackage, g_cfEmbeddedObject, DVASPECT_CONTENT, TYMED_ISTORAGE), FSDH(_FilterDeskImage, g_cfHTML, DVASPECT_CONTENT, TYMED_HGLOBAL), FSDH(_FilterDeskComp, g_cfShellURL, DVASPECT_CONTENT, TYMED_HGLOBAL), _FSDH(_FilterOleObj, 0, DVASPECT_CONTENT, TYMED_HGLOBAL), _FSDH(_FilterOleLink, 0, DVASPECT_CONTENT, TYMED_HGLOBAL), }; // Note that it's safe to race with another thread in this code // since the function is idemponent. (Call it as many times as you // like -- only the first time through actually does anything.) void CFSDropTarget::_Init_rg_data_handlers() { for (int i = 0; i < ARRAYSIZE(rg_data_handlers); i++) { // If this assertion fires, then you have to change the value of // NUM_DATA_HANDLERS to match the number of entries in the array // definition. ASSERT(rg_data_handlers[i].fmte.tymed); if (rg_data_handlers[i].pcfInit) { rg_data_handlers[i].fmte.cfFormat = *rg_data_handlers[i].pcfInit; } } } // // returns the default effect. // also modifies *pdwEffectInOut to indicate "available" operations. // DWORD CFSDropTarget::_DetermineEffects(DWORD grfKeyState, DWORD *pdwEffectInOut, HMENU hmenu) { DWORD dwDefaultEffect = DROPEFFECT_NONE; DWORD dwEffectsUsed = DROPEFFECT_NONE; _Init_rg_data_handlers(); // Loop through formats, factoring in both the order of the enumerator and // the order of our rg_data_handlers to determine the default effect // (and possibly, to create the drop context menu) FSMENUINFO fsmi = { hmenu, 0, 0, 0 }; IEnumFORMATETC *penum; AssertMsg((NULL != _pdtobj), TEXT("CFSDropTarget::_DetermineEffects() _pdtobj is NULL but we need it. this=%#08lx"), this); if (_pdtobj && SUCCEEDED(_pdtobj->EnumFormatEtc(DATADIR_GET, &penum))) { FORMATETC fmte; ULONG celt; while (penum->Next(1, &fmte, &celt) == S_OK) { for (int i = 0; i < ARRAYSIZE(rg_data_handlers); i++) { if (rg_data_handlers[i].fmte.cfFormat == fmte.cfFormat && rg_data_handlers[i].fmte.dwAspect == fmte.dwAspect && (rg_data_handlers[i].fmte.tymed & fmte.tymed)) { // keep passing dwDefaultEffect until someone computes one, this // lets the first guy that figures out the default be the default (this->*(rg_data_handlers[i].pfnGetDragDropInfo))( &fmte, grfKeyState, *pdwEffectInOut, &dwEffectsUsed, (DROPEFFECT_NONE == dwDefaultEffect) ? &dwDefaultEffect : NULL, hmenu ? &fsmi : NULL); } } SHFree(fmte.ptd); } penum->Release(); } // Loop through the rg_data_handlers that don't have an associated clipboard format last for (int i = 0; i < ARRAYSIZE(rg_data_handlers); i++) { if (0 == rg_data_handlers[i].fmte.cfFormat) { // if default effect is still not computed continue to pass that (this->*(rg_data_handlers[i].pfnGetDragDropInfo))( NULL, grfKeyState, *pdwEffectInOut, &dwEffectsUsed, (DROPEFFECT_NONE == dwDefaultEffect) ? &dwDefaultEffect : NULL, hmenu ? &fsmi : NULL); } } *pdwEffectInOut &= dwEffectsUsed; dwDefaultEffect = _LimitDefaultEffect(dwDefaultEffect, *pdwEffectInOut); DebugMsg(TF_FSTREE, TEXT("CFSDT::GetDefaultEffect dwDef=%x, dwEffUsed=%x, *pdw=%x"), dwDefaultEffect, dwEffectsUsed, *pdwEffectInOut); return dwDefaultEffect; // this is what we want to do } // This is used to map command id's back to dropeffect's: const struct { UINT uID; DWORD dwEffect; } c_IDFSEffects[] = { DDIDM_COPY, DROPEFFECT_COPY, DDIDM_MOVE, DROPEFFECT_MOVE, DDIDM_CONTENTS_DESKCOMP, DROPEFFECT_LINK, DDIDM_LINK, DROPEFFECT_LINK, DDIDM_SCRAP_COPY, DROPEFFECT_COPY, DDIDM_SCRAP_MOVE, DROPEFFECT_MOVE, DDIDM_DOCLINK, DROPEFFECT_LINK, DDIDM_CONTENTS_COPY, DROPEFFECT_COPY, DDIDM_CONTENTS_MOVE, DROPEFFECT_MOVE, DDIDM_CONTENTS_LINK, DROPEFFECT_LINK, DDIDM_CONTENTS_DESKIMG, DROPEFFECT_LINK, DDIDM_SYNCCOPYTYPE, DROPEFFECT_COPY, // (order is important) DDIDM_SYNCCOPY, DROPEFFECT_COPY, DDIDM_OBJECT_COPY, DROPEFFECT_COPY, DDIDM_OBJECT_MOVE, DROPEFFECT_MOVE, DDIDM_CONTENTS_DESKURL, DROPEFFECT_LINK, }; void CFSDropTarget::_FixUpDefaultItem(HMENU hmenu, DWORD dwDefEffect) { // only do stuff if there is no default item already and we have a default effect if ((GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1) && dwDefEffect) { for (int i = 0; i < GetMenuItemCount(hmenu); i++) { // for menu item matching default effect, make it the default. MENUITEMINFO mii = { 0 }; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA | MIIM_STATE; if (GetMenuItemInfo(hmenu, i, MF_BYPOSITION, &mii) && (mii.dwItemData == dwDefEffect)) { mii.fState |= MFS_DEFAULT; SetMenuItemInfo(hmenu, i, MF_BYPOSITION, &mii); break; } } } } HRESULT CFSDropTarget::_DragDropMenu(FSDRAGDROPMENUPARAM *pddm) { HRESULT hr = E_OUTOFMEMORY; // assume error DWORD dwEffectOut = 0; // assume no-ope. if (pddm->hmenu) { UINT idCmd; UINT idCmdFirst = DDIDM_EXTFIRST; HDXA hdxa = HDXA_Create(); HDCA hdca = DCA_Create(); if (hdxa && hdca) { // Enumerate the DD handlers and let them append menu items. for (DWORD i = 0; i < pddm->ck; i++) { DCA_AddItemsFromKey(hdca, pddm->rghk[i], STRREG_SHEX_DDHANDLER); } idCmdFirst = HDXA_AppendMenuItems(hdxa, pddm->pdtobj, pddm->ck, pddm->rghk, _GetIDList(), pddm->hmenu, 0, DDIDM_EXTFIRST, DDIDM_EXTLAST, 0, hdca); } // modifier keys held down to force operations that are not permitted (for example // alt to force a shortcut from the start menu, which does not have SFGAO_CANLINK) // can result in no default items on the context menu. however in displaying the // cursor overlay in this case we fall back to DROPEFFECT_COPY. a left drag then // tries to invoke the default menu item (user thinks its copy) but theres no default. // this function selects a default menu item to match the default effect if there // is no default item already. _FixUpDefaultItem(pddm->hmenu, pddm->dwDefEffect); // If this dragging is caused by the left button, simply choose // the default one, otherwise, pop up the context menu. If there // is no key state info and the original effect is the same as the // current effect, choose the default one, otherwise pop up the // context menu. if ((_grfKeyStateLast & MK_LBUTTON) || (!_grfKeyStateLast && (*(pddm->pdwEffect) == pddm->dwDefEffect))) { idCmd = GetMenuDefaultItem(pddm->hmenu, MF_BYCOMMAND, 0); // This one MUST be called here. Please read its comment block. DAD_DragLeave(); if (_hwnd) SetForegroundWindow(_hwnd); } else { // Note that SHTrackPopupMenu calls DAD_DragLeave(). idCmd = SHTrackPopupMenu(pddm->hmenu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN, pddm->pt.x, pddm->pt.y, 0, _hwnd, NULL); } // // We also need to call this here to release the dragged image. // DAD_SetDragImage(NULL, NULL); // // Check if the user selected one of add-in menu items. // if (idCmd == 0) { hr = S_OK; // Canceled by the user, return S_OK } else if (InRange(idCmd, DDIDM_EXTFIRST, DDIDM_EXTLAST)) { // // Yes. Let the context menu handler process it. // CMINVOKECOMMANDINFOEX ici = { sizeof(ici), 0L, _hwnd, (LPSTR)MAKEINTRESOURCE(idCmd - DDIDM_EXTFIRST), NULL, NULL, SW_NORMAL, }; // record if the shift/control keys were down at the time of the drop if (_grfKeyStateLast & MK_SHIFT) { ici.fMask |= CMIC_MASK_SHIFT_DOWN; } if (_grfKeyStateLast & MK_CONTROL) { ici.fMask |= CMIC_MASK_CONTROL_DOWN; } // We may not want to ignore the error code. (Can happen when you use the context menu // to create new folders, but I don't know if that can happen here.). HDXA_LetHandlerProcessCommandEx(hdxa, &ici, NULL); hr = S_OK; } else { for (int nItem = 0; nItem < ARRAYSIZE(c_IDFSEffects); ++nItem) { if (idCmd == c_IDFSEffects[nItem].uID) { dwEffectOut = c_IDFSEffects[nItem].dwEffect; break; } } hr = S_FALSE; } if (hdca) DCA_Destroy(hdca); if (hdxa) HDXA_Destroy(hdxa); pddm->idCmd = idCmd; } *pddm->pdwEffect = dwEffectOut; return hr; } void _MapName(void *hNameMap, LPTSTR pszPath, int cchPath) { if (hNameMap) { SHNAMEMAPPING *pNameMapping; for (int i = 0; (pNameMapping = SHGetNameMappingPtr((HDSA)hNameMap, i)) != NULL; i++) { if (lstrcmpi(pszPath, pNameMapping->pszOldPath) == 0) { StringCchCopy(pszPath, cchPath, pNameMapping->pszNewPath); break; } } } } // convert double null list of files to array of pidls int FileListToIDArray(LPCTSTR pszFiles, void *hNameMap, LPITEMIDLIST **pppidl) { int i = 0; int nItems = CountFiles(pszFiles); LPITEMIDLIST *ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, nItems * sizeof(*ppidl)); if (ppidl) { *pppidl = ppidl; while (*pszFiles) { TCHAR szPath[MAX_PATH]; StrCpyN(szPath, pszFiles, ARRAYSIZE(szPath)); _MapName(hNameMap, szPath, ARRAYSIZE(szPath)); ppidl[i] = SHSimpleIDListFromPath(szPath); pszFiles += lstrlen(pszFiles) + 1; i++; } } return i; } // move items to the new drop location void CFSDropTarget::_MoveSelectIcons(IDataObject *pdtobj, IFolderView* pfv, void *hNameMap, LPCTSTR pszFiles, BOOL fMove, HDROP hDrop) { LPITEMIDLIST *ppidl = NULL; int cidl; if (pszFiles) { cidl = FileListToIDArray(pszFiles, hNameMap, &ppidl); } else { cidl = CreateMoveCopyList(hDrop, hNameMap, &ppidl); } if (ppidl) { if (pfv) PositionItems(pfv, (LPCITEMIDLIST*)ppidl, cidl, pdtobj, fMove ? &_ptDrop : NULL); FreeIDListArray(ppidl, cidl); } } // this is the ILIsParent which matches up the desktop with the desktop directory. BOOL AliasILIsParent(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { LPITEMIDLIST pidlUse1 = SHLogILFromFSIL(pidl1); if (pidlUse1) pidl1 = pidlUse1; LPITEMIDLIST pidlUse2 = SHLogILFromFSIL(pidl2); if (pidlUse2) pidl2 = pidlUse2; BOOL fSame = ILIsParent(pidl1, pidl2, TRUE); ILFree(pidlUse1); // NULL is OK here ILFree(pidlUse2); return fSame; } // in: // pszDestDir destination dir for new file names // pszDestSpecs double null list of destination specs // // returns: // double null list of fully qualified destination file names to be freed // with LocalFree() // LPTSTR RemapDestNamesW(LPCTSTR pszDestDir, LPCWSTR pszDestSpecs) { UINT cbDestSpec = (lstrlen(pszDestDir) + 1) * sizeof(TCHAR); LPCWSTR pszTemp; UINT cbAlloc = sizeof(TCHAR); // for double NULL teriminaion of entire string // compute length of buffer to aloc for (pszTemp = pszDestSpecs; *pszTemp; pszTemp += lstrlenW(pszTemp) + 1) { // +1 for null teriminator cbAlloc += cbDestSpec + (lstrlenW(pszTemp) + 1) * sizeof(TCHAR); } LPTSTR pszRet = (LPTSTR)LocalAlloc(LPTR, cbAlloc); if (pszRet) { LPTSTR pszDest = pszRet; for (pszTemp = pszDestSpecs; *pszTemp; pszTemp += lstrlenW(pszTemp) + 1) { // PathCombine requires dest buffer of MAX_PATH size or it'll rip in call // to PathCanonicalize (IsBadWritePtr) TCHAR szTempDest[MAX_PATH]; PathCombine(szTempDest, pszDestDir, pszTemp); // pszDest allocated exactly, strcpy okay. lstrcpy(pszDest, szTempDest); pszDest += lstrlen(pszDest) + 1; ASSERT((UINT)((BYTE *)pszDest - (BYTE *)pszRet) < cbAlloc); ASSERT(*pszDest == 0); // zero init alloc } ASSERT((LPTSTR)((BYTE *)pszRet + cbAlloc - sizeof(TCHAR)) >= pszDest); ASSERT(*pszDest == 0); // zero init alloc } return pszRet; } LPTSTR RemapDestNamesA(LPCTSTR pszDestDir, LPCSTR pszDestSpecs) { UINT cbDestSpec = (lstrlen(pszDestDir) + 1) * sizeof(TCHAR); LPCSTR pszTemp; LPTSTR pszRet; UINT cbAlloc = sizeof(TCHAR); // for double NULL teriminaion of entire string // compute length of buffer to aloc for (pszTemp = pszDestSpecs; *pszTemp; pszTemp += lstrlenA(pszTemp) + 1) { // +1 for null teriminator cbAlloc += cbDestSpec + (lstrlenA(pszTemp) + 1) * sizeof(TCHAR); } pszRet = (LPTSTR)LocalAlloc(LPTR, cbAlloc); if (pszRet) { LPTSTR pszDest = pszRet; for (pszTemp = pszDestSpecs; *pszTemp; pszTemp += lstrlenA(pszTemp) + 1) { // PathCombine requires dest buffer of MAX_PATH size or it'll rip in call // to PathCanonicalize (IsBadWritePtr) TCHAR szTempDest[MAX_PATH]; WCHAR wszTemp[MAX_PATH]; SHAnsiToUnicode(pszTemp, wszTemp, ARRAYSIZE(wszTemp)); PathCombine(szTempDest, pszDestDir, wszTemp); // pszDest allocated exactly, strcpy okay. lstrcpy(pszDest, szTempDest); pszDest += lstrlen(pszDest) + 1; ASSERT((UINT)((BYTE *)pszDest - (BYTE *)pszRet) < cbAlloc); ASSERT(*pszDest == 0); // zero init alloc } ASSERT((LPTSTR)((BYTE *)pszRet + cbAlloc - sizeof(TCHAR)) >= pszDest); ASSERT(*pszDest == 0); // zero init alloc } return pszRet; } LPTSTR _GetDestNames(IDataObject *pdtobj, LPCTSTR pszPath) { LPTSTR pszDestNames = NULL; STGMEDIUM medium; FORMATETC fmte = {g_cfFileNameMapW, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (S_OK == pdtobj->GetData(&fmte, &medium)) { pszDestNames = RemapDestNamesW(pszPath, (LPWSTR)GlobalLock(medium.hGlobal)); ReleaseStgMediumHGLOBAL(medium.hGlobal, &medium); } else { fmte.cfFormat = g_cfFileNameMapA; if (S_OK == pdtobj->GetData(&fmte, &medium)) { pszDestNames = RemapDestNamesA(pszPath, (LPSTR)GlobalLock(medium.hGlobal)); ReleaseStgMediumHGLOBAL(medium.hGlobal, &medium); } } return pszDestNames; } BOOL _IsInSameFolder(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj) { BOOL bRet = FALSE; STGMEDIUM medium = {0}; LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { for (UINT i = 0; i < pida->cidl; i++) { LPITEMIDLIST pidl = IDA_FullIDList(pida, i); if (pidl) { // if we're doing keyboard cut/copy/paste // to and from the same directories // This is needed for common desktop support - BobDay/EricFlo if (AliasILIsParent(pidlFolder, pidl)) { bRet = TRUE; } ILFree(pidl); } } HIDA_ReleaseStgMedium(pida, &medium); } return bRet; } LPCTSTR _RootSpecialCase(LPCTSTR pszFiles, LPTSTR pszSrc, UINT cchSrc, LPTSTR pszDest, UINT cchDest) { if ((1 == CountFiles(pszFiles)) && PathIsRoot(pszFiles) && (cchDest >= MAX_PATH) && (cchSrc >= MAX_PATH)) { SHFILEINFO sfi; // NOTE: don't use SHGFI_USEFILEATTRIBUTES because the simple IDList // support for \\server\share produces the wrong name if (SHGetFileInfo(pszFiles, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME)) { if (!(PCS_FATAL & PathCleanupSpec(pszDest, sfi.szDisplayName))) { PathAppend(pszDest, sfi.szDisplayName); // sub dir name based on source root path PathCombine(pszSrc, pszFiles, TEXT("*.*")); // all files on source pszFiles = pszSrc; } } } return pszFiles; } void CFSDropTarget::_MoveCopy(IDataObject *pdtobj, IFolderView* pfv, HDROP hDrop) { #ifdef DEBUG if (_hwnd == NULL) { TraceMsg(TF_GENERAL, "_MoveCopy() without an hwnd which will prevent displaying insert disk UI"); } #endif // DEBUG DRAGINFO di = { sizeof(di) }; if (DragQueryInfo(hDrop, &di)) { TCHAR szDest[MAX_PATH] = {0}; // zero init for dbl null termination _GetPath(szDest, ARRAYSIZE(szDest)); switch (_idCmd) { case DDIDM_MOVE: if (_fSameHwnd) { _MoveSelectIcons(pdtobj, pfv, NULL, NULL, TRUE, hDrop); break; } // fall through... case DDIDM_COPY: { TCHAR szAltSource[MAX_PATH] = {0}; // zero init for dbl null termination LPCTSTR pszSource = _RootSpecialCase(di.lpFileList, szAltSource, ARRAYSIZE(szAltSource), szDest, ARRAYSIZE(szDest)); SHFILEOPSTRUCT fo = { _hwnd, (DDIDM_COPY == _idCmd) ? FO_COPY : FO_MOVE, pszSource, szDest, FOF_WANTMAPPINGHANDLE | FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR }; if (fo.wFunc == FO_MOVE && IsFolderSecurityModeOn()) { fo.fFlags |= FOF_NOCOPYSECURITYATTRIBS; } // if they are in the same hwnd or to and from // the same directory, turn on the automatic rename on collision flag if (_fSameHwnd || ((DDIDM_COPY == _idCmd) && _IsInSameFolder(_GetIDList(), pdtobj))) { // do rename on collision for copy; fo.fFlags |= FOF_RENAMEONCOLLISION; } // see if there is a rename mapping from recycle bin (or someone else) LPTSTR pszDestNames = _GetDestNames(pdtobj, szDest); if (pszDestNames) { fo.pTo = pszDestNames; fo.fFlags |= FOF_MULTIDESTFILES; fo.fFlags &= ~FOF_ALLOWUNDO; // HACK, this came from the recycle bin, don't allow undo } { static UINT s_cfFileOpFlags = 0; if (0 == s_cfFileOpFlags) s_cfFileOpFlags = RegisterClipboardFormat(TEXT("FileOpFlags")); fo.fFlags = (FILEOP_FLAGS)DataObj_GetDWORD(pdtobj, s_cfFileOpFlags, fo.fFlags); } // Check if there were any errors if (SHFileOperation(&fo) == 0 && !fo.fAnyOperationsAborted) { if (_fBkDropTarget) ShellFolderView_SetRedraw(_hwnd, 0); SHChangeNotifyHandleEvents(); // force update now if (_fBkDropTarget) { _MoveSelectIcons(pdtobj, pfv, fo.hNameMappings, pszDestNames, _fDragDrop, hDrop); ShellFolderView_SetRedraw(_hwnd, TRUE); } } if (fo.hNameMappings) SHFreeNameMappings(fo.hNameMappings); if (pszDestNames) { LocalFree((HLOCAL)pszDestNames); // HACK, this usually comes from the bitbucket // but in our shell, we don't handle the moves from the source if (DDIDM_MOVE == _idCmd) BBCheckRestoredFiles(pszSource); } } break; } SHFree(di.lpFileList); } } const UINT c_rgFolderShortcutTargets[] = { CSIDL_STARTMENU, CSIDL_COMMON_STARTMENU, CSIDL_PROGRAMS, CSIDL_COMMON_PROGRAMS, CSIDL_NETHOOD, }; BOOL _ShouldCreateFolderShortcut(LPCTSTR pszFolder) { return PathIsEqualOrSubFolderOf(pszFolder, c_rgFolderShortcutTargets, ARRAYSIZE(c_rgFolderShortcutTargets)); } void CFSDropTarget::_DoDrop(IDataObject *pdtobj, IFolderView* pfv) { HRESULT hr = E_FAIL; // Sleep(10 * 1000); // to debug async case TCHAR szPath[MAX_PATH]; _GetPath(szPath, ARRAYSIZE(szPath)); SHCreateDirectory(NULL, szPath); // if this fails we catch it later switch (_idCmd) { case DDIDM_SYNCCOPY: case DDIDM_SYNCCOPYTYPE: if (_IsBriefcaseTarget()) { IBriefcaseStg *pbrfstg; if (SUCCEEDED(CreateBrfStgFromPath(szPath, _hwnd, &pbrfstg))) { hr = pbrfstg->AddObject(pdtobj, NULL, (DDIDM_SYNCCOPYTYPE == _idCmd) ? AOF_FILTERPROMPT : AOF_DEFAULT, _hwnd); pbrfstg->Release(); } } else { // Perform a sneakernet addition to the briefcase STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { // Is there a briefcase root in this pdtobj? IBriefcaseStg *pbrfstg; if (SUCCEEDED(CreateBrfStgFromIDList(HIDA_GetPIDLFolder(pida), _hwnd, &pbrfstg))) { hr = pbrfstg->AddObject(pdtobj, szPath, (DDIDM_SYNCCOPYTYPE == _idCmd) ? AOF_FILTERPROMPT : AOF_DEFAULT, _hwnd); pbrfstg->Release(); } HIDA_ReleaseStgMedium(pida, &medium); } } break; case DDIDM_COPY: case DDIDM_MOVE: { STGMEDIUM medium; FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; hr = pdtobj->GetData(&fmte, &medium); if (SUCCEEDED(hr)) { _MoveCopy(pdtobj, pfv, (HDROP)medium.hGlobal); ReleaseStgMedium(&medium); } } break; case DDIDM_LINK: { int i = 0; LPITEMIDLIST *ppidl = NULL; if (_fBkDropTarget) { i = DataObj_GetHIDACount(pdtobj); ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, sizeof(*ppidl) * i); } // _grfKeyStateLast of 0 means this was a simulated drop UINT uCreateFlags = _grfKeyStateLast && !(_dwEffectFolder & DROPEFFECT_LINK) ? SHCL_USETEMPLATE : 0; if (_ShouldCreateFolderShortcut(szPath)) uCreateFlags |= SHCL_MAKEFOLDERSHORTCUT; ShellFolderView_SetRedraw(_hwnd, FALSE); // passing ppidl == NULL is correct in failure case hr = SHCreateLinks(_hwnd, szPath, pdtobj, uCreateFlags, ppidl); if (ppidl) { if (pfv) PositionItems(pfv, (LPCITEMIDLIST*)ppidl, i, pdtobj, &_ptDrop); FreeIDListArray(ppidl, i); } ShellFolderView_SetRedraw(_hwnd, TRUE); } break; } if (SUCCEEDED(hr) && _dwEffect) { DataObj_SetDWORD(pdtobj, g_cfLogicalPerformedDropEffect, _dwEffect); DataObj_SetDWORD(pdtobj, g_cfPerformedDropEffect, _dwEffect); } SHChangeNotifyHandleEvents(); // force update now } DWORD CALLBACK CFSDropTarget::_DoDropThreadProc(void *pv) { DROPTHREADPARAMS *pdtp = (DROPTHREADPARAMS *)pv; IDataObject *pdtobj; if (SUCCEEDED(CoGetInterfaceAndReleaseStream(pdtp->pstmDataObj, IID_PPV_ARG(IDataObject, &pdtobj)))) { IFolderView* pfv; if (FAILED(CoGetInterfaceAndReleaseStream(pdtp->pstmFolderView, IID_PPV_ARG(IFolderView, &pfv)))) pfv = NULL; pdtp->pThis->_DoDrop(pdtobj, pfv); if (pfv) pfv->Release(); pdtp->pstmFolderView = NULL; // stream now invalid; CoGetInterfaceAndReleaseStream already released it pdtobj->Release(); } pdtp->pstmDataObj = NULL; // stream now invalid; CoGetInterfaceAndReleaseStream already released it _FreeThreadParams(pdtp); CoFreeUnusedLibraries(); return 0; } // REARCHITECT: view and drop related helpers, these use the ugly old private defview messages // we should replace the usage of this stuff with IShellFolderView programming // create the pidl array that contains the destination file names. this is // done by taking the source file names, and translating them through the // name mapping returned by the copy engine. // // // in: // hDrop HDROP containing files recently moved/copied // hNameMap used to translate names // // out: // *pppidl id array of length return value // # of items in pppida // // WARNING! You must use the provided HDROP. Do not attempt to ask the // data object for a HDROP or HIDA or WS_FTP will break! They don't like // it if you ask them for HDROP/HIDA, move the files to a new location // (via the copy engine), and then ask them for HDROP/HIDA a second time. // They notice that "Hey, those files I downloaded last time are gone!" // and then get confused. // STDAPI_(int) CreateMoveCopyList(HDROP hDrop, void *hNameMap, LPITEMIDLIST **pppidl) { int nItems = 0; if (hDrop) { nItems = DragQueryFile(hDrop, (UINT)-1, NULL, 0); *pppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, nItems * sizeof(*pppidl)); if (*pppidl) { for (int i = nItems - 1; i >= 0; i--) { TCHAR szPath[MAX_PATH]; DragQueryFile(hDrop, i, szPath, ARRAYSIZE(szPath)); _MapName(hNameMap, szPath, ARRAYSIZE(szPath)); (*pppidl)[i] = SHSimpleIDListFromPath(szPath); } } } return nItems; } // this is really not related to CFSFolder. it is generic over any view // REARCHITECT: convert view hwnd programming to site pointer STDAPI_(void) PositionFileFromDrop(HWND hwnd, LPCTSTR pszFile, DROPHISTORY *pdh) { LPITEMIDLIST pidl = SHSimpleIDListFromPath(pszFile); if (pidl) { LPITEMIDLIST pidlNew = ILFindLastID(pidl); HWND hwndView = ShellFolderViewWindow(hwnd); SFM_SAP sap; SHChangeNotifyHandleEvents(); // Fill in some easy SAP fields first. sap.uSelectFlags = SVSI_SELECT; sap.fMove = TRUE; sap.pidl = pidlNew; // Now compute the x,y coordinates. // If we have a drop history, use it to determine the // next point. if (pdh) { // fill in the anchor point first... if (!pdh->fInitialized) { ITEMSPACING is; ShellFolderView_GetDropPoint(hwnd, &pdh->ptOrigin); pdh->pt = pdh->ptOrigin; // Compute the first point. // Compute the point deltas. if (ShellFolderView_GetItemSpacing(hwnd, &is)) { pdh->cxItem = is.cxSmall; pdh->cyItem = is.cySmall; pdh->xDiv = is.cxLarge; pdh->yDiv = is.cyLarge; pdh->xMul = is.cxSmall; pdh->yMul = is.cySmall; } else { pdh->cxItem = g_cxIcon; pdh->cyItem = g_cyIcon; pdh->xDiv = pdh->yDiv = pdh->xMul = pdh->yMul = 1; } // First point gets special flags. sap.uSelectFlags |= SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED; pdh->fInitialized = TRUE; // We be initialized. } // if we have no list of offsets, then just inc by icon size.. else if ( !pdh->pptOffset ) { // Simple computation of the next point. pdh->pt.x += pdh->cxItem; pdh->pt.y += pdh->cyItem; } // do this after the above stuff so that we always get our position relative to the anchor // point, if we use the anchor point as the first one things get screwy... if (pdh->pptOffset) { // Transform the old offset to our coordinates. pdh->pt.x = ((pdh->pptOffset[pdh->iItem].x * pdh->xMul) / pdh->xDiv) + pdh->ptOrigin.x; pdh->pt.y = ((pdh->pptOffset[pdh->iItem].y * pdh->yMul) / pdh->yDiv) + pdh->ptOrigin.y; } sap.pt = pdh->pt; // Copy the next point from the drop history. } else { // Preinitialize this puppy in case the folder view doesn't // know what the drop point is (e.g., if it didn't come from // a drag/drop but rather from a paste or a ChangeNotify.) sap.pt.x = 0x7FFFFFFF; // "don't know" sap.pt.y = 0x7FFFFFFF; // Get the drop point, conveniently already in // defview's screen coordinates. // // pdv->bDropAnchor should be TRUE at this point, // see DefView's GetDropPoint() for details. ShellFolderView_GetDropPoint(hwnd, &sap.pt); // Only point gets special flags. sap.uSelectFlags |= SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED; } SendMessage(hwndView, SVM_SELECTANDPOSITIONITEM, 1, (LPARAM)&sap); ILFree(pidl); } } // // Class used to scale and position items for drag and drops. Handles // scaling between different sized views. // // // Bug 165413 (edwardp 8/16/00) Convert IShellFolderView usage in CItemPositioning to IFolderView // class CItemPositioning { // Methods public: CItemPositioning(IFolderView* pifv, LPCITEMIDLIST* apidl, UINT cidl, IDataObject* pdtobj, POINT* ppt); void DragSetPoints(void); void DropPositionItems(void); private: typedef enum { DPIWP_AUTOARRANGE, DPIWP_DATAOBJ, } DPIWP; BOOL _DragShouldPositionItems(void); BOOL _DragGetPoints(POINT* apts); void _DragPositionPoints(POINT* apts); void _DragScalePoints(POINT* apts); POINT* _DropGetPoints(DPIWP dpiwp, STGMEDIUM* pMediam); void _DropFreePoints(DPIWP dpiwp, POINT* apts, STGMEDIUM* pmedium); void _DropPositionPoints(POINT* apts); void _DropScalePoints(POINT* apts); void _DropPositionItemsWithPoints(DPIWP dpiwp); void _DropPositionItems(POINT* apts); void _ScalePoints(POINT* apts, POINT ptFrom, POINT ptTo); POINT* _SkipAnchorPoint(POINT* apts); // Data private: IFolderView* _pfv; LPCITEMIDLIST* _apidl; UINT _cidl; IDataObject* _pdtobj; POINT* _ppt; }; CItemPositioning::CItemPositioning(IFolderView* pifv, LPCITEMIDLIST* apidl, UINT cidl, IDataObject* pdtobj, POINT* ppt) { ASSERT(pifv); ASSERT(apidl); ASSERT(cidl); ASSERT(pdtobj); _pfv = pifv; // No need to addref as long as CPostionItems is only used locally. _apidl = apidl; _cidl = cidl; _pdtobj = pdtobj; // No need to addref as long as CPostionItems is only used locally. _ppt = ppt; } void CItemPositioning::DragSetPoints(void) { if (_DragShouldPositionItems()) { POINT* apts = (POINT*) GlobalAlloc(GPTR, sizeof(POINT) * (_cidl + 1)); if (apts) { if (_DragGetPoints(apts)) { _DragPositionPoints(_SkipAnchorPoint(apts)); _DragScalePoints(_SkipAnchorPoint(apts)); if (FAILED(DataObj_SetGlobal(_pdtobj, g_cfOFFSETS, apts))) GlobalFree((HGLOBAL)apts); } else { GlobalFree((HGLOBAL)apts); } } } } BOOL CItemPositioning::_DragShouldPositionItems() { // Don't position multiple items if they come from a view that doesn't allow // positioning. The position information is not likely to be usefull in this // case. // Always position single items so they show up at the drop point. // Don't bother with position data for 100 or more items. return ((S_OK == _pfv->GetSpacing(NULL)) || 1 == _cidl) && _cidl < 100; } BOOL CItemPositioning::_DragGetPoints(POINT* apts) { BOOL fRet = TRUE; // The first point is the anchor. apts[0] = *_ppt; for (UINT i = 0; i < _cidl; i++) { if (FAILED(_pfv->GetItemPosition(_apidl[i], &apts[i + 1]))) { if (1 == _cidl) { apts[i + 1].x = _ppt->x; apts[i + 1].y = _ppt->y; } else { fRet = FALSE; } } } return fRet; } void CItemPositioning::_DragPositionPoints(POINT* apts) { for (UINT i = 0; i < _cidl; i++) { apts[i].x -= _ppt->x; apts[i].y -= _ppt->y; } } void CItemPositioning::_DragScalePoints(POINT* apts) { POINT ptFrom; POINT ptTo; _pfv->GetSpacing(&ptFrom); _pfv->GetDefaultSpacing(&ptTo); if (ptFrom.x != ptTo.x || ptFrom.y != ptTo.y) _ScalePoints(apts, ptFrom, ptTo); } void CItemPositioning::DropPositionItems(void) { if (S_OK == _pfv->GetAutoArrange()) { _DropPositionItemsWithPoints(DPIWP_AUTOARRANGE); } else if (S_OK == _pfv->GetSpacing(NULL) && _ppt) { _DropPositionItemsWithPoints(DPIWP_DATAOBJ); } else { _DropPositionItems(NULL); } } void CItemPositioning::_DropPositionItemsWithPoints(DPIWP dpiwp) { STGMEDIUM medium; POINT* apts = _DropGetPoints(dpiwp, &medium); if (apts) { if (DPIWP_DATAOBJ == dpiwp) { _DropScalePoints(_SkipAnchorPoint(apts)); _DropPositionPoints(_SkipAnchorPoint(apts)); } _DropPositionItems(_SkipAnchorPoint(apts)); _DropFreePoints(dpiwp, apts, &medium); } else if (_ppt) { POINT *ppts = (POINT *)LocalAlloc(LPTR, _cidl * sizeof(POINT)); if (ppts) { POINT pt; _pfv->GetDefaultSpacing(&pt); for (UINT i = 0; i < _cidl; i++) { ppts[i].x = (-g_cxIcon / 2) + (i * pt.x); ppts[i].y = (-g_cyIcon / 2) + (i * pt.y); } _DropScalePoints(ppts); _DropPositionPoints(ppts); _DropPositionItems(ppts); LocalFree(ppts); } else { _DropPositionItems(NULL); } } else { _DropPositionItems(NULL); } } void CItemPositioning::_DropPositionItems(POINT* apts) { // Drop the first item with special selection flags. LPCITEMIDLIST pidl = ILFindLastID(_apidl[0]); _pfv->SelectAndPositionItems(1, &pidl, apts, SVSI_SELECT | SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED); // Drop the rest of the items. if (_cidl > 1) { LPCITEMIDLIST* apidl = (LPCITEMIDLIST*)LocalAlloc(GPTR, sizeof(LPCITEMIDLIST) * (_cidl - 1)); if (apidl) { for (UINT i = 1; i < _cidl; i++) apidl[i - 1] = ILFindLastID(_apidl[i]); _pfv->SelectAndPositionItems(_cidl - 1, apidl, (apts) ? &apts[1] : NULL, SVSI_SELECT); LocalFree(apidl); } } } POINT* CItemPositioning::_DropGetPoints(DPIWP dpiwp, STGMEDIUM* pmedium) { POINT* pptRet = NULL; if (DPIWP_DATAOBJ == dpiwp) { FORMATETC fmte = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (SUCCEEDED(_pdtobj->GetData(&fmte, pmedium))) { if (pmedium->hGlobal) { POINT *pptSrc; pptSrc = (POINT *)GlobalLock(pmedium->hGlobal); if (pptSrc) { pptRet = (POINT*)LocalAlloc(GPTR, (_cidl + 1) * sizeof(POINT)); if (pptRet) { for (UINT i = 0; i <= _cidl; i++) { pptRet[i] = pptSrc[i]; } } GlobalUnlock(pptSrc); } } ReleaseStgMedium(pmedium); } } else if (DPIWP_AUTOARRANGE == dpiwp) { if (_ppt) { pptRet = (POINT*)LocalAlloc(GPTR, (_cidl + 1) * sizeof(POINT)); if (pptRet) { // skip first point to simulate data object use of first point for (UINT i = 1; i <= _cidl; i++) { pptRet[i] = *_ppt; } } } } return pptRet; } void CItemPositioning::_DropFreePoints(DPIWP dpiwp, POINT* apts, STGMEDIUM* pmedium) { LocalFree(apts); } void CItemPositioning::_DropScalePoints(POINT* apts) { POINT ptFrom; POINT ptTo; _pfv->GetDefaultSpacing(&ptFrom); _pfv->GetSpacing(&ptTo); if (ptFrom.x != ptTo.x || ptFrom.y != ptTo.y) _ScalePoints(apts, ptFrom, ptTo); } void CItemPositioning::_DropPositionPoints(POINT* apts) { for (UINT i = 0; i < _cidl; i++) { apts[i].x += _ppt->x; apts[i].y += _ppt->y; } } void CItemPositioning::_ScalePoints(POINT* apts, POINT ptFrom, POINT ptTo) { for (UINT i = 0; i < _cidl; i++) { apts[i].x = MulDiv(apts[i].x, ptTo.x, ptFrom.x); apts[i].y = MulDiv(apts[i].y, ptTo.y, ptFrom.y); } } POINT* CItemPositioning::_SkipAnchorPoint(POINT* apts) { return &apts[1]; } STDAPI_(void) SetPositionItemsPoints(IFolderView* pifv, LPCITEMIDLIST* apidl, UINT cidl, IDataObject* pdtobj, POINT* ptDrag) { CItemPositioning cpi(pifv, apidl, cidl, pdtobj, ptDrag); cpi.DragSetPoints(); } STDAPI_(void) PositionItems(IFolderView* pifv, LPCITEMIDLIST* apidl, UINT cidl, IDataObject* pdtobj, POINT* ptDrop) { CItemPositioning cip(pifv, apidl, cidl, pdtobj, ptDrop); cip.DropPositionItems(); } // // Don't use PositionItems_DontUse. Instead convert to PositionItems. // PositionItems_DontUse will be removed. // // Bug#163533 (edwardp 8/15/00) Remove this code. STDAPI_(void) PositionItems_DontUse(HWND hwndOwner, UINT cidl, const LPITEMIDLIST *ppidl, IDataObject *pdtobj, POINT *pptOrigin, BOOL fMove, BOOL fUseExactOrigin) { if (!ppidl || !IsWindow(hwndOwner)) return; SFM_SAP *psap = (SFM_SAP *)GlobalAlloc(GPTR, sizeof(SFM_SAP) * cidl); if (psap) { UINT i, cxItem, cyItem; int xMul, yMul, xDiv, yDiv; STGMEDIUM medium; POINT *pptItems = NULL; POINT pt; ITEMSPACING is; // select those objects; // this had better not fail HWND hwnd = ShellFolderViewWindow(hwndOwner); if (fMove) { FORMATETC fmte = {g_cfOFFSETS, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; if (SUCCEEDED(pdtobj->GetData(&fmte, &medium)) && medium.hGlobal) { pptItems = (POINT *)GlobalLock(medium.hGlobal); pptItems++; // The first point is the anchor } else { // By default, drop at (-g_cxIcon/2, -g_cyIcon/2), and increase // x and y by icon dimension for each icon pt.x = ((-3 * g_cxIcon) / 2) + pptOrigin->x; pt.y = ((-3 * g_cyIcon) / 2) + pptOrigin->y; medium.hGlobal = NULL; } if (ShellFolderView_GetItemSpacing(hwndOwner, &is)) { xDiv = is.cxLarge; yDiv = is.cyLarge; xMul = is.cxSmall; yMul = is.cySmall; cxItem = is.cxSmall; cyItem = is.cySmall; } else { xDiv = yDiv = xMul = yMul = 1; cxItem = g_cxIcon; cyItem = g_cyIcon; } } for (i = 0; i < cidl; i++) { if (ppidl[i]) { psap[i].pidl = ILFindLastID(ppidl[i]); psap[i].fMove = fMove; if (fMove) { if (fUseExactOrigin) { psap[i].pt = *pptOrigin; } else { if (pptItems) { psap[i].pt.x = ((pptItems[i].x * xMul) / xDiv) + pptOrigin->x; psap[i].pt.y = ((pptItems[i].y * yMul) / yDiv) + pptOrigin->y; } else { pt.x += cxItem; pt.y += cyItem; psap[i].pt = pt; } } } // do regular selection from all of the rest of the items psap[i].uSelectFlags = SVSI_SELECT; } } // do this special one for the first only psap[0].uSelectFlags = SVSI_SELECT | SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED; SendMessage(hwnd, SVM_SELECTANDPOSITIONITEM, cidl, (LPARAM)psap); if (fMove && medium.hGlobal) ReleaseStgMediumHGLOBAL(NULL, &medium); GlobalFree(psap); } }