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))
244 bool Undo::hasUndoStack() const
246 return !d->undostack_.empty();
250 bool Undo::hasRedoStack() const
252 return !d->redostack_.empty();
256 void Undo::markDirty()
258 d->undo_finished_ = true;
259 d->undostack_.markDirty();
260 d->redostack_.markDirty();
264 /////////////////////////////////////////////////////////////////////
268 ///////////////////////////////////////////////////////////////////////
270 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
272 StableDocIterator tmpi2 = i2;
273 tmpi2.pos() = i1.pos();
278 void Undo::Private::doRecordUndo(UndoKind kind,
279 DocIterator const & cell,
280 pit_type first_pit, pit_type last_pit,
281 DocIterator const & cur,
283 UndoElementStack & stack)
286 LYXERR0("There is no group open (creating one)");
290 if (first_pit > last_pit)
291 swap(first_pit, last_pit);
293 // Undo::ATOMIC are always recorded (no overlapping there).
294 // As nobody wants all removed character appear one by one when undoing,
295 // we want combine 'similar' non-ATOMIC undo recordings to one.
296 pit_type from = first_pit;
297 pit_type end = cell.lastpit() - last_pit;
299 && kind != ATOMIC_UNDO
301 && samePar(stack.top().cell, cell)
302 && stack.top().kind == kind
303 && stack.top().from == from
304 && stack.top().end == end)
308 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
310 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
311 // create the position information of the Undo entry
312 UndoElement undo(kind, cur, cell, from, end, 0, 0,
313 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
315 // fill in the real data to be saved
316 if (cell.inMathed()) {
317 // simply use the whole cell
318 MathData & ar = cell.cell();
319 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
321 // some more effort needed here as 'the whole cell' of the
322 // main Text _is_ the whole document.
323 // record the relevant paragraphs
324 Text const * text = cell.text();
326 ParagraphList const & plist = text->paragraphs();
327 ParagraphList::const_iterator first = plist.begin();
328 advance(first, first_pit);
329 ParagraphList::const_iterator last = plist.begin();
330 advance(last, last_pit + 1);
331 undo.pars = new ParagraphList(first, last);
334 // push the undo entry to undo stack
336 //lyxerr << "undo record: " << stack.top() << endl;
340 void Undo::Private::recordUndo(UndoKind kind,
341 DocIterator const & cell,
342 pit_type first_pit, pit_type last_pit,
343 DocIterator const & cur,
346 LASSERT(first_pit <= cell.lastpit(), /**/);
347 LASSERT(last_pit <= cell.lastpit(), /**/);
349 doRecordUndo(kind, cell, first_pit, last_pit, cur,
350 isFullBuffer, undostack_);
352 // next time we'll try again to combine entries if possible
353 undo_finished_ = false;
355 // If we ran recordUndo, it means that we plan to change the buffer
359 //lyxerr << "undostack:\n";
360 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
361 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
365 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
367 // Adjust undo stack and get hold of current undo data.
368 UndoElement & undo = stack.top();
369 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
370 // We'll pop the stack only when we're done with this element. So do NOT
371 // try to return early.
373 // We will store in otherstack the part of the document under 'undo'
374 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
376 doRecordUndo(ATOMIC_UNDO, cell_dit,
377 undo.from, cell_dit.lastpit() - undo.end, cur,
378 undo.isFullBuffer, otherstack);
380 // This does the actual undo/redo.
381 //LYXERR0("undo, performing: " << undo);
382 DocIterator dit = undo.cell.asDocIterator(&buffer_);
383 if (undo.isFullBuffer) {
384 LASSERT(undo.pars, /**/);
385 // This is a full document
386 delete otherstack.top().bparams;
387 otherstack.top().bparams = new BufferParams(buffer_.params());
388 buffer_.params() = *undo.bparams;
389 swap(buffer_.paragraphs(), *undo.pars);
392 } else if (dit.inMathed()) {
393 // We stored the full cell here as there is not much to be
394 // gained by storing just 'a few' paragraphs (most if not
395 // all math inset cells have just one paragraph!)
396 //LYXERR0("undo.array: " << *undo.array);
397 LASSERT(undo.array, /**/);
398 dit.cell().swap(*undo.array);
402 // Some finer machinery is needed here.
403 Text * text = dit.text();
405 LASSERT(undo.pars, /**/);
406 ParagraphList & plist = text->paragraphs();
408 // remove new stuff between first and last
409 ParagraphList::iterator first = plist.begin();
410 advance(first, undo.from);
411 ParagraphList::iterator last = plist.begin();
412 advance(last, plist.size() - undo.end);
413 plist.erase(first, last);
415 // re-insert old stuff instead
416 first = plist.begin();
417 advance(first, undo.from);
419 // this ugly stuff is needed until we get rid of the
420 // inset_owner backpointer
421 ParagraphList::iterator pit = undo.pars->begin();
422 ParagraphList::iterator const end = undo.pars->end();
423 for (; pit != end; ++pit)
424 pit->setInsetOwner(dit.realInset());
425 plist.insert(first, undo.pars->begin(), undo.pars->end());
429 LASSERT(undo.pars == 0, /**/);
430 LASSERT(undo.array == 0, /**/);
432 cur = undo.cursor.asDocIterator(&buffer_);
437 // Now that we're done with undo, we pop it off the stack.
442 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
444 undo_finished_ = true;
446 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
452 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
454 const size_t gid = stack.top().group_id;
455 while (!stack.empty() && stack.top().group_id == gid)
456 doTextUndoOrRedo(cur, stack, otherstack);
458 // Adapt the new material to current buffer.
459 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
464 void Undo::finishUndo()
466 // Make sure the next operation will be stored.
467 d->undo_finished_ = true;
471 bool Undo::textUndo(DocIterator & cur)
473 return d->textUndoOrRedo(cur, true);
477 bool Undo::textRedo(DocIterator & cur)
479 return d->textUndoOrRedo(cur, false);
483 void Undo::beginUndoGroup()
485 if (d->group_level == 0) {
486 // create a new group
488 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
494 void Undo::endUndoGroup()
496 if (d->group_level == 0)
497 LYXERR0("There is no undo group to end here");
499 if (d->group_level == 0) {
500 // real end of the group
501 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
505 // FIXME: remove these convenience functions and make
506 // Private::recordUndo public as sole interface. The code in the
507 // convenience functions can move to Cursor.cpp.
509 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
511 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
515 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind,
518 if (!inset || inset == &cur.inset()) {
521 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
522 } else if (inset == cur.nextInset())
523 recordUndo(cur, kind);
525 LYXERR0("Inset not found, no undo stack added.");
529 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
531 d->recordUndo(kind, cur, cur.pit(), from, cur, false);
535 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
536 pit_type from, pit_type to)
538 d->recordUndo(kind, cur, from, to, cur, false);
542 void Undo::recordUndoFullDocument(DocIterator const & cur)
544 // This one may happen outside of the main undo group, so we
545 // put it in its own subgroup to avoid complaints.
547 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
548 0, d->buffer_.paragraphs().size() - 1, cur, true);