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.
238 /// the position of cursor before the group was created
239 CursorData group_cur_before_;
243 /////////////////////////////////////////////////////////////////////
247 /////////////////////////////////////////////////////////////////////
250 Undo::Undo(Buffer & buffer)
251 : d(new Undo::Private(buffer))
263 d->undostack_.clear();
264 d->redostack_.clear();
265 d->undo_finished_ = true;
266 // We used to do that, but I believe it is better to keep
267 // groups (only used in Buffer::reload for now (JMarc)
269 //d->group_level_ = 0;
273 bool Undo::hasUndoStack() const
275 return !d->undostack_.empty();
279 bool Undo::hasRedoStack() const
281 return !d->redostack_.empty();
285 void Undo::markDirty()
287 d->undo_finished_ = true;
288 d->undostack_.markDirty();
289 d->redostack_.markDirty();
293 /////////////////////////////////////////////////////////////////////
297 ///////////////////////////////////////////////////////////////////////
299 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
301 StableDocIterator tmpi2 = i2;
302 tmpi2.pos() = i1.pos();
307 void Undo::Private::doRecordUndo(UndoKind kind,
308 DocIterator const & cell,
309 pit_type first_pit, pit_type last_pit,
310 CursorData const & cur_before,
311 UndoElementStack & stack)
314 LYXERR0("There is no group open (creating one)");
318 if (first_pit > last_pit)
319 swap(first_pit, last_pit);
321 // Undo::ATOMIC are always recorded (no overlapping there).
322 // As nobody wants all removed character appear one by one when undoing,
323 // we want combine 'similar' non-ATOMIC undo recordings to one.
324 pit_type from = first_pit;
325 pit_type end = cell.lastpit() - last_pit;
327 && kind != ATOMIC_UNDO
329 && !stack.top().bparams
330 && samePar(stack.top().cell, cell)
331 && stack.top().kind == kind
332 && stack.top().from == from
333 && stack.top().end == end
334 && stack.top().cur_after == cur_before
335 && current_time() - stack.top().time <= 2) {
336 // reset cur_after; it will be filled correctly by endUndoGroup.
337 stack.top().cur_after = CursorData();
338 // update the timestamp of the undo element
339 stack.top().time = current_time();
343 LYXERR(Debug::UNDO, "Create undo element of group " << group_id_);
344 // create the position information of the Undo entry
345 UndoElement undo(kind,
346 group_cur_before_.empty() ? cur_before : group_cur_before_,
347 cell, from, end, 0, 0, buffer_.isClean(), group_id_);
349 // fill in the real data to be saved
350 if (cell.inMathed()) {
351 // simply use the whole cell
352 MathData & ar = cell.cell();
353 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
355 // some more effort needed here as 'the whole cell' of the
356 // main Text _is_ the whole document.
357 // record the relevant paragraphs
358 Text const * text = cell.text();
360 ParagraphList const & plist = text->paragraphs();
361 ParagraphList::const_iterator first = plist.begin();
362 advance(first, first_pit);
363 ParagraphList::const_iterator last = plist.begin();
364 advance(last, last_pit + 1);
365 undo.pars = new ParagraphList(first, last);
368 // push the undo entry to undo stack
370 //lyxerr << "undo record: " << stack.top() << endl;
374 void Undo::Private::recordUndo(UndoKind kind,
375 DocIterator const & cell,
376 pit_type first_pit, pit_type last_pit,
377 CursorData const & cur)
379 LASSERT(first_pit <= cell.lastpit(), return);
380 LASSERT(last_pit <= cell.lastpit(), return);
382 doRecordUndo(kind, cell, first_pit, last_pit, cur,
385 // next time we'll try again to combine entries if possible
386 undo_finished_ = false;
388 // If we ran recordUndo, it means that we plan to change the buffer
395 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
396 UndoElementStack & stack)
399 LYXERR0("There is no group open (creating one)");
403 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id_);
404 // create the position information of the Undo entry
405 UndoElement undo(group_cur_before_.empty() ? cur_before : group_cur_before_,
406 buffer_.params(), buffer_.isClean(),
409 // push the undo entry to undo stack
414 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
416 doRecordUndoBufferParams(cur, undostack_);
418 // next time we'll try again to combine entries if possible
419 undo_finished_ = false;
421 // If we ran recordUndo, it means that we plan to change the buffer
428 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
430 // Adjust undo stack and get hold of current undo data.
431 UndoElement & undo = stack.top();
432 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
433 // We'll pop the stack only when we're done with this element. So do NOT
434 // try to return early.
436 // We will store in otherstack the part of the document under 'undo'
437 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
440 doRecordUndoBufferParams(undo.cur_after, otherstack);
442 LATTEST(undo.end <= cell_dit.lastpit());
443 doRecordUndo(ATOMIC_UNDO, cell_dit,
444 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
447 otherstack.top().cur_after = undo.cur_before;
449 // This does the actual undo/redo.
450 //LYXERR0("undo, performing: " << undo);
451 DocIterator dit = undo.cell.asDocIterator(&buffer_);
453 // This is a params undo element
454 delete otherstack.top().bparams;
455 otherstack.top().bparams = new BufferParams(buffer_.params());
456 buffer_.params() = *undo.bparams;
457 } else if (dit.inMathed()) {
458 // We stored the full cell here as there is not much to be
459 // gained by storing just 'a few' paragraphs (most if not
460 // all math inset cells have just one paragraph!)
461 //LYXERR0("undo.array: " << *undo.array);
463 dit.cell().swap(*undo.array);
467 // Some finer machinery is needed here.
468 Text * text = dit.text();
471 ParagraphList & plist = text->paragraphs();
473 // remove new stuff between first and last
474 ParagraphList::iterator first = plist.begin();
475 advance(first, undo.from);
476 ParagraphList::iterator last = plist.begin();
477 advance(last, plist.size() - undo.end);
478 plist.erase(first, last);
480 // re-insert old stuff instead
481 first = plist.begin();
482 advance(first, undo.from);
484 // this ugly stuff is needed until we get rid of the
485 // inset_owner backpointer
486 ParagraphList::iterator pit = undo.pars->begin();
487 ParagraphList::iterator const end = undo.pars->end();
488 for (; pit != end; ++pit)
489 pit->setInsetOwner(dit.realInset());
490 plist.insert(first, undo.pars->begin(), undo.pars->end());
495 // We'll clean up in release mode.
496 LASSERT(undo.pars == 0, undo.pars = 0);
497 LASSERT(undo.array == 0, undo.array = 0);
499 if (!undo.cur_before.empty())
500 cur = undo.cur_before;
505 // Now that we're done with undo, we pop it off the stack.
510 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
512 undo_finished_ = true;
514 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
520 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
522 const size_t gid = stack.top().group_id;
523 while (!stack.empty() && stack.top().group_id == gid)
524 doTextUndoOrRedo(cur, stack, otherstack);
526 // Adapt the new material to current buffer.
527 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
532 void Undo::finishUndo()
534 // Make sure the next operation will be stored.
535 d->undo_finished_ = true;
539 bool Undo::textUndo(CursorData & cur)
541 return d->textUndoOrRedo(cur, true);
545 bool Undo::textRedo(CursorData & cur)
547 return d->textUndoOrRedo(cur, false);
551 void Undo::beginUndoGroup()
553 if (d->group_level_ == 0) {
554 // create a new group
556 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id_);
562 void Undo::beginUndoGroup(CursorData const & cur_before)
565 if (d->group_cur_before_.empty())
566 d->group_cur_before_ = cur_before;
570 void Undo::endUndoGroup()
572 if (d->group_level_ == 0) {
573 LYXERR0("There is no undo group to end here");
577 if (d->group_level_ == 0) {
578 // real end of the group
579 d->group_cur_before_ = CursorData();
580 LYXERR(Debug::UNDO, "-------End of group " << d->group_id_);
585 void Undo::endUndoGroup(CursorData const & cur_after)
588 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
589 d->undostack_.top().cur_after = cur_after;
593 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
595 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
599 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
601 d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
605 void Undo::recordUndoInset(CursorData const & cur, Inset const * inset)
607 if (!inset || inset == &cur.inset()) {
610 d->recordUndo(ATOMIC_UNDO, c, c.pit(), c.pit(), cur);
611 } else if (inset == cur.nextInset())
614 LYXERR0("Inset not found, no undo stack added.");
618 void Undo::recordUndoBufferParams(CursorData const & cur)
620 d->recordUndoBufferParams(cur);
624 void Undo::recordUndoFullBuffer(CursorData const & cur)
626 // This one may happen outside of the main undo group, so we
627 // put it in its own subgroup to avoid complaints.
629 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
630 0, d->buffer_.paragraphs().size() - 1, cur);
631 d->recordUndoBufferParams(cur);
635 /// UndoGroupHelper class stuff
637 void UndoGroupHelper::resetBuffer(Buffer * buf)
642 buffer_->undo().endUndoGroup();
645 buffer_->undo().beginUndoGroup();