windows-nt/Source/XPSP1/NT/windows/richedit/re30/dragdrp.cpp
2020-09-26 16:20:57 +08:00

1197 lines
26 KiB
C++

/*
* DRAGDRP.C
*
* Purpose:
* Implementation of Richedit's OLE drag drop objects (namely,
* the drop target and drop source objects)
*
* Author:
* alexgo (4/24/95)
*
* Copyright (c) 1995-1998, Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_edit.h"
#include "_dragdrp.h"
#include "_disp.h"
#include "_select.h"
#include "_font.h"
#include "_measure.h"
ASSERTDATA
//
// CDropSource PUBLIC methods
//
/*
* CDropSource::QueryInterface (riid, ppv)
*/
STDMETHODIMP CDropSource::QueryInterface(REFIID riid, void ** ppv)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::QueryInterface");
if( IsEqualIID(riid, IID_IUnknown) )
{
*ppv = (IUnknown *)this;
}
else if( IsEqualIID(riid, IID_IDropSource) )
{
*ppv = (IDropSource *)this;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
/*
* CDropSource::AddRef
*/
STDMETHODIMP_(ULONG) CDropSource::AddRef()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::AddRef");
return ++_crefs;
}
/*
* CDropSource::Release
*
* @devnote. Do not even think about making an outgoing call here.
* If you do, be sure make sure all callers use a
* SafeReleaseAndNULL (null the pointer before releasing)
* technique to avoid re-entrancy problems.
*/
STDMETHODIMP_(ULONG) CDropSource::Release()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::Release");
_crefs--;
if( _crefs == 0 )
{
delete this;
return 0;
}
return _crefs;
}
/*
* CDropSource::QueryContinueDrag (fEscapePressed, grfKeyState)
*
* Purpose:
* determines whether or not to continue a drag drop operation
*
* Algorithm:
* if the escape key has been pressed, cancel
* if the left mouse button has been release, then attempt to
* do a drop
*/
STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEscapePressed,
DWORD grfKeyState)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::QueryContinueDrag");
if (fEscapePressed)
{
return DRAGDROP_S_CANCEL;
}
else if (!(grfKeyState & MK_LBUTTON) && !(grfKeyState & MK_RBUTTON))
{
return DRAGDROP_S_DROP;
}
else
{
return NOERROR;
}
}
/*
* CDropSource::GiveFeedback (dwEffect)
*
* Purpose:
* gives feedback during a drag drop operation
*
* Notes:
* FUTURE (alexgo): maybe put in some neater feedback effects
* than the standard OLE stuff??
*/
STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::GiveFeedback");
return DRAGDROP_S_USEDEFAULTCURSORS;
}
/*
* CDropSource::CDropSource
*/
CDropSource::CDropSource()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::CDropSource");
_crefs = 1;
}
//
// CDropSource PRIVATE methods
//
/*
* CDropSource::~CDropSource
*/
CDropSource::~CDropSource()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropSource::~CDropSource");
;
}
//
// CDropTarget PUBLIC methods
//
/*
* CDropTarget::QueryInterface (riid, ppv)
*/
STDMETHODIMP CDropTarget::QueryInterface (REFIID riid, void ** ppv)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::QueryInterface");
if( IsEqualIID(riid, IID_IUnknown) )
{
*ppv = (IUnknown *)this;
}
else if( IsEqualIID(riid, IID_IDropTarget) )
{
*ppv = (IDropTarget *)this;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
/*
* CDropTarget::AddRef
*/
STDMETHODIMP_(ULONG) CDropTarget::AddRef()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::AddRef");
return ++_crefs;
}
/*
* CDropTarget::Release()
*/
STDMETHODIMP_(ULONG) CDropTarget::Release()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::Release");
_crefs--;
if( _crefs == 0 )
{
delete this;
return 0;
}
return _crefs;
}
/*
* CDropTarget::DragEnter (pdo, grfKeyState, pt, pdwEffect)
*
* Purpose:
* called when OLE drag drop enters our "window"
*
* Algorithm:
* first we check to see if the data object being transferred contains
* any data that we support. Then we verify that the 'type' of drag
* is acceptable (i.e., currently, we do not accept links).
*
*
* FUTURE: (alexgo): we may want to accept links as well.
*/
STDMETHODIMP CDropTarget::DragEnter(IDataObject *pdo, DWORD grfKeyState,
POINTL pt, DWORD *pdwEffect)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::DragEnter");
// We don't have a position yet.
_cpCur = -1;
HRESULT hr = NOERROR;
DWORD result;
CTxtSelection *psel;
// At drag enter time, we should have no cached info about what the data
// object supports. This flag should be cleared in DragLeave. Note
// that we slightly override _dwFlags, as it's possible for a data object
// given during drag drop to also generate DOI_NONE.
if( !_ped )
{
return CO_E_RELEASED;
}
Assert(_pcallmgr == NULL);
Assert(_dwFlags == 0);
_pcallmgr = new CCallMgr(_ped);
if( !_pcallmgr )
{
return E_OUTOFMEMORY;
}
// Find out if we can paste the object
result = _ped->GetDTE()->CanPaste(pdo, 0, RECO_DROP);
if( result )
{
if( result == DF_CLIENTCONTROL )
{
_dwFlags |= DF_CLIENTCONTROL;
}
// Create the object that implements the drag caret
_pdrgcrt = new CDropCaret(_ped);
if ((NULL == _pdrgcrt) || !_pdrgcrt->Init())
{
// Initialization failed so go without a caret
delete _pdrgcrt;
_pdrgcrt = NULL;
}
// cache the current selection so we can restore it on return
psel = _ped->GetSel();
Assert(psel);
_cpSel = psel->GetCp();
_cchSel = psel->GetCch();
_dwFlags |= DF_CANDROP;
// just call DragOver to handle our visual feedback
hr = DragOver(grfKeyState, pt, pdwEffect);
}
else if (_ped->fInOurHost())
{
// Just tell the caller that we can't drop.
*pdwEffect = DROPEFFECT_NONE;
}
else
{
// this is new behaviour for Win95 OLE; if we don't
// understand anything about the data object given to us,
// we return S_FALSE to allow our parent to give the
// drag drop a try.
// In theory, only forms^3 uses this information and
// this return exposes an error in NT OLE, therefore,
// we only do this now when not in our own host.
hr = S_FALSE;
}
if( hr != NOERROR )
{
delete _pcallmgr;
_pcallmgr = NULL;
_dwFlags = 0;
}
return hr;
}
/*
* CDropTarget::DragOver (grfKeyState, pt, pdwEffect)
*
* Purpose:
* handles the visual feedback for a drag drop operation zooming
* around over text
*
* FUTURE (alexgo): maybe we should do some snazzy visuals here
*/
STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt,
DWORD *pdwEffect)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::DragOver");
LONG cpCur = _cpCur;
if( !_ped )
{
return CO_E_RELEASED;
}
Assert(_pcallmgr);
// note if we're doing right mouse drag drop; note that we
// can't do this in UpdateEffect as it's called from Drop
// as well (and the mouse button is up then!)
if( (grfKeyState & MK_RBUTTON) )
{
_dwFlags |= DF_RIGHTMOUSEDRAG;
}
else
{
_dwFlags &= ~DF_RIGHTMOUSEDRAG;
}
UpdateEffect(grfKeyState, pt, pdwEffect);
// only draw if we've changed position
if( *pdwEffect != DROPEFFECT_NONE
&& ((cpCur != _cpCur)
|| (_pdrgcrt && _pdrgcrt->NoCaret())))
{
DrawFeedback();
}
return NOERROR;
}
/*
* CDropTarget::DragLeave
*
* Purpose:
* called when the mouse leaves our window during drag drop. Here we clean
* up any temporary state setup for the drag operation.
*/
STDMETHODIMP CDropTarget::DragLeave()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::DragLeave");
CTxtSelection *psel = _ped->GetSel();
if( !_ped )
{
return CO_E_RELEASED;
}
Assert(_pcallmgr);
_dwFlags = 0;
// now restore the selection
psel->SetSelection(_cpSel - _cchSel, _cpSel);
psel->Update(FALSE);
_cpSel = _cchSel = 0;
delete _pcallmgr;
_pcallmgr = NULL;
delete _pdrgcrt;
_pdrgcrt = NULL;
return NOERROR;
}
/*
* CDropTarget::Drop (pdo, grfKeyState, pt, pdwEffect)
*
* @mfunc
* called when the mouse button is released. We should attempt
* to 'paste' the data object into a selection corresponding to
* the mouse location
*
* @devnote
* first, we make sure that we can still do a paste (via UpdateEffect).
* If so, then set the selection to the current point and then insert
* the text.
*
* @rdesc
* HRESULT
*/
STDMETHODIMP CDropTarget::Drop(
IDataObject *pdo,
DWORD grfKeyState,
POINTL ptl,
DWORD * pdwEffect)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::Drop");
HRESULT hr = NOERROR;
if( !_ped )
return CO_E_RELEASED;
Assert(_pcallmgr);
CDropCleanup cleanup(this);
// see if we can still drop
UpdateEffect(grfKeyState, ptl, pdwEffect);
// UpdateEffect will show a drop cursor but at this point we don't need one
// so we hide the drop cursor here.
if (_pdrgcrt)
_pdrgcrt->HideCaret();
if (_dwFlags & DF_OVERSOURCE)
{
*pdwEffect = DROPEFFECT_NONE;
_dwFlags = 0;
return NOERROR;
}
if(*pdwEffect & (DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK))
{
IUndoBuilder * publdr;
CGenUndoBuilder undobldr( _ped, UB_AUTOCOMMIT, &publdr);
// If this is a right mouse drag drop; handle that
if(_dwFlags & DF_RIGHTMOUSEDRAG)
{
hr = HandleRightMouseDrop(pdo, ptl);
// If S_FALSE is returned, treat drag drop normally
if( hr != S_FALSE )
goto Exit;
}
// Get an undo builder. If we already have one cached, that means
// we are dropping onto the same edit instance that started the drag
// In this case, we want to use the cached undo builder so that
// a drag move can be undone as one "operation".
if(_publdr)
publdr = _publdr;
CTxtSelection *psel = _ped->GetSel();
psel->SetSelection(_cpCur, _cpCur);
if( !_ped->IsProtectedRange(WM_PASTE, 0, 0, psel) )
{
hr = _ped->PasteDataObjectToRange(pdo, (CTxtRange *)psel,
0, NULL, publdr, PDOR_DROP);
}
// If we are dropping onto ourselves, the UI specifies
// that we should select the entire range dragged. We use
// _publdr as an easy way to tell if the drop originated from
// this instance
if(SUCCEEDED(hr) && _pdrgcrt)
{
// If the drop worked, then we don't want to restore the area
// where the drop caret used to be since this is not out of date.
_pdrgcrt->CancelRestoreCaretArea();
}
// Now set the selection anti-events. If the selection preceded the
// paste poiont subtract its length from the redo position, since
// the selection will get deleted if we are doing a DRAGMOVE within
// this instance.
LONG cpNext = psel->GetCp();
LONG cchNext = cpNext - _cpCur;
if(_cpSel < _cpCur && _publdr && (*pdwEffect & DROPEFFECT_MOVE))
cpNext -= abs(_cchSel);
HandleSelectionAEInfo(_ped, publdr, _cpCur, 0, cpNext, cchNext,
SELAE_FORCEREPLACE);
if(_publdr)
{
// If we are doing a drag move, then *don't* set the
// selection directly on the screen--doing so will result in
// unsightly UI--we'll set the selection to one spot, draw it
// and then immediately move the selection somewhere else.
// In this case, just change where the selection range exists.
// Floating ranges and the drag-move code in ldte.c will take
// care of the rest.
if( *pdwEffect == DROPEFFECT_COPY )
psel->SetSelection(_cpCur, psel->GetCp());
else
psel->Set(psel->GetCp(), cchNext);
}
else if(publdr)
{
// The drop call landed in us from outside, so we need
// to fire the appropriate notifications. First, however,
// commit the undo builder.
publdr->SetNameID(UID_DRAGDROP);
publdr->Done();
if(SUCCEEDED(hr))
{
// Make this window the foreground window after the drop. Note
// that the host needs to support ITextHost2 to really get to
// be the foreground window. If they don't this is a no-op.
_ped->TxSetForegroundWindow();
}
}
// If nothing changed on the drop && the effect is a move, then return
// failure. This is an ugly hack to improve drag-move scenarios; if
// nothing happened on the drop, then chances are, you don't want
// to have the correspong "Cut" happen on the drag source side.
//
// Of course, this relies on the drag source responding gracefully to
// E_FAIL w/o hitting too much trauma.
if (*pdwEffect == DROPEFFECT_MOVE &&
!_ped->GetCallMgr()->GetChangeEvent() )
{
hr = E_FAIL;
}
}
Exit:
_dwFlags = 0;
return hr;
}
/*
* CDropTarget::CDropTarget (ped)
*
*/
CDropTarget::CDropTarget(CTxtEdit *ped)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::CDropTarget");
_ped = ped;
_crefs = 1;
_dwFlags = 0;
_publdr = NULL;
_cpMin = -1;
_cpMost = -1;
_pcallmgr = NULL;
}
/*
* CDropTarget::SetDragInfo (publdr, cpMin, cpMost)
*
* Purpose:
* allows the data transfer engine to cache important information
* about a drag drop with this drop target.
*
* Arguments:
* publdr -- the undo builder for the operation. With this
* intra-instance drag drop operations can be treated
* as a single user action
* cpMin -- the minimim character position of the range that is
* being dragged. With this and cpMost, we can disable
* dragging into the range that is being dragged!
* cpMost -- the max character position
*
* Notes:
* this method must be called again in order to clear the cached info
*
* -1 for cpMin and cpMost will "clear" those values (as 0 is a valid cp)
*/
void CDropTarget::SetDragInfo( IUndoBuilder *publdr, LONG cpMin, LONG cpMost )
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::SetDragInfo");
_publdr = publdr;
_cpMin = cpMin;
_cpMost = cpMost;
}
/*
* CDropTarget::Zombie
*
* @mfunc This method clears the state in this drop target object. It is
* used to recover 'gracefully' from reference counting errors
*/
void CDropTarget::Zombie()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::Zombie");
_ped = NULL;
if( _pcallmgr )
{
delete _pcallmgr;
_pcallmgr = NULL;
}
}
//
// CDropTarget PRIVATE methods
//
/*
* CDropTarget::~CDropTarget
*/
CDropTarget::~CDropTarget()
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::~CDropTarget");
;
}
/*
* CDropTarget::ConvertScreenPtToClientPt (pptScreen, pptClient)
*
* Purpose:
* OLE drag drop sends points in using screen coordinates. However,
* all of our display code internally relies on client coordinates
* (i.e. the coordinates relative to the window that we are being
* drawn in). This routine will convert between the two
*
* Notes:
* the client coordinates use a POINT structure instead of POINTL.
* while nominally they are the same, OLE uses POINTL and the display
* engine uses POINT.
*
*/
void CDropTarget::ConvertScreenPtToClientPt( POINTL *pptScreen,
POINT *pptClient )
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::ConvertScreenPtToClientPt");
POINT ptS;
pptClient->x = ptS.x = pptScreen->x;
pptClient->y = ptS.y = pptScreen->y;
_ped->TxScreenToClient(pptClient);
return;
}
/*
* CDropTarget::UpdateEffect (grfKeyState, pt, pdwEffect)
*
* Purpose:
* given the keyboard state and point, and knowledge of what
* the data object being transferred can offer, calculate
* the correct drag drop feedback.
*
* Requires:
* this function should only be called during a drag drop
* operation; doing otherwise will simply result in a return
* of DROPEFFECT_NONE.
*
*/
void CDropTarget::UpdateEffect( DWORD grfKeyState, POINTL ptl,
DWORD *pdwEffect)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::UpdateEffect");
POINT pt;
BOOL fHot;
WORD nScrollInset;
HRESULT hr;
LPRICHEDITOLECALLBACK const precall = _ped->GetRECallback();
pt.x = ptl.x;
pt.y = ptl.y;
// first, find out where we are
ConvertScreenPtToClientPt(&ptl, &pt);
_cpCur = _ped->_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE);
// if we are on top of the range that is being
// dragged, then remeber it for later
_dwFlags &= ~DF_OVERSOURCE;
if( _cpCur > _cpMin && _cpCur < _cpMost )
{
_dwFlags |= DF_OVERSOURCE;
}
// Scroll if we need to and remember if we are in the hot zone.
nScrollInset = W32->GetScrollInset();
if (_pdrgcrt != NULL)
{
_pdrgcrt->HideCaret();
}
fHot = _ped->_pdp->AutoScroll(pt, nScrollInset, nScrollInset);
if (_pdrgcrt != NULL)
{
if (((_dwFlags & DF_OVERSOURCE) == 0) && !fHot)
{
_pdrgcrt->ShowCaret();
}
else
{
// The hide above restored the caret so we just
// need to turn off the caret while we are over the
// source.
_pdrgcrt->CancelRestoreCaretArea();
}
}
// Let the client set the effect if it wants, but first, we need
// to check for protection.
if( _ped->IsRich() )
{
// we don't allow dropping onto protected text. Note that
// the _edges_ of a protected range may be dragged to; therefore,
// we need to check for protection at _cpCur and _cpCur-1.
// If both cp's are protected, then we are inside a protected
// range.
CTxtRange rg(_ped, _cpCur, 0);
LONG iProt;
if( (iProt = rg.IsProtected(1)) == CTxtRange::PROTECTED_YES ||
iProt == CTxtRange::PROTECTED_ASK )
{
rg.Advance(-1);
// if we're at the BOD or if the CF of the preceding cp
// is PROTECTED
if(!_cpCur ||
(iProt = rg.IsProtected(-1)) == CTxtRange::PROTECTED_YES ||
iProt == CTxtRange::PROTECTED_ASK)
{
// give the caller a chance to do something if the
// ENM_PROTECTED mask is set.
if( iProt == CTxtRange::PROTECTED_YES ||
!_ped->IsProtectionCheckingEnabled() ||
_ped->QueryUseProtection(&rg, WM_MOUSEMOVE,0, 0) )
{
*pdwEffect = DROPEFFECT_NONE;
goto Exit;
}
}
}
}
if( precall )
{
hr = precall->GetDragDropEffect(FALSE, grfKeyState, pdwEffect);
// Note : RichEdit 1.0 does not check the return code of this call.
// If callback specified a single effect, use it.
// Otherwise pick one ourselves.
// trick: (x & (x-1)) is non-zero if more than one bit is set.
if (!(*pdwEffect & (*pdwEffect - 1) ))
{
goto Exit;
}
}
// If we don't know anything about the data object or the control
// is read-only, set the effect to none.
// If the client is handling this, we don't worry about read-only.
if (!(_dwFlags & DF_CLIENTCONTROL) &&
( !(_dwFlags & DF_CANDROP) || _ped->TxGetReadOnly()))
{
*pdwEffect = DROPEFFECT_NONE;
_cpCur = -1;
// no need to do anything else
return;
}
// if we are on top of the range that is being
// dragged, then we can't drop there!
if( _dwFlags & DF_OVERSOURCE )
{
*pdwEffect = DROPEFFECT_NONE;
goto Exit;
}
// now check the keyboard state and the requested drop effects.
if( (_dwFlags & DF_CANDROP) )
{
// if we can paste plain text, then see if a MOVE or COPY
// operation was requested and set the right effect. Note
// that we prefer MOVEs over COPY's in accordance with OLE
// UI guidelines.
// we do not yet support linking
if( (grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT) )
{
//COMPATIBILITY: Richedit 1.0 did not appear to support drag
//linking correctly.
*pdwEffect = DROPEFFECT_NONE;
}
else if( !(grfKeyState & MK_CONTROL) &&
(*pdwEffect & DROPEFFECT_MOVE) )
{
// if the control key is *not* depressed, then assume a "move"
// operation (note that shift and alt or no keys will also give
// a move) iff the source supports move.
*pdwEffect = DROPEFFECT_MOVE;
}
else if( (grfKeyState & MK_CONTROL) && !((grfKeyState & MK_ALT) &&
(grfKeyState & MK_SHIFT)) && (*pdwEffect & DROPEFFECT_COPY) )
{
// if only the control key is down and we're allowed to do a copy,
// then do a copy
*pdwEffect = DROPEFFECT_COPY;
}
else if( !(grfKeyState & MK_CONTROL) &&
(*pdwEffect & DROPEFFECT_COPY) )
{
// if the control key is *not* depressed, and we are *not* allowed
// to do a move (note that this if comes below the second one), then
// do a COPY operation (if available)
*pdwEffect = DROPEFFECT_COPY;
}
else
{
// not a combination that we support
*pdwEffect = DROPEFFECT_NONE;
}
}
else
{
*pdwEffect = DROPEFFECT_NONE;
}
Exit:
//Add the scrolling effect if we are in the hot zone.
if (fHot)
{
*pdwEffect |= DROPEFFECT_SCROLL;
}
}
/*
* CDropTarget::DrawFeedback
*
* Purpose:
* draws any feeback necessary on the target side (specifically, setting the
* cursor
*
* Notes:
* assumes _cpCur is correctly set.
*/
void CDropTarget::DrawFeedback(void)
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropTarget::DrawFeedback");
if (_pdrgcrt != NULL)
{
// We need to indicate a drop location because a drop is possible
_pdrgcrt->DrawCaret(_cpCur);
}
}
/*
* CDropTarget::HandleRightMouseDrop
*
* @mfunc Handles calling back to the client to get a context menu
* for a right-mouse drag drop.
*
* @rdesc HRESULT
*/
HRESULT CDropTarget::HandleRightMouseDrop(
IDataObject *pdo, //@parm the data object to drop
POINTL ptl) //@parm the location of the drop (screen coords)
{
LPRICHEDITOLECALLBACK precall = NULL;
CHARRANGE cr = {_cpCur, _cpCur};
HMENU hmenu = NULL;
HWND hwnd, hwndParent;
precall = _ped->GetRECallback();
if( !precall || _ped->Get10Mode() )
{
return S_FALSE;
}
// HACK ALERT! evil pointer casting going on here.
precall->GetContextMenu( GCM_RIGHTMOUSEDROP, (IOleObject *)(void *)pdo,
&cr, &hmenu);
if( hmenu && _ped->TxGetWindow(&hwnd) == NOERROR )
{
hwndParent = GetParent(hwnd);
if( !hwndParent )
{
hwndParent = hwnd;
}
TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
ptl.x, ptl.y, 0, hwndParent, NULL);
return NOERROR;
}
return S_FALSE;
}
/*
* CDropCaret::DrawCaret
*
* Purpose:
* Draws a "caret" to indicate where the drop will occur.
*
*/
CDropCaret::CDropCaret(
CTxtEdit *ped) //@parm Edit control
: _ped(ped), _yHeight(-1), _hdcWindow(NULL)
{
// Header does all the work
}
/*
* CDropCaret::~CDropCaret
*
* Purpose:
* Clean up caret object
*
*/
CDropCaret::~CDropCaret()
{
if (_hdcWindow != NULL)
{
// Restore the any updated window area
HideCaret();
// Free the DC we held on to
_ped->_pdp->ReleaseDC(_hdcWindow);
}
}
/*
* CDropCaret::Init
*
* Purpose:
* Do initialization that can fail
*
*/
BOOL CDropCaret::Init()
{
// Get the DC for the window
_hdcWindow = _ped->_pdp->GetDC();
if (NULL == _hdcWindow)
{
// Could not get a DC, we are toast.
AssertSz(FALSE, "CDropCaret::Init could not get hdc");
return FALSE;
}
// Keep pixels per inch since we will need it
_yPixelsPerInch = GetDeviceCaps(_hdcWindow, LOGPIXELSY);
// Set the default maximum size
_yHeightMax = DEFAULT_DROPCARET_MAXHEIGHT;
// Preallocate a bitmap for saving screen
return (_osdc.Init(
_hdcWindow,
WIDTH_DROPCARET,
DEFAULT_DROPCARET_MAXHEIGHT,
CLR_INVALID) != NULL);
}
/*
* CDropCaret::DrawCaret (cpCur)
*
* @mfunc
* Draws a "caret" to indicate where the drop will occur.
*/
void CDropCaret::DrawCaret(
LONG cpCur) //@parm current cp of where drop would occur
{
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "CDropCaret::DrawCaret");
CLock lock; // Uses global (shared) FontCache
CDisplay * pdp = _ped->_pdp;
POINT ptNew;
RECT rcClient;
CLinePtr rp(pdp);
CRchTxtPtr rtp(_ped, cpCur);
// Restore old caret position bits and save new caret position bits
HideCaret();
// We no longer have a caret to restore
_yHeight = -1;
// Get new cp from point
pdp->PointFromTp(rtp, NULL, FALSE, ptNew, &rp, TA_TOP | TA_LOGICAL);
// Get client rectangle
_ped->TxGetClientRect(&rcClient);
// Figure out height of new caret
// Get charformat
const CCharFormat *pCF = rtp.GetCF();
// Get zoomed height
LONG dypInch = MulDiv(_yPixelsPerInch, pdp->GetZoomNumerator(), pdp->GetZoomDenominator());
CCcs *pccs = fc().GetCcs(pCF, dypInch);
if (NULL == pccs)
{
// We can't do anything sensible so give up.
return;
}
// Convert height in charformat to height on screen
LONG yHeight = pdp->LXtoDX(pCF->_yHeight);
LONG yOffset, yAdjust;
pccs->GetOffset(pCF, dypInch, &yOffset, &yAdjust);
// Save new position
ptNew.y += (rp->_yHeight - rp->_yDescent
+ pccs->_yDescent - yHeight - yOffset - yAdjust);
// Release cache entry since we are done with it.
pccs->Release();
// Check if new point is in the client rectangle
if(!PtInRect(&rcClient, ptNew))
return;
// Save new height
_yHeight = yHeight;
// Save the new caret position
_ptCaret.x = ptNew.x;
_ptCaret.y = ptNew.y;
// Is current bit map big enough to hold the bit map we want to put in?
if(yHeight > _yHeightMax)
{
// No - reallocate the bitmap.
if(!_osdc.Realloc(WIDTH_DROPCARET, yHeight))
{
// Reallocation failed - no visual feedback for now
AssertSz(FALSE, "CDropCaret::DrawCaret bitmap reallocation failed");
return;
}
_yHeightMax = yHeight;
}
// Save bits at new caret position
_osdc.Get(_hdcWindow, _ptCaret.x, _ptCaret.y, WIDTH_DROPCARET, yHeight);
// Actually put caret on screen
ShowCaret();
}
/*
* CDropCaret::ShowCaret
*
* Purpose:
* Actually draw caret on the screen
*
*/
void CDropCaret::ShowCaret()
{
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
BOOL fSuccess;
#endif // DEBUG
// Don't show the caret if the height says that there is no caret to show.
if (-1 == _yHeight)
{
return;
}
// Create a pen
HPEN hPenCaret = CreatePen(PS_SOLID, WIDTH_DROPCARET, 0);
if (NULL == hPenCaret)
{
// Call failed, this isn't really catastrophic so just don't
// draw the caret.
AssertSz(FALSE, "CDropCaret::DrawCaret could not create pen");
return;
}
// Put the dotted pen in the DC
HPEN hPenOld = (HPEN) SelectObject(_hdcWindow, hPenCaret);
if (NULL == hPenOld)
{
// Call failed, this isn't really catastrophic so just don't
// draw the caret.
AssertSz(FALSE, "CDropCaret::DrawCaret SelectObject failed");
goto DeleteObject;
}
// Move the drawing pen to where to draw the caret
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
fSuccess =
#endif // DEBUG
MoveToEx(_hdcWindow, _ptCaret.x, _ptCaret.y, NULL);
AssertSz(fSuccess, "CDropCaret::DrawCaret MoveToEx failed");
// Draw the line
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
fSuccess =
#endif // DEBUG
LineTo(_hdcWindow, _ptCaret.x, _ptCaret.y + _yHeight);
AssertSz(fSuccess, "CDropCaret::DrawCaret LineTo failed");
// Restore the current pen
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
hPenCaret = (HPEN)
#endif // DEBUG
SelectObject(_hdcWindow, hPenOld);
AssertSz(hPenCaret != NULL,
"CDropCaret::DrawCaret Restore Original Pen failed");
DeleteObject:
// Dump the pen
#if defined(DEBUG) || defined(_RELEASE_ASSERTS_)
fSuccess =
#endif // DEBUG
DeleteObject(hPenCaret);
AssertSz(fSuccess,
"CDropCaret::DrawCaret Could not delete dotted Pen");
}
/*
* CDropCaret::HideCaret
*
* Purpose:
* Restore caret area after cursor has moved
*
*/
void CDropCaret::HideCaret()
{
if (_yHeight != -1)
{
_osdc.RenderBitMap(_hdcWindow, _ptCaret.x, _ptCaret.y, WIDTH_DROPCARET,
_yHeight);
}
}