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 "CutAndPaste.h"
25 #include "ErrorList.h"
26 #include "Paragraph.h"
27 #include "ParagraphList.h"
30 #include "mathed/MathSupport.h"
31 #include "mathed/MathData.h"
33 #include "insets/Inset.h"
34 #include "insets/InsetText.h"
36 #include "support/debug.h"
37 #include "support/gettext.h"
38 #include "support/lassert.h"
39 #include "support/lyxtime.h"
46 using namespace lyx::support;
52 These are the elements put on the undo stack. Each object contains
53 complete paragraphs from some cell and sufficient information to
54 restore the cursor state.
56 The cell is given by a DocIterator pointing to this cell, the
57 'interesting' range of paragraphs by counting them from begin and end
58 of cell, respectively.
60 The cursor is also given as DocIterator and should point to some place
61 in the stored paragraph range. In case of math, we simply store the
62 whole cell, as there usually is just a simple paragraph in a cell.
64 The idea is to store the contents of 'interesting' paragraphs in some
65 structure ('Undo') _before_ it is changed in some edit operation.
66 Obviously, the stored range should be as small as possible. However,
67 there is a lower limit: The StableDocIterator stored in the undo class
68 must be valid after the changes, too, as it will used as a pointer
69 where to insert the stored bits when performining undo.
74 UndoElement(UndoKind kin, CursorData const & cb,
75 StableDocIterator const & cel,
76 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
77 bool lc, size_t gid) :
78 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
79 pars(pl), array(ar), bparams(0),
80 lyx_clean(lc), group_id(gid), time(current_time())
84 UndoElement(CursorData const & cb, BufferParams const & bp,
85 bool lc, size_t gid) :
86 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
87 pars(0), array(0), bparams(new BufferParams(bp)),
88 lyx_clean(lc), group_id(gid), time(current_time())
92 UndoElement(UndoElement const & ue) : time(current_time())
95 cur_before = ue.cur_before;
96 cur_after = ue.cur_after;
103 ? new BufferParams(*ue.bparams) : 0;
104 lyx_clean = ue.lyx_clean;
105 group_id = ue.group_id;
113 /// Which kind of operation are we recording for?
115 /// the position of the cursor before recordUndo
116 CursorData cur_before;
117 /// the position of the cursor at the end of the undo group
118 CursorData cur_after;
119 /// the position of the cell described
120 StableDocIterator cell;
121 /// counted from begin of cell
123 /// complement to end of this cell
125 /// the contents of the saved Paragraphs (for texted)
126 ParagraphList * pars;
127 /// the contents of the saved MathData (for mathed)
129 /// Only used in case of params undo
130 BufferParams const * bparams;
131 /// Was the buffer clean at this point?
133 /// the element's group id
138 /// Protect construction
143 class UndoElementStack
146 /// limit is the maximum size of the stack
147 UndoElementStack(size_t limit = 100) { limit_ = limit; }
148 /// limit is the maximum size of the stack
149 ~UndoElementStack() { clear(); }
151 /// Return the top element.
152 UndoElement & top() { return c_.front(); }
154 /// Pop and throw away the top element.
155 void pop() { c_.pop_front(); }
157 /// Return true if the stack is empty.
158 bool empty() const { return c_.empty(); }
160 /// Clear all elements, deleting them.
162 for (size_t i = 0; i != c_.size(); ++i) {
169 /// Push an item on to the stack, deleting the bottom group on
171 void push(UndoElement const & v) {
172 // Remove some entries if the limit has been reached.
173 // However, if the only group on the stack is the one
174 // we are currently populating, do nothing.
175 if (c_.size() >= limit_
176 && c_.front().group_id != v.group_id) {
177 // remove a whole group at once.
178 const size_t gid = c_.back().group_id;
179 while (!c_.empty() && c_.back().group_id == gid)
185 /// Mark all the elements of the stack as dirty
187 for (size_t i = 0; i != c_.size(); ++i)
188 c_[i].lyx_clean = false;
192 /// Internal contents.
193 std::deque<UndoElement> c_;
194 /// The maximum number elements stored.
201 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
202 group_id_(0), group_level_(0) {}
204 // Do one undo/redo step
205 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
206 UndoElementStack & otherStack);
207 // Apply one undo/redo group. Returns false if no undo possible.
208 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
211 void doRecordUndo(UndoKind kind,
212 DocIterator const & cell,
215 CursorData const & cur,
216 UndoElementStack & stack);
218 void recordUndo(UndoKind kind,
219 DocIterator const & cell,
222 CursorData const & cur);
224 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
226 void recordUndoBufferParams(CursorData const & cur);
231 UndoElementStack undostack_;
233 UndoElementStack redostack_;
235 /// The flag used by Undo::finishUndo().
238 /// Current group Id.
240 /// Current group nesting nevel.
242 /// the position of cursor before the group was created
243 CursorData group_cur_before_;
247 /////////////////////////////////////////////////////////////////////
251 /////////////////////////////////////////////////////////////////////
254 Undo::Undo(Buffer & buffer)
255 : d(new Undo::Private(buffer))
267 d->undostack_.clear();
268 d->redostack_.clear();
269 d->undo_finished_ = true;
270 // We used to do that, but I believe it is better to keep
271 // groups (only used in Buffer::reload for now (JMarc)
273 //d->group_level_ = 0;
277 bool Undo::hasUndoStack() const
279 return !d->undostack_.empty();
283 bool Undo::hasRedoStack() const
285 return !d->redostack_.empty();
289 void Undo::markDirty()
291 d->undo_finished_ = true;
292 d->undostack_.markDirty();
293 d->redostack_.markDirty();
297 /////////////////////////////////////////////////////////////////////
301 ///////////////////////////////////////////////////////////////////////
303 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
305 StableDocIterator tmpi2 = i2;
306 tmpi2.pos() = i1.pos();
311 void Undo::Private::doRecordUndo(UndoKind kind,
312 DocIterator const & cell,
313 pit_type first_pit, pit_type last_pit,
314 CursorData const & cur_before,
315 UndoElementStack & stack)
318 LYXERR0("There is no group open (creating one)");
322 if (first_pit > last_pit)
323 swap(first_pit, last_pit);
325 // Undo::ATOMIC are always recorded (no overlapping there).
326 // As nobody wants all removed character appear one by one when undoing,
327 // we want combine 'similar' non-ATOMIC undo recordings to one.
328 pit_type from = first_pit;
329 pit_type end = cell.lastpit() - last_pit;
331 && kind != ATOMIC_UNDO
333 && !stack.top().bparams
334 && samePar(stack.top().cell, cell)
335 && stack.top().kind == kind
336 && stack.top().from == from
337 && stack.top().end == end
338 && stack.top().cur_after == cur_before
339 && current_time() - stack.top().time <= 2) {
340 // reset cur_after; it will be filled correctly by endUndoGroup.
341 stack.top().cur_after = CursorData();
342 // update the timestamp of the undo element
343 stack.top().time = current_time();
347 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
348 // create the position information of the Undo entry
349 UndoElement undo(kind,
350 group_cur_before_.empty() ? cur_before : group_cur_before_,
351 cell, from, end, 0, 0, buffer_.isClean(), group_id_);
353 // fill in the real data to be saved
354 if (cell.inMathed()) {
355 // simply use the whole cell
356 MathData & ar = cell.cell();
357 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
359 // some more effort needed here as 'the whole cell' of the
360 // main Text _is_ the whole document.
361 // record the relevant paragraphs
362 Text const * text = cell.text();
364 ParagraphList const & plist = text->paragraphs();
365 ParagraphList::const_iterator first = plist.begin();
366 advance(first, first_pit);
367 ParagraphList::const_iterator last = plist.begin();
368 advance(last, last_pit + 1);
369 undo.pars = new ParagraphList(first, last);
372 // push the undo entry to undo stack
374 //lyxerr << "undo record: " << stack.top() << endl;
378 void Undo::Private::recordUndo(UndoKind kind,
379 DocIterator const & cell,
380 pit_type first_pit, pit_type last_pit,
381 CursorData const & cur)
383 LASSERT(first_pit <= cell.lastpit(), return);
384 LASSERT(last_pit <= cell.lastpit(), return);
386 doRecordUndo(kind, cell, first_pit, last_pit, cur,
389 // next time we'll try again to combine entries if possible
390 undo_finished_ = false;
392 // If we ran recordUndo, it means that we plan to change the buffer
399 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
400 UndoElementStack & stack)
403 LYXERR0("There is no group open (creating one)");
407 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
408 // create the position information of the Undo entry
409 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
410 buffer_.params(), buffer_.isClean(),
413 // push the undo entry to undo stack
418 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
420 doRecordUndoBufferParams(cur, undostack_);
422 // next time we'll try again to combine entries if possible
423 undo_finished_ = false;
425 // If we ran recordUndo, it means that we plan to change the buffer
432 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
434 // Adjust undo stack and get hold of current undo data.
435 UndoElement & undo = stack.top();
436 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
437 // We'll pop the stack only when we're done with this element. So do NOT
438 // try to return early.
440 // We will store in otherstack the part of the document under 'undo'
441 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
444 doRecordUndoBufferParams(undo.cur_after, otherstack);
446 LATTEST(undo.end <= cell_dit.lastpit());
447 doRecordUndo(ATOMIC_UNDO, cell_dit,
448 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
451 otherstack.top().cur_after = undo.cur_before;
453 // This does the actual undo/redo.
454 //LYXERR0("undo, performing: " << undo);
455 DocIterator dit = undo.cell.asDocIterator(&buffer_);
457 // This is a params undo element
458 delete otherstack.top().bparams;
459 otherstack.top().bparams = new BufferParams(buffer_.params());
460 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
461 buffer_.params() = *undo.bparams;
462 // The error list is not supposed to be helpful here.
464 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
465 static_cast<InsetText &>(buffer_.inset()), el);
467 } else if (dit.inMathed()) {
468 // We stored the full cell here as there is not much to be
469 // gained by storing just 'a few' paragraphs (most if not
470 // all math inset cells have just one paragraph!)
471 //LYXERR0("undo.array: " << *undo.array);
473 dit.cell().swap(*undo.array);
477 // Some finer machinery is needed here.
478 Text * text = dit.text();
481 ParagraphList & plist = text->paragraphs();
483 // remove new stuff between first and last
484 ParagraphList::iterator first = plist.begin();
485 advance(first, undo.from);
486 ParagraphList::iterator last = plist.begin();
487 advance(last, plist.size() - undo.end);
488 plist.erase(first, last);
490 // re-insert old stuff instead
491 first = plist.begin();
492 advance(first, undo.from);
494 // this ugly stuff is needed until we get rid of the
495 // inset_owner backpointer
496 ParagraphList::iterator pit = undo.pars->begin();
497 ParagraphList::iterator const end = undo.pars->end();
498 for (; pit != end; ++pit)
499 pit->setInsetOwner(dit.realInset());
500 plist.insert(first, undo.pars->begin(), undo.pars->end());
502 // set the buffers for insets we created
503 ParagraphList::iterator fpit = plist.begin();
504 advance(fpit, undo.from);
505 ParagraphList::iterator fend = fpit;
506 advance(fend, undo.pars->size());
507 for (; fpit != fend; ++fpit)
508 fpit->setInsetBuffers(buffer_);
514 // We'll clean up in release mode.
515 LASSERT(undo.pars == 0, undo.pars = 0);
516 LASSERT(undo.array == 0, undo.array = 0);
518 if (!undo.cur_before.empty())
519 cur = undo.cur_before;
524 // Now that we're done with undo, we pop it off the stack.
529 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
531 undo_finished_ = true;
533 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
539 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
541 const size_t gid = stack.top().group_id;
542 while (!stack.empty() && stack.top().group_id == gid)
543 doTextUndoOrRedo(cur, stack, otherstack);
548 void Undo::finishUndo()
550 // Make sure the next operation will be stored.
551 d->undo_finished_ = true;
555 bool Undo::textUndo(CursorData & cur)
557 return d->textUndoOrRedo(cur, true);
561 bool Undo::textRedo(CursorData & cur)
563 return d->textUndoOrRedo(cur, false);
567 void Undo::beginUndoGroup()
569 if (d->group_level_ == 0) {
570 // create a new group
572 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id_);
578 void Undo::beginUndoGroup(CursorData const & cur_before)
581 if (d->group_cur_before_.empty())
582 d->group_cur_before_ = cur_before;
586 void Undo::endUndoGroup()
588 if (d->group_level_ == 0) {
589 LYXERR0("There is no undo group to end here");
593 if (d->group_level_ == 0) {
594 // real end of the group
595 d->group_cur_before_ = CursorData();
596 LYXERR(Debug::UNDO, "-------End of group " << d->group_id_);
601 void Undo::endUndoGroup(CursorData const & cur_after)
604 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
605 d->undostack_.top().cur_after = cur_after;
609 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
611 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
615 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
617 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
621 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
623 if (!inset || inset == &cur.inset()) {
626 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
627 } else if (inset == cur.nextInset())
630 LYXERR0("Inset not found, no undo stack added.");
634 void Undo::recordUndoBufferParams(CursorData const & cur)
636 d->recordUndoBufferParams(cur);
640 void Undo::recordUndoFullBuffer(CursorData const & cur)
642 // This one may happen outside of the main undo group, so we
643 // put it in its own subgroup to avoid complaints.
645 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
646 0, d->buffer_.paragraphs().size() - 1, cur);
647 d->recordUndoBufferParams(cur);
651 /// UndoGroupHelper class stuff
653 class UndoGroupHelper::Impl {
654 friend class UndoGroupHelper;
655 set<Buffer *> buffers_;
659 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
665 UndoGroupHelper::~UndoGroupHelper()
667 for (Buffer * buf : d->buffers_)
668 buf->undo().endUndoGroup();
672 void UndoGroupHelper::resetBuffer(Buffer * buf)
674 if (buf && d->buffers_.count(buf) == 0) {
675 d->buffers_.insert(buf);
676 buf->undo().beginUndoGroup();