]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
#5502 add binding for full screen toggle on mac
[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 };
239
240
241 /////////////////////////////////////////////////////////////////////
242 //
243 // Undo
244 //
245 /////////////////////////////////////////////////////////////////////
246
247
248 Undo::Undo(Buffer & buffer)
249         : d(new Undo::Private(buffer))
250 {}
251
252
253 Undo::~Undo()
254 {
255         delete d;
256 }
257
258
259 void Undo::clear()
260 {
261         d->undostack_.clear();
262         d->redostack_.clear();
263         d->undo_finished_ = true;
264         // We used to do that, but I believe it is better to keep
265         // groups (only used in Buffer::reload for now (JMarc)
266         //d->group_id = 0;
267         //d->group_level = 0;
268 }
269
270
271 bool Undo::hasUndoStack() const
272 {
273         return !d->undostack_.empty();
274 }
275
276
277 bool Undo::hasRedoStack() const
278 {
279         return !d->redostack_.empty();
280 }
281
282
283 void Undo::markDirty()
284 {
285         d->undo_finished_ = true;
286         d->undostack_.markDirty();
287         d->redostack_.markDirty();
288 }
289
290
291 /////////////////////////////////////////////////////////////////////
292 //
293 // Undo::Private
294 //
295 ///////////////////////////////////////////////////////////////////////
296
297 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
298 {
299         StableDocIterator tmpi2 = i2;
300         tmpi2.pos() = i1.pos();
301         return i1 == tmpi2;
302 }
303
304
305 void Undo::Private::doRecordUndo(UndoKind kind,
306         DocIterator const & cell,
307         pit_type first_pit, pit_type last_pit,
308         CursorData const & cur_before,
309         UndoElementStack & stack)
310 {
311         if (!group_level) {
312                 LYXERR0("There is no group open (creating one)");
313                 ++group_id;
314         }
315
316         if (first_pit > last_pit)
317                 swap(first_pit, last_pit);
318
319         // Undo::ATOMIC are always recorded (no overlapping there).
320         // As nobody wants all removed character appear one by one when undoing,
321         // we want combine 'similar' non-ATOMIC undo recordings to one.
322         pit_type from = first_pit;
323         pit_type end = cell.lastpit() - last_pit;
324         if (!undo_finished_
325             && kind != ATOMIC_UNDO
326             && !stack.empty()
327             && !stack.top().bparams
328             && samePar(stack.top().cell, cell)
329             && stack.top().kind == kind
330             && stack.top().from == from
331             && stack.top().end == end
332             && stack.top().cur_after == cur_before
333             && current_time() - stack.top().time <= 2) {
334                 // reset cur_after; it will be filled correctly by endUndoGroup.
335                 stack.top().cur_after = CursorData();
336                 // update the timestamp of the undo element
337                 stack.top().time = current_time();
338                 return;
339         }
340
341         LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
342         // create the position information of the Undo entry
343         UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
344                          buffer_.isClean(), group_id);
345
346         // fill in the real data to be saved
347         if (cell.inMathed()) {
348                 // simply use the whole cell
349                 MathData & ar = cell.cell();
350                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
351         } else {
352                 // some more effort needed here as 'the whole cell' of the
353                 // main Text _is_ the whole document.
354                 // record the relevant paragraphs
355                 Text const * text = cell.text();
356                 LBUFERR(text);
357                 ParagraphList const & plist = text->paragraphs();
358                 ParagraphList::const_iterator first = plist.begin();
359                 advance(first, first_pit);
360                 ParagraphList::const_iterator last = plist.begin();
361                 advance(last, last_pit + 1);
362                 undo.pars = new ParagraphList(first, last);
363         }
364
365         // push the undo entry to undo stack
366         stack.push(undo);
367         //lyxerr << "undo record: " << stack.top() << endl;
368 }
369
370
371 void Undo::Private::recordUndo(UndoKind kind,
372                                DocIterator const & cell,
373                                pit_type first_pit, pit_type last_pit,
374                                CursorData const & cur)
375 {
376         LASSERT(first_pit <= cell.lastpit(), return);
377         LASSERT(last_pit <= cell.lastpit(), return);
378
379         doRecordUndo(kind, cell, first_pit, last_pit, cur,
380                 undostack_);
381
382         // next time we'll try again to combine entries if possible
383         undo_finished_ = false;
384
385         // If we ran recordUndo, it means that we plan to change the buffer
386         buffer_.markDirty();
387
388         redostack_.clear();
389 }
390
391
392 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
393                                                                            UndoElementStack & stack)
394 {
395         if (!group_level) {
396                 LYXERR0("There is no group open (creating one)");
397                 ++group_id;
398         }
399
400         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
401         // create the position information of the Undo entry
402         UndoElement undo(cur_before, buffer_.params(), buffer_.isClean(),
403                                          group_id);
404
405         // push the undo entry to undo stack
406         stack.push(undo);
407 }
408
409
410 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
411 {
412         doRecordUndoBufferParams(cur, undostack_);
413
414         // next time we'll try again to combine entries if possible
415         undo_finished_ = false;
416
417         // If we ran recordUndo, it means that we plan to change the buffer
418         buffer_.markDirty();
419
420         redostack_.clear();
421 }
422
423
424 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
425 {
426         // Adjust undo stack and get hold of current undo data.
427         UndoElement & undo = stack.top();
428         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
429         // We'll pop the stack only when we're done with this element. So do NOT
430         // try to return early.
431
432         // We will store in otherstack the part of the document under 'undo'
433         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
434
435         if (undo.bparams)
436                 doRecordUndoBufferParams(undo.cur_after, otherstack);
437         else
438                 doRecordUndo(ATOMIC_UNDO, cell_dit,
439                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
440                                          otherstack);
441         otherstack.top().cur_after = undo.cur_before;
442
443         // This does the actual undo/redo.
444         //LYXERR0("undo, performing: " << undo);
445         DocIterator dit = undo.cell.asDocIterator(&buffer_);
446         if (undo.bparams) {
447                 // This is a params undo element
448                 delete otherstack.top().bparams;
449                 otherstack.top().bparams = new BufferParams(buffer_.params());
450                 buffer_.params() = *undo.bparams;
451         } else if (dit.inMathed()) {
452                 // We stored the full cell here as there is not much to be
453                 // gained by storing just 'a few' paragraphs (most if not
454                 // all math inset cells have just one paragraph!)
455                 //LYXERR0("undo.array: " << *undo.array);
456                 LBUFERR(undo.array);
457                 dit.cell().swap(*undo.array);
458                 delete undo.array;
459                 undo.array = 0;
460         } else {
461                 // Some finer machinery is needed here.
462                 Text * text = dit.text();
463                 LBUFERR(text);
464                 LBUFERR(undo.pars);
465                 ParagraphList & plist = text->paragraphs();
466
467                 // remove new stuff between first and last
468                 ParagraphList::iterator first = plist.begin();
469                 advance(first, undo.from);
470                 ParagraphList::iterator last = plist.begin();
471                 advance(last, plist.size() - undo.end);
472                 plist.erase(first, last);
473
474                 // re-insert old stuff instead
475                 first = plist.begin();
476                 advance(first, undo.from);
477
478                 // this ugly stuff is needed until we get rid of the
479                 // inset_owner backpointer
480                 ParagraphList::iterator pit = undo.pars->begin();
481                 ParagraphList::iterator const end = undo.pars->end();
482                 for (; pit != end; ++pit)
483                         pit->setInsetOwner(dit.realInset());
484                 plist.insert(first, undo.pars->begin(), undo.pars->end());
485                 delete undo.pars;
486                 undo.pars = 0;
487         }
488
489         // We'll clean up in release mode.
490         LASSERT(undo.pars == 0, undo.pars = 0);
491         LASSERT(undo.array == 0, undo.array = 0);
492
493         if (!undo.cur_before.empty())
494                 cur = undo.cur_before;
495         if (undo.lyx_clean)
496                 buffer_.markClean();
497         else
498                 buffer_.markDirty();
499         // Now that we're done with undo, we pop it off the stack.
500         stack.pop();
501 }
502
503
504 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
505 {
506         undo_finished_ = true;
507
508         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
509
510         if (stack.empty())
511                 // Nothing to do.
512                 return false;
513
514         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
515
516         const size_t gid = stack.top().group_id;
517         while (!stack.empty() && stack.top().group_id == gid)
518                 doTextUndoOrRedo(cur, stack, otherstack);
519
520         // Adapt the new material to current buffer.
521         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
522         return true;
523 }
524
525
526 void Undo::finishUndo()
527 {
528         // Make sure the next operation will be stored.
529         d->undo_finished_ = true;
530 }
531
532
533 bool Undo::textUndo(CursorData & cur)
534 {
535         return d->textUndoOrRedo(cur, true);
536 }
537
538
539 bool Undo::textRedo(CursorData & cur)
540 {
541         return d->textUndoOrRedo(cur, false);
542 }
543
544
545 void Undo::beginUndoGroup()
546 {
547         if (d->group_level == 0) {
548                 // create a new group
549                 ++d->group_id;
550                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
551         }
552         ++d->group_level;
553 }
554
555
556 void Undo::endUndoGroup()
557 {
558         if (d->group_level == 0) {
559                 LYXERR0("There is no undo group to end here");
560                 return;
561         }
562         --d->group_level;
563         if (d->group_level == 0) {
564                 // real end of the group
565                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
566         }
567 }
568
569
570 void Undo::endUndoGroup(CursorData const & cur)
571 {
572         endUndoGroup();
573         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
574                 d->undostack_.top().cur_after = cur;
575 }
576
577
578 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
579 {
580         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
581 }
582
583
584 void Undo::recordUndo(CursorData const & cur, pit_type from, pit_type to)
585 {
586         d->recordUndo(ATOMIC_UNDO, cur, from, to, cur);
587 }
588
589
590 void Undo::recordUndoBufferParams(CursorData const & cur)
591 {
592         d->recordUndoBufferParams(cur);
593 }
594
595
596 void Undo::recordUndoFullBuffer(CursorData const & cur)
597 {
598         // This one may happen outside of the main undo group, so we
599         // put it in its own subgroup to avoid complaints.
600         beginUndoGroup();
601         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
602                       0, d->buffer_.paragraphs().size() - 1, cur);
603         d->recordUndoBufferParams(cur);
604         endUndoGroup();
605 }
606
607
608 } // namespace lyx