X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FUndo.cpp;h=a24bdead03e3861df2d6d18ab52e0b738be626ed;hb=0d75c1117f278efc59c2e5a108f60efa9e017bc4;hp=11224908ccc8b51deea6b12a78714dae8e72699b;hpb=9abb7db46800e554f57e865a3e768602ffd9d6f1;p=lyx.git diff --git a/src/Undo.cpp b/src/Undo.cpp index 11224908cc..a24bdead03 100644 --- a/src/Undo.cpp +++ b/src/Undo.cpp @@ -4,10 +4,10 @@ * Licence details can be found in the file COPYING. * * \author Asger Alstrup - * \author Lars Gullik Bjønnes + * \author Lars Gullik Bjønnes * \author John Levon - * \author André Pönitz - * \author Jürgen Vigna + * \author André Pönitz + * \author Jürgen Vigna * \author Abdelrazak Younes * * Full author contact details are available in file CREDITS. @@ -20,7 +20,6 @@ #include "Buffer.h" #include "BufferParams.h" #include "buffer_funcs.h" -#include "support/debug.h" #include "DocIterator.h" #include "Paragraph.h" #include "ParagraphList.h" @@ -31,16 +30,17 @@ #include "insets/Inset.h" -#include "support/limited_stack.h" +#include "support/lassert.h" +#include "support/debug.h" #include -#include +#include using namespace std; using namespace lyx::support; -namespace lyx { +namespace lyx { /** These are the elements put on the undo stack. Each object contains complete @@ -61,10 +61,44 @@ Obviously, the stored ranged should be as small as possible. However, it there is a lower limit: The StableDocIterator pointing stored in the undo class must be valid after the changes, too, as it will used as a pointer where to insert the stored bits when performining undo. - */ + + struct UndoElement { + /// + UndoElement(UndoKind kin, StableDocIterator const & cur, + StableDocIterator const & cel, + pit_type fro, pit_type en, ParagraphList * pl, + MathData * ar, BufferParams const & bp, + bool ifb, size_t gid) : + kind(kin), cursor(cur), cell(cel), from(fro), end(en), + pars(pl), array(ar), bparams(0), isFullBuffer(ifb), group_id(gid) + { + if (isFullBuffer) + bparams = new BufferParams(bp); + } + /// + UndoElement(UndoElement const & ue) + { + kind = ue.kind; + cursor = ue.cursor; + cell = ue.cell; + from = ue.from; + end = ue.end; + pars = ue.pars; + array = ue.array; + bparams = ue.isFullBuffer + ? new BufferParams(*ue.bparams) : ue.bparams; + isFullBuffer = ue.isFullBuffer; + group_id = ue.group_id; + } + /// + ~UndoElement() + { + if (isFullBuffer) + delete bparams; + } /// Which kind of operation are we recording for? UndoKind kind; /// the position of the cursor @@ -80,18 +114,73 @@ struct UndoElement /// the contents of the saved MathData (for mathed) MathData * array; /// Only used in case of full backups - BufferParams bparams; + BufferParams const * bparams; /// Only used in case of full backups bool isFullBuffer; + /// the element's group id + size_t group_id; +private: + /// Protect construction + UndoElement(); +}; + + +class UndoElementStack +{ +public: + /// limit is the maximum size of the stack + UndoElementStack(size_t limit = 100) { limit_ = limit; } + /// limit is the maximum size of the stack + ~UndoElementStack() { clear(); } + + /// Return the top element. + UndoElement & top() { return c_.front(); } + + /// Pop and throw away the top element. + void pop() { c_.pop_front(); } + + /// Return true if the stack is empty. + bool empty() const { return c_.empty(); } + + /// Clear all elements, deleting them. + void clear() { + for (size_t i = 0; i != c_.size(); ++i) { + delete c_[i].array; + delete c_[i].pars; + } + c_.clear(); + } + + /// Push an item on to the stack, deleting the bottom group on + /// overflow. + void push(UndoElement const & v) { + c_.push_front(v); + if (c_.size() > limit_) { + // remove a whole group at once. + const size_t gid = c_.back().group_id; + while (!c_.empty() && c_.back().group_id == gid) + c_.pop_back(); + } + } + +private: + /// Internal contents. + std::deque c_; + /// The maximum number elements stored. + size_t limit_; }; struct Undo::Private { - Private(Buffer & buffer): buffer_(buffer) {} + Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true), + group_id(0), group_level(0) {} - // Returns false if no undo possible. + // Do one undo/redo step + void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherStack); + // Apply one undo/redo group. Returns false if no undo possible. bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation); + /// void doRecordUndo(UndoKind kind, DocIterator const & cell, @@ -99,29 +188,40 @@ struct Undo::Private pit_type last_pit, DocIterator const & cur, bool isFullBuffer, - bool isUndoOperation); - + UndoElementStack & stack); /// void recordUndo(UndoKind kind, - DocIterator & cur, + DocIterator const & cur, pit_type first_pit, pit_type last_pit); /// Buffer & buffer_; /// Undo stack. - limited_stack undostack; + UndoElementStack undostack_; /// Redo stack. - limited_stack redostack; + UndoElementStack redostack_; /// The flag used by Undo::finishUndo(). - bool undo_finished; + bool undo_finished_; + + /// Current group Id. + size_t group_id; + /// Current group nesting nevel. + size_t group_level; }; -Undo::Undo(Buffer & buffer): d(new Undo::Private(buffer)) -{ -} +///////////////////////////////////////////////////////////////////// +// +// Undo +// +///////////////////////////////////////////////////////////////////// + + +Undo::Undo(Buffer & buffer) + : d(new Undo::Private(buffer)) +{} Undo::~Undo() @@ -132,77 +232,67 @@ Undo::~Undo() bool Undo::hasUndoStack() const { - return !d->undostack.empty(); + return !d->undostack_.empty(); } bool Undo::hasRedoStack() const { - return !d->redostack.empty(); + return !d->redostack_.empty(); } - - -namespace { - -ostream & operator<<(ostream & os, UndoElement const & undo) -{ - return os << " from: " << undo.from << " end: " << undo.end - << " cell:\n" << undo.cell - << " cursor:\n" << undo.cursor; -} - - -bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) +static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2) { StableDocIterator tmpi2 = i2; tmpi2.pos() = i1.pos(); return i1 == tmpi2; } -} // namespace anon +///////////////////////////////////////////////////////////////////// +// +// Undo::Private +// +/////////////////////////////////////////////////////////////////////// void Undo::Private::doRecordUndo(UndoKind kind, DocIterator const & cell, pit_type first_pit, pit_type last_pit, DocIterator const & cur, bool isFullBuffer, - bool isUndoOperation) + UndoElementStack & stack) { + if (!group_level) { + LYXERR0("There is no group open (creating one)"); + ++group_id; + } + if (first_pit > last_pit) swap(first_pit, last_pit); - // create the position information of the Undo entry - UndoElement undo; - undo.array = 0; - undo.pars = 0; - undo.kind = kind; - undo.cell = cell; - undo.cursor = cur; - undo.bparams = buffer_.params(); - undo.isFullBuffer = isFullBuffer; - //lyxerr << "recordUndo: cur: " << cur << endl; - //lyxerr << "recordUndo: pos: " << cur.pos() << endl; - //lyxerr << "recordUndo: cell: " << cell << endl; - undo.from = first_pit; - undo.end = cell.lastpit() - last_pit; - - limited_stack & stack = isUndoOperation ? - undostack : redostack; // Undo::ATOMIC are always recorded (no overlapping there). // As nobody wants all removed character appear one by one when undoing, // we want combine 'similar' non-ATOMIC undo recordings to one. - if (!undo_finished + pit_type from = first_pit; + pit_type end = cell.lastpit() - last_pit; + if (!undo_finished_ && kind != ATOMIC_UNDO && !stack.empty() - && samePar(stack.top().cell, undo.cell) - && stack.top().kind == undo.kind - && stack.top().from == undo.from - && stack.top().end == undo.end) + && samePar(stack.top().cell, cell) + && stack.top().kind == kind + && stack.top().from == from + && stack.top().end == end) return; + if (isFullBuffer) + LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id); + else + LYXERR(Debug::UNDO, "Create undo element of group " << group_id); + // create the position information of the Undo entry + UndoElement undo(kind, cur, cell, from, end, 0, 0, + buffer_.params(), isFullBuffer, group_id); + // fill in the real data to be saved if (cell.inMathed()) { // simply use the whole cell @@ -212,7 +302,7 @@ void Undo::Private::doRecordUndo(UndoKind kind, // main Text _is_ the whole document. // record the relevant paragraphs Text const * text = cell.text(); - BOOST_ASSERT(text); + LASSERT(text, /**/); ParagraphList const & plist = text->paragraphs(); ParagraphList::const_iterator first = plist.begin(); advance(first, first_pit); @@ -226,61 +316,51 @@ void Undo::Private::doRecordUndo(UndoKind kind, //lyxerr << "undo record: " << stack.top() << endl; // next time we'll try again to combine entries if possible - undo_finished = false; + undo_finished_ = false; } -void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur, +void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cur, pit_type first_pit, pit_type last_pit) { - BOOST_ASSERT(first_pit <= cur.lastpit()); - BOOST_ASSERT(last_pit <= cur.lastpit()); + LASSERT(first_pit <= cur.lastpit(), /**/); + LASSERT(last_pit <= cur.lastpit(), /**/); doRecordUndo(kind, cur, first_pit, last_pit, cur, - false, true); + false, undostack_); - undo_finished = false; - redostack.clear(); + undo_finished_ = false; + redostack_.clear(); //lyxerr << "undostack:\n"; //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i) // lyxerr << " " << i << ": " << buf.undostack()[i] << endl; } -bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) +void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack) { - undo_finished = true; - - limited_stack & stack = isUndoOperation ? - undostack : redostack; - - if (stack.empty()) - // Nothing to do. - return false; - - limited_stack & otherstack = isUndoOperation ? - redostack : undostack; - // Adjust undo stack and get hold of current undo data. - UndoElement undo = stack.top(); - stack.pop(); + UndoElement & undo = stack.top(); + LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id); + // We'll pop the stack only when we're done with this element. So do NOT + // try to return early. // We will store in otherstack the part of the document under 'undo' - DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset()); + DocIterator cell_dit = undo.cell.asDocIterator(&buffer_); doRecordUndo(ATOMIC_UNDO, cell_dit, undo.from, cell_dit.lastpit() - undo.end, cur, - undo.isFullBuffer, !isUndoOperation); + undo.isFullBuffer, otherstack); // This does the actual undo/redo. //LYXERR0("undo, performing: " << undo); - bool labelsUpdateNeeded = false; - DocIterator dit = undo.cell.asDocIterator(&buffer_.inset()); + DocIterator dit = undo.cell.asDocIterator(&buffer_); if (undo.isFullBuffer) { - BOOST_ASSERT(undo.pars); + LASSERT(undo.pars, /**/); // This is a full document - otherstack.top().bparams = buffer_.params(); - buffer_.params() = undo.bparams; + delete otherstack.top().bparams; + otherstack.top().bparams = new BufferParams(buffer_.params()); + buffer_.params() = *undo.bparams; swap(buffer_.paragraphs(), *undo.pars); delete undo.pars; undo.pars = 0; @@ -289,15 +369,15 @@ bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) // gained by storing just 'a few' paragraphs (most if not // all math inset cells have just one paragraph!) //LYXERR0("undo.array: " << *undo.array); - BOOST_ASSERT(undo.array); + LASSERT(undo.array, /**/); dit.cell().swap(*undo.array); delete undo.array; undo.array = 0; } else { // Some finer machinery is needed here. Text * text = dit.text(); - BOOST_ASSERT(text); - BOOST_ASSERT(undo.pars); + LASSERT(text, /**/); + LASSERT(undo.pars, /**/); ParagraphList & plist = text->paragraphs(); // remove new stuff between first and last @@ -320,16 +400,35 @@ bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) plist.insert(first, undo.pars->begin(), undo.pars->end()); delete undo.pars; undo.pars = 0; - labelsUpdateNeeded = true; } - BOOST_ASSERT(undo.pars == 0); - BOOST_ASSERT(undo.array == 0); + LASSERT(undo.pars == 0, /**/); + LASSERT(undo.array == 0, /**/); - cur = undo.cursor.asDocIterator(&buffer_.inset()); - - if (labelsUpdateNeeded) - updateLabels(buffer_); - undo_finished = true; + cur = undo.cursor.asDocIterator(&buffer_); + // Now that we're done with undo, we pop it off the stack. + stack.pop(); +} + + +bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) +{ + undo_finished_ = true; + + UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_; + + if (stack.empty()) + // Nothing to do. + return false; + + UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_; + + const size_t gid = stack.top().group_id; + while (!stack.empty() && stack.top().group_id == gid) + doTextUndoOrRedo(cur, stack, otherstack); + + // Adapt the new material to current buffer. + buffer_.setBuffersForInsets(); // FIXME This shouldn't be here. + buffer_.updateLabels(); return true; } @@ -337,7 +436,7 @@ bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation) void Undo::finishUndo() { // Make sure the next operation will be stored. - d->undo_finished = true; + d->undo_finished_ = true; } @@ -353,43 +452,71 @@ bool Undo::textRedo(DocIterator & cur) } -void Undo::recordUndo(DocIterator & cur, UndoKind kind) +void Undo::beginUndoGroup() +{ + if (d->group_level == 0) { + // create a new group + ++d->group_id; + LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id); + } + ++d->group_level; +} + + +void Undo::endUndoGroup() +{ + if (d->group_level == 0) + LYXERR0("There is no undo group to end here"); + --d->group_level; + if (d->group_level == 0) { + // real end of the group + LYXERR(Debug::UNDO, "-------End of group " << d->group_id); + } +} + + + +void Undo::recordUndo(DocIterator const & cur, UndoKind kind) { d->recordUndo(kind, cur, cur.pit(), cur.pit()); } -void Undo::recordUndoInset(DocIterator & cur, UndoKind kind) +void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind) { DocIterator c = cur; c.pop_back(); - d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, true); + d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, d->undostack_); } -void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from) +void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from) { d->recordUndo(kind, cur, cur.pit(), from); } -void Undo::recordUndo(DocIterator & cur, UndoKind kind, +void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from, pit_type to) { d->recordUndo(kind, cur, from, to); } -void Undo::recordUndoFullDocument(DocIterator & cur) +void Undo::recordUndoFullDocument(DocIterator const & cur) { + // This one may happen outside of the main undo group, so we + // put it in its own subgroup to avoid complaints. + beginUndoGroup(); d->doRecordUndo( ATOMIC_UNDO, - doc_iterator_begin(d->buffer_.inset()), + doc_iterator_begin(&d->buffer_), 0, d->buffer_.paragraphs().size() - 1, cur, true, - true + d->undostack_ ); + endUndoGroup(); }