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"
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/debug.h"
34 #include "support/gettext.h"
35 #include "support/lassert.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, CursorData const & cb,
70 StableDocIterator const & cel,
71 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
72 bool lc, size_t gid) :
73 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
74 pars(pl), array(ar), bparams(0),
75 lyx_clean(lc), group_id(gid)
79 UndoElement(CursorData const & cb, BufferParams const & bp,
80 bool lc, size_t gid) :
81 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
82 pars(0), array(0), bparams(new BufferParams(bp)),
83 lyx_clean(lc), group_id(gid)
87 UndoElement(UndoElement const & ue)
90 cur_before = ue.cur_before;
91 cur_after = ue.cur_after;
98 ? new BufferParams(*ue.bparams) : 0;
99 lyx_clean = ue.lyx_clean;
100 group_id = ue.group_id;
108 /// Which kind of operation are we recording for?
110 /// the position of the cursor before recordUndo
111 CursorData cur_before;
112 /// the position of the cursor at the end of the undo group
113 CursorData cur_after;
114 /// the position of the cell described
115 StableDocIterator cell;
116 /// counted from begin of cell
118 /// complement to end of this cell
120 /// the contents of the saved Paragraphs (for texted)
121 ParagraphList * pars;
122 /// the contents of the saved MathData (for mathed)
124 /// Only used in case of full backups
125 BufferParams const * bparams;
126 /// Was the buffer clean at this point?
128 /// the element's group id
131 /// Protect construction
136 class UndoElementStack
139 /// limit is the maximum size of the stack
140 UndoElementStack(size_t limit = 100) { limit_ = limit; }
141 /// limit is the maximum size of the stack
142 ~UndoElementStack() { clear(); }
144 /// Return the top element.
145 UndoElement & top() { return c_.front(); }
147 /// Pop and throw away the top element.
148 void pop() { c_.pop_front(); }
150 /// Return true if the stack is empty.
151 bool empty() const { return c_.empty(); }
153 /// Clear all elements, deleting them.
155 for (size_t i = 0; i != c_.size(); ++i) {
162 /// Push an item on to the stack, deleting the bottom group on
164 void push(UndoElement const & v) {
165 // Remove some entries if the limit has been reached.
166 // However, if the only group on the stack is the one
167 // we are currently populating, do nothing.
168 if (c_.size() >= limit_
169 && c_.front().group_id != v.group_id) {
170 // remove a whole group at once.
171 const size_t gid = c_.back().group_id;
172 while (!c_.empty() && c_.back().group_id == gid)
178 /// Mark all the elements of the stack as dirty
180 for (size_t i = 0; i != c_.size(); ++i)
181 c_[i].lyx_clean = false;
185 /// Internal contents.
186 std::deque<UndoElement> c_;
187 /// The maximum number elements stored.
194 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
195 group_id(0), group_level(0) {}
197 // Do one undo/redo step
198 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
199 UndoElementStack & otherStack);
200 // Apply one undo/redo group. Returns false if no undo possible.
201 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
204 void doRecordUndo(UndoKind kind,
205 DocIterator const & cell,
208 CursorData const & cur,
209 UndoElementStack & stack);
211 void recordUndo(UndoKind kind,
212 DocIterator const & cell,
215 CursorData const & cur);
217 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
219 void recordUndoBufferParams(CursorData const & cur);
224 UndoElementStack undostack_;
226 UndoElementStack redostack_;
228 /// The flag used by Undo::finishUndo().
231 /// Current group Id.
233 /// Current group nesting nevel.
238 /////////////////////////////////////////////////////////////////////
242 /////////////////////////////////////////////////////////////////////
245 Undo::Undo(Buffer & buffer)
246 : d(new Undo::Private(buffer))
258 d->undostack_.clear();
259 d->redostack_.clear();
260 d->undo_finished_ = true;
261 // We used to do that, but I believe it is better to keep
262 // groups (only used in Buffer::reload for now (JMarc)
264 //d->group_level = 0;
268 bool Undo::hasUndoStack() const
270 return !d->undostack_.empty();
274 bool Undo::hasRedoStack() const
276 return !d->redostack_.empty();
280 void Undo::markDirty()
282 d->undo_finished_ = true;
283 d->undostack_.markDirty();
284 d->redostack_.markDirty();
288 /////////////////////////////////////////////////////////////////////
292 ///////////////////////////////////////////////////////////////////////
294 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
296 StableDocIterator tmpi2 = i2;
297 tmpi2.pos() = i1.pos();
302 void Undo::Private::doRecordUndo(UndoKind kind,
303 DocIterator const & cell,
304 pit_type first_pit, pit_type last_pit,
305 CursorData const & cur_before,
306 UndoElementStack & stack)
309 LYXERR0("There is no group open (creating one)");
313 if (first_pit > last_pit)
314 swap(first_pit, last_pit);
316 // Undo::ATOMIC are always recorded (no overlapping there).
317 // As nobody wants all removed character appear one by one when undoing,
318 // we want combine 'similar' non-ATOMIC undo recordings to one.
319 pit_type from = first_pit;
320 pit_type end = cell.lastpit() - last_pit;
322 && kind != ATOMIC_UNDO
324 && samePar(stack.top().cell, cell)
325 && stack.top().kind == kind
326 && stack.top().from == from
327 && stack.top().end == end) {
328 // reset cur_after; it will be filled correctly by endUndoGroup.
329 stack.top().cur_after = CursorData();
333 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
334 // create the position information of the Undo entry
335 UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
336 buffer_.isClean(), group_id);
338 // fill in the real data to be saved
339 if (cell.inMathed()) {
340 // simply use the whole cell
341 MathData & ar = cell.cell();
342 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
344 // some more effort needed here as 'the whole cell' of the
345 // main Text _is_ the whole document.
346 // record the relevant paragraphs
347 Text const * text = cell.text();
349 ParagraphList const & plist = text->paragraphs();
350 ParagraphList::const_iterator first = plist.begin();
351 advance(first, first_pit);
352 ParagraphList::const_iterator last = plist.begin();
353 advance(last, last_pit + 1);
354 undo.pars = new ParagraphList(first, last);
357 // push the undo entry to undo stack
359 //lyxerr << "undo record: " << stack.top() << endl;
363 void Undo::Private::recordUndo(UndoKind kind,
364 DocIterator const & cell,
365 pit_type first_pit, pit_type last_pit,
366 CursorData const & cur)
368 LASSERT(first_pit <= cell.lastpit(), return);
369 LASSERT(last_pit <= cell.lastpit(), return);
371 doRecordUndo(kind, cell, first_pit, last_pit, cur,
374 // next time we'll try again to combine entries if possible
375 undo_finished_ = false;
377 // If we ran recordUndo, it means that we plan to change the buffer
384 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
385 UndoElementStack & stack)
388 LYXERR0("There is no group open (creating one)");
392 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
393 // create the position information of the Undo entry
394 UndoElement undo(cur_before, buffer_.params(), buffer_.isClean(),
397 // push the undo entry to undo stack
402 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
404 doRecordUndoBufferParams(cur, undostack_);
406 // next time we'll try again to combine entries if possible
407 undo_finished_ = false;
409 // If we ran recordUndo, it means that we plan to change the buffer
416 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
418 // Adjust undo stack and get hold of current undo data.
419 UndoElement & undo = stack.top();
420 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
421 // We'll pop the stack only when we're done with this element. So do NOT
422 // try to return early.
424 // We will store in otherstack the part of the document under 'undo'
425 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
428 doRecordUndoBufferParams(undo.cur_after, otherstack);
430 doRecordUndo(ATOMIC_UNDO, cell_dit,
431 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
433 otherstack.top().cur_after = undo.cur_before;
435 // This does the actual undo/redo.
436 //LYXERR0("undo, performing: " << undo);
437 DocIterator dit = undo.cell.asDocIterator(&buffer_);
439 // This is a params undo element
440 delete otherstack.top().bparams;
441 otherstack.top().bparams = new BufferParams(buffer_.params());
442 buffer_.params() = *undo.bparams;
443 } else if (dit.inMathed()) {
444 // We stored the full cell here as there is not much to be
445 // gained by storing just 'a few' paragraphs (most if not
446 // all math inset cells have just one paragraph!)
447 //LYXERR0("undo.array: " << *undo.array);
449 dit.cell().swap(*undo.array);
453 // Some finer machinery is needed here.
454 Text * text = dit.text();
457 ParagraphList & plist = text->paragraphs();
459 // remove new stuff between first and last
460 ParagraphList::iterator first = plist.begin();
461 advance(first, undo.from);
462 ParagraphList::iterator last = plist.begin();
463 advance(last, plist.size() - undo.end);
464 plist.erase(first, last);
466 // re-insert old stuff instead
467 first = plist.begin();
468 advance(first, undo.from);
470 // this ugly stuff is needed until we get rid of the
471 // inset_owner backpointer
472 ParagraphList::iterator pit = undo.pars->begin();
473 ParagraphList::iterator const end = undo.pars->end();
474 for (; pit != end; ++pit)
475 pit->setInsetOwner(dit.realInset());
476 plist.insert(first, undo.pars->begin(), undo.pars->end());
481 // We'll clean up in release mode.
482 LASSERT(undo.pars == 0, undo.pars = 0);
483 LASSERT(undo.array == 0, undo.array = 0);
485 if (!undo.cur_before.empty())
486 cur = undo.cur_before;
491 // Now that we're done with undo, we pop it off the stack.
496 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
498 undo_finished_ = true;
500 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
506 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
508 const size_t gid = stack.top().group_id;
509 while (!stack.empty() && stack.top().group_id == gid)
510 doTextUndoOrRedo(cur, stack, otherstack);
512 // Adapt the new material to current buffer.
513 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
518 void Undo::finishUndo()
520 // Make sure the next operation will be stored.
521 d->undo_finished_ = true;
525 bool Undo::textUndo(CursorData & cur)
527 return d->textUndoOrRedo(cur, true);
531 bool Undo::textRedo(CursorData & cur)
533 return d->textUndoOrRedo(cur, false);
537 void Undo::beginUndoGroup()
539 if (d->group_level == 0) {
540 // create a new group
542 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
548 void Undo::endUndoGroup()
550 if (d->group_level == 0) {
551 LYXERR0("There is no undo group to end here");
555 if (d->group_level == 0) {
556 // real end of the group
557 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
562 void Undo::endUndoGroup(CursorData const & cur)
565 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
566 d->undostack_.top().cur_after = cur;
570 // FIXME: remove these convenience functions and make
571 // Private::recordUndo public as sole interface. The code in the
572 // convenience functions can move to Cursor.cpp.
574 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
576 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
580 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
583 if (!inset || inset == &cur.inset()) {
586 d->recordUndo(kind, c, c.pit(), c.pit(), cur);
587 } else if (inset == cur.nextInset())
588 recordUndo(cur, kind);
590 LYXERR0("Inset not found, no undo stack added.");
594 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
596 d->recordUndo(kind, cur, cur.pit(), from, cur);
600 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
601 pit_type from, pit_type to)
603 d->recordUndo(kind, cur, from, to, cur);
607 void Undo::recordUndoBufferParams(CursorData const & cur)
609 d->recordUndoBufferParams(cur);
613 void Undo::recordUndoFullBuffer(CursorData const & cur)
615 // This one may happen outside of the main undo group, so we
616 // put it in its own subgroup to avoid complaints.
618 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
619 0, d->buffer_.paragraphs().size() - 1, cur);
620 d->recordUndoBufferParams(cur);