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