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