3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
10 * \author Jürgen Vigna
11 * \author Abdelrazak Younes
13 * Full author contact details are available in file CREDITS.
21 #include "BufferParams.h"
22 #include "buffer_funcs.h"
23 #include "DocIterator.h"
24 #include "Paragraph.h"
25 #include "ParagraphList.h"
28 #include "mathed/MathSupport.h"
29 #include "mathed/MathData.h"
31 #include "insets/Inset.h"
33 #include "support/lassert.h"
34 #include "support/debug.h"
40 using namespace lyx::support;
46 These are the elements put on the undo stack. Each object contains complete
47 paragraphs from some cell and sufficient information to restore the cursor
50 The cell is given by a DocIterator pointing to this cell, the 'interesting'
51 range of paragraphs by counting them from begin and end of cell,
54 The cursor is also given as DocIterator and should point to some place in
55 the stored paragraph range. In case of math, we simply store the whole
56 cell, as there usually is just a simple paragraph in a cell.
58 The idea is to store the contents of 'interesting' paragraphs in some
59 structure ('Undo') _before_ it is changed in some edit operation.
60 Obviously, the stored ranged should be as small as possible. However, it
61 there is a lower limit: The StableDocIterator pointing stored in the undo
62 class must be valid after the changes, too, as it will used as a pointer
63 where to insert the stored bits when performining undo.
70 UndoElement(UndoKind kin, StableDocIterator const & cur,
71 StableDocIterator const & cel,
72 pit_type fro, pit_type en, ParagraphList * pl,
73 MathData * ar, BufferParams const & bp,
74 bool ifb, bool lc, size_t gid) :
75 kind(kin), cursor(cur), cell(cel), from(fro), end(en),
76 pars(pl), array(ar), bparams(0), isFullBuffer(ifb), lyx_clean(lc), group_id(gid)
79 bparams = new BufferParams(bp);
82 UndoElement(UndoElement const & ue)
91 bparams = ue.isFullBuffer
92 ? new BufferParams(*ue.bparams) : ue.bparams;
93 isFullBuffer = ue.isFullBuffer;
94 lyx_clean = ue.lyx_clean;
95 group_id = ue.group_id;
103 /// Which kind of operation are we recording for?
105 /// the position of the cursor
106 StableDocIterator cursor;
107 /// the position of the cell described
108 StableDocIterator cell;
109 /// counted from begin of cell
111 /// complement to end of this cell
113 /// the contents of the saved Paragraphs (for texted)
114 ParagraphList * pars;
115 /// the contents of the saved MathData (for mathed)
117 /// Only used in case of full backups
118 BufferParams const * bparams;
119 /// Only used in case of full backups
121 /// Was the buffer clean at this point?
123 /// the element's group id
126 /// Protect construction
131 class UndoElementStack
134 /// limit is the maximum size of the stack
135 UndoElementStack(size_t limit = 100) { limit_ = limit; }
136 /// limit is the maximum size of the stack
137 ~UndoElementStack() { clear(); }
139 /// Return the top element.
140 UndoElement & top() { return c_.front(); }
142 /// Pop and throw away the top element.
143 void pop() { c_.pop_front(); }
145 /// Return true if the stack is empty.
146 bool empty() const { return c_.empty(); }
148 /// Clear all elements, deleting them.
150 for (size_t i = 0; i != c_.size(); ++i) {
157 /// Push an item on to the stack, deleting the bottom group on
159 void push(UndoElement const & v) {
161 if (c_.size() > limit_) {
162 // remove a whole group at once.
163 const size_t gid = c_.back().group_id;
164 while (!c_.empty() && c_.back().group_id == gid)
169 /// Mark all the elements of the stack as dirty
171 for (size_t i = 0; i != c_.size(); ++i)
172 c_[i].lyx_clean = false;
176 /// Internal contents.
177 std::deque<UndoElement> c_;
178 /// The maximum number elements stored.
185 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
186 group_id(0), group_level(0) {}
188 // Do one undo/redo step
189 void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherStack);
190 // Apply one undo/redo group. Returns false if no undo possible.
191 bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
194 void doRecordUndo(UndoKind kind,
195 DocIterator const & cell,
198 DocIterator const & cur,
200 UndoElementStack & stack);
202 void recordUndo(UndoKind kind,
203 DocIterator const & cur,
210 UndoElementStack undostack_;
212 UndoElementStack redostack_;
214 /// The flag used by Undo::finishUndo().
217 /// Current group Id.
219 /// Current group nesting nevel.
224 /////////////////////////////////////////////////////////////////////
228 /////////////////////////////////////////////////////////////////////
231 Undo::Undo(Buffer & buffer)
232 : d(new Undo::Private(buffer))
242 bool Undo::hasUndoStack() const
244 return !d->undostack_.empty();
248 bool Undo::hasRedoStack() const
250 return !d->redostack_.empty();
254 void Undo::markDirty()
256 d->undostack_.markDirty();
257 d->redostack_.markDirty();
261 /////////////////////////////////////////////////////////////////////
265 ///////////////////////////////////////////////////////////////////////
267 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
269 StableDocIterator tmpi2 = i2;
270 tmpi2.pos() = i1.pos();
275 void Undo::Private::doRecordUndo(UndoKind kind,
276 DocIterator const & cell,
277 pit_type first_pit, pit_type last_pit,
278 DocIterator const & cur,
280 UndoElementStack & stack)
283 LYXERR0("There is no group open (creating one)");
287 if (first_pit > last_pit)
288 swap(first_pit, last_pit);
290 // Undo::ATOMIC are always recorded (no overlapping there).
291 // As nobody wants all removed character appear one by one when undoing,
292 // we want combine 'similar' non-ATOMIC undo recordings to one.
293 pit_type from = first_pit;
294 pit_type end = cell.lastpit() - last_pit;
296 && kind != ATOMIC_UNDO
298 && samePar(stack.top().cell, cell)
299 && stack.top().kind == kind
300 && stack.top().from == from
301 && stack.top().end == end)
305 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
307 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
308 // create the position information of the Undo entry
309 UndoElement undo(kind, cur, cell, from, end, 0, 0,
310 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
312 // fill in the real data to be saved
313 if (cell.inMathed()) {
314 // simply use the whole cell
315 MathData & ar = cell.cell();
316 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
318 // some more effort needed here as 'the whole cell' of the
319 // main Text _is_ the whole document.
320 // record the relevant paragraphs
321 Text const * text = cell.text();
323 ParagraphList const & plist = text->paragraphs();
324 ParagraphList::const_iterator first = plist.begin();
325 advance(first, first_pit);
326 ParagraphList::const_iterator last = plist.begin();
327 advance(last, last_pit + 1);
328 undo.pars = new ParagraphList(first, last);
331 // push the undo entry to undo stack
333 //lyxerr << "undo record: " << stack.top() << endl;
335 // next time we'll try again to combine entries if possible
336 undo_finished_ = false;
340 void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cur,
341 pit_type first_pit, pit_type last_pit)
343 LASSERT(first_pit <= cur.lastpit(), /**/);
344 LASSERT(last_pit <= cur.lastpit(), /**/);
346 doRecordUndo(kind, cur, first_pit, last_pit, cur,
349 undo_finished_ = false;
351 //lyxerr << "undostack:\n";
352 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
353 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
357 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
359 // Adjust undo stack and get hold of current undo data.
360 UndoElement & undo = stack.top();
361 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
362 // We'll pop the stack only when we're done with this element. So do NOT
363 // try to return early.
365 // We will store in otherstack the part of the document under 'undo'
366 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
368 doRecordUndo(ATOMIC_UNDO, cell_dit,
369 undo.from, cell_dit.lastpit() - undo.end, cur,
370 undo.isFullBuffer, otherstack);
372 // This does the actual undo/redo.
373 //LYXERR0("undo, performing: " << undo);
374 DocIterator dit = undo.cell.asDocIterator(&buffer_);
375 if (undo.isFullBuffer) {
376 LASSERT(undo.pars, /**/);
377 // This is a full document
378 delete otherstack.top().bparams;
379 otherstack.top().bparams = new BufferParams(buffer_.params());
380 buffer_.params() = *undo.bparams;
381 swap(buffer_.paragraphs(), *undo.pars);
384 } else if (dit.inMathed()) {
385 // We stored the full cell here as there is not much to be
386 // gained by storing just 'a few' paragraphs (most if not
387 // all math inset cells have just one paragraph!)
388 //LYXERR0("undo.array: " << *undo.array);
389 LASSERT(undo.array, /**/);
390 dit.cell().swap(*undo.array);
394 // Some finer machinery is needed here.
395 Text * text = dit.text();
397 LASSERT(undo.pars, /**/);
398 ParagraphList & plist = text->paragraphs();
400 // remove new stuff between first and last
401 ParagraphList::iterator first = plist.begin();
402 advance(first, undo.from);
403 ParagraphList::iterator last = plist.begin();
404 advance(last, plist.size() - undo.end);
405 plist.erase(first, last);
407 // re-insert old stuff instead
408 first = plist.begin();
409 advance(first, undo.from);
411 // this ugly stuff is needed until we get rid of the
412 // inset_owner backpointer
413 ParagraphList::iterator pit = undo.pars->begin();
414 ParagraphList::iterator const end = undo.pars->end();
415 for (; pit != end; ++pit)
416 pit->setInsetOwner(dit.realInset());
417 plist.insert(first, undo.pars->begin(), undo.pars->end());
421 LASSERT(undo.pars == 0, /**/);
422 LASSERT(undo.array == 0, /**/);
424 cur = undo.cursor.asDocIterator(&buffer_);
429 // Now that we're done with undo, we pop it off the stack.
434 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
436 undo_finished_ = true;
438 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
444 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
446 const size_t gid = stack.top().group_id;
447 while (!stack.empty() && stack.top().group_id == gid)
448 doTextUndoOrRedo(cur, stack, otherstack);
450 // Adapt the new material to current buffer.
451 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
456 void Undo::finishUndo()
458 // Make sure the next operation will be stored.
459 d->undo_finished_ = true;
463 bool Undo::textUndo(DocIterator & cur)
465 return d->textUndoOrRedo(cur, true);
469 bool Undo::textRedo(DocIterator & cur)
471 return d->textUndoOrRedo(cur, false);
475 void Undo::beginUndoGroup()
477 if (d->group_level == 0) {
478 // create a new group
480 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
486 void Undo::endUndoGroup()
488 if (d->group_level == 0)
489 LYXERR0("There is no undo group to end here");
491 if (d->group_level == 0) {
492 // real end of the group
493 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
499 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
501 d->recordUndo(kind, cur, cur.pit(), cur.pit());
505 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind)
509 d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, d->undostack_);
513 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
515 d->recordUndo(kind, cur, cur.pit(), from);
519 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
520 pit_type from, pit_type to)
522 d->recordUndo(kind, cur, from, to);
526 void Undo::recordUndoFullDocument(DocIterator const & cur)
528 // This one may happen outside of the main undo group, so we
529 // put it in its own subgroup to avoid complaints.
533 doc_iterator_begin(&d->buffer_),
534 0, d->buffer_.paragraphs().size() - 1,