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