]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Added Liviu Andronic, and modified generate_contributions.py to match what was in...
[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         LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
289         // create the position information of the Undo entry
290         UndoElement undo(kind, cur, cell, from, end, 0, 0, 
291                          buffer_.params(), isFullBuffer, group_id);
292
293         // fill in the real data to be saved
294         if (cell.inMathed()) {
295                 // simply use the whole cell
296                 undo.array = new MathData(cell.cell());
297         } else {
298                 // some more effort needed here as 'the whole cell' of the
299                 // main Text _is_ the whole document.
300                 // record the relevant paragraphs
301                 Text const * text = cell.text();
302                 LASSERT(text, /**/);
303                 ParagraphList const & plist = text->paragraphs();
304                 ParagraphList::const_iterator first = plist.begin();
305                 advance(first, first_pit);
306                 ParagraphList::const_iterator last = plist.begin();
307                 advance(last, last_pit + 1);
308                 undo.pars = new ParagraphList(first, last);
309         }
310
311         // push the undo entry to undo stack
312         stack.push(undo);
313         //lyxerr << "undo record: " << stack.top() << endl;
314
315         // next time we'll try again to combine entries if possible
316         undo_finished_ = false;
317 }
318
319
320 void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cur,
321         pit_type first_pit, pit_type last_pit)
322 {
323         LASSERT(first_pit <= cur.lastpit(), /**/);
324         LASSERT(last_pit <= cur.lastpit(), /**/);
325
326         doRecordUndo(kind, cur, first_pit, last_pit, cur,
327                 false, undostack_);
328
329         undo_finished_ = false;
330         redostack_.clear();
331         //lyxerr << "undostack:\n";
332         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
333         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
334 }
335
336
337 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
338 {
339         // Adjust undo stack and get hold of current undo data.
340         UndoElement & undo = stack.top();
341         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
342         // We'll pop the stack only when we're done with this element. So do NOT
343         // try to return early.
344
345         // We will store in otherstack the part of the document under 'undo'
346         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
347
348         doRecordUndo(ATOMIC_UNDO, cell_dit,
349                 undo.from, cell_dit.lastpit() - undo.end, cur,
350                 undo.isFullBuffer, otherstack);
351
352         // This does the actual undo/redo.
353         //LYXERR0("undo, performing: " << undo);
354         DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
355         if (undo.isFullBuffer) {
356                 LASSERT(undo.pars, /**/);
357                 // This is a full document
358                 delete otherstack.top().bparams;
359                 otherstack.top().bparams = new BufferParams(buffer_.params());
360                 buffer_.params() = *undo.bparams;
361                 swap(buffer_.paragraphs(), *undo.pars);
362                 delete undo.pars;
363                 undo.pars = 0;
364         } else if (dit.inMathed()) {
365                 // We stored the full cell here as there is not much to be
366                 // gained by storing just 'a few' paragraphs (most if not
367                 // all math inset cells have just one paragraph!)
368                 //LYXERR0("undo.array: " << *undo.array);
369                 LASSERT(undo.array, /**/);
370                 dit.cell().swap(*undo.array);
371                 delete undo.array;
372                 undo.array = 0;
373         } else {
374                 // Some finer machinery is needed here.
375                 Text * text = dit.text();
376                 LASSERT(text, /**/);
377                 LASSERT(undo.pars, /**/);
378                 ParagraphList & plist = text->paragraphs();
379
380                 // remove new stuff between first and last
381                 ParagraphList::iterator first = plist.begin();
382                 advance(first, undo.from);
383                 ParagraphList::iterator last = plist.begin();
384                 advance(last, plist.size() - undo.end);
385                 plist.erase(first, last);
386
387                 // re-insert old stuff instead
388                 first = plist.begin();
389                 advance(first, undo.from);
390
391                 // this ugly stuff is needed until we get rid of the
392                 // inset_owner backpointer
393                 ParagraphList::iterator pit = undo.pars->begin();
394                 ParagraphList::iterator const end = undo.pars->end();
395                 for (; pit != end; ++pit)
396                         pit->setInsetOwner(dit.realInset());
397                 plist.insert(first, undo.pars->begin(), undo.pars->end());
398                 delete undo.pars;
399                 undo.pars = 0;
400         }
401         LASSERT(undo.pars == 0, /**/);
402         LASSERT(undo.array == 0, /**/);
403
404         cur = undo.cursor.asDocIterator(&buffer_.inset());
405         // Now that we're done with undo, we pop it off the stack.
406         stack.pop();
407 }
408
409
410 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
411 {
412         undo_finished_ = true;
413
414         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
415
416         if (stack.empty())
417                 // Nothing to do.
418                 return false;
419
420         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
421
422         const size_t gid = stack.top().group_id;
423         while (!stack.empty() && stack.top().group_id == gid)
424                 doTextUndoOrRedo(cur, stack, otherstack);
425
426         // Addapt the new material to current buffer.
427         updateLabels(buffer_);
428         return true;
429 }
430
431
432 void Undo::finishUndo()
433 {
434         // Make sure the next operation will be stored.
435         d->undo_finished_ = true;
436 }
437
438
439 bool Undo::textUndo(DocIterator & cur)
440 {
441         return d->textUndoOrRedo(cur, true);
442 }
443
444
445 bool Undo::textRedo(DocIterator & cur)
446 {
447         return d->textUndoOrRedo(cur, false);
448 }
449
450
451 void Undo::beginUndoGroup()
452 {
453         if (d->group_level == 0) {
454                 // create a new group
455                 ++d->group_id;
456                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
457         }
458         ++d->group_level;
459 }
460
461
462 void Undo::endUndoGroup()
463 {
464         if (d->group_level == 0)
465                 LYXERR0("There is no undo group to end here");
466         --d->group_level;
467         if (d->group_level == 0) {
468                 // real end of the group
469                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
470         }
471 }
472
473
474
475 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
476 {
477         d->recordUndo(kind, cur, cur.pit(), cur.pit());
478 }
479
480
481 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind)
482 {
483         DocIterator c = cur;
484         c.pop_back();
485         d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, d->undostack_);
486 }
487
488
489 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
490 {
491         d->recordUndo(kind, cur, cur.pit(), from);
492 }
493
494
495 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
496         pit_type from, pit_type to)
497 {
498         d->recordUndo(kind, cur, from, to);
499 }
500
501
502 void Undo::recordUndoFullDocument(DocIterator const & cur)
503 {
504         // This one may happen outside of the main undo group, so we
505         // put it in its own subgroup to avoid complaints.
506         beginUndoGroup();
507         d->doRecordUndo(
508                 ATOMIC_UNDO,
509                 doc_iterator_begin(d->buffer_.inset()),
510                 0, d->buffer_.paragraphs().size() - 1,
511                 cur,
512                 true,
513                 d->undostack_
514         );
515         endUndoGroup();
516 }
517
518
519 } // namespace lyx