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"
26 #include "ParagraphParameters.h"
29 #include "mathed/MathSupport.h"
30 #include "mathed/MathData.h"
32 #include "insets/Inset.h"
34 #include "support/lassert.h"
35 #include "support/debug.h"
41 using namespace lyx::support;
47 These are the elements put on the undo stack. Each object contains
48 complete paragraphs from some cell and sufficient information to
49 restore the cursor state.
51 The cell is given by a DocIterator pointing to this cell, the
52 'interesting' range of paragraphs by counting them from begin and end
53 of cell, respectively.
55 The cursor is also given as DocIterator and should point to some place
56 in the stored paragraph range. In case of math, we simply store the
57 whole cell, as there usually is just a simple paragraph in a cell.
59 The idea is to store the contents of 'interesting' paragraphs in some
60 structure ('Undo') _before_ it is changed in some edit operation.
61 Obviously, the stored range should be as small as possible. However,
62 there is a lower limit: The StableDocIterator stored in the undo class
63 must be valid after the changes, too, as it will used as a pointer
64 where to insert the stored bits when performining undo.
69 UndoElement(UndoKind kin, StableDocIterator const & cb,
70 StableDocIterator const & cel,
71 pit_type fro, pit_type en, ParagraphList * pl,
72 MathData * ar, BufferParams const & bp,
73 bool ifb, bool lc, size_t gid) :
74 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
75 pars(pl), array(ar), bparams(0), isFullBuffer(ifb),
76 lyx_clean(lc), group_id(gid)
79 bparams = new BufferParams(bp);
82 UndoElement(UndoElement const & ue)
85 cur_before = ue.cur_before;
86 cur_after = ue.cur_after;
92 bparams = ue.isFullBuffer
93 ? new BufferParams(*ue.bparams) : ue.bparams;
94 isFullBuffer = ue.isFullBuffer;
95 lyx_clean = ue.lyx_clean;
96 group_id = ue.group_id;
104 /// Which kind of operation are we recording for?
106 /// the position of the cursor before recordUndo
107 StableDocIterator cur_before;
108 /// the position of the cursor at the end of the undo group
109 StableDocIterator cur_after;
110 /// the position of the cell described
111 StableDocIterator cell;
112 /// counted from begin of cell
114 /// complement to end of this cell
116 /// the contents of the saved Paragraphs (for texted)
117 ParagraphList * pars;
118 /// the contents of the saved MathData (for mathed)
120 /// Only used in case of full backups
121 BufferParams const * bparams;
122 /// Only used in case of full backups
124 /// Was the buffer clean at this point?
126 /// the element's group id
129 /// Protect construction
134 class UndoElementStack
137 /// limit is the maximum size of the stack
138 UndoElementStack(size_t limit = 100) { limit_ = limit; }
139 /// limit is the maximum size of the stack
140 ~UndoElementStack() { clear(); }
142 /// Return the top element.
143 UndoElement & top() { return c_.front(); }
145 /// Pop and throw away the top element.
146 void pop() { c_.pop_front(); }
148 /// Return true if the stack is empty.
149 bool empty() const { return c_.empty(); }
151 /// Clear all elements, deleting them.
153 for (size_t i = 0; i != c_.size(); ++i) {
160 /// Push an item on to the stack, deleting the bottom group on
162 void push(UndoElement const & v) {
164 if (c_.size() > limit_) {
165 // remove a whole group at once.
166 const size_t gid = c_.back().group_id;
167 while (!c_.empty() && c_.back().group_id == gid)
172 /// Mark all the elements of the stack as dirty
174 for (size_t i = 0; i != c_.size(); ++i)
175 c_[i].lyx_clean = false;
179 /// Internal contents.
180 std::deque<UndoElement> c_;
181 /// The maximum number elements stored.
188 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
189 group_id(0), group_level(0) {}
191 // Do one undo/redo step
192 void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack,
193 UndoElementStack & otherStack);
194 // Apply one undo/redo group. Returns false if no undo possible.
195 bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
198 void doRecordUndo(UndoKind kind,
199 DocIterator const & cell,
202 StableDocIterator const & cur,
204 UndoElementStack & stack);
206 void recordUndo(UndoKind kind,
207 DocIterator const & cell,
210 DocIterator const & cur,
216 UndoElementStack undostack_;
218 UndoElementStack redostack_;
220 /// The flag used by Undo::finishUndo().
223 /// Current group Id.
225 /// Current group nesting nevel.
230 /////////////////////////////////////////////////////////////////////
234 /////////////////////////////////////////////////////////////////////
237 Undo::Undo(Buffer & buffer)
238 : d(new Undo::Private(buffer))
250 d->undostack_.clear();
251 d->redostack_.clear();
252 d->undo_finished_ = true;
258 bool Undo::hasUndoStack() const
260 return !d->undostack_.empty();
264 bool Undo::hasRedoStack() const
266 return !d->redostack_.empty();
270 void Undo::markDirty()
272 d->undo_finished_ = true;
273 d->undostack_.markDirty();
274 d->redostack_.markDirty();
278 /////////////////////////////////////////////////////////////////////
282 ///////////////////////////////////////////////////////////////////////
284 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
286 StableDocIterator tmpi2 = i2;
287 tmpi2.pos() = i1.pos();
292 void Undo::Private::doRecordUndo(UndoKind kind,
293 DocIterator const & cell,
294 pit_type first_pit, pit_type last_pit,
295 StableDocIterator const & cur_before,
297 UndoElementStack & stack)
300 LYXERR0("There is no group open (creating one)");
304 if (first_pit > last_pit)
305 swap(first_pit, last_pit);
307 // Undo::ATOMIC are always recorded (no overlapping there).
308 // As nobody wants all removed character appear one by one when undoing,
309 // we want combine 'similar' non-ATOMIC undo recordings to one.
310 pit_type from = first_pit;
311 pit_type end = cell.lastpit() - last_pit;
313 && kind != ATOMIC_UNDO
315 && samePar(stack.top().cell, cell)
316 && stack.top().kind == kind
317 && stack.top().from == from
318 && stack.top().end == end) {
319 // reset cur_after; it will be filled correctly by endUndoGroup.
320 stack.top().cur_after = StableDocIterator();
325 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
327 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
328 // create the position information of the Undo entry
329 UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
330 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
332 // fill in the real data to be saved
333 if (cell.inMathed()) {
334 // simply use the whole cell
335 MathData & ar = cell.cell();
336 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
338 // some more effort needed here as 'the whole cell' of the
339 // main Text _is_ the whole document.
340 // record the relevant paragraphs
341 Text const * text = cell.text();
343 ParagraphList const & plist = text->paragraphs();
344 ParagraphList::const_iterator first = plist.begin();
345 advance(first, first_pit);
346 ParagraphList::const_iterator last = plist.begin();
347 advance(last, last_pit + 1);
348 // If the paragraphs after the last one have a
349 // non-zero depth and the depth of last paragraph is
350 // decremented, then these paragraphs may be affected
351 // (ticket #8159). We guard against that by saving
352 // these extra paragraphs.
353 while (last != plist.end() && last->params().depth() > 0) {
357 undo.pars = new ParagraphList(first, last);
360 // push the undo entry to undo stack
362 //lyxerr << "undo record: " << stack.top() << endl;
366 void Undo::Private::recordUndo(UndoKind kind,
367 DocIterator const & cell,
368 pit_type first_pit, pit_type last_pit,
369 DocIterator const & cur,
372 LASSERT(first_pit <= cell.lastpit(), /**/);
373 LASSERT(last_pit <= cell.lastpit(), /**/);
375 doRecordUndo(kind, cell, first_pit, last_pit, cur,
376 isFullBuffer, undostack_);
378 // next time we'll try again to combine entries if possible
379 undo_finished_ = false;
381 // If we ran recordUndo, it means that we plan to change the buffer
385 //lyxerr << "undostack:\n";
386 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
387 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
391 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
393 // Adjust undo stack and get hold of current undo data.
394 UndoElement & undo = stack.top();
395 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
396 // We'll pop the stack only when we're done with this element. So do NOT
397 // try to return early.
399 // We will store in otherstack the part of the document under 'undo'
400 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
402 doRecordUndo(ATOMIC_UNDO, cell_dit,
403 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
404 undo.isFullBuffer, otherstack);
405 otherstack.top().cur_after = undo.cur_before;
407 // This does the actual undo/redo.
408 //LYXERR0("undo, performing: " << undo);
409 DocIterator dit = undo.cell.asDocIterator(&buffer_);
410 if (undo.isFullBuffer) {
411 LASSERT(undo.pars, /**/);
412 // This is a full document
413 delete otherstack.top().bparams;
414 otherstack.top().bparams = new BufferParams(buffer_.params());
415 buffer_.params() = *undo.bparams;
416 swap(buffer_.paragraphs(), *undo.pars);
419 } else if (dit.inMathed()) {
420 // We stored the full cell here as there is not much to be
421 // gained by storing just 'a few' paragraphs (most if not
422 // all math inset cells have just one paragraph!)
423 //LYXERR0("undo.array: " << *undo.array);
424 LASSERT(undo.array, /**/);
425 dit.cell().swap(*undo.array);
429 // Some finer machinery is needed here.
430 Text * text = dit.text();
432 LASSERT(undo.pars, /**/);
433 ParagraphList & plist = text->paragraphs();
435 // remove new stuff between first and last
436 ParagraphList::iterator first = plist.begin();
437 advance(first, undo.from);
438 ParagraphList::iterator last = plist.begin();
439 advance(last, plist.size() - undo.end);
440 plist.erase(first, last);
442 // re-insert old stuff instead
443 first = plist.begin();
444 advance(first, undo.from);
446 // this ugly stuff is needed until we get rid of the
447 // inset_owner backpointer
448 ParagraphList::iterator pit = undo.pars->begin();
449 ParagraphList::iterator const end = undo.pars->end();
450 for (; pit != end; ++pit)
451 pit->setInsetOwner(dit.realInset());
452 plist.insert(first, undo.pars->begin(), undo.pars->end());
456 LASSERT(undo.pars == 0, /**/);
457 LASSERT(undo.array == 0, /**/);
459 if (undo.cur_before.size())
460 cur = undo.cur_before.asDocIterator(&buffer_);
465 // Now that we're done with undo, we pop it off the stack.
470 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
472 undo_finished_ = true;
474 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
480 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
482 const size_t gid = stack.top().group_id;
483 while (!stack.empty() && stack.top().group_id == gid)
484 doTextUndoOrRedo(cur, stack, otherstack);
486 // Adapt the new material to current buffer.
487 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
492 void Undo::finishUndo()
494 // Make sure the next operation will be stored.
495 d->undo_finished_ = true;
499 bool Undo::textUndo(DocIterator & cur)
501 return d->textUndoOrRedo(cur, true);
505 bool Undo::textRedo(DocIterator & cur)
507 return d->textUndoOrRedo(cur, false);
511 void Undo::beginUndoGroup()
513 if (d->group_level == 0) {
514 // create a new group
516 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
522 void Undo::endUndoGroup()
524 if (d->group_level == 0)
525 LYXERR0("There is no undo group to end here");
527 if (d->group_level == 0) {
528 // real end of the group
529 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
534 void Undo::endUndoGroup(DocIterator const & cur)
537 if (!d->undostack_.empty() && !d->undostack_.top().cur_after.size())
538 d->undostack_.top().cur_after = cur;
542 // FIXME: remove these convenience functions and make
543 // Private::recordUndo public as sole interface. The code in the
544 // convenience functions can move to Cursor.cpp.
546 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
548 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
552 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind,
555 if (!inset || inset == &cur.inset()) {
558 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
559 } else if (inset == cur.nextInset())
560 recordUndo(cur, kind);
562 LYXERR0("Inset not found, no undo stack added.");
566 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
568 d->recordUndo(kind, cur, cur.pit(), from, cur, false);
572 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
573 pit_type from, pit_type to)
575 d->recordUndo(kind, cur, from, to, cur, false);
579 void Undo::recordUndoFullDocument(DocIterator const & cur)
581 // This one may happen outside of the main undo group, so we
582 // put it in its own subgroup to avoid complaints.
584 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
585 0, d->buffer_.paragraphs().size() - 1, cur, true);