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