* de.po: sync with branch.
[lyx.git] / src / Undo.cpp
bloba24bdead03e3861df2d6d18ab52e0b738be626ed
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.
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
13 * Full author contact details are available in file CREDITS.
16 #include <config.h>
18 #include "Undo.h"
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"
28 #include "mathed/MathSupport.h"
29 #include "mathed/MathData.h"
31 #include "insets/Inset.h"
33 #include "support/lassert.h"
34 #include "support/debug.h"
36 #include <algorithm>
37 #include <deque>
39 using namespace std;
40 using namespace lyx::support;
43 namespace lyx {
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.
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.
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.
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.
67 struct UndoElement
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)
78 if (isFullBuffer)
79 bparams = new BufferParams(bp);
81 ///
82 UndoElement(UndoElement const & ue)
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;
96 ///
97 ~UndoElement()
99 if (isFullBuffer)
100 delete bparams;
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();
128 class UndoElementStack
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(); }
136 /// Return the top element.
137 UndoElement & top() { return c_.front(); }
139 /// Pop and throw away the top element.
140 void pop() { c_.pop_front(); }
142 /// Return true if the stack is empty.
143 bool empty() const { return c_.empty(); }
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;
151 c_.clear();
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();
166 private:
167 /// Internal contents.
168 std::deque<UndoElement> c_;
169 /// The maximum number elements stored.
170 size_t limit_;
174 struct Undo::Private
176 Private(Buffer & buffer) : buffer_(buffer), undo_finished_(true),
177 group_id(0), group_level(0) {}
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);
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);
193 void recordUndo(UndoKind kind,
194 DocIterator const & cur,
195 pit_type first_pit,
196 pit_type last_pit);
199 Buffer & buffer_;
200 /// Undo stack.
201 UndoElementStack undostack_;
202 /// Redo stack.
203 UndoElementStack redostack_;
205 /// The flag used by Undo::finishUndo().
206 bool undo_finished_;
208 /// Current group Id.
209 size_t group_id;
210 /// Current group nesting nevel.
211 size_t group_level;
215 /////////////////////////////////////////////////////////////////////
217 // Undo
219 /////////////////////////////////////////////////////////////////////
222 Undo::Undo(Buffer & buffer)
223 : d(new Undo::Private(buffer))
227 Undo::~Undo()
229 delete d;
233 bool Undo::hasUndoStack() const
235 return !d->undostack_.empty();
239 bool Undo::hasRedoStack() const
241 return !d->redostack_.empty();
245 static bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
247 StableDocIterator tmpi2 = i2;
248 tmpi2.pos() = i1.pos();
249 return i1 == tmpi2;
253 /////////////////////////////////////////////////////////////////////
255 // Undo::Private
257 ///////////////////////////////////////////////////////////////////////
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)
266 if (!group_level) {
267 LYXERR0("There is no group open (creating one)");
268 ++group_id;
271 if (first_pit > last_pit)
272 swap(first_pit, last_pit);
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;
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);
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);
314 // push the undo entry to undo stack
315 stack.push(undo);
316 //lyxerr << "undo record: " << stack.top() << endl;
318 // next time we'll try again to combine entries if possible
319 undo_finished_ = false;
323 void Undo::Private::recordUndo(UndoKind kind, DocIterator const & cur,
324 pit_type first_pit, pit_type last_pit)
326 LASSERT(first_pit <= cur.lastpit(), /**/);
327 LASSERT(last_pit <= cur.lastpit(), /**/);
329 doRecordUndo(kind, cur, first_pit, last_pit, cur,
330 false, undostack_);
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;
340 void Undo::Private::doTextUndoOrRedo(DocIterator & cur, UndoElementStack & stack, UndoElementStack & otherstack)
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.
348 // We will store in otherstack the part of the document under 'undo'
349 DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
351 doRecordUndo(ATOMIC_UNDO, cell_dit,
352 undo.from, cell_dit.lastpit() - undo.end, cur,
353 undo.isFullBuffer, otherstack);
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();
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);
390 // re-insert old stuff instead
391 first = plist.begin();
392 advance(first, undo.from);
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;
404 LASSERT(undo.pars == 0, /**/);
405 LASSERT(undo.array == 0, /**/);
407 cur = undo.cursor.asDocIterator(&buffer_);
408 // Now that we're done with undo, we pop it off the stack.
409 stack.pop();
413 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
415 undo_finished_ = true;
417 UndoElementStack & stack = isUndoOperation ? undostack_ : redostack_;
419 if (stack.empty())
420 // Nothing to do.
421 return false;
423 UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
425 const size_t gid = stack.top().group_id;
426 while (!stack.empty() && stack.top().group_id == gid)
427 doTextUndoOrRedo(cur, stack, otherstack);
429 // Adapt the new material to current buffer.
430 buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
431 buffer_.updateLabels();
432 return true;
436 void Undo::finishUndo()
438 // Make sure the next operation will be stored.
439 d->undo_finished_ = true;
443 bool Undo::textUndo(DocIterator & cur)
445 return d->textUndoOrRedo(cur, true);
449 bool Undo::textRedo(DocIterator & cur)
451 return d->textUndoOrRedo(cur, false);
455 void Undo::beginUndoGroup()
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);
462 ++d->group_level;
466 void Undo::endUndoGroup()
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);
479 void Undo::recordUndo(DocIterator const & cur, UndoKind kind)
481 d->recordUndo(kind, cur, cur.pit(), cur.pit());
485 void Undo::recordUndoInset(DocIterator const & cur, UndoKind kind)
487 DocIterator c = cur;
488 c.pop_back();
489 d->doRecordUndo(kind, c, c.pit(), c.pit(), cur, false, d->undostack_);
493 void Undo::recordUndo(DocIterator const & cur, UndoKind kind, pit_type from)
495 d->recordUndo(kind, cur, cur.pit(), from);
499 void Undo::recordUndo(DocIterator const & cur, UndoKind kind,
500 pit_type from, pit_type to)
502 d->recordUndo(kind, cur, from, to);
506 void Undo::recordUndoFullDocument(DocIterator const & cur)
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_
519 endUndoGroup();
523 } // namespace lyx