From 403750325d372829ea8162462cc1cd8a92368ac4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 13 Oct 2012 22:53:46 +0000 Subject: [PATCH] Display system-provided drag images during drag-and-drop in wxMSW. 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 | 1 + include/wx/msw/ole/droptgt.h | 19 ++- src/msw/ole/dataobj.cpp | 267 ++++++++++++++++++++++++++++++++--- src/msw/ole/droptgt.cpp | 137 +++++++++++++++--- 4 files changed, 384 insertions(+), 40 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 0e1cb3a0ca..de8db9d89c 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -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) diff --git a/include/wx/msw/ole/droptgt.h b/include/wx/msw/ole/droptgt.h index 659bf39713..d58b1cc426 100644 --- a/include/wx/msw/ole/droptgt.h +++ b/include/wx/msw/ole/droptgt.h @@ -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); }; diff --git a/src/msw/ole/dataobj.cpp b/src/msw/ole/dataobj.cpp index ad02bc6be3..2f683cb5e8 100644 --- a/src/msw/ole/dataobj.cpp +++ b/src/msw/ole/dataobj.cpp @@ -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 #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 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 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; } diff --git a/src/msw/ole/droptgt.cpp b/src/msw/ole/droptgt.cpp index e63e55038e..1c68f26e4a 100644 --- a/src/msw/ole/droptgt.cpp +++ b/src/msw/ole/droptgt.cpp @@ -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 // ---------------------------------------------------------------------------- -- 2.45.2