//----------------------------------------------------------------------------- // Copyright 2007 Jonathan Westhues // // This file is part of LDmicro. // // LDmicro 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 3 of the License, or // (at your option) any later version. // // LDmicro 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 LDmicro. If not, see . //------ // // Routines to maintain the stack of recent versions of the program that we // use for the undo/redo feature. Don't be smart at all; keep deep copies of // the entire program at all times. // Jonathan Westhues, split May 2005 //----------------------------------------------------------------------------- #include "linuxUI.h" #include #include #include #include "ldmicro.h" using namespace std; // Store a `deep copy' of the entire program before every change, in a // circular buffer so that the first one scrolls out as soon as the buffer // is full and we try to push another one. #define MAX_LEVELS_UNDO 32 typedef struct ProgramStackTag { PlcProgram prog[MAX_LEVELS_UNDO]; struct { int gx; int gy; } cursor[MAX_LEVELS_UNDO]; int write; int count; } ProgramStack; static struct { ProgramStack undo; ProgramStack redo; } Undo; //----------------------------------------------------------------------------- // Make a `deep copy' of a circuit. Used for making a copy of the program // whenever we change it, for undo purposes. Fast enough that we shouldn't // need to be smart. //----------------------------------------------------------------------------- static void *DeepCopy(int which, void *any) { switch(which) { CASE_LEAF { ElemLeaf *l = AllocLeaf(); memcpy(l, any, sizeof(*l)); l->selectedState = SELECTED_NONE; return l; } case ELEM_SERIES_SUBCKT: { int i; ElemSubcktSeries *n = AllocSubcktSeries(); ElemSubcktSeries *s = (ElemSubcktSeries *)any; n->count = s->count; for(i = 0; i < s->count; i++) { n->contents[i].which = s->contents[i].which; n->contents[i].d.any = DeepCopy(s->contents[i].which, s->contents[i].d.any); } return n; } case ELEM_PARALLEL_SUBCKT: { int i; ElemSubcktParallel *n = AllocSubcktParallel(); ElemSubcktParallel *p = (ElemSubcktParallel *)any; n->count = p->count; for(i = 0; i < p->count; i++) { n->contents[i].which = p->contents[i].which; n->contents[i].d.any = DeepCopy(p->contents[i].which, p->contents[i].d.any); } return n; } default: oops(); break; } } //----------------------------------------------------------------------------- // Empty out a ProgramStack data structure, either .undo or .redo: set the // count to zero and free all the program copies in it. //----------------------------------------------------------------------------- static void EmptyProgramStack(ProgramStack *ps) { while(ps->count > 0) { int a = (ps->write - 1); if(a < 0) a += MAX_LEVELS_UNDO; ps->write = a; (ps->count)--; int i; for(i = 0; i < ps->prog[ps->write].numRungs; i++) { FreeCircuit(ELEM_SERIES_SUBCKT, ps->prog[ps->write].rungs[i]); } } } //----------------------------------------------------------------------------- // Push the current program onto a program stack. Can either make a deep or // a shallow copy of the linked data structures. //----------------------------------------------------------------------------- static void PushProgramStack(ProgramStack *ps, BOOL deepCopy) { if(ps->count == MAX_LEVELS_UNDO) { int i; for(i = 0; i < ps->prog[ps->write].numRungs; i++) { FreeCircuit(ELEM_SERIES_SUBCKT, ps->prog[ps->write].rungs[i]); } } else { (ps->count)++; } memcpy(&(ps->prog[ps->write]), &Prog, sizeof(Prog)); if(deepCopy) { int i; for(i = 0; i < Prog.numRungs; i++) { ps->prog[ps->write].rungs[i] = (ElemSubcktSeries *)DeepCopy(ELEM_SERIES_SUBCKT, Prog.rungs[i]); } } int gx, gy; if(FindSelected(&gx, &gy)) { ps->cursor[ps->write].gx = gx; ps->cursor[ps->write].gy = gy; } else { ps->cursor[ps->write].gx = -1; ps->cursor[ps->write].gy = -1; } int a = (ps->write + 1); if(a >= MAX_LEVELS_UNDO) a -= MAX_LEVELS_UNDO; ps->write = a; } //----------------------------------------------------------------------------- // Pop a program stack onto the current program. Always does a shallow copy. // Internal error if the stack was empty. //----------------------------------------------------------------------------- static void PopProgramStack(ProgramStack *ps) { int a = (ps->write - 1); if(a < 0) a += MAX_LEVELS_UNDO; ps->write = a; (ps->count)--; memcpy(&Prog, &ps->prog[ps->write], sizeof(Prog)); SelectedGxAfterNextPaint = ps->cursor[ps->write].gx; SelectedGyAfterNextPaint = ps->cursor[ps->write].gy; } //----------------------------------------------------------------------------- // Push a copy of the PLC program onto the undo history, replacing (and // freeing) the oldest one if necessary. //----------------------------------------------------------------------------- void UndoRemember(void) { // can't redo after modifying the program EmptyProgramStack(&(Undo.redo)); PushProgramStack(&(Undo.undo), TRUE); SetUndoEnabled(TRUE, FALSE); } //----------------------------------------------------------------------------- // Pop the undo history one level, or do nothing if we're out of levels of // undo. This means that we push the current program on the redo stack, and // pop the undo stack onto the current program. //----------------------------------------------------------------------------- void UndoUndo(void) { if(Undo.undo.count <= 0) return; ForgetEverything(); PushProgramStack(&(Undo.redo), FALSE); PopProgramStack(&(Undo.undo)); if(Undo.undo.count > 0) { SetUndoEnabled(TRUE, TRUE); } else { SetUndoEnabled(FALSE, TRUE); } RefreshControlsToSettings(); RefreshScrollbars(); InvalidateRect(MainWindow, NULL, FALSE); } //----------------------------------------------------------------------------- // Redo an undone operation. Push the current program onto the undo stack, // and pop the redo stack into the current program. //----------------------------------------------------------------------------- void UndoRedo(void) { if(Undo.redo.count <= 0) return; ForgetEverything(); PushProgramStack(&(Undo.undo), FALSE); PopProgramStack(&(Undo.redo)); if(Undo.redo.count > 0) { SetUndoEnabled(TRUE, TRUE); } else { SetUndoEnabled(TRUE, FALSE); } RefreshControlsToSettings(); RefreshScrollbars(); InvalidateRect(MainWindow, NULL, FALSE); } //----------------------------------------------------------------------------- // Empty out our undo history entirely, as when loading a new file. //----------------------------------------------------------------------------- void UndoFlush(void) { EmptyProgramStack(&(Undo.undo)); EmptyProgramStack(&(Undo.redo)); SetUndoEnabled(FALSE, FALSE); } //----------------------------------------------------------------------------- // Is it possible to undo some operation? The display code needs to do that, // due to an ugly hack for handling too-long lines; the only thing that // notices that easily is the display code, which will respond by undoing // the last operation, presumably the one that added the long line. //----------------------------------------------------------------------------- BOOL CanUndo(void) { return (Undo.undo.count > 0); }