[SHELL32]
authorAmine Khaldi <amine.khaldi@reactos.org>
Sun, 5 Jan 2014 12:48:42 +0000 (12:48 +0000)
committerAmine Khaldi <amine.khaldi@reactos.org>
Sun, 5 Jan 2014 12:48:42 +0000 (12:48 +0000)
* Move shell file operations to background threads to prevent the shell from hanging during long copies, deletes, and moves.
* Improve drag and drop functionality.
* Add a partial drop handler to the recycle bin.
* Brought to you by Huw Campbell.
CORE-3760

svn path=/trunk/; revision=61537

reactos/dll/win32/shell32/defcontextmenu.cpp
reactos/dll/win32/shell32/folders/fs.cpp
reactos/dll/win32/shell32/folders/fs.h
reactos/dll/win32/shell32/folders/recyclebin.cpp
reactos/dll/win32/shell32/folders/recyclebin.h
reactos/dll/win32/shell32/shlview.cpp

index e7dadab..72988d1 100644 (file)
@@ -1037,7 +1037,6 @@ CDefaultContextMenu::DoPaste(
     }
 
     SHSimulateDrop(pdrop, pda, dwKey, NULL, NULL);
-    NotifyShellViewWindow(lpcmi, TRUE);
 
     TRACE("CP result %x\n", hr);
     return S_OK;
@@ -1104,73 +1103,24 @@ CDefaultContextMenu::DoCreateLink(
         ERR("no IDropTarget Interface\n");
         return hr;
     }
-    //DWORD link = DROPEFFECT_LINK;
     SHSimulateDrop(pDT, pDataObj, MK_CONTROL|MK_SHIFT, NULL, NULL);
-    NotifyShellViewWindow(lpcmi, TRUE);
 
     return S_OK;
 }
 
-HRESULT
-CDefaultContextMenu::DoDelete(
-    LPCMINVOKECOMMANDINFO lpcmi)
-{
-    STRRET strTemp;
-    HRESULT hr = m_Dcm.psf->GetDisplayNameOf(m_Dcm.apidl[0], SHGDN_FORPARSING, &strTemp);
-    if(hr != S_OK)
-    {
-        ERR("IShellFolder_GetDisplayNameOf failed with %x\n", hr);
-        return hr;
-    }
-
-    WCHAR wszPath[MAX_PATH];
-    hr = StrRetToBufW(&strTemp, m_Dcm.apidl[0], wszPath, _countof(wszPath));
-    if (hr != S_OK)
-    {
-        ERR("StrRetToBufW failed with %x\n", hr);
-        return hr;
-    }
-
-    /* Only keep the base path */
-    LPWSTR pwszFilename = PathFindFileNameW(wszPath);
-    *pwszFilename = L'\0';
-
-    /* Build paths list */
-    LPWSTR pwszPaths = BuildPathsList(wszPath, m_Dcm.cidl, m_Dcm.apidl);
-    if (!pwszPaths)
-        return E_FAIL;
-
-    /* Delete them */
-    SHFILEOPSTRUCTW FileOp;
-    ZeroMemory(&FileOp, sizeof(FileOp));
-    FileOp.hwnd = GetActiveWindow();
-    FileOp.wFunc = FO_DELETE;
-    FileOp.pFrom = pwszPaths;
-    FileOp.fFlags = FOF_ALLOWUNDO;
+HRESULT CDefaultContextMenu::DoDelete(LPCMINVOKECOMMANDINFO lpcmi) {
+    TRACE("(%p) Deleting\n", this);
 
-    if (SHFileOperationW(&FileOp) != 0)
-    {
-        ERR("SHFileOperation failed with 0x%x for %s\n", GetLastError(), debugstr_w(pwszPaths));
-        return S_OK;
-    }
+    LPDATAOBJECT pDataObj;
 
-    /* Get the active IShellView */
-    LPSHELLBROWSER lpSB = (LPSHELLBROWSER)SendMessageW(lpcmi->hwnd, CWM_GETISHELLBROWSER, 0, 0);
-    if (lpSB)
+    if (SUCCEEDED(SHCreateDataObject(m_Dcm.pidlFolder, m_Dcm.cidl, m_Dcm.apidl, NULL, IID_PPV_ARG(IDataObject, &pDataObj))))
     {
-        /* Is the treeview focused */
-        HWND hwnd;
-        if (SUCCEEDED(lpSB->GetControlWindow(FCW_TREE, &hwnd)))
-        {
-            /* Remove selected items from treeview */
-            HTREEITEM hItem = TreeView_GetSelection(hwnd);
-            if (hItem)
-                (void)TreeView_DeleteItem(hwnd, hItem);
-        }
-    }
-    NotifyShellViewWindow(lpcmi, TRUE);
-
-    HeapFree(GetProcessHeap(), 0, pwszPaths);
+        pDataObj->AddRef();
+        SHCreateThread(DoDeleteThreadProc, pDataObj, NULL, NULL);
+        pDataObj->Release();
+    } 
+    else 
+        return E_FAIL;
     return S_OK;
 
 }
index 6aee02f..52ffbeb 100644 (file)
@@ -1419,6 +1419,27 @@ HRESULT WINAPI CFSFolder::Drop(IDataObject *pDataObject,
 {
     TRACE("(%p) object dropped, effect %u\n", this, *pdwEffect);
 
+    _DoDropData *data = reinterpret_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).
+    data->This->AddRef();
+    data->pDataObject = pDataObject;
+    // Also keep the data object in case it gets freed elsewhere.
+    data->pDataObject->AddRef();
+    data->dwKeyState = dwKeyState;
+    data->pt = pt;
+    // Need to dereference as pdweffect is freed.
+    data->pdwEffect = *pdwEffect;
+
+    SHCreateThread(reinterpret_cast<LPTHREAD_START_ROUTINE> (CFSFolder::_DoDropThreadProc), reinterpret_cast<void *> (data), NULL, NULL);
+    return S_OK;
+}
+
+HRESULT WINAPI CFSFolder::_DoDrop(IDataObject *pDataObject,
+                               DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
+{
+    TRACE("(%p) performing drop, effect %u\n", this, *pdwEffect);
+
     HRESULT hr;
     bool bCopy = TRUE;
     bool bLinking = FALSE;
@@ -1451,9 +1472,9 @@ HRESULT WINAPI CFSFolder::Drop(IDataObject *pDataObject,
     if (pdwEffect)
     {
         TRACE("Current drop effect flag %i\n", *pdwEffect);
-        if (*pdwEffect & DROPEFFECT_MOVE)
+        if ((*pdwEffect & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
             bCopy = FALSE;
-        if (*pdwEffect & DROPEFFECT_LINK)
+        if ((*pdwEffect & DROPEFFECT_LINK) == DROPEFFECT_LINK)
             bLinking = TRUE;
     }
 
@@ -1631,5 +1652,12 @@ HRESULT WINAPI CFSFolder::Drop(IDataObject *pDataObject,
 }
 
 DWORD CFSFolder::_DoDropThreadProc(LPVOID lpParameter) {
+    _DoDropData *data = reinterpret_cast<_DoDropData*>(lpParameter);
+    data->This->_DoDrop(data->pDataObject, data->dwKeyState, data->pt, &data->pdwEffect);
+    //Release the CFSFolder and data object holds in the copying thread.
+    data->pDataObject->Release();
+    data->This->Release();
+    //Release the parameter from the heap.
+    HeapFree(GetProcessHeap(), 0, data);
     return 0;
 }
\ No newline at end of file
index 191fafa..a613a1f 100644 (file)
@@ -46,7 +46,8 @@ class CFSFolder :
         BOOL QueryDrop (DWORD dwKeyState, LPDWORD pdwEffect);
         void SF_RegisterClipFmt();
         BOOL GetUniqueFileName(LPWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut);
-        DWORD _DoDropThreadProc(LPVOID lpParameter);
+        static DWORD _DoDropThreadProc(LPVOID lpParameter);
+        virtual HRESULT WINAPI _DoDrop(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect);
 
     public:
         CFSFolder();
@@ -116,4 +117,12 @@ class CFSFolder :
         END_COM_MAP()
 };
 
+struct _DoDropData {
+    CFSFolder *This;
+    IDataObject *pDataObject;
+    DWORD dwKeyState;
+    POINTL pt; 
+    DWORD pdwEffect;
+};
+
 #endif // _CFSFOLDER_H_
index 570e12d..50753d2 100644 (file)
@@ -429,10 +429,24 @@ static HRESULT WINAPI CRecycleBinItemContextMenuConstructor(REFIID riid, LPCITEM
     return S_OK;
 }
 
+/**************************************************************************
+* registers clipboardformat once
+*/
+void CRecycleBin::SF_RegisterClipFmt()
+{
+    TRACE ("(%p)\n", this);
+
+    if (!cfShellIDList)
+        cfShellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST);
+}
+
 CRecycleBin::CRecycleBin()
 {
     pidl = NULL;
     iIdEmpty = 0;
+    cfShellIDList = 0;
+    SF_RegisterClipFmt();
+    fAcceptFmt = FALSE;
 }
 
 CRecycleBin::~CRecycleBin()
@@ -556,8 +570,7 @@ HRESULT WINAPI CRecycleBin::CreateViewObject(HWND hwndOwner, REFIID riid, void *
 
     if (IsEqualIID (riid, IID_IDropTarget))
     {
-        WARN ("IDropTarget not implemented\n");
-        hr = E_NOTIMPL;
+        hr = this->QueryInterface (IID_IDropTarget, ppv);
     }
     else if (IsEqualIID (riid, IID_IContextMenu) || IsEqualIID (riid, IID_IContextMenu2))
     {
@@ -605,7 +618,7 @@ HRESULT WINAPI CRecycleBin::GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLI
     {
         hr = CRecycleBinItemContextMenuConstructor(riid, apidl[0], (void **)&pObj);
     }
-    else if (IsEqualIID (riid, IID_IDropTarget) && (cidl >= 1))
+    else if (IsEqualIID (riid, IID_IDropTarget) && (cidl == 1))
     {
         hr = this->QueryInterface(IID_IDropTarget, (LPVOID *) & pObj);
     }
@@ -1372,3 +1385,214 @@ EXTERN_C HRESULT WINAPI SHUpdateRecycleBinIcon(void)
 
     return S_OK;
 }
+
+/****************************************************************************
+ * IDropTarget implementation
+ */
+BOOL CRecycleBin::QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
+{
+    /* TODO on shift we should delete, we should update the cursor manager to show this. */
+
+    DWORD dwEffect = DROPEFFECT_COPY;
+
+    *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 WINAPI CRecycleBin::DragEnter(IDataObject *pDataObject,
+                                    DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
+{
+    FIXME("Recycle bin drag over (%p)\n", this);
+    /* The recycle bin accepts pretty much everything, and sets a CSIDL flag. */
+    fAcceptFmt = TRUE;
+
+    QueryDrop(dwKeyState, pdwEffect);
+    return S_OK;
+}
+
+HRESULT WINAPI CRecycleBin::DragOver(DWORD dwKeyState, POINTL pt,
+                                   DWORD *pdwEffect)
+{
+    TRACE("(%p)\n", this);
+
+    if (!pdwEffect)
+        return E_INVALIDARG;
+
+    QueryDrop(dwKeyState, pdwEffect);
+
+    return S_OK;
+}
+
+HRESULT WINAPI CRecycleBin::DragLeave()
+{
+    TRACE("(%p)\n", this);
+
+    fAcceptFmt = FALSE;
+
+    return S_OK;
+}
+
+HRESULT WINAPI CRecycleBin::Drop(IDataObject *pDataObject,
+                               DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
+{
+    FIXME("(%p) object dropped on recycle bin, effect %u\n", this, *pdwEffect);
+    
+    /* TODO: pdwEffect should be read and make the drop object be permanently deleted in the move case (shift held) */
+
+    FORMATETC fmt;
+    TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
+    InitFormatEtc (fmt, cfShellIDList, TYMED_HGLOBAL);
+
+    /* Handle cfShellIDList Drop objects here, otherwise send the approriate message to other software */
+    if (SUCCEEDED(pDataObject->QueryGetData(&fmt))) {
+        pDataObject->AddRef();
+        SHCreateThread(DoDeleteThreadProc, pDataObject, NULL, NULL);
+    }
+    else
+    {
+        /* 
+         * TODO call SetData on the data object with format CFSTR_TARGETCLSID
+         * set to the Recycle Bin's class identifier CLSID_RecycleBin.
+         */
+    }
+    return S_OK;
+}
+
+DWORD WINAPI DoDeleteThreadProc(LPVOID lpParameter) 
+{
+    IDataObject *pda = (IDataObject*) lpParameter;
+    DoDeleteDataObject(pda);
+    //Release the data object
+    pda->Release();
+    return 0;
+}
+
+HRESULT WINAPI DoDeleteDataObject(IDataObject *pda) 
+{
+    TRACE("performing delete");
+    HRESULT hr;
+
+    STGMEDIUM medium;
+    FORMATETC formatetc;
+    InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_SHELLIDLIST), TYMED_HGLOBAL);
+    hr = pda->GetData(&formatetc, &medium);
+    if (FAILED(hr))
+        return hr;
+
+    /* 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<IShellFolder> psfDesktop;
+    CComPtr<IShellFolder> 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))
+    {
+        psfFrom = psfDesktop;
+    }
+    else 
+    {
+        hr = psfDesktop->BindToObject(pidl, NULL, IID_IShellFolder, (LPVOID*)&psfFrom);
+        if (FAILED(hr))
+        {
+            ERR("no IShellFolder\n");
+            SHFree(pidl);
+            _ILFreeaPidl(apidl, lpcida->cidl);
+            ReleaseStgMedium(&medium);
+            return E_FAIL;
+        }
+    }
+
+    STRRET strTemp;
+    hr = psfFrom->GetDisplayNameOf(apidl[0], SHGDN_FORPARSING, &strTemp);
+    if (FAILED(hr))
+    {
+        ERR("IShellFolder_GetDisplayNameOf failed with %x\n", hr);
+        SHFree(pidl);
+        _ILFreeaPidl(apidl, lpcida->cidl);
+        ReleaseStgMedium(&medium);
+        return hr;
+    }
+
+    WCHAR wszPath[MAX_PATH];
+    hr = StrRetToBufW(&strTemp, apidl[0], wszPath, _countof(wszPath));
+    if (FAILED(hr))
+    {
+        ERR("StrRetToBufW failed with %x\n", hr);
+        SHFree(pidl);
+        _ILFreeaPidl(apidl, lpcida->cidl);
+        ReleaseStgMedium(&medium);
+        return hr;
+    }
+
+    /* Only keep the base path */
+    LPWSTR pwszFilename = PathFindFileNameW(wszPath);
+    *pwszFilename = L'\0';
+
+    /* Build paths list */
+    LPWSTR pwszPaths = BuildPathsList(wszPath, lpcida->cidl, (LPCITEMIDLIST*) apidl);
+    if (!pwszPaths)
+    {
+        SHFree(pidl);
+        _ILFreeaPidl(apidl, lpcida->cidl);
+        ReleaseStgMedium(&medium);
+        return E_FAIL;
+    }
+
+    /* Delete them */
+    SHFILEOPSTRUCTW FileOp;
+    ZeroMemory(&FileOp, sizeof(FileOp));
+    FileOp.wFunc = FO_DELETE;
+    FileOp.pFrom = pwszPaths;
+    FileOp.fFlags = FOF_ALLOWUNDO;
+
+    if (SHFileOperationW(&FileOp) != 0)
+    {
+        ERR("SHFileOperation failed with 0x%x for %s\n", GetLastError(), debugstr_w(pwszPaths));
+        hr = E_FAIL;
+    }
+
+    HeapFree(GetProcessHeap(), 0, pwszPaths);
+    SHFree(pidl);
+    _ILFreeaPidl(apidl, lpcida->cidl);
+    ReleaseStgMedium(&medium);
+
+    return hr;
+}
\ No newline at end of file
index 31a5901..709c3ec 100644 (file)
@@ -22,6 +22,9 @@
 #ifndef _SHFLDR_RECYCLEBIN_H_
 #define _SHFLDR_RECYCLEBIN_H_
 
+DWORD WINAPI DoDeleteThreadProc(LPVOID lpParameter);
+HRESULT WINAPI DoDeleteDataObject(IDataObject *pda);
+
 class CRecycleBin :
     public CComCoClass<CRecycleBin, &CLSID_RecycleBin>,
     public CComObjectRootEx<CComMultiThreadModelNoCS>,
@@ -29,11 +32,16 @@ class CRecycleBin :
     public IPersistFolder2,
     public IContextMenu,
     public IShellPropSheetExt,
+    public IDropTarget,
     public IShellExtInit
 {
     private:
         LPITEMIDLIST pidl;
         INT iIdEmpty;
+        UINT cfShellIDList;
+        void SF_RegisterClipFmt();
+        BOOL fAcceptFmt;       /* flag for pending Drop */
+        BOOL QueryDrop (DWORD dwKeyState, LPDWORD pdwEffect);
 
     public:
         CRecycleBin();
@@ -75,6 +83,12 @@ class CRecycleBin :
         // IShellPropSheetExt
         virtual HRESULT WINAPI AddPages(LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam);
         virtual HRESULT WINAPI ReplacePage(EXPPS uPageID, LPFNSVADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam);
+        
+        // IDropTarget
+        virtual HRESULT WINAPI DragEnter(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect);
+        virtual HRESULT WINAPI DragOver(DWORD dwKeyState, POINTL pt, DWORD *pdwEffect);
+        virtual HRESULT WINAPI DragLeave();
+        virtual HRESULT WINAPI Drop(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect);
 
         // IShellExtInit
         virtual HRESULT STDMETHODCALLTYPE Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
@@ -91,6 +105,7 @@ class CRecycleBin :
         COM_INTERFACE_ENTRY_IID(IID_IShellFolder2, IShellFolder2)
         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
         COM_INTERFACE_ENTRY_IID(IID_IShellPropSheetExt, IShellPropSheetExt)
+        COM_INTERFACE_ENTRY_IID(IID_IDropTarget, IDropTarget)
         COM_INTERFACE_ENTRY_IID(IID_IShellExtInit, IShellExtInit)
         END_COM_MAP()
 };
index 0f051ed..0c2a0c4 100644 (file)
@@ -1691,8 +1691,6 @@ LRESULT CDefView::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandl
                     {
                         DWORD dwEffect2;
                         DoDragDrop(pda, pds, dwEffect, &dwEffect2);
-                        if ((dwEffect2 & DROPEFFECT_MOVE) == DROPEFFECT_MOVE)
-                            this->Refresh();
                     }
                     pda->Release();
                 }