/* * file system folder drop target * * Copyright 1997 Marcus Meissner * Copyright 1998, 1999, 2002 Juergen Schmied * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include WINE_DEFAULT_DEBUG_CHANNEL (shell); /**************************************************************************** * BuildPathsList * * Builds a list of paths like the one used in SHFileOperation from a table of * PIDLs relative to the given base folder */ static WCHAR* BuildPathsList(LPCWSTR wszBasePath, int cidl, LPCITEMIDLIST *pidls) { WCHAR *pwszPathsList = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) * cidl + 1); WCHAR *pwszListPos = pwszPathsList; for (int i = 0; i < cidl; i++) { FileStructW* pDataW = _ILGetFileStructW(pidls[i]); if (!pDataW) { ERR("Got garbage pidl\n"); continue; } PathCombineW(pwszListPos, wszBasePath, pDataW->wszName); pwszListPos += wcslen(pwszListPos) + 1; } *pwszListPos = 0; return pwszPathsList; } /**************************************************************************** * CFSDropTarget::CopyItems * * copies items to this folder */ HRESULT WINAPI CFSDropTarget::CopyItems(IShellFolder * pSFFrom, UINT cidl, LPCITEMIDLIST * apidl, BOOL bCopy) { LPWSTR pszSrcList; HRESULT hr; WCHAR wszTargetPath[MAX_PATH + 1]; wcscpy(wszTargetPath, sPathTarget); //Double NULL terminate. wszTargetPath[wcslen(wszTargetPath) + 1] = '\0'; TRACE ("(%p)->(%p,%u,%p)\n", this, pSFFrom, cidl, apidl); STRRET strretFrom; hr = pSFFrom->GetDisplayNameOf(NULL, SHGDN_FORPARSING, &strretFrom); if (FAILED_UNEXPECTEDLY(hr)) return hr; pszSrcList = BuildPathsList(strretFrom.pOleStr, cidl, apidl); ERR("Source file (just the first) = %s, target path = %s, bCopy: %d\n", debugstr_w(pszSrcList), debugstr_w(sPathTarget), bCopy); CoTaskMemFree(strretFrom.pOleStr); if (!pszSrcList) return E_OUTOFMEMORY; SHFILEOPSTRUCTW op = {0}; op.pFrom = pszSrcList; op.pTo = wszTargetPath; op.hwnd = m_hwndSite; op.wFunc = bCopy ? FO_COPY : FO_MOVE; op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR; int res = SHFileOperationW(&op); HeapFree(GetProcessHeap(), 0, pszSrcList); if (res) return E_FAIL; else return S_OK; } CFSDropTarget::CFSDropTarget(): cfShellIDList(0), fAcceptFmt(FALSE), sPathTarget(NULL), m_hwndSite(NULL), m_grfKeyState(0) { } HRESULT WINAPI CFSDropTarget::Initialize(LPWSTR PathTarget) { if (!PathTarget) return E_UNEXPECTED; cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST); if (!cfShellIDList) return E_FAIL; sPathTarget = (WCHAR *)SHAlloc((wcslen(PathTarget) + 1) * sizeof(WCHAR)); if (!sPathTarget) return E_OUTOFMEMORY; wcscpy(sPathTarget, PathTarget); return S_OK; } CFSDropTarget::~CFSDropTarget() { SHFree(sPathTarget); } BOOL CFSDropTarget::GetUniqueFileName(LPWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut) { WCHAR wszLink[40]; if (!bShortcut) { if (!LoadStringW(shell32_hInstance, IDS_LNK_FILE, wszLink, _countof(wszLink))) wszLink[0] = L'\0'; } if (!bShortcut) swprintf(pwszTarget, L"%s%s%s", wszLink, pwszBasePath, pwszExt); else swprintf(pwszTarget, L"%s%s", pwszBasePath, pwszExt); for (UINT i = 2; PathFileExistsW(pwszTarget); ++i) { if (!bShortcut) swprintf(pwszTarget, L"%s%s (%u)%s", wszLink, pwszBasePath, i, pwszExt); else swprintf(pwszTarget, L"%s (%u)%s", pwszBasePath, i, pwszExt); } return TRUE; } /**************************************************************************** * IDropTarget implementation */ BOOL CFSDropTarget::QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect) { /* TODO Windows does different drop effects if dragging across drives. i.e., it will copy instead of move if the directories are on different disks. */ DWORD dwEffect = m_dwDefaultEffect; *pdwEffect = DROPEFFECT_NONE; if (fAcceptFmt) { /* Does our interpretation of the keystate ... */ *pdwEffect = KeyStateToDropEffect (dwKeyState); if (*pdwEffect == DROPEFFECT_NONE) *pdwEffect = dwEffect; /* ... matches the desired effect ? */ if (dwEffect & *pdwEffect) { return TRUE; } } return FALSE; } HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, DWORD *pdwEffect, DWORD dwAvailableEffects) { HMENU hmenu = LoadMenuW(shell32_hInstance, MAKEINTRESOURCEW(IDM_DRAGFILE)); if (!hmenu) return E_OUTOFMEMORY; HMENU hpopupmenu = GetSubMenu(hmenu, 0); if ((dwAvailableEffects & DROPEFFECT_COPY) == 0) DeleteMenu(hpopupmenu, IDM_COPYHERE, MF_BYCOMMAND); else if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0) DeleteMenu(hpopupmenu, IDM_MOVEHERE, MF_BYCOMMAND); else if ((dwAvailableEffects & DROPEFFECT_LINK) == 0) DeleteMenu(hpopupmenu, IDM_LINKHERE, MF_BYCOMMAND); if ((*pdwEffect & DROPEFFECT_COPY)) SetMenuDefaultItem(hpopupmenu, IDM_COPYHERE, FALSE); else if ((*pdwEffect & DROPEFFECT_MOVE)) SetMenuDefaultItem(hpopupmenu, IDM_MOVEHERE, FALSE); else if ((*pdwEffect & DROPEFFECT_LINK)) SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE); /* FIXME: We need to support shell extensions here */ UINT uCommand = TrackPopupMenu(hpopupmenu, TPM_LEFTALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY, pt.x, pt.y, 0, m_hwndSite, NULL); if (uCommand == 0) return S_FALSE; else if (uCommand == IDM_COPYHERE) *pdwEffect = DROPEFFECT_COPY; else if (uCommand == IDM_MOVEHERE) *pdwEffect = DROPEFFECT_MOVE; else if (uCommand == IDM_LINKHERE) *pdwEffect = DROPEFFECT_LINK; return S_OK; } HRESULT CFSDropTarget::_RepositionItems(IShellFolderView *psfv, IDataObject *pdtobj, POINTL pt) { CComPtr pfv; POINT ptDrag; HRESULT hr = psfv->QueryInterface(IID_PPV_ARG(IFolderView, &pfv)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = psfv->GetDragPoint(&ptDrag); if (FAILED_UNEXPECTEDLY(hr)) return hr; PIDLIST_ABSOLUTE pidlFolder; PUITEMID_CHILD *apidl; UINT cidl; hr = SH_GetApidlFromDataObject(pdtobj, &pidlFolder, &apidl, &cidl); if (FAILED_UNEXPECTEDLY(hr)) return hr; CComHeapPtr apt; if (!apt.Allocate(cidl)) { SHFree(pidlFolder); _ILFreeaPidl(apidl, cidl); return E_OUTOFMEMORY; } for (UINT i = 0; iGetItemPosition(apidl[i], &apt[i]); apt[i].x += pt.x - ptDrag.x; apt[i].y += pt.y - ptDrag.y; } pfv->SelectAndPositionItems(cidl, apidl, apt, SVSI_SELECT); SHFree(pidlFolder); _ILFreeaPidl(apidl, cidl); return S_OK; } HRESULT WINAPI CFSDropTarget::DragEnter(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) { TRACE("(%p)->(DataObject=%p)\n", this, pDataObject); FORMATETC fmt; FORMATETC fmt2; fAcceptFmt = FALSE; InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL); InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL); if (SUCCEEDED(pDataObject->QueryGetData(&fmt))) fAcceptFmt = TRUE; else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2))) fAcceptFmt = TRUE; m_grfKeyState = dwKeyState; m_dwDefaultEffect = DROPEFFECT_MOVE; STGMEDIUM medium; if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium))) { WCHAR wstrFirstFile[MAX_PATH]; if (DragQueryFileW((HDROP)medium.hGlobal, 0, wstrFirstFile, _countof(wstrFirstFile))) { /* Check if the drive letter is different */ if (wstrFirstFile[0] != sPathTarget[0]) { m_dwDefaultEffect = DROPEFFECT_COPY; } } ReleaseStgMedium(&medium); } QueryDrop(dwKeyState, pdwEffect); return S_OK; } HRESULT WINAPI CFSDropTarget::DragOver(DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) { TRACE("(%p)\n", this); if (!pdwEffect) return E_INVALIDARG; m_grfKeyState = dwKeyState; QueryDrop(dwKeyState, pdwEffect); return S_OK; } HRESULT WINAPI CFSDropTarget::DragLeave() { TRACE("(%p)\n", this); fAcceptFmt = FALSE; return S_OK; } HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) { TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect); if (!pdwEffect) return E_INVALIDARG; IUnknown_GetWindow(m_site, &m_hwndSite); DWORD dwAvailableEffects = *pdwEffect; QueryDrop(dwKeyState, pdwEffect); TRACE("pdwEffect: 0x%x, m_dwDefaultEffect: 0x%x, dwAvailableEffects: 0x%x\n", *pdwEffect, m_dwDefaultEffect, dwAvailableEffects); if (m_grfKeyState & MK_RBUTTON) { HRESULT hr = _GetEffectFromMenu(pDataObject, pt, pdwEffect, dwAvailableEffects); if (FAILED_UNEXPECTEDLY(hr) || hr == S_FALSE) return hr; } if (*pdwEffect == DROPEFFECT_MOVE && m_site) { CComPtr psfv; HRESULT hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellFolderView, &psfv)); if (SUCCEEDED(hr) && psfv->IsDropOnSource(this) == S_OK) { _RepositionItems(psfv, pDataObject, pt); return S_OK; } } BOOL fIsOpAsync = FALSE; CComPtr pAsyncOperation; if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation)))) { if (SUCCEEDED(pAsyncOperation->GetAsyncMode(&fIsOpAsync)) && fIsOpAsync) { _DoDropData *data = static_cast<_DoDropData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_DoDropData))); data->This = this; // Need to maintain this class in case the window is closed or the class exists temporarily (when dropping onto a folder). pDataObject->AddRef(); pAsyncOperation->StartOperation(NULL); CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDataObject, &data->pStream); this->AddRef(); data->dwKeyState = dwKeyState; data->pt = pt; // Need to dereference as pdweffect gets freed. data->pdwEffect = *pdwEffect; SHCreateThread(CFSDropTarget::_DoDropThreadProc, data, NULL, NULL); return S_OK; } } return this->_DoDrop(pDataObject, dwKeyState, pt, pdwEffect); } HRESULT WINAPI CFSDropTarget::SetSite(IUnknown *pUnkSite) { m_site = pUnkSite; return S_OK; } HRESULT WINAPI CFSDropTarget::GetSite(REFIID riid, void **ppvSite) { if (!m_site) return E_FAIL; return m_site->QueryInterface(riid, ppvSite); } HRESULT WINAPI CFSDropTarget::_DoDrop(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) { TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect); FORMATETC fmt; FORMATETC fmt2; STGMEDIUM medium; InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL); InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL); HRESULT hr; bool bCopy = TRUE; bool bLinking = FALSE; /* Figure out what drop operation we're doing */ if (pdwEffect) { TRACE("Current drop effect flag %i\n", *pdwEffect); if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE) bCopy = FALSE; if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK) bLinking = TRUE; } if (SUCCEEDED(pDataObject->QueryGetData(&fmt))) { hr = pDataObject->GetData(&fmt, &medium); TRACE("CFSTR_SHELLIDLIST.\n"); /* lock the handle */ LPIDA lpcida = (LPIDA)GlobalLock(medium.hGlobal); if (!lpcida) { ReleaseStgMedium(&medium); return E_FAIL; } /* convert the data into pidl */ LPITEMIDLIST pidl; LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, lpcida); if (!apidl) { ReleaseStgMedium(&medium); return E_FAIL; } CComPtr psfDesktop; CComPtr psfFrom = NULL; /* Grab the desktop shell folder */ hr = SHGetDesktopFolder(&psfDesktop); if (FAILED(hr)) { ERR("SHGetDesktopFolder failed\n"); SHFree(pidl); _ILFreeaPidl(apidl, lpcida->cidl); ReleaseStgMedium(&medium); return E_FAIL; } /* Find source folder, this is where the clipboard data was copied from */ if (_ILIsDesktop(pidl)) { /* use desktop shell folder */ psfFrom = psfDesktop; } else { hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &psfFrom)); if (FAILED(hr)) { ERR("no IShellFolder\n"); SHFree(pidl); _ILFreeaPidl(apidl, lpcida->cidl); ReleaseStgMedium(&medium); return E_FAIL; } } if (bLinking) { WCHAR wszTargetPath[MAX_PATH]; WCHAR wszPath[MAX_PATH]; WCHAR wszTarget[MAX_PATH]; wcscpy(wszTargetPath, sPathTarget); TRACE("target path = %s", debugstr_w(wszTargetPath)); /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */ for (UINT i = 0; i < lpcida->cidl; i++) { //Find out which file we're copying STRRET strFile; hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile); if (FAILED(hr)) { ERR("Error source obtaining path"); break; } hr = StrRetToBufW(&strFile, apidl[i], wszPath, _countof(wszPath)); if (FAILED(hr)) { ERR("Error putting source path into buffer"); break; } TRACE("source path = %s", debugstr_w(wszPath)); // Creating a buffer to hold the combined path WCHAR buffer_1[MAX_PATH] = L""; WCHAR *lpStr1; lpStr1 = buffer_1; LPWSTR pwszFileName = PathFindFileNameW(wszPath); LPWSTR pwszExt = PathFindExtensionW(wszPath); LPWSTR placementPath = PathCombineW(lpStr1, sPathTarget, pwszFileName); CComPtr ppf; //Check to see if it's already a link. if (!wcsicmp(pwszExt, L".lnk")) { //It's a link so, we create a new one which copies the old. if(!GetUniqueFileName(placementPath, pwszExt, wszTarget, TRUE)) { ERR("Error getting unique file name"); hr = E_FAIL; break; } hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf)); if (FAILED(hr)) { ERR("Error constructing link from file"); break; } hr = ppf->Save(wszTarget, FALSE); if (FAILED(hr)) break; SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL); } else { //It's not a link, so build a new link using the creator class and fill it in. //Create a file name for the link if (!GetUniqueFileName(placementPath, L".lnk", wszTarget, TRUE)) { ERR("Error creating unique file name"); hr = E_FAIL; break; } CComPtr pLink; hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink)); if (FAILED(hr)) { ERR("Error instantiating IShellLinkW"); break; } WCHAR szDirPath[MAX_PATH], *pwszFile; GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile); if (pwszFile) pwszFile[0] = 0; hr = pLink->SetPath(wszPath); if(FAILED(hr)) break; hr = pLink->SetWorkingDirectory(szDirPath); if(FAILED(hr)) break; hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if(FAILED(hr)) break; hr = ppf->Save(wszTarget, TRUE); if (FAILED(hr)) break; SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL); } } } else { hr = this->CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy); } SHFree(pidl); _ILFreeaPidl(apidl, lpcida->cidl); ReleaseStgMedium(&medium); } else if (SUCCEEDED(pDataObject->QueryGetData(&fmt2))) { FORMATETC fmt2; InitFormatEtc (fmt2, CF_HDROP, TYMED_HGLOBAL); if (SUCCEEDED(pDataObject->GetData(&fmt2, &medium)) /* && SUCCEEDED(pDataObject->GetData(&fmt2, &medium))*/) { WCHAR wszTargetPath[MAX_PATH + 1]; LPWSTR pszSrcList; wcscpy(wszTargetPath, sPathTarget); //Double NULL terminate. wszTargetPath[wcslen(wszTargetPath) + 1] = '\0'; LPDROPFILES lpdf = (LPDROPFILES) GlobalLock(medium.hGlobal); if (!lpdf) { ERR("Error locking global\n"); return E_FAIL; } pszSrcList = (LPWSTR) (((byte*) lpdf) + lpdf->pFiles); ERR("Source file (just the first) = %s, target path = %s, bCopy: %d\n", debugstr_w(pszSrcList), debugstr_w(wszTargetPath), bCopy); SHFILEOPSTRUCTW op; ZeroMemory(&op, sizeof(op)); op.pFrom = pszSrcList; op.pTo = wszTargetPath; op.hwnd = m_hwndSite; op.wFunc = bCopy ? FO_COPY : FO_MOVE; op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR; hr = SHFileOperationW(&op); return hr; } ERR("Error calling GetData\n"); hr = E_FAIL; } else { ERR("No viable drop format.\n"); hr = E_FAIL; } return hr; } DWORD WINAPI CFSDropTarget::_DoDropThreadProc(LPVOID lpParameter) { CoInitialize(NULL); _DoDropData *data = static_cast<_DoDropData*>(lpParameter); CComPtr pDataObject; HRESULT hr = CoGetInterfaceAndReleaseStream (data->pStream, IID_PPV_ARG(IDataObject, &pDataObject)); if (SUCCEEDED(hr)) { CComPtr pAsyncOperation; hr = data->This->_DoDrop(pDataObject, data->dwKeyState, data->pt, &data->pdwEffect); if (SUCCEEDED(pDataObject->QueryInterface(IID_PPV_ARG(IAsyncOperation, &pAsyncOperation)))) { pAsyncOperation->EndOperation(hr, NULL, data->pdwEffect); } } //Release the CFSFolder and data object holds in the copying thread. data->This->Release(); //Release the parameter from the heap. HeapFree(GetProcessHeap(), 0, data); CoUninitialize(); return 0; } HRESULT CFSDropTarget_CreateInstance(LPWSTR sPathTarget, REFIID riid, LPVOID * ppvOut) { return ShellObjectCreatorInit(sPathTarget, riid, ppvOut); }