442 lines
9.5 KiB
C++
442 lines
9.5 KiB
C++
#include "ctlspriv.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
STDAPI GetItemObject(CCONTROLINFO *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;
|
|
|
|
//
|
|
// and process this like a DragOver
|
|
//
|
|
DragOver(fKeys, pt, pdwEffect);
|
|
|
|
//
|
|
// always succeed DragEnter
|
|
//
|
|
return NOERROR;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
//
|
|
// all done
|
|
//
|
|
return NOERROR;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// and update our selection state accordingly
|
|
//
|
|
UpdateSelection(*pdwEffect);
|
|
|
|
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;
|
|
|
|
//
|
|
// do the drop
|
|
//
|
|
hres = pdtCur->Drop(pdo, fKeys, pt, pdwEffect);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//
|
|
// now clean up
|
|
//
|
|
DragLeave();
|
|
|
|
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
|
|
//
|
|
|
|
|
|
//
|
|
// make sure nothing is selected
|
|
//
|
|
UpdateSelection(DROPEFFECT_NONE);
|
|
|
|
//
|
|
// leave/release the old item
|
|
//
|
|
if (_pdtgtItem)
|
|
{
|
|
_pdtgtItem->DragLeave();
|
|
_pdtgtItem->Release();
|
|
}
|
|
|
|
//
|
|
// store the new item
|
|
//
|
|
_pdtgtItem = pdt;
|
|
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// update the selection
|
|
//
|
|
UpdateSelection(dwEffect);
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
if (SUCCEEDED(CoInitialize(NULL)))
|
|
{
|
|
if (SUCCEEDED(RegisterDragDrop(_hwnd, this)))
|
|
return TRUE;
|
|
|
|
CoUninitialize();
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void CDragProxy::RevokeAndFreeCB()
|
|
{
|
|
RevokeDragDrop(_hwnd);
|
|
CoUninitialize();
|
|
_pfnCallback = NULL;
|
|
}
|
|
|