]> git.lyx.org Git - lyx.git/blob - src/undo_funcs.C
*** empty log message ***
[lyx.git] / src / undo_funcs.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995-2001 The LyX Team.
7  *
8  * ====================================================== */
9
10 #include <config.h>
11
12 #include "undo_funcs.h"
13 #include "lyxtext.h"
14 #include "BufferView.h"
15 #include "buffer.h"
16 #include "insets/updatableinset.h"
17 #include "insets/insettext.h"
18 #include "debug.h"
19 #include "support/LAssert.h"
20
21 #include "iterators.h"
22
23 #include <vector>
24
25 using std::vector;
26 using boost::shared_ptr;
27
28
29 /// The flag used by FinishUndo().
30 bool undo_finished;
31 /// Whether actions are not added to the undo stacks.
32 bool undo_frozen;
33
34 namespace {
35
36 /// Utility to return the cursor.
37 LyXCursor const & undoCursor(BufferView * bv)
38 {
39         if (bv->theLockingInset())
40                 return bv->theLockingInset()->cursor(bv);
41         return bv->text->cursor;
42 }
43
44
45 /**
46  * Returns a pointer to the very first Paragraph depending of where
47  * we are so it will return the first paragraph of the buffer or the
48  * first paragraph of the textinset we're in.
49  */
50 Paragraph * firstUndoParagraph(BufferView * bv, int inset_id)
51 {
52         Inset * inset = bv->buffer()->getInsetFromID(inset_id);
53         if (inset) {
54                 Paragraph * result = inset->getFirstParagraph(0);
55                 if (result)
56                         return result;
57         }
58         return &*bv->text->ownerParagraphs().begin();
59 }
60
61
62 /**
63  * Finish the undo operation in the case there was no entry
64  * on the stack to perform.
65  */
66 void finishNoUndo(BufferView * bv)
67 {
68         freezeUndo();
69         bv->unlockInset(bv->theLockingInset());
70         finishUndo();
71         bv->text->postPaint(0);
72         unFreezeUndo();
73 }
74
75
76 // Returns false if no undo possible.
77 bool textHandleUndo(BufferView * bv, Undo & undo)
78 {
79         Buffer * b = bv->buffer();
80
81         Paragraph * const before = &*b->getParFromID(undo.number_of_before_par);
82         Paragraph * const behind = &*b->getParFromID(undo.number_of_behind_par);
83
84         // If there's no before take the beginning
85         // of the document for redoing.
86         if (!before) {
87                 LyXText * t = bv->text;
88                 int num = undo.number_of_inset_id;
89                 if (undo.number_of_inset_id >= 0) {
90                         Inset * in = bv->buffer()->getInsetFromID(num);
91                         if (in) {
92                                 t = in->getLyXText(bv);
93                         } else {
94                                 num = -1;
95                         }
96                 }
97                 t->setCursorIntern(firstUndoParagraph(bv, num), 0);
98         }
99
100         // Replace the paragraphs with the undo informations.
101
102         Paragraph * undopar = undo.pars.empty() ? 0 : undo.pars[0];
103         // Otherwise the undo destructor would
104         // delete the paragraph.
105         undo.pars.resize(0);
106
107         // Get last undo par and set the right(new) inset-owner of the
108         // paragraph if there is any. This is not needed if we don't
109         // have a paragraph before because then in is automatically
110         // done in the function which assigns the first paragraph to
111         // an InsetText. (Jug)
112         Paragraph * lastundopar = undopar;
113         if (lastundopar) {
114                 Inset * in = 0;
115                 if (before)
116                         in = before->inInset();
117                 else if (undo.number_of_inset_id >= 0)
118                         in = bv->buffer()->getInsetFromID(undo.number_of_inset_id);
119                 lastundopar->setInsetOwner(in);
120                 while (lastundopar->next()) {
121                         lastundopar = lastundopar->next();
122                         lastundopar->setInsetOwner(in);
123                 }
124         }
125
126         vector<Paragraph *> deletelist;
127
128         // Now add old paragraphs to be deleted.
129         if (before != behind || (!behind && !before)) {
130                 Paragraph * deletepar;
131                 if (before)
132                         deletepar = before->next();
133                 else
134                         deletepar = firstUndoParagraph(bv, undo.number_of_inset_id);
135                 Paragraph * tmppar2 = undopar;
136                 while (deletepar && deletepar != behind) {
137                         deletelist.push_back(deletepar);
138                         Paragraph * tmppar = deletepar;
139                         deletepar = deletepar->next();
140
141                         // A memory optimization for edit:
142                         // Only layout information
143                         // is stored in the undo. So restore
144                         // the text informations.
145                         if (undo.kind == Undo::EDIT) {
146                                 tmppar2->setContentsFromPar(*tmppar);
147                                 tmppar2 = tmppar2->next();
148                         }
149                 }
150         }
151
152         // The order here is VERY IMPORTANT. We have to set the right
153         // next/prev pointer in the paragraphs so that a rebuild of
154         // the LyXText works!!!
155
156         // Thread the end of the undo onto the par in front if any.
157         if (lastundopar) {
158                 lastundopar->next(behind);
159                 if (behind)
160                         behind->previous(lastundopar);
161         }
162
163         // Put the new stuff in the list if there is one.
164         if (undopar) {
165                 undopar->previous(before);
166                 if (before)
167                         before->next(undopar);
168                 else {
169                         int id = firstUndoParagraph(bv, undo.number_of_inset_id)->id();
170                         Paragraph * op = &*bv->buffer()->getParFromID(id);
171                         if (op && op->inInset()) {
172                                 static_cast<InsetText*>(op->inInset())->paragraph(undopar);
173                         } else {
174                                 bv->buffer()->paragraphs.set(undopar);
175                         }
176                 }
177         } else {
178                 // We enter here on DELETE undo operations where we
179                 // have to substitue the second paragraph with the
180                 // first if the removed one is the first.
181                 if (!before && behind) {
182                         int id = firstUndoParagraph(bv, undo.number_of_inset_id)->id();
183                         Paragraph * op = &*bv->buffer()->getParFromID(id);
184                         if (op && op->inInset()) {
185                                 static_cast<InsetText*>(op->inInset())->paragraph(behind);
186                         } else {
187                                 bv->buffer()->paragraphs.set(behind);
188                         }
189
190                         undopar = behind;
191                 }
192         }
193
194
195         // Set the cursor for redoing.
196         // If we have a par before the undopar.
197         if (before) {
198                 Inset * it = before->inInset();
199                 if (it)
200                         it->getLyXText(bv)->setCursorIntern(before, 0);
201                 else
202                         bv->text->setCursorIntern(before, 0);
203         }
204
205 // we are not ready for this we cannot set the cursor for a paragraph
206 // which is not already in a row of LyXText!!!
207 #if 0
208         else { // otherwise this is the first one and we start here
209                 Inset * it = undopar->inInset();
210                 if (it)
211                         it->getLyXText(bv)->setCursorIntern(bv, undopar, 0);
212                 else
213                         bv->text->setCursorIntern(bv, undopar, 0);
214         }
215 #endif
216
217         Paragraph * endpar = 0;
218
219         // Calculate the endpar for redoing the paragraphs.
220         if (behind)
221                 endpar = behind->next();
222
223         UpdatableInset * it = 0;
224         if (undopar)
225                 it = static_cast<UpdatableInset*>(undopar->inInset());
226         if (it) {
227                 it->getLyXText(bv)->redoParagraphs(
228                                                    it->getLyXText(bv)->cursor,
229                                                    endpar);
230                 Paragraph * tmppar =
231                         &*bv->buffer()->getParFromID(undo.number_of_cursor_par);
232                 if (tmppar) {
233                         it = static_cast<UpdatableInset*>(tmppar->inInset());
234                         LyXText * t;
235                         if (it) {
236                                 it->edit(bv);
237                                 t = it->getLyXText(bv);
238                         } else {
239                                 t = bv->text;
240                         }
241                         t->setCursorIntern(tmppar, undo.cursor_pos);
242                         // Clear any selection and set the selection
243                         // cursor for an evt. new selection.
244                         t->clearSelection();
245                         t->selection.cursor = t->cursor;
246                         t->updateCounters();
247                         bv->fitCursor();
248                 }
249                 bv->updateInset(it);
250                 bv->text->setCursorIntern(bv->text->cursor.par(),
251                                           bv->text->cursor.pos());
252         } else {
253                 bv->text->redoParagraphs(bv->text->cursor, endpar);
254                 Paragraph * tmppar =
255                         &*bv->buffer()->getParFromID(undo.number_of_cursor_par);
256                 if (tmppar) {
257                         LyXText * t;
258                         Inset * it = tmppar->inInset();
259                         if (it) {
260                                 it->edit(bv);
261                                 t = it->getLyXText(bv);
262                         } else {
263                                 t = bv->text;
264                         }
265                         t->setCursorIntern(tmppar, undo.cursor_pos);
266                         // Clear any selection and set the selection
267                         // cursor for an evt. new selection.
268                         t->clearSelection();
269                         t->selection.cursor = t->cursor;
270                         t->updateCounters();
271                 }
272         }
273
274         // And here it's safe enough to delete all removed paragraphs.
275         vector<Paragraph *>::iterator pit = deletelist.begin();
276         for(; pit != deletelist.end(); ++pit) {
277                 (*pit)->previous(0);
278                 (*pit)->next(0);
279                 delete (*pit);
280         }
281
282         finishUndo();
283         bv->text->postPaint(0);
284         return true;
285 }
286
287
288 bool createUndo(BufferView * bv, Undo::undo_kind kind,
289         ParagraphList::iterator itfirst, ParagraphList::iterator itbehind,
290         shared_ptr<Undo> & u)
291 {
292         Paragraph * const first = &*itfirst;
293         Paragraph * const behind = &*itbehind;
294         lyx::Assert(first);
295
296         int before_number = -1;
297         int behind_number = -1;
298         int inset_id = -1;
299
300         if (first->previous())
301                 before_number = first->previous()->id();
302         if (behind)
303                 behind_number = behind->id();
304         if (first->inInset())
305                 inset_id = first->inInset()->id();
306
307         Buffer * b = bv->buffer();
308
309         // Undo::EDIT  and Undo::FINISH are
310         // always finished. (no overlapping there)
311         // overlapping only with insert and delete inside one paragraph:
312         // Nobody wants all removed  character
313         // appear one by one when undoing.
314         // EDIT is special since only layout information, not the
315         // contents of a paragaph are stored.
316         if (!undo_finished && (kind != Undo::EDIT) && (kind != Undo::FINISH)) {
317                 // Check whether storing is needed.
318                 if (!b->undostack.empty() &&
319                     b->undostack.top()->kind == kind &&
320                     b->undostack.top()->number_of_before_par == before_number &&
321                     b->undostack.top()->number_of_behind_par == behind_number) {
322                         // No undo needed.
323                         return false;
324                 }
325         }
326
327         // Create a new Undo.
328         std::vector<Paragraph *> undo_pars;
329
330         Paragraph const * end = 0;
331
332         if (behind)
333                 end = behind->previous();
334         else {
335                 end = first;
336                 while (end->next())
337                         end = end->next();
338         }
339
340         if (first && end && (first != end->next()) &&
341             ((before_number != behind_number) ||
342                  ((before_number < 0) && (behind_number < 0))))
343         {
344                 undo_pars.push_back(new Paragraph(*first, true));
345                 for (Paragraph * tmppar = first; tmppar != end && tmppar->next(); ) {
346                         tmppar = tmppar->next();
347                         undo_pars.push_back(new Paragraph(*tmppar, true));
348                         size_t const n = undo_pars.size();
349                         undo_pars[n - 2]->next(undo_pars[n - 1]);
350                         undo_pars[n - 1]->previous(undo_pars[n - 2]);
351                 }
352                 undo_pars.back()->next(0);
353         }
354
355         // A memory optimization: Just store the layout
356         // information when only edit.
357         if (kind == Undo::EDIT) {
358                 for (size_t i = 0, n = undo_pars.size(); i < n; ++i)
359                         undo_pars[i]->clearContents();
360         }               
361
362         int cursor_par = undoCursor(bv).par()->id();
363         int cursor_pos = undoCursor(bv).pos();
364
365         //lyxerr << "createUndo: inset_id: " << inset_id << "  before_number: " 
366         //      << before_number << "  behind_number: " << behind_number << "\n";
367         u.reset(new Undo(kind, inset_id,
368                 before_number, behind_number,
369                 cursor_par, cursor_pos, undo_pars));
370
371         undo_finished = false;
372         return true;
373 }
374
375
376 // Returns false if no undo possible.
377 bool textUndoOrRedo(BufferView * bv,
378         limited_stack<boost::shared_ptr<Undo> > & stack,
379         limited_stack<boost::shared_ptr<Undo> > & otherstack)
380 {
381         Buffer * b = bv->buffer();
382
383         if (stack.empty()) {
384                 finishNoUndo(bv);
385                 return false;
386         }
387
388         shared_ptr<Undo> undo = stack.top();
389         stack.pop();
390         finishUndo();
391
392         if (!undo_frozen) {
393                 Paragraph * first = &*b->getParFromID(undo->number_of_before_par);
394                 if (first && first->next())
395                         first = first->next();
396                 else if (!first)
397                         first = firstUndoParagraph(bv, undo->number_of_inset_id);
398                 if (first) {
399                         shared_ptr<Undo> u;
400                         if (createUndo(bv, undo->kind, first,
401                                              b->getParFromID(undo->number_of_behind_par), u))
402                                 otherstack.push(u);
403                 }
404         }
405
406         // Now we can unlock the inset for saftey because the inset
407         // pointer could be changed during the undo-function. Anyway
408         // if needed we have to lock the right inset/position if this
409         // is requested.
410         freezeUndo();
411         bv->unlockInset(bv->theLockingInset());
412         bool const ret = textHandleUndo(bv, *undo.get());
413         unFreezeUndo();
414         return ret;
415 }
416
417 } // namespace anon
418
419
420 void finishUndo()
421 {
422         // Makes sure the next operation will be stored.
423         undo_finished = true;
424 }
425
426
427 void freezeUndo()
428 {
429         // This is dangerous and for internal use only.
430         undo_frozen = true;
431 }
432
433
434 void unFreezeUndo()
435 {
436         // This is dangerous and for internal use only.
437         undo_frozen = false;
438 }
439
440
441 bool textUndo(BufferView * bv)
442 {
443         return textUndoOrRedo(bv, bv->buffer()->undostack,
444                               bv->buffer()->redostack);
445 }
446
447
448 bool textRedo(BufferView * bv)
449 {
450         return textUndoOrRedo(bv, bv->buffer()->redostack,
451                               bv->buffer()->undostack);
452 }
453
454
455 void setUndo(BufferView * bv, Undo::undo_kind kind,
456              ParagraphList::iterator first, ParagraphList::iterator behind)
457 {
458         if (!undo_frozen) {
459                 shared_ptr<Undo> u;
460                 if (createUndo(bv, kind, first, behind, u))
461                         bv->buffer()->undostack.push(u);
462                 bv->buffer()->redostack.clear();
463         }
464 }
465
466
467 void setRedo(BufferView * bv, Undo::undo_kind kind,
468              ParagraphList::iterator first, ParagraphList::iterator behind)
469 {
470         shared_ptr<Undo> u;
471         if (createUndo(bv, kind, first, behind, u))
472                 bv->buffer()->redostack.push(u);
473 }
474
475
476 void setCursorParUndo(BufferView * bv)
477 {
478         setUndo(bv, Undo::FINISH, bv->text->cursor.par(),
479                 boost::next(bv->text->cursor.par()));
480 }