]> git.saurik.com Git - wxWidgets.git/commitdiff
Display system-provided drag images during drag-and-drop in wxMSW.
authorVadim Zeitlin <vadim@wxwidgets.org>
Sat, 13 Oct 2012 22:53:46 +0000 (22:53 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sat, 13 Oct 2012 22:53:46 +0000 (22:53 +0000)
This is especially useful when dragging files from Explorer as it provides
big, informative drag images for them that can be easily displayed using
Windows shell support for them.

See #14697.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72668 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/changes.txt
include/wx/msw/ole/droptgt.h
src/msw/ole/dataobj.cpp
src/msw/ole/droptgt.cpp

index 0e1cb3a0ca6f64f2c17fc2b5ab1e29c18b078247..de8db9d89c9f48846376d4ca077e0ce5a36a3efe 100644 (file)
@@ -585,6 +585,7 @@ wxMSW:
 - Better support for SAFEARRAY in OLE Automation code (PB).
 - Fix calling Iconize(false) on hidden top level windows (Christian Walther).
 - Don't send any events from wxSpinCtrl::SetRange() even if the value changed.
+- Display system drag images during drag and drop if available (PeterO).
 
 
 2.9.4: (released 2012-07-09)
index 659bf397132ac7c2b6ffc9811fc50f6dfaecd355..d58b1cc42601ca81d974f318d11787d273e9acc5 100644 (file)
@@ -19,6 +19,7 @@
 // ----------------------------------------------------------------------------
 
 class  wxIDropTarget;
+struct IDropTargetHelper;
 struct IDataObject;
 
 // ----------------------------------------------------------------------------
@@ -60,12 +61,26 @@ public:
     // GetData() when it's called from inside OnData()
     void MSWSetDataSource(IDataObject *pIDataSource);
 
+    // These functions take care of all things necessary to support native drag
+    // images.
+    //
+    // {Init,End}DragImageSupport() are called during Register/Revoke,
+    // UpdateDragImageOnXXX() functions are called on the corresponding drop
+    // target events.
+    void MSWInitDragImageSupport();
+    void MSWEndDragImageSupport();
+    void MSWUpdateDragImageOnData(wxCoord x, wxCoord y, wxDragResult res);
+    void MSWUpdateDragImageOnDragOver(wxCoord x, wxCoord y, wxDragResult res);
+    void MSWUpdateDragImageOnEnter(wxCoord x, wxCoord y, wxDragResult res);
+    void MSWUpdateDragImageOnLeave();
+
 private:
     // helper used by IsAcceptedData() and GetData()
     wxDataFormat MSWGetSupportedFormat(IDataObject *pIDataSource) const;
 
-    wxIDropTarget *m_pIDropTarget; // the pointer to our COM interface
-    IDataObject   *m_pIDataSource; // the pointer to the source data object
+    wxIDropTarget     *m_pIDropTarget; // the pointer to our COM interface
+    IDataObject       *m_pIDataSource; // the pointer to the source data object
+    IDropTargetHelper *m_dropTargetHelper; // the pointer to the drop target helper
 
     wxDECLARE_NO_COPY_CLASS(wxDropTarget);
 };
index ad02bc6be353dd972682f684af490816862d64da..2f683cb5e84b7ec7c01a9a71addbf9ad794f9416 100644 (file)
@@ -28,6 +28,7 @@
     #include "wx/intl.h"
     #include "wx/log.h"
     #include "wx/utils.h"
+    #include "wx/vector.h"
     #include "wx/wxcrtvararg.h"
 #endif
 
@@ -35,6 +36,7 @@
 
 #if wxUSE_OLE && defined(__WIN32__) && !defined(__GNUWIN32_OLD__)
 
+#include "wx/scopedarray.h"
 #include "wx/msw/private.h"         // includes <windows.h>
 
 #ifdef __WXWINCE__
@@ -67,6 +69,9 @@
     #define GetTymedName(tymed) wxEmptyString
 #endif // wxDEBUG_LEVEL/!wxDEBUG_LEVEL
 
+namespace
+{
+
 wxDataFormat HtmlFormatFixup(wxDataFormat format)
 {
     // Since the HTML format is dynamically registered, the wxDF_HTML
@@ -82,6 +87,78 @@ wxDataFormat HtmlFormatFixup(wxDataFormat format)
     return format;
 }
 
+// helper function for wxCopyStgMedium()
+HGLOBAL wxGlobalClone(HGLOBAL hglobIn)
+{
+    HGLOBAL hglobOut = NULL;
+
+    LPVOID pvIn = GlobalLock(hglobIn);
+    if (pvIn)
+    {
+        SIZE_T cb = GlobalSize(hglobIn);
+        hglobOut = GlobalAlloc(GMEM_FIXED, cb);
+        if (hglobOut)
+        {
+            CopyMemory(hglobOut, pvIn, cb);
+        }
+        GlobalUnlock(hglobIn);
+    }
+
+    return hglobOut;
+}
+
+// Copies the given STGMEDIUM structure.
+//
+// This is an local implementation of the function with the same name in
+// urlmon.lib but to use that function would require linking with urlmon.lib
+// and we don't want to require it, so simple reimplement it here.
+HRESULT wxCopyStgMedium(const STGMEDIUM *pmediumIn, STGMEDIUM *pmediumOut)
+{
+    HRESULT hres = S_OK;
+    STGMEDIUM stgmOut = *pmediumIn;
+
+    if (pmediumIn->pUnkForRelease == NULL &&
+        !(pmediumIn->tymed & (TYMED_ISTREAM | TYMED_ISTORAGE)))
+    {
+        // Object needs to be cloned.
+        if (pmediumIn->tymed == TYMED_HGLOBAL)
+        {
+            stgmOut.hGlobal = wxGlobalClone(pmediumIn->hGlobal);
+            if (!stgmOut.hGlobal)
+            {
+                hres = E_OUTOFMEMORY;
+            }
+        }
+        else
+        {
+            hres = DV_E_TYMED; // Don't know how to clone GDI objects.
+        }
+    }
+
+    if ( SUCCEEDED(hres) )
+    {
+        switch ( stgmOut.tymed )
+        {
+            case TYMED_ISTREAM:
+                stgmOut.pstm->AddRef();
+                break;
+
+            case TYMED_ISTORAGE:
+                stgmOut.pstg->AddRef();
+                break;
+        }
+
+        if ( stgmOut.pUnkForRelease )
+            stgmOut.pUnkForRelease->AddRef();
+
+        *pmediumOut = stgmOut;
+    }
+
+    return hres;
+}
+
+} // anonymous namespace
+
 // ----------------------------------------------------------------------------
 // wxIEnumFORMATETC interface implementation
 // ----------------------------------------------------------------------------
@@ -142,8 +219,122 @@ private:
     bool m_mustDelete;
 
     wxDECLARE_NO_COPY_CLASS(wxIDataObject);
+
+    // The following code is need to be able to store system data the operating
+    // system is using for it own purposes, e.g. drag images.
+
+    class SystemDataEntry
+    {
+    public:
+        // Ctor takes ownership of the pointers.
+        SystemDataEntry(FORMATETC *pformatetc, STGMEDIUM *pmedium)
+            : pformatetc(pformatetc), pmedium(pmedium)
+        {
+        }
+
+        ~SystemDataEntry()
+        {
+            delete pformatetc;
+            delete pmedium;
+        }
+
+        FORMATETC *pformatetc;
+        STGMEDIUM *pmedium;
+    };
+    typedef wxVector<SystemDataEntry*> SystemData;
+
+    // get system data specified by the given format
+    bool GetSystemData(wxDataFormat format, STGMEDIUM*) const;
+
+    // determines if the data object contains system data specified by the given format.
+    bool HasSystemData(wxDataFormat format) const;
+
+    // save system data
+    HRESULT SaveSystemData(FORMATETC*, STGMEDIUM*, BOOL fRelease);
+
+    // container for system data
+    SystemData m_systemData;
 };
 
+bool
+wxIDataObject::GetSystemData(wxDataFormat format, STGMEDIUM *pmedium) const
+{
+    for ( SystemData::const_iterator it = m_systemData.begin();
+          it != m_systemData.end();
+          ++it )
+    {
+        FORMATETC* formatEtc = (*it)->pformatetc;
+        if ( formatEtc->cfFormat == format )
+        {
+            wxCopyStgMedium((*it)->pmedium, pmedium);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+wxIDataObject::HasSystemData(wxDataFormat format) const
+{
+    for ( SystemData::const_iterator it = m_systemData.begin();
+          it != m_systemData.end();
+          ++it )
+    {
+        FORMATETC* formatEtc = (*it)->pformatetc;
+        if ( formatEtc->cfFormat == format )
+            return true;
+    }
+
+    return false;
+}
+
+// save system data
+HRESULT
+wxIDataObject::SaveSystemData(FORMATETC *pformatetc,
+                                 STGMEDIUM *pmedium,
+                                 BOOL fRelease)
+{
+    if ( pformatetc == NULL || pmedium == NULL )
+        return E_INVALIDARG;
+
+    // remove entry if already available
+    for ( SystemData::iterator it = m_systemData.begin();
+          it != m_systemData.end();
+          ++it )
+    {
+        if ( pformatetc->tymed & (*it)->pformatetc->tymed &&
+             pformatetc->dwAspect == (*it)->pformatetc->dwAspect &&
+             pformatetc->cfFormat == (*it)->pformatetc->cfFormat )
+        {
+            delete (*it);
+            m_systemData.erase(it);
+            break;
+        }
+    }
+
+    // create new format/medium
+    FORMATETC* pnewformatEtc = new FORMATETC;
+    STGMEDIUM* pnewmedium = new STGMEDIUM;
+
+    wxZeroMemory(*pnewformatEtc);
+    wxZeroMemory(*pnewmedium);
+
+    // copy format
+    *pnewformatEtc = *pformatetc;
+
+    // copy or take ownerschip of medium
+    if ( fRelease )
+        *pnewmedium = *pmedium;
+    else
+        wxCopyStgMedium(pmedium, pnewmedium);
+
+    // save entry
+    m_systemData.push_back(new SystemDataEntry(pnewformatEtc, pnewmedium));
+
+    return S_OK;
+}
+
 // ============================================================================
 // implementation
 // ============================================================================
@@ -289,6 +480,14 @@ wxIDataObject::wxIDataObject(wxDataObject *pDataObject)
 
 wxIDataObject::~wxIDataObject()
 {
+    // delete system data
+    for ( SystemData::iterator it = m_systemData.begin();
+          it != m_systemData.end();
+          ++it )
+    {
+        delete (*it);
+    }
+
     if ( m_mustDelete )
     {
         delete m_pDataObject;
@@ -310,6 +509,13 @@ STDMETHODIMP wxIDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
     wxDataFormat format = (wxDataFormat::NativeFormat)pformatetcIn->cfFormat;
     format = HtmlFormatFixup(format);
 
+    // is this system data?
+    if ( GetSystemData(format, pmedium) )
+    {
+        // pmedium is already filled with corresponding data, so we're ready.
+        return S_OK;
+    }
+
     switch ( format )
     {
         case wxDF_BITMAP:
@@ -445,6 +651,17 @@ STDMETHODIMP wxIDataObject::SetData(FORMATETC *pformatetc,
             m_pDataObject->SetData(wxDF_ENHMETAFILE, 0, &pmedium->hEnhMetaFile);
             break;
 
+        case TYMED_ISTREAM:
+            // check if this format is supported
+            if ( !m_pDataObject->IsSupported(pformatetc->cfFormat,
+                                             wxDataObject::Set) )
+            {
+                // As this is not a supported format (content data), assume it
+                // is system data and save it.
+                return SaveSystemData(pformatetc, pmedium, fRelease);
+            }
+            break;
+
         case TYMED_MFPICT:
             // fall through - we pass METAFILEPICT through HGLOBAL
         case TYMED_HGLOBAL:
@@ -453,15 +670,11 @@ STDMETHODIMP wxIDataObject::SetData(FORMATETC *pformatetc,
 
                 format = HtmlFormatFixup(format);
 
-                // this is quite weird, but for file drag and drop, explorer
-                // calls our SetData() with the formats we do *not* support!
-                //
-                // as we can't fix this bug in explorer (it's a bug because it
-                // should only use formats returned by EnumFormatEtc), do the
-                // check here
+                // check if this format is supported
                 if ( !m_pDataObject->IsSupported(format, wxDataObject::Set) ) {
-                    // go away!
-                    return DV_E_FORMATETC;
+                    // As above, assume that unsupported format must be system
+                    // data and just save it.
+                    return SaveSystemData(pformatetc, pmedium, fRelease);
                 }
 
                 // copy data
@@ -595,6 +808,13 @@ STDMETHODIMP wxIDataObject::QueryGetData(FORMATETC *pformatetc)
         wxLogTrace(wxTRACE_OleCalls, wxT("wxIDataObject::QueryGetData: %s ok"),
                    wxGetFormatName(format));
     }
+    else if ( HasSystemData(format) )
+    {
+        wxLogTrace(wxTRACE_OleCalls, wxT("wxIDataObject::QueryGetData: %s ok (system data)"),
+                   wxGetFormatName(format));
+        // this is system data, so no further checks needed.
+        return S_OK;
+    }
     else {
         wxLogTrace(wxTRACE_OleCalls,
                    wxT("wxIDataObject::QueryGetData: %s unsupported"),
@@ -640,20 +860,31 @@ STDMETHODIMP wxIDataObject::EnumFormatEtc(DWORD dwDir,
     wxDataObject::Direction dir = dwDir == DATADIR_GET ? wxDataObject::Get
                                                        : wxDataObject::Set;
 
-    ULONG nFormatCount = wx_truncate_cast(ULONG, m_pDataObject->GetFormatCount(dir));
-    wxDataFormat format;
-    wxDataFormat *formats;
-    formats = nFormatCount == 1 ? &format : new wxDataFormat[nFormatCount];
-    m_pDataObject->GetAllFormats(formats, dir);
+    // format count is total of user specified and system formats.
+    const size_t ourFormatCount = m_pDataObject->GetFormatCount(dir);
+    const size_t sysFormatCount = m_systemData.size();
 
-    wxIEnumFORMATETC *pEnum = new wxIEnumFORMATETC(formats, nFormatCount);
-    pEnum->AddRef();
-    *ppenumFormatEtc = pEnum;
+    const ULONG
+        nFormatCount = wx_truncate_cast(ULONG, ourFormatCount + sysFormatCount);
 
-    if ( formats != &format ) {
-        delete [] formats;
+    // fill format array with formats ...
+    wxScopedArray<wxDataFormat> formats(new wxDataFormat[nFormatCount]);
+
+    // ... from content data (supported formats)
+    m_pDataObject->GetAllFormats(formats.get(), dir);
+
+    // ... from system data
+    for ( size_t j = 0; j < sysFormatCount; j++ )
+    {
+        SystemDataEntry* entry = m_systemData[j];
+        wxDataFormat& format = formats[ourFormatCount + j];
+        format = entry->pformatetc->cfFormat;
     }
 
+    wxIEnumFORMATETC *pEnum = new wxIEnumFORMATETC(formats.get(), nFormatCount);
+    pEnum->AddRef();
+    *ppenumFormatEtc = pEnum;
+
     return S_OK;
 }
 
index e63e55038e7a75437365aab8aa765aec9d1cdce3..1c68f26e4a9e231c05753c5d7baf5e8ba84d50db 100644 (file)
@@ -63,6 +63,7 @@ public:
     virtual ~wxIDropTarget();
 
     // accessors for wxDropTarget
+    HWND GetHWND() const { return m_hwnd; }
     void SetHwnd(HWND hwnd) { m_hwnd = hwnd; }
 
     // IDropTarget methods
@@ -192,13 +193,6 @@ STDMETHODIMP wxIDropTarget::DragEnter(IDataObject *pIDataSource,
     }
 #endif // 0
 
-    if ( !m_pTarget->MSWIsAcceptedData(pIDataSource) ) {
-        // we don't accept this kind of data
-        *pdwEffect = DROPEFFECT_NONE;
-
-        return S_OK;
-    }
-
     // for use in OnEnter and OnDrag calls
     m_pTarget->MSWSetDataSource(pIDataSource);
 
@@ -206,22 +200,36 @@ STDMETHODIMP wxIDropTarget::DragEnter(IDataObject *pIDataSource,
     m_pIDataObject = pIDataSource;
     m_pIDataObject->AddRef();
 
-    // we need client coordinates to pass to wxWin functions
-    if ( !ScreenToClient(m_hwnd, (POINT *)&pt) )
+    if ( !m_pTarget->MSWIsAcceptedData(pIDataSource) ) {
+        // we don't accept this kind of data
+        *pdwEffect = DROPEFFECT_NONE;
+    }
+    else
     {
-        wxLogLastError(wxT("ScreenToClient"));
+        // we need client coordinates to pass to wxWin functions
+        if ( !ScreenToClient(m_hwnd, (POINT *)&pt) )
+        {
+            wxLogLastError(wxT("ScreenToClient"));
+        }
+
+        // give some visual feedback
+        *pdwEffect = ConvertDragResultToEffect(
+            m_pTarget->OnEnter(pt.x, pt.y, ConvertDragEffectToResult(
+                GetDropEffect(grfKeyState, m_pTarget->GetDefaultAction(), *pdwEffect))
+                        )
+                     );
     }
 
-    // give some visual feedback
-    *pdwEffect = ConvertDragResultToEffect(
-        m_pTarget->OnEnter(pt.x, pt.y, ConvertDragEffectToResult(
-            GetDropEffect(grfKeyState, m_pTarget->GetDefaultAction(), *pdwEffect))
-                    )
-                 );
+    // update drag image
+    const wxDragResult res = ConvertDragEffectToResult(*pdwEffect);
+    m_pTarget->MSWUpdateDragImageOnEnter(pt.x, pt.y, res);
+    m_pTarget->MSWUpdateDragImageOnDragOver(pt.x, pt.y, res);
 
     return S_OK;
 }
 
+
+
 // Name    : wxIDropTarget::DragOver
 // Purpose : Indicates that the mouse was moved inside the window represented
 //           by this drop target.
@@ -262,6 +270,10 @@ STDMETHODIMP wxIDropTarget::DragOver(DWORD   grfKeyState,
         *pdwEffect = DROPEFFECT_NONE;
     }
 
+    // update drag image
+    m_pTarget->MSWUpdateDragImageOnDragOver(pt.x, pt.y,
+                                            ConvertDragEffectToResult(*pdwEffect));
+
     return S_OK;
 }
 
@@ -279,6 +291,9 @@ STDMETHODIMP wxIDropTarget::DragLeave()
   // release the held object
   RELEASE_AND_NULL(m_pIDataObject);
 
+  // update drag image
+  m_pTarget->MSWUpdateDragImageOnLeave();
+
   return S_OK;
 }
 
@@ -333,6 +348,10 @@ STDMETHODIMP wxIDropTarget::Drop(IDataObject *pIDataSource,
     // release the held object
     RELEASE_AND_NULL(m_pIDataObject);
 
+    // update drag image
+    m_pTarget->MSWUpdateDragImageOnData(pt.x, pt.y,
+                                        ConvertDragEffectToResult(*pdwEffect));
+
     return S_OK;
 }
 
@@ -345,7 +364,8 @@ STDMETHODIMP wxIDropTarget::Drop(IDataObject *pIDataSource,
 // ----------------------------------------------------------------------------
 
 wxDropTarget::wxDropTarget(wxDataObject *dataObj)
-            : wxDropTargetBase(dataObj)
+            : wxDropTargetBase(dataObj),
+              m_dropTargetHelper(NULL)
 {
     // create an IDropTarget implementation which will notify us about d&d
     // operations.
@@ -396,6 +416,8 @@ bool wxDropTarget::Register(WXHWND hwnd)
     // we will need the window handle for coords transformation later
     m_pIDropTarget->SetHwnd((HWND)hwnd);
 
+    MSWInitDragImageSupport();
+
     return true;
 #endif
 }
@@ -417,6 +439,9 @@ void wxDropTarget::Revoke(WXHWND hwnd)
     ::CoLockObjectExternal(m_pIDropTarget, FALSE, TRUE);
 #endif
 
+    MSWEndDragImageSupport();
+
+    // remove window reference
     m_pIDropTarget->SetHwnd(0);
 #endif
 }
@@ -437,9 +462,6 @@ bool wxDropTarget::GetData()
 {
     wxDataFormat format = MSWGetSupportedFormat(m_pIDataSource);
     if ( format == wxDF_INVALID ) {
-        // this is strange because IsAcceptedData() succeeded previously!
-        wxFAIL_MSG(wxT("strange - did supported formats list change?"));
-
         return false;
     }
 
@@ -539,6 +561,81 @@ wxDataFormat wxDropTarget::MSWGetSupportedFormat(IDataObject *pIDataSource) cons
     return n < nFormats ? format : wxFormatInvalid;
 }
 
+// ----------------------------------------------------------------------------
+// drag image functions
+// ----------------------------------------------------------------------------
+
+void
+wxDropTarget::MSWEndDragImageSupport()
+{
+    // release drop target helper
+    if ( m_dropTargetHelper != NULL )
+    {
+        m_dropTargetHelper->Release();
+        m_dropTargetHelper = NULL;
+    }
+}
+
+void
+wxDropTarget::MSWInitDragImageSupport()
+{
+    // Use the default drop target helper to show shell drag images
+    CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,
+                     IID_IDropTargetHelper, (LPVOID*)&m_dropTargetHelper);
+}
+
+void
+wxDropTarget::MSWUpdateDragImageOnData(wxCoord x,
+                                       wxCoord y,
+                                       wxDragResult dragResult)
+{
+    // call corresponding event on drop target helper
+    if ( m_dropTargetHelper != NULL )
+    {
+        POINT pt = {x, y};
+        DWORD dwEffect = ConvertDragResultToEffect(dragResult);
+        m_dropTargetHelper->Drop(m_pIDataSource, &pt, dwEffect);
+    }
+}
+
+void
+wxDropTarget::MSWUpdateDragImageOnDragOver(wxCoord x,
+                                           wxCoord y,
+                                           wxDragResult dragResult)
+{
+    // call corresponding event on drop target helper
+    if ( m_dropTargetHelper != NULL )
+    {
+        POINT pt = {x, y};
+        DWORD dwEffect = ConvertDragResultToEffect(dragResult);
+        m_dropTargetHelper->DragOver(&pt, dwEffect);
+    }
+}
+
+void
+wxDropTarget::MSWUpdateDragImageOnEnter(wxCoord x,
+                                        wxCoord y,
+                                        wxDragResult dragResult)
+{
+    // call corresponding event on drop target helper
+    if ( m_dropTargetHelper != NULL )
+    {
+        POINT pt = {x, y};
+        DWORD dwEffect = ConvertDragResultToEffect(dragResult);
+        m_dropTargetHelper->DragEnter(m_pIDropTarget->GetHWND(), m_pIDataSource, &pt, dwEffect);
+    }
+}
+
+void
+wxDropTarget::MSWUpdateDragImageOnLeave()
+{
+    // call corresponding event on drop target helper
+    if ( m_dropTargetHelper != NULL )
+    {
+        m_dropTargetHelper->DragLeave();
+    }
+}
+
 // ----------------------------------------------------------------------------
 // private functions
 // ----------------------------------------------------------------------------