📄 m_undo.cpp
字号:
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*
* @doc INTERNAL
*
* @module M_UNDO.C |
*
* Purpose:
* Implementation of the global mutli-undo stack
*
* Author:
* alexgo 3/25/95
*/
#include "_common.h"
#include "_m_undo.h"
#include "_edit.h"
#include "_disp.h"
#include "_urlsup.h"
#include "_antievt.h"
ASSERTDATA
//
// PUBLIC METHODS
//
/*
* CUndoStack::CUndoStack (ped)
*
* @mfunc Constructor
*/
CUndoStack::CUndoStack(
CTxtEdit *ped, //@parm the CTxtEdit parent
DWORD & rdwLim, //@parm the initial limit
USFlags flags ) //@parm flags for this undo stack
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::CUndoStack");
_ped = ped;
_prgActions = NULL;
_index = 0;
_dwLim = 0;
//we should be creating an undo stack if there's nothing to
//put in it!
Assert(rdwLim);
SetUndoLimit(rdwLim);
if( flags & US_REDO )
{
_fRedo = TRUE;
}
}
/*
* CUndoStack::~CUndoStack
*
* @mfunc Destructor
*
* @comm
* deletes any remaining anti-events. The anti event dispenser
* should *not* clean up because of this!!
*/
CUndoStack::~CUndoStack()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::~CUndoStack");
// clear out any remaining antievents
ClearAll();
delete _prgActions;
}
/*
* CUndoStack::Destroy
*
* @mfunc
* deletes this instance
*/
void CUndoStack::Destroy()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Destroy");
delete this;
}
/*
* CUndoStack::SetUndoLimit (dwLim)
*
* @mfunc
* allows the undo stack to be enlarged or reduced
*
* @rdesc
* the size to which the stack is actually set.
*
* @comm
* the algorithm we use is the following: <nl>
* try to allocate space for the requested size.
* if there's not enough memory then we try to recover
* with the largest block possible.
*
* if the requested size is bigger than the default,
* and the current size is less than the default, go
* ahead and try to allocate the default.
*
* if that fails then just stick with the existing stack
*/
DWORD CUndoStack::SetUndoLimit(
DWORD dwLim ) //@parm the new undo limit. May not
// be zero
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::SetUndoLimit");
UndoAction *prgnew = NULL;
// if the undo limit is zero, we should get rid of the entire
// undo stack instead.
Assert(dwLim);
if( _fSingleLevelMode )
{
// if fSingleLevelMode is on, we can't be the redo stack
Assert(_fRedo == FALSE);
if( dwLim != 1 )
{
TRACEERRORSZ("Trying to grow/shrink the undo buffer while in"
"single level mode");
dwLim = 1;
}
}
prgnew = new UndoAction[dwLim];
if( prgnew )
{
TransferToNewBuffer(prgnew, dwLim);
}
else if( dwLim > DEFAULT_UNDO_SIZE && _dwLim < DEFAULT_UNDO_SIZE )
{
// we are trying to grow past the default but failed. So
// try to allocate the default
prgnew = new UndoAction[DEFAULT_UNDO_SIZE];
if( prgnew )
{
TransferToNewBuffer(prgnew, DEFAULT_UNDO_SIZE);
}
}
// in either success or failure, _dwLim will be set correctly.
return _dwLim;
}
/*
* CUndoStack::GetUndoLimit
*
* @mfunc
* gets the current limit size
*
* @rdesc
* the current undo limit
*/
DWORD CUndoStack::GetUndoLimit()
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetUndoLimit");
return _dwLim;
}
/*
* CUndoStack::PushAntiEvent (pae)
*
* @mfunc
* adds an undoable event to the event stack
*
* @rdesc HRESULT
*
* @comm
* Algorithm:
*
* if merging is set, then we to merge the given anti-event
* list *into* the current list (assuming it's a typing
* undo action).
*
*/
HRESULT CUndoStack::PushAntiEvent(
UNDONAMEID idName, //@parm name for this AE collection
IAntiEvent *pae ) //@parm the AE collection
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PushAntiEvent");
// Fix for WinCEOS RAID #12736
if (_ped->_DeleteBeforeConvert == 2) {
_ped->_DeleteBeforeConvert = 0;
idName = UID_TYPING;
_fMerge = TRUE;
}
if (_ped->_DeleteBeforeConvert == 1) {
_ped->_DeleteBeforeConvert = 2;
idName = UID_TYPING;
}
// _index should be at next available position
if( !_fMerge )
{
// clear out any existing event
if( _prgActions[_index].pae != NULL )
{
DestroyAEList(_prgActions[_index].pae);
_prgActions[_index].pae = NULL;
}
if( _fRedo )
{
_ped->GetCallMgr()->SetNewRedo();
}
else
{
_ped->GetCallMgr()->SetNewUndo();
}
}
if( _fMerge )
{
IAntiEvent *paetemp = pae, *paeNext;
DWORD i = GetPrev();
// if these asserts fail, then somebody did not call
// StopGroupTyping
Assert(_prgActions[i].id == idName);
Assert(idName == UID_TYPING);
// put the existing anti-event chain onto the *end* of the
// current one.
while( (paeNext = paetemp->GetNext()) != NULL )
{
paetemp = paeNext;
}
paetemp->SetNext(_prgActions[i].pae);
_index = i;
// fall through!
}
else if( _fGroupTyping )
{
// in this case, we are *starting* a group typing session.
// Any subsequent push'es of anti events should be merged
_fMerge = TRUE;
// fall through!!
}
_prgActions[_index].pae = pae;
_prgActions[_index].id = idName;
Next();
return NOERROR;
}
/*
* CUndoStack::PopAndExecuteAntiEvent( void )
*
* @mfunc
* Undo! Takes the most recent anti-event and executes it
*
* @rdesc HRESULT from invoking the anti-events (AEs)
*/
HRESULT CUndoStack::PopAndExecuteAntiEvent(
DWORD dwDoToCookie) //@parm if non-NULL, undo up to this point.
{
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PopAndExecuteAntiEvent");
HRESULT hresult = NOERROR;
IAntiEvent *pae, *paeDoTo;
DWORD i, j;
CCallMgr *pcallmgr = _ped->GetCallMgr();
// we need to check to see if there are any non-empty undo builders
// higher on the stack. In this case, we have been re-entered
if( pcallmgr->IsReEntered() )
{
IUndoBuilder *publdr;
// there are two cases to handle: we are invoking redo or we
// are invoking undo. If we are invoking undo and there are
// existing undo actions in the undo builder, then simply commit
// those actions and undo them. We can assert in this case
// that the redo stack is empty.
//
// In the second case if we are invoking redo while there are
// undo actions in progress, simply cancel the call. When the
// undo actions are added, they will clear the redo stack.
//
// We never need to check for a redo builder as that _only_
// gets created in this routine and it's use is carefully guarded.
publdr = (CGenUndoBuilder *)pcallmgr->GetComponent(COMP_UNDOBUILDER);
// commit the anti-events to this undo stack, so that we will simply
// undo them first.
if( publdr )
{
TRACEWARNSZ("Undo/Redo Invoked with uncommitted anti-events");
TRACEWARNSZ(" Recovering....");
if( _fRedo )
{
// if we are the redo stack, simply fail the redo call
return NOERROR;
}
else
{
// just commit the anti-events and the routine below
// will take of the rest.
publdr->Done();
}
}
}
// if we are in single level mode, check to see if our current buffer is
// empty. If so, simply delegate to the redo stack if it exists. We only
// support this mode for dwDoToCookies being NULL. Note that we can't call
// CanUndo here as it will consider the redo stack as well
if( _fSingleLevelMode && !_prgActions[GetPrev()].pae )
{
Assert(_fRedo == FALSE);
Assert(dwDoToCookie == 0);
if( _ped->GetRedoMgr() )
{
return _ped->GetRedoMgr()->PopAndExecuteAntiEvent(0);
}
// nothing to redo && nothing to do here; don't bother continuing
return NOERROR;
}
// this next bit of logic is tricky. What is says is create
// an undo builder for the stack *opposite* of the current one
// (namely, undo actions go on the redo stack and vice versa).
// Also, if we are the redo stack, then we don't want to flush
// the redo stack as anti-events are added to the undo stack.
CGenUndoBuilder undobldr(_ped,
(!_fRedo ? UB_REDO : UB_DONTFLUSHREDO) | UB_AUTOCOMMIT);
// obviously, we can't be grouping typing if we're undoing!
StopGroupTyping();
// _index by default points to the next available slot
// so we need to back up to the previous one.
Prev();
// do some verification on the cookie--make sure it's one
// of ours
paeDoTo = (IAntiEvent *)dwDoToCookie;
if( paeDoTo != NULL )
{
for( i = 0, j = _index; i < _dwLim; i++ )
{
if( IsCookieInList(_prgActions[j].pae, (IAntiEvent *)paeDoTo) )
{
paeDoTo = _prgActions[j].pae;
break;
}
// go backwards through the ring buffer; typically
// paeDoTo will be "close" to the top
if( !j )
{
j = _dwLim - 1;
}
else
{
j--;
}
}
if( i == _dwLim )
{
TRACEERRORSZ("Invalid Cookie passed into Undo; cookie ignored");
hresult = E_INVALIDARG;
paeDoTo = NULL;
}
}
else
{
paeDoTo = _prgActions[_index].pae;
}
undobldr.SetNameID(_prgActions[_index].id);
while( paeDoTo )
{
CUndoStackGuard guard(_ped);
pae = _prgActions[_index].pae;
Assert(pae);
// fixup our state _before_ calling Undo, so
// that we can handle being re-entered.
_prgActions[_index].pae = NULL;
hresult = guard.SafeUndo(pae, &undobldr);
DestroyAEList(pae);
if( pae == paeDoTo || guard.WasReEntered() )
{
paeDoTo = NULL;
}
Prev();
}
// put _index at the next unused slot
Next();
return hresult;
}
/*
* CUndoStack::GetNameIDFromTopAE
*
* @mfunc
* retrieves the name of the most recent undo-able operation
*
* @rdesc the name ID of the most recent collection of anti-events
*/
UNDONAMEID CUndoStack::GetNameIDFromAE(
DWORD dwAECookie) //@parm the anti-event whose name is desired;
// 0 for the the top
{
IAntiEvent *pae = (IAntiEvent *)dwAECookie;
DWORD i, j = GetPrev(); // _index by default points to the next available
// slot
TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetNameIDFromTopAE");
if( pae == NULL )
{
pae = _prgActions[j].pae;
}
if( _fSingleLevelMode && !pae)
{
// if fSingleLevelMode is on, we can't be the redo stack
Assert(_fRedo == FALSE);
// if pae is NULL, our answer may be on the redo stack. Note that
// we if somebody tries to pass in a cookie while in SingleLevelMode,
// they won't be able to get actions off the redo stack.
if( _ped->GetRedoMgr() )
{
return _ped->GetRedoMgr()->GetNameIDFromAE(0);
}
}
for( i = 0; i < _dwLim; i++ )
{
if( _prgActions[j].pae == pae )
{
return _prgActions[j].id;
}
if( j == 0 )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -