]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
I'll find a solution for the 'dirList problem', Abdel.
[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 "support/debug.h"
24 #include "DocIterator.h"
25 #include "Paragraph.h"
26 #include "ParagraphList.h"
27 #include "Text.h"
28
29 #include "mathed/MathSupport.h"
30 #include "mathed/MathData.h"
31
32 #include "insets/Inset.h"
33
34 #include "support/limited_stack.h"
35
36 #include <algorithm>
37 #include <ostream>
38
39 using std::advance;
40
41 namespace lyx {
42
43
44 /**
45 These are the elements put on the undo stack. Each object contains complete
46 paragraphs from some cell and sufficient information to restore the cursor
47 state.
48
49 The cell is given by a DocIterator pointing to this cell, the 'interesting'
50 range of paragraphs by counting them from begin and end of cell,
51 respectively.
52
53 The cursor is also given as DocIterator and should point to some place in
54 the stored paragraph range.  In case of math, we simply store the whole
55 cell, as there usually is just a simple paragraph in a cell.
56
57 The idea is to store the contents of 'interesting' paragraphs in some
58 structure ('Undo') _before_ it is changed in some edit operation.
59 Obviously, the stored ranged should be as small as possible. However, it
60 there is a lower limit: The StableDocIterator pointing stored in the undo
61 class must be valid after the changes, too, as it will used as a pointer
62 where to insert the stored bits when performining undo.
63
64 */
65 struct UndoElement
66 {
67         /// Which kind of operation are we recording for?
68         UndoKind kind;
69         /// the position of the cursor
70         StableDocIterator cursor;
71         /// the position of the cell described
72         StableDocIterator cell;
73         /// counted from begin of cell
74         pit_type from;
75         /// complement to end of this cell
76         pit_type end;
77         /// the contents of the saved Paragraphs (for texted)
78         ParagraphList * pars;
79         /// the contents of the saved MathData (for mathed)
80         MathData * array;
81         /// Only used in case of full backups
82         BufferParams bparams;
83         /// Only used in case of full backups
84         bool isFullBuffer;
85 };
86
87
88 struct Undo::Private
89 {
90         Private(Buffer & buffer): buffer_(buffer) {}
91         
92         // Returns false if no undo possible.
93         bool textUndoOrRedo(DocIterator & cur, bool isUndoOperation);
94         ///
95         void doRecordUndo(UndoKind kind,
96                 DocIterator const & cell,
97                 pit_type first_pit,
98                 pit_type last_pit,
99                 DocIterator const & cur,
100                 bool isFullBuffer,
101                 bool isUndoOperation);
102
103         ///
104         void recordUndo(UndoKind kind,
105                 DocIterator & cur,
106                 pit_type first_pit,
107                 pit_type last_pit);
108
109         ///
110         Buffer & buffer_;
111         /// Undo stack.
112         limited_stack<UndoElement> undostack;
113         /// Redo stack.
114         limited_stack<UndoElement> redostack;
115
116         /// The flag used by Undo::finishUndo().
117         bool undo_finished;
118 };
119
120
121 Undo::Undo(Buffer & buffer): d(new Undo::Private(buffer))
122 {
123 }
124
125
126 Undo::~Undo()
127 {
128         delete d;
129 }
130
131
132 bool Undo::hasUndoStack() const
133 {
134         return !d->undostack.empty();
135 }
136
137
138 bool Undo::hasRedoStack() const
139 {
140         return !d->redostack.empty();
141 }
142
143
144
145
146 namespace {
147
148 std::ostream & operator<<(std::ostream & os, UndoElement const & undo)
149 {
150         return os << " from: " << undo.from << " end: " << undo.end
151                 << " cell:\n" << undo.cell
152                 << " cursor:\n" << undo.cursor;
153 }
154
155
156 bool samePar(StableDocIterator const & i1, StableDocIterator const & i2)
157 {
158         StableDocIterator tmpi2 = i2;
159         tmpi2.pos() = i1.pos();
160         return i1 == tmpi2;
161 }
162
163 } // namespace anon
164
165
166 void Undo::Private::doRecordUndo(UndoKind kind,
167         DocIterator const & cell,
168         pit_type first_pit, pit_type last_pit,
169         DocIterator const & cur,
170         bool isFullBuffer,
171         bool isUndoOperation)
172 {
173         if (first_pit > last_pit)
174                 std::swap(first_pit, last_pit);
175         // create the position information of the Undo entry
176         UndoElement undo;
177         undo.array = 0;
178         undo.pars = 0;
179         undo.kind = kind;
180         undo.cell = cell;
181         undo.cursor = cur;
182         undo.bparams = buffer_.params();
183         undo.isFullBuffer = isFullBuffer;
184         //lyxerr << "recordUndo: cur: " << cur << endl;
185         //lyxerr << "recordUndo: pos: " << cur.pos() << endl;
186         //lyxerr << "recordUndo: cell: " << cell << endl;
187         undo.from = first_pit;
188         undo.end = cell.lastpit() - last_pit;
189
190         limited_stack<UndoElement> & stack = isUndoOperation ?
191                 undostack : redostack;
192
193         // Undo::ATOMIC are always recorded (no overlapping there).
194         // As nobody wants all removed character appear one by one when undoing,
195         // we want combine 'similar' non-ATOMIC undo recordings to one.
196         if (!undo_finished
197             && kind != ATOMIC_UNDO
198             && !stack.empty()
199             && samePar(stack.top().cell, undo.cell)
200             && stack.top().kind == undo.kind
201             && stack.top().from == undo.from
202             && stack.top().end == undo.end)
203                 return;
204
205         // fill in the real data to be saved
206         if (cell.inMathed()) {
207                 // simply use the whole cell
208                 undo.array = new MathData(cell.cell());
209         } else {
210                 // some more effort needed here as 'the whole cell' of the
211                 // main Text _is_ the whole document.
212                 // record the relevant paragraphs
213                 Text const * text = cell.text();
214                 BOOST_ASSERT(text);
215                 ParagraphList const & plist = text->paragraphs();
216                 ParagraphList::const_iterator first = plist.begin();
217                 advance(first, first_pit);
218                 ParagraphList::const_iterator last = plist.begin();
219                 advance(last, last_pit + 1);
220                 undo.pars = new ParagraphList(first, last);
221         }
222
223         // push the undo entry to undo stack
224         stack.push(undo);
225         //lyxerr << "undo record: " << stack.top() << std::endl;
226
227         // next time we'll try again to combine entries if possible
228         undo_finished = false;
229 }
230
231
232 void Undo::Private::recordUndo(UndoKind kind, DocIterator & cur,
233         pit_type first_pit, pit_type last_pit)
234 {
235         BOOST_ASSERT(first_pit <= cur.lastpit());
236         BOOST_ASSERT(last_pit <= cur.lastpit());
237
238         doRecordUndo(kind, cur, first_pit, last_pit, cur,
239                 false, true);
240
241         undo_finished = false;
242         redostack.clear();
243         //lyxerr << "undostack:\n";
244         //for (size_t i = 0, n = buf.undostack().size(); i != n && i < 6; ++i)
245         //      lyxerr << "  " << i << ": " << buf.undostack()[i] << std::endl;
246 }
247
248
249 bool Undo::Private::textUndoOrRedo(DocIterator & cur, bool isUndoOperation)
250 {
251         undo_finished = true;
252
253         limited_stack<UndoElement> & stack = isUndoOperation ?
254                 undostack : redostack;
255
256         if (stack.empty())
257                 // Nothing to do.
258                 return false;
259
260         limited_stack<UndoElement> & otherstack = isUndoOperation ?
261                 redostack : undostack;
262
263         // Adjust undo stack and get hold of current undo data.
264         UndoElement undo = stack.top();
265         stack.pop();
266
267         // We will store in otherstack the part of the document under 'undo'
268         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_.inset());
269
270         doRecordUndo(ATOMIC_UNDO, cell_dit,
271                 undo.from, cell_dit.lastpit() - undo.end, cur,
272                 undo.isFullBuffer, !isUndoOperation);
273
274         // This does the actual undo/redo.
275         //LYXERR0("undo, performing: " << undo);
276         bool labelsUpdateNeeded = false;
277         DocIterator dit = undo.cell.asDocIterator(&buffer_.inset());
278         if (undo.isFullBuffer) {
279                 BOOST_ASSERT(undo.pars);
280                 // This is a full document
281                 otherstack.top().bparams = buffer_.params();
282                 buffer_.params() = undo.bparams;
283                 std::swap(buffer_.paragraphs(), *undo.pars);
284                 delete undo.pars;
285                 undo.pars = 0;
286         } else if (dit.inMathed()) {
287                 // We stored the full cell here as there is not much to be
288                 // gained by storing just 'a few' paragraphs (most if not
289                 // all math inset cells have just one paragraph!)
290                 //LYXERR0("undo.array: " << *undo.array);
291                 BOOST_ASSERT(undo.array);
292                 dit.cell().swap(*undo.array);
293                 delete undo.array;
294                 undo.array = 0;
295         } else {
296                 // Some finer machinery is needed here.
297                 Text * text = dit.text();
298                 BOOST_ASSERT(text);
299                 BOOST_ASSERT(undo.pars);
300                 ParagraphList & plist = text->paragraphs();
301
302                 // remove new stuff between first and last
303                 ParagraphList::iterator first = plist.begin();
304                 advance(first, undo.from);
305                 ParagraphList::iterator last = plist.begin();
306                 advance(last, plist.size() - undo.end);
307                 plist.erase(first, last);
308
309                 // re-insert old stuff instead
310                 first = plist.begin();
311                 advance(first, undo.from);
312
313                 // this ugly stuff is needed until we get rid of the
314                 // inset_owner backpointer
315                 ParagraphList::iterator pit = undo.pars->begin();
316                 ParagraphList::iterator const end = undo.pars->end();
317                 for (; pit != end; ++pit)
318                         pit->setInsetOwner(dit.realInset());
319                 plist.insert(first, undo.pars->begin(), undo.pars->end());
320                 delete undo.pars;
321                 undo.pars = 0;
322                 labelsUpdateNeeded = true;
323         }
324         BOOST_ASSERT(undo.pars == 0);
325         BOOST_ASSERT(undo.array == 0);
326
327         cur = undo.cursor.asDocIterator(&buffer_.inset());
328         
329         if (labelsUpdateNeeded)
330                 updateLabels(buffer_);
331         undo_finished = true;
332         return true;
333 }
334
335
336 void Undo::finishUndo()
337 {
338         // Make sure the next operation will be stored.
339         d->undo_finished = true;
340 }
341
342
343 bool Undo::textUndo(DocIterator & cur)
344 {
345         return d->textUndoOrRedo(cur, true);
346 }
347
348
349 bool Undo::textRedo(DocIterator & cur)
350 {
351         return d->textUndoOrRedo(cur, false);
352 }
353
354
355 void Undo::recordUndo(DocIterator & cur, UndoKind kind)
356 {
357         d->recordUndo(kind, cur, cur.pit(), cur.pit());
358 }
359
360
361 void Undo::recordUndoInset(DocIterator & cur, UndoKind kind)
362 {
363         DocIterator c = cur;
364         c.pop_back();
365         d->doRecordUndo(kind, c, c.pit(), c.pit(),      cur, false, true);
366 }
367
368
369 void Undo::recordUndo(DocIterator & cur, UndoKind kind, pit_type from)
370 {
371         d->recordUndo(kind, cur, cur.pit(), from);
372 }
373
374
375 void Undo::recordUndo(DocIterator & cur, UndoKind kind,
376         pit_type from, pit_type to)
377 {
378         d->recordUndo(kind, cur, from, to);
379 }
380
381
382 void Undo::recordUndoFullDocument(DocIterator & cur)
383 {
384         d->doRecordUndo(
385                 ATOMIC_UNDO,
386                 doc_iterator_begin(d->buffer_.inset()),
387                 0, d->buffer_.paragraphs().size() - 1,
388                 cur,
389                 true,
390                 true
391         );
392 }
393
394
395 } // namespace lyx