]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
11224908ccc8b51deea6b12a78714dae8e72699b
[lyx.git] / src / Undo.cpp
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.
5  *
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
12  *
13  * Full author contact details are available in file CREDITS.
14  */
15
16 #include <config.h>
17
18 #include "Undo.h"
19
20 #include "Buffer.h"
21 #include "BufferParams.h"
22 #include "buffer_funcs.h"
23 #include "support/debug.h"
24 #include "DocIterator.h"
25 #include "Paragraph.h"
26 #include "ParagraphList.h"
27 #include "Text.h"
28
29 #include "mathed/MathSupport.h"
30 #include "mathed/MathData.h"
31
32 #include "insets/Inset.h"
33
34 #include "support/limited_stack.h"
35
36 #include <algorithm>
37 #include <ostream>
38
39 using namespace std;
40 using namespace lyx::support;
41
42 namespace lyx {
43
44
45 /**
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
48 state.
49
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,
52 respectively.
53
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.
57
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.
64
65 */
66 struct UndoElement
67 {
68         /// Which kind of operation are we recording for?
69         UndoKind kind;
70         /// the position of the cursor
71         StableDocIterator cursor;
72         /// the position of the cell described
73         StableDocIterator cell;
74         /// counted from begin of cell
75         pit_type from;
76         /// complement to end of this cell
77         pit_type end;
78         /// the contents of the saved Paragraphs (for texted)
79         ParagraphList * pars;
80         /// the contents of the saved MathData (for mathed)
81         MathData * array;
82         /// Only used in case of full backups
83         BufferParams bparams;
84         /// Only used in case of full backups
85         bool isFullBuffer;
86 };
87
88
89 struct Undo::Private
90 {
91         Private(Buffer & buffer): buffer_(buffer) {}
92         
93         // Returns false if no undo possible.
94         bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
95         ///
96         void doRecordUndo(UndoKind kind,
97                 DocIterator const & cell,
98                 pit_type first_pit,
99                 pit_type last_pit,
100                 DocIterator const & cur,
101                 bool isFullBuffer,
102                 bool isUndoOperation);
103
104         ///
105         void recordUndo(UndoKind kind,
106                 DocIterator & cur,
107                 pit_type first_pit,
108                 pit_type last_pit);
109
110         ///
111         Buffer & buffer_;
112         /// Undo stack.
113         limited_stack<UndoElement> undostack;
114         /// Redo stack.
115         limited_stack<UndoElement> redostack;
116
117         /// The flag used by Undo::finishUndo().
118         bool undo_finished;
119 };
120
121
122 Undo::Undo(Buffer & buffer): d(new Undo::Private(buffer))
123 {
124 }
125
126
127 Undo::~Undo()
128 {
129         delete d;
130 }
131
132
133 bool Undo::hasUndoStack() const
134 {
135         return !d->undostack.empty();
136 }
137
138
139 bool Undo::hasRedoStack() const
140 {
141         return !d->redostack.empty();
142 }
143
144
145
146
147 namespace {
148
149 ostream & operator<<(ostream & os, UndoElement const & undo)
150 {
151         return os << " from: " << undo.from << " end: " << undo.end
152                 << " cell:\n" << undo.cell
153                 << " cursor:\n" << undo.cursor;
154 }
155
156
157 bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
158 {
159         StableDocIterator tmpi2 = i2;
160         tmpi2.pos() = i1.pos();
161         return i1 == tmpi2;
162 }
163
164 } // namespace anon
165
166
167 void Undo::Private::doRecordUndo(UndoKind kind,
168         DocIterator const & cell,
169         pit_type first_pit, pit_type last_pit,
170         DocIterator const & cur,
171         bool isFullBuffer,
172         bool isUndoOperation)
173 {
174         if (first_pit > last_pit)
175                 swap(first_pit, last_pit);
176         // create the position information of the Undo entry
177         UndoElement undo;
178         undo.array = 0;
179         undo.pars = 0;
180         undo.kind = kind;
181         undo.cell = cell;
182         undo.cursor = cur;
183         undo.bparams = buffer_.params();
184         undo.isFullBuffer = isFullBuffer;
185         //lyxerr << "recordUndo: cur: " << cur << endl;
186         //lyxerr << "recordUndo: pos: " << cur.pos() << endl;
187         //lyxerr << "recordUndo: cell: " << cell << endl;
188         undo.from = first_pit;
189         undo.end = cell.lastpit() - last_pit;
190
191         limited_stack<UndoElement> & stack = isUndoOperation ?
192                 undostack : redostack;
193
194         // Undo::ATOMIC are always recorded (no overlapping there).
195         // As nobody wants all removed character appear one by one when undoing,
196         // we want combine 'similar' non-ATOMIC undo recordings to one.
197         if (!undo_finished
198             && kind != ATOMIC_UNDO
199             && !stack.empty()
200             && samePar(stack.top().cell, undo.cell)
201             && stack.top().kind == undo.kind
202             && stack.top().from == undo.from
203             && stack.top().end == undo.end)
204                 return;
205
206         // fill in the real data to be saved
207         if (cell.inMathed()) {
208                 // simply use the whole cell
209                 undo.array = new MathData(cell.cell());
210         } else {
211                 // some more effort needed here as 'the whole cell' of the
212                 // main Text _is_ the whole document.
213                 // record the relevant paragraphs
214                 Text const * text = cell.text();
215                 BOOST_ASSERT(text);
216                 ParagraphList const & plist = text->paragraphs();
217                 ParagraphList::const_iterator first = plist.begin();
218                 advance(first, first_pit);
219                 ParagraphList::const_iterator last = plist.begin();
220                 advance(last, last_pit + 1);
221                 undo.pars = new ParagraphList(first, last);
222         }
223
224         // push the undo entry to undo stack
225         stack.push(undo);
226         //lyxerr << "undo record: " << stack.top() << endl;
227
228         // next time we'll try again to combine entries if possible
229         undo_finished = false;
230 }
231
232
233 void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur,
234         pit_type first_pit, pit_type last_pit)
235 {
236         BOOST_ASSERT(first_pit <= cur.lastpit());
237         BOOST_ASSERT(last_pit <= cur.lastpit());
238
239         doRecordUndo(kind, cur, first_pit, last_pit, cur,
240                 false, true);
241
242         undo_finished = false;
243         redostack.clear();
244         //lyxerr << "undostack:\n";
245         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
246         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
247 }
248
249
250 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
251 {
252         undo_finished = true;
253
254         limited_stack<UndoElement> & stack = isUndoOperation ?
255                 undostack : redostack;
256
257         if (stack.empty())
258                 // Nothing to do.
259                 return false;
260
261         limited_stack<UndoElement> & otherstack = isUndoOperation ?
262                 redostack : undostack;
263
264         // Adjust undo stack and get hold of current undo data.
265         UndoElement undo = stack.top();
266         stack.pop();
267
268         // We will store in otherstack the part of the document under 'undo'
269         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
270
271         doRecordUndo(ATOMIC_UNDO, cell_dit,
272                 undo.from, cell_dit.lastpit() - undo.end, cur,
273                 undo.isFullBuffer, !isUndoOperation);
274
275         // This does the actual undo/redo.
276         //LYXERR0("undo, performing: " << undo);
277         bool labelsUpdateNeeded = false;
278         DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
279         if (undo.isFullBuffer) {
280                 BOOST_ASSERT(undo.pars);
281                 // This is a full document
282                 otherstack.top().bparams = buffer_.params();
283                 buffer_.params() = undo.bparams;
284                 swap(buffer_.paragraphs(), *undo.pars);
285                 delete undo.pars;
286                 undo.pars = 0;
287         } else if (dit.inMathed()) {
288                 // We stored the full cell here as there is not much to be
289                 // gained by storing just 'a few' paragraphs (most if not
290                 // all math inset cells have just one paragraph!)
291                 //LYXERR0("undo.array: " << *undo.array);
292                 BOOST_ASSERT(undo.array);
293                 dit.cell().swap(*undo.array);
294                 delete undo.array;
295                 undo.array = 0;
296         } else {
297                 // Some finer machinery is needed here.
298                 Text * text = dit.text();
299                 BOOST_ASSERT(text);
300                 BOOST_ASSERT(undo.pars);
301                 ParagraphList & plist = text->paragraphs();
302
303                 // remove new stuff between first and last
304                 ParagraphList::iterator first = plist.begin();
305                 advance(first, undo.from);
306                 ParagraphList::iterator last = plist.begin();
307                 advance(last, plist.size() - undo.end);
308                 plist.erase(first, last);
309
310                 // re-insert old stuff instead
311                 first = plist.begin();
312                 advance(first, undo.from);
313
314                 // this ugly stuff is needed until we get rid of the
315                 // inset_owner backpointer
316                 ParagraphList::iterator pit = undo.pars->begin();
317                 ParagraphList::iterator const end = undo.pars->end();
318                 for (; pit != end; ++pit)
319                         pit->setInsetOwner(dit.realInset());
320                 plist.insert(first, undo.pars->begin(), undo.pars->end());
321                 delete undo.pars;
322                 undo.pars = 0;
323                 labelsUpdateNeeded = true;
324         }
325         BOOST_ASSERT(undo.pars == 0);
326         BOOST_ASSERT(undo.array == 0);
327
328         cur = undo.cursor.asDocIterator(&buffer_.inset());
329         
330         if (labelsUpdateNeeded)
331                 updateLabels(buffer_);
332         undo_finished = true;
333         return true;
334 }
335
336
337 void Undo::finishUndo()
338 {
339         // Make sure the next operation will be stored.
340         d->undo_finished = true;
341 }
342
343
344 bool Undo::textUndo(DocIterator & cur)
345 {
346         return d->textUndoOrRedo(cur, true);
347 }
348
349
350 bool Undo::textRedo(DocIterator & cur)
351 {
352         return d->textUndoOrRedo(cur, false);
353 }
354
355
356 void Undo::recordUndo(DocIterator & cur, UndoKind kind)
357 {
358         d->recordUndo(kind, cur, cur.pit(), cur.pit());
359 }
360
361
362 void Undo::recordUndoInset(DocIterator & cur, UndoKind kind)
363 {
364         DocIterator c = cur;
365         c.pop_back();
366         d->doRecordUndo(kind, c, c.pit(), c.pit(),      cur, false, true);
367 }
368
369
370 void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from)
371 {
372         d->recordUndo(kind, cur, cur.pit(), from);
373 }
374
375
376 void Undo::recordUndo(DocIterator & cur, UndoKind kind,
377         pit_type from, pit_type to)
378 {
379         d->recordUndo(kind, cur, from, to);
380 }
381
382
383 void Undo::recordUndoFullDocument(DocIterator & cur)
384 {
385         d->doRecordUndo(
386                 ATOMIC_UNDO,
387                 doc_iterator_begin(d->buffer_.inset()),
388                 0, d->buffer_.paragraphs().size() - 1,
389                 cur,
390                 true,
391                 true
392         );
393 }
394
395
396 } // namespace lyx