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"
41 using namespace lyx::support;
47 These are the elements put on the undo stack. Each object contains
48 complete paragraphs from some cell and sufficient information to
49 restore the cursor state.
51 The cell is given by a DocIterator pointing to this cell, the
52 'interesting' range of paragraphs by counting them from begin and end
53 of cell, respectively.
55 The cursor is also given as DocIterator and should point to some place
56 in the stored paragraph range. In case of math, we simply store the
57 whole cell, as there usually is just a simple paragraph in a cell.
59 The idea is to store the contents of 'interesting' paragraphs in some
60 structure ('Undo') _before_ it is changed in some edit operation.
61 Obviously, the stored range should be as small as possible. However,
62 there is a lower limit: The StableDocIterator stored in the undo class
63 must be valid after the changes, too, as it will used as a pointer
64 where to insert the stored bits when performining undo.
69 UndoElement(UndoKind kin, CursorData const & cb,
70 StableDocIterator const & cel,
71 pit_type fro, pit_type en, ParagraphList * pl,
72 MathData * ar, BufferParams const & bp,
73 bool ifb, bool lc, size_t gid) :
74 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
75 pars(pl), array(ar), bparams(0), isFullBuffer(ifb),
76 lyx_clean(lc), group_id(gid)
79 bparams = new BufferParams(bp);
82 UndoElement(UndoElement const & ue)
85 cur_before = ue.cur_before;
86 cur_after = ue.cur_after;
92 bparams = ue.isFullBuffer
93 ? new BufferParams(*ue.bparams) : ue.bparams;
94 isFullBuffer = ue.isFullBuffer;
95 lyx_clean = ue.lyx_clean;
96 group_id = ue.group_id;
104 /// Which kind of operation are we recording for?
106 /// the position of the cursor before recordUndo
107 CursorData cur_before;
108 /// the position of the cursor at the end of the undo group
109 CursorData cur_after;
110 /// the position of the cell described
111 StableDocIterator cell;
112 /// counted from begin of cell
114 /// complement to end of this cell
116 /// the contents of the saved Paragraphs (for texted)
117 ParagraphList * pars;
118 /// the contents of the saved MathData (for mathed)
120 /// Only used in case of full backups
121 BufferParams const * bparams;
122 /// Only used in case of full backups
124 /// Was the buffer clean at this point?
126 /// the element's group id
129 /// Protect construction
134 class UndoElementStack
137 /// limit is the maximum size of the stack
138 UndoElementStack(size_t limit = 100) { limit_ = limit; }
139 /// limit is the maximum size of the stack
140 ~UndoElementStack() { clear(); }
142 /// Return the top element.
143 UndoElement & top() { return c_.front(); }
145 /// Pop and throw away the top element.
146 void pop() { c_.pop_front(); }
148 /// Return true if the stack is empty.
149 bool empty() const { return c_.empty(); }
151 /// Clear all elements, deleting them.
153 for (size_t i = 0; i != c_.size(); ++i) {
160 /// Push an item on to the stack, deleting the bottom group on
162 void push(UndoElement const & v) {
163 // Remove some entries if the limit has been reached.
164 // However, if the only group on the stack is the one
165 // we are currently populating, do nothing.
166 if (c_.size() >= limit_
167 && c_.front().group_id != v.group_id) {
168 // remove a whole group at once.
169 const size_t gid = c_.back().group_id;
170 while (!c_.empty() && c_.back().group_id == gid)
176 /// Mark all the elements of the stack as dirty
178 for (size_t i = 0; i != c_.size(); ++i)
179 c_[i].lyx_clean = false;
183 /// Internal contents.
184 std::deque<UndoElement> c_;
185 /// The maximum number elements stored.
192 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
193 group_id(0), group_level(0) {}
195 // Do one undo/redo step
196 void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
197 UndoElementStack & otherStack);
198 // Apply one undo/redo group. Returns false if no undo possible.
199 bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
202 void doRecordUndo(UndoKind kind,
203 DocIterator const & cell,
206 CursorData const & cur,
208 UndoElementStack & stack);
210 void recordUndo(UndoKind kind,
211 DocIterator const & cell,
214 CursorData const & cur,
220 UndoElementStack undostack_;
222 UndoElementStack redostack_;
224 /// The flag used by Undo::finishUndo().
227 /// Current group Id.
229 /// Current group nesting nevel.
234 /////////////////////////////////////////////////////////////////////
238 /////////////////////////////////////////////////////////////////////
241 Undo::Undo(Buffer & buffer)
242 : d(new Undo::Private(buffer))
254 d->undostack_.clear();
255 d->redostack_.clear();
256 d->undo_finished_ = true;
257 // We used to do that, but I believe it is better to keep
258 // groups (only used in Buffer::reload for now (JMarc)
260 //d->group_level = 0;
264 bool Undo::hasUndoStack() const
266 return !d->undostack_.empty();
270 bool Undo::hasRedoStack() const
272 return !d->redostack_.empty();
276 void Undo::markDirty()
278 d->undo_finished_ = true;
279 d->undostack_.markDirty();
280 d->redostack_.markDirty();
284 /////////////////////////////////////////////////////////////////////
288 ///////////////////////////////////////////////////////////////////////
290 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
292 StableDocIterator tmpi2 = i2;
293 tmpi2.pos() = i1.pos();
298 void Undo::Private::doRecordUndo(UndoKind kind,
299 DocIterator const & cell,
300 pit_type first_pit, pit_type last_pit,
301 CursorData const & cur_before,
303 UndoElementStack & stack)
306 LYXERR0("There is no group open (creating one)");
310 if (first_pit > last_pit)
311 swap(first_pit, last_pit);
313 // Undo::ATOMIC are always recorded (no overlapping there).
314 // As nobody wants all removed character appear one by one when undoing,
315 // we want combine 'similar' non-ATOMIC undo recordings to one.
316 pit_type from = first_pit;
317 pit_type end = cell.lastpit() - last_pit;
319 && kind != ATOMIC_UNDO
321 && samePar(stack.top().cell, cell)
322 && stack.top().kind == kind
323 && stack.top().from == from
324 && stack.top().end == end) {
325 // reset cur_after; it will be filled correctly by endUndoGroup.
326 stack.top().cur_after = CursorData();
331 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
333 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
334 // create the position information of the Undo entry
335 UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
336 buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
338 // fill in the real data to be saved
339 if (cell.inMathed()) {
340 // simply use the whole cell
341 MathData & ar = cell.cell();
342 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
344 // some more effort needed here as 'the whole cell' of the
345 // main Text _is_ the whole document.
346 // record the relevant paragraphs
347 Text const * text = cell.text();
349 ParagraphList const & plist = text->paragraphs();
350 ParagraphList::const_iterator first = plist.begin();
351 advance(first, first_pit);
352 ParagraphList::const_iterator last = plist.begin();
353 advance(last, last_pit + 1);
354 undo.pars = new ParagraphList(first, last);
357 // push the undo entry to undo stack
359 //lyxerr << "undo record: " << stack.top() << endl;
363 void Undo::Private::recordUndo(UndoKind kind,
364 DocIterator const & cell,
365 pit_type first_pit, pit_type last_pit,
366 CursorData const & cur,
369 LASSERT(first_pit <= cell.lastpit(), return);
370 LASSERT(last_pit <= cell.lastpit(), return);
372 doRecordUndo(kind, cell, first_pit, last_pit, cur,
373 isFullBuffer, undostack_);
375 // next time we'll try again to combine entries if possible
376 undo_finished_ = false;
378 // If we ran recordUndo, it means that we plan to change the buffer
382 //lyxerr << "undostack:\n";
383 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
384 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
388 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
390 // Adjust undo stack and get hold of current undo data.
391 UndoElement & undo = stack.top();
392 LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
393 // We'll pop the stack only when we're done with this element. So do NOT
394 // try to return early.
396 // We will store in otherstack the part of the document under 'undo'
397 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
399 doRecordUndo(ATOMIC_UNDO, cell_dit,
400 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
401 undo.isFullBuffer, otherstack);
402 otherstack.top().cur_after = undo.cur_before;
404 // This does the actual undo/redo.
405 //LYXERR0("undo, performing: " << undo);
406 DocIterator dit = undo.cell.asDocIterator(&buffer_);
407 if (undo.isFullBuffer) {
409 // This is a full document
410 delete otherstack.top().bparams;
411 otherstack.top().bparams = new BufferParams(buffer_.params());
412 buffer_.params() = *undo.bparams;
413 swap(buffer_.paragraphs(), *undo.pars);
416 } else if (dit.inMathed()) {
417 // We stored the full cell here as there is not much to be
418 // gained by storing just 'a few' paragraphs (most if not
419 // all math inset cells have just one paragraph!)
420 //LYXERR0("undo.array: " << *undo.array);
422 dit.cell().swap(*undo.array);
426 // Some finer machinery is needed here.
427 Text * text = dit.text();
430 ParagraphList & plist = text->paragraphs();
432 // remove new stuff between first and last
433 ParagraphList::iterator first = plist.begin();
434 advance(first, undo.from);
435 ParagraphList::iterator last = plist.begin();
436 advance(last, plist.size() - undo.end);
437 plist.erase(first, last);
439 // re-insert old stuff instead
440 first = plist.begin();
441 advance(first, undo.from);
443 // this ugly stuff is needed until we get rid of the
444 // inset_owner backpointer
445 ParagraphList::iterator pit = undo.pars->begin();
446 ParagraphList::iterator const end = undo.pars->end();
447 for (; pit != end; ++pit)
448 pit->setInsetOwner(dit.realInset());
449 plist.insert(first, undo.pars->begin(), undo.pars->end());
454 // We'll clean up in release mode.
455 LASSERT(undo.pars == 0, undo.pars = 0);
456 LASSERT(undo.array == 0, undo.array = 0);
458 if (!undo.cur_before.empty())
459 cur = undo.cur_before;
464 // Now that we're done with undo, we pop it off the stack.
469 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
471 undo_finished_ = true;
473 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
479 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
481 const size_t gid = stack.top().group_id;
482 while (!stack.empty() && stack.top().group_id == gid)
483 doTextUndoOrRedo(cur, stack, otherstack);
485 // Adapt the new material to current buffer.
486 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
491 void Undo::finishUndo()
493 // Make sure the next operation will be stored.
494 d->undo_finished_ = true;
498 bool Undo::textUndo(CursorData & cur)
500 return d->textUndoOrRedo(cur, true);
504 bool Undo::textRedo(CursorData & cur)
506 return d->textUndoOrRedo(cur, false);
510 void Undo::beginUndoGroup()
512 if (d->group_level == 0) {
513 // create a new group
515 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
521 void Undo::endUndoGroup()
523 if (d->group_level == 0) {
524 LYXERR0("There is no undo group to end here");
528 if (d->group_level == 0) {
529 // real end of the group
530 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
535 void Undo::endUndoGroup(CursorData const & cur)
538 if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
539 d->undostack_.top().cur_after = cur;
543 // FIXME: remove these convenience functions and make
544 // Private::recordUndo public as sole interface. The code in the
545 // convenience functions can move to Cursor.cpp.
547 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
549 d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
553 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
556 if (!inset || inset == &cur.inset()) {
559 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
560 } else if (inset == cur.nextInset())
561 recordUndo(cur, kind);
563 LYXERR0("Inset not found, no undo stack added.");
567 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
569 d->recordUndo(kind, cur, cur.pit(), from, cur, false);
573 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
574 pit_type from, pit_type to)
576 d->recordUndo(kind, cur, from, to, cur, false);
580 void Undo::recordUndoFullDocument(CursorData const & cur)
582 // This one may happen outside of the main undo group, so we
583 // put it in its own subgroup to avoid complaints.
585 d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
586 0, d->buffer_.paragraphs().size() - 1, cur, true);