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 "BufferList.h"
22 #include "BufferParams.h"
23 #include "buffer_funcs.h"
25 #include "CutAndPaste.h"
26 #include "ErrorList.h"
27 #include "Paragraph.h"
28 #include "ParagraphList.h"
31 #include "mathed/MathSupport.h"
32 #include "mathed/MathData.h"
34 #include "insets/Inset.h"
35 #include "insets/InsetText.h"
37 #include "support/debug.h"
38 #include "support/gettext.h"
39 #include "support/lassert.h"
40 #include "support/lyxtime.h"
47 using namespace lyx::support;
53 These are the elements put on the undo stack. Each object contains
54 complete paragraphs from some cell and sufficient information to
55 restore the cursor state.
57 The cell is given by a DocIterator pointing to this cell, the
58 'interesting' range of paragraphs by counting them from begin and end
59 of cell, respectively.
61 The cursor is also given as DocIterator and should point to some place
62 in the stored paragraph range. In case of math, we simply store the
63 whole cell, as there usually is just a simple paragraph in a cell.
65 The idea is to store the contents of 'interesting' paragraphs in some
66 structure ('Undo') _before_ it is changed in some edit operation.
67 Obviously, the stored range should be as small as possible. However,
68 there is a lower limit: The StableDocIterator stored in the undo class
69 must be valid after the changes, too, as it will used as a pointer
70 where to insert the stored bits when performining undo.
75 UndoElement(UndoKind kin, CursorData const & cb,
76 StableDocIterator const & cel,
77 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
78 bool lc, size_t gid) :
79 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
80 pars(pl), array(ar), bparams(0),
81 lyx_clean(lc), group_id(gid), time(current_time())
85 UndoElement(CursorData const & cb, BufferParams const & bp,
86 bool lc, size_t gid) :
87 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
88 pars(0), array(0), bparams(new BufferParams(bp)),
89 lyx_clean(lc), group_id(gid), time(current_time())
93 UndoElement(UndoElement const & ue) :
95 cur_before(ue.cur_before), cur_after(ue.cur_after),
96 cell(ue.cell), from(ue.from), end(ue.end),
97 pars(ue.pars), array(ue.array),
98 bparams(ue.bparams ? new BufferParams(*ue.bparams) : 0),
99 lyx_clean(ue.lyx_clean), 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 params undo
125 BufferParams const * bparams;
126 /// Was the buffer clean at this point?
128 /// the element's group id
133 /// Protect construction
138 class UndoElementStack
141 /// limit is the maximum size of the stack
142 UndoElementStack(size_t limit = 100) { limit_ = limit; }
143 /// limit is the maximum size of the stack
144 ~UndoElementStack() { clear(); }
146 /// Return the top element.
147 UndoElement & top() { return c_.front(); }
149 /// Pop and throw away the top element.
150 void pop() { c_.pop_front(); }
152 /// Return true if the stack is empty.
153 bool empty() const { return c_.empty(); }
155 /// Clear all elements, deleting them.
157 for (size_t i = 0; i != c_.size(); ++i) {
164 /// Push an item on to the stack, deleting the bottom group on
166 void push(UndoElement const & v) {
167 // Remove some entries if the limit has been reached.
168 // However, if the only group on the stack is the one
169 // we are currently populating, do nothing.
170 if (c_.size() >= limit_
171 && c_.front().group_id != v.group_id) {
172 // remove a whole group at once.
173 const size_t gid = c_.back().group_id;
174 while (!c_.empty() && c_.back().group_id == gid)
180 /// Mark all the elements of the stack as dirty
182 for (size_t i = 0; i != c_.size(); ++i)
183 c_[i].lyx_clean = false;
187 /// Internal contents.
188 std::deque<UndoElement> c_;
189 /// The maximum number elements stored.
196 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
197 group_id_(0), group_level_(0) {}
199 // Do one undo/redo step
200 void doUndoRedoAction(CursorData & cur, UndoElementStack & stack,
201 UndoElementStack & otherStack);
202 // Apply one undo/redo group. Returns false if no undo possible.
203 bool undoRedoAction(CursorData & cur, bool isUndoOperation);
206 void doRecordUndo(UndoKind kind,
207 DocIterator const & cell,
210 CursorData const & cur,
211 UndoElementStack & stack);
213 void recordUndo(UndoKind kind,
214 DocIterator const & cell,
217 CursorData const & cur);
219 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
221 void recordUndoBufferParams(CursorData const & cur);
226 UndoElementStack undostack_;
228 UndoElementStack redostack_;
230 /// The flag used by Undo::finishUndo().
233 /// Current group Id.
235 /// Current group nesting nevel.
237 /// the position of cursor before the group was created
238 CursorData group_cur_before_;
242 /////////////////////////////////////////////////////////////////////
246 /////////////////////////////////////////////////////////////////////
249 Undo::Undo(Buffer & buffer)
250 : d(new Undo::Private(buffer))
262 d->undostack_.clear();
263 d->redostack_.clear();
264 d->undo_finished_ = true;
265 // We used to do that, but I believe it is better to keep
266 // groups (only used in Buffer::reload for now (JMarc)
268 //d->group_level_ = 0;
272 bool Undo::hasUndoStack() const
274 return !d->undostack_.empty();
278 bool Undo::hasRedoStack() const
280 return !d->redostack_.empty();
284 void Undo::markDirty()
286 d->undo_finished_ = true;
287 d->undostack_.markDirty();
288 d->redostack_.markDirty();
292 /////////////////////////////////////////////////////////////////////
296 ///////////////////////////////////////////////////////////////////////
298 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
300 StableDocIterator tmpi2 = i2;
301 tmpi2.pos() = i1.pos();
306 void Undo::Private::doRecordUndo(UndoKind kind,
307 DocIterator const & cell,
308 pit_type first_pit, pit_type last_pit,
309 CursorData const & cur_before,
310 UndoElementStack & stack)
313 LYXERR0("There is no group open (creating one)");
317 if (first_pit > last_pit)
318 swap(first_pit, last_pit);
320 // Undo::ATOMIC are always recorded (no overlapping there).
321 // As nobody wants all removed character appear one by one when undoing,
322 // we want combine 'similar' non-ATOMIC undo recordings to one.
323 pit_type from = first_pit;
324 pit_type end = cell.lastpit() - last_pit;
326 && kind != ATOMIC_UNDO
328 && !stack.top().bparams
329 && samePar(stack.top().cell, cell)
330 && stack.top().kind == kind
331 && stack.top().from == from
332 && stack.top().end == end
333 && stack.top().cur_after == cur_before
334 && current_time() - stack.top().time <= 2) {
335 // reset cur_after; it will be filled correctly by endUndoGroup.
336 stack.top().cur_after = CursorData();
337 // update the timestamp of the undo element
338 stack.top().time = current_time();
342 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
343 // create the position information of the Undo entry
344 UndoElement undo(kind,
345 group_cur_before_.empty() ? cur_before : group_cur_before_,
346 cell, from, end, 0, 0, buffer_.isClean(), group_id_);
348 // fill in the real data to be saved
349 if (cell.inMathed()) {
350 // simply use the whole cell
351 MathData & ar = cell.cell();
352 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
354 // some more effort needed here as 'the whole cell' of the
355 // main Text _is_ the whole document.
356 // record the relevant paragraphs
357 Text const * text = cell.text();
359 ParagraphList const & plist = text->paragraphs();
360 ParagraphList::const_iterator first = plist.begin();
361 advance(first, first_pit);
362 ParagraphList::const_iterator last = plist.begin();
363 advance(last, last_pit + 1);
364 undo.pars = new ParagraphList(first, last);
367 // push the undo entry to undo stack
369 //lyxerr << "undo record: " << stack.top() << endl;
373 void Undo::Private::recordUndo(UndoKind kind,
374 DocIterator const & cell,
375 pit_type first_pit, pit_type last_pit,
376 CursorData const & cur)
378 LASSERT(first_pit <= cell.lastpit(), return);
379 LASSERT(last_pit <= cell.lastpit(), return);
381 doRecordUndo(kind, cell, first_pit, last_pit, cur,
384 // next time we'll try again to combine entries if possible
385 undo_finished_ = false;
387 // If we ran recordUndo, it means that we plan to change the buffer
394 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
395 UndoElementStack & stack)
398 LYXERR0("There is no group open (creating one)");
402 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
403 // create the position information of the Undo entry
404 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
405 buffer_.params(), buffer_.isClean(),
408 // push the undo entry to undo stack
413 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
415 doRecordUndoBufferParams(cur, undostack_);
417 // next time we'll try again to combine entries if possible
418 undo_finished_ = false;
420 // If we ran recordUndo, it means that we plan to change the buffer
427 void Undo::Private::doUndoRedoAction(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
429 // Adjust undo stack and get hold of current undo data.
430 UndoElement & undo = stack.top();
431 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
432 // We'll pop the stack only when we're done with this element. So do NOT
433 // try to return early.
435 // We will store in otherstack the part of the document under 'undo'
436 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
439 doRecordUndoBufferParams(undo.cur_after, otherstack);
441 LATTEST(undo.end <= cell_dit.lastpit());
442 doRecordUndo(ATOMIC_UNDO, cell_dit,
443 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
446 otherstack.top().cur_after = undo.cur_before;
448 // This does the actual undo/redo.
449 //LYXERR0("undo, performing: " << undo);
450 DocIterator dit = undo.cell.asDocIterator(&buffer_);
452 // This is a params undo element
453 delete otherstack.top().bparams;
454 otherstack.top().bparams = new BufferParams(buffer_.params());
455 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
456 buffer_.params() = *undo.bparams;
457 // The error list is not supposed to be helpful here.
459 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
460 static_cast<InsetText &>(buffer_.inset()), el);
462 } else if (dit.inMathed()) {
463 // We stored the full cell here as there is not much to be
464 // gained by storing just 'a few' paragraphs (most if not
465 // all math inset cells have just one paragraph!)
466 //LYXERR0("undo.array: " << *undo.array);
468 dit.cell().swap(*undo.array);
469 dit.inset().setBuffer(buffer_);
473 // Some finer machinery is needed here.
474 Text * text = dit.text();
477 ParagraphList & plist = text->paragraphs();
479 // remove new stuff between first and last
480 ParagraphList::iterator first = plist.begin();
481 advance(first, undo.from);
482 ParagraphList::iterator last = plist.begin();
483 advance(last, plist.size() - undo.end);
484 plist.erase(first, last);
486 // re-insert old stuff instead
487 first = plist.begin();
488 advance(first, undo.from);
490 // this ugly stuff is needed until we get rid of the
491 // inset_owner backpointer
492 ParagraphList::iterator pit = undo.pars->begin();
493 ParagraphList::iterator const end = undo.pars->end();
494 for (; pit != end; ++pit)
495 pit->setInsetOwner(dit.realInset());
496 plist.insert(first, undo.pars->begin(), undo.pars->end());
498 // set the buffers for insets we created
499 ParagraphList::iterator fpit = plist.begin();
500 advance(fpit, undo.from);
501 ParagraphList::iterator fend = fpit;
502 advance(fend, undo.pars->size());
503 for (; fpit != fend; ++fpit)
504 fpit->setInsetBuffers(buffer_);
510 // We'll clean up in release mode.
511 LASSERT(undo.pars == 0, undo.pars = 0);
512 LASSERT(undo.array == 0, undo.array = 0);
514 if (!undo.cur_before.empty())
515 cur = undo.cur_before;
520 // Now that we're done with undo, we pop it off the stack.
525 bool Undo::Private::undoRedoAction(CursorData & cur, bool isUndoOperation)
527 undo_finished_ = true;
529 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
535 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
537 const size_t gid = stack.top().group_id;
538 while (!stack.empty() && stack.top().group_id == gid)
539 doUndoRedoAction(cur, stack, otherstack);
544 void Undo::finishUndo()
546 // Make sure the next operation will be stored.
547 d->undo_finished_ = true;
551 bool Undo::undoAction(CursorData & cur)
553 return d->undoRedoAction(cur, true);
557 bool Undo::redoAction(CursorData & cur)
559 return d->undoRedoAction(cur, false);
563 void Undo::beginUndoGroup()
565 if (d->group_level_ == 0) {
566 // create a new group
568 LYXERR(Debug::UNDO, "+++++++ Creating new group " << d->group_id_
569 << " for buffer " << &d->buffer_);
575 void Undo::beginUndoGroup(CursorData const & cur_before)
578 if (d->group_cur_before_.empty())
579 d->group_cur_before_ = cur_before;
583 void Undo::endUndoGroup()
585 if (d->group_level_ == 0) {
586 LYXERR0("There is no undo group to end here");
590 if (d->group_level_ == 0) {
591 // real end of the group
592 d->group_cur_before_ = CursorData();
593 LYXERR(Debug::UNDO, "------- End of group " << d->group_id_
594 << " of buffer " << &d->buffer_);
599 void Undo::endUndoGroup(CursorData const & cur_after)
602 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
603 d->undostack_.top().cur_after = cur_after;
607 void Undo::splitUndoGroup(CursorData const & cur)
609 size_t const level = d->group_level_;
613 d->group_level_ = level;
617 bool Undo::activeUndoGroup() const
619 return d->group_level_ > 0
620 && !d->undostack_.empty()
621 && d->undostack_.top().group_id == d->group_id_;
625 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
627 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
631 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
633 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
637 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
639 if (!inset || inset == &cur.inset()) {
642 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
643 } else if (inset == cur.nextInset())
646 LYXERR0("Inset not found, no undo stack added.");
650 void Undo::recordUndoBufferParams(CursorData const & cur)
652 d->recordUndoBufferParams(cur);
656 void Undo::recordUndoFullBuffer(CursorData const & cur)
658 // This one may happen outside of the main undo group, so we
659 // put it in its own subgroup to avoid complaints.
661 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
662 0, d->buffer_.paragraphs().size() - 1, cur);
663 d->recordUndoBufferParams(cur);
667 /// UndoGroupHelper class stuff
669 class UndoGroupHelper::Impl {
670 friend class UndoGroupHelper;
671 set<Buffer *> buffers_;
675 UndoGroupHelper::UndoGroupHelper(Buffer * buf) : d(new UndoGroupHelper::Impl)
681 UndoGroupHelper::~UndoGroupHelper()
683 for (Buffer * buf : d->buffers_)
684 if (theBufferList().isLoaded(buf) || theBufferList().isInternal(buf))
685 buf->undo().endUndoGroup();
689 void UndoGroupHelper::resetBuffer(Buffer * buf)
691 if (buf && d->buffers_.count(buf) == 0) {
692 d->buffers_.insert(buf);
693 buf->undo().beginUndoGroup();