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