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/lassert.h"
34 #include "support/debug.h"
40 using namespace lyx::support;
46 These are the elements put on the undo stack. Each object contains
47 complete paragraphs from some cell and sufficient information to
48 restore the cursor state.
50 The cell is given by a DocIterator pointing to this cell, the
51 'interesting' range of paragraphs by counting them from begin and end
52 of cell, respectively.
54 The cursor is also given as DocIterator and should point to some place
55 in the stored paragraph range. In case of math, we simply store the
56 whole cell, as there usually is just a simple paragraph in a cell.
58 The idea is to store the contents of 'interesting' paragraphs in some
59 structure ('Undo') _before_ it is changed in some edit operation.
60 Obviously, the stored range should be as small as possible. However,
61 there is a lower limit: The StableDocIterator stored in the undo class
62 must be valid after the changes, too, as it will used as a pointer
63 where to insert the stored bits when performining undo.
68 UndoElement(UndoKind kin, CursorData const & cb,
69 StableDocIterator const & cel,
70 pit_type fro, pit_type en, ParagraphList * pl,
71 MathData * ar, BufferParams const & bp,
72 bool ifb, bool lc, size_t gid) :
73 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
74 pars(pl), array(ar), bparams(0), isFullBuffer(ifb),
75 lyx_clean(lc), group_id(gid)
78 bparams = new BufferParams(bp);
81 UndoElement(UndoElement const & ue)
84 cur_before = ue.cur_before;
85 cur_after = ue.cur_after;
91 bparams = ue.isFullBuffer
92 ? new BufferParams(*ue.bparams) : ue.bparams;
93 isFullBuffer = ue.isFullBuffer;
94 lyx_clean = ue.lyx_clean;
95 group_id = ue.group_id;
103 /// Which kind of operation are we recording for?
105 /// the position of the cursor before recordUndo
106 CursorData cur_before;
107 /// the position of the cursor at the end of the undo group
108 CursorData cur_after;
109 /// the position of the cell described
110 StableDocIterator cell;
111 /// counted from begin of cell
113 /// complement to end of this cell
115 /// the contents of the saved Paragraphs (for texted)
116 ParagraphList * pars;
117 /// the contents of the saved MathData (for mathed)
119 /// Only used in case of full backups
120 BufferParams const * bparams;
121 /// Only used in case of full backups
123 /// Was the buffer clean at this point?
125 /// the element's group id
128 /// Protect construction
133 class UndoElementStack
136 /// limit is the maximum size of the stack
137 UndoElementStack(size_t limit = 100) { limit_ = limit; }
138 /// limit is the maximum size of the stack
139 ~UndoElementStack() { clear(); }
141 /// Return the top element.
142 UndoElement & top() { return c_.front(); }
144 /// Pop and throw away the top element.
145 void pop() { c_.pop_front(); }
147 /// Return true if the stack is empty.
148 bool empty() const { return c_.empty(); }
150 /// Clear all elements, deleting them.
152 for (size_t i = 0; i != c_.size(); ++i) {
159 /// Push an item on to the stack, deleting the bottom group on
161 void push(UndoElement const & v) {
163 if (c_.size() > limit_) {
164 // remove a whole group at once.
165 const size_t gid = c_.back().group_id;
166 while (!c_.empty() && c_.back().group_id == gid)
171 /// Mark all the elements of the stack as dirty
173 for (size_t i = 0; i != c_.size(); ++i)
174 c_[i].lyx_clean = false;
178 /// Internal contents.
179 std::deque<UndoElement> c_;
180 /// The maximum number elements stored.
187 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
188 group_id(0), group_level(0) {}
190 // Do one undo/redo step
191 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
192 UndoElementStack & otherStack);
193 // Apply one undo/redo group. Returns false if no undo possible.
194 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
197 void doRecordUndo(UndoKind kind,
198 DocIterator const & cell,
201 CursorData const & cur,
203 UndoElementStack & stack);
205 void recordUndo(UndoKind kind,
206 DocIterator const & cell,
209 CursorData const & cur,
215 UndoElementStack undostack_;
217 UndoElementStack redostack_;
219 /// The flag used by Undo::finishUndo().
222 /// Current group Id.
224 /// Current group nesting nevel.
229 /////////////////////////////////////////////////////////////////////
233 /////////////////////////////////////////////////////////////////////
236 Undo::Undo(Buffer & buffer)
237 : d(new Undo::Private(buffer))
249 d->undostack_.clear();
250 d->redostack_.clear();
251 d->undo_finished_ = true;
252 // We used to do that, but I believe it is better to keep
253 // groups (only used in Buffer::reload for now (JMarc)
255 //d->group_level = 0;
259 bool Undo::hasUndoStack() const
261 return !d->undostack_.empty();
265 bool Undo::hasRedoStack() const
267 return !d->redostack_.empty();
271 void Undo::markDirty()
273 d->undo_finished_ = true;
274 d->undostack_.markDirty();
275 d->redostack_.markDirty();
279 /////////////////////////////////////////////////////////////////////
283 ///////////////////////////////////////////////////////////////////////
285 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
287 StableDocIterator tmpi2 = i2;
288 tmpi2.pos() = i1.pos();
293 void Undo::Private::doRecordUndo(UndoKind kind,
294 DocIterator const & cell,
295 pit_type first_pit, pit_type last_pit,
296 CursorData const & cur_before,
298 UndoElementStack & stack)
301 LYXERR0("There is no group open (creating one)");
305 if (first_pit > last_pit)
306 swap(first_pit, last_pit);
308 // Undo::ATOMIC are always recorded (no overlapping there).
309 // As nobody wants all removed character appear one by one when undoing,
310 // we want combine 'similar' non-ATOMIC undo recordings to one.
311 pit_type from = first_pit;
312 pit_type end = cell.lastpit() - last_pit;
314 && kind != ATOMIC_UNDO
316 && samePar(stack.top().cell, cell)
317 && stack.top().kind == kind
318 && stack.top().from == from
319 && stack.top().end == end) {
320 // reset cur_after; it will be filled correctly by endUndoGroup.
321 stack.top().cur_after = CursorData();
326 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
328 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
329 // create the position information of the Undo entry
330 UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
331 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
333 // fill in the real data to be saved
334 if (cell.inMathed()) {
335 // simply use the whole cell
336 MathData & ar = cell.cell();
337 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
339 // some more effort needed here as 'the whole cell' of the
340 // main Text _is_ the whole document.
341 // record the relevant paragraphs
342 Text const * text = cell.text();
344 ParagraphList const & plist = text->paragraphs();
345 ParagraphList::const_iterator first = plist.begin();
346 advance(first, first_pit);
347 ParagraphList::const_iterator last = plist.begin();
348 advance(last, last_pit + 1);
349 undo.pars = new ParagraphList(first, last);
352 // push the undo entry to undo stack
354 //lyxerr << "undo record: " << stack.top() << endl;
358 void Undo::Private::recordUndo(UndoKind kind,
359 DocIterator const & cell,
360 pit_type first_pit, pit_type last_pit,
361 CursorData const & cur,
364 LASSERT(first_pit <= cell.lastpit(), /**/);
365 LASSERT(last_pit <= cell.lastpit(), /**/);
367 doRecordUndo(kind, cell, first_pit, last_pit, cur,
368 isFullBuffer, undostack_);
370 // next time we'll try again to combine entries if possible
371 undo_finished_ = false;
373 // If we ran recordUndo, it means that we plan to change the buffer
377 //lyxerr << "undostack:\n";
378 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
379 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
383 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
385 // Adjust undo stack and get hold of current undo data.
386 UndoElement & undo = stack.top();
387 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
388 // We'll pop the stack only when we're done with this element. So do NOT
389 // try to return early.
391 // We will store in otherstack the part of the document under 'undo'
392 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
394 doRecordUndo(ATOMIC_UNDO, cell_dit,
395 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
396 undo.isFullBuffer, otherstack);
397 otherstack.top().cur_after = undo.cur_before;
399 // This does the actual undo/redo.
400 //LYXERR0("undo, performing: " << undo);
401 DocIterator dit = undo.cell.asDocIterator(&buffer_);
402 if (undo.isFullBuffer) {
403 LASSERT(undo.pars, /**/);
404 // This is a full document
405 delete otherstack.top().bparams;
406 otherstack.top().bparams = new BufferParams(buffer_.params());
407 buffer_.params() = *undo.bparams;
408 swap(buffer_.paragraphs(), *undo.pars);
411 } else if (dit.inMathed()) {
412 // We stored the full cell here as there is not much to be
413 // gained by storing just 'a few' paragraphs (most if not
414 // all math inset cells have just one paragraph!)
415 //LYXERR0("undo.array: " << *undo.array);
416 LASSERT(undo.array, /**/);
417 dit.cell().swap(*undo.array);
421 // Some finer machinery is needed here.
422 Text * text = dit.text();
424 LASSERT(undo.pars, /**/);
425 ParagraphList & plist = text->paragraphs();
427 // remove new stuff between first and last
428 ParagraphList::iterator first = plist.begin();
429 advance(first, undo.from);
430 ParagraphList::iterator last = plist.begin();
431 advance(last, plist.size() - undo.end);
432 plist.erase(first, last);
434 // re-insert old stuff instead
435 first = plist.begin();
436 advance(first, undo.from);
438 // this ugly stuff is needed until we get rid of the
439 // inset_owner backpointer
440 ParagraphList::iterator pit = undo.pars->begin();
441 ParagraphList::iterator const end = undo.pars->end();
442 for (; pit != end; ++pit)
443 pit->setInsetOwner(dit.realInset());
444 plist.insert(first, undo.pars->begin(), undo.pars->end());
448 LASSERT(undo.pars == 0, /**/);
449 LASSERT(undo.array == 0, /**/);
451 if (!undo.cur_before.empty())
452 cur = undo.cur_before;
457 // Now that we're done with undo, we pop it off the stack.
462 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
464 undo_finished_ = true;
466 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
472 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
474 const size_t gid = stack.top().group_id;
475 while (!stack.empty() && stack.top().group_id == gid)
476 doTextUndoOrRedo(cur, stack, otherstack);
478 // Adapt the new material to current buffer.
479 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
484 void Undo::finishUndo()
486 // Make sure the next operation will be stored.
487 d->undo_finished_ = true;
491 bool Undo::textUndo(CursorData & cur)
493 return d->textUndoOrRedo(cur, true);
497 bool Undo::textRedo(CursorData & cur)
499 return d->textUndoOrRedo(cur, false);
503 void Undo::beginUndoGroup()
505 if (d->group_level == 0) {
506 // create a new group
508 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
514 void Undo::endUndoGroup()
516 if (d->group_level == 0) {
517 LYXERR0("There is no undo group to end here");
521 if (d->group_level == 0) {
522 // real end of the group
523 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
528 void Undo::endUndoGroup(CursorData const & cur)
531 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
532 d->undostack_.top().cur_after = cur;
536 // FIXME: remove these convenience functions and make
537 // Private::recordUndo public as sole interface. The code in the
538 // convenience functions can move to Cursor.cpp.
540 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
542 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
546 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
549 if (!inset || inset == &cur.inset()) {
552 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
553 } else if (inset == cur.nextInset())
554 recordUndo(cur, kind);
556 LYXERR0("Inset not found, no undo stack added.");
560 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
562 d->recordUndo(kind, cur, cur.pit(), from, cur, false);
566 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
567 pit_type from, pit_type to)
569 d->recordUndo(kind, cur, from, to, cur, false);
573 void Undo::recordUndoFullDocument(CursorData const & cur)
575 // This one may happen outside of the main undo group, so we
576 // put it in its own subgroup to avoid complaints.
578 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
579 0, d->buffer_.paragraphs().size() - 1, cur, true);