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,
75 kind(kin), cursor(cur), cell(cel), from(fro), end(en),
76 pars(pl), array(ar), bparams(0), isFullBuffer(ifb)
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;
101 /// Which kind of operation are we recording for?
103 /// the position of the cursor
104 StableDocIterator cursor;
105 /// the position of the cell described
106 StableDocIterator cell;
107 /// counted from begin of cell
109 /// complement to end of this cell
111 /// the contents of the saved Paragraphs (for texted)
112 ParagraphList * pars;
113 /// the contents of the saved MathData (for mathed)
115 /// Only used in case of full backups
116 BufferParams const * bparams;
117 /// Only used in case of full backups
120 /// Protect construction
125 class UndoElementStack
128 /// limit is the maximum size of the stack
129 UndoElementStack(size_t limit = 100) { limit_ = limit; }
130 /// limit is the maximum size of the stack
131 ~UndoElementStack() { clear(); }
133 /// Return the top element.
134 UndoElement & top() { return c_.front(); }
136 /// Pop and throw away the top element.
137 void pop() { c_.pop_front(); }
139 /// Return true if the stack is empty.
140 bool empty() const { return c_.empty(); }
142 /// Clear all elements, deleting them.
144 for (size_t i = 0; i != c_.size(); ++i) {
151 /// Push an item on to the stack, deleting the
152 /// bottom item on overflow.
153 void push(UndoElement const & v) {
155 if (c_.size() > limit_)
160 /// Internal contents.
161 std::deque<UndoElement> c_;
162 /// The maximum number elements stored.
169 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true) {}
171 // Returns false if no undo possible.
172 bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
174 void doRecordUndo(UndoKind kind,
175 DocIterator const & cell,
178 DocIterator const & cur,
180 bool isUndoOperation);
183 void recordUndo(UndoKind kind,
191 UndoElementStack undostack_;
193 UndoElementStack redostack_;
195 /// The flag used by Undo::finishUndo().
200 /////////////////////////////////////////////////////////////////////
204 /////////////////////////////////////////////////////////////////////
207 Undo::Undo(Buffer & buffer)
208 : d(new Undo::Private(buffer))
218 bool Undo::hasUndoStack() const
220 return !d->undostack_.empty();
224 bool Undo::hasRedoStack() const
226 return !d->redostack_.empty();
230 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
232 StableDocIterator tmpi2 = i2;
233 tmpi2.pos() = i1.pos();
238 /////////////////////////////////////////////////////////////////////
242 ///////////////////////////////////////////////////////////////////////
244 void Undo::Private::doRecordUndo(UndoKind kind,
245 DocIterator const & cell,
246 pit_type first_pit, pit_type last_pit,
247 DocIterator const & cur,
249 bool isUndoOperation)
251 if (first_pit > last_pit)
252 swap(first_pit, last_pit);
254 // Undo::ATOMIC are always recorded (no overlapping there).
255 // As nobody wants all removed character appear one by one when undoing,
256 // we want combine 'similar' non-ATOMIC undo recordings to one.
257 pit_type from = first_pit;
258 pit_type end = cell.lastpit() - last_pit;
259 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
261 && kind != ATOMIC_UNDO
263 && samePar(stack.top().cell, cell)
264 && stack.top().kind == kind
265 && stack.top().from == from
266 && stack.top().end == end)
269 // create the position information of the Undo entry
270 UndoElement undo(kind, cur, cell, from, end, 0, 0,
271 buffer_.params(), isFullBuffer);
273 // fill in the real data to be saved
274 if (cell.inMathed()) {
275 // simply use the whole cell
276 undo.array = new MathData(cell.cell());
278 // some more effort needed here as 'the whole cell' of the
279 // main Text _is_ the whole document.
280 // record the relevant paragraphs
281 Text const * text = cell.text();
283 ParagraphList const & plist = text->paragraphs();
284 ParagraphList::const_iterator first = plist.begin();
285 advance(first, first_pit);
286 ParagraphList::const_iterator last = plist.begin();
287 advance(last, last_pit + 1);
288 undo.pars = new ParagraphList(first, last);
291 // push the undo entry to undo stack
293 //lyxerr << "undo record: " << stack.top() << endl;
295 // next time we'll try again to combine entries if possible
296 undo_finished_ = false;
300 void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur,
301 pit_type first_pit, pit_type last_pit)
303 LASSERT(first_pit <= cur.lastpit(), /**/);
304 LASSERT(last_pit <= cur.lastpit(), /**/);
306 doRecordUndo(kind, cur, first_pit, last_pit, cur,
309 undo_finished_ = false;
311 //lyxerr << "undostack:\n";
312 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
313 // lyxerr << " " << i << ": " << buf.undostack()[i] << endl;
317 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
319 undo_finished_ = true;
321 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
327 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
329 // Adjust undo stack and get hold of current undo data.
330 UndoElement & undo = stack.top();
331 // We'll pop the stack only when we're done with this element. So do NOT
332 // try to return early.
334 // We will store in otherstack the part of the document under 'undo'
335 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
337 doRecordUndo(ATOMIC_UNDO, cell_dit,
338 undo.from, cell_dit.lastpit() - undo.end, cur,
339 undo.isFullBuffer, !isUndoOperation);
341 // This does the actual undo/redo.
342 //LYXERR0("undo, performing: " << undo);
343 bool labelsUpdateNeeded = false;
344 DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
345 if (undo.isFullBuffer) {
346 LASSERT(undo.pars, /**/);
347 // This is a full document
348 delete otherstack.top().bparams;
349 otherstack.top().bparams = new BufferParams(buffer_.params());
350 buffer_.params() = *undo.bparams;
351 swap(buffer_.paragraphs(), *undo.pars);
354 } else if (dit.inMathed()) {
355 // We stored the full cell here as there is not much to be
356 // gained by storing just 'a few' paragraphs (most if not
357 // all math inset cells have just one paragraph!)
358 //LYXERR0("undo.array: " << *undo.array);
359 LASSERT(undo.array, /**/);
360 dit.cell().swap(*undo.array);
364 // Some finer machinery is needed here.
365 Text * text = dit.text();
367 LASSERT(undo.pars, /**/);
368 ParagraphList & plist = text->paragraphs();
370 // remove new stuff between first and last
371 ParagraphList::iterator first = plist.begin();
372 advance(first, undo.from);
373 ParagraphList::iterator last = plist.begin();
374 advance(last, plist.size() - undo.end);
375 plist.erase(first, last);
377 // re-insert old stuff instead
378 first = plist.begin();
379 advance(first, undo.from);
381 // this ugly stuff is needed until we get rid of the
382 // inset_owner backpointer
383 ParagraphList::iterator pit = undo.pars->begin();
384 ParagraphList::iterator const end = undo.pars->end();
385 for (; pit != end; ++pit)
386 pit->setInsetOwner(dit.realInset());
387 plist.insert(first, undo.pars->begin(), undo.pars->end());
390 labelsUpdateNeeded = true;
392 LASSERT(undo.pars == 0, /**/);
393 LASSERT(undo.array == 0, /**/);
395 cur = undo.cursor.asDocIterator(&buffer_.inset());
396 // Now that we're done with undo, we pop it off the stack.
399 if (labelsUpdateNeeded)
400 updateLabels(buffer_);
401 undo_finished_ = true;
406 void Undo::finishUndo()
408 // Make sure the next operation will be stored.
409 d->undo_finished_ = true;
413 bool Undo::textUndo(DocIterator & cur)
415 return d->textUndoOrRedo(cur, true);
419 bool Undo::textRedo(DocIterator & cur)
421 return d->textUndoOrRedo(cur, false);
425 void Undo::recordUndo(DocIterator & cur, UndoKind kind)
427 d->recordUndo(kind, cur, cur.pit(), cur.pit());
431 void Undo::recordUndoInset(DocIterator & cur, UndoKind kind)
435 d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, true);
439 void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from)
441 d->recordUndo(kind, cur, cur.pit(), from);
445 void Undo::recordUndo(DocIterator & cur, UndoKind kind,
446 pit_type from, pit_type to)
448 d->recordUndo(kind, cur, from, to);
452 void Undo::recordUndoFullDocument(DocIterator & cur)
456 doc_iterator_begin(d->buffer_.inset()),
457 0, d->buffer_.paragraphs().size() - 1,