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