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"
23 #include "DocIterator.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, StableDocIterator const & cur,
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), cursor(cur), 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)
90 bparams = ue.isFullBuffer
91 ? new BufferParams(*ue.bparams) : ue.bparams;
92 isFullBuffer = ue.isFullBuffer;
93 lyx_clean = ue.lyx_clean;
94 group_id = ue.group_id;
102 /// Which kind of operation are we recording for?
104 /// the position of the cursor
105 StableDocIterator cursor;
106 /// the position of the cell described
107 StableDocIterator cell;
108 /// counted from begin of cell
110 /// complement to end of this cell
112 /// the contents of the saved Paragraphs (for texted)
113 ParagraphList * pars;
114 /// the contents of the saved MathData (for mathed)
116 /// Only used in case of full backups
117 BufferParams const * bparams;
118 /// Only used in case of full backups
120 /// Was the buffer clean at this point?
122 /// the element's group id
125 /// Protect construction
130 class UndoElementStack
133 /// limit is the maximum size of the stack
134 UndoElementStack(size_t limit = 100) { limit_ = limit; }
135 /// limit is the maximum size of the stack
136 ~UndoElementStack() { clear(); }
138 /// Return the top element.
139 UndoElement & top() { return c_.front(); }
141 /// Pop and throw away the top element.
142 void pop() { c_.pop_front(); }
144 /// Return true if the stack is empty.
145 bool empty() const { return c_.empty(); }
147 /// Clear all elements, deleting them.
149 for (size_t i = 0; i != c_.size(); ++i) {
156 /// Push an item on to the stack, deleting the bottom group on
158 void push(UndoElement const & v) {
160 if (c_.size() > limit_) {
161 // remove a whole group at once.
162 const size_t gid = c_.back().group_id;
163 while (!c_.empty() && c_.back().group_id == gid)
168 /// Mark all the elements of the stack as dirty
170 for (size_t i = 0; i != c_.size(); ++i)
171 c_[i].lyx_clean = false;
175 /// Internal contents.
176 std::deque<UndoElement> c_;
177 /// The maximum number elements stored.
184 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
185 group_id(0), group_level(0) {}
187 // Do one undo/redo step
188 void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack,
189 UndoElementStack & otherStack);
190 // Apply one undo/redo group. Returns false if no undo possible.
191 bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
194 void doRecordUndo(UndoKind kind,
195 DocIterator const & cell,
198 DocIterator const & cur,
200 UndoElementStack & stack);
202 void recordUndo(UndoKind kind,
203 DocIterator const & cell,
206 DocIterator const & cur,
212 UndoElementStack undostack_;
214 UndoElementStack redostack_;
216 /// The flag used by Undo::finishUndo().
219 /// Current group Id.
221 /// Current group nesting nevel.
226 /////////////////////////////////////////////////////////////////////
230 /////////////////////////////////////////////////////////////////////
233 Undo::Undo(Buffer & buffer)
234 : d(new Undo::Private(buffer))
246 d->undostack_.clear();
247 d->redostack_.clear();
248 d->undo_finished_ = true;
254 bool Undo::hasUndoStack() const
256 return !d->undostack_.empty();
260 bool Undo::hasRedoStack() const
262 return !d->redostack_.empty();
266 void Undo::markDirty()
268 d->undo_finished_ = true;
269 d->undostack_.markDirty();
270 d->redostack_.markDirty();
274 /////////////////////////////////////////////////////////////////////
278 ///////////////////////////////////////////////////////////////////////
280 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
282 StableDocIterator tmpi2 = i2;
283 tmpi2.pos() = i1.pos();
288 void Undo::Private::doRecordUndo(UndoKind kind,
289 DocIterator const & cell,
290 pit_type first_pit, pit_type last_pit,
291 DocIterator const & cur,
293 UndoElementStack & stack)
296 LYXERR0("There is no group open (creating one)");
300 if (first_pit > last_pit)
301 swap(first_pit, last_pit);
303 // Undo::ATOMIC are always recorded (no overlapping there).
304 // As nobody wants all removed character appear one by one when undoing,
305 // we want combine 'similar' non-ATOMIC undo recordings to one.
306 pit_type from = first_pit;
307 pit_type end = cell.lastpit() - last_pit;
309 && kind != ATOMIC_UNDO
311 && samePar(stack.top().cell, cell)
312 && stack.top().kind == kind
313 && stack.top().from == from
314 && stack.top().end == end)
318 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
320 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
321 // create the position information of the Undo entry
322 UndoElement undo(kind, cur, cell, from, end, 0, 0,
323 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
325 // fill in the real data to be saved
326 if (cell.inMathed()) {
327 // simply use the whole cell
328 MathData & ar = cell.cell();
329 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
331 // some more effort needed here as 'the whole cell' of the
332 // main Text _is_ the whole document.
333 // record the relevant paragraphs
334 Text const * text = cell.text();
336 ParagraphList const & plist = text->paragraphs();
337 ParagraphList::const_iterator first = plist.begin();
338 advance(first, first_pit);
339 ParagraphList::const_iterator last = plist.begin();
340 advance(last, last_pit + 1);
341 undo.pars = new ParagraphList(first, last);
344 // push the undo entry to undo stack
346 //lyxerr << "undo record: " << stack.top() << endl;
350 void Undo::Private::recordUndo(UndoKind kind,
351 DocIterator const & cell,
352 pit_type first_pit, pit_type last_pit,
353 DocIterator const & cur,
356 LASSERT(first_pit <= cell.lastpit(), /**/);
357 LASSERT(last_pit <= cell.lastpit(), /**/);
359 doRecordUndo(kind, cell, first_pit, last_pit, cur,
360 isFullBuffer, undostack_);
362 // next time we'll try again to combine entries if possible
363 undo_finished_ = false;
365 // If we ran recordUndo, it means that we plan to change the buffer
369 //lyxerr << "undostack:\n";
370 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
371 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
375 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
377 // Adjust undo stack and get hold of current undo data.
378 UndoElement & undo = stack.top();
379 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
380 // We'll pop the stack only when we're done with this element. So do NOT
381 // try to return early.
383 // We will store in otherstack the part of the document under 'undo'
384 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
386 doRecordUndo(ATOMIC_UNDO, cell_dit,
387 undo.from, cell_dit.lastpit() - undo.end, cur,
388 undo.isFullBuffer, otherstack);
390 // This does the actual undo/redo.
391 //LYXERR0("undo, performing: " << undo);
392 DocIterator dit = undo.cell.asDocIterator(&buffer_);
393 if (undo.isFullBuffer) {
394 LASSERT(undo.pars, /**/);
395 // This is a full document
396 delete otherstack.top().bparams;
397 otherstack.top().bparams = new BufferParams(buffer_.params());
398 buffer_.params() = *undo.bparams;
399 swap(buffer_.paragraphs(), *undo.pars);
402 } else if (dit.inMathed()) {
403 // We stored the full cell here as there is not much to be
404 // gained by storing just 'a few' paragraphs (most if not
405 // all math inset cells have just one paragraph!)
406 //LYXERR0("undo.array: " << *undo.array);
407 LASSERT(undo.array, /**/);
408 dit.cell().swap(*undo.array);
412 // Some finer machinery is needed here.
413 Text * text = dit.text();
415 LASSERT(undo.pars, /**/);
416 ParagraphList & plist = text->paragraphs();
418 // remove new stuff between first and last
419 ParagraphList::iterator first = plist.begin();
420 advance(first, undo.from);
421 ParagraphList::iterator last = plist.begin();
422 advance(last, plist.size() - undo.end);
423 plist.erase(first, last);
425 // re-insert old stuff instead
426 first = plist.begin();
427 advance(first, undo.from);
429 // this ugly stuff is needed until we get rid of the
430 // inset_owner backpointer
431 ParagraphList::iterator pit = undo.pars->begin();
432 ParagraphList::iterator const end = undo.pars->end();
433 for (; pit != end; ++pit)
434 pit->setInsetOwner(dit.realInset());
435 plist.insert(first, undo.pars->begin(), undo.pars->end());
439 LASSERT(undo.pars == 0, /**/);
440 LASSERT(undo.array == 0, /**/);
442 cur = undo.cursor.asDocIterator(&buffer_);
447 // Now that we're done with undo, we pop it off the stack.
452 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
454 undo_finished_ = true;
456 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
462 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
464 const size_t gid = stack.top().group_id;
465 while (!stack.empty() && stack.top().group_id == gid)
466 doTextUndoOrRedo(cur, stack, otherstack);
468 // Adapt the new material to current buffer.
469 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
474 void Undo::finishUndo()
476 // Make sure the next operation will be stored.
477 d->undo_finished_ = true;
481 bool Undo::textUndo(DocIterator & cur)
483 return d->textUndoOrRedo(cur, true);
487 bool Undo::textRedo(DocIterator & cur)
489 return d->textUndoOrRedo(cur, false);
493 void Undo::beginUndoGroup()
495 if (d->group_level == 0) {
496 // create a new group
498 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
504 void Undo::endUndoGroup()
506 if (d->group_level == 0)
507 LYXERR0("There is no undo group to end here");
509 if (d->group_level == 0) {
510 // real end of the group
511 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
515 // FIXME: remove these convenience functions and make
516 // Private::recordUndo public as sole interface. The code in the
517 // convenience functions can move to Cursor.cpp.
519 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
521 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
525 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind,
528 if (!inset || inset == &cur.inset()) {
531 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
532 } else if (inset == cur.nextInset())
533 recordUndo(cur, kind);
535 LYXERR0("Inset not found, no undo stack added.");
539 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
541 d->recordUndo(kind, cur, cur.pit(), from, cur, false);
545 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
546 pit_type from, pit_type to)
548 d->recordUndo(kind, cur, from, to, cur, false);
552 void Undo::recordUndoFullDocument(DocIterator const & cur)
554 // This one may happen outside of the main undo group, so we
555 // put it in its own subgroup to avoid complaints.
557 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
558 0, d->buffer_.paragraphs().size() - 1, cur, true);