WindowsXP-SP1/windows/richedit/re41/callmgr.cpp
2020-09-30 16:53:49 +02:00

612 lines
14 KiB
C++

/*
*
* @doc INTERNAL
*
* @module CALLMGR.CPP CCallMgr implementation |
*
* Purpose: The call manager controls various aspects of
* a client call chain, including re-entrancy management,
* undo contexts, and change notifications.
*
* Author: <nl>
* alexgo 2/8/96
*
* See the documentation in reimplem.doc for a detailed explanation
* of how all this stuff works.
*
* Copyright (c) 1995-2000 Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_edit.h"
#include "_m_undo.h"
#include "_callmgr.h"
#include "_select.h"
#include "_disp.h"
#include "_dxfrobj.h"
#ifndef NOPRIVATEMESSAGE
#include "_MSREMSG.H"
#endif
#define EN_CLIPFORMAT 0x0712
#define ENM_CLIPFORMAT 0x00000080
typedef struct _clipboardformat
{
NMHDR nmhdr;
CLIPFORMAT cf;
} CLIPBOARDFORMAT;
ASSERTDATA
/*
* CCallMgr::SetChangeEvent(fType)
*
* @mfunc informs the callmgr that some data in the document
* changed. The fType parameter describes the actual change
*/
void CCallMgr::SetChangeEvent(
CHANGETYPE fType) //@parm the type of change (e.g. text, etc)
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetChangeEvent");
// if another callmgr exists higher up the chain, then
// delegate the call to it
if( _pPrevcallmgr )
{
Assert(_fChange == FALSE);
Assert(_fTextChanged == FALSE);
_pPrevcallmgr->SetChangeEvent(fType);
}
else
{
_fChange = TRUE;
_ped->_fModified = TRUE;
_ped->_fSaved = FALSE;
_fTextChanged = !!(fType & CN_TEXTCHANGED);
}
}
/*
* CCallmgr::ClearChangeEvent()
*
* @mfunc If a change happened, then clear the change event bit.
* This allows callers to make changes to the edit control
* _without_ having a notifcation fire. Sometimes, this
* is necessary for backwards compatibility.
*
* @devnote This is a very dangerous method to use. If _fChange
* is set, it may represent more than 1 change; in other words,
* other changes than the one that should be ignored. However,
* for all existing uses of this method, earlier changes are
* irrelevant.
*/
void CCallMgr::ClearChangeEvent()
{
if( _pPrevcallmgr )
{
Assert(_fChange == FALSE);
Assert(_fTextChanged == FALSE);
_pPrevcallmgr->ClearChangeEvent();
}
else
{
_fChange = FALSE;
_fTextChanged = FALSE;
// caller is responsible for setting _fModifed
}
}
/*
* CCallMgr::SetNewUndo()
*
* @mfunc Informs the notification code that a new undo action has
* been added to the undo stack
*/
void CCallMgr::SetNewUndo()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetNewUndo");
// we should only ever do this once per call
// It's assert during IME composition in Outlook. (see bug #3883)
// Removing the assert does not caused any side effect.
// Assert(_fNewUndo == FALSE);
if( _pPrevcallmgr )
{
_pPrevcallmgr->SetNewUndo();
}
else
{
_fNewUndo = TRUE;
}
}
/*
*
* CCallMgr::SetNewRedo ()
*
* @mfunc Informs the notification code that a new redo action has
* been added to the redo stack.
*/
void CCallMgr::SetNewRedo()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetNewRedo");
// we should only ever do this once per call.
// The following assert looks bogus as it is forced to occur when an undo is
// called with a count greater than 1. Therefore, for now, I (a-rsail) am
// commenting it out.
// Assert(_fNewRedo == FALSE);
if( _pPrevcallmgr )
{
_pPrevcallmgr->SetNewRedo();
}
else
{
_fNewRedo = TRUE;
}
}
/*
* CCallMgr::SetMaxText()
*
* @mfunc Informs the notification code that the max text limit has
* been reached.
*/
void CCallMgr::SetMaxText()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetMaxText");
// if there is a call context higher on the stack, delegate to it.
if( _pPrevcallmgr )
{
Assert(_fMaxText == 0);
_pPrevcallmgr->SetMaxText();
}
else
{
_fMaxText = TRUE;
}
}
/*
* CCallMgr::SetSelectionChanged()
*
* @mfunc Informs the notification code that the selection has
* changed
*/
void CCallMgr::SetSelectionChanged()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetSelectionChanged");
AssertSz(_ped->DelayChangeNotification() ? _ped->Get10Mode() : 1, "Flag only should be set in 1.0 mode");
if (_ped->DelayChangeNotification())
return;
// if there is a call context higher on the stack, delegate to it.
if( _pPrevcallmgr )
{
Assert(_fSelChanged == 0);
_pPrevcallmgr->SetSelectionChanged();
}
else
{
_fSelChanged = TRUE;
}
}
/*
* CCallMgr::SetOutOfMemory()
*
* @mfunc Informs the notification code that we were unable to allocate
* enough memory.
*/
void CCallMgr::SetOutOfMemory()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetOutOfMemory");
// if there is a call context higher on the stack, delegate to it.
if( _pPrevcallmgr )
{
Assert(_fOutOfMemory == 0);
_pPrevcallmgr->SetOutOfMemory();
}
else
{
_fOutOfMemory = TRUE;
}
}
/*
* CCallMgr::SetInProtected
*
* @mfunc Indicates that we are currently processing an EN_PROTECTED
* notification
*
* @rdesc void
*/
void CCallMgr::SetInProtected(BOOL flag)
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::SetInProtected");
if( _pPrevcallmgr )
{
_pPrevcallmgr->SetInProtected(flag);
}
else
{
_fInProtected = flag;
}
}
/*
* CCallMgr:GetInProtected()
*
* @mfunc retrieves the InProtected flag, whether or not we are currently
* processing an EN_PROTECTED notification
*
* @rdesc TRUE if we're processing an EN_PROTECTED notification
*/
BOOL CCallMgr::GetInProtected()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::GetInProtected");
if( _pPrevcallmgr )
{
return _pPrevcallmgr->GetInProtected();
}
else
{
return _fInProtected;
}
}
/*
* CCallMgr::RegisterComponent(pcomp, name)
*
* @mfunc Registers a subsystem component implementing IReEntrantComponent.
* This enables this call manager to inform those objects about
* relevant changes in our re-entrancy status.
*/
void CCallMgr::RegisterComponent(
IReEntrantComponent *pcomp, //@parm The component to register
CompName name) //@parm The name for the component
{
pcomp->_idName = name;
pcomp->_pnext = _pcomplist;
_pcomplist = pcomp;
}
/*
* CCallMgr::RevokeComponent(pcomp)
*
* @mfunc Removes a subsystem component from the list of components. The
* component must have been previously registered with _this_
* call context.
*/
void CCallMgr::RevokeComponent(
IReEntrantComponent *pcomp) //@parm The component to remove
{
IReEntrantComponent *plist, **ppprev;
plist = _pcomplist;
ppprev = &_pcomplist;
while( plist != NULL )
{
if( plist == pcomp )
{
*ppprev = plist->_pnext;
break;
}
ppprev = &(plist->_pnext);
plist = plist->_pnext;
}
}
/*
* CCallMgr::GetComponent(name)
*
* @mfunc Retrieves the earliest instance of a registered sub-component.
*
* @rdesc A pointer to the component, if one has been registered. NULL
* otherwise.
*/
IReEntrantComponent *CCallMgr::GetComponent(
CompName name) //@parm the subsystem to look for
{
IReEntrantComponent *plist = _pcomplist;
while( plist != NULL )
{
if( plist->_idName == name )
{
return plist;
}
plist = plist->_pnext;
}
// hmm, didn't find anything. Try contexts higher up, if we're
// the top context, then just return NULL.
if( _pPrevcallmgr )
{
return _pPrevcallmgr->GetComponent(name);
}
return NULL;
}
/*
* CCallMgr::CCallMgr(ped)
*
* @mfunc Constructor
*
* @rdesc void
*/
CCallMgr::CCallMgr(CTxtEdit *ped)
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");
// set everthing to NULL
ZeroMemory(this, sizeof(CCallMgr));
if(ped) // If ped is NULL, a zombie has
{ // been entered
_ped = ped;
_pPrevcallmgr = ped->_pcallmgr;
ped->_pcallmgr = this;
NotifyEnterContext();
#ifndef NOPRIVATEMESSAGE
if (!_pPrevcallmgr && _ped->_pMsgFilter && _ped->_pMsgCallBack)
_ped->_pMsgCallBack->NotifyEvents(NE_ENTERTOPLEVELCALLMGR);
#endif
}
}
/*
* CCallMgr::~CCallMgr()
*
* @mfunc Destructor. If appropriate, we will fire any cached
* notifications and cause the edit object to be destroyed.
*
* @rdesc void
*/
CCallMgr::~CCallMgr()
{
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");
if(IsZombie()) // No reentrancy with Zombies
return;
if( _pPrevcallmgr )
{
// we don't allow these flags to be set in re-entrant call
// states
Assert(_fMaxText == FALSE);
Assert(_fSelChanged == FALSE);
Assert(_fTextChanged == FALSE);
Assert(_fChange == FALSE);
Assert(_fNewRedo == FALSE);
Assert(_fNewUndo == FALSE);
Assert(_fOutOfMemory == FALSE);
// set the ped to the next level of the call state
_ped->_pcallmgr = _pPrevcallmgr;
return;
}
// we're the top level. Note that we explicity do not
// have an overall guard for cases where we are re-entered
// while firing these notifications. This is necessary for
// better 1.0 compatibility and for Forms^3, which wants
// to 'guard' their implementation of ITextHost::TxNotify and
// ignore any notifications that happen while they are
// processing our notifications. Make sense?
_ped->_pcallmgr = NULL;
// Process our internal notifications
if(_ped->_fUpdateSelection)
{
CTxtSelection *psel = _ped->GetSel();
_ped->_fUpdateSelection = FALSE;
if(psel && !_ped->_pdp->IsFrozen() && !_fOutOfMemory )
{
// this may cause an out of memory, so set things
// up for that
CCallMgr callmgr(_ped);
psel->Update(FALSE);
}
}
// Now fire any external notifications that may be necessary
if( _fChange || _fSelChanged || _fMaxText || _fOutOfMemory )
{
SendAllNotifications();
}
// finally, we should check to see if we should delete the
// CTxtEdit instance.
if(!_ped->_fSelfDestruct)
{
if( _ped->_unk._cRefs == 0)
{
delete _ped;
}
#ifndef NOPRIVATEMESSAGE
else
{
if (_ped->_pMsgFilter && _ped->_pMsgCallBack)
{
DWORD dwEvents = NE_EXITTOPLEVELCALLMGR;
if (_fSelChanged)
dwEvents |= NE_CALLMGRSELCHANGE;
if (_fChange)
dwEvents |= NE_CALLMGRCHANGE;
_ped->_pMsgCallBack->NotifyEvents(dwEvents);
}
}
#endif
}
}
//
// PRIVATE methods
//
/*
* CCallMgr::SendAllNotifications()
*
* @mfunc sends notifications for any cached notification bits.
*/
void CCallMgr::SendAllNotifications()
{
//if the ped has already destructed, we cant make calls though it
if (_ped->_fSelfDestruct)
return;
ITextHost *phost = _ped->GetHost();
CHANGENOTIFY cn;
TRACEBEGIN(TRCSUBSYSEDIT, TRCSCOPEINTERN, "CCallMgr::");
//
// COMPATIBILITY ISSUE: The ordering of these events _may_
// be an issue. I've attempted to preserve the ordering
// that the original code would use, but we have ~many~ more
// control paths, so it's difficult.
//
if( _fMaxText )
{
// Beep if we are to emulate the system edit control
if (_ped->_fSystemEditBeep)
_ped->Beep();
phost->TxNotify(EN_MAXTEXT, NULL);
}
if( _fSelChanged )
{
if( (_ped->_dwEventMask & ENM_SELCHANGE) && !(_ped->_fSuppressNotify))
{
CTxtSelection * const psel = _ped->GetSel();
if(psel)
{
SELCHANGE selchg;
ZeroMemory(&selchg, sizeof(SELCHANGE));
psel->SetSelectionInfo(&selchg);
if (_ped->Get10Mode())
{
selchg.chrg.cpMin = _ped->GetAcpFromCp(selchg.chrg.cpMin);
selchg.chrg.cpMost = _ped->GetAcpFromCp(selchg.chrg.cpMost);
}
phost->TxNotify(EN_SELCHANGE, &selchg);
}
}
}
if( _fOutOfMemory && !_ped->GetOOMNotified())
{
_fNewUndo = 0;
_fNewRedo = 0;
_ped->ClearUndo(NULL);
_ped->_pdp->InvalidateRecalc();
_ped->SetOOMNotified(TRUE);
phost->TxNotify(EN_ERRSPACE, NULL);
_ped->SetOOMNotified(FALSE);
}
if( _fChange )
{
if( (_ped->_dwEventMask & ENM_CHANGE) && !(_ped->_fSuppressNotify))
{
cn.dwChangeType = 0;
cn.pvCookieData = 0;
if( _fNewUndo )
{
Assert(_ped->_pundo);
cn.dwChangeType |= CN_NEWUNDO;
cn.pvCookieData = _ped->_pundo->GetTopAECookie();
}
else if( _fNewRedo )
{
Assert(_ped->_predo);
cn.dwChangeType |= CN_NEWREDO;
cn.pvCookieData = _ped->_predo->GetTopAECookie();
}
if( _fTextChanged )
{
cn.dwChangeType |= CN_TEXTCHANGED;
}
_ped->_dwEventMask &= ~ENM_CHANGE;
phost->TxNotify(EN_CHANGE, &cn);
_ped->_dwEventMask |= ENM_CHANGE;
}
}
if((_ped->_dwEventMask & ENM_CLIPFORMAT) && _ped->_ClipboardFormat)
{
CLIPBOARDFORMAT cf;
ZeroMemory(&cf, sizeof(CLIPBOARDFORMAT));
cf.cf = g_rgFETC[_ped->_ClipboardFormat - 1].cfFormat;
_ped->_ClipboardFormat = 0;
phost->TxNotify(EN_CLIPFORMAT, &cf);
}
#if !defined(NOLINESERVICES) && !defined(NOCOMPLEXSCRIPTS)
extern char *g_szMsgBox;
if(g_szMsgBox)
{
CLock lock;
if(g_szMsgBox)
{
MessageBoxA(NULL, g_szMsgBox, NULL, MB_ICONEXCLAMATION | MB_TASKMODAL | MB_SETFOREGROUND);
FreePv((void *)g_szMsgBox);
g_szMsgBox = NULL;
}
}
#endif
}
/*
* CCallMgr::NotifyEnterContext()
*
* @mfunc Notify any registered components that a new context
* has been entered.
*
*/
void CCallMgr::NotifyEnterContext()
{
IReEntrantComponent *pcomp = _pcomplist;
while( pcomp )
{
pcomp->OnEnterContext();
pcomp = pcomp->_pnext;
}
if( _pPrevcallmgr )
{
_pPrevcallmgr->NotifyEnterContext();
}
}