Windows2000/private/shell/comctl32/ddproxy.cpp
2020-09-30 17:12:32 +02:00

379 lines
8.9 KiB
C++

#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;
}