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