]> 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 "ParagraphParameters.h"
27 #include "Text.h"
28
29 #include "mathed/MathSupport.h"
30 #include "mathed/MathData.h"
31
32 #include "insets/Inset.h"
33
34 #include "support/lassert.h"
35 #include "support/debug.h"
36
37 #include <algorithm>
38 #include <deque>
39
40 using namespace std;
41 using namespace lyx::support;
42
43
44 namespace lyx {
45
46 /**
47 These are the elements put on the undo stack. Each object contains
48 complete paragraphs from some cell and sufficient information to
49 restore the cursor state.
50
51 The cell is given by a DocIterator pointing to this cell, the
52 'interesting' range of paragraphs by counting them from begin and end
53 of cell, respectively.
54
55 The cursor is also given as DocIterator and should point to some place
56 in the stored paragraph range. In case of math, we simply store the
57 whole cell, as there usually is just a simple paragraph in a cell.
58
59 The idea is to store the contents of 'interesting' paragraphs in some
60 structure ('Undo') _before_ it is changed in some edit operation.
61 Obviously, the stored range should be as small as possible. However,
62 there is a lower limit: The StableDocIterator stored in the undo class
63 must be valid after the changes, too, as it will used as a pointer
64 where to insert the stored bits when performining undo. 
65 */
66 struct UndoElement
67 {
68         ///
69         UndoElement(UndoKind kin, StableDocIterator const & cb, 
70                     StableDocIterator const & cel,
71                     pit_type fro, pit_type en, ParagraphList * pl, 
72                     MathData * ar, BufferParams const & bp, 
73                     bool ifb, bool lc, size_t gid) :
74                 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
75                 pars(pl), array(ar), bparams(0), isFullBuffer(ifb),
76                 lyx_clean(lc), 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                 cur_before = ue.cur_before;
86                 cur_after = ue.cur_after;
87                 cell = ue.cell;
88                 from = ue.from;
89                 end = ue.end;
90                 pars = ue.pars;
91                 array = ue.array;
92                 bparams = ue.isFullBuffer
93                         ? new BufferParams(*ue.bparams) : ue.bparams;
94                 isFullBuffer = ue.isFullBuffer;
95                 lyx_clean = ue.lyx_clean;
96                 group_id = ue.group_id;
97         }
98         ///
99         ~UndoElement()
100         {
101                 if (isFullBuffer)
102                         delete bparams;
103         }
104         /// Which kind of operation are we recording for?
105         UndoKind kind;
106         /// the position of the cursor before recordUndo
107         StableDocIterator cur_before;
108         /// the position of the cursor at the end of the undo group
109         StableDocIterator cur_after;
110         /// the position of the cell described
111         StableDocIterator cell;
112         /// counted from begin of cell
113         pit_type from;
114         /// complement to end of this cell
115         pit_type end;
116         /// the contents of the saved Paragraphs (for texted)
117         ParagraphList * pars;
118         /// the contents of the saved MathData (for mathed)
119         MathData * array;
120         /// Only used in case of full backups
121         BufferParams const * bparams;
122         /// Only used in case of full backups
123         bool isFullBuffer;
124         /// Was the buffer clean at this point?
125         bool lyx_clean;
126         /// the element's group id
127         size_t group_id;
128 private:
129         /// Protect construction
130         UndoElement();  
131 };
132
133
134 class UndoElementStack 
135 {
136 public:
137         /// limit is the maximum size of the stack
138         UndoElementStack(size_t limit = 100) { limit_ = limit; }
139         /// limit is the maximum size of the stack
140         ~UndoElementStack() { clear(); }
141
142         /// Return the top element.
143         UndoElement & top() { return c_.front(); }
144
145         /// Pop and throw away the top element.
146         void pop() { c_.pop_front(); }
147
148         /// Return true if the stack is empty.
149         bool empty() const { return c_.empty(); }
150
151         /// Clear all elements, deleting them.
152         void clear() {
153                 for (size_t i = 0; i != c_.size(); ++i) {
154                         delete c_[i].array;
155                         delete c_[i].pars;
156                 }
157                 c_.clear();
158         }
159
160         /// Push an item on to the stack, deleting the bottom group on
161         /// overflow.
162         void push(UndoElement const & v) {
163                 c_.push_front(v);
164                 if (c_.size() > limit_) {
165                         // remove a whole group at once.
166                         const size_t gid = c_.back().group_id;
167                         while (!c_.empty() && c_.back().group_id == gid)
168                                 c_.pop_back();
169                 }
170         }
171
172         /// Mark all the elements of the stack as dirty
173         void markDirty() {
174                 for (size_t i = 0; i != c_.size(); ++i)
175                         c_[i].lyx_clean = false;
176         }               
177
178 private:
179         /// Internal contents.
180         std::deque<UndoElement> c_;
181         /// The maximum number elements stored.
182         size_t limit_;
183 };
184
185
186 struct Undo::Private
187 {
188         Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true), 
189                                    group_id(0), group_level(0) {}
190         
191         // Do one undo/redo step
192         void doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, 
193                               UndoElementStack & otherStack);
194         // Apply one undo/redo group. Returns false if no undo possible.
195         bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
196
197         ///
198         void doRecordUndo(UndoKind kind,
199                 DocIterator const & cell,
200                 pit_type first_pit,
201                 pit_type last_pit,
202                 StableDocIterator const & cur,
203                 bool isFullBuffer,
204                 UndoElementStack & stack);
205         ///
206         void recordUndo(UndoKind kind,
207                 DocIterator const & cell,
208                 pit_type first_pit,
209                 pit_type last_pit,
210                 DocIterator const & cur,
211                 bool isFullBuffer);
212
213         ///
214         Buffer & buffer_;
215         /// Undo stack.
216         UndoElementStack undostack_;
217         /// Redo stack.
218         UndoElementStack redostack_;
219
220         /// The flag used by Undo::finishUndo().
221         bool undo_finished_;
222
223         /// Current group Id.
224         size_t group_id;
225         /// Current group nesting nevel.
226         size_t group_level;
227 };
228
229
230 /////////////////////////////////////////////////////////////////////
231 //
232 // Undo
233 //
234 /////////////////////////////////////////////////////////////////////
235
236
237 Undo::Undo(Buffer & buffer)
238         : d(new Undo::Private(buffer))
239 {}
240
241
242 Undo::~Undo()
243 {
244         delete d;
245 }
246
247
248 void Undo::clear()
249 {
250         d->undostack_.clear();
251         d->redostack_.clear();
252         d->undo_finished_ = true;
253         d->group_id = 0;
254         d->group_level = 0;
255 }
256
257
258 bool Undo::hasUndoStack() const
259 {
260         return !d->undostack_.empty();
261 }
262
263
264 bool Undo::hasRedoStack() const
265 {
266         return !d->redostack_.empty();
267 }
268
269
270 void Undo::markDirty()
271 {
272         d->undo_finished_ = true;
273         d->undostack_.markDirty();
274         d->redostack_.markDirty();      
275 }
276
277
278 /////////////////////////////////////////////////////////////////////
279 //
280 // Undo::Private
281 //
282 ///////////////////////////////////////////////////////////////////////
283
284 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
285 {
286         StableDocIterator tmpi2 = i2;
287         tmpi2.pos() = i1.pos();
288         return i1 == tmpi2;
289 }
290
291
292 void Undo::Private::doRecordUndo(UndoKind kind,
293         DocIterator const & cell,
294         pit_type first_pit, pit_type last_pit,
295         StableDocIterator const & cur_before,
296         bool isFullBuffer,
297         UndoElementStack & stack)
298 {
299         if (!group_level) {
300                 LYXERR0("There is no group open (creating one)");
301                 ++group_id;
302         }
303
304         if (first_pit > last_pit)
305                 swap(first_pit, last_pit);
306
307         // Undo::ATOMIC are always recorded (no overlapping there).
308         // As nobody wants all removed character appear one by one when undoing,
309         // we want combine 'similar' non-ATOMIC undo recordings to one.
310         pit_type from = first_pit;
311         pit_type end = cell.lastpit() - last_pit;
312         if (!undo_finished_
313             && kind != ATOMIC_UNDO
314             && !stack.empty()
315             && samePar(stack.top().cell, cell)
316             && stack.top().kind == kind
317             && stack.top().from == from
318             && stack.top().end == end) {
319                 // reset cur_after; it will be filled correctly by endUndoGroup.
320                 stack.top().cur_after = StableDocIterator();
321                 return;
322         }
323
324         if (isFullBuffer)
325                 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
326         else
327                 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
328         // create the position information of the Undo entry
329         UndoElement undo(kind, cur_before, cell, from, end, 0, 0, 
330                          buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
331
332         // fill in the real data to be saved
333         if (cell.inMathed()) {
334                 // simply use the whole cell
335                 MathData & ar = cell.cell();
336                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
337         } else {
338                 // some more effort needed here as 'the whole cell' of the
339                 // main Text _is_ the whole document.
340                 // record the relevant paragraphs
341                 Text const * text = cell.text();
342                 LASSERT(text, /**/);
343                 ParagraphList const & plist = text->paragraphs();
344                 ParagraphList::const_iterator first = plist.begin();
345                 advance(first, first_pit);
346                 ParagraphList::const_iterator last = plist.begin();
347                 advance(last, last_pit + 1);
348                 // If the paragraphs after the last one have a
349                 // non-zero depth and the depth of last paragraph is
350                 // decremented, then these paragraphs may be affected
351                 // (ticket #8159). We guard against that by saving
352                 // these extra paragraphs.
353                 while (last != plist.end() && last->params().depth() > 0) {
354                         ++last;
355                         --undo.end;
356                 }
357                 undo.pars = new ParagraphList(first, last);
358         }
359
360         // push the undo entry to undo stack
361         stack.push(undo);
362         //lyxerr << "undo record: " << stack.top() << endl;
363 }
364
365
366 void Undo::Private::recordUndo(UndoKind kind,
367                                DocIterator const & cell,
368                                pit_type first_pit, pit_type last_pit,
369                                DocIterator const & cur,
370                                bool isFullBuffer)
371 {
372         LASSERT(first_pit <= cell.lastpit(), /**/);
373         LASSERT(last_pit <= cell.lastpit(), /**/);
374
375         doRecordUndo(kind, cell, first_pit, last_pit, cur,
376                 isFullBuffer, undostack_);
377
378         // next time we'll try again to combine entries if possible
379         undo_finished_ = false;
380
381         // If we ran recordUndo, it means that we plan to change the buffer
382         buffer_.markDirty();
383
384         redostack_.clear();
385         //lyxerr << "undostack:\n";
386         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
387         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
388 }
389
390
391 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
392 {
393         // Adjust undo stack and get hold of current undo data.
394         UndoElement & undo = stack.top();
395         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
396         // We'll pop the stack only when we're done with this element. So do NOT
397         // try to return early.
398
399         // We will store in otherstack the part of the document under 'undo'
400         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
401
402         doRecordUndo(ATOMIC_UNDO, cell_dit,
403                 undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
404                 undo.isFullBuffer, otherstack);
405         otherstack.top().cur_after = undo.cur_before;
406
407         // This does the actual undo/redo.
408         //LYXERR0("undo, performing: " << undo);
409         DocIterator dit = undo.cell.asDocIterator(&buffer_);
410         if (undo.isFullBuffer) {
411                 LASSERT(undo.pars, /**/);
412                 // This is a full document
413                 delete otherstack.top().bparams;
414                 otherstack.top().bparams = new BufferParams(buffer_.params());
415                 buffer_.params() = *undo.bparams;
416                 swap(buffer_.paragraphs(), *undo.pars);
417                 delete undo.pars;
418                 undo.pars = 0;
419         } else if (dit.inMathed()) {
420                 // We stored the full cell here as there is not much to be
421                 // gained by storing just 'a few' paragraphs (most if not
422                 // all math inset cells have just one paragraph!)
423                 //LYXERR0("undo.array: " << *undo.array);
424                 LASSERT(undo.array, /**/);
425                 dit.cell().swap(*undo.array);
426                 delete undo.array;
427                 undo.array = 0;
428         } else {
429                 // Some finer machinery is needed here.
430                 Text * text = dit.text();
431                 LASSERT(text, /**/);
432                 LASSERT(undo.pars, /**/);
433                 ParagraphList & plist = text->paragraphs();
434
435                 // remove new stuff between first and last
436                 ParagraphList::iterator first = plist.begin();
437                 advance(first, undo.from);
438                 ParagraphList::iterator last = plist.begin();
439                 advance(last, plist.size() - undo.end);
440                 plist.erase(first, last);
441
442                 // re-insert old stuff instead
443                 first = plist.begin();
444                 advance(first, undo.from);
445
446                 // this ugly stuff is needed until we get rid of the
447                 // inset_owner backpointer
448                 ParagraphList::iterator pit = undo.pars->begin();
449                 ParagraphList::iterator const end = undo.pars->end();
450                 for (; pit != end; ++pit)
451                         pit->setInsetOwner(dit.realInset());
452                 plist.insert(first, undo.pars->begin(), undo.pars->end());
453                 delete undo.pars;
454                 undo.pars = 0;
455         }
456         LASSERT(undo.pars == 0, /**/);
457         LASSERT(undo.array == 0, /**/);
458
459         if (undo.cur_before.size())
460                 cur = undo.cur_before.asDocIterator(&buffer_);
461         if (undo.lyx_clean)
462                 buffer_.markClean();
463         else
464                 buffer_.markDirty();
465         // Now that we're done with undo, we pop it off the stack.
466         stack.pop();
467 }
468
469
470 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
471 {
472         undo_finished_ = true;
473
474         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
475
476         if (stack.empty())
477                 // Nothing to do.
478                 return false;
479
480         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
481
482         const size_t gid = stack.top().group_id;
483         while (!stack.empty() && stack.top().group_id == gid)
484                 doTextUndoOrRedo(cur, stack, otherstack);
485
486         // Adapt the new material to current buffer.
487         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
488         return true;
489 }
490
491
492 void Undo::finishUndo()
493 {
494         // Make sure the next operation will be stored.
495         d->undo_finished_ = true;
496 }
497
498
499 bool Undo::textUndo(DocIterator & cur)
500 {
501         return d->textUndoOrRedo(cur, true);
502 }
503
504
505 bool Undo::textRedo(DocIterator & cur)
506 {
507         return d->textUndoOrRedo(cur, false);
508 }
509
510
511 void Undo::beginUndoGroup()
512 {
513         if (d->group_level == 0) {
514                 // create a new group
515                 ++d->group_id;
516                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
517         }
518         ++d->group_level;
519 }
520
521
522 void Undo::endUndoGroup()
523 {
524         if (d->group_level == 0)
525                 LYXERR0("There is no undo group to end here");
526         --d->group_level;
527         if (d->group_level == 0) {
528                 // real end of the group
529                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
530         }
531 }
532
533
534 void Undo::endUndoGroup(DocIterator const & cur)
535 {
536         endUndoGroup();
537         if (!d->undostack_.empty() && !d->undostack_.top().cur_after.size())
538                 d->undostack_.top().cur_after = cur;
539 }
540
541
542 // FIXME: remove these convenience functions and make
543 // Private::recordUndo public as sole interface. The code in the
544 // convenience functions can move to Cursor.cpp.
545
546 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
547 {
548         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
549 }
550
551
552 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind,
553                            Inset const * inset)
554 {
555         if (!inset || inset == &cur.inset()) {
556                 DocIterator c = cur;
557                 c.pop_back();
558                 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
559         } else if (inset == cur.nextInset())
560                 recordUndo(cur, kind);
561         else
562                 LYXERR0("Inset not found, no undo stack added.");
563 }
564
565
566 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
567 {
568         d->recordUndo(kind, cur, cur.pit(), from, cur, false);
569 }
570
571
572 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
573         pit_type from, pit_type to)
574 {
575         d->recordUndo(kind, cur, from, to, cur, false);
576 }
577
578
579 void Undo::recordUndoFullDocument(DocIterator const & cur)
580 {
581         // This one may happen outside of the main undo group, so we
582         // put it in its own subgroup to avoid complaints.
583         beginUndoGroup();
584         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_), 
585                       0, d->buffer_.paragraphs().size() - 1, cur, true);
586         endUndoGroup();
587 }
588
589
590 } // namespace lyx