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"
36 #include "support/lyxtime.h"
42 using namespace lyx::support;
48 These are the elements put on the undo stack. Each object contains
49 complete paragraphs from some cell and sufficient information to
50 restore the cursor state.
52 The cell is given by a DocIterator pointing to this cell, the
53 'interesting' range of paragraphs by counting them from begin and end
54 of cell, respectively.
56 The cursor is also given as DocIterator and should point to some place
57 in the stored paragraph range. In case of math, we simply store the
58 whole cell, as there usually is just a simple paragraph in a cell.
60 The idea is to store the contents of 'interesting' paragraphs in some
61 structure ('Undo') _before_ it is changed in some edit operation.
62 Obviously, the stored range should be as small as possible. However,
63 there is a lower limit: The StableDocIterator stored in the undo class
64 must be valid after the changes, too, as it will used as a pointer
65 where to insert the stored bits when performining undo.
70 UndoElement(UndoKind kin, CursorData const & cb,
71 StableDocIterator const & cel,
72 pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
73 bool lc, size_t gid) :
74 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
75 pars(pl), array(ar), bparams(0),
76 lyx_clean(lc), group_id(gid), time(current_time())
80 UndoElement(CursorData const & cb, BufferParams const & bp,
81 bool lc, size_t gid) :
82 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
83 pars(0), array(0), bparams(new BufferParams(bp)),
84 lyx_clean(lc), group_id(gid), time(current_time())
88 UndoElement(UndoElement const & ue) : time(current_time())
91 cur_before = ue.cur_before;
92 cur_after = ue.cur_after;
99 ? new BufferParams(*ue.bparams) : 0;
100 lyx_clean = ue.lyx_clean;
101 group_id = ue.group_id;
109 /// Which kind of operation are we recording for?
111 /// the position of the cursor before recordUndo
112 CursorData cur_before;
113 /// the position of the cursor at the end of the undo group
114 CursorData cur_after;
115 /// the position of the cell described
116 StableDocIterator cell;
117 /// counted from begin of cell
119 /// complement to end of this cell
121 /// the contents of the saved Paragraphs (for texted)
122 ParagraphList * pars;
123 /// the contents of the saved MathData (for mathed)
125 /// Only used in case of params undo
126 BufferParams const * bparams;
127 /// Was the buffer clean at this point?
129 /// the element's group id
134 /// Protect construction
139 class UndoElementStack
142 /// limit is the maximum size of the stack
143 UndoElementStack(size_t limit = 100) { limit_ = limit; }
144 /// limit is the maximum size of the stack
145 ~UndoElementStack() { clear(); }
147 /// Return the top element.
148 UndoElement & top() { return c_.front(); }
150 /// Pop and throw away the top element.
151 void pop() { c_.pop_front(); }
153 /// Return true if the stack is empty.
154 bool empty() const { return c_.empty(); }
156 /// Clear all elements, deleting them.
158 for (size_t i = 0; i != c_.size(); ++i) {
165 /// Push an item on to the stack, deleting the bottom group on
167 void push(UndoElement const & v) {
168 // Remove some entries if the limit has been reached.
169 // However, if the only group on the stack is the one
170 // we are currently populating, do nothing.
171 if (c_.size() >= limit_
172 && c_.front().group_id != v.group_id) {
173 // remove a whole group at once.
174 const size_t gid = c_.back().group_id;
175 while (!c_.empty() && c_.back().group_id == gid)
181 /// Mark all the elements of the stack as dirty
183 for (size_t i = 0; i != c_.size(); ++i)
184 c_[i].lyx_clean = false;
188 /// Internal contents.
189 std::deque<UndoElement> c_;
190 /// The maximum number elements stored.
197 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
198 group_id(0), group_level(0) {}
200 // Do one undo/redo step
201 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
202 UndoElementStack & otherStack);
203 // Apply one undo/redo group. Returns false if no undo possible.
204 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
207 void doRecordUndo(UndoKind kind,
208 DocIterator const & cell,
211 CursorData const & cur,
212 UndoElementStack & stack);
214 void recordUndo(UndoKind kind,
215 DocIterator const & cell,
218 CursorData const & cur);
220 void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
222 void recordUndoBufferParams(CursorData const & cur);
227 UndoElementStack undostack_;
229 UndoElementStack redostack_;
231 /// The flag used by Undo::finishUndo().
234 /// Current group Id.
236 /// Current group nesting nevel.
241 /////////////////////////////////////////////////////////////////////
245 /////////////////////////////////////////////////////////////////////
248 Undo::Undo(Buffer & buffer)
249 : d(new Undo::Private(buffer))
261 d->undostack_.clear();
262 d->redostack_.clear();
263 d->undo_finished_ = true;
264 // We used to do that, but I believe it is better to keep
265 // groups (only used in Buffer::reload for now (JMarc)
267 //d->group_level = 0;
271 bool Undo::hasUndoStack() const
273 return !d->undostack_.empty();
277 bool Undo::hasRedoStack() const
279 return !d->redostack_.empty();
283 void Undo::markDirty()
285 d->undo_finished_ = true;
286 d->undostack_.markDirty();
287 d->redostack_.markDirty();
291 /////////////////////////////////////////////////////////////////////
295 ///////////////////////////////////////////////////////////////////////
297 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
299 StableDocIterator tmpi2 = i2;
300 tmpi2.pos() = i1.pos();
305 void Undo::Private::doRecordUndo(UndoKind kind,
306 DocIterator const & cell,
307 pit_type first_pit, pit_type last_pit,
308 CursorData const & cur_before,
309 UndoElementStack & stack)
312 LYXERR0("There is no group open (creating one)");
316 if (first_pit > last_pit)
317 swap(first_pit, last_pit);
319 // Undo::ATOMIC are always recorded (no overlapping there).
320 // As nobody wants all removed character appear one by one when undoing,
321 // we want combine 'similar' non-ATOMIC undo recordings to one.
322 pit_type from = first_pit;
323 pit_type end = cell.lastpit() - last_pit;
325 && kind != ATOMIC_UNDO
327 && !stack.top().bparams
328 && samePar(stack.top().cell, cell)
329 && stack.top().kind == kind
330 && stack.top().from == from
331 && stack.top().end == end
332 && stack.top().cur_after == cur_before
333 && current_time() - stack.top().time <= 2) {
334 // reset cur_after; it will be filled correctly by endUndoGroup.
335 stack.top().cur_after = CursorData();
336 // update the timestamp of the undo element
337 stack.top().time = current_time();
341 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
342 // create the position information of the Undo entry
343 UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
344 buffer_.isClean(), group_id);
346 // fill in the real data to be saved
347 if (cell.inMathed()) {
348 // simply use the whole cell
349 MathData & ar = cell.cell();
350 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
352 // some more effort needed here as 'the whole cell' of the
353 // main Text _is_ the whole document.
354 // record the relevant paragraphs
355 Text const * text = cell.text();
357 ParagraphList const & plist = text->paragraphs();
358 ParagraphList::const_iterator first = plist.begin();
359 advance(first, first_pit);
360 ParagraphList::const_iterator last = plist.begin();
361 advance(last, last_pit + 1);
362 undo.pars = new ParagraphList(first, last);
365 // push the undo entry to undo stack
367 //lyxerr << "undo record: " << stack.top() << endl;
371 void Undo::Private::recordUndo(UndoKind kind,
372 DocIterator const & cell,
373 pit_type first_pit, pit_type last_pit,
374 CursorData const & cur)
376 LASSERT(first_pit <= cell.lastpit(), return);
377 LASSERT(last_pit <= cell.lastpit(), return);
379 doRecordUndo(kind, cell, first_pit, last_pit, cur,
382 // next time we'll try again to combine entries if possible
383 undo_finished_ = false;
385 // If we ran recordUndo, it means that we plan to change the buffer
392 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
393 UndoElementStack & stack)
396 LYXERR0("There is no group open (creating one)");
400 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
401 // create the position information of the Undo entry
402 UndoElement undo(cur_before, buffer_.params(), buffer_.isClean(),
405 // push the undo entry to undo stack
410 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
412 doRecordUndoBufferParams(cur, undostack_);
414 // next time we'll try again to combine entries if possible
415 undo_finished_ = false;
417 // If we ran recordUndo, it means that we plan to change the buffer
424 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
426 // Adjust undo stack and get hold of current undo data.
427 UndoElement & undo = stack.top();
428 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
429 // We'll pop the stack only when we're done with this element. So do NOT
430 // try to return early.
432 // We will store in otherstack the part of the document under 'undo'
433 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
436 doRecordUndoBufferParams(undo.cur_after, otherstack);
438 LATTEST(undo.end <= cell_dit.lastpit());
439 doRecordUndo(ATOMIC_UNDO, cell_dit,
440 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
443 otherstack.top().cur_after = undo.cur_before;
445 // This does the actual undo/redo.
446 //LYXERR0("undo, performing: " << undo);
447 DocIterator dit = undo.cell.asDocIterator(&buffer_);
449 // This is a params undo element
450 delete otherstack.top().bparams;
451 otherstack.top().bparams = new BufferParams(buffer_.params());
452 buffer_.params() = *undo.bparams;
453 } else if (dit.inMathed()) {
454 // We stored the full cell here as there is not much to be
455 // gained by storing just 'a few' paragraphs (most if not
456 // all math inset cells have just one paragraph!)
457 //LYXERR0("undo.array: " << *undo.array);
459 dit.cell().swap(*undo.array);
463 // Some finer machinery is needed here.
464 Text * text = dit.text();
467 ParagraphList & plist = text->paragraphs();
469 // remove new stuff between first and last
470 ParagraphList::iterator first = plist.begin();
471 advance(first, undo.from);
472 ParagraphList::iterator last = plist.begin();
473 advance(last, plist.size() - undo.end);
474 plist.erase(first, last);
476 // re-insert old stuff instead
477 first = plist.begin();
478 advance(first, undo.from);
480 // this ugly stuff is needed until we get rid of the
481 // inset_owner backpointer
482 ParagraphList::iterator pit = undo.pars->begin();
483 ParagraphList::iterator const end = undo.pars->end();
484 for (; pit != end; ++pit)
485 pit->setInsetOwner(dit.realInset());
486 plist.insert(first, undo.pars->begin(), undo.pars->end());
491 // We'll clean up in release mode.
492 LASSERT(undo.pars == 0, undo.pars = 0);
493 LASSERT(undo.array == 0, undo.array = 0);
495 if (!undo.cur_before.empty())
496 cur = undo.cur_before;
501 // Now that we're done with undo, we pop it off the stack.
506 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
508 undo_finished_ = true;
510 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
516 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
518 const size_t gid = stack.top().group_id;
519 while (!stack.empty() && stack.top().group_id == gid)
520 doTextUndoOrRedo(cur, stack, otherstack);
522 // Adapt the new material to current buffer.
523 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
528 void Undo::finishUndo()
530 // Make sure the next operation will be stored.
531 d->undo_finished_ = true;
535 bool Undo::textUndo(CursorData & cur)
537 return d->textUndoOrRedo(cur, true);
541 bool Undo::textRedo(CursorData & cur)
543 return d->textUndoOrRedo(cur, false);
547 void Undo::beginUndoGroup()
549 if (d->group_level == 0) {
550 // create a new group
552 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
558 void Undo::endUndoGroup()
560 if (d->group_level == 0) {
561 LYXERR0("There is no undo group to end here");
565 if (d->group_level == 0) {
566 // real end of the group
567 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
572 void Undo::endUndoGroup(CursorData const & cur)
575 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
576 d->undostack_.top().cur_after = cur;
580 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
582 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
586 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
588 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
592 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
594 if (!inset || inset == &cur.inset()) {
597 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
598 } else if (inset == cur.nextInset())
601 LYXERR0("Inset not found, no undo stack added.");
605 void Undo::recordUndoBufferParams(CursorData const & cur)
607 d->recordUndoBufferParams(cur);
611 void Undo::recordUndoFullBuffer(CursorData const & cur)
613 // This one may happen outside of the main undo group, so we
614 // put it in its own subgroup to avoid complaints.
616 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
617 0, d->buffer_.paragraphs().size() - 1, cur);
618 d->recordUndoBufferParams(cur);
622 /// UndoGroupHelper class stuff
624 void UndoGroupHelper::resetBuffer(Buffer * buf)
629 buffer_->undo().endUndoGroup();
632 buffer_->undo().beginUndoGroup();