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