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