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