Browse Source

COM implementation of Windows drag and drop

JoshEngebretson 10 years ago
parent
commit
254814e7f5
1 changed files with 294 additions and 60 deletions
  1. 294 60
      Source/Atomic/UI/UIDragDropWindows.cpp

+ 294 - 60
Source/Atomic/UI/UIDragDropWindows.cpp

@@ -21,42 +21,109 @@
 #include <windows.h>
 #include <windowsx.h>
 #include <shellapi.h>
-
-#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (SDL_wcslen(S)+1)*sizeof(WCHAR))
+#include <OleIdl.h>
 
 namespace Atomic
 {
-    class UIDragDropWindows : public Object
-    {
 
-        OBJECT(UIDragDropWindows);
+    // global weakptr to dragAndDrop system as it is sometimes accessed outside of an Object method
+    static WeakPtr<UIDragDrop> dragAndDrop_;
+
+    // Win32 proc hook so we can catch close and unregister drag and drop
+    static LRESULT CALLBACK Atomic_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
 
+    class CDropTarget : public IDropTarget
+    {
     public:
-        /// Construct.
-        UIDragDropWindows(Context* context);
-        virtual ~UIDragDropWindows();
+        // IUnknown implementation
+        HRESULT __stdcall QueryInterface(REFIID iid, void ** ppvObject);
+        ULONG	__stdcall AddRef(void);
+        ULONG	__stdcall Release(void);
 
-        void HandleUpdate(StringHash eventType, VariantMap& eventData);
+        void UpdateMousePos();
 
-        static WNDPROC sdlWndProc_;
+        // IDropTarget implementation
+        HRESULT __stdcall DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
+        HRESULT __stdcall DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
+        HRESULT __stdcall DragLeave(void);        
+        HRESULT __stdcall Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect);
 
-        HWND hwnd_;
+        void OpenFilesFromDataObject(IDataObject * pdto);
+
+        // Constructor
+        CDropTarget(HWND hwnd);
+        ~CDropTarget();
 
     private:
 
+        // internal helper function
+        DWORD DropEffect(DWORD grfKeyState, POINTL pt, DWORD dwAllowed);
+        bool  QueryDataObject(IDataObject *pDataObject);
+
+        // Private member variables
+        LONG	m_lRefCount;
+        HWND	m_hWnd;
+        bool    m_fAllowDrop;
+
+        IDataObject *m_pDataObject;
+
     };
 
-    WNDPROC UIDragDropWindows::sdlWndProc_ = NULL;
-    static WeakPtr<UIDragDrop> dragAndDrop_;
+    CDropTarget::CDropTarget(HWND hwnd)
+    {
+        m_lRefCount = 1;
+        m_hWnd = hwnd;
+        m_fAllowDrop = false;
+    }
+
+    CDropTarget::~CDropTarget()
+    {
+
+    }
+
+    HRESULT __stdcall CDropTarget::QueryInterface(REFIID iid, void ** ppvObject)
+    {
+        if (iid == IID_IDropTarget || iid == IID_IUnknown)
+        {
+            AddRef();
+            *ppvObject = this;
+            return S_OK;
+        }
+        else
+        {
+            *ppvObject = 0;
+            return E_NOINTERFACE;
+        }
+    }
+
+    ULONG __stdcall CDropTarget::AddRef(void)
+    {
+        return InterlockedIncrement(&m_lRefCount);
+    }
+
+    ULONG __stdcall CDropTarget::Release(void)
+    {
+        LONG count = InterlockedDecrement(&m_lRefCount);
+
+        if (count == 0)
+        {
+            delete this;
+            return 0;
+        }
+        else
+        {
+            return count;
+        }
+    }
 
-    void UpdateMousePos(HWND hwnd)
+    void CDropTarget::UpdateMousePos()
     {
-        if (!hwnd || dragAndDrop_.Null())
+        if (!m_hWnd || dragAndDrop_.Null())
             return;
 
         POINT p;
         GetCursorPos(&p);
-        ScreenToClient(hwnd, &p);
+        ScreenToClient(m_hWnd, &p);
         using namespace Atomic::MouseMove;
 
         Atomic::VariantMap mvEventData;
@@ -79,75 +146,223 @@ namespace Atomic
 
     }
 
-    LRESULT CALLBACK Atomic_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+
+    bool CDropTarget::QueryDataObject(IDataObject *pDataObject)
+    {
+        FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT,
+            -1, TYMED_HGLOBAL };
+
+
+        // does the data object support CF_HDROP using a HGLOBAL?
+        return pDataObject->QueryGetData(&fmte) == S_OK ? true : false;
+    }
+
+    DWORD CDropTarget::DropEffect(DWORD grfKeyState, POINTL pt, DWORD dwAllowed)
+    {
+        DWORD dwEffect = 0;
+
+        // 1. check "pt" -> do we allow a drop at the specified coordinates?
+
+        // 2. work out that the drop-effect should be based on grfKeyState
+        if (grfKeyState & MK_CONTROL)
+        {
+            dwEffect = dwAllowed & DROPEFFECT_COPY;
+        }
+        else if (grfKeyState & MK_SHIFT)
+        {
+            dwEffect = dwAllowed & DROPEFFECT_MOVE;
+        }
+
+        // 3. no key-modifiers were specified (or drop effect not allowed), so
+        //    base the effect on those allowed by the dropsource
+        if (dwEffect == 0)
+        {
+            if (dwAllowed & DROPEFFECT_COPY) dwEffect = DROPEFFECT_COPY;
+            if (dwAllowed & DROPEFFECT_MOVE) dwEffect = DROPEFFECT_MOVE;
+        }
+
+        return dwEffect;
+    }
+
+    HRESULT __stdcall CDropTarget::DragEnter(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect)
     {
+
         if (dragAndDrop_.Null())
+            return S_OK;
+
+        // does the dataobject contain data we want?
+        m_fAllowDrop = QueryDataObject(pDataObject);
+
+        if (m_fAllowDrop)
         {
-            return CallWindowProc(UIDragDropWindows::sdlWndProc_, hwnd, msg, wParam, lParam);
+            // get the dropeffect based on keyboard state
+            *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
+
+            SetFocus(m_hWnd);
+
+            dragAndDrop_->FileDragEntered();
+            UpdateMousePos();
+
+        }
+        else
+        {
+            *pdwEffect = DROPEFFECT_NONE;
         }
 
-        switch (msg) {
+        return S_OK;
+    }
+
+    HRESULT __stdcall CDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD * pdwEffect)
+    {
+        if (m_fAllowDrop)
+        {
+            *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
+            UpdateMousePos();
+        }
+        else
+        {
+            *pdwEffect = DROPEFFECT_NONE;
+        }
+
+        return S_OK;
+    }
+
+    HRESULT __stdcall CDropTarget::DragLeave(void)
+    {
+        return S_OK;
+    }
+
+    void CDropTarget::OpenFilesFromDataObject(IDataObject *pdto)
+    {
+        FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT,
+            -1, TYMED_HGLOBAL };
+
+        STGMEDIUM stgm;
 
-        case WM_DROPFILES:
+        if (SUCCEEDED(pdto->GetData(&fmte, &stgm)))
         {
+            PVOID data = GlobalLock(stgm.hGlobal);
 
-            //implement the IDropTarget interface and then register the HWND using RegisterDragDrop() to start receiving notifications. Be sure to call RevokeDragDrop() before exiting the app.
+            HDROP hdrop = reinterpret_cast<HDROP>(stgm.hGlobal);
 
-            UINT i;
-            HDROP drop = (HDROP)wParam;
-            UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
+            UINT cFiles = DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);
 
-            if (count)
+            for (UINT i = 0; i < cFiles; i++)
             {
-                dragAndDrop_->FileDragEntered();
-
-                UpdateMousePos(hwnd);
-
-                for (i = 0; i < count; ++i) {
-                    UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
-                    LPTSTR buffer = SDL_stack_alloc(TCHAR, size);
-                    if (buffer) {
-                        if (DragQueryFile(drop, i, buffer, size)) {
-
-                            char *file = WIN_StringToUTF8(buffer);
-                            dragAndDrop_->FileDragAddFile(file);
-                            SDL_free(file);
-                        }
-                        SDL_stack_free(buffer);
-                    }
+                TCHAR szFile[MAX_PATH];
+                UINT cch = DragQueryFile(hdrop, i, szFile, MAX_PATH);
+
+                if (cch > 0 && cch < MAX_PATH)
+                {
+                    dragAndDrop_->FileDragAddFile(szFile);
                 }
+            }
 
-                dragAndDrop_->FileDragConclude();
+            DragFinish(hdrop);
 
-            }
+            GlobalUnlock(stgm.hGlobal);
 
-            // sdlWndProc will handle this
-            //DragFinish(drop);
-            break;
+            ReleaseStgMedium(&stgm);
+
+            dragAndDrop_->FileDragConclude();
         }
+    }
 
-        };
+    HRESULT __stdcall CDropTarget::Drop(IDataObject * pDataObject, DWORD grfKeyState, POINTL pt, DWORD * pdwEffect)
+    {
+        if (dragAndDrop_.Null())
+            return S_OK;
 
-        return CallWindowProc(UIDragDropWindows::sdlWndProc_, hwnd, msg, wParam, lParam);
+        UpdateMousePos();
+
+        if (m_fAllowDrop)
+        {
+            OpenFilesFromDataObject(pDataObject);
+
+            *pdwEffect = DropEffect(grfKeyState, pt, *pdwEffect);
+        }
+        else
+        {
+            *pdwEffect = DROPEFFECT_NONE;
+        }
+
+        return S_OK;
+    }
+
+
+    static void RegisterDropWindow(HWND hwnd, IDropTarget **ppDropTarget)
+    {
+        OleInitialize(0);
+
+        CDropTarget *pDropTarget = new CDropTarget(hwnd);
+
+        // acquire a strong lock
+        CoLockObjectExternal(pDropTarget, TRUE, FALSE);
+
+        // tell OLE that the window is a drop target
+        HRESULT result = RegisterDragDrop(hwnd, pDropTarget);
+
+        *ppDropTarget = pDropTarget;
+    }
+
+    static void UnregisterDropWindow(HWND hwnd, IDropTarget *pDropTarget)
+    {
+        // remove drag+drop
+        HRESULT result = RevokeDragDrop(hwnd);
+
+        // remove the strong lock
+        CoLockObjectExternal(pDropTarget, FALSE, TRUE);
+
+        // release our own reference
+        pDropTarget->Release();
+
+        OleUninitialize();
     }
 
+    // UIDragDropWindows
+
+    class UIDragDropWindows : public Object
+    {
+        OBJECT(UIDragDropWindows);
+
+    public:
+        /// Construct.
+        UIDragDropWindows(Context* context);
+        virtual ~UIDragDropWindows();
+
+        void Shutdown();
+
+        static WNDPROC sdlWndProc_;
+
+    private:
+
+        IDropTarget* dropTarget_;
+        HWND hwnd_;
+
+    };
+
+    WNDPROC UIDragDropWindows::sdlWndProc_ = NULL;
+
     UIDragDropWindows::UIDragDropWindows(Context* context) : Object(context),
-        hwnd_(NULL)
+        dropTarget_(NULL), hwnd_(NULL)
     {
         SDL_Window* window = (SDL_Window*)GetSubsystem<Graphics>()->GetSDLWindow();
 
         SDL_SysWMinfo info;
         SDL_VERSION(&info.version);
 
-        if (SDL_GetWindowWMInfo(window, &info)) {
+        if (SDL_GetWindowWMInfo(window, &info))
+        {
 
             hwnd_ = info.info.win.window;
-            // hook the wnd proc so we can handle drag/drop ourselves
+
+            // hook the wnd proc so we can catch close and deregister drag and drop
             sdlWndProc_ = (WNDPROC)GetWindowLongPtr(hwnd_, GWLP_WNDPROC);
             SetWindowLongPtr(hwnd_, GWLP_WNDPROC, (LONG_PTR)Atomic_WindowProc);
-        }
 
-        SubscribeToEvent(E_UPDATE, HANDLER(UIDragDropWindows, HandleUpdate));
+            RegisterDropWindow(hwnd_, &dropTarget_);
+
+        }
 
     }
 
@@ -156,16 +371,15 @@ namespace Atomic
 
     }
 
-    void UIDragDropWindows::HandleUpdate(StringHash eventType, VariantMap & eventData)
+    void UIDragDropWindows::Shutdown()
     {
-
-        if (!hwnd_ || dragAndDrop_.Null())
+        if (!hwnd_ || !dropTarget_)
             return;
 
-        if (GetActiveWindow() == hwnd_)
-            return;
+        UnregisterDropWindow(hwnd_, dropTarget_);
 
-        UpdateMousePos(hwnd_);
+        // restore SDL winproc
+        SetWindowLongPtr(hwnd_, GWLP_WNDPROC, (LONG_PTR)sdlWndProc_);
 
     }
 
@@ -176,4 +390,24 @@ namespace Atomic
 
     }
 
-}
+    LRESULT CALLBACK Atomic_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+    {
+        if (dragAndDrop_.Null())
+        {
+            return CallWindowProc(UIDragDropWindows::sdlWndProc_, hwnd, msg, wParam, lParam);
+        }
+
+        switch (msg) {
+
+        case WM_CLOSE:
+        {
+            dragAndDrop_->GetSubsystem<UIDragDropWindows>()->Shutdown();
+            break;
+        }
+
+        };
+
+        return CallWindowProc(UIDragDropWindows::sdlWndProc_, hwnd, msg, wParam, lParam);
+    }
+
+}