/** \file cundo.c * Undo / redo functions. */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "cselect.h" #include "custom.h" #include "fileio.h" #include "i18n.h" #include "messages.h" #include "paths.h" #include "track.h" #include "trackx.h" #include "cundo.h" /***************************************************************************** * * UNDO * */ static int log_undo = 0; /**< loglevel, can only be set at compile time */ #define UNDO_STACK_SIZE (10) typedef struct { wIndex_t modCnt; wIndex_t newCnt; wIndex_t delCnt; wIndex_t trackCount; track_p newTrks; long undoStart; long undoEnd; long redoStart; long redoEnd; BOOL_T needRedo; track_p * oldTail; track_p * newTail; char * label; } undoStack_t, *undoStack_p; static undoStack_t undoStack[UNDO_STACK_SIZE]; static wIndex_t undoHead = -1; static BOOL_T undoActive = FALSE; static int doCount = 0; static int undoCount = 0; static char ModifyOp = 1; static char DeleteOp = 2; static BOOL_T recordUndo = 1; #define UASSERT( ARG, VAL ) \ if (!(ARG)) return UndoFail( #ARG, VAL, __FILE__, __LINE__ ) #define INC_UNDO_INX( INX ) {\ if (++INX >= UNDO_STACK_SIZE) \ INX = 0; \ } #define DEC_UNDO_INX( INX ) {\ if (--INX < 0) \ INX = UNDO_STACK_SIZE-1; \ } #define BSTREAM_SIZE (4096) typedef char streamBlocks_t[BSTREAM_SIZE]; typedef streamBlocks_t *streamBlocks_p; typedef struct { dynArr_t stream_da; long startBInx; long end; long curr; } stream_t; typedef stream_t *stream_p; static stream_t undoStream; static stream_t redoStream; static BOOL_T needAttachTrains = FALSE; void UndoResume( void ) { LOG( log_undo, 1, ( "UndoResume()\n" ) ) undoActive = TRUE; } void UndoSuspend( void ) { LOG( log_undo, 1, ( "UndoSuspend()\n" ) ) undoActive = FALSE; } static void DumpStream( FILE * outf, stream_p stream, char * name ) { long binx; long i, j; long off; streamBlocks_p blk; int zeroCnt; static char zeros[16] = { 0 }; fprintf( outf, "Dumping %s\n", name ); off = stream->startBInx*BSTREAM_SIZE; zeroCnt = 0; for ( binx=0; binxstream_da.cnt; binx++ ) { blk = DYNARR_N( streamBlocks_p, stream->stream_da, binx ); for ( i=0; i 2 ) fprintf( outf, "%6.6lx 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n", off-16 ); } static BOOL_T UndoFail( char * cause, long val, char * fileName, int lineNumber ) { int inx, cnt; undoStack_p us; FILE * outf; time_t clock; char *temp; NoticeMessage( MSG_UNDO_ASSERT, _("Ok"), NULL, fileName, lineNumber, val, val, cause ); MakeFullpath(&temp, workingDir, sUndoF, NULL); outf = fopen( temp, "a+" ); free(temp); if ( outf == NULL ) { NoticeMessage( MSG_OPEN_FAIL, _("Ok"), NULL, _("Undo Trace"), temp, strerror(errno) ); return FALSE; } time( &clock ); fprintf(outf, "\nUndo Assert: %s @ %s:%d (%s)\n", cause, fileName, lineNumber, ctime(&clock) ); fprintf(outf, "Val = %ld(%lx)\n", val, val ); fprintf(outf, "to_first=%lx, to_last=%lx\n", (long)to_first, (long)to_last ); fprintf(outf, "undoHead=%d, doCount=%d, undoCount=%d\n", undoHead, doCount, undoCount ); if (undoHead >= 0 && undoHead < UNDO_STACK_SIZE) inx=undoHead; else inx = 0; for (cnt=0; cntmodCnt, us->newCnt, us->delCnt, us->trackCount, (long)us->newTrks, (long)us->oldTail, (long)us->newTail, us->undoStart, us->undoEnd, us->redoStart, us->redoEnd, us->needRedo ); INC_UNDO_INX(inx); } fprintf( outf, "Undo: SBI:%ld E:%lx C:%lx SC:%d SM:%d\n", undoStream.startBInx, undoStream.end, undoStream.curr, undoStream.stream_da.cnt, undoStream.stream_da.max ); fprintf( outf, "Redo: SBI:%ld E:%lx C:%lx SC:%d SM:%d\n", redoStream.startBInx, redoStream.end, redoStream.curr, redoStream.stream_da.cnt, redoStream.stream_da.max ); DumpStream( outf, &undoStream, "undoStream" ); DumpStream( outf, &redoStream, "redoStream" ); Rdump(outf); fclose( outf ); UndoClear(); UndoStart( "undoFail", "undoFail" ); return FALSE; } BOOL_T ReadStream( stream_t * stream, void * ptr, int size ) { long binx, boff, brem; streamBlocks_p blk; if ( stream->curr+size > stream->end ) { UndoFail( "Overrun on stream", (long)(stream->curr+size), __FILE__, __LINE__ ); return FALSE; } LOG( log_undo, 5, ( "ReadStream( , %lx, %d ) %ld %ld %ld\n", (long)ptr, size, stream->startBInx, stream->curr, stream->end ) ) binx = stream->curr/BSTREAM_SIZE; boff = stream->curr%BSTREAM_SIZE; stream->curr += size; binx -= stream->startBInx; brem = BSTREAM_SIZE - boff; while ( brem < size ) { UASSERT( binx>=0 && binx < stream->stream_da.cnt, binx ); blk = DYNARR_N( streamBlocks_p, stream->stream_da, binx ); memcpy( ptr, &(*blk)[boff], (size_t)brem ); ptr = (char*)ptr + brem; size -= (int)brem; binx++; boff = 0; brem = BSTREAM_SIZE; } if (size) { UASSERT( binx>=0 && binx < stream->stream_da.cnt, binx ); blk = DYNARR_N( streamBlocks_p, stream->stream_da, binx ); memcpy( ptr, &(*blk)[boff], size ); } return TRUE; } BOOL_T WriteStream( stream_p stream, void * ptr, int size ) { long binx, boff, brem; streamBlocks_p blk; LOG( log_undo, 5, ( "WriteStream( , %lx, %d ) %ld %ld %ld\n", (long)ptr, size, stream->startBInx, stream->curr, stream->end ) ) if (size == 0) return TRUE; binx = stream->end/BSTREAM_SIZE; boff = stream->end%BSTREAM_SIZE; stream->end += size; binx -= stream->startBInx; brem = BSTREAM_SIZE - boff; while ( size ) { if (boff==0) { UASSERT( binx == stream->stream_da.cnt, binx ); DYNARR_APPEND( streamBlocks_p, stream->stream_da, 10 ); blk = (streamBlocks_p)MyMalloc( sizeof *blk ); DYNARR_N( streamBlocks_p, stream->stream_da, binx ) = blk; } else { UASSERT( binx == stream->stream_da.cnt-1, binx ); blk = DYNARR_N( streamBlocks_p, stream->stream_da, binx ); } if (size > brem) { memcpy( &(*blk)[boff], ptr, (size_t)brem ); ptr = (char*)ptr + brem; size -= (size_t)brem; binx++; boff = 0; brem = BSTREAM_SIZE; } else { memcpy( &(*blk)[boff], ptr, size ); break; } } return TRUE; } BOOL_T TrimStream( stream_p stream, long off ) { long binx, cnt, inx; streamBlocks_p blk; LOG( log_undo, 3, ( "TrimStream( , %ld )\n", off ) ) binx = off/BSTREAM_SIZE; cnt = binx-stream->startBInx; if (recordUndo) Rprintf("Trim(%ld) %ld blocks (out of %d)\n", off, cnt, stream->stream_da.cnt); UASSERT( cnt >= 0 && cnt <= stream->stream_da.cnt, cnt ); if (cnt == 0) return TRUE; for (inx=0; inxstream_da, inx ); MyFree( blk ); } for (inx=cnt; inxstream_da.cnt; inx++ ) { DYNARR_N( streamBlocks_p, stream->stream_da, inx-cnt ) = DYNARR_N( streamBlocks_p, stream->stream_da, inx ); } stream->startBInx = binx; stream->stream_da.cnt -= (wIndex_t)cnt; UASSERT( stream->stream_da.cnt >= 0, stream->stream_da.cnt ); return TRUE; } void ClearStream( stream_p stream ) { long inx; streamBlocks_p blk; for (inx=0; inxstream_da.cnt; inx++) { blk = DYNARR_N( streamBlocks_p, stream->stream_da, inx ); MyFree( blk ); } stream->stream_da.cnt = 0; stream->startBInx = stream->end = stream->curr = 0; } BOOL_T TruncateStream( stream_p stream, long off ) { long binx, boff, cnt, inx; streamBlocks_p blk; LOG( log_undo, 3, ( "TruncateStream( , %ld )\n", off ) ) binx = off/BSTREAM_SIZE; boff = off%BSTREAM_SIZE; if (boff!=0) binx++; binx -= stream->startBInx; cnt = stream->stream_da.cnt-binx; if (recordUndo) Rprintf("Truncate(%ld) %ld blocks (out of %d)\n", off, cnt, stream->stream_da.cnt); UASSERT( cnt >= 0 && cnt <= stream->stream_da.cnt, cnt ); if (cnt == 0) return TRUE; for (inx=binx; inxstream_da.cnt; inx++) { blk = DYNARR_N( streamBlocks_p, stream->stream_da, inx ); MyFree( blk ); } stream->stream_da.cnt = (wIndex_t)binx; stream->end = off; UASSERT( stream->stream_da.cnt >= 0, stream->stream_da.cnt ); return TRUE; } BOOL_T WriteObject( stream_p stream, char op, track_p trk ) { void * buff = NULL; long len = 0; if (!WriteStream( stream, &op, sizeof op ) || !WriteStream( stream, &trk, sizeof trk ) || !WriteStream( stream, trk, sizeof *trk ) || !WriteStream( stream, trk->endPt, trk->endCnt * sizeof trk->endPt[0] ) || !WriteStream( stream, trk->extraData, trk->extraSize )) return FALSE; /* Add a copy of the any type specific data before it is tampered with, for example */ StoreTrackData(trk,&buff,&len); if (!WriteStream( stream, &len, sizeof len )) return FALSE; if (len) if (!WriteStream( stream, buff, len )) return FALSE; return TRUE; } static BOOL_T ReadObject( stream_p stream, BOOL_T needRedo ) { track_p trk; track_t tempTrk; char op; if (!ReadStream( stream, &op, sizeof op )) return FALSE; if (!ReadStream( stream, &trk, sizeof trk )) return FALSE; if (needRedo) { if (!WriteObject( &redoStream, op, trk )) return FALSE; } if (!ReadStream( stream, &tempTrk, sizeof tempTrk )) return FALSE; if (tempTrk.endCnt != trk->endCnt) tempTrk.endPt = MyRealloc( trk->endPt, tempTrk.endCnt * sizeof tempTrk.endPt[0] ); else tempTrk.endPt = trk->endPt; if (!ReadStream( stream, tempTrk.endPt, tempTrk.endCnt * sizeof tempTrk.endPt[0] )) return FALSE; if (tempTrk.extraSize != trk->extraSize) tempTrk.extraData = MyRealloc( trk->extraData, tempTrk.extraSize ); else tempTrk.extraData = trk->extraData; if (!ReadStream( stream, tempTrk.extraData, tempTrk.extraSize )) return FALSE; long Addsize; void * tempBuff; /* Fix up pts to be as big as it was before -> because it may have changed since */ if (!ReadStream (stream, &Addsize, sizeof Addsize)) return FALSE; if (Addsize) { tempBuff = MyMalloc(Addsize); if (!ReadStream( stream, tempBuff, Addsize )) return FALSE; ReplayTrackData(&tempTrk, tempBuff, Addsize); MyFree(tempBuff); } RebuildTrackSegs(&tempTrk); //If we had an array of Segs - recreate it if (recordUndo) Rprintf( "Restore T%D(%d) @ %lx\n", trk->index, tempTrk.index, (long)trk ); tempTrk.index = trk->index; tempTrk.next = trk->next; if ( (tempTrk.bits&TB_CARATTACHED) != 0 ) needAttachTrains = TRUE; tempTrk.bits &= ~TB_TEMPBITS; *trk = tempTrk; if (!trk->deleted) ClrTrkElev( trk ); return TRUE; } static BOOL_T RedrawInStream( stream_p stream, long start, long end, BOOL_T draw ) { char op; track_p trk; track_t tempTrk; stream->curr = start; while (stream->curr < end ) { if (!ReadStream( stream, &op, sizeof op ) || !ReadStream( stream, &trk, sizeof trk ) || !ReadStream( stream, &tempTrk, sizeof tempTrk ) ) return FALSE; stream->curr += tempTrk.extraSize + tempTrk.endCnt*sizeof tempTrk.endPt[0];; long Addsize; if (!ReadStream( stream, &Addsize, sizeof Addsize )) return FALSE; stream->curr += Addsize; if (!trk->deleted) { if (draw) DrawNewTrack( trk ); else UndrawNewTrack( trk ); } } return TRUE; } static BOOL_T DeleteInStream( stream_p stream, long start, long end ) { char op; track_p trk; track_p *ptrk; track_t tempTrk; int delCount = 0; LOG( log_undo, 3, ( "DeleteInSteam( , %ld, %ld )\n", start, end ) ) stream->curr = start; while (stream->curr < end ) { if (!ReadStream( stream, &op, sizeof op )) return FALSE; UASSERT( op == ModifyOp || op == DeleteOp, (long)op ); if (!ReadStream( stream, &trk, sizeof trk ) || !ReadStream( stream, &tempTrk, sizeof tempTrk )) return FALSE; stream->curr += tempTrk.extraSize + tempTrk.endCnt*sizeof tempTrk.endPt[0]; long Addsize; if (!ReadStream( stream, &Addsize, sizeof Addsize )) return FALSE; stream->curr += Addsize; if (op == DeleteOp) { if (recordUndo) Rprintf( " Free T%D(%d) @ %lx\n", trk->index, tempTrk.index, (long)trk ); UASSERT( IsTrackDeleted(trk), (long)trk ); trk->index = -1; delCount++; } } if (delCount) { for (ptrk=&to_first; *ptrk; ) { if ((*ptrk)->index == -1) { trk = *ptrk; UASSERT( IsTrackDeleted(trk), (long)trk ); *ptrk = trk->next; FreeTrack(trk); } else { ptrk = &(*ptrk)->next; } } to_last = ptrk; } return TRUE; } static BOOL_T SetDeleteOpInStream( stream_p stream, long start, long end, track_p trk0 ) { char op; track_p trk; track_t tempTrk; long binx, boff; streamBlocks_p blk; stream->curr = start; while (stream->curr < end) { binx = stream->curr/BSTREAM_SIZE; binx -= stream->startBInx; boff = stream->curr%BSTREAM_SIZE; if (!ReadStream( stream, &op, sizeof op )) return FALSE; UASSERT( op == ModifyOp || op == DeleteOp, (long)op ); if (!ReadStream( stream, &trk, sizeof trk ) ) return FALSE; if (trk == trk0) { UASSERT( op == ModifyOp, (long)op ); blk = DYNARR_N( streamBlocks_p, stream->stream_da, binx ); memcpy( &(*blk)[boff], &DeleteOp, sizeof DeleteOp ); return TRUE; } if (!ReadStream( stream, &tempTrk, sizeof tempTrk )) return FALSE; stream->curr += tempTrk.extraSize + tempTrk.endCnt*sizeof tempTrk.endPt[0]; long Addsize; if (!ReadStream( stream, &Addsize, sizeof Addsize)) return FALSE; stream->curr += Addsize; } UASSERT( "Cannot find undo record to convert to DeleteOp", 0 ); return FALSE; } static void SetButtons( BOOL_T undoSetting, BOOL_T redoSetting ) { static BOOL_T undoButtonEnabled = FALSE; static BOOL_T redoButtonEnabled = FALSE; int index; static char undoHelp[STR_SHORT_SIZE]; static char redoHelp[STR_SHORT_SIZE]; if (undoButtonEnabled != undoSetting) { wControlActive( (wControl_p)undoB, undoSetting ); undoButtonEnabled = undoSetting; } if (redoButtonEnabled != redoSetting) { wControlActive( (wControl_p)redoB, redoSetting ); redoButtonEnabled = redoSetting; } if (undoSetting) { sprintf( undoHelp, _("Undo: %s"), undoStack[undoHead].label ); wControlSetBalloonText( (wControl_p)undoB, undoHelp ); } else { wControlSetBalloonText( (wControl_p)undoB, _("Undo last command") ); } if (redoSetting) { index = undoHead; INC_UNDO_INX(index); sprintf( redoHelp, _("Redo: %s"), undoStack[index].label ); wControlSetBalloonText( (wControl_p)redoB, redoHelp ); } else { wControlSetBalloonText( (wControl_p)redoB, _("Redo last undo") ); } } static track_p * FindParent( track_p trk, int lineNum ) { track_p *ptrk; ptrk = &to_first; while ( 1 ) { if ( *ptrk == trk ) return ptrk; if (*ptrk == NULL) break; ptrk = &(*ptrk)->next; } UndoFail( "Cannot find trk on list", (long)trk, "cundo.c", lineNum ); return NULL; } static int undoIgnoreEmpty = 0; void UndoStart( char * label, char * format, ... ) { static char buff[STR_SIZE]; va_list ap; track_p trk, next; undoStack_p us, us1; int inx; int usp; LOG( log_undo, 1, ( "UndoStart(%s) [%d] d:%d u:%d us:%ld\n", label, undoHead, doCount, undoCount, undoStream.end ) ) if (recordUndo) { va_start( ap, format ); vsprintf( buff, format, ap ); va_end( ap ); Rprintf( "Start(%s)[%d] d:%d u:%d us:%ld\n", buff, undoHead, doCount, undoCount, undoStream.end ); } if ( undoHead >= 0 ) { us = &undoStack[undoHead]; if ( us->modCnt == 0 && us->delCnt == 0 && us->newCnt == 0 ) { #ifndef WINDOWS #ifdef DEBUG printf( "undoStart noop: %s - %s\n", us->label?us->label:"<>", label?label:"<>" ); #endif #endif if ( undoIgnoreEmpty ) { us->label = label; return; } } } INC_UNDO_INX(undoHead); us = &undoStack[undoHead]; changed++; SetWindowTitle(); if (doCount == UNDO_STACK_SIZE) { if (recordUndo) Rprintf( " Wrapped N:%d M:%d D:%d\n", us->newCnt, us->modCnt, us->delCnt ); /* wrapped around stack */ /* if track saved in undoStream is deleted then really deleted since we can't get it back */ if (!DeleteInStream( &undoStream, us->undoStart, us->undoEnd )) return; /* strip off unused head of stream */ if (!TrimStream( &undoStream, us->undoEnd )) return; } else if (undoCount != 0) { if (recordUndo) Rprintf( " Undid N:%d M:%d D:%d\n", us->newCnt, us->modCnt, us->delCnt ); /* reusing an undid entry */ /* really delete all new tracks since this point */ for( inx=0,usp = undoHead; inxnewCnt ); for (trk=us1->newTrks; trk; trk=next) { if (recordUndo) Rprintf( " Free T%d @ %lx\n", trk->index, (long)trk ); /*ASSERT( IsTrackDeleted(trk) );*/ next = trk->next; FreeTrack( trk ); } INC_UNDO_INX(usp); } /* strip off unused tail of stream */ if (!TruncateStream( &undoStream, us->undoStart )) return; } us->label = label; us->modCnt = 0; us->newCnt = 0; us->delCnt = 0; us->undoStart = us->undoEnd = undoStream.end; ClearStream( &redoStream ); for ( inx=0; inxnewTrks = NULL; undoStack[undoHead].trackCount = trackCount; undoCount = 0; undoActive = TRUE; for (trk=to_first; trk; trk=trk->next ) { trk->modified = FALSE; trk->new = FALSE; } if (doCount < UNDO_STACK_SIZE) doCount++; SetButtons( TRUE, FALSE ); } BOOL_T UndoModify( track_p trk ) { undoStack_p us; if ( !undoActive ) return TRUE; if (trk == NULL) return TRUE; UASSERT(undoCount==0, undoCount); UASSERT(undoHead >= 0, undoHead); UASSERT(!IsTrackDeleted(trk), (long)trk); if (trk->modified || trk->new) return TRUE; LOG( log_undo, 2, ( " UndoModify( T%d, E%d, X%ld )\n", trk->index, trk->endCnt, trk->extraSize ) ) if ( (GetTrkBits(trk)&TB_CARATTACHED)!=0 ) needAttachTrains = TRUE; us = &undoStack[undoHead]; if (recordUndo) Rprintf( " MOD T%d @ %lx\n", trk->index, (long)trk ); if (!WriteObject( &undoStream, ModifyOp, trk )) return FALSE; us->undoEnd = undoStream.end; trk->modified = TRUE; us->modCnt++; return TRUE; } BOOL_T UndoDelete( track_p trk ) { undoStack_p us; if ( !undoActive ) return TRUE; LOG( log_undo, 2, ( " UndoDelete( T%d, E%d, X%ld )\n", trk->index, trk->endCnt, trk->extraSize ) ) if ( (GetTrkBits(trk)&TB_CARATTACHED)!=0 ) needAttachTrains = TRUE; us = &undoStack[undoHead]; if (recordUndo) Rprintf( " DEL T%d @ %lx\n", trk->index, (long)trk ); UASSERT( !IsTrackDeleted(trk), (long)trk ); if ( trk->modified ) { if (!SetDeleteOpInStream( &undoStream, us->undoStart, us->undoEnd, trk )) return FALSE; } else if ( !trk->new ) { if (!WriteObject( &undoStream, DeleteOp, trk )) return FALSE; us->undoEnd = undoStream.end; } else { track_p * ptrk; if (us->newTrks == trk) us->newTrks = trk->next; if (!(ptrk = FindParent( trk, __LINE__ ))) return FALSE; if (trk->next == NULL) { UASSERT( to_last == &(*ptrk)->next, (long)&(*ptrk)->next ); to_last = ptrk; } *ptrk = trk->next; FreeTrack( trk ); us->newCnt--; return TRUE; } trk->deleted = TRUE; us->delCnt++; return TRUE; } BOOL_T UndoNew( track_p trk ) { undoStack_p us; if (!undoActive) return TRUE; LOG( log_undo, 2, ( " UndoNew( T%d )\n", trk->index ) ) if (recordUndo) Rprintf( " NEW T%d @%lx\n", trk->index, (long)trk ); UASSERT(undoCount==0, undoCount); UASSERT(undoHead >= 0, undoHead); us = &undoStack[undoHead]; trk->new = TRUE; if (us->newTrks == NULL) us->newTrks = trk; us->newCnt++; return TRUE; } void UndoEnd( void ) { if (recordUndo) Rprintf( "End[%d] d:%d\n", undoHead, doCount ); /*undoActive = FALSE;*/ if ( needAttachTrains ) { AttachTrains(); needAttachTrains = FALSE; } UpdateAllElevations(); } void UndoClear( void ) { int inx; LOG( log_undo, 2, ( " UndoClear()\n" ) ) undoActive = FALSE; undoHead = -1; undoCount = 0; doCount = 0; ClearStream( &undoStream ); ClearStream( &redoStream ); for (inx=0; inxnewCnt, us->modCnt, us->delCnt ) ) if (recordUndo) Rprintf( "Undo[%d] d:%d u:%d N:%d M:%d D:%d\n", undoHead, doCount, undoCount, us->newCnt, us->modCnt, us->delCnt ); //redrawAll = (us->newCnt+us->modCnt) > incrementalDrawLimit; redrawAll = TRUE; if (!redrawAll) { for (trk=us->newTrks; trk; trk=trk->next ) UndrawNewTrack( trk ); RedrawInStream( &undoStream, us->undoStart, us->undoEnd, FALSE ); } if (us->needRedo) us->redoStart = us->redoEnd = redoStream.end; for (trk=us->newTrks; trk; trk=trk->next ) { if (recordUndo) Rprintf(" Deleting New Track T%d @ %lx\n", trk->index, (long)trk ); UASSERT( !IsTrackDeleted(trk), (long)trk ); trk->deleted = TRUE; } if (!(us->oldTail=FindParent(us->newTrks,__LINE__))) return FALSE; us->newTail = to_last; to_last = us->oldTail; *to_last = NULL; needAttachTrains = FALSE; undoStream.curr = us->undoStart; while ( undoStream.curr < us->undoEnd ) { if (!ReadObject( &undoStream, us->needRedo )) return FALSE; } if (us->needRedo) us->redoEnd = redoStream.end; us->needRedo = FALSE; if ( needAttachTrains ) { AttachTrains(); needAttachTrains = FALSE; } UpdateAllElevations(); if (!redrawAll) RedrawInStream( &undoStream, us->undoStart, us->undoEnd, TRUE ); else DoRedraw(); oldCount = trackCount; trackCount = us->trackCount; us->trackCount = oldCount; InfoCount( trackCount ); doCount--; undoCount++; DEC_UNDO_INX( undoHead ); AuditTracks( "undoUndo" ); SelectRecount(); SetButtons( doCount>0, TRUE ); wBalloonHelpUpdate(); wDrawDelayUpdate( mainD.d, FALSE ); return TRUE; } BOOL_T UndoRedo( void ) { undoStack_p us; wIndex_t oldCount; BOOL_T redrawAll; track_p trk; if (undoCount <= 0) { ErrorMessage( MSG_NO_REDO ); return FALSE; } ConfirmReset( FALSE ); wDrawDelayUpdate( mainD.d, TRUE ); INC_UNDO_INX( undoHead ); us = &undoStack[undoHead]; LOG( log_undo, 1, ( " undoRedo[%d] d:%d u:%d N:%d M:%d D:%d\n", undoHead, doCount, undoCount, us->newCnt, us->modCnt, us->delCnt ) ) if (recordUndo) Rprintf( "Redo[%d] d:%d u:%d N:%d M:%d D:%d\n", undoHead, doCount, undoCount, us->newCnt, us->modCnt, us->delCnt ); //redrawAll = (us->newCnt+us->modCnt) > incrementalDrawLimit; redrawAll = TRUE; if (!redrawAll) { RedrawInStream( &redoStream, us->redoStart, us->redoEnd, FALSE ); } for (trk=us->newTrks; trk; trk=trk->next ) { if (recordUndo) Rprintf(" Undeleting New Track T%d @ %lx\n", trk->index, (long)trk ); UASSERT( IsTrackDeleted(trk), (long)trk ); trk->deleted = FALSE; } UASSERT( us->newTail != NULL, (long)us->newTail ); *to_last = us->newTrks; to_last = us->newTail; UASSERT( (*to_last) == NULL, (long)*to_last ); RenumberTracks(); needAttachTrains = FALSE; redoStream.curr = us->redoStart; while ( redoStream.curr < us->redoEnd ) { if (!ReadObject( &redoStream, FALSE )) return FALSE; } if ( needAttachTrains ) { AttachTrains(); needAttachTrains = FALSE; } UpdateAllElevations(); if (!redrawAll) { for (trk=us->newTrks; trk; trk=trk->next ) DrawNewTrack( trk ); RedrawInStream( &redoStream, us->redoStart, us->redoEnd, TRUE ); } else DoRedraw(); oldCount = trackCount; trackCount = us->trackCount; us->trackCount = oldCount; InfoCount( trackCount ); undoCount--; doCount++; AuditTracks( "undoRedo" ); SelectRecount(); SetButtons( TRUE, undoCount>0 ); wBalloonHelpUpdate(); wDrawDelayUpdate( mainD.d, FALSE ); return TRUE; } void InitCmdUndo( void ) { log_undo = LogFindIndex( "undo" ); }