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); + const ULONG + nFormatCount = wx_truncate_cast(ULONG, ourFormatCount + sysFormatCount); + + // 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; - if ( formats != &format ) { - delete [] formats; - } - 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 // ----------------------------------------------------------------------------