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