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