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