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