]> git.lyx.org Git - lyx.git/blob - src/Undo.cpp
Make sure a temporary file is always created in the global temporary dir.
[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 params undo
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             && !stack.top().bparams
325             && samePar(stack.top().cell, cell)
326             && stack.top().kind == kind
327             && stack.top().from == from
328             && stack.top().end == end) {
329                 // reset cur_after; it will be filled correctly by endUndoGroup.
330                 stack.top().cur_after = CursorData();
331                 return;
332         }
333
334         LYXERR(Debug::UNDO, "Create undo element of group " << group_id);
335         // create the position information of the Undo entry
336         UndoElement undo(kind, cur_before, cell, from, end, 0, 0,
337                          buffer_.isClean(), group_id);
338
339         // fill in the real data to be saved
340         if (cell.inMathed()) {
341                 // simply use the whole cell
342                 MathData & ar = cell.cell();
343                 undo.array = new MathData(ar.buffer(), ar.begin(), ar.end());
344         } else {
345                 // some more effort needed here as 'the whole cell' of the
346                 // main Text _is_ the whole document.
347                 // record the relevant paragraphs
348                 Text const * text = cell.text();
349                 LBUFERR(text);
350                 ParagraphList const & plist = text->paragraphs();
351                 ParagraphList::const_iterator first = plist.begin();
352                 advance(first, first_pit);
353                 ParagraphList::const_iterator last = plist.begin();
354                 advance(last, last_pit + 1);
355                 undo.pars = new ParagraphList(first, last);
356         }
357
358         // push the undo entry to undo stack
359         stack.push(undo);
360         //lyxerr << "undo record: " << stack.top() << endl;
361 }
362
363
364 void Undo::Private::recordUndo(UndoKind kind,
365                                DocIterator const & cell,
366                                pit_type first_pit, pit_type last_pit,
367                                CursorData const & cur)
368 {
369         LASSERT(first_pit <= cell.lastpit(), return);
370         LASSERT(last_pit <= cell.lastpit(), return);
371
372         doRecordUndo(kind, cell, first_pit, last_pit, cur,
373                 undostack_);
374
375         // next time we'll try again to combine entries if possible
376         undo_finished_ = false;
377
378         // If we ran recordUndo, it means that we plan to change the buffer
379         buffer_.markDirty();
380
381         redostack_.clear();
382 }
383
384
385 void Undo::Private::doRecordUndoBufferParams(CursorData const & cur_before,
386                                                                            UndoElementStack & stack)
387 {
388         if (!group_level) {
389                 LYXERR0("There is no group open (creating one)");
390                 ++group_id;
391         }
392
393         LYXERR(Debug::UNDO, "Create full buffer undo element of group " << group_id);
394         // create the position information of the Undo entry
395         UndoElement undo(cur_before, buffer_.params(), buffer_.isClean(),
396                                          group_id);
397
398         // push the undo entry to undo stack
399         stack.push(undo);
400 }
401
402
403 void Undo::Private::recordUndoBufferParams(CursorData const & cur)
404 {
405         doRecordUndoBufferParams(cur, undostack_);
406
407         // next time we'll try again to combine entries if possible
408         undo_finished_ = false;
409
410         // If we ran recordUndo, it means that we plan to change the buffer
411         buffer_.markDirty();
412
413         redostack_.clear();
414 }
415
416
417 void Undo::Private::doTextUndoOrRedo(CursorData & cur, UndoElementStack & stack, UndoElementStack & otherstack)
418 {
419         // Adjust undo stack and get hold of current undo data.
420         UndoElement & undo = stack.top();
421         LYXERR(Debug::UNDO, "Undo element of group " << undo.group_id);
422         // We'll pop the stack only when we're done with this element. So do NOT
423         // try to return early.
424
425         // We will store in otherstack the part of the document under 'undo'
426         DocIterator cell_dit = undo.cell.asDocIterator(&buffer_);
427
428         if (undo.bparams)
429                 doRecordUndoBufferParams(undo.cur_after, otherstack);
430         else
431                 doRecordUndo(ATOMIC_UNDO, cell_dit,
432                                          undo.from, cell_dit.lastpit() - undo.end, undo.cur_after,
433                                          otherstack);
434         otherstack.top().cur_after = undo.cur_before;
435
436         // This does the actual undo/redo.
437         //LYXERR0("undo, performing: " << undo);
438         DocIterator dit = undo.cell.asDocIterator(&buffer_);
439         if (undo.bparams) {
440                 // This is a params undo element
441                 delete otherstack.top().bparams;
442                 otherstack.top().bparams = new BufferParams(buffer_.params());
443                 buffer_.params() = *undo.bparams;
444         } else if (dit.inMathed()) {
445                 // We stored the full cell here as there is not much to be
446                 // gained by storing just 'a few' paragraphs (most if not
447                 // all math inset cells have just one paragraph!)
448                 //LYXERR0("undo.array: " << *undo.array);
449                 LBUFERR(undo.array);
450                 dit.cell().swap(*undo.array);
451                 delete undo.array;
452                 undo.array = 0;
453         } else {
454                 // Some finer machinery is needed here.
455                 Text * text = dit.text();
456                 LBUFERR(text);
457                 LBUFERR(undo.pars);
458                 ParagraphList & plist = text->paragraphs();
459
460                 // remove new stuff between first and last
461                 ParagraphList::iterator first = plist.begin();
462                 advance(first, undo.from);
463                 ParagraphList::iterator last = plist.begin();
464                 advance(last, plist.size() - undo.end);
465                 plist.erase(first, last);
466
467                 // re-insert old stuff instead
468                 first = plist.begin();
469                 advance(first, undo.from);
470
471                 // this ugly stuff is needed until we get rid of the
472                 // inset_owner backpointer
473                 ParagraphList::iterator pit = undo.pars->begin();
474                 ParagraphList::iterator const end = undo.pars->end();
475                 for (; pit != end; ++pit)
476                         pit->setInsetOwner(dit.realInset());
477                 plist.insert(first, undo.pars->begin(), undo.pars->end());
478                 delete undo.pars;
479                 undo.pars = 0;
480         }
481
482         // We'll clean up in release mode.
483         LASSERT(undo.pars == 0, undo.pars = 0);
484         LASSERT(undo.array == 0, undo.array = 0);
485
486         if (!undo.cur_before.empty())
487                 cur = undo.cur_before;
488         if (undo.lyx_clean)
489                 buffer_.markClean();
490         else
491                 buffer_.markDirty();
492         // Now that we're done with undo, we pop it off the stack.
493         stack.pop();
494 }
495
496
497 bool Undo::Private::textUndoOrRedo(CursorData & cur, bool isUndoOperation)
498 {
499         undo_finished_ = true;
500
501         UndoElementStack & stack = isUndoOperation ?  undostack_ : redostack_;
502
503         if (stack.empty())
504                 // Nothing to do.
505                 return false;
506
507         UndoElementStack & otherstack = isUndoOperation ? redostack_ : undostack_;
508
509         const size_t gid = stack.top().group_id;
510         while (!stack.empty() && stack.top().group_id == gid)
511                 doTextUndoOrRedo(cur, stack, otherstack);
512
513         // Adapt the new material to current buffer.
514         buffer_.setBuffersForInsets(); // FIXME This shouldn't be here.
515         return true;
516 }
517
518
519 void Undo::finishUndo()
520 {
521         // Make sure the next operation will be stored.
522         d->undo_finished_ = true;
523 }
524
525
526 bool Undo::textUndo(CursorData & cur)
527 {
528         return d->textUndoOrRedo(cur, true);
529 }
530
531
532 bool Undo::textRedo(CursorData & cur)
533 {
534         return d->textUndoOrRedo(cur, false);
535 }
536
537
538 void Undo::beginUndoGroup()
539 {
540         if (d->group_level == 0) {
541                 // create a new group
542                 ++d->group_id;
543                 LYXERR(Debug::UNDO, "+++++++Creating new group " << d->group_id);
544         }
545         ++d->group_level;
546 }
547
548
549 void Undo::endUndoGroup()
550 {
551         if (d->group_level == 0) {
552                 LYXERR0("There is no undo group to end here");
553                 return;
554         }
555         --d->group_level;
556         if (d->group_level == 0) {
557                 // real end of the group
558                 LYXERR(Debug::UNDO, "-------End of group " << d->group_id);
559         }
560 }
561
562
563 void Undo::endUndoGroup(CursorData const & cur)
564 {
565         endUndoGroup();
566         if (!d->undostack_.empty() && d->undostack_.top().cur_after.empty())
567                 d->undostack_.top().cur_after = cur;
568 }
569
570
571 // FIXME: remove these convenience functions and make
572 // Private::recordUndo public as sole interface. The code in the
573 // convenience functions can move to Cursor.cpp.
574
575 void Undo::recordUndo(CursorData const & cur, UndoKind kind)
576 {
577         d->recordUndo(kind, cur, cur.pit(), cur.pit(), cur);
578 }
579
580
581 void Undo::recordUndoInset(CursorData const & cur, UndoKind kind,
582                            Inset const * inset)
583 {
584         if (!inset || inset == &cur.inset()) {
585                 DocIterator c = cur;
586                 c.pop_back();
587                 d->recordUndo(kind, c, c.pit(), c.pit(), cur);
588         } else if (inset == cur.nextInset())
589                 recordUndo(cur, kind);
590         else
591                 LYXERR0("Inset not found, no undo stack added.");
592 }
593
594
595 void Undo::recordUndo(CursorData const & cur, UndoKind kind, pit_type from)
596 {
597         d->recordUndo(kind, cur, cur.pit(), from, cur);
598 }
599
600
601 void Undo::recordUndo(CursorData const & cur, UndoKind kind,
602         pit_type from, pit_type to)
603 {
604         d->recordUndo(kind, cur, from, to, cur);
605 }
606
607
608 void Undo::recordUndoBufferParams(CursorData const & cur)
609 {
610         d->recordUndoBufferParams(cur);
611 }
612
613
614 void Undo::recordUndoFullBuffer(CursorData const & cur)
615 {
616         // This one may happen outside of the main undo group, so we
617         // put it in its own subgroup to avoid complaints.
618         beginUndoGroup();
619         d->recordUndo(ATOMIC_UNDO, doc_iterator_begin(&d->buffer_),
620                       0, d->buffer_.paragraphs().size() - 1, cur);
621         d->recordUndoBufferParams(cur);
622         endUndoGroup();
623 }
624
625
626 } // namespace lyx