You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1277 lines
37 KiB
1277 lines
37 KiB
//____________________________________________________________________________
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
|
//
|
|
// File: dd.cpp
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 8/3/1997 RaviR Created
|
|
//____________________________________________________________________________
|
|
//
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
|
|
#include "AMCDoc.h" // AMC Console Document
|
|
#include "amcview.h"
|
|
#include "TreeCtrl.h"
|
|
#include "cclvctl.h"
|
|
#include "amcpriv.h"
|
|
#include "mainfrm.h"
|
|
#include "rsltitem.h"
|
|
#include "conview.h"
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* CLASS: CMMCDropSource
|
|
*
|
|
* PURPOSE: Implements everything required for drop source:
|
|
* a) com object to register with OLE
|
|
* b) static method to perform drag&drop operation
|
|
*
|
|
* USAGE: All you need is to invoke CMMCDropSource::ScDoDragDrop static method
|
|
*
|
|
\***************************************************************************/
|
|
class CMMCDropSource :
|
|
public IDropSource,
|
|
public CComObjectRoot
|
|
{
|
|
public:
|
|
|
|
BEGIN_COM_MAP(CMMCDropSource)
|
|
COM_INTERFACE_ENTRY(IDropSource)
|
|
END_COM_MAP()
|
|
|
|
// IDropSource methods
|
|
STDMETHOD(QueryContinueDrag)( BOOL fEscapePressed, DWORD grfKeyState );
|
|
STDMETHOD(GiveFeedback)( DWORD dwEffect );
|
|
|
|
// method to perform D&D
|
|
static SC ScDoDragDrop(IDataObject *pDataObject, bool bCopyAllowed, bool bMoveAllowed);
|
|
};
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* CLASS: CMMCDropTarget
|
|
*
|
|
* PURPOSE: Implements com object to be osed by OLE for drop target operations
|
|
*
|
|
* USAGE: Used by CMMCViewDropTarget, which creates and registers it with OLE
|
|
* to be invoked on OLE D&D opeartions on the window (target)
|
|
*
|
|
\***************************************************************************/
|
|
class CMMCDropTarget :
|
|
public CTiedComObject<CMMCViewDropTarget>,
|
|
public IDropTarget,
|
|
public CComObjectRoot
|
|
{
|
|
public:
|
|
typedef CMMCViewDropTarget MyTiedObject;
|
|
|
|
BEGIN_COM_MAP(CMMCDropTarget)
|
|
COM_INTERFACE_ENTRY(IDropTarget)
|
|
END_COM_MAP()
|
|
|
|
// IDropTarget methods
|
|
|
|
STDMETHOD(DragEnter)( IDataObject * pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD * pdwEffect );
|
|
STDMETHOD(DragOver)( DWORD grfKeyState, POINTL pt, DWORD * pdwEffect );
|
|
STDMETHOD(DragLeave)(void);
|
|
STDMETHOD(Drop)( IDataObject * pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD * pdwEffect );
|
|
private:
|
|
|
|
// implementation helpers
|
|
|
|
SC ScDropOnTarget(bool bHitTestOnly, IDataObject * pDataObject, POINTL pt, bool& bCopyOperation);
|
|
SC ScRemoveDropTargetHiliting();
|
|
static SC ScAddMenuString(CMenu& menu, DWORD id, UINT idString);
|
|
SC ScDisplayDropMenu(POINTL pt, DWORD dwEffectsAvailable, DWORD& dwSelected);
|
|
DWORD CalculateEffect(DWORD dwEffectsAvailable, DWORD grfKeyState, bool bCopyPreferred);
|
|
|
|
private:
|
|
IDataObjectPtr m_spDataObject; // cached data object
|
|
bool m_bRightDrag; // operation is right click drag
|
|
bool m_bCopyByDefault; // if default operation is copy (not move)
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
////////// CAMCTreeView methods for supporting d&d ///////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCTreeView::ScDropOnTarget
|
|
*
|
|
* PURPOSE: called to hittest or perform drop operation
|
|
*
|
|
* PARAMETERS:
|
|
* bool bHitTestOnly [in] - HitTest / drop
|
|
* IDataObject * pDataObject [in] - data object to copy/move
|
|
* CPoint point [in] - current cursor position
|
|
* bool& bCopyOperation [in/out]
|
|
* [in] - operation to perform (HitTest == false)
|
|
* [out] - default op. (HitTest == true)
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CAMCTreeView::ScDropOnTarget(bool bHitTestOnly, IDataObject * pDataObject, CPoint point, bool& bCopyOperation)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CAMCTreeView::ScDropOnTarget"));
|
|
|
|
// 1. see where it falls
|
|
CTreeCtrl& ctc = GetTreeCtrl();
|
|
UINT flags;
|
|
HTREEITEM htiDrop = ctc.HitTest(point, &flags);
|
|
|
|
if (flags & TVHT_NOWHERE)
|
|
return sc = S_FALSE; // not an error, but no paste;
|
|
|
|
// 2. if we missed the tree item...
|
|
if (!htiDrop)
|
|
{
|
|
// really mad if it was a paste
|
|
if (! bHitTestOnly)
|
|
MessageBeep(0);
|
|
|
|
return sc = S_FALSE; // not an error, but no paste;
|
|
}
|
|
|
|
// 3. get the target node
|
|
|
|
HNODE hNode = (HNODE) ctc.GetItemData(htiDrop);
|
|
|
|
INodeCallback* pNC = GetNodeCallback();
|
|
sc = ScCheckPointers(pNC, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 4. ask what snapin thinks about this paste
|
|
bool bGetDataObjectFromClipboard = false;
|
|
bool bPasteAllowed = false;
|
|
bool bIsCopyDefaultOperation = false;
|
|
sc = pNC->QueryPaste(hNode, /*bScope*/ true, /*LVDATA*/ NULL,
|
|
pDataObject, bPasteAllowed, bIsCopyDefaultOperation);
|
|
if (sc)
|
|
return sc;
|
|
|
|
if (!bPasteAllowed)
|
|
return sc = S_FALSE; // not an error, but no paste;
|
|
|
|
// 5. visual effect
|
|
ctc.SelectDropTarget(htiDrop);
|
|
|
|
// 6. OK so far. If it was a test - we passed
|
|
if (bHitTestOnly)
|
|
{
|
|
bCopyOperation = bIsCopyDefaultOperation;
|
|
return sc;
|
|
}
|
|
|
|
// 7. do paste NOW
|
|
sc = pNC->Drop(hNode, /*bScope*/TRUE, /*LVDATA*/NULL, pDataObject, !bCopyOperation);
|
|
if (sc)
|
|
return sc;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCTreeView::RemoveDropTargetHiliting
|
|
*
|
|
* PURPOSE: called to remove hiliting put for drop target
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
* RETURNS:
|
|
* void
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCTreeView::RemoveDropTargetHiliting()
|
|
{
|
|
CTreeCtrl& ctc = GetTreeCtrl();
|
|
ctc.SelectDropTarget(NULL);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCTreeView::OnBeginRDrag
|
|
*
|
|
* PURPOSE: called when the drag operation is initiated with right mouse button
|
|
*
|
|
* PARAMETERS:
|
|
* NMHDR* pNMHDR
|
|
* LRESULT* pResult
|
|
*
|
|
* RETURNS:
|
|
* void
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCTreeView::OnBeginRDrag(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
OnBeginDrag(pNMHDR, pResult);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCTreeView::OnBeginDrag
|
|
*
|
|
* PURPOSE: called when the drag operation is initiated with
|
|
*
|
|
* PARAMETERS:
|
|
* NMHDR* pNMHDR
|
|
* LRESULT* pResult
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCTreeView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CAMCTreeView::OnBeginDrag"));
|
|
|
|
// 1. parameter check
|
|
sc = ScCheckPointers( pNMHDR, pResult );
|
|
if (sc)
|
|
return;
|
|
|
|
*pResult = 0;
|
|
|
|
// 2. get node calback
|
|
CTreeCtrl& ctc = GetTreeCtrl();
|
|
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
|
|
HNODE hNode = (HNODE) ctc.GetItemData(pNMTreeView->itemNew.hItem);
|
|
|
|
INodeCallback* pNC = GetNodeCallback();
|
|
sc = ScCheckPointers( pNC, E_UNEXPECTED );
|
|
if (sc)
|
|
return;
|
|
|
|
// 3. get data object
|
|
IDataObjectPtr spDO;
|
|
bool bCopyAllowed = false;
|
|
bool bMoveAllowed = false;
|
|
sc = pNC->GetDragDropDataObject(hNode, TRUE, 0, 0, &spDO, bCopyAllowed, bMoveAllowed);
|
|
if ( sc != S_OK || spDO == NULL)
|
|
return;
|
|
|
|
// 4. start d&d
|
|
sc = CMMCDropSource::ScDoDragDrop(spDO, bCopyAllowed, bMoveAllowed);
|
|
if (sc)
|
|
return;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
////////// CAMCListView methods for supporting d&d ///////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// helpers
|
|
|
|
INodeCallback* CAMCListView::GetNodeCallback()
|
|
{
|
|
ASSERT (m_pAMCView != NULL);
|
|
return (m_pAMCView->GetNodeCallback());
|
|
}
|
|
|
|
HNODE CAMCListView::GetScopePaneSelNode()
|
|
{
|
|
ASSERT (m_pAMCView != NULL);
|
|
return (m_pAMCView->GetSelectedNode());
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCListView::ScDropOnTarget
|
|
*
|
|
* PURPOSE: called to hittest or perform drop operation
|
|
*
|
|
* PARAMETERS:
|
|
* bool bHitTestOnly [in] - HitTest / drop
|
|
* IDataObject * pDataObject [in] - data object to copy/move
|
|
* CPoint point [in] - current cursor position
|
|
* bool& bCopyOperation [in/out]
|
|
* [in] - operation to perform (HitTest == false)
|
|
* [out] - default op. (HitTest == true)
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CAMCListView::ScDropOnTarget(bool bHitTestOnly, IDataObject * pDataObject, CPoint point, bool& bCopyOperation)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CAMCListView::ScDropOnTarget"));
|
|
|
|
// 1. sanity check
|
|
sc = ScCheckPointers( m_pAMCView , E_UNEXPECTED );
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 2. see if view does not have a paste tabu (listpads do)
|
|
if (!m_pAMCView->CanDoDragDrop())
|
|
return sc = (bHitTestOnly ? S_FALSE : E_FAIL); // not an error if testing
|
|
|
|
// 3. HitTest the target
|
|
HNODE hNode = NULL;
|
|
bool bScope = false;
|
|
LPARAM lvData = NULL;
|
|
int iDrop = -1;
|
|
|
|
sc = ScGetDropTarget(point, hNode, bScope, lvData, iDrop);
|
|
if (sc.IsError() || (sc == SC(S_FALSE)))
|
|
return sc;
|
|
|
|
// 4. get the callback
|
|
INodeCallback* pNC = GetNodeCallback();
|
|
sc = ScCheckPointers(pNC, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 5. ask what snapin thinks about this paste
|
|
const bool bGetDataObjectFromClipboard = false;
|
|
bool bPasteAllowed = false;
|
|
bool bIsCopyDefaultOperation = false;
|
|
sc = pNC->QueryPaste(hNode, bScope, lvData,
|
|
pDataObject, bPasteAllowed, bIsCopyDefaultOperation);
|
|
if (sc)
|
|
return sc;
|
|
|
|
if (!bPasteAllowed)
|
|
return sc = S_FALSE; // not an error, but no paste;
|
|
|
|
// 6. visual effect
|
|
SelectDropTarget(iDrop);
|
|
|
|
// 7. OK so far. If it was a test - we passed
|
|
if (bHitTestOnly)
|
|
{
|
|
bCopyOperation = bIsCopyDefaultOperation;
|
|
return sc;
|
|
}
|
|
|
|
// 8. do paste NOW
|
|
sc = pNC->Drop(hNode, bScope, lvData, pDataObject, !bCopyOperation);
|
|
if (sc)
|
|
return sc;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCListView::RemoveDropTargetHiliting
|
|
*
|
|
* PURPOSE: called to remove target hiliting
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
* RETURNS:
|
|
* void
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCListView::RemoveDropTargetHiliting()
|
|
{
|
|
SelectDropTarget(-1);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CAMCListView::ScGetDropTarget
|
|
//
|
|
// Synopsis: Get the drop target item (result item or scope item).
|
|
//
|
|
// Arguments: [point] - where the drop is done.
|
|
// [hNode] - Owner node of result pane.
|
|
// [bScope] - scope or result selected.
|
|
// [lvData] - If result the LPARAM of result item.
|
|
// [iDrop] - index of the lv item that is drop target.
|
|
//
|
|
// Returns: SC, S_FALSE means no drop target item.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
SC CAMCListView::ScGetDropTarget(const CPoint& point, HNODE& hNode, bool& bScope, LPARAM& lvData, int& iDrop)
|
|
{
|
|
DECLARE_SC(sc, _T("CAMCListView::ScGetDropTarget"));
|
|
|
|
hNode = NULL;
|
|
bScope = false;
|
|
lvData = NULL;
|
|
iDrop = -1;
|
|
|
|
CListCtrl& lc = GetListCtrl();
|
|
UINT flags;
|
|
iDrop = lc.HitTest(point, &flags);
|
|
|
|
// background is drop target.
|
|
if (iDrop < 0)
|
|
{
|
|
hNode = GetScopePaneSelNode();
|
|
bScope = true;
|
|
return sc;
|
|
}
|
|
|
|
// Need to change this to LVIS_DROPHILITED.
|
|
if (lc.GetItemState(iDrop, LVIS_SELECTED) & LVIS_SELECTED)
|
|
{
|
|
HWND hWnd = ::GetForegroundWindow();
|
|
if (hWnd && (hWnd == m_hWnd))
|
|
return (sc = S_FALSE);
|
|
}
|
|
|
|
/*
|
|
* virtual list? lvData is the item index, hNode is the item selected
|
|
* in the scope pane
|
|
*/
|
|
if (m_bVirtual)
|
|
{
|
|
hNode = GetScopePaneSelNode();
|
|
lvData = iDrop;
|
|
bScope = false;
|
|
}
|
|
else
|
|
{
|
|
LPARAM lParam = lc.GetItemData(iDrop);
|
|
ASSERT (lParam != 0);
|
|
if (lParam == 0)
|
|
return (sc = S_FALSE);
|
|
|
|
CResultItem* pri = CResultItem::FromHandle(lParam);
|
|
|
|
if (pri == NULL)
|
|
return (sc = S_FALSE);
|
|
|
|
if (pri->IsScopeItem())
|
|
{
|
|
hNode = pri->GetScopeNode();
|
|
bScope = true;
|
|
}
|
|
else
|
|
{
|
|
hNode = GetScopePaneSelNode();
|
|
lvData = lParam;
|
|
}
|
|
}
|
|
|
|
sc = ScCheckPointers(hNode, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
return (sc);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCListView::OnBeginRDrag
|
|
*
|
|
* PURPOSE: called when the drag operation is initiated with right mouse button
|
|
*
|
|
* PARAMETERS:
|
|
* NMHDR* pNMHDR
|
|
* LRESULT* pResult
|
|
*
|
|
* RETURNS:
|
|
* void
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCListView::OnBeginRDrag(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
OnBeginDrag(pNMHDR, pResult);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CAMCListView::OnBeginDrag
|
|
*
|
|
* PURPOSE: called when the drag operation is initiated
|
|
*
|
|
* PARAMETERS:
|
|
* NMHDR* pNMHDR
|
|
* LRESULT* pResult
|
|
*
|
|
* RETURNS:
|
|
* void
|
|
*
|
|
\***************************************************************************/
|
|
void CAMCListView::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CAMCListView::OnBeginDrag"));
|
|
|
|
// 1. parameter check
|
|
sc = ScCheckPointers( pNMHDR, pResult );
|
|
if (sc)
|
|
return;
|
|
|
|
*pResult = 0;
|
|
|
|
// 2. sanity check
|
|
sc = ScCheckPointers( m_pAMCView, E_UNEXPECTED );
|
|
if (sc)
|
|
return;
|
|
|
|
// 3. see if view does not have a paste tabu (listpads do)
|
|
if (!m_pAMCView->CanDoDragDrop())
|
|
return;
|
|
|
|
// 4. get selected items
|
|
CListCtrl& lc = GetListCtrl();
|
|
UINT cSel = lc.GetSelectedCount();
|
|
if (cSel <= 0)
|
|
{
|
|
sc = E_UNEXPECTED;
|
|
return;
|
|
}
|
|
|
|
// 5. get node callback
|
|
HNODE hNode = GetScopePaneSelNode();
|
|
long iSel = lc.GetNextItem(-1, LVIS_SELECTED);
|
|
LONG_PTR lvData = m_bVirtual ? iSel : lc.GetItemData(iSel);
|
|
|
|
INodeCallback* pNC = GetNodeCallback();
|
|
sc = ScCheckPointers( pNC, E_UNEXPECTED );
|
|
if (sc)
|
|
return;
|
|
|
|
// 6. retrieve data object
|
|
IDataObjectPtr spDO;
|
|
bool bCopyAllowed = false;
|
|
bool bMoveAllowed = false;
|
|
sc = pNC->GetDragDropDataObject(hNode, FALSE, (cSel > 1), lvData, &spDO, bCopyAllowed, bMoveAllowed);
|
|
if ( sc != S_OK || spDO == NULL)
|
|
{
|
|
/*
|
|
* Problem:
|
|
* If snapin does not return dataobject then drag & drop is not possible.
|
|
* Assume focus in on tree, user downclick's and drags a result item
|
|
* At this time common control sends LVN_ITEMCHANGED to mmc. MMC translates
|
|
* this to MMCN_SELECT and tells snapin that result item is selected.
|
|
* Now when user releases the mouse the tree item still has focus & selection,
|
|
* but the snapin thinks result item is selected (also verbs correspond to
|
|
* result item).
|
|
*
|
|
* Solution:
|
|
* So we will set focus to the result pane.
|
|
*/
|
|
sc = m_pAMCView->ScSetFocusToPane(CConsoleView::ePane_Results);
|
|
return;
|
|
}
|
|
|
|
// 7. do d&d
|
|
sc = CMMCDropSource::ScDoDragDrop(spDO, bCopyAllowed, bMoveAllowed);
|
|
if (sc)
|
|
return;
|
|
|
|
/*
|
|
* Problem:
|
|
* If a result item is dropped into another result item or another
|
|
* scope item in list-view then focus disappears from that item.
|
|
* But there should be always an item selected after any de-select.
|
|
* We cannot change the focus to result pane because if focus is already
|
|
* in result pane, this change focus does nothing and no item is selected.
|
|
* So we change the focus to scope pane. For this we first change the focus
|
|
* to result pane and then to scope pane. Because if tree already has focus,
|
|
* setting focus to tree does nothing (CAMCView::ScOnTreeViewActivated).
|
|
*
|
|
* Solution:
|
|
* So we change the focus to result pane and then to scope pane.
|
|
*/
|
|
sc = m_pAMCView->ScSetFocusToPane(CConsoleView::ePane_Results);
|
|
if (sc)
|
|
return;
|
|
|
|
sc = m_pAMCView->ScSetFocusToPane(CConsoleView::ePane_ScopeTree);
|
|
if (sc)
|
|
return;
|
|
|
|
return;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
///////////////////// CMMCViewDropTarget methods /////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCViewDropTarget::CMMCViewDropTarget
|
|
*
|
|
* PURPOSE: constructor
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
\***************************************************************************/
|
|
CMMCViewDropTarget::CMMCViewDropTarget() : m_hwndOwner(0)
|
|
{
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCViewDropTarget::~CMMCViewDropTarget
|
|
*
|
|
* PURPOSE: destructor. Revokes drop target for derived view class
|
|
*
|
|
\***************************************************************************/
|
|
CMMCViewDropTarget::~CMMCViewDropTarget()
|
|
{
|
|
DECLARE_SC(sc, TEXT("CViewDropTarget::~CViewDropTarget"));
|
|
|
|
if (m_hwndOwner != NULL)
|
|
sc = RevokeDragDrop(m_hwndOwner);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCViewDropTarget::ScRegisterAsDropTarget
|
|
*
|
|
* PURPOSE: This method is called by the derived class after the view window
|
|
* is created. Method registers the drop target for the window.
|
|
*
|
|
* PARAMETERS:
|
|
* HWND hWnd [in] - drop target view handle
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCViewDropTarget::ScRegisterAsDropTarget(HWND hWnd)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCViewDropTarget::ScRegister"));
|
|
|
|
// 1. parameter check
|
|
if (hWnd == NULL)
|
|
return sc = E_INVALIDARG;
|
|
|
|
// 2. sanity check - should not come here twice
|
|
if (m_spTarget != NULL)
|
|
return sc = E_UNEXPECTED;
|
|
|
|
// 3. create a drop target com object
|
|
IDropTargetPtr spTarget;
|
|
sc = ScCreateTarget(&spTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 4. recheck
|
|
sc = ScCheckPointers(spTarget, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 5. register with OLE
|
|
sc = RegisterDragDrop(hWnd, spTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 6. store info into members
|
|
m_spTarget.Attach( spTarget.Detach() );
|
|
m_hwndOwner = hWnd;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCViewDropTarget::ScCreateTarget
|
|
*
|
|
* PURPOSE: helper. creates tied com object to regiter with OLE
|
|
*
|
|
* PARAMETERS:
|
|
* IDropTarget **ppTarget [out] tied com object
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCViewDropTarget::ScCreateTarget(IDropTarget **ppTarget)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCViewDropTarget::ScCreateTarget"));
|
|
|
|
// 1. check parameters
|
|
sc = ScCheckPointers(ppTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 2. init out parameter
|
|
*ppTarget = NULL;
|
|
|
|
// 3. create com object to register as target
|
|
IDropTargetPtr spDropTarget;
|
|
sc = CTiedComObjectCreator<CMMCDropTarget>::ScCreateAndConnect(*this, spDropTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
sc = ScCheckPointers(spDropTarget, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 4. pass reference to client
|
|
*ppTarget = spDropTarget.Detach();
|
|
|
|
return sc;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
| class CMMCDropSource methods |
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropSource::QueryContinueDrag
|
|
*
|
|
* PURPOSE: implements IDropSource::QueryContinueDrag interface used bu OLE
|
|
*
|
|
* PARAMETERS:
|
|
* BOOL fEscapePressed [in] - ESC was pressed
|
|
* DWORD grfKeyState [in] - mouse & control button state
|
|
*
|
|
* RETURNS:
|
|
* HRESULT - error or S_OK(continue), DRAGDROP_S_CANCEL(cancel), DRAGDROP_S_DROP(drop)
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropSource::QueryContinueDrag( BOOL fEscapePressed, DWORD grfKeyState )
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropSource::QueryContinueDrag"));
|
|
|
|
// 1. quit on cancel
|
|
if (fEscapePressed)
|
|
return (sc = DRAGDROP_S_CANCEL).ToHr();
|
|
|
|
// 2. inspect mouse buttons
|
|
DWORD mButtons = (grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON) );
|
|
if ( mButtons == 0 ) // all released?
|
|
return (sc = DRAGDROP_S_DROP).ToHr();
|
|
|
|
// 3. quit also if more than one mouse button is pressed
|
|
if ( mButtons != MK_LBUTTON && mButtons != MK_RBUTTON && mButtons != MK_MBUTTON )
|
|
return (sc = DRAGDROP_S_CANCEL).ToHr();
|
|
|
|
// 4. else just continue ...
|
|
|
|
return sc.ToHr();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropSource::GiveFeedback
|
|
*
|
|
* PURPOSE: Gives feedback for d&d operations
|
|
*
|
|
* PARAMETERS:
|
|
* DWORD dwEffect
|
|
*
|
|
* RETURNS:
|
|
* DRAGDROP_S_USEDEFAULTCURSORS
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropSource::GiveFeedback( DWORD dwEffect )
|
|
{
|
|
// nothing special by now
|
|
return DRAGDROP_S_USEDEFAULTCURSORS;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropSource::ScDoDragDrop
|
|
*
|
|
* PURPOSE: Performs DragAndDrop operation
|
|
* This is static method to be used to initiate drag and drop
|
|
*
|
|
* PARAMETERS:
|
|
* IDataObject *pDataObject [in] data object to copy/move
|
|
* bool bCopyAllowed [in] if copy is allowed
|
|
* bool bMoveAllowed [in] if move is allowed
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCDropSource::ScDoDragDrop(IDataObject *pDataObject, bool bCopyAllowed, bool bMoveAllowed)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropSource::ScDoDragDrop"));
|
|
|
|
// 1. cocreate com object for OLE
|
|
typedef CComObject<CMMCDropSource> ComCMMCDropSource;
|
|
ComCMMCDropSource *pSource = NULL;
|
|
sc = ComCMMCDropSource::CreateInstance(&pSource);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 2. recheck
|
|
sc = ScCheckPointers(pSource, E_UNEXPECTED);
|
|
if (sc)
|
|
{
|
|
delete pSource;
|
|
return sc;
|
|
}
|
|
|
|
// 3. QI for IDropSource interface
|
|
IDropSourcePtr spDropSource = pSource;
|
|
sc = ScCheckPointers(spDropSource, E_UNEXPECTED);
|
|
if (sc)
|
|
{
|
|
delete pSource;
|
|
return sc;
|
|
}
|
|
|
|
// 4. perform DragDrop
|
|
DWORD dwEffect = DROPEFFECT_NONE;
|
|
const DWORD dwEffectAvailable = (bCopyAllowed ? DROPEFFECT_COPY : 0)
|
|
|(bMoveAllowed ? DROPEFFECT_MOVE : 0);
|
|
sc = DoDragDrop(pDataObject, spDropSource, dwEffectAvailable, &dwEffect);
|
|
if (sc)
|
|
return sc;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*\
|
|
| class CMMCDropTarget methods |
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::DragEnter
|
|
*
|
|
* PURPOSE: Invoked by OLE when d&d cursor enters the window for which
|
|
* this target was registered.
|
|
*
|
|
* PARAMETERS:
|
|
* IDataObject * pDataObject [in] - data object to copy/move
|
|
* DWORD grfKeyState [in] - current key state
|
|
* POINTL pt [in] - current cursor position
|
|
* DWORD * pdwEffect [out] - operations supported
|
|
*
|
|
* RETURNS:
|
|
* HRESULT - result code
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropTarget::DragEnter( IDataObject * pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD * pdwEffect )
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::DragEnter"));
|
|
|
|
// 1. cache for drag over
|
|
m_spDataObject = pDataObject;
|
|
|
|
// 2. parameter check
|
|
sc = ScCheckPointers(pDataObject, pdwEffect);
|
|
if (sc)
|
|
return sc.ToHr();
|
|
|
|
// 3. let it happen - will do more exact filtering on DragOver
|
|
*pdwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY;
|
|
|
|
return sc.ToHr();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::DragOver
|
|
*
|
|
* PURPOSE: called continuosly while cursor is drgged over the window
|
|
*
|
|
* PARAMETERS:
|
|
* DWORD grfKeyState [in] - current key state
|
|
* POINTL pt [in] - current cursor position
|
|
* DWORD * pdwEffect [out] - operations supported
|
|
*
|
|
* RETURNS:
|
|
* HRESULT - result code
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropTarget::DragOver( DWORD grfKeyState, POINTL pt, DWORD * pdwEffect )
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::DragOver"));
|
|
|
|
// 1. parameter check
|
|
sc = ScCheckPointers(pdwEffect);
|
|
if (sc)
|
|
return sc.ToHr();
|
|
|
|
// 2. sanity check
|
|
sc = ScCheckPointers(m_spDataObject, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc.ToHr();
|
|
|
|
bool bCopyByDefault = false; // initially we are to move
|
|
|
|
// 3. ask the view to estimate what can be done in this position
|
|
sc = ScDropOnTarget( true /*bHitTestOnly*/, m_spDataObject, pt, bCopyByDefault );
|
|
if ( sc == S_OK )
|
|
*pdwEffect = CalculateEffect( *pdwEffect, grfKeyState, bCopyByDefault );
|
|
else
|
|
*pdwEffect = DROPEFFECT_NONE; // no-op on failure or S_FALSE
|
|
|
|
return sc.ToHr();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::DragLeave
|
|
*
|
|
* PURPOSE: called when cursor leave the window
|
|
*
|
|
* PARAMETERS:
|
|
* void
|
|
*
|
|
* RETURNS:
|
|
* HRESULT - result code
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropTarget::DragLeave(void)
|
|
{
|
|
DECLARE_SC(sc, TEXT("DragLeave"));
|
|
|
|
// 1. release data object
|
|
m_spDataObject = NULL;
|
|
|
|
// 2. ask the view to remove hiliting it put on target
|
|
sc = ScRemoveDropTargetHiliting();
|
|
if (sc)
|
|
sc.TraceAndClear();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::Drop
|
|
*
|
|
* PURPOSE: Called when data is dropped on target
|
|
*
|
|
* PARAMETERS:
|
|
* IDataObject * pDataObject [in] - data object to copy/move
|
|
* DWORD grfKeyState [in] - current key state
|
|
* POINTL pt [in] - current cursor position
|
|
* DWORD * pdwEffect [out] - operation performed
|
|
*
|
|
* RETURNS:
|
|
* HRESULT - result code
|
|
*
|
|
\***************************************************************************/
|
|
STDMETHODIMP CMMCDropTarget::Drop( IDataObject * pDataObject, DWORD grfKeyState,
|
|
POINTL pt, DWORD * pdwEffect )
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::DragEnter"));
|
|
|
|
// 1. release in case we have anything
|
|
m_spDataObject = NULL;
|
|
|
|
// 2. parameter check
|
|
sc = ScCheckPointers(pDataObject, pdwEffect);
|
|
if (sc)
|
|
return sc.ToHr();
|
|
|
|
// 3. init operation with cached value
|
|
bool bCopyOperation = m_bCopyByDefault;
|
|
|
|
// 4. see what operation to perform
|
|
if (m_bRightDrag)
|
|
{
|
|
// 4.1. give user the choice
|
|
DWORD dwSelected = ( m_bCopyByDefault ? DROPEFFECT_COPY : DROPEFFECT_MOVE );
|
|
sc = ScDisplayDropMenu( pt, *pdwEffect, dwSelected );
|
|
if (sc)
|
|
return sc.ToHr();
|
|
|
|
*pdwEffect = dwSelected;
|
|
}
|
|
else
|
|
{
|
|
// 4.2. inspect keyboard
|
|
*pdwEffect = CalculateEffect(*pdwEffect, grfKeyState, bCopyOperation);
|
|
}
|
|
|
|
// 5. perform
|
|
if (*pdwEffect != DROPEFFECT_NONE) // not canceled yet?
|
|
{
|
|
// now the final decision - copy or move
|
|
bCopyOperation = ( *pdwEffect & DROPEFFECT_COPY );
|
|
|
|
// Let it happen. Drop.
|
|
sc = ScDropOnTarget( false /*bHitTestOnly*/, pDataObject, pt, bCopyOperation );
|
|
if ( sc != S_OK )
|
|
*pdwEffect = DROPEFFECT_NONE;
|
|
}
|
|
|
|
// 6. remove hiliting. reuse DragLeave (don't care about the results)
|
|
DragLeave();
|
|
|
|
return sc.ToHr();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::ScDropOnTarget
|
|
*
|
|
* PURPOSE: helper, forwarding calls to the view
|
|
* Called as a request to hittest / perform drop operation
|
|
*
|
|
* PARAMETERS:
|
|
* bool bHitTestOnly [in] - HitTest / drop
|
|
* IDataObject * pDataObject [in] - data object to copy/move
|
|
* POINTL pt [in] - current cursor position
|
|
* bool& bCopyOperation [in/out]
|
|
* [in] - operation to perform (HitTest == false)
|
|
* [out] - default op. (HitTest == true)
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCDropTarget::ScDropOnTarget(bool bHitTestOnly, IDataObject * pDataObject, POINTL pt, bool& bCopyOperation)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::ScDropOnTarget"));
|
|
|
|
// 1. get tied object - view
|
|
CMMCViewDropTarget *pTarget = NULL;
|
|
sc = ScGetTiedObject(pTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 2. recheck
|
|
sc = ScCheckPointers(pTarget, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 3. calculate client coordinates
|
|
CPoint point(pt.x, pt.y);
|
|
ScreenToClient(pTarget->GetWindowHandle(), &point);
|
|
|
|
// 4. forward to the view
|
|
sc = pTarget->ScDropOnTarget( bHitTestOnly, pDataObject, point, bCopyOperation );
|
|
if ( sc != S_OK )
|
|
ScRemoveDropTargetHiliting(); // remove hiliting if missed the traget
|
|
if (sc)
|
|
return sc;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::ScRemoveDropTargetHiliting
|
|
*
|
|
* PURPOSE: helper, forwarding calls to the view
|
|
* Called to cancel visual effects on target
|
|
*
|
|
* PARAMETERS:
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCDropTarget::ScRemoveDropTargetHiliting()
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::ScRemoveDropTargetHiliting"));
|
|
|
|
// `. get tied object - view
|
|
CMMCViewDropTarget *pTarget = NULL;
|
|
sc = ScGetTiedObject(pTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
sc = ScCheckPointers(pTarget, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 2. forward to the view
|
|
pTarget->RemoveDropTargetHiliting();
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::ScAddMenuString
|
|
*
|
|
* PURPOSE: Helper. Adds resource string to paste context menu
|
|
*
|
|
* PARAMETERS:
|
|
* CMenu& menu [in] - menu to modify
|
|
* DWORD id [in] - menu command
|
|
* UINT idString [in] - id of resource string
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCDropTarget::ScAddMenuString(CMenu& menu, DWORD id, UINT idString)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::ScAddMenuString"));
|
|
|
|
// 1. load the string
|
|
CString strItem;
|
|
bool bOK = LoadString( strItem, idString );
|
|
if ( !bOK )
|
|
return sc = E_FAIL;
|
|
|
|
// 2. add to the menu
|
|
bOK = menu.AppendMenu( MF_STRING, id, strItem );
|
|
if ( !bOK )
|
|
return sc = E_FAIL;
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::ScDisplayDropMenu
|
|
*
|
|
* PURPOSE: Helper. Displays paste context menu
|
|
*
|
|
* PARAMETERS:
|
|
* POINTL pt [in] - point where the menu should appear
|
|
* DWORD dwEffectsAvailable [in] - available commands
|
|
* DWORD& dwSelected [in] - selected command
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
SC CMMCDropTarget::ScDisplayDropMenu(POINTL pt, DWORD dwEffectsAvailable, DWORD& dwSelected)
|
|
{
|
|
DECLARE_SC(sc, TEXT("CMMCDropTarget::ScDisplayDropMenu"));
|
|
|
|
CMenu menu;
|
|
|
|
// 0. create the menu
|
|
bool bOK = menu.CreatePopupMenu();
|
|
if ( !bOK )
|
|
return sc = E_FAIL;
|
|
|
|
// 1. add choice for copy
|
|
if ( dwEffectsAvailable & DROPEFFECT_COPY )
|
|
{
|
|
sc = ScAddMenuString(menu, DROPEFFECT_COPY, IDS_DragDrop_CopyHere);
|
|
if (sc)
|
|
return sc;
|
|
}
|
|
|
|
// 2. add choice for move
|
|
if ( dwEffectsAvailable & DROPEFFECT_MOVE )
|
|
{
|
|
sc = ScAddMenuString(menu, DROPEFFECT_MOVE, IDS_DragDrop_MoveHere);
|
|
if (sc)
|
|
return sc;
|
|
}
|
|
|
|
// 3. add separator if copy or paste was added
|
|
if ( dwEffectsAvailable & ( DROPEFFECT_COPY | DROPEFFECT_MOVE ) )
|
|
{
|
|
bool bOK = menu.AppendMenu( MF_SEPARATOR );
|
|
if ( !bOK )
|
|
return sc = E_FAIL;
|
|
}
|
|
|
|
// 4. always add choice for cancel
|
|
sc = ScAddMenuString(menu, DROPEFFECT_NONE, IDS_DragDrop_Cancel);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 5. set the default item
|
|
if ( dwSelected != DROPEFFECT_NONE )
|
|
{
|
|
bool bOK = menu.SetDefaultItem( dwSelected );
|
|
if ( !bOK )
|
|
return sc = E_FAIL;
|
|
}
|
|
|
|
// 6. find the tied object
|
|
CMMCViewDropTarget *pTarget = NULL;
|
|
sc = ScGetTiedObject(pTarget);
|
|
if (sc)
|
|
return sc;
|
|
|
|
sc = ScCheckPointers(pTarget, E_UNEXPECTED);
|
|
if (sc)
|
|
return sc;
|
|
|
|
// 7. display the menu
|
|
dwSelected = menu.TrackPopupMenu(TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_LEFTBUTTON,
|
|
pt.x, pt.y, CWnd::FromHandlePermanent( pTarget->GetWindowHandle() ) );
|
|
|
|
return sc;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* METHOD: CMMCDropTarget::CalculateEffect
|
|
*
|
|
* PURPOSE: Helper. calculates drop effect by combining:
|
|
* a) available operations
|
|
* b) the default operation
|
|
* c) keyboard key combination
|
|
*
|
|
* PARAMETERS:
|
|
* DWORD dwEffectsAvailable [in] available operations
|
|
* DWORD grfKeyState [in] keyboard / mouse state
|
|
* bool bCopyPreferred [in] default operation
|
|
*
|
|
* RETURNS:
|
|
* SC - result code
|
|
*
|
|
\***************************************************************************/
|
|
DWORD CMMCDropTarget::CalculateEffect(DWORD dwEffectsAvailable, DWORD grfKeyState, bool bCopyPreferred)
|
|
{
|
|
const bool bShiftPressed = (grfKeyState & MK_SHIFT);
|
|
const bool bControlPressed = (grfKeyState & MK_CONTROL);
|
|
const bool bRightClickDrag = (grfKeyState & MK_RBUTTON);
|
|
|
|
m_bRightDrag = bRightClickDrag;
|
|
m_bCopyByDefault = bCopyPreferred;
|
|
|
|
if (!bRightClickDrag) // affected by keyboard only in non-right-drag
|
|
{
|
|
// do nothing if user holds on shift+control
|
|
if ( bShiftPressed && bControlPressed )
|
|
return DROPEFFECT_NONE;
|
|
|
|
// modify by user interactive preferences
|
|
if ( bShiftPressed )
|
|
{
|
|
// if user cannot get what he wants to - indicate it
|
|
if ( !(dwEffectsAvailable & DROPEFFECT_MOVE) )
|
|
return DROPEFFECT_NONE;
|
|
|
|
bCopyPreferred = false;
|
|
}
|
|
else if ( bControlPressed )
|
|
{
|
|
// if user cannot get what he wants to - indicate it
|
|
if ( !(dwEffectsAvailable & DROPEFFECT_COPY) )
|
|
return DROPEFFECT_NONE;
|
|
|
|
bCopyPreferred = true;
|
|
}
|
|
}
|
|
|
|
// return preferred, if available
|
|
if ( bCopyPreferred && (dwEffectsAvailable & DROPEFFECT_COPY) )
|
|
return DROPEFFECT_COPY;
|
|
|
|
if ( !bCopyPreferred && (dwEffectsAvailable & DROPEFFECT_MOVE) )
|
|
return DROPEFFECT_MOVE;
|
|
|
|
// preferred not available - return what is available
|
|
|
|
if ( dwEffectsAvailable & DROPEFFECT_COPY )
|
|
{
|
|
m_bCopyByDefault = true;
|
|
return DROPEFFECT_COPY;
|
|
}
|
|
else if ( dwEffectsAvailable & DROPEFFECT_MOVE )
|
|
{
|
|
m_bCopyByDefault = false;
|
|
return DROPEFFECT_MOVE;
|
|
}
|
|
|
|
return DROPEFFECT_NONE;
|
|
}
|