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