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