]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Properly define MultiPar status of caption in the layout definition.
[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 "Cursor.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/debug.h"
34 #include "support/gettext.h"
35 #include "support/lassert.h"
36
37 #include <algorithm>
38 #include <deque>
39
40 using namespace std;
41 using namespace lyx::support;
42
43
44 namespace lyx {
45
46 /**
47 These are the elements put on the undo stack. Each object contains
48 complete paragraphs from some cell and sufficient information to
49 restore the cursor state.
50
51 The cell is given by a DocIterator pointing to this cell, the
52 'interesting' range of paragraphs by counting them from begin and end
53 of cell, respectively.
54
55 The cursor is also given as DocIterator and should point to some place
56 in the stored paragraph range. In case of math, we simply store the
57 whole cell, as there usually is just a simple paragraph in a cell.
58
59 The idea is to store the contents of 'interesting' paragraphs in some
60 structure ('Undo') _before_ it is changed in some edit operation.
61 Obviously, the stored range should be as small as possible. However,
62 there is a lower limit: The StableDocIterator stored in the undo class
63 must be valid after the changes, too, as it will used as a pointer
64 where to insert the stored bits when performining undo.
65 */
66 struct UndoElement
67 {
68         ///
69         UndoElement(UndoKind kin, CursorData const & cb,
70                     StableDocIterator const & cel,
71                     pit_type fro, pit_type en, ParagraphList * pl, MathData * ar,
72                     bool lc, size_t gid) :
73                 kind(kin), cur_before(cb), cell(cel), from(fro), end(en),
74                 pars(pl), array(ar), bparams(0),
75                 lyx_clean(lc), group_id(gid)
76                 {
77                 }
78         ///
79         UndoElement(CursorData const & cb, BufferParams const & bp,
80                                 bool lc, size_t gid) :
81                 kind(ATOMIC_UNDO), cur_before(cb), cell(), from(0), end(0),
82                 pars(0), array(0), bparams(new BufferParams(bp)),
83                 lyx_clean(lc), group_id(gid)
84         {
85         }
86         ///
87         UndoElement(UndoElement const & ue)
88         {
89                 kind = ue.kind;
90                 cur_before = ue.cur_before;
91                 cur_after = ue.cur_after;
92                 cell = ue.cell;
93                 from = ue.from;
94                 end = ue.end;
95                 pars = ue.pars;
96                 array = ue.array;
97                 bparams = ue.bparams
98                         ? new BufferParams(*ue.bparams) : 0;
99                 lyx_clean = ue.lyx_clean;
100                 group_id = ue.group_id;
101         }
102         ///
103         ~UndoElement()
104         {
105                 if (bparams)
106                         delete bparams;
107         }
108         /// Which kind of operation are we recording for?
109         UndoKind kind;
110         /// the position of the cursor before recordUndo
111         CursorData cur_before;
112         /// the position of the cursor at the end of the undo group
113         CursorData cur_after;
114         /// the position of the cell described
115         StableDocIterator cell;
116         /// counted from begin of cell
117         pit_type from;
118         /// complement to end of this cell
119         pit_type end;
120         /// the contents of the saved Paragraphs (for texted)
121         ParagraphList * pars;
122         /// the contents of the saved MathData (for mathed)
123         MathData * array;
124         /// Only used in case of full backups
125         BufferParams const * bparams;
126         /// Was the buffer clean at this point?
127         bool lyx_clean;
128         /// the element's group id
129         size_t group_id;
130 private:
131         /// Protect construction
132         UndoElement();
133 };
134
135
136 class UndoElementStack
137 {
138 public:
139         /// limit is the maximum size of the stack
140         UndoElementStack(size_t limit = 100) { limit_ = limit; }
141         /// limit is the maximum size of the stack
142         ~UndoElementStack() { clear(); }
143
144         /// Return the top element.
145         UndoElement & top() { return c_.front(); }
146
147         /// Pop and throw away the top element.
148         void pop() { c_.pop_front(); }
149
150         /// Return true if the stack is empty.
151         bool empty() const { return c_.empty(); }
152
153         /// Clear all elements, deleting them.
154         void clear() {
155                 for (size_t i = 0; i != c_.size(); ++i) {
156                         delete c_[i].array;
157                         delete c_[i].pars;
158                 }
159                 c_.clear();
160         }
161
162         /// Push an item on to the stack, deleting the bottom group on
163         /// overflow.
164         void push(UndoElement const & v) {
165                 // Remove some entries if the limit has been reached.
166                 // However, if the only group on the stack is the one
167                 // we are currently populating, do nothing.
168                 if (c_.size() >= limit_
169                     && c_.front().group_id != v.group_id) {
170                         // remove a whole group at once.
171                         const size_t gid = c_.back().group_id;
172                         while (!c_.empty() && c_.back().group_id == gid)
173                                 c_.pop_back();
174                 }
175                 c_.push_front(v);
176         }
177
178         /// Mark all the elements of the stack as dirty
179         void markDirty() {
180                 for (size_t i = 0; i != c_.size(); ++i)
181                         c_[i].lyx_clean = false;
182         }
183
184 private:
185         /// Internal contents.
186         std::deque<UndoElement> c_;
187         /// The maximum number elements stored.
188         size_t limit_;
189 };
190
191
192 struct Undo::Private
193 {
194         Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
195                                    group_id(0), group_level(0) {}
196
197         // Do one undo/redo step
198         void doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack,
199                               UndoElementStack & otherStack);
200         // Apply one undo/redo group. Returns false if no undo possible.
201         bool textUndoOrRedo(CursorData & cur, bool isUndoOperation);
202
203         ///
204         void doRecordUndo(UndoKind kind,
205                 DocIterator const & cell,
206                 pit_type first_pit,
207                 pit_type last_pit,
208                 CursorData const & cur,
209                 UndoElementStack & stack);
210         ///
211         void recordUndo(UndoKind kind,
212                 DocIterator const & cell,
213                 pit_type first_pit,
214                 pit_type last_pit,
215                 CursorData const & cur);
216         ///
217         void doRecordUndoBufferParams(CursorData const & cur, UndoElementStack & stack);
218         ///
219         void recordUndoBufferParams(CursorData const & cur);
220
221         ///
222         Buffer & buffer_;
223         /// Undo stack.
224         UndoElementStack undostack_;
225         /// Redo stack.
226         UndoElementStack redostack_;
227
228         /// The flag used by Undo::finishUndo().
229         bool undo_finished_;
230
231         /// Current group Id.
232         size_t group_id;
233         /// Current group nesting nevel.
234         size_t group_level;
235 };
236
237
238 /////////////////////////////////////////////////////////////////////
239 //
240 // Undo
241 //
242 /////////////////////////////////////////////////////////////////////
243
244
245 Undo::Undo(Buffer & buffer)
246         : d(new Undo::Private(buffer))
247 {}
248
249
250 Undo::~Undo()
251 {
252         delete d;
253 }
254
255
256 void Undo::clear()
257 {
258         d->undostack_.clear();
259         d->redostack_.clear();
260         d->undo_finished_ = true;
261         // We used to do that, but I believe it is better to keep
262         // groups (only used in Buffer::reload for now (JMarc)
263         //d->group_id = 0;
264         //d->group_level = 0;
265 }
266
267
268 bool Undo::hasUndoStack() const
269 {
270         return !d->undostack_.empty();
271 }
272
273
274 bool Undo::hasRedoStack() const
275 {
276         return !d->redostack_.empty();
277 }
278
279
280 void Undo::markDirty()
281 {
282         d->undo_finished_ = true;
283         d->undostack_.markDirty();
284         d->redostack_.markDirty();
285 }
286
287
288 /////////////////////////////////////////////////////////////////////
289 //
290 // Undo::Private
291 //
292 ///////////////////////////////////////////////////////////////////////
293
294 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
295 {
296         StableDocIterator tmpi2 = i2;
297         tmpi2.pos() = i1.pos();
298         return i1 == tmpi2;
299 }
300
301
302 void Undo::Private::doRecordUndo(UndoKind kind,
303         DocIterator const & cell,
304         pit_type first_pit, pit_type last_pit,
305         CursorData const & cur_before,
306         UndoElementStack & stack)
307 {
308         if (!group_level) {
309                 LYXERR0("There is no group open (creating one)");
310                 ++group_id;
311         }
312
313         if (first_pit > last_pit)
314                 swap(first_pit, last_pit);
315
316         // Undo::ATOMIC are always recorded (no overlapping there).
317         // As nobody wants all removed character appear one by one when undoing,
318         // we want combine 'similar' non-ATOMIC undo recordings to one.
319         pit_type from = first_pit;
320         pit_type end = cell.lastpit() - last_pit;
321         if (!undo_finished_
322             && kind != ATOMIC_UNDO
323             && !stack.empty()
324             && samePar(stack.top().cell, cell)
325             && stack.top().kind == kind
326             && stack.top().from == from
327             && stack.top().end == end) {
328                 // reset cur_after; it will be filled correctly by endUndoGroup.
329                 stack.top().cur_after = CursorData();
330                 return;
331         }
332
333         LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
334         // create the position information of the Undo entry
335         UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
336                          buffer_.isClean(), group_id);
337
338         // fill in the real data to be saved
339         if (cell.inMathed()) {
340                 // simply use the whole cell
341                 MathData & ar = cell.cell();
342                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
343         } else {
344                 // some more effort needed here as 'the whole cell' of the
345                 // main Text _is_ the whole document.
346                 // record the relevant paragraphs
347                 Text const * text = cell.text();
348                 LBUFERR(text);
349                 ParagraphList const & plist = text->paragraphs();
350                 ParagraphList::const_iterator first = plist.begin();
351                 advance(first, first_pit);
352                 ParagraphList::const_iterator last = plist.begin();
353                 advance(last, last_pit + 1);
354                 undo.pars = new ParagraphList(first, last);
355         }
356
357         // push the undo entry to undo stack
358         stack.push(undo);
359         //lyxerr << "undo record: " << stack.top() << endl;
360 }
361
362
363 void Undo::Private::recordUndo(UndoKind kind,
364                                DocIterator const & cell,
365                                pit_type first_pit, pit_type last_pit,
366                                CursorData const & cur)
367 {
368         LASSERT(first_pit <= cell.lastpit(), return);
369         LASSERT(last_pit <= cell.lastpit(), return);
370
371         doRecordUndo(kind, cell, first_pit, last_pit, cur,
372                 undostack_);
373
374         // next time we'll try again to combine entries if possible
375         undo_finished_ = false;
376
377         // If we ran recordUndo, it means that we plan to change the buffer
378         buffer_.markDirty();
379
380         redostack_.clear();
381 }
382
383
384 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
385                                                                            UndoElementStack & stack)
386 {
387         if (!group_level) {
388                 LYXERR0("There is no group open (creating one)");
389                 ++group_id;
390         }
391
392         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
393         // create the position information of the Undo entry
394         UndoElement undo(cur_before, buffer_.params(), buffer_.isClean(),
395                                          group_id);
396
397         // push the undo entry to undo stack
398         stack.push(undo);
399 }
400
401
402 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
403 {
404         doRecordUndoBufferParams(cur, undostack_);
405
406         // next time we'll try again to combine entries if possible
407         undo_finished_ = false;
408
409         // If we ran recordUndo, it means that we plan to change the buffer
410         buffer_.markDirty();
411
412         redostack_.clear();
413 }
414
415
416 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
417 {
418         // Adjust undo stack and get hold of current undo data.
419         UndoElement & undo = stack.top();
420         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
421         // We'll pop the stack only when we're done with this element. So do NOT
422         // try to return early.
423
424         // We will store in otherstack the part of the document under 'undo'
425         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
426
427         if (undo.bparams)
428                 doRecordUndoBufferParams(undo.cur_after, otherstack);
429         else
430                 doRecordUndo(ATOMIC_UNDO, cell_dit,
431                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
432                                          otherstack);
433         otherstack.top().cur_after = undo.cur_before;
434
435         // This does the actual undo/redo.
436         //LYXERR0("undo, performing: " << undo);
437         DocIterator dit = undo.cell.asDocIterator(&buffer_);
438         if (undo.bparams) {
439                 // This is a params undo element
440                 delete otherstack.top().bparams;
441                 otherstack.top().bparams = new BufferParams(buffer_.params());
442                 buffer_.params() = *undo.bparams;
443         } else if (dit.inMathed()) {
444                 // We stored the full cell here as there is not much to be
445                 // gained by storing just 'a few' paragraphs (most if not
446                 // all math inset cells have just one paragraph!)
447                 //LYXERR0("undo.array: " << *undo.array);
448                 LBUFERR(undo.array);
449                 dit.cell().swap(*undo.array);
450                 delete undo.array;
451                 undo.array = 0;
452         } else {
453                 // Some finer machinery is needed here.
454                 Text * text = dit.text();
455                 LBUFERR(text);
456                 LBUFERR(undo.pars);
457                 ParagraphList & plist = text->paragraphs();
458
459                 // remove new stuff between first and last
460                 ParagraphList::iterator first = plist.begin();
461                 advance(first, undo.from);
462                 ParagraphList::iterator last = plist.begin();
463                 advance(last, plist.size() - undo.end);
464                 plist.erase(first, last);
465
466                 // re-insert old stuff instead
467                 first = plist.begin();
468                 advance(first, undo.from);
469
470                 // this ugly stuff is needed until we get rid of the
471                 // inset_owner backpointer
472                 ParagraphList::iterator pit = undo.pars->begin();
473                 ParagraphList::iterator const end = undo.pars->end();
474                 for (; pit != end; ++pit)
475                         pit->setInsetOwner(dit.realInset());
476                 plist.insert(first, undo.pars->begin(), undo.pars->end());
477                 delete undo.pars;
478                 undo.pars = 0;
479         }
480
481         // We'll clean up in release mode.
482         LASSERT(undo.pars == 0, undo.pars = 0);
483         LASSERT(undo.array == 0, undo.array = 0);
484
485         if (!undo.cur_before.empty())
486                 cur = undo.cur_before;
487         if (undo.lyx_clean)
488                 buffer_.markClean();
489         else
490                 buffer_.markDirty();
491         // Now that we're done with undo, we pop it off the stack.
492         stack.pop();
493 }
494
495
496 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
497 {
498         undo_finished_ = true;
499
500         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
501
502         if (stack.empty())
503                 // Nothing to do.
504                 return false;
505
506         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
507
508         const size_t gid = stack.top().group_id;
509         while (!stack.empty() && stack.top().group_id == gid)
510                 doTextUndoOrRedo(cur, stack, otherstack);
511
512         // Adapt the new material to current buffer.
513         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
514         return true;
515 }
516
517
518 void Undo::finishUndo()
519 {
520         // Make sure the next operation will be stored.
521         d->undo_finished_ = true;
522 }
523
524
525 bool Undo::textUndo(CursorData & cur)
526 {
527         return d->textUndoOrRedo(cur, true);
528 }
529
530
531 bool Undo::textRedo(CursorData & cur)
532 {
533         return d->textUndoOrRedo(cur, false);
534 }
535
536
537 void Undo::beginUndoGroup()
538 {
539         if (d->group_level == 0) {
540                 // create a new group
541                 ++d->group_id;
542                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
543         }
544         ++d->group_level;
545 }
546
547
548 void Undo::endUndoGroup()
549 {
550         if (d->group_level == 0) {
551                 LYXERR0("There is no undo group to end here");
552                 return;
553         }
554         --d->group_level;
555         if (d->group_level == 0) {
556                 // real end of the group
557                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
558         }
559 }
560
561
562 void Undo::endUndoGroup(CursorData const & cur)
563 {
564         endUndoGroup();
565         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
566                 d->undostack_.top().cur_after = cur;
567 }
568
569
570 // FIXME: remove these convenience functions and make
571 // Private::recordUndo public as sole interface. The code in the
572 // convenience functions can move to Cursor.cpp.
573
574 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
575 {
576         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
577 }
578
579
580 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
581                            Inset const * inset)
582 {
583         if (!inset || inset == &cur.inset()) {
584                 DocIterator c = cur;
585                 c.pop_back();
586                 d->recordUndo(kind, c, c.pit(), c.pit(), cur);
587         } else if (inset == cur.nextInset())
588                 recordUndo(cur, kind);
589         else
590                 LYXERR0("Inset not found, no undo stack added.");
591 }
592
593
594 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
595 {
596         d->recordUndo(kind, cur, cur.pit(), from, cur);
597 }
598
599
600 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
601         pit_type from, pit_type to)
602 {
603         d->recordUndo(kind, cur, from, to, cur);
604 }
605
606
607 void Undo::recordUndoBufferParams(CursorData const & cur)
608 {
609         d->recordUndoBufferParams(cur);
610 }
611
612
613 void Undo::recordUndoFullBuffer(CursorData const & cur)
614 {
615         // This one may happen outside of the main undo group, so we
616         // put it in its own subgroup to avoid complaints.
617         beginUndoGroup();
618         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
619                       0, d->buffer_.paragraphs().size() - 1, cur);
620         d->recordUndoBufferParams(cur);
621         endUndoGroup();
622 }
623
624
625 } // namespace lyx