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