scramble email addresses
[lyx.git] / src / Undo.cpp
blobc228eec48a252e464801437b2e6e58a0897f27b5
1 /**
2 * \file Undo.cpp
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
8 * \author John Levon
9 * \author André Pönitz
10 * \author Jürgen Vigna
11 * \author Abdelrazak Younes
13 * Full author contact details are available in file CREDITS.
16 #include <config.h>
18 #include "Undo.h"
20 #include "Buffer.h"
21 #include "BufferParams.h"
22 #include "buffer_funcs.h"
23 #include "debug.h"
24 #include "DocIterator.h"
25 #include "Paragraph.h"
26 #include "ParagraphList.h"
27 #include "Text.h"
29 #include "mathed/MathSupport.h"
30 #include "mathed/MathData.h"
32 #include "insets/Inset.h"
34 #include "support/limited_stack.h"
36 #include <algorithm>
38 using std::advance;
39 using std::endl;
41 namespace lyx {
44 /**
45 These are the elements put on the undo stack. Each object contains complete
46 paragraphs from some cell and sufficient information to restore the cursor
47 state.
49 The cell is given by a DocIterator pointing to this cell, the 'interesting'
50 range of paragraphs by counting them from begin and end of cell,
51 respectively.
53 The cursor is also given as DocIterator and should point to some place in
54 the stored paragraph range. In case of math, we simply store the whole
55 cell, as there usually is just a simple paragraph in a cell.
57 The idea is to store the contents of 'interesting' paragraphs in some
58 structure ('Undo') _before_ it is changed in some edit operation.
59 Obviously, the stored ranged should be as small as possible. However, it
60 there is a lower limit: The StableDocIterator pointing stored in the undo
61 class must be valid after the changes, too, as it will used as a pointer
62 where to insert the stored bits when performining undo.
65 struct UndoElement
67 /// Which kind of operation are we recording for?
68 UndoKind kind;
69 /// the position of the cursor
70 StableDocIterator cursor;
71 /// the position of the cell described
72 StableDocIterator cell;
73 /// counted from begin of cell
74 pit_type from;
75 /// complement to end of this cell
76 pit_type end;
77 /// the contents of the saved Paragraphs (for texted)
78 ParagraphList * pars;
79 /// the contents of the saved MathData (for mathed)
80 MathData * array;
81 /// Only used in case of full backups
82 BufferParams bparams;
83 /// Only used in case of full backups
84 bool isFullBuffer;
88 struct Undo::Private
90 Private(Buffer & buffer): buffer_(buffer) {}
92 // Returns false if no undo possible.
93 bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
94 ///
95 void doRecordUndo(UndoKind kind,
96 DocIterator const & cell,
97 pit_type first_pit,
98 pit_type last_pit,
99 DocIterator const & cur,
100 bool isFullBuffer,
101 bool isUndoOperation);
104 void recordUndo(UndoKind kind,
105 DocIterator & cur,
106 pit_type first_pit,
107 pit_type last_pit);
110 Buffer & buffer_;
111 /// Undo stack.
112 limited_stack<UndoElement> undostack;
113 /// Redo stack.
114 limited_stack<UndoElement> redostack;
116 /// The flag used by Undo::finishUndo().
117 bool undo_finished;
121 Undo::Undo(Buffer & buffer): d(new Undo::Private(buffer))
126 Undo::~Undo()
128 delete d;
132 bool Undo::hasUndoStack() const
134 return !d->undostack.empty();
138 bool Undo::hasRedoStack() const
140 return !d->redostack.empty();
146 namespace {
148 std::ostream & operator<<(std::ostream & os, UndoElement const & undo)
150 return os << " from: " << undo.from << " end: " << undo.end
151 << " cell:\n" << undo.cell
152 << " cursor:\n" << undo.cursor;
156 bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
158 StableDocIterator tmpi2 = i2;
159 tmpi2.pos() = i1.pos();
160 return i1 == tmpi2;
163 } // namespace anon
166 void Undo::Private::doRecordUndo(UndoKind kind,
167 DocIterator const & cell,
168 pit_type first_pit, pit_type last_pit,
169 DocIterator const & cur,
170 bool isFullBuffer,
171 bool isUndoOperation)
173 if (first_pit > last_pit)
174 std::swap(first_pit, last_pit);
175 // create the position information of the Undo entry
176 UndoElement undo;
177 undo.array = 0;
178 undo.pars = 0;
179 undo.kind = kind;
180 undo.cell = cell;
181 undo.cursor = cur;
182 undo.bparams = buffer_.params();
183 undo.isFullBuffer = isFullBuffer;
184 //lyxerr << "recordUndo: cur: " << cur << endl;
185 //lyxerr << "recordUndo: pos: " << cur.pos() << endl;
186 //lyxerr << "recordUndo: cell: " << cell << endl;
187 undo.from = first_pit;
188 undo.end = cell.lastpit() - last_pit;
190 limited_stack<UndoElement> & stack = isUndoOperation ?
191 undostack : redostack;
193 // Undo::ATOMIC are always recorded (no overlapping there).
194 // As nobody wants all removed character appear one by one when undoing,
195 // we want combine 'similar' non-ATOMIC undo recordings to one.
196 if (!undo_finished
197 && kind != ATOMIC_UNDO
198 && !stack.empty()
199 && samePar(stack.top().cell, undo.cell)
200 && stack.top().kind == undo.kind
201 && stack.top().from == undo.from
202 && stack.top().end == undo.end)
203 return;
205 // fill in the real data to be saved
206 if (cell.inMathed()) {
207 // simply use the whole cell
208 undo.array = new MathData(cell.cell());
209 } else {
210 // some more effort needed here as 'the whole cell' of the
211 // main Text _is_ the whole document.
212 // record the relevant paragraphs
213 Text const * text = cell.text();
214 BOOST_ASSERT(text);
215 ParagraphList const & plist = text->paragraphs();
216 ParagraphList::const_iterator first = plist.begin();
217 advance(first, first_pit);
218 ParagraphList::const_iterator last = plist.begin();
219 advance(last, last_pit + 1);
220 undo.pars = new ParagraphList(first, last);
223 // push the undo entry to undo stack
224 stack.push(undo);
225 //lyxerr << "undo record: " << stack.top() << std::endl;
227 // next time we'll try again to combine entries if possible
228 undo_finished = false;
232 void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur,
233 pit_type first_pit, pit_type last_pit)
235 BOOST_ASSERT(first_pit <= cur.lastpit());
236 BOOST_ASSERT(last_pit <= cur.lastpit());
238 doRecordUndo(kind, cur, first_pit, last_pit, cur,
239 false, true);
241 undo_finished = false;
242 redostack.clear();
243 //lyxerr << "undostack:\n";
244 //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
245 // lyxerr << " " << i << ": " << buf.undostack()[i] << std::endl;
249 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
251 undo_finished = true;
253 limited_stack<UndoElement> & stack = isUndoOperation ?
254 undostack : redostack;
256 if (stack.empty())
257 // Nothing to do.
258 return false;
260 limited_stack<UndoElement> & otherstack = isUndoOperation ?
261 redostack : undostack;
263 // Adjust undo stack and get hold of current undo data.
264 UndoElement undo = stack.top();
265 stack.pop();
267 // We will store in otherstack the part of the document under 'undo'
268 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
270 doRecordUndo(ATOMIC_UNDO, cell_dit,
271 undo.from, cell_dit.lastpit() - undo.end, cur,
272 undo.isFullBuffer, !isUndoOperation);
274 // This does the actual undo/redo.
275 //lyxerr << "undo, performing: " << undo << std::endl;
276 bool labelsUpdateNeeded = false;
277 DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
278 if (undo.isFullBuffer) {
279 BOOST_ASSERT(undo.pars);
280 // This is a full document
281 otherstack.top().bparams = buffer_.params();
282 buffer_.params() = undo.bparams;
283 std::swap(buffer_.paragraphs(), *undo.pars);
284 delete undo.pars;
285 undo.pars = 0;
286 } else if (dit.inMathed()) {
287 // We stored the full cell here as there is not much to be
288 // gained by storing just 'a few' paragraphs (most if not
289 // all math inset cells have just one paragraph!)
290 //lyxerr << "undo.array: " << *undo.array <<endl;
291 BOOST_ASSERT(undo.array);
292 dit.cell().swap(*undo.array);
293 delete undo.array;
294 undo.array = 0;
295 } else {
296 // Some finer machinery is needed here.
297 Text * text = dit.text();
298 BOOST_ASSERT(text);
299 BOOST_ASSERT(undo.pars);
300 ParagraphList & plist = text->paragraphs();
302 // remove new stuff between first and last
303 ParagraphList::iterator first = plist.begin();
304 advance(first, undo.from);
305 ParagraphList::iterator last = plist.begin();
306 advance(last, plist.size() - undo.end);
307 plist.erase(first, last);
309 // re-insert old stuff instead
310 first = plist.begin();
311 advance(first, undo.from);
313 // this ugly stuff is needed until we get rid of the
314 // inset_owner backpointer
315 ParagraphList::iterator pit = undo.pars->begin();
316 ParagraphList::iterator const end = undo.pars->end();
317 for (; pit != end; ++pit)
318 pit->setInsetOwner(dit.realInset());
319 plist.insert(first, undo.pars->begin(), undo.pars->end());
320 delete undo.pars;
321 undo.pars = 0;
322 labelsUpdateNeeded = true;
324 BOOST_ASSERT(undo.pars == 0);
325 BOOST_ASSERT(undo.array == 0);
327 cur = undo.cursor.asDocIterator(&buffer_.inset());
329 if (labelsUpdateNeeded)
330 updateLabels(buffer_);
331 undo_finished = true;
332 return true;
336 void Undo::finishUndo()
338 // Make sure the next operation will be stored.
339 d->undo_finished = true;
343 bool Undo::textUndo(DocIterator & cur)
345 return d->textUndoOrRedo(cur, true);
349 bool Undo::textRedo(DocIterator & cur)
351 return d->textUndoOrRedo(cur, false);
355 void Undo::recordUndo(DocIterator & cur, UndoKind kind)
357 d->recordUndo(kind, cur, cur.pit(), cur.pit());
361 void Undo::recordUndoInset(DocIterator & cur, UndoKind kind)
363 DocIterator c = cur;
364 c.pop_back();
365 d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, true);
369 void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from)
371 d->recordUndo(kind, cur, cur.pit(), from);
375 void Undo::recordUndo(DocIterator & cur, UndoKind kind,
376 pit_type from, pit_type to)
378 d->recordUndo(kind, cur, from, to);
382 void Undo::recordUndoFullDocument(DocIterator & cur)
384 d->doRecordUndo(
385 ATOMIC_UNDO,
386 doc_iterator_begin(d->buffer_.inset()),
387 0, d->buffer_.paragraphs().size() - 1,
388 cur,
389 true,
390 true
395 } // namespace lyx