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"
45 using namespace lyx::support;
51 These are the elements put on the undo stack. Each object contains
52 complete paragraphs from some cell and sufficient information to
53 restore the cursor state.
55 The cell is given by a DocIterator pointing to this cell, the
56 'interesting' range of paragraphs by counting them from begin and end
57 of cell, respectively.
59 The cursor is also given as DocIterator and should point to some place
60 in the stored paragraph range. In case of math, we simply store the
61 whole cell, as there usually is just a simple paragraph in a cell.
63 The idea is to store the contents of 'interesting' paragraphs in some
64 structure ('Undo') _before_ it is changed in some edit operation.
65 Obviously, the stored range should be as small as possible. However,
66 there is a lower limit: The StableDocIterator stored in the undo class
67 must be valid after the changes, too, as it will used as a pointer
68 where to insert the stored bits when performining undo.
73 UndoElement(UndoKind kin, CursorData const & cb,
74 StableDocIterator const & cel,
75 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
76 bool lc, size_t gid) :
77 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
78 pars(pl), array(ar), bparams(0),
79 lyx_clean(lc), group_id(gid), time(current_time())
83 UndoElement(CursorData const & cb, BufferParams const & bp,
84 bool lc, size_t gid) :
85 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
86 pars(0), array(0), bparams(new BufferParams(bp)),
87 lyx_clean(lc), group_id(gid), time(current_time())
91 UndoElement(UndoElement const & ue) : time(current_time())
94 cur_before = ue.cur_before;
95 cur_after = ue.cur_after;
102 ? new BufferParams(*ue.bparams) : 0;
103 lyx_clean = ue.lyx_clean;
104 group_id = ue.group_id;
112 /// Which kind of operation are we recording for?
114 /// the position of the cursor before recordUndo
115 CursorData cur_before;
116 /// the position of the cursor at the end of the undo group
117 CursorData cur_after;
118 /// the position of the cell described
119 StableDocIterator cell;
120 /// counted from begin of cell
122 /// complement to end of this cell
124 /// the contents of the saved Paragraphs (for texted)
125 ParagraphList * pars;
126 /// the contents of the saved MathData (for mathed)
128 /// Only used in case of params undo
129 BufferParams const * bparams;
130 /// Was the buffer clean at this point?
132 /// the element's group id
137 /// Protect construction
142 class UndoElementStack
145 /// limit is the maximum size of the stack
146 UndoElementStack(size_t limit = 100) { limit_ = limit; }
147 /// limit is the maximum size of the stack
148 ~UndoElementStack() { clear(); }
150 /// Return the top element.
151 UndoElement & top() { return c_.front(); }
153 /// Pop and throw away the top element.
154 void pop() { c_.pop_front(); }
156 /// Return true if the stack is empty.
157 bool empty() const { return c_.empty(); }
159 /// Clear all elements, deleting them.
161 for (size_t i = 0; i != c_.size(); ++i) {
168 /// Push an item on to the stack, deleting the bottom group on
170 void push(UndoElement const & v) {
171 // Remove some entries if the limit has been reached.
172 // However, if the only group on the stack is the one
173 // we are currently populating, do nothing.
174 if (c_.size() >= limit_
175 && c_.front().group_id != v.group_id) {
176 // remove a whole group at once.
177 const size_t gid = c_.back().group_id;
178 while (!c_.empty() && c_.back().group_id == gid)
184 /// Mark all the elements of the stack as dirty
186 for (size_t i = 0; i != c_.size(); ++i)
187 c_[i].lyx_clean = false;
191 /// Internal contents.
192 std::deque<UndoElement> c_;
193 /// The maximum number elements stored.
200 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
201 group_id_(0), group_level_(0) {}
203 // Do one undo/redo step
204 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
205 UndoElementStack & otherStack);
206 // Apply one undo/redo group. Returns false if no undo possible.
207 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
210 void doRecordUndo(UndoKind kind,
211 DocIterator const & cell,
214 CursorData const & cur,
215 UndoElementStack & stack);
217 void recordUndo(UndoKind kind,
218 DocIterator const & cell,
221 CursorData const & cur);
223 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
225 void recordUndoBufferParams(CursorData const & cur);
230 UndoElementStack undostack_;
232 UndoElementStack redostack_;
234 /// The flag used by Undo::finishUndo().
237 /// Current group Id.
239 /// Current group nesting nevel.
241 /// the position of cursor before the group was created
242 CursorData group_cur_before_;
246 /////////////////////////////////////////////////////////////////////
250 /////////////////////////////////////////////////////////////////////
253 Undo::Undo(Buffer & buffer)
254 : d(new Undo::Private(buffer))
266 d->undostack_.clear();
267 d->redostack_.clear();
268 d->undo_finished_ = true;
269 // We used to do that, but I believe it is better to keep
270 // groups (only used in Buffer::reload for now (JMarc)
272 //d->group_level_ = 0;
276 bool Undo::hasUndoStack() const
278 return !d->undostack_.empty();
282 bool Undo::hasRedoStack() const
284 return !d->redostack_.empty();
288 void Undo::markDirty()
290 d->undo_finished_ = true;
291 d->undostack_.markDirty();
292 d->redostack_.markDirty();
296 /////////////////////////////////////////////////////////////////////
300 ///////////////////////////////////////////////////////////////////////
302 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
304 StableDocIterator tmpi2 = i2;
305 tmpi2.pos() = i1.pos();
310 void Undo::Private::doRecordUndo(UndoKind kind,
311 DocIterator const & cell,
312 pit_type first_pit, pit_type last_pit,
313 CursorData const & cur_before,
314 UndoElementStack & stack)
317 LYXERR0("There is no group open (creating one)");
321 if (first_pit > last_pit)
322 swap(first_pit, last_pit);
324 // Undo::ATOMIC are always recorded (no overlapping there).
325 // As nobody wants all removed character appear one by one when undoing,
326 // we want combine 'similar' non-ATOMIC undo recordings to one.
327 pit_type from = first_pit;
328 pit_type end = cell.lastpit() - last_pit;
330 && kind != ATOMIC_UNDO
332 && !stack.top().bparams
333 && samePar(stack.top().cell, cell)
334 && stack.top().kind == kind
335 && stack.top().from == from
336 && stack.top().end == end
337 && stack.top().cur_after == cur_before
338 && current_time() - stack.top().time <= 2) {
339 // reset cur_after; it will be filled correctly by endUndoGroup.
340 stack.top().cur_after = CursorData();
341 // update the timestamp of the undo element
342 stack.top().time = current_time();
346 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
347 // create the position information of the Undo entry
348 UndoElement undo(kind,
349 group_cur_before_.empty() ? cur_before : group_cur_before_,
350 cell, from, end, 0, 0, buffer_.isClean(), group_id_);
352 // fill in the real data to be saved
353 if (cell.inMathed()) {
354 // simply use the whole cell
355 MathData & ar = cell.cell();
356 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
358 // some more effort needed here as 'the whole cell' of the
359 // main Text _is_ the whole document.
360 // record the relevant paragraphs
361 Text const * text = cell.text();
363 ParagraphList const & plist = text->paragraphs();
364 ParagraphList::const_iterator first = plist.begin();
365 advance(first, first_pit);
366 ParagraphList::const_iterator last = plist.begin();
367 advance(last, last_pit + 1);
368 undo.pars = new ParagraphList(first, last);
371 // push the undo entry to undo stack
373 //lyxerr << "undo record: " << stack.top() << endl;
377 void Undo::Private::recordUndo(UndoKind kind,
378 DocIterator const & cell,
379 pit_type first_pit, pit_type last_pit,
380 CursorData const & cur)
382 LASSERT(first_pit <= cell.lastpit(), return);
383 LASSERT(last_pit <= cell.lastpit(), return);
385 doRecordUndo(kind, cell, first_pit, last_pit, cur,
388 // next time we'll try again to combine entries if possible
389 undo_finished_ = false;
391 // If we ran recordUndo, it means that we plan to change the buffer
398 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
399 UndoElementStack & stack)
402 LYXERR0("There is no group open (creating one)");
406 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
407 // create the position information of the Undo entry
408 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
409 buffer_.params(), buffer_.isClean(),
412 // push the undo entry to undo stack
417 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
419 doRecordUndoBufferParams(cur, undostack_);
421 // next time we'll try again to combine entries if possible
422 undo_finished_ = false;
424 // If we ran recordUndo, it means that we plan to change the buffer
431 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
433 // Adjust undo stack and get hold of current undo data.
434 UndoElement & undo = stack.top();
435 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
436 // We'll pop the stack only when we're done with this element. So do NOT
437 // try to return early.
439 // We will store in otherstack the part of the document under 'undo'
440 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
443 doRecordUndoBufferParams(undo.cur_after, otherstack);
445 LATTEST(undo.end <= cell_dit.lastpit());
446 doRecordUndo(ATOMIC_UNDO, cell_dit,
447 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
450 otherstack.top().cur_after = undo.cur_before;
452 // This does the actual undo/redo.
453 //LYXERR0("undo, performing: " << undo);
454 DocIterator dit = undo.cell.asDocIterator(&buffer_);
456 // This is a params undo element
457 delete otherstack.top().bparams;
458 otherstack.top().bparams = new BufferParams(buffer_.params());
459 DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
460 buffer_.params() = *undo.bparams;
461 // The error list is not supposed to be helpful here.
463 cap::switchBetweenClasses(olddc, buffer_.params().documentClassPtr(),
464 static_cast<InsetText &>(buffer_.inset()), el);
466 } else if (dit.inMathed()) {
467 // We stored the full cell here as there is not much to be
468 // gained by storing just 'a few' paragraphs (most if not
469 // all math inset cells have just one paragraph!)
470 //LYXERR0("undo.array: " << *undo.array);
472 dit.cell().swap(*undo.array);
476 // Some finer machinery is needed here.
477 Text * text = dit.text();
480 ParagraphList & plist = text->paragraphs();
482 // remove new stuff between first and last
483 ParagraphList::iterator first = plist.begin();
484 advance(first, undo.from);
485 ParagraphList::iterator last = plist.begin();
486 advance(last, plist.size() - undo.end);
487 plist.erase(first, last);
489 // re-insert old stuff instead
490 first = plist.begin();
491 advance(first, undo.from);
493 // this ugly stuff is needed until we get rid of the
494 // inset_owner backpointer
495 ParagraphList::iterator pit = undo.pars->begin();
496 ParagraphList::iterator const end = undo.pars->end();
497 for (; pit != end; ++pit)
498 pit->setInsetOwner(dit.realInset());
499 plist.insert(first, undo.pars->begin(), undo.pars->end());
504 // We'll clean up in release mode.
505 LASSERT(undo.pars == 0, undo.pars = 0);
506 LASSERT(undo.array == 0, undo.array = 0);
508 if (!undo.cur_before.empty())
509 cur = undo.cur_before;
514 // Now that we're done with undo, we pop it off the stack.
519 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
521 undo_finished_ = true;
523 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
529 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
531 const size_t gid = stack.top().group_id;
532 while (!stack.empty() && stack.top().group_id == gid)
533 doTextUndoOrRedo(cur, stack, otherstack);
535 // Adapt the new material to current buffer.
536 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
541 void Undo::finishUndo()
543 // Make sure the next operation will be stored.
544 d->undo_finished_ = true;
548 bool Undo::textUndo(CursorData & cur)
550 return d->textUndoOrRedo(cur, true);
554 bool Undo::textRedo(CursorData & cur)
556 return d->textUndoOrRedo(cur, false);
560 void Undo::beginUndoGroup()
562 if (d->group_level_ == 0) {
563 // create a new group
565 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id_);
571 void Undo::beginUndoGroup(CursorData const & cur_before)
574 if (d->group_cur_before_.empty())
575 d->group_cur_before_ = cur_before;
579 void Undo::endUndoGroup()
581 if (d->group_level_ == 0) {
582 LYXERR0("There is no undo group to end here");
586 if (d->group_level_ == 0) {
587 // real end of the group
588 d->group_cur_before_ = CursorData();
589 LYXERR(Debug::UNDO, "-------End of group " << d->group_id_);
594 void Undo::endUndoGroup(CursorData const & cur_after)
597 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
598 d->undostack_.top().cur_after = cur_after;
602 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
604 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
608 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
610 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
614 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
616 if (!inset || inset == &cur.inset()) {
619 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
620 } else if (inset == cur.nextInset())
623 LYXERR0("Inset not found, no undo stack added.");
627 void Undo::recordUndoBufferParams(CursorData const & cur)
629 d->recordUndoBufferParams(cur);
633 void Undo::recordUndoFullBuffer(CursorData const & cur)
635 // This one may happen outside of the main undo group, so we
636 // put it in its own subgroup to avoid complaints.
638 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
639 0, d->buffer_.paragraphs().size() - 1, cur);
640 d->recordUndoBufferParams(cur);
644 /// UndoGroupHelper class stuff
646 void UndoGroupHelper::resetBuffer(Buffer * buf)
651 buffer_->undo().endUndoGroup();
654 buffer_->undo().beginUndoGroup();