Mercurial > projects > doodle
view doodle/core/undo.d @ 132:bc5baa585b32
Updated to dmd 2.060
author | David Bryant <bagnose@gmail.com> |
---|---|
date | Thu, 02 Aug 2012 15:32:43 +0930 |
parents | ab745d8b10e5 |
children |
line wrap: on
line source
module doodle.core.undo; import std.array; import std.datetime; // An abstract framework for undo/redo. // Assume the application works on one document at a time, // therefore a single undo/redo history. // Each change to the document is modelled as an Edit. // Previous edits are represented by an undo stack // and can be undone in order. // As edits are undone the are placed on a redo stack // and can be redone in order. // As edits are redone the are placed on the undo stack, etc. // // When a new edit is made an attempt to merge it with // the previous Edit is made. For example, typing characters // in short succession generates many Edits that may be // merged into one Edit. // When a new edit is made the redo stack is cleared. // // Application code must generate Edits from user interaction. // Typically application code will receive some input event and: // * Attempt to perform an action, // * If the action succeeds then encapsulate the action // in an Edit and add it to the undo manager. // Note, not all interaction results in Edits, for example, // changing the view, zooming/scrolling, etc are not edits // and do not affect undo/redo abstract class Edit { private { string _description; SysTime _timeStamp; } this(in string description) { _description = description; _timeStamp = Clock.currTime(); } this(in string description, SysTime timeStamp) { assert(description); _description = description; _timeStamp = timeStamp; } string description() const { return _description; } const(SysTime) timeStamp() const { return _timeStamp; } final bool merge(Edit subsequent) { if (mergeImpl(subsequent)) { // Adopt the new timestamp and description _timeStamp = subsequent._timeStamp; _description = subsequent._description; return true; } else { return false; } } void undo(); void redo(); protected bool mergeImpl(Edit subsequent) { return false; } } interface IUndoManagerObserver { // Each description is null if the associated bool is false void undoRedoUpdate(in bool canUndo, in string undoDescription, in bool canRedo, in string redoDescription); } // XXX This interface doesn't appear to add any value interface IUndoManager { void addEdit(Edit edit); void undo(); void redo(); void reset(); void addObserver(IUndoManagerObserver observer); void removeObserver(IUndoManagerObserver observer); } class UndoManager : IUndoManager { this(int maxUndoLevel = -1) { _maxUndoLevel = maxUndoLevel; } ~this() { assert(_observers.length == 0); } void addEdit(Edit edit) { _redoEdits.length = 0; if (_undoEdits.empty || !_undoEdits.back.merge(edit)) { _undoEdits ~= edit; if (_maxUndoLevel >= 0 && _undoEdits.length > _maxUndoLevel) { _undoEdits.length = _undoEdits.length - 1; } } notifyObservers(); } void undo() { assert(canUndo()); auto edit = _undoEdits.back; edit.undo(); _undoEdits.popBack(); _redoEdits ~= edit; notifyObservers(); } void redo() { assert(canRedo()); auto edit = _redoEdits.back; edit.redo(); _redoEdits.popBack(); _undoEdits ~= edit; notifyObservers(); } bool canUndo() const { return !_undoEdits.empty; } bool canRedo() const { return !_redoEdits.empty; } void reset() { _undoEdits.length = _redoEdits.length = 0; notifyObservers(); } void addObserver(IUndoManagerObserver observer) { _observers ~= observer; } void removeObserver(IUndoManagerObserver observer) { // NYI } // IUndoManager overrides: private { int _maxUndoLevel; Edit[] _undoEdits; Edit[] _redoEdits; IUndoManagerObserver[] _observers; // FIXME, use a different container void notifyObservers() { foreach (o; _observers) { o.undoRedoUpdate(canUndo(), canUndo() ? _undoEdits.back.description() : null, canRedo(), canRedo() ? _redoEdits.back.description() : null); } } } }