]> git.lyx.org Git - lyx.git/blob - src/undo_funcs.C
copy some code over to allow work to start on prefs
[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         }
197 // we are not ready for this we cannot set the cursor for a paragraph
198 // which is not already in a row of LyXText!!!
199 #if 0
200         else { // otherwise this is the first one and we start here
201                 Inset * it = undopar->inInset();
202                 if (it)
203                         it->getLyXText(bv)->setCursorIntern(bv, undopar, 0);
204                 else
205                         bv->text->setCursorIntern(bv, undopar, 0);
206         }
207 #endif
208
209         Paragraph * endpar = 0;
210         // calculate the endpar for redoing the paragraphs.
211         if (behind)
212                 endpar = behind->next();
213
214         tmppar = bv->buffer()->getParFromID(undo.number_of_cursor_par);
215         UpdatableInset* it = 0;
216         if (undopar)
217                 it = static_cast<UpdatableInset*>(undopar->inInset());
218         if (it) {
219                 it->getLyXText(bv)->redoParagraphs(bv,
220                                                    it->getLyXText(bv)->cursor,
221                                                    endpar);
222                 if (tmppar) {
223                         it = static_cast<UpdatableInset*>(tmppar->inInset());
224                         LyXText * t;
225                         if (it) {
226                                 it->edit(bv);
227                                 t = it->getLyXText(bv);
228                         } else {
229                                 t = bv->text;
230                         }
231                         t->setCursorIntern(bv, tmppar, undo.cursor_pos);
232                         // clear any selection and set the selection cursor
233                         // for an evt. new selection.
234                         t->clearSelection();
235                         t->selection.cursor = t->cursor;
236                         t->updateCounters(bv);
237                         bv->fitCursor();
238                 }
239                 bv->updateInset(it, false);
240                 bv->text->setCursorIntern(bv, bv->text->cursor.par(),
241                                           bv->text->cursor.pos());
242         } else {
243                 bv->text->redoParagraphs(bv, bv->text->cursor, endpar);
244                 if (tmppar) {
245                         LyXText * t;
246                         Inset * it = tmppar->inInset();
247                         if (it) {
248                                 it->edit(bv);
249                                 t = it->getLyXText(bv);
250                         } else {
251                                 t = bv->text;
252                         }
253                         t->setCursorIntern(bv, tmppar, undo.cursor_pos);
254                         // clear any selection and set the selection cursor
255                         // for an evt. new selection.
256                         t->clearSelection();
257                         t->selection.cursor = t->cursor;
258                         t->updateCounters(bv);
259                 }
260         }
261
262         // And here it's safe enough to delete all removed paragraphs
263         vector<Paragraph *>::iterator pit = deletelist.begin();
264         if (pit != deletelist.end()) {
265                         for(;pit != deletelist.end(); ++pit) {
266                                 (*pit)->previous(0);
267                                 (*pit)->next(0);
268                                 delete (*pit);
269                         }
270                 }
271
272         finishUndo();
273         bv->text->status(bv, LyXText::NEED_MORE_REFRESH);
274         return true;
275 }
276
277
278 bool createUndo(BufferView * bv, Undo::undo_kind kind,
279         Paragraph const * first, Paragraph const * behind, shared_ptr<Undo> & u)
280 {
281         lyx::Assert(first);
282
283         int before_number = -1;
284         int behind_number = -1;
285         int inset_id = -1;
286
287         if (first->previous())
288                 before_number = first->previous()->id();
289         if (behind)
290                 behind_number = behind->id();
291         if (first->inInset())
292                 inset_id = first->inInset()->id();
293
294         Buffer * b = bv->buffer();
295
296         // Undo::EDIT  and Undo::FINISH are
297         // always finished. (no overlapping there)
298         // overlapping only with insert and delete inside one paragraph:
299         // Nobody wants all removed  character
300         // appear one by one when undoing.
301         // EDIT is special since only layout information, not the
302         // contents of a paragaph are stored.
303         if (!undo_finished && (kind != Undo::EDIT) && (kind != Undo::FINISH)) {
304                 // check whether storing is needed
305                 if (!b->undostack.empty() &&
306                     b->undostack.top()->kind == kind &&
307                     b->undostack.top()->number_of_before_par == before_number &&
308                     b->undostack.top()->number_of_behind_par == behind_number) {
309                         // no undo needed
310                         return false;
311                 }
312         }
313
314         // create a new Undo
315         Paragraph * undopar;
316
317         Paragraph * start = const_cast<Paragraph *>(first);
318         Paragraph * end = 0;
319
320         if (behind)
321                 end = const_cast<Paragraph*>(behind->previous());
322         else {
323                 end = start;
324                 while (end->next())
325                         end = end->next();
326         }
327         if (start && end && (start != end->next()) &&
328             ((before_number != behind_number) ||
329                  ((before_number < 0) && (behind_number < 0))))
330         {
331                 Paragraph * tmppar = start;
332                 Paragraph * tmppar2 = new Paragraph(*tmppar, true);
333
334                 // a memory optimization: Just store the layout information
335                 // when only edit
336                 if (kind == Undo::EDIT) {
337                         tmppar2->clearContents();
338                 }
339
340                 undopar = tmppar2;
341
342                 while (tmppar != end && tmppar->next()) {
343                         tmppar = tmppar->next();
344                         tmppar2->next(new Paragraph(*tmppar, true));
345                         // a memory optimization: Just store the layout
346                         // information when only edit
347                         if (kind == Undo::EDIT) {
348                                 tmppar2->clearContents();
349                         }
350                         tmppar2->next()->previous(tmppar2);
351
352                         tmppar2 = tmppar2->next();
353                 }
354                 tmppar2->next(0);
355         } else
356                 undopar = 0; // nothing to replace (undo of delete maybe)
357
358         int cursor_par = undoCursor(bv).par()->id();
359         int cursor_pos =  undoCursor(bv).pos();
360
361         u.reset(new Undo(kind, inset_id,
362                 before_number, behind_number,
363                 cursor_par, cursor_pos, undopar));
364
365         undo_finished = false;
366         return true;
367 }
368
369 } // namespace anon
370
371 void finishUndo()
372 {
373         // makes sure the next operation will be stored
374         undo_finished = true;
375 }
376
377
378 void freezeUndo()
379 {
380         // this is dangerous and for internal use only
381         undo_frozen = true;
382 }
383
384
385 void unFreezeUndo()
386 {
387         // this is dangerous and for internal use only
388         undo_frozen = false;
389 }
390
391
392 // returns false if no undo possible
393 bool textUndo(BufferView * bv)
394 {
395         Buffer * b = bv->buffer();
396
397         if (b->undostack.empty()) {
398                 finishNoUndo(bv);
399                 return false;
400         }
401
402         shared_ptr<Undo> undo = b->undostack.top();
403         b->undostack.pop();
404         finishUndo();
405
406         if (!undo_frozen) {
407                 Paragraph * first = b->getParFromID(undo->number_of_before_par);
408                 if (first && first->next())
409                         first = first->next();
410                 else if (!first)
411                         first = firstUndoParagraph(bv, undo->number_of_inset_id);
412                 if (first) {
413                         shared_ptr<Undo> u;
414                         if (createUndo(bv, undo->kind, first,
415                                 b->getParFromID(undo->number_of_behind_par), u))
416                                 b->redostack.push(u);
417                 }
418         }
419
420         // now we can unlock the inset for saftey because the inset pointer could
421         // be changed during the undo-function. Anyway if needed we have to lock
422         // the right inset/position if this is requested.
423         freezeUndo();
424         bv->unlockInset(bv->theLockingInset());
425         bool const ret = textHandleUndo(bv, *undo.get());
426         unFreezeUndo();
427         return ret;
428 }
429
430
431 // returns false if no redo possible
432 bool textRedo(BufferView * bv)
433 {
434         Buffer * b = bv->buffer();
435
436         if (b->redostack.empty()) {
437                 finishNoUndo(bv);
438                 return false;
439         }
440
441         shared_ptr<Undo> undo = b->redostack.top();
442         b->redostack.pop();
443         finishUndo();
444
445         if (!undo_frozen) {
446         Paragraph * first = bv->buffer()->getParFromID(undo->number_of_before_par);
447                 if (first && first->next())
448                         first = first->next();
449                 else if (!first)
450                         first = firstUndoParagraph(bv, undo->number_of_inset_id);
451                 if (first) {
452                         shared_ptr<Undo> u;
453                         if (createUndo(bv, undo->kind, first,
454                                 bv->buffer()->getParFromID(undo->number_of_behind_par), u))
455                                 bv->buffer()->undostack.push(u);
456                 }
457         }
458
459         // now we can unlock the inset for saftey because the inset pointer could
460         // be changed during the undo-function. Anyway if needed we have to lock
461         // the right inset/position if this is requested.
462         freezeUndo();
463         bv->unlockInset(bv->theLockingInset());
464         bool ret = textHandleUndo(bv, *undo.get());
465         unFreezeUndo();
466         return ret;
467 }
468
469
470 void setUndo(BufferView * bv, Undo::undo_kind kind,
471              Paragraph const * first, Paragraph const * behind)
472 {
473         if (!undo_frozen) {
474                 shared_ptr<Undo> u;
475                 if (createUndo(bv, kind, first, behind, u))
476                         bv->buffer()->undostack.push(u);
477                 bv->buffer()->redostack.clear();
478         }
479 }
480
481
482 void setRedo(BufferView * bv, Undo::undo_kind kind,
483              Paragraph const * first, Paragraph const * behind)
484 {
485         shared_ptr<Undo> u;
486         if (createUndo(bv, kind, first, behind, u))
487                 bv->buffer()->redostack.push(u);
488 }
489
490
491 void setCursorParUndo(BufferView * bv)
492 {
493         setUndo(bv, Undo::FINISH, bv->text->cursor.par(),
494                 bv->text->cursor.par()->next());
495 }