]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
A small optimization: Don't do a copy here.
[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/lassert.h"
34 #include "support/debug.h"
35
36 #include <algorithm>
37 #include <deque>
38
39 using namespace std;
40 using namespace lyx::support;
41
42
43 namespace lyx {
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
67 struct UndoElement
68 {
69         ///
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, 
74                     bool ifb) :
75                 kind(kin), cursor(cur), cell(cel), from(fro), end(en),
76                 pars(pl), array(ar), bparams(bp), isFullBuffer(ifb)
77         {}
78         /// Which kind of operation are we recording for?
79         UndoKind kind;
80         /// the position of the cursor
81         StableDocIterator cursor;
82         /// the position of the cell described
83         StableDocIterator cell;
84         /// counted from begin of cell
85         pit_type from;
86         /// complement to end of this cell
87         pit_type end;
88         /// the contents of the saved Paragraphs (for texted)
89         ParagraphList * pars;
90         /// the contents of the saved MathData (for mathed)
91         MathData * array;
92         /// Only used in case of full backups
93         BufferParams bparams;
94         /// Only used in case of full backups
95         bool isFullBuffer;
96 private:
97         /// Protect construction
98         UndoElement();  
99 };
100
101
102 class UndoElementStack 
103 {
104 public:
105         /// limit is the maximum size of the stack
106         UndoElementStack(size_t limit = 100) { limit_ = limit; }
107         /// limit is the maximum size of the stack
108         ~UndoElementStack() { clear(); }
109
110         /// Return the top element.
111         UndoElement & top() { return c_.front(); }
112
113         /// Pop and throw away the top element.
114         void pop() { c_.pop_front(); }
115
116         /// Return true if the stack is empty.
117         bool empty() const { return c_.empty(); }
118
119         /// Clear all elements, deleting them.
120         void clear() {
121                 for (size_t i = 0; i != c_.size(); ++i) {
122                         delete c_[i].array;
123                         delete c_[i].pars;
124                 }
125                 c_.clear();
126         }
127
128         /// Push an item on to the stack, deleting the
129         /// bottom item on overflow.
130         void push(UndoElement const & v) {
131                 c_.push_front(v);
132                 if (c_.size() > limit_)
133                         c_.pop_back();
134         }
135
136 private:
137         /// Internal contents.
138         std::deque<UndoElement> c_;
139         /// The maximum number elements stored.
140         size_t limit_;
141 };
142
143
144 struct Undo::Private
145 {
146         Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true) {}
147         
148         // Returns false if no undo possible.
149         bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
150         ///
151         void doRecordUndo(UndoKind kind,
152                 DocIterator const & cell,
153                 pit_type first_pit,
154                 pit_type last_pit,
155                 DocIterator const & cur,
156                 bool isFullBuffer,
157                 bool isUndoOperation);
158
159         ///
160         void recordUndo(UndoKind kind,
161                 DocIterator & cur,
162                 pit_type first_pit,
163                 pit_type last_pit);
164
165         ///
166         Buffer & buffer_;
167         /// Undo stack.
168         UndoElementStack undostack_;
169         /// Redo stack.
170         UndoElementStack redostack_;
171
172         /// The flag used by Undo::finishUndo().
173         bool undo_finished_;
174 };
175
176
177 /////////////////////////////////////////////////////////////////////
178 //
179 // Undo
180 //
181 /////////////////////////////////////////////////////////////////////
182
183
184 Undo::Undo(Buffer & buffer)
185         : d(new Undo::Private(buffer))
186 {}
187
188
189 Undo::~Undo()
190 {
191         delete d;
192 }
193
194
195 bool Undo::hasUndoStack() const
196 {
197         return !d->undostack_.empty();
198 }
199
200
201 bool Undo::hasRedoStack() const
202 {
203         return !d->redostack_.empty();
204 }
205
206
207 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
208 {
209         StableDocIterator tmpi2 = i2;
210         tmpi2.pos() = i1.pos();
211         return i1 == tmpi2;
212 }
213
214
215 /////////////////////////////////////////////////////////////////////
216 //
217 // Undo::Private
218 //
219 ///////////////////////////////////////////////////////////////////////
220
221 void Undo::Private::doRecordUndo(UndoKind kind,
222         DocIterator const & cell,
223         pit_type first_pit, pit_type last_pit,
224         DocIterator const & cur,
225         bool isFullBuffer,
226         bool isUndoOperation)
227 {
228         if (first_pit > last_pit)
229                 swap(first_pit, last_pit);
230
231         // Undo::ATOMIC are always recorded (no overlapping there).
232         // As nobody wants all removed character appear one by one when undoing,
233         // we want combine 'similar' non-ATOMIC undo recordings to one.
234         pit_type from = first_pit;
235         pit_type end = cell.lastpit() - last_pit;
236         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
237         if (!undo_finished_
238             && kind != ATOMIC_UNDO
239             && !stack.empty()
240             && samePar(stack.top().cell, cell)
241             && stack.top().kind == kind
242             && stack.top().from == from
243             && stack.top().end == end)
244                 return;
245
246         // create the position information of the Undo entry
247         UndoElement undo(kind, cur, cell, from, end, 0, 0, 
248                          buffer_.params(), isFullBuffer);
249
250         // fill in the real data to be saved
251         if (cell.inMathed()) {
252                 // simply use the whole cell
253                 undo.array = new MathData(cell.cell());
254         } else {
255                 // some more effort needed here as 'the whole cell' of the
256                 // main Text _is_ the whole document.
257                 // record the relevant paragraphs
258                 Text const * text = cell.text();
259                 LASSERT(text, /**/);
260                 ParagraphList const & plist = text->paragraphs();
261                 ParagraphList::const_iterator first = plist.begin();
262                 advance(first, first_pit);
263                 ParagraphList::const_iterator last = plist.begin();
264                 advance(last, last_pit + 1);
265                 undo.pars = new ParagraphList(first, last);
266         }
267
268         // push the undo entry to undo stack
269         stack.push(undo);
270         //lyxerr << "undo record: " << stack.top() << endl;
271
272         // next time we'll try again to combine entries if possible
273         undo_finished_ = false;
274 }
275
276
277 void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur,
278         pit_type first_pit, pit_type last_pit)
279 {
280         LASSERT(first_pit <= cur.lastpit(), /**/);
281         LASSERT(last_pit <= cur.lastpit(), /**/);
282
283         doRecordUndo(kind, cur, first_pit, last_pit, cur,
284                 false, true);
285
286         undo_finished_ = false;
287         redostack_.clear();
288         //lyxerr << "undostack:\n";
289         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
290         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
291 }
292
293
294 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
295 {
296         undo_finished_ = true;
297
298         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
299
300         if (stack.empty())
301                 // Nothing to do.
302                 return false;
303
304         UndoElementStack & otherstack = isUndoOperation ?  redostack_ : undostack_;
305
306         // Adjust undo stack and get hold of current undo data.
307         UndoElement & undo = stack.top();
308         // We'll pop the stack only when we're done with this element. So do NOT
309         // try to return early.
310
311         // We will store in otherstack the part of the document under 'undo'
312         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
313
314         doRecordUndo(ATOMIC_UNDO, cell_dit,
315                 undo.from, cell_dit.lastpit() - undo.end, cur,
316                 undo.isFullBuffer, !isUndoOperation);
317
318         // This does the actual undo/redo.
319         //LYXERR0("undo, performing: " << undo);
320         bool labelsUpdateNeeded = false;
321         DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
322         if (undo.isFullBuffer) {
323                 LASSERT(undo.pars, /**/);
324                 // This is a full document
325                 otherstack.top().bparams = buffer_.params();
326                 buffer_.params() = undo.bparams;
327                 swap(buffer_.paragraphs(), *undo.pars);
328                 delete undo.pars;
329                 undo.pars = 0;
330         } else if (dit.inMathed()) {
331                 // We stored the full cell here as there is not much to be
332                 // gained by storing just 'a few' paragraphs (most if not
333                 // all math inset cells have just one paragraph!)
334                 //LYXERR0("undo.array: " << *undo.array);
335                 LASSERT(undo.array, /**/);
336                 dit.cell().swap(*undo.array);
337                 delete undo.array;
338                 undo.array = 0;
339         } else {
340                 // Some finer machinery is needed here.
341                 Text * text = dit.text();
342                 LASSERT(text, /**/);
343                 LASSERT(undo.pars, /**/);
344                 ParagraphList & plist = text->paragraphs();
345
346                 // remove new stuff between first and last
347                 ParagraphList::iterator first = plist.begin();
348                 advance(first, undo.from);
349                 ParagraphList::iterator last = plist.begin();
350                 advance(last, plist.size() - undo.end);
351                 plist.erase(first, last);
352
353                 // re-insert old stuff instead
354                 first = plist.begin();
355                 advance(first, undo.from);
356
357                 // this ugly stuff is needed until we get rid of the
358                 // inset_owner backpointer
359                 ParagraphList::iterator pit = undo.pars->begin();
360                 ParagraphList::iterator const end = undo.pars->end();
361                 for (; pit != end; ++pit)
362                         pit->setInsetOwner(dit.realInset());
363                 plist.insert(first, undo.pars->begin(), undo.pars->end());
364                 delete undo.pars;
365                 undo.pars = 0;
366                 labelsUpdateNeeded = true;
367         }
368         LASSERT(undo.pars == 0, /**/);
369         LASSERT(undo.array == 0, /**/);
370
371         cur = undo.cursor.asDocIterator(&buffer_.inset());
372         // Now that we're done with undo, we pop it off the stack.
373         stack.pop();
374
375         if (labelsUpdateNeeded)
376                 updateLabels(buffer_);
377         undo_finished_ = true;
378         return true;
379 }
380
381
382 void Undo::finishUndo()
383 {
384         // Make sure the next operation will be stored.
385         d->undo_finished_ = true;
386 }
387
388
389 bool Undo::textUndo(DocIterator & cur)
390 {
391         return d->textUndoOrRedo(cur, true);
392 }
393
394
395 bool Undo::textRedo(DocIterator & cur)
396 {
397         return d->textUndoOrRedo(cur, false);
398 }
399
400
401 void Undo::recordUndo(DocIterator & cur, UndoKind kind)
402 {
403         d->recordUndo(kind, cur, cur.pit(), cur.pit());
404 }
405
406
407 void Undo::recordUndoInset(DocIterator & cur, UndoKind kind)
408 {
409         DocIterator c = cur;
410         c.pop_back();
411         d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, true);
412 }
413
414
415 void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from)
416 {
417         d->recordUndo(kind, cur, cur.pit(), from);
418 }
419
420
421 void Undo::recordUndo(DocIterator & cur, UndoKind kind,
422         pit_type from, pit_type to)
423 {
424         d->recordUndo(kind, cur, from, to);
425 }
426
427
428 void Undo::recordUndoFullDocument(DocIterator & cur)
429 {
430         d->doRecordUndo(
431                 ATOMIC_UNDO,
432                 doc_iterator_begin(d->buffer_.inset()),
433                 0, d->buffer_.paragraphs().size() - 1,
434                 cur,
435                 true,
436                 true
437         );
438 }
439
440
441 } // namespace lyx