]> git.lyx.org Git - lyx.git/blob - src/undo_funcs.C
Fix deleting of paragraphs after undo (fix #236).
[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                         LyXFont font;
234                         it->update(bv, font, false);
235                         // we now would have to rebreak the whole paragraph the
236                         // undo-par was in. How we do it here is not really true.
237                         // We would have to save this information in the undo-struct
238                         // and then we could do the right rebreak. Here we only
239                         // handle the case where this was in the actual paragraph,
240                         // which not always is true.
241                         bv->text->redoParagraphs(bv, bv->text->cursor,
242                                                  bv->text->cursor.par());
243                         if (tmppar) {
244                                 it = static_cast<UpdatableInset*>(tmppar->inInset());
245                                 LyXText * t;
246                                 if (it) {
247                                         it->edit(bv);
248                                         t = it->getLyXText(bv);
249                                 } else {
250                                         t = bv->text;
251                                 }
252                                 t->setCursorIntern(bv, tmppar, undo->cursor_pos);
253                                 t->updateCounters(bv, t->cursor.row());
254                         }
255                         bv->text->setCursorIntern(bv, bv->text->cursor.par(),
256                                                   bv->text->cursor.pos());
257                 } else {
258                         bv->text->redoParagraphs(bv, bv->text->cursor, endpar);
259                         if (tmppar) {
260                                 LyXText * t;
261                                 Inset * it = tmppar->inInset();
262                                 if (it) {
263                                         it->edit(bv);
264                                         t = it->getLyXText(bv);
265                                 } else {
266                                         t = bv->text;
267                                 }
268                                 t->setCursorIntern(bv, tmppar, undo->cursor_pos);
269                                 t->updateCounters(bv, t->cursor.row());
270                         }
271                 }
272                 result = true;
273                 delete undo;
274 #ifdef DELETE_UNUSED_PARAGRAPHS
275                 // And here it's save enough to delete all removed paragraphs
276                 vector<Paragraph *>::iterator pit = vvpar.begin();
277                 if (pit != vvpar.end()) {
278 #if 0
279                         lyxerr << endl << "PARS BEFORE:";
280                         ParIterator end = bv->buffer()->par_iterator_end();
281                         ParIterator it = bv->buffer()->par_iterator_begin();
282                         for (; it != end; ++it)
283                                 lyxerr << (*it)->previous() << "<- " << (*it) << " ->" << (*it)->next() << endl;
284                         lyxerr << "DEL: ";
285 #endif
286                         for(;pit != vvpar.end(); ++pit) {
287 //                              lyxerr << *pit << " ";
288                                 (*pit)->previous(0);
289                                 (*pit)->next(0);
290                                 delete (*pit);
291                         }
292 #if 0
293                         lyxerr << endl << "PARS AFTER:";
294                         end = bv->buffer()->par_iterator_end();
295                         it = bv->buffer()->par_iterator_begin();
296                         for (; it != end; ++it)
297                                 lyxerr << (*it)->previous() << "<- " << (*it) << " ->" << (*it)->next() << endl;
298 #endif
299                 }
300 #endif
301         }
302         finishUndo();
303         bv->text->status(bv, LyXText::NEED_MORE_REFRESH);
304         return result;
305 }
306
307
308 void finishUndo()
309 {
310         // makes sure the next operation will be stored
311         undo_finished = true;
312 }
313
314
315 void freezeUndo()
316 {
317         // this is dangerous and for internal use only
318         undo_frozen = true;
319 }
320
321
322 void unFreezeUndo()
323 {
324         // this is dangerous and for internal use only
325         undo_frozen = false;
326 }
327
328
329 void setUndo(BufferView * bv, Undo::undo_kind kind,
330              Paragraph const * first, Paragraph const * behind)
331 {
332         if (!undo_frozen) {
333                 bv->buffer()->undostack.push(createUndo(bv, kind, first, behind));
334                 bv->buffer()->redostack.clear();
335         }
336 }
337
338
339 void setRedo(BufferView * bv, Undo::undo_kind kind,
340              Paragraph const * first, Paragraph const * behind)
341 {
342         bv->buffer()->redostack.push(createUndo(bv, kind, first, behind));
343 }
344
345
346 Undo * createUndo(BufferView * bv, Undo::undo_kind kind,
347                   Paragraph const * first, Paragraph const * behind)
348 {
349         lyx::Assert(first);
350
351         int before_number = -1;
352         int behind_number = -1;
353         int inset_id = -1;
354
355         if (first->previous())
356                 before_number = first->previous()->id();
357         if (behind)
358                 behind_number = behind->id();
359         if (first->inInset())
360                 inset_id = first->inInset()->id();
361
362         // Undo::EDIT  and Undo::FINISH are
363         // always finished. (no overlapping there)
364         // overlapping only with insert and delete inside one paragraph: 
365         // Nobody wants all removed  character
366         // appear one by one when undoing. 
367         // EDIT is special since only layout information, not the
368         // contents of a paragaph are stored.
369         if (!undo_finished && (kind != Undo::EDIT) && (kind != Undo::FINISH)) {
370                 // check wether storing is needed
371                 if (!bv->buffer()->undostack.empty() && 
372                     bv->buffer()->undostack.top()->kind == kind &&
373                     bv->buffer()->undostack.top()->number_of_before_par == before_number &&
374                     bv->buffer()->undostack.top()->number_of_behind_par == behind_number) {
375                         // no undo needed
376                         return 0;
377                 }
378         }
379         // create a new Undo
380         Paragraph * undopar;
381
382         Paragraph * start = const_cast<Paragraph *>(first);
383         Paragraph * end = 0;
384
385         if (behind)
386                 end = const_cast<Paragraph*>(behind->previous());
387         else {
388                 end = start;
389                 while (end->next())
390                         end = end->next();
391         }
392         if (start && end && (start != end->next()) &&
393             ((before_number != behind_number) ||
394                  ((before_number < 0) && (behind_number < 0))))
395         {
396                 Paragraph * tmppar = start;
397                 Paragraph * tmppar2 = new Paragraph(*tmppar, true);
398                 
399                 // a memory optimization: Just store the layout information
400                 // when only edit
401                 if (kind == Undo::EDIT) {
402                         tmppar2->clearContents();
403                 }
404
405                 undopar = tmppar2;
406   
407                 while (tmppar != end && tmppar->next()) {
408                         tmppar = tmppar->next();
409 #if 0
410                         tmppar2->next(new Paragraph(*tmppar, true));
411 #else
412                         Paragraph * ptmp = new Paragraph(*tmppar, true);
413                         tmppar2->next(ptmp);
414 #endif
415                         // a memory optimization: Just store the layout
416                         // information when only edit
417                         if (kind == Undo::EDIT) {
418                                 tmppar2->clearContents();
419                         }
420                         tmppar2->next()->previous(tmppar2);
421                         
422                         tmppar2 = tmppar2->next();
423                 }
424                 tmppar2->next(0);
425         } else
426                 undopar = 0; // nothing to replace (undo of delete maybe)
427
428         int cursor_par = undoCursor(bv).par()->id();
429         int cursor_pos =  undoCursor(bv).pos();
430         
431         Undo * undo = new Undo(kind, inset_id,
432                                before_number, behind_number,  
433                                cursor_par, cursor_pos, undopar);
434   
435         undo_finished = false;
436         return undo;
437 }
438
439
440 void setCursorParUndo(BufferView * bv)
441 {
442         setUndo(bv, Undo::FINISH, bv->text->cursor.par(),
443                 bv->text->cursor.par()->next());
444 }
445
446
447 Paragraph * firstUndoParagraph(BufferView * bv, int inset_id)
448 {
449         Inset * inset = bv->buffer()->getInsetFromID(inset_id);
450         if (inset) {
451                 Paragraph * result = inset->getFirstParagraph(0);
452                 if (result)
453                         return result;
454         }
455         return bv->text->ownerParagraph();
456 }
457
458
459 LyXCursor const & undoCursor(BufferView * bv)
460 {
461         if (bv->theLockingInset())
462                 return bv->theLockingInset()->cursor(bv);
463         return bv->text->cursor;
464 }