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
This commit is contained in:
Vadim Zeitlin
2012-10-13 22:53:46 +00:00
parent 0ec1064b79
commit 403750325d
4 changed files with 384 additions and 40 deletions

View 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)

View 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);
};

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

View 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
// ----------------------------------------------------------------------------