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