📄 m_undo.cpp
字号:
*
* @comm
* This is a *PUBLIC* destructor
*
* Algorithm:
* If this builder hasn't been committed to an undo stack
* via ::Done, then we must be sure to free up any resources
* (antievents) we may be hanging onto
*
*/
CGenUndoBuilder::~CGenUndoBuilder( )
{
_ped->GetCallMgr()->RevokeComponent((IReEntrantComponent *)this);
if( _fAutoCommit == TRUE )
{
Done();
return;
}
// free resources
if( _pfirstae )
{
DestroyAEList(_pfirstae);
}
}
/*
* CGenUndoBuilder::SetNameID (idName)
*
* @mfunc
* Allows a name to be assigned to this anti-event collection.
* The ID should be an index that can be used to retrieve a
* language specific string (like "Paste"). This string is
* typically composed into undo menu items (i.e. "Undo Paste").
*/
void CGenUndoBuilder::SetNameID(
UNDONAMEID idName ) //@parm the name ID for this undo operation
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::SetNameID");
// don't delegate to the higher undobuilder, even if it exists. The
// original name should win in re-entrancy cases.
_idName = idName;
}
/*
* CGenUndoBuilder::AddAntiEvent (pae)
*
* @mfunc
* Adds an anti-event to the end of the list
*
* @rdesc NOERROR
*/
HRESULT CGenUndoBuilder::AddAntiEvent(
IAntiEvent *pae ) //@parm the anti-event to add
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::AddAntiEvent");
if( _publdrPrev )
{
return _publdrPrev->AddAntiEvent(pae);
}
pae->SetNext(_pfirstae);
_pfirstae = pae;
return NOERROR;
}
/*
* CGenUndoBuilder::GetTopAntiEvent
*
* @mfunc Gets the top anti-event for this context.
*
* @comm The current context can be either the current
* operation *or* to a previous operation if we are in
* merge typing mode.
*
* @rdesc the top anti-event
*
*/
IAntiEvent *CGenUndoBuilder::GetTopAntiEvent()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::GetTopAntiEvent");
if( _publdrPrev )
{
Assert(_pfirstae == NULL);
return _publdrPrev->GetTopAntiEvent();
}
if( !_pfirstae && _pundo )
{
return _pundo->GetMergeAntiEvent();
}
return _pfirstae;
}
/*
* CGenUndoBuilder::Done
*
* @mfunc
* puts the combined anti-events (if any) into the undo stack
*/
HRESULT CGenUndoBuilder::Done( void )
{
HRESULT hr = NOERROR;
DWORD dwLim = DEFAULT_UNDO_SIZE;
IUndoMgr *predo;
IAntiEvent *paetemp;
if( _publdrPrev )
{
Assert(_pfirstae == NULL);
return NOERROR;
}
if( _ped->GetDetectURL() )
{
_ped->GetDetectURL()->ScanAndUpdate(this);
}
// if nothing changed, discard any selection anti-events
// or other no-op actions.
if( !_ped->GetCallMgr()->GetChangeEvent() )
{
Discard();
return NOERROR;
}
if( _pfirstae )
{
if( !_pundo )
{
// yikes! There is no undo stack; better create one.
// if we are a redo guy, we should create a redo
// stack the size of the undo stack
if( _fRedo )
{
Assert(_ped->GetUndoMgr());
dwLim = _ped->GetUndoMgr()->GetUndoLimit();
}
_pundo = _ped->CreateUndoMgr(dwLim, _fRedo ? US_REDO : US_UNDO );
// FUTURE: A NULL ptr returned from CreateUndoMgr means either
// we are out of memory, or the undo limit is set to 0. For the
// latter case, we have collected AE's to push onto a non-existent
// undo stack. It may be more efficient to not generate
// the AE's at all when the undo limit is 0.
if(!_pundo)
{
goto CleanUp;
}
}
// we may need to flush the redo stack if we are adding
// more anti-events to the undo stack *AND* we haven't been
// told not to flush the redo stack. The only time we won't
// flush the redo stack is if it's the redo stack itself
// adding anti-events to undo.
if( !_fRedo )
{
// if our destination is the undo stack, then check
// to see if we should flush
if( !_fDontFlushRedo )
{
predo = _ped->GetRedoMgr();
if( predo )
{
predo->ClearAll();
}
}
}
#ifdef DEBUG
else
{
Assert(!_fDontFlushRedo);
}
#endif // DEBUG
// if we should enter into the group typing state, inform
// the undo manager. Note that we only do this *iff*
// there is actually some anti-event to put in the undo
// manager. This makes the undo manager easier to implement
if( _fStartGroupTyping )
{
_pundo->StartGroupTyping();
}
hr = _pundo->PushAntiEvent( _idName, _pfirstae );
// the change event flag should be set if we're adding
// undo items! If this test is true, it probably means
// the somebody earlier in the call stack sent change
// notifiations (either via SendAllNotifications or
// the CAutonotify class) _before_ this undo context
// was committed _or_ it means that we were re-entered
// in some way that was not handled properly.
//
// Needless to say, this is not an ideal state.
CleanUp:
Assert(_ped->GetCallMgr()->GetChangeEvent());
paetemp = _pfirstae;
_pfirstae = NULL;
CommitAEList(paetemp, _ped);
if(!_pundo || hr != NOERROR)
{
// Either we failed to add the AE's to the undo stack
// or the undo limit is 0 in which case there won't be
// an undo stack to push the AE's onto.
DestroyAEList(paetemp);
}
}
return hr;
}
/*
* CGenUndoBuilder::Discard
*
* @mfunc
* Gets rid of any anti-events that we may be hanging onto without
* executing or committing them. Typically used for recovering
* from certain failure or re-entrancy scenarios. Note that
* an _entire_ anti-event chain will be removed in this fashion.
*
* @rdesc void
*/
void CGenUndoBuilder::Discard(void)
{
if( _pfirstae )
{
DestroyAEList(_pfirstae);
_pfirstae = NULL;
}
else if( _publdrPrev )
{
_publdrPrev->Discard();
}
}
/*
* CGenUndoBuilder::StartGroupTyping
*
* @mfunc
* hangs onto the the fact that group typing should start.
* We'll forward the the state transition to the undo manager
* only if an anti-event is actually added to the undo manager.
*
* @devnote
* group typing is disabled for redo stacks.
*/
void CGenUndoBuilder::StartGroupTyping()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StartGroupTyping");
_fStartGroupTyping = TRUE;
}
/*
* CGenUndoBuilder::StopGroupTyping
*
* @mfunc
* forwards a stop grouped typing to the undo manager
*/
void CGenUndoBuilder::StopGroupTyping()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StopGroupTyping");
if( _pundo )
{
_pundo->StopGroupTyping();
}
}
//
// CUndoStackGuard IMPLEMENTATION
//
/*
* CUndoStackGuard::CUndoStackGuard
*
* @mfunc Constructor. Registers this object with the call manager
*
* @rdesc void
*/
CUndoStackGuard::CUndoStackGuard(
CTxtEdit *ped) //@parm the edit context
{
_ped = ped;
_fReEntered = FALSE;
_hr = NOERROR;
ped->GetCallMgr()->RegisterComponent(this, COMP_UNDOGUARD);
}
/*
* CUndoStackGuard::~CUndoStackGuard
*
* @mfunc Destructor. Revokes the registration of this object
* with the call manager
*
* @rdesc void
*/
CUndoStackGuard::~CUndoStackGuard()
{
_ped->GetCallMgr()->RevokeComponent(this);
}
/*
* CUndoStackGuard::SafeUndo
*
* @mfunc Loops through the given list of anti-events, invoking
* undo on each.
*
* @rdesc HRESULT, from the undo actions
*
* @devnote This routine is coded so that OnEnterContext can pick up
* and continue the undo operation should we become re-entered
*/
HRESULT CUndoStackGuard::SafeUndo(
IAntiEvent *pae, //@parm the start of the anti-event list
IUndoBuilder *publdr) //@parm the undo builder to use
{
HRESULT hr;
_publdr = publdr;
while( pae )
{
_paeNext = pae->GetNext();
hr = pae->Undo(_ped, publdr);
// save the first returned error.
if( hr != NOERROR && _hr == NOERROR)
{
_hr = hr;
}
pae = (IAntiEvent *)_paeNext;
}
return _hr;
}
/*
* CUndoStackGuard::OnEnterContext
*
* @mfunc Handle re-entrancy during undo operations.
*
* @rdesc void
*
* @devnote If this method is called, it's pretty serious. In general,
* we shoud never be re-entered while processing undo stuff.
* However, to ensure that, block the incoming call and process
* the remaining actions.
*/
void CUndoStackGuard::OnEnterContext()
{
TRACEWARNSZ("ReEntered while processing undo. Blocking call and");
TRACEWARNSZ(" attempting to recover.");
_fReEntered = TRUE;
SafeUndo((IAntiEvent *)_paeNext, _publdr);
}
//
// PUBLIC helper functions
//
/*
* @func DestroyAEList | Destroys a list of anti-events
*
* @rdesc void
*/
void DestroyAEList(
IAntiEvent *pae) //@parm the anti-event from which to start
{
IAntiEvent *pnext;
while( pae )
{
pnext = pae->GetNext();
pae->Destroy();
pae = pnext;
}
}
/*
* @func CommitAEList | Calls OnCommit to the given list of anti-events
*
* @rdesc void
*/
void CommitAEList(
IAntiEvent *pae, //@parm the anti-event from which to start
CTxtEdit *ped) //@parm the edit context
{
IAntiEvent *pnext;
while( pae )
{
pnext = pae->GetNext();
pae->OnCommit(ped);
pae = pnext;
}
}
/*
* @func HandleSelectionAEInfo | Tries to merge the given info with
* the existing undo context; if that fails, then it allocates
* a new selection anti-event to handle the info
*/
HRESULT HandleSelectionAEInfo(
CTxtEdit *ped, //@parm the edit context
IUndoBuilder *publdr, //@parm the undo context
LONG cp, //@parm the cp to use for the sel ae
LONG cch, //@parm the signed selection extension
LONG cpNext, //@parm the cp to use for the AE of the AE
LONG cchNext, //@parm the cch to use for the AE of the AE
SELAE flags) //@parm controls how to intepret the info
{
IAntiEvent *pae;
Assert(publdr);
pae = publdr->GetTopAntiEvent();
// first see if we can merge the selection info into any existing
// anti-events. Note that the selection anti-event may be anywhere
// in the list, so go through them all
if( pae )
{
SelRange sr;
sr.cp = cp;
sr.cch = cch;
sr.cpNext = cpNext;
sr.cchNext = cchNext;
sr.flags = flags;
while( pae )
{
if( pae->MergeData(MD_SELECTIONRANGE, (void *)&sr) == NOERROR )
{
break;
}
pae = pae->GetNext();
}
if( pae )
{
return NOERROR;
}
}
// oops; can't do a merge. Go ahead and create a new anti-event.
Assert(!pae);
pae = gAEDispenser.CreateSelectionAE(ped, cp, cch, cpNext, cchNext);
if( pae )
{
publdr->AddAntiEvent(pae);
return NOERROR;
}
return E_OUTOFMEMORY;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -