]> git.lyx.org Git - lyx.git/blob - src/undo_funcs.C
preparations to regain ability of per-inset undo.
[lyx.git] / src / undo_funcs.C
1 /**
2  * \file undo_funcs.C
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  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "undo_funcs.h"
18
19 #include "buffer.h"
20 #include "debug.h"
21 #include "BufferView.h"
22 #include "funcrequest.h"
23 #include "iterators.h"
24 #include "lyxtext.h"
25 #include "paragraph.h"
26
27 #include "insets/updatableinset.h"
28
29 #include <iostream>
30
31 using std::endl;
32 using lyx::paroffset_type;
33
34
35 /// The flag used by finishUndo().
36 bool undo_finished;
37
38 /// Whether actions are not added to the undo stacks.
39 bool undo_frozen;
40
41 namespace {
42
43
44 void recordUndo(BufferView * bv, Undo::undo_kind kind,
45         ParagraphList & plist, paroffset_type firstpar, paroffset_type lastpar,
46         limited_stack<Undo> & stack)
47 {
48         Buffer * buf = bv->buffer();
49
50         ParagraphList::iterator first = plist.begin();
51         advance(first, firstpar);
52         ParagraphList::iterator last = plist.begin();
53         advance(last, lastpar);
54
55         // First, record inset id, if cursor is in one
56         UpdatableInset * inset = first->inInset();
57         LyXText * text = inset ? inset->getLyXText(bv) : bv->text;
58         int const inset_id = inset ? inset->id() : -1;
59
60         // Try to find the appropriate list by counting the,
61         ParIterator null = buf->par_iterator_end();
62
63         int pcount = 0;
64         for (ParIterator it = buf->par_iterator_begin(); it != null; ++it, ++pcount)
65                 if (&it.plist() == &plist)
66                         break;
67         //lyxerr << "This is plist #" << pcount << ' ' << &plist << endl;
68                 
69         // We simply record the entire outer paragraphs
70         // First, identify the outer paragraphs
71         for (ParIterator it = buf->par_iterator_begin(); it != null; ++it) {
72                 if (it->id() == first->id())
73                         first = it.outerPar();
74                 if (it->id() == last->id())
75                         last = it.outerPar();
76         }
77
78         // And calculate a stable reference to them
79         int const first_offset = firstpar;
80         int const last_offset = plist.size() - lastpar;
81
82         // Undo::ATOMIC are always recorded (no overlapping there).
83
84         // Overlapping only with insert and delete inside one paragraph:
85         // Nobody wants all removed character appear one by one when undoing.
86         if (! undo_finished && kind != Undo::ATOMIC) {
87                 // Check whether storing is needed.
88                 if (! buf->undostack().empty()
89                     && buf->undostack().top().kind == kind
90                     && buf->undostack().top().first_par_offset == first_offset
91                     && buf->undostack().top().last_par_offset == last_offset) {
92                         // No additonal undo recording needed -
93                         // effectively, we combine undo recordings to one.
94                         return;
95                 }
96         }
97
98         // Record the cursor position in a stable way.
99         int const cursor_offset = text->cursor.par();
100
101         // Make and push the Undo entry
102         stack.push(Undo(kind, inset_id,
103                 pcount,
104                 first_offset, last_offset,
105                 cursor_offset, text->cursor.pos(),
106                 ParagraphList()));
107
108         // Record the relevant paragraphs
109         ParagraphList & undo_pars = stack.top().pars;
110
111         for (ParagraphList::iterator it = first; it != last; ++it) {
112                 undo_pars.push_back(*it);
113                 undo_pars.back().id(it->id());
114         }
115         undo_pars.push_back(*last);
116         undo_pars.back().id(last->id());
117
118         // And make sure that next time, we should be combining if possible
119         undo_finished = false;
120 }
121
122
123 // Returns false if no undo possible.
124 bool performUndoOrRedo(BufferView * bv, Undo const & undo)
125 {
126         Buffer * buf = bv->buffer();
127         ParagraphList & plist = buf->paragraphs();
128
129         ParIterator null = buf->par_iterator_end();
130         ParIterator plit = buf->par_iterator_begin();
131         int n = undo.plist;
132         for ( ; n && plit != null; ++plit, --n)
133                 ;
134         //lyxerr << "undo: using plist #" << undo.plist << " " << &plit.plist() << endl;
135
136         // Remove new stuff between first and last
137         {
138                 ParagraphList::iterator first = plist.begin();
139                 advance(first, undo.first_par_offset);
140                 ParagraphList::iterator last = plist.begin();
141                 advance(last, plist.size() - undo.last_par_offset);
142                 plist.erase(first, ++last);
143         }
144
145         // Re-insert old stuff instead
146         {
147                 if (plist.empty()) {
148                         plist.assign(undo.pars.begin(), undo.pars.end());
149                 } else {
150                         ParagraphList::iterator first = plist.begin();
151                         advance(first, undo.first_par_offset);
152                         plist.insert(first, undo.pars.begin(), undo.pars.end());
153                 }
154         }
155
156         // Rebreak the entire document
157         bv->text->fullRebreak();
158
159         // set cursor
160         {
161                 // Get a hold of the inset for the cursor, if relevant
162                 UpdatableInset * inset =
163                         static_cast<UpdatableInset *>(
164                                 buf->getInsetFromID(undo.inset_id));
165
166                 LyXText * text = inset ? inset->getLyXText(bv) : bv->text;
167                 text->setCursorIntern(undo.cursor_par_offset, undo.cursor_pos);
168
169                 //lyxerr << "undo, inset: " << inset << endl;
170
171                 if (inset) {
172                         //lyxerr << "undo, inset owner: " << inset->owner() << endl;
173
174                         // Magic needed to update inset internal state
175                         FuncRequest cmd(bv, LFUN_INSET_EDIT, "left");
176                         if (inset->owner())
177                                 inset->owner()->localDispatch(cmd);
178                         else
179                                 inset->localDispatch(cmd);
180                 }
181
182                 // set cursor again to force the position to be the right one
183                 text->setCursorIntern(undo.cursor_par_offset, undo.cursor_pos);
184
185                 // Clear any selection and set the selection
186                 // cursor for any new selection.
187                 text->clearSelection();
188                 text->selection.cursor = text->cursor;
189                 text->updateCounters();
190         }
191
192         finishUndo();
193         return true;
194 }
195
196
197 // Returns false if no undo possible.
198 bool textUndoOrRedo(BufferView * bv,
199         limited_stack<Undo> & stack,
200         limited_stack<Undo> & otherstack)
201 {
202         if (stack.empty()) {
203                 /*
204                  * Finish the undo operation in the case there was no entry
205                  * on the stack to perform.
206                  */
207                 freezeUndo();
208                 bv->unlockInset(bv->theLockingInset());
209                 finishUndo();
210                 unFreezeUndo();
211                 return false;
212         }
213
214         Undo undo = stack.top();
215         stack.pop();
216         finishUndo();
217
218         if (!undo_frozen) {
219                 otherstack.push(undo);
220                 otherstack.top().pars.clear();
221                 Buffer * buf = bv->buffer();
222                 ParagraphList & plist = buf->paragraphs();
223                 if (undo.first_par_offset + undo.last_par_offset <= int(plist.size())) {
224                         ParagraphList::iterator first = plist.begin();
225                         advance(first, undo.first_par_offset);
226                         ParagraphList::iterator last = plist.begin();
227                         advance(last, plist.size() - undo.last_par_offset + 1);
228                         otherstack.top().pars.insert(otherstack.top().pars.begin(), first, last);
229                 }
230         }
231
232         // Now we can unlock the inset for safety because the inset
233         // pointer could be changed during the undo-function. Anyway
234         // if needed we have to lock the right inset/position if this
235         // is requested.
236         freezeUndo();
237         bv->unlockInset(bv->theLockingInset());
238         bool const ret = performUndoOrRedo(bv, undo);
239         unFreezeUndo();
240         return ret;
241 }
242
243 } // namespace anon
244
245
246 void freezeUndo()
247 {
248         // This is dangerous and for internal use only.
249         undo_frozen = true;
250 }
251
252
253 void unFreezeUndo()
254 {
255         // This is dangerous and for internal use only.
256         undo_frozen = false;
257 }
258
259
260 void finishUndo()
261 {
262         // Makes sure the next operation will be stored.
263         undo_finished = true;
264 }
265
266
267 bool textUndo(BufferView * bv)
268 {
269         return textUndoOrRedo(bv, bv->buffer()->undostack(),
270                               bv->buffer()->redostack());
271 }
272
273
274 bool textRedo(BufferView * bv)
275 {
276         return textUndoOrRedo(bv, bv->buffer()->redostack(),
277                               bv->buffer()->undostack());
278 }
279
280
281 void recordUndo(BufferView * bv, Undo::undo_kind kind,
282         ParagraphList & plist, paroffset_type first, paroffset_type last)
283 {
284         if (!undo_frozen) {
285                 recordUndo(bv, kind, plist, first, last, bv->buffer()->undostack());
286                 bv->buffer()->redostack().clear();
287         }
288 }
289
290
291 void recordUndo(BufferView * bv, Undo::undo_kind kind,
292         ParagraphList & plist, paroffset_type par)
293 {
294         recordUndo(bv, kind, plist, par, par);
295 }
296
297
298 void recordUndo(BufferView * bv, Undo::undo_kind kind)
299 {
300         recordUndo(bv, kind, bv->text->ownerParagraphs(), bv->text->cursor.par());
301 }