|
|
/*
* * @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(); } }
|