#include "ctlspriv.h" #include "olestuff.h" STDAPI GetItemObject(CONTROLINFO *pci, UINT uMsg, const IID *piid, LPNMOBJECTNOTIFY pnon) { pnon->piid = piid; pnon->pObject = NULL; pnon->hResult = E_NOINTERFACE; CCSendNotify(pci, uMsg, &pnon->hdr); ASSERT(SUCCEEDED(pnon->hResult) ? (pnon->pObject != NULL) : (pnon->pObject == NULL)); return pnon->hResult; } class CDragProxy : public IDropTarget { public: // IUnknown STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IDropTarget STDMETHODIMP DragEnter(IDataObject *, DWORD, POINTL, DWORD *); STDMETHODIMP DragOver(DWORD, POINTL, DWORD *); STDMETHODIMP DragLeave(); STDMETHODIMP Drop(IDataObject *, DWORD, POINTL, DWORD *); CDragProxy(HWND hwnd, PFNDRAGCB pfn); BOOL Register(); void RevokeAndFreeCB(); private: ~CDragProxy(); int _cRef; // object reference count HWND _hwnd; // window that owns us PFNDRAGCB _pfnCallback; // callback for that window IDataObject *_pdtobj; // data object being dragged IDropTarget *_pdtgtItem; // drop target of item under mouse int _idItem; // id of item under mouse DWORD _dwFlags; int _idDefault; // id to use when outside a drag etc DWORD _dwEffectItem; // DROPEFFECT returned for item under mouse DWORD _fKeysLast; // key flags from last DragOver POINTL _ptLast; // location of last DragOver DWORD _dwEffectLast; // effect available from last DragOver HMODULE _hmodOLE; // OLE32 ref, also indicates we did a Register() void SetTargetItem(int id, DWORD dwFlags); void SetDropTarget(IDropTarget *pdt); void UpdateSelection(DWORD dwEffect); LRESULT CallCB(UINT code, WPARAM wp, LPARAM lp); }; STDAPI_(HDRAGPROXY) CreateDragProxy(HWND hwnd, PFNDRAGCB pfn, BOOL bRegister) { CDragProxy *pdp = new CDragProxy(hwnd, pfn); // register as needed if (pdp && bRegister && !pdp->Register()) { pdp->Release(); pdp = NULL; } return (HDRAGPROXY)pdp; } STDAPI_(void) DestroyDragProxy(HDRAGPROXY hdp) { if (hdp) { ((CDragProxy *)hdp)->RevokeAndFreeCB(); ((CDragProxy *)hdp)->Release(); } } STDAPI GetDragProxyTarget(HDRAGPROXY hdp, IDropTarget **ppdtgt) { if (hdp) { *ppdtgt = SAFECAST((CDragProxy *)hdp, IDropTarget *); ((CDragProxy *)hdp)->AddRef(); return NOERROR; } *ppdtgt = NULL; return E_FAIL; } CDragProxy::CDragProxy(HWND hwnd, PFNDRAGCB pfn) : _hwnd(hwnd), _pfnCallback(pfn), _cRef(1), _hmodOLE(NULL), _pdtobj(NULL), _pdtgtItem(NULL), _dwEffectItem(DROPEFFECT_NONE) { _idDefault = _idItem = (int)CallCB(DPX_DRAGHIT, 0, 0); } CDragProxy::~CDragProxy() { DragLeave(); } HRESULT CDragProxy::QueryInterface(REFIID iid, void **ppv) { if (IsEqualIID(iid, IID_IDropTarget) || IsEqualIID(iid, IID_IUnknown)) { *ppv = SAFECAST(this, IDropTarget *); } else { *ppv = NULL; return E_NOINTERFACE; } _cRef++; return NOERROR; } ULONG CDragProxy::AddRef() { return ++_cRef; } ULONG CDragProxy::Release() { if (--_cRef) return _cRef; delete this; return 0; } HRESULT CDragProxy::DragEnter(IDataObject *pdo, DWORD fKeys, POINTL pt, DWORD *pdwEffect) { // some sanity ASSERT(!_pdtgtItem); ASSERT(!_pdtobj); if (!pdo) { ASSERT(FALSE); return E_INVALIDARG; } // make sure our callback will allow us to do d/d now if (!CallCB(DPX_ENTER, 0, 0)) return E_FAIL; // save away the data object pdo->AddRef(); _pdtobj = pdo; DragOver(fKeys, pt, pdwEffect);// and process this like a DragOver return NOERROR;// always succeed DragEnter } HRESULT CDragProxy::DragLeave() { // release any drop target that we are holding SetDropTarget(NULL); _idItem = _idDefault; // if we had a data object then we were actually dragging if (_pdtobj) { CallCB(DPX_LEAVE, 0, 0); IDataObject* p = _pdtobj; _pdtobj = NULL; p->Release(); } return NOERROR;// all done } HRESULT CDragProxy::DragOver(DWORD fKeys, POINTL pt, DWORD *pdwEffect) { DWORD dwFlags = 0; HRESULT hres; int id; ASSERT(_pdtobj); // save the current drag state _fKeysLast = fKeys; _ptLast = pt; _dwEffectLast = *pdwEffect; // make sure we have the correct drop target for this location id = (int)CallCB(DPX_DRAGHIT, (WPARAM)&dwFlags, (LPARAM)&pt); SetTargetItem(id, dwFlags); // do we have a target to drop on? if (_pdtgtItem) { // forward the DragOver along to the item's drop target (if any) hres = _pdtgtItem->DragOver(fKeys, pt, pdwEffect); } else { // can't drop here *pdwEffect = DROPEFFECT_NONE; hres = NOERROR; } UpdateSelection(*pdwEffect);// and update our selection state accordingly return hres; } HRESULT CDragProxy::Drop(IDataObject *pdo, DWORD fKeys, POINTL pt, DWORD *pdwEffect) { HRESULT hres; AddRef(); // do we have a target to drop on? if (_pdtgtItem) { // From a comment in browseui, there's apparently a chance to put up UI // which could cause us to get re-entered. Hard to believe, but see if this fixes the fault: IDropTarget * pdtCur = _pdtgtItem; _pdtgtItem = NULL; hres = pdtCur->Drop(pdo, fKeys, pt, pdwEffect); // do the drop // we call our DragLeave below but we don't want the item's to be called (since it already saw the Drop) so we release right away pdtCur->Release(); } else { // can't drop here *pdwEffect = DROPEFFECT_NONE; hres = NOERROR; } DragLeave();// now clean up Release(); return hres; } void CDragProxy::SetTargetItem(int id, DWORD dwFlags) { // anything to do? if (id == _idItem && dwFlags == _dwFlags) return; // deselect the old item (if any) // the GETOBJECT below could take a long time and we don't want a lingering highlight on the object we are leaving UpdateSelection(DROPEFFECT_NONE); // get a drop target for the new item _idItem = id; _dwFlags = dwFlags; NMOBJECTNOTIFY non; non.iItem = id; non.dwFlags = dwFlags; if (!_pdtobj || FAILED((HRESULT)CallCB(DPX_GETOBJECT, 0, (LPARAM)&non))) non.pObject = NULL; // use this drop target (if any) SetDropTarget((IDropTarget*)non.pObject); // release our ref from the GETOBJECT above if (non.pObject) ((IDropTarget*)non.pObject)->Release(); } void CDragProxy::SetDropTarget(IDropTarget *pdt) { // NOTE: we intentionally skip the test for drop-target equality here // this allows controls owners to share a target among multiple items while retaining the proper leave/enter sequence... // BOGUS: we should actually compare here when the Internet Toolbar gets fixed (see comment in CDragProxy::SetTargetItem). // anybody who wants to share a target like this should just do the right hit-testing in their DragOver implementation UpdateSelection(DROPEFFECT_NONE);// make sure nothing is selected // leave/release the old item if (_pdtgtItem) { _pdtgtItem->DragLeave(); _pdtgtItem->Release(); } _pdtgtItem = pdt;// store the new item // addref/enter the new item if (_pdtgtItem) { ASSERT(_pdtobj); // must have a data object by now _pdtgtItem->AddRef(); DWORD dwEffect = _dwEffectLast; if (FAILED(_pdtgtItem->DragEnter(_pdtobj, _fKeysLast, _ptLast, &dwEffect))) dwEffect = DROPEFFECT_NONE; UpdateSelection(dwEffect);// update the selection } } void CDragProxy::UpdateSelection(DWORD dwEffect) { // anything to do? if (dwEffect == _dwEffectItem) return; // update the flags and tell the callback they changed _dwEffectItem = dwEffect; CallCB(DPX_SELECT, (WPARAM)_idItem, (LPARAM)dwEffect); } LRESULT CDragProxy::CallCB(UINT code, WPARAM wp, LPARAM lp) { return _pfnCallback ? _pfnCallback(_hwnd, code, wp, lp) : (LRESULT)-1; } BOOL CDragProxy::Register() { _hmodOLE = PrivLoadOleLibrary(); if (_hmodOLE) { if (SUCCEEDED(PrivCoInitialize(_hmodOLE))) { if (SUCCEEDED(PrivRegisterDragDrop(_hmodOLE, _hwnd, this))) return TRUE; PrivCoUninitialize(_hmodOLE); } PrivFreeOleLibrary(_hmodOLE); _hmodOLE = NULL; } return FALSE; } void CDragProxy::RevokeAndFreeCB() { if (_hmodOLE) { PrivRevokeDragDrop(_hmodOLE, _hwnd); PrivCoUninitialize(_hmodOLE); PrivFreeOleLibrary(_hmodOLE); } _pfnCallback = NULL; }