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