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 complete
47 paragraphs from some cell and sufficient information to restore the cursor
50 The cell is given by a DocIterator pointing to this cell, the 'interesting'
51 range of paragraphs by counting them from begin and end of cell,
54 The cursor is also given as DocIterator and should point to some place in
55 the stored paragraph range. In case of math, we simply store the whole
56 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 ranged should be as small as possible. However, it
61 there is a lower limit: The StableDocIterator pointing stored in the undo
62 class must be valid after the changes, too, as it will used as a pointer
63 where to insert the stored bits when performining undo.
70 UndoElement(UndoKind kin, StableDocIterator const & cur,
71 StableDocIterator const & cel,
72 pit_type fro, pit_type en, ParagraphList * pl,
73 MathData * ar, BufferParams const & bp,
74 bool ifb, bool lc, size_t gid) :
75 kind(kin), cursor(cur), cell(cel), from(fro), end(en),
76 pars(pl), array(ar), bparams(0), isFullBuffer(ifb), lyx_clean(lc), group_id(gid)
79 bparams = new BufferParams(bp);
82 UndoElement(UndoElement const & ue)
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
106 StableDocIterator cursor;
107 /// the position of the cell described
108 StableDocIterator cell;
109 /// counted from begin of cell
111 /// complement to end of this cell
113 /// the contents of the saved Paragraphs (for texted)
114 ParagraphList * pars;
115 /// the contents of the saved MathData (for mathed)
117 /// Only used in case of full backups
118 BufferParams const * bparams;
119 /// Only used in case of full backups
121 /// Was the buffer clean at this point?
123 /// the element's group id
126 /// Protect construction
131 class UndoElementStack
134 /// limit is the maximum size of the stack
135 UndoElementStack(size_t limit = 100) { limit_ = limit; }
136 /// limit is the maximum size of the stack
137 ~UndoElementStack() { clear(); }
139 /// Return the top element.
140 UndoElement & top() { return c_.front(); }
142 /// Pop and throw away the top element.
143 void pop() { c_.pop_front(); }
145 /// Return true if the stack is empty.
146 bool empty() const { return c_.empty(); }
148 /// Clear all elements, deleting them.
150 for (size_t i = 0; i != c_.size(); ++i) {
157 /// Push an item on to the stack, deleting the bottom group on
159 void push(UndoElement const & v) {
161 if (c_.size() > limit_) {
162 // remove a whole group at once.
163 const size_t gid = c_.back().group_id;
164 while (!c_.empty() && c_.back().group_id == gid)
169 /// Mark all the elements of the stack as dirty
171 for (size_t i = 0; i != c_.size(); ++i)
172 c_[i].lyx_clean = false;
176 /// Internal contents.
177 std::deque<UndoElement> c_;
178 /// The maximum number elements stored.
185 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
186 group_id(0), group_level(0) {}
188 // Do one undo/redo step
189 void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, 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 & cur,
210 UndoElementStack undostack_;
212 UndoElementStack redostack_;
214 /// The flag used by Undo::finishUndo().
217 /// Current group Id.
219 /// Current group nesting nevel.
224 /////////////////////////////////////////////////////////////////////
228 /////////////////////////////////////////////////////////////////////
231 Undo::Undo(Buffer & buffer)
232 : d(new Undo::Private(buffer))
242 bool Undo::hasUndoStack() const
244 return !d->undostack_.empty();
248 bool Undo::hasRedoStack() const
250 return !d->redostack_.empty();
254 void Undo::markDirty()
256 d->undo_finished_ = true;
257 d->undostack_.markDirty();
258 d->redostack_.markDirty();
262 /////////////////////////////////////////////////////////////////////
266 ///////////////////////////////////////////////////////////////////////
268 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
270 StableDocIterator tmpi2 = i2;
271 tmpi2.pos() = i1.pos();
276 void Undo::Private::doRecordUndo(UndoKind kind,
277 DocIterator const & cell,
278 pit_type first_pit, pit_type last_pit,
279 DocIterator const & cur,
281 UndoElementStack & stack)
284 LYXERR0("There is no group open (creating one)");
288 if (first_pit > last_pit)
289 swap(first_pit, last_pit);
291 // Undo::ATOMIC are always recorded (no overlapping there).
292 // As nobody wants all removed character appear one by one when undoing,
293 // we want combine 'similar' non-ATOMIC undo recordings to one.
294 pit_type from = first_pit;
295 pit_type end = cell.lastpit() - last_pit;
297 && kind != ATOMIC_UNDO
299 && samePar(stack.top().cell, cell)
300 && stack.top().kind == kind
301 && stack.top().from == from
302 && stack.top().end == end)
306 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
308 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
309 // create the position information of the Undo entry
310 UndoElement undo(kind, cur, cell, from, end, 0, 0,
311 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
313 // fill in the real data to be saved
314 if (cell.inMathed()) {
315 // simply use the whole cell
316 MathData & ar = cell.cell();
317 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
319 // some more effort needed here as 'the whole cell' of the
320 // main Text _is_ the whole document.
321 // record the relevant paragraphs
322 Text const * text = cell.text();
324 ParagraphList const & plist = text->paragraphs();
325 ParagraphList::const_iterator first = plist.begin();
326 advance(first, first_pit);
327 ParagraphList::const_iterator last = plist.begin();
328 advance(last, last_pit + 1);
329 undo.pars = new ParagraphList(first, last);
332 // push the undo entry to undo stack
334 //lyxerr << "undo record: " << stack.top() << endl;
336 // next time we'll try again to combine entries if possible
337 undo_finished_ = false;
341 void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cur,
342 pit_type first_pit, pit_type last_pit)
344 LASSERT(first_pit <= cur.lastpit(), /**/);
345 LASSERT(last_pit <= cur.lastpit(), /**/);
347 doRecordUndo(kind, cur, first_pit, last_pit, cur,
350 undo_finished_ = false;
352 //lyxerr << "undostack:\n";
353 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
354 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
358 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
360 // Adjust undo stack and get hold of current undo data.
361 UndoElement & undo = stack.top();
362 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
363 // We'll pop the stack only when we're done with this element. So do NOT
364 // try to return early.
366 // We will store in otherstack the part of the document under 'undo'
367 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
369 doRecordUndo(ATOMIC_UNDO, cell_dit,
370 undo.from, cell_dit.lastpit() - undo.end, cur,
371 undo.isFullBuffer, otherstack);
373 // This does the actual undo/redo.
374 //LYXERR0("undo, performing: " << undo);
375 DocIterator dit = undo.cell.asDocIterator(&buffer_);
376 if (undo.isFullBuffer) {
377 LASSERT(undo.pars, /**/);
378 // This is a full document
379 delete otherstack.top().bparams;
380 otherstack.top().bparams = new BufferParams(buffer_.params());
381 buffer_.params() = *undo.bparams;
382 swap(buffer_.paragraphs(), *undo.pars);
385 } else if (dit.inMathed()) {
386 // We stored the full cell here as there is not much to be
387 // gained by storing just 'a few' paragraphs (most if not
388 // all math inset cells have just one paragraph!)
389 //LYXERR0("undo.array: " << *undo.array);
390 LASSERT(undo.array, /**/);
391 dit.cell().swap(*undo.array);
395 // Some finer machinery is needed here.
396 Text * text = dit.text();
398 LASSERT(undo.pars, /**/);
399 ParagraphList & plist = text->paragraphs();
401 // remove new stuff between first and last
402 ParagraphList::iterator first = plist.begin();
403 advance(first, undo.from);
404 ParagraphList::iterator last = plist.begin();
405 advance(last, plist.size() - undo.end);
406 plist.erase(first, last);
408 // re-insert old stuff instead
409 first = plist.begin();
410 advance(first, undo.from);
412 // this ugly stuff is needed until we get rid of the
413 // inset_owner backpointer
414 ParagraphList::iterator pit = undo.pars->begin();
415 ParagraphList::iterator const end = undo.pars->end();
416 for (; pit != end; ++pit)
417 pit->setInsetOwner(dit.realInset());
418 plist.insert(first, undo.pars->begin(), undo.pars->end());
422 LASSERT(undo.pars == 0, /**/);
423 LASSERT(undo.array == 0, /**/);
425 cur = undo.cursor.asDocIterator(&buffer_);
430 // Now that we're done with undo, we pop it off the stack.
435 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
437 undo_finished_ = true;
439 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
445 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
447 const size_t gid = stack.top().group_id;
448 while (!stack.empty() && stack.top().group_id == gid)
449 doTextUndoOrRedo(cur, stack, otherstack);
451 // Adapt the new material to current buffer.
452 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
457 void Undo::finishUndo()
459 // Make sure the next operation will be stored.
460 d->undo_finished_ = true;
464 bool Undo::textUndo(DocIterator & cur)
466 return d->textUndoOrRedo(cur, true);
470 bool Undo::textRedo(DocIterator & cur)
472 return d->textUndoOrRedo(cur, false);
476 void Undo::beginUndoGroup()
478 if (d->group_level == 0) {
479 // create a new group
481 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
487 void Undo::endUndoGroup()
489 if (d->group_level == 0)
490 LYXERR0("There is no undo group to end here");
492 if (d->group_level == 0) {
493 // real end of the group
494 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
500 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
502 d->recordUndo(kind, cur, cur.pit(), cur.pit());
506 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind)
510 d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, d->undostack_);
514 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
516 d->recordUndo(kind, cur, cur.pit(), from);
520 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
521 pit_type from, pit_type to)
523 d->recordUndo(kind, cur, from, to);
527 void Undo::recordUndoFullDocument(DocIterator const & cur)
529 // This one may happen outside of the main undo group, so we
530 // put it in its own subgroup to avoid complaints.
534 doc_iterator_begin(&d->buffer_),
535 0, d->buffer_.paragraphs().size() - 1,