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