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