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