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