]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Fix bug #7212: Paragraph::forToc has to include the labelString.
[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 bool Undo::hasUndoStack() const
245 {
246         return !d->undostack_.empty();
247 }
248
249
250 bool Undo::hasRedoStack() const
251 {
252         return !d->redostack_.empty();
253 }
254
255
256 void Undo::markDirty()
257 {
258         d->undo_finished_ = true;
259         d->undostack_.markDirty();
260         d->redostack_.markDirty();      
261 }
262
263
264 /////////////////////////////////////////////////////////////////////
265 //
266 // Undo::Private
267 //
268 ///////////////////////////////////////////////////////////////////////
269
270 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
271 {
272         StableDocIterator tmpi2 = i2;
273         tmpi2.pos() = i1.pos();
274         return i1 == tmpi2;
275 }
276
277
278 void Undo::Private::doRecordUndo(UndoKind kind,
279         DocIterator const & cell,
280         pit_type first_pit, pit_type last_pit,
281         DocIterator const & cur,
282         bool isFullBuffer,
283         UndoElementStack & stack)
284 {
285         if (!group_level) {
286                 LYXERR0("There is no group open (creating one)");
287                 ++group_id;
288         }
289
290         if (first_pit > last_pit)
291                 swap(first_pit, last_pit);
292
293         // Undo::ATOMIC are always recorded (no overlapping there).
294         // As nobody wants all removed character appear one by one when undoing,
295         // we want combine 'similar' non-ATOMIC undo recordings to one.
296         pit_type from = first_pit;
297         pit_type end = cell.lastpit() - last_pit;
298         if (!undo_finished_
299             && kind != ATOMIC_UNDO
300             && !stack.empty()
301             && samePar(stack.top().cell, cell)
302             && stack.top().kind == kind
303             && stack.top().from == from
304             && stack.top().end == end)
305                 return;
306
307         if (isFullBuffer)
308                 LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
309         else
310                 LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
311         // create the position information of the Undo entry
312         UndoElement undo(kind, cur, cell, from, end, 0, 0, 
313                          buffer_.params(), isFullBuffer, buffer_.isClean(), group_id);
314
315         // fill in the real data to be saved
316         if (cell.inMathed()) {
317                 // simply use the whole cell
318                 MathData & ar = cell.cell();
319                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
320         } else {
321                 // some more effort needed here as 'the whole cell' of the
322                 // main Text _is_ the whole document.
323                 // record the relevant paragraphs
324                 Text const * text = cell.text();
325                 LASSERT(text, /**/);
326                 ParagraphList const & plist = text->paragraphs();
327                 ParagraphList::const_iterator first = plist.begin();
328                 advance(first, first_pit);
329                 ParagraphList::const_iterator last = plist.begin();
330                 advance(last, last_pit + 1);
331                 undo.pars = new ParagraphList(first, last);
332         }
333
334         // push the undo entry to undo stack
335         stack.push(undo);
336         //lyxerr << "undo record: " << stack.top() << endl;
337 }
338
339
340 void Undo::Private::recordUndo(UndoKind kind,
341                                DocIterator const & cell,
342                                pit_type first_pit, pit_type last_pit,
343                                DocIterator const & cur,
344                                bool isFullBuffer)
345 {
346         LASSERT(first_pit <= cell.lastpit(), /**/);
347         LASSERT(last_pit <= cell.lastpit(), /**/);
348
349         doRecordUndo(kind, cell, first_pit, last_pit, cur,
350                 isFullBuffer, undostack_);
351
352         // next time we'll try again to combine entries if possible
353         undo_finished_ = false;
354
355         // If we ran recordUndo, it means that we plan to change the buffer
356         buffer_.markDirty();
357
358         redostack_.clear();
359         //lyxerr << "undostack:\n";
360         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
361         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << endl;
362 }
363
364
365 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
366 {
367         // Adjust undo stack and get hold of current undo data.
368         UndoElement & undo = stack.top();
369         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
370         // We'll pop the stack only when we're done with this element. So do NOT
371         // try to return early.
372
373         // We will store in otherstack the part of the document under 'undo'
374         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
375
376         doRecordUndo(ATOMIC_UNDO, cell_dit,
377                 undo.from, cell_dit.lastpit() - undo.end, cur,
378                 undo.isFullBuffer, otherstack);
379
380         // This does the actual undo/redo.
381         //LYXERR0("undo, performing: " << undo);
382         DocIterator dit = undo.cell.asDocIterator(&buffer_);
383         if (undo.isFullBuffer) {
384                 LASSERT(undo.pars, /**/);
385                 // This is a full document
386                 delete otherstack.top().bparams;
387                 otherstack.top().bparams = new BufferParams(buffer_.params());
388                 buffer_.params() = *undo.bparams;
389                 swap(buffer_.paragraphs(), *undo.pars);
390                 delete undo.pars;
391                 undo.pars = 0;
392         } else if (dit.inMathed()) {
393                 // We stored the full cell here as there is not much to be
394                 // gained by storing just 'a few' paragraphs (most if not
395                 // all math inset cells have just one paragraph!)
396                 //LYXERR0("undo.array: " << *undo.array);
397                 LASSERT(undo.array, /**/);
398                 dit.cell().swap(*undo.array);
399                 delete undo.array;
400                 undo.array = 0;
401         } else {
402                 // Some finer machinery is needed here.
403                 Text * text = dit.text();
404                 LASSERT(text, /**/);
405                 LASSERT(undo.pars, /**/);
406                 ParagraphList & plist = text->paragraphs();
407
408                 // remove new stuff between first and last
409                 ParagraphList::iterator first = plist.begin();
410                 advance(first, undo.from);
411                 ParagraphList::iterator last = plist.begin();
412                 advance(last, plist.size() - undo.end);
413                 plist.erase(first, last);
414
415                 // re-insert old stuff instead
416                 first = plist.begin();
417                 advance(first, undo.from);
418
419                 // this ugly stuff is needed until we get rid of the
420                 // inset_owner backpointer
421                 ParagraphList::iterator pit = undo.pars->begin();
422                 ParagraphList::iterator const end = undo.pars->end();
423                 for (; pit != end; ++pit)
424                         pit->setInsetOwner(dit.realInset());
425                 plist.insert(first, undo.pars->begin(), undo.pars->end());
426                 delete undo.pars;
427                 undo.pars = 0;
428         }
429         LASSERT(undo.pars == 0, /**/);
430         LASSERT(undo.array == 0, /**/);
431
432         cur = undo.cursor.asDocIterator(&buffer_);
433         if (undo.lyx_clean)
434                 buffer_.markClean();
435         else
436                 buffer_.markDirty();
437         // Now that we're done with undo, we pop it off the stack.
438         stack.pop();
439 }
440
441
442 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
443 {
444         undo_finished_ = true;
445
446         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
447
448         if (stack.empty())
449                 // Nothing to do.
450                 return false;
451
452         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
453
454         const size_t gid = stack.top().group_id;
455         while (!stack.empty() && stack.top().group_id == gid)
456                 doTextUndoOrRedo(cur, stack, otherstack);
457
458         // Adapt the new material to current buffer.
459         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
460         return true;
461 }
462
463
464 void Undo::finishUndo()
465 {
466         // Make sure the next operation will be stored.
467         d->undo_finished_ = true;
468 }
469
470
471 bool Undo::textUndo(DocIterator & cur)
472 {
473         return d->textUndoOrRedo(cur, true);
474 }
475
476
477 bool Undo::textRedo(DocIterator & cur)
478 {
479         return d->textUndoOrRedo(cur, false);
480 }
481
482
483 void Undo::beginUndoGroup()
484 {
485         if (d->group_level == 0) {
486                 // create a new group
487                 ++d->group_id;
488                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
489         }
490         ++d->group_level;
491 }
492
493
494 void Undo::endUndoGroup()
495 {
496         if (d->group_level == 0)
497                 LYXERR0("There is no undo group to end here");
498         --d->group_level;
499         if (d->group_level == 0) {
500                 // real end of the group
501                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
502         }
503 }
504
505 // FIXME: remove these convenience functions and make
506 // Private::recordUndo public as sole interface. The code in the
507 // convenience functions can move to Cursor.cpp.
508
509 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
510 {
511         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur, false);
512 }
513
514
515 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind,
516                            Inset const * inset)
517 {
518         if (!inset || inset == &cur.inset()) {
519                 DocIterator c = cur;
520                 c.pop_back();
521                 d->recordUndo(kind, c, c.pit(), c.pit(), cur, false);
522         } else if (inset == cur.nextInset())
523                 recordUndo(cur, kind);
524         else
525                 LYXERR0("Inset not found, no undo stack added.");
526 }
527
528
529 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
530 {
531         d->recordUndo(kind, cur, cur.pit(), from, cur, false);
532 }
533
534
535 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
536         pit_type from, pit_type to)
537 {
538         d->recordUndo(kind, cur, from, to, cur, false);
539 }
540
541
542 void Undo::recordUndoFullDocument(DocIterator const & cur)
543 {
544         // This one may happen outside of the main undo group, so we
545         // put it in its own subgroup to avoid complaints.
546         beginUndoGroup();
547         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_), 
548                       0, d->buffer_.paragraphs().size() - 1, cur, true);
549         endUndoGroup();
550 }
551
552
553 } // namespace lyx