The design for this system is based on Visual C++/MFC code by Jim Beveridge in Dr. Dobb's Journal, February 1996. The following components are the elements of a powerful and flexible undo/redo mechanism. The system is based on the abstract notion of tasks.
Each task has 4 methods, applications are expected to classify all undoable tasks and to subclass the C_TASK class to provide task-specific behaviors:
The undo manager maintains the list and current undo/redo position, and under the direction of the application's command manager, will carry out undo/redo of tasks:
Since solipeg is relatively simple, the Solipeg C_JUMP class, relegates all of its work to the engine, sending the engine an O_ENG_JUMP message. In more complicated applications with more tasks, it is more likely that the engine will also provide more atomic or elemental services, which the task would call several of. For example, the code for O_ENG_JUMP is relatively simple, yet accesses much private engine data:
METHOD VOID soleng_eng_jump( PR_SOLENG * self,
INT x,
INT y,
INT iDir ) {
BOARD[x][y] = S_EMPTY ;
BOARD[x + vectors[iDir].x][y + vectors[iDir].y] = S_EMPTY ;
BOARD[x + 2 * vectors[iDir].x][y + 2 * vectors[iDir].y] = S_FULL ;
p_send3(w_ws->wserv.com, O_SCM_SPOS, XY(x + 2 * vectors[iDir].x, y + 2 * vectors[iDir].y)) ;
p_send2(w_ws->wserv.cli, O_WN_CURSOR) ;
p_send2(w_ws->wserv.com, O_SCM_UPDATE) ;
}
! Abstract type to represent tasks
CLASS task root
{
DEFER task_init
DEFER task_do
DEFER task_undo
DEFER task_text
}
! The only kind of task in Solipeg is a jump
CLASS jump task
{
REPLACE task_init
REPLACE task_do
REPLACE task_undo
REPLACE task_text
PROPERTY
{
UBYTE x ;
UBYTE y ;
UBYTE dir ;
}
}
! The task undo manager
CLASS undoman root
{
ADD uman_init
ADD uman_submit
ADD uman_undo
ADD uman_redo
ADD uman_canredo
ADD uman_canundo
ADD uman_redotxt
ADD uman_undotxt
ADD uman_clear
PROPERTY 1
{
PR_VAFLAT * pList ;
VOID * pEngine ;
INT UndoIndex ;
}
}
// UNDOMAN.C - Undo manager
// This file includes methods for:
// C_UNDOMAN: the undo manager
#define this (self->undoman)
// Macro: TASK_PTR(index)
// Returns: &TASK[index] (A PR_TASK * in VAFLAT)
#define TASK_PTR(index) (*(PR_TASK **)p_send3(this.pList, O_VA_PREC, index))
// Initaliaze the undoman object, using category and class specified for
// the list manager
METHOD VOID undoman_uman_init( PR_UNDOMAN * self,
VOID * pEngine,
INT iCat,
INT iClass ) {
this.pEngine = pEngine ;
this.pList = f_newsend( iCat,
iClass,
O_VA_INIT,
sizeof(PR_TASK *),
10 ) ;
this.UndoIndex = 0 ;
}
// Submit a task - the task is sent a DO message
METHOD VOID undoman_uman_submit( PR_UNDOMAN * self,
PR_TASK * pTask ) {
INT nCount ;
nCount = p_send2(this.pList, O_VA_COUNT) ;
while ( this.UndoIndex < nCount ) {
nCount-- ;
p_send2(TASK_PTR(nCount), O_DESTROY) ;
p_send3(this.pList, O_VA_DELETE, nCount) ;
}
p_send3(this.pList, O_VA_APPEND, &pTask) ;
this.UndoIndex++ ;
p_send3(pTask, O_TASK_DO, this.pEngine) ;
}
// Undo from the current position
METHOD VOID undoman_uman_undo(PR_UNDOMAN *self) {
if ( p_send2(self, O_UMAN_CANUNDO) ) {
this.UndoIndex-- ;
p_send3(TASK_PTR(this.UndoIndex), O_TASK_UNDO, this.pEngine) ;
}
}
// Redo from the current position
METHOD VOID undoman_uman_redo(PR_UNDOMAN *self) {
if ( p_send2(self, O_UMAN_CANREDO) ) {
p_send3(TASK_PTR(this.UndoIndex), O_TASK_DO, this.pEngine) ;
this.UndoIndex++ ;
}
}
// Is Redo allowed?
METHOD INT undoman_uman_canredo(PR_UNDOMAN *self) {
return( this.UndoIndex < p_send2(this.pList, O_VA_COUNT) ) ;
}
// Is Undo allowed?
METHOD INT undoman_uman_canundo(PR_UNDOMAN *self) {
return( this.UndoIndex > 0 ) ;
}
// Get text describing the Redo operation
METHOD VOID undoman_uman_redotxt(PR_UNDOMAN *self, TEXT *str) {
if ( p_send2(self, O_UMAN_CANREDO) ) {
p_send3(TASK_PTR(this.UndoIndex), O_TASK_TEXT, str) ;
}
else {
*str = 0 ;
}
}
// Get text describing the Undo operation
METHOD VOID undoman_uman_undotxt(PR_UNDOMAN *self, TEXT *str) {
if ( p_send2(self, O_UMAN_CANUNDO) ) {
p_send3(TASK_PTR(this.UndoIndex - 1), O_TASK_TEXT, str) ;
}
else {
*str = 0 ;
}
}
// Clear the undo history
// Fixed for Solipeg 2.1
METHOD VOID undoman_uman_clear(PR_UNDOMAN *self) {
this.UndoIndex = p_send2(this.pList, O_VA_COUNT) ;
while ( this.UndoIndex > 0 ) {
this.UndoIndex-- ;
p_send2(TASK_PTR(this.UndoIndex), O_DESTROY) ;
}
p_send2(this.pList, O_VA_RESET) ;
}
// SOLTASK.C - Solipeg tasks
// This file includes methods for:
// Solipeg tasks
#undef this
#define this (self->jump)
// Initialize an object of class jump
METHOD VOID jump_task_init( PR_JUMP * self,
INT x,
INT y,
INT dir ) {
this.x = x ;
this.y = y ;
this.dir = dir ;
}
// Do a jump
METHOD VOID jump_task_do( PR_JUMP * self,
PR_SOLENG * engine ) {
p_send5(engine, O_ENG_JUMP, this.x, this.y, this.dir) ;
}
// Undo a jump
METHOD VOID jump_task_undo( PR_JUMP * self,
PR_SOLENG * engine ) {
p_send5(engine, O_ENG_UNJUMP, this.x, this.y, this.dir) ;
}
// Return a suitable description of a jump
METHOD VOID jump_task_text( PR_JUMP * self,
TEXT * str ) {
hLoadResBuf(RS_TASK_JUMP, str) ;
}
Back to My home page