]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Embedding: saving inzip name to .lyx file so that embedded files can always be found...
[lyx.git] / src / Text2.cpp
1 /**
2  * \file text2.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Stefan Schimanski
15  * \author Dekel Tsur
16  * \author Jürgen Vigna
17  *
18  * Full author contact details are available in file CREDITS.
19  */
20
21 #include <config.h>
22
23 #include "Text.h"
24
25 #include "Buffer.h"
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "Bullet.h"
31 #include "Changes.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "FuncRequest.h"
37 #include "Language.h"
38 #include "Layout.h"
39 #include "Lexer.h"
40 #include "LyXFunc.h"
41 #include "LyXRC.h"
42 #include "Paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "ParIterator.h"
46 #include "TextClass.h"
47 #include "TextMetrics.h"
48 #include "VSpace.h"
49
50 #include "frontends/FontMetrics.h"
51
52 #include "insets/InsetEnvironment.h"
53
54 #include "mathed/InsetMathHull.h"
55
56 #include "support/debug.h"
57 #include "support/gettext.h"
58 #include "support/textutils.h"
59
60 #include <boost/next_prior.hpp>
61
62 #include <sstream>
63
64 using namespace std;
65
66 namespace lyx {
67
68 Text::Text()
69         : autoBreakRows_(false)
70 {}
71
72
73 bool Text::isMainText(Buffer const & buffer) const
74 {
75         return &buffer.text() == this;
76 }
77
78
79 FontInfo Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
80 {
81         LayoutPtr const & layout = pars_[pit].layout();
82
83         if (!pars_[pit].getDepth())  {
84                 FontInfo lf = layout->resfont;
85                 // In case the default family has been customized
86                 if (layout->font.family() == INHERIT_FAMILY)
87                         lf.setFamily(buffer.params().getFont().fontInfo().family());
88                 return lf;
89         }
90
91         FontInfo font = layout->font;
92         // Realize with the fonts of lesser depth.
93         //font.realize(outerFont(pit, paragraphs()));
94         font.realize(buffer.params().getFont().fontInfo());
95
96         return font;
97 }
98
99
100 FontInfo Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
101 {
102         LayoutPtr const & layout = par.layout();
103
104         if (!par.getDepth()) {
105                 FontInfo lf = layout->reslabelfont;
106                 // In case the default family has been customized
107                 if (layout->labelfont.family() == INHERIT_FAMILY)
108                         lf.setFamily(buffer.params().getFont().fontInfo().family());
109                 return lf;
110         }
111
112         FontInfo font = layout->labelfont;
113         // Realize with the fonts of lesser depth.
114         font.realize(buffer.params().getFont().fontInfo());
115
116         return font;
117 }
118
119
120 void Text::setCharFont(Buffer const & buffer, pit_type pit,
121                 pos_type pos, Font const & fnt, Font const & display_font)
122 {
123         Font font = fnt;
124         LayoutPtr const & layout = pars_[pit].layout();
125
126         // Get concrete layout font to reduce against
127         FontInfo layoutfont;
128
129         if (pos < pars_[pit].beginOfBody())
130                 layoutfont = layout->labelfont;
131         else
132                 layoutfont = layout->font;
133
134         // Realize against environment font information
135         if (pars_[pit].getDepth()) {
136                 pit_type tp = pit;
137                 while (!layoutfont.resolved() &&
138                        tp != pit_type(paragraphs().size()) &&
139                        pars_[tp].getDepth()) {
140                         tp = outerHook(tp, paragraphs());
141                         if (tp != pit_type(paragraphs().size()))
142                                 layoutfont.realize(pars_[tp].layout()->font);
143                 }
144         }
145
146         // Inside inset, apply the inset's font attributes if any
147         // (charstyle!)
148         if (!isMainText(buffer))
149                 layoutfont.realize(display_font.fontInfo());
150
151         layoutfont.realize(buffer.params().getFont().fontInfo());
152
153         // Now, reduce font against full layout font
154         font.fontInfo().reduce(layoutfont);
155
156         pars_[pit].setFont(pos, font);
157 }
158
159
160 void Text::setInsetFont(BufferView const & bv, pit_type pit,
161                 pos_type pos, Font const & font, bool toggleall)
162 {
163         BOOST_ASSERT(pars_[pit].isInset(pos) &&
164                      pars_[pit].getInset(pos)->noFontChange());
165
166         Inset * const inset = pars_[pit].getInset(pos);
167         CursorSlice::idx_type endidx = inset->nargs();
168         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
169                 Text * text = cs.text();
170                 if (text) {
171                         // last position of the cell
172                         CursorSlice cellend = cs;
173                         cellend.pit() = cellend.lastpit();
174                         cellend.pos() = cellend.lastpos();
175                         text->setFont(bv, cs, cellend, font, toggleall);
176                 }
177         }
178 }
179
180
181 // return past-the-last paragraph influenced by a layout change on pit
182 pit_type Text::undoSpan(pit_type pit)
183 {
184         pit_type end = paragraphs().size();
185         pit_type nextpit = pit + 1;
186         if (nextpit == end)
187                 return nextpit;
188         //because of parindents
189         if (!pars_[pit].getDepth())
190                 return boost::next(nextpit);
191         //because of depth constrains
192         for (; nextpit != end; ++pit, ++nextpit) {
193                 if (!pars_[pit].getDepth())
194                         break;
195         }
196         return nextpit;
197 }
198
199
200 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
201                 docstring const & layout)
202 {
203         BOOST_ASSERT(start != end);
204
205         BufferParams const & bufparams = buffer.params();
206         LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
207
208         for (pit_type pit = start; pit != end; ++pit) {
209                 Paragraph & par = pars_[pit];
210                 par.applyLayout(lyxlayout);
211                 if (lyxlayout->margintype == MARGIN_MANUAL)
212                         par.setLabelWidthString(par.translateIfPossible(
213                                 lyxlayout->labelstring(), buffer.params()));
214         }
215 }
216
217
218 // set layout over selection and make a total rebreak of those paragraphs
219 void Text::setLayout(Cursor & cur, docstring const & layout)
220 {
221         BOOST_ASSERT(this == cur.text());
222         // special handling of new environment insets
223         BufferView & bv = cur.bv();
224         BufferParams const & params = bv.buffer().params();
225         LayoutPtr const & lyxlayout = params.getTextClass()[layout];
226         if (lyxlayout->is_environment) {
227                 // move everything in a new environment inset
228                 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
229                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
230                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
231                 lyx::dispatch(FuncRequest(LFUN_CUT));
232                 Inset * inset = new InsetEnvironment(params, layout);
233                 insertInset(cur, inset);
234                 //inset->edit(cur, true);
235                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
236                 return;
237         }
238
239         pit_type start = cur.selBegin().pit();
240         pit_type end = cur.selEnd().pit() + 1;
241         pit_type undopit = undoSpan(end - 1);
242         recUndo(cur, start, undopit - 1);
243         setLayout(cur.buffer(), start, end, layout);
244         updateLabels(cur.buffer());
245 }
246
247
248 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
249                         Paragraph const & par, int max_depth)
250 {
251         if (par.layout()->labeltype == LABEL_BIBLIO)
252                 return false;
253         int const depth = par.params().depth();
254         if (type == Text::INC_DEPTH && depth < max_depth)
255                 return true;
256         if (type == Text::DEC_DEPTH && depth > 0)
257                 return true;
258         return false;
259 }
260
261
262 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
263 {
264         BOOST_ASSERT(this == cur.text());
265         // this happens when selecting several cells in tabular (bug 2630)
266         if (cur.selBegin().idx() != cur.selEnd().idx())
267                 return false;
268
269         pit_type const beg = cur.selBegin().pit();
270         pit_type const end = cur.selEnd().pit() + 1;
271         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
272
273         for (pit_type pit = beg; pit != end; ++pit) {
274                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
275                         return true;
276                 max_depth = pars_[pit].getMaxDepthAfter();
277         }
278         return false;
279 }
280
281
282 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
283 {
284         BOOST_ASSERT(this == cur.text());
285         pit_type const beg = cur.selBegin().pit();
286         pit_type const end = cur.selEnd().pit() + 1;
287         cur.recordUndoSelection();
288         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
289
290         for (pit_type pit = beg; pit != end; ++pit) {
291                 Paragraph & par = pars_[pit];
292                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
293                         int const depth = par.params().depth();
294                         if (type == INC_DEPTH)
295                                 par.params().depth(depth + 1);
296                         else
297                                 par.params().depth(depth - 1);
298                 }
299                 max_depth = par.getMaxDepthAfter();
300         }
301         // this handles the counter labels, and also fixes up
302         // depth values for follow-on (child) paragraphs
303         updateLabels(cur.buffer());
304 }
305
306
307 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
308 {
309         BOOST_ASSERT(this == cur.text());
310         // Set the current_font
311         // Determine basis font
312         FontInfo layoutfont;
313         pit_type pit = cur.pit();
314         if (cur.pos() < pars_[pit].beginOfBody())
315                 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
316         else
317                 layoutfont = getLayoutFont(cur.buffer(), pit);
318
319         // Update current font
320         cur.real_current_font.update(font,
321                                         cur.buffer().params().language,
322                                         toggleall);
323
324         // Reduce to implicit settings
325         cur.current_font = cur.real_current_font;
326         cur.current_font.fontInfo().reduce(layoutfont);
327         // And resolve it completely
328         cur.real_current_font.fontInfo().realize(layoutfont);
329
330         // if there is no selection that's all we need to do
331         if (!cur.selection())
332                 return;
333
334         // Ok, we have a selection.
335         cur.recordUndoSelection();
336
337         setFont(cur.bv(), cur.selectionBegin().top(), 
338                 cur.selectionEnd().top(), font, toggleall);
339 }
340
341
342 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
343                 CursorSlice const & end, Font const & font,
344                 bool toggleall)
345 {
346         Buffer const & buffer = bv.buffer();
347
348         // Don't use forwardChar here as ditend might have
349         // pos() == lastpos() and forwardChar would miss it.
350         // Can't use forwardPos either as this descends into
351         // nested insets.
352         Language const * language = buffer.params().language;
353         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
354                 if (dit.pos() != dit.lastpos()) {
355                         pit_type const pit = dit.pit();
356                         pos_type const pos = dit.pos();
357                         if (pars_[pit].isInset(pos) &&
358                             pars_[pit].getInset(pos)->noFontChange())
359                                 // We need to propagate the font change to all
360                                 // text cells of the inset (bug 1973).
361                                 // FIXME: This should change, see documentation
362                                 // of noFontChange in Inset.h
363                                 setInsetFont(bv, pit, pos, font, toggleall);
364                         TextMetrics const & tm = bv.textMetrics(this);
365                         Font f = tm.getDisplayFont(pit, pos);
366                         f.update(font, language, toggleall);
367                         setCharFont(buffer, pit, pos, f, tm.font_);
368                 }
369         }
370 }
371
372
373 bool Text::cursorTop(Cursor & cur)
374 {
375         BOOST_ASSERT(this == cur.text());
376         return setCursor(cur, 0, 0);
377 }
378
379
380 bool Text::cursorBottom(Cursor & cur)
381 {
382         BOOST_ASSERT(this == cur.text());
383         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
384 }
385
386
387 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
388 {
389         BOOST_ASSERT(this == cur.text());
390         // If the mask is completely neutral, tell user
391         if (font.fontInfo() == ignore_font && 
392                 (font.language() == 0 || font.language() == ignore_language)) {
393                 // Could only happen with user style
394                 cur.message(_("No font change defined."));
395                 return;
396         }
397
398         // Try implicit word selection
399         // If there is a change in the language the implicit word selection
400         // is disabled.
401         CursorSlice resetCursor = cur.top();
402         bool implicitSelection =
403                 font.language() == ignore_language
404                 && font.fontInfo().number() == FONT_IGNORE
405                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
406
407         // Set font
408         setFont(cur, font, toggleall);
409
410         // Implicit selections are cleared afterwards
411         // and cursor is set to the original position.
412         if (implicitSelection) {
413                 cur.clearSelection();
414                 cur.top() = resetCursor;
415                 cur.resetAnchor();
416         }
417 }
418
419
420 docstring Text::getStringToIndex(Cursor const & cur)
421 {
422         BOOST_ASSERT(this == cur.text());
423
424         docstring idxstring;
425         if (cur.selection())
426                 idxstring = cur.selectionAsString(false);
427         else {
428                 // Try implicit word selection. If there is a change
429                 // in the language the implicit word selection is
430                 // disabled.
431                 Cursor tmpcur = cur;
432                 selectWord(tmpcur, PREVIOUS_WORD);
433
434                 if (!tmpcur.selection())
435                         cur.message(_("Nothing to index!"));
436                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
437                         cur.message(_("Cannot index more than one paragraph!"));
438                 else
439                         idxstring = tmpcur.selectionAsString(false);
440         }
441
442         return idxstring;
443 }
444
445
446 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
447 {
448         BOOST_ASSERT(cur.text());
449         // make sure that the depth behind the selection are restored, too
450         pit_type undopit = undoSpan(cur.selEnd().pit());
451         recUndo(cur, cur.selBegin().pit(), undopit - 1);
452
453         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
454              pit <= end; ++pit) {
455                 Paragraph & par = pars_[pit];
456                 ParagraphParameters params = par.params();
457                 params.read(to_utf8(arg), merge);
458                 Layout const & layout = *(par.layout());
459                 par.params().apply(params, layout);
460         }
461 }
462
463
464 //FIXME This is a little redundant now, but it's probably worth keeping,
465 //especially if we're going to go away from using serialization internally
466 //quite so much.
467 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
468 {
469         BOOST_ASSERT(cur.text());
470         // make sure that the depth behind the selection are restored, too
471         pit_type undopit = undoSpan(cur.selEnd().pit());
472         recUndo(cur, cur.selBegin().pit(), undopit - 1);
473
474         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
475              pit <= end; ++pit) {
476                 Paragraph & par = pars_[pit];
477                 Layout const & layout = *(par.layout());
478                 par.params().apply(p, layout);
479         }       
480 }
481
482
483 // this really should just insert the inset and not move the cursor.
484 void Text::insertInset(Cursor & cur, Inset * inset)
485 {
486         BOOST_ASSERT(this == cur.text());
487         BOOST_ASSERT(inset);
488         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
489                                     Change(cur.buffer().params().trackChanges ?
490                                            Change::INSERTED : Change::UNCHANGED));
491 }
492
493
494 // needed to insert the selection
495 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
496 {
497         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
498                                          cur.current_font, str, autoBreakRows_);
499 }
500
501
502 // turn double CR to single CR, others are converted into one
503 // blank. Then insertStringAsLines is called
504 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
505 {
506         docstring linestr = str;
507         bool newline_inserted = false;
508
509         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
510                 if (linestr[i] == '\n') {
511                         if (newline_inserted) {
512                                 // we know that \r will be ignored by
513                                 // insertStringAsLines. Of course, it is a dirty
514                                 // trick, but it works...
515                                 linestr[i - 1] = '\r';
516                                 linestr[i] = '\n';
517                         } else {
518                                 linestr[i] = ' ';
519                                 newline_inserted = true;
520                         }
521                 } else if (isPrintable(linestr[i])) {
522                         newline_inserted = false;
523                 }
524         }
525         insertStringAsLines(cur, linestr);
526 }
527
528
529 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
530                         bool setfont, bool boundary)
531 {
532         Cursor old = cur;
533         setCursorIntern(cur, par, pos, setfont, boundary);
534         return cur.bv().checkDepm(cur, old);
535 }
536
537
538 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
539 {
540         BOOST_ASSERT(par != int(paragraphs().size()));
541         cur.pit() = par;
542         cur.pos() = pos;
543
544         // now some strict checking
545         Paragraph & para = getPar(par);
546
547         // None of these should happen, but we're scaredy-cats
548         if (pos < 0) {
549                 lyxerr << "dont like -1" << endl;
550                 BOOST_ASSERT(false);
551         }
552
553         if (pos > para.size()) {
554                 lyxerr << "dont like 1, pos: " << pos
555                        << " size: " << para.size()
556                        << " par: " << par << endl;
557                 BOOST_ASSERT(false);
558         }
559 }
560
561
562 void Text::setCursorIntern(Cursor & cur,
563                               pit_type par, pos_type pos, bool setfont, bool boundary)
564 {
565         BOOST_ASSERT(this == cur.text());
566         cur.boundary(boundary);
567         setCursor(cur.top(), par, pos);
568         if (setfont)
569                 cur.setCurrentFont();
570 }
571
572
573 bool Text::checkAndActivateInset(Cursor & cur, bool front)
574 {
575         if (cur.selection())
576                 return false;
577         if (front && cur.pos() == cur.lastpos())
578                 return false;
579         if (!front && cur.pos() == 0)
580                 return false;
581         Inset * inset = front ? cur.nextInset() : cur.prevInset();
582         if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
583                 return false;
584         /*
585          * Apparently, when entering an inset we are expected to be positioned
586          * *before* it in the containing paragraph, regardless of the direction
587          * from which we are entering. Otherwise, cursor placement goes awry,
588          * and when we exit from the beginning, we'll be placed *after* the
589          * inset.
590          */
591         if (!front)
592                 --cur.pos();
593         inset->edit(cur, front);
594         return true;
595 }
596
597
598 bool Text::cursorBackward(Cursor & cur)
599 {
600         // Tell BufferView to test for FitCursor in any case!
601         cur.updateFlags(Update::FitCursor);
602
603         // not at paragraph start?
604         if (cur.pos() > 0) {
605                 // if on right side of boundary (i.e. not at paragraph end, but line end)
606                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
607                 // there are some exceptions to ignore this: lineseps, newlines, spaces
608 #if 0
609                 // some effectless debug code to see the values in the debugger
610                 bool bound = cur.boundary();
611                 int rowpos = cur.textRow().pos();
612                 int pos = cur.pos();
613                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
614                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
615                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
616 #endif
617                 if (!cur.boundary() &&
618                                 cur.textRow().pos() == cur.pos() &&
619                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
620                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
621                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
622                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
623                 }
624                 
625                 // go left and try to enter inset
626                 if (checkAndActivateInset(cur, false))
627                         return false;
628                 
629                 // normal character left
630                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
631         }
632
633         // move to the previous paragraph or do nothing
634         if (cur.pit() > 0)
635                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
636         return false;
637 }
638
639
640 bool Text::cursorForward(Cursor & cur)
641 {
642         // Tell BufferView to test for FitCursor in any case!
643         cur.updateFlags(Update::FitCursor);
644
645         // not at paragraph end?
646         if (cur.pos() != cur.lastpos()) {
647                 // in front of editable inset, i.e. jump into it?
648                 if (checkAndActivateInset(cur, true))
649                         return false;
650
651                 TextMetrics const & tm = cur.bv().textMetrics(this);
652                 // if left of boundary -> just jump to right side
653                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
654                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
655                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
656
657                 // next position is left of boundary, 
658                 // but go to next line for special cases like space, newline, linesep
659 #if 0
660                 // some effectless debug code to see the values in the debugger
661                 int endpos = cur.textRow().endpos();
662                 int lastpos = cur.lastpos();
663                 int pos = cur.pos();
664                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
665                 bool newline = cur.paragraph().isNewline(cur.pos());
666                 bool sep = cur.paragraph().isSeparator(cur.pos());
667                 if (cur.pos() != cur.lastpos()) {
668                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
669                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
670                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
671                 }
672 #endif
673                 if (cur.textRow().endpos() == cur.pos() + 1 &&
674                     cur.textRow().endpos() != cur.lastpos() &&
675                                 !cur.paragraph().isNewline(cur.pos()) &&
676                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
677                                 !cur.paragraph().isSeparator(cur.pos())) {
678                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
679                 }
680                 
681                 // in front of RTL boundary? Stay on this side of the boundary because:
682                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
683                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
684                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
685                 
686                 // move right
687                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
688         }
689
690         // move to next paragraph
691         if (cur.pit() != cur.lastpit())
692                 return setCursor(cur, cur.pit() + 1, 0, true, false);
693         return false;
694 }
695
696
697 bool Text::cursorUpParagraph(Cursor & cur)
698 {
699         bool updated = false;
700         if (cur.pos() > 0)
701                 updated = setCursor(cur, cur.pit(), 0);
702         else if (cur.pit() != 0)
703                 updated = setCursor(cur, cur.pit() - 1, 0);
704         return updated;
705 }
706
707
708 bool Text::cursorDownParagraph(Cursor & cur)
709 {
710         bool updated = false;
711         if (cur.pit() != cur.lastpit())
712                 updated = setCursor(cur, cur.pit() + 1, 0);
713         else
714                 updated = setCursor(cur, cur.pit(), cur.lastpos());
715         return updated;
716 }
717
718
719 // fix the cursor `cur' after a characters has been deleted at `where'
720 // position. Called by deleteEmptyParagraphMechanism
721 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
722 {
723         // Do nothing if cursor is not in the paragraph where the
724         // deletion occured,
725         if (cur.pit() != where.pit())
726                 return;
727
728         // If cursor position is after the deletion place update it
729         if (cur.pos() > where.pos())
730                 --cur.pos();
731
732         // Check also if we don't want to set the cursor on a spot behind the
733         // pagragraph because we erased the last character.
734         if (cur.pos() > cur.lastpos())
735                 cur.pos() = cur.lastpos();
736 }
737
738
739 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
740                 Cursor & old, bool & need_anchor_change)
741 {
742         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
743
744         Paragraph & oldpar = old.paragraph();
745
746         // We allow all kinds of "mumbo-jumbo" when freespacing.
747         if (oldpar.isFreeSpacing())
748                 return false;
749
750         /* Ok I'll put some comments here about what is missing.
751            There are still some small problems that can lead to
752            double spaces stored in the document file or space at
753            the beginning of paragraphs(). This happens if you have
754            the cursor between to spaces and then save. Or if you
755            cut and paste and the selection have a space at the
756            beginning and then save right after the paste. (Lgb)
757         */
758
759         // If old.pos() == 0 and old.pos()(1) == LineSeparator
760         // delete the LineSeparator.
761         // MISSING
762
763         // If old.pos() == 1 and old.pos()(0) == LineSeparator
764         // delete the LineSeparator.
765         // MISSING
766
767         bool const same_inset = &old.inset() == &cur.inset();
768         bool const same_par = same_inset && old.pit() == cur.pit();
769         bool const same_par_pos = same_par && old.pos() == cur.pos();
770
771         // If the chars around the old cursor were spaces, delete one of them.
772         if (!same_par_pos) {
773                 // Only if the cursor has really moved.
774                 if (old.pos() > 0
775                     && old.pos() < oldpar.size()
776                     && oldpar.isLineSeparator(old.pos())
777                     && oldpar.isLineSeparator(old.pos() - 1)
778                     && !oldpar.isDeleted(old.pos() - 1)
779                     && !oldpar.isDeleted(old.pos())) {
780                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
781 // FIXME: This will not work anymore when we have multiple views of the same buffer
782 // In this case, we will have to correct also the cursors held by
783 // other bufferviews. It will probably be easier to do that in a more
784 // automated way in CursorSlice code. (JMarc 26/09/2001)
785                         // correct all cursor parts
786                         if (same_par) {
787                                 fixCursorAfterDelete(cur.top(), old.top());
788                                 need_anchor_change = true;
789                         }
790                         return true;
791                 }
792         }
793
794         // only do our magic if we changed paragraph
795         if (same_par)
796                 return false;
797
798         // don't delete anything if this is the ONLY paragraph!
799         if (old.lastpit() == 0)
800                 return false;
801
802         // Do not delete empty paragraphs with keepempty set.
803         if (oldpar.allowEmpty())
804                 return false;
805
806         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
807                 // Delete old par.
808                 old.recordUndo(ATOMIC_UNDO,
809                            max(old.pit() - 1, pit_type(0)),
810                            min(old.pit() + 1, old.lastpit()));
811                 ParagraphList & plist = old.text()->paragraphs();
812                 bool const soa = oldpar.params().startOfAppendix();
813                 plist.erase(boost::next(plist.begin(), old.pit()));
814                 // do not lose start of appendix marker (bug 4212)
815                 if (soa && old.pit() < pit_type(plist.size()))
816                         plist[old.pit()].params().startOfAppendix(true);
817
818                 // see #warning (FIXME?) above 
819                 if (cur.depth() >= old.depth()) {
820                         CursorSlice & curslice = cur[old.depth() - 1];
821                         if (&curslice.inset() == &old.inset()
822                             && curslice.pit() > old.pit()) {
823                                 --curslice.pit();
824                                 // since a paragraph has been deleted, all the
825                                 // insets after `old' have been copied and
826                                 // their address has changed. Therefore we
827                                 // need to `regenerate' cur. (JMarc)
828                                 cur.updateInsets(&(cur.bottom().inset()));
829                                 need_anchor_change = true;
830                         }
831                 }
832                 return true;
833         }
834
835         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
836                 need_anchor_change = true;
837                 // We return true here because the Paragraph contents changed and
838                 // we need a redraw before further action is processed.
839                 return true;
840         }
841
842         return false;
843 }
844
845
846 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
847 {
848         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
849
850         for (pit_type pit = first; pit <= last; ++pit) {
851                 Paragraph & par = pars_[pit];
852
853                 // We allow all kinds of "mumbo-jumbo" when freespacing.
854                 if (par.isFreeSpacing())
855                         continue;
856
857                 for (pos_type pos = 1; pos < par.size(); ++pos) {
858                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
859                             && !par.isDeleted(pos - 1)) {
860                                 if (par.eraseChar(pos - 1, trackChanges)) {
861                                         --pos;
862                                 }
863                         }
864                 }
865
866                 // don't delete anything if this is the only remaining paragraph within the given range
867                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
868                 if (first == last)
869                         continue;
870
871                 // don't delete empty paragraphs with keepempty set
872                 if (par.allowEmpty())
873                         continue;
874
875                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
876                         pars_.erase(boost::next(pars_.begin(), pit));
877                         --pit;
878                         --last;
879                         continue;
880                 }
881
882                 par.stripLeadingSpaces(trackChanges);
883         }
884 }
885
886
887 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
888 {
889         cur.recordUndo(ATOMIC_UNDO, first, last);
890 }
891
892
893 void Text::recUndo(Cursor & cur, pit_type par) const
894 {
895         cur.recordUndo(ATOMIC_UNDO, par, par);
896 }
897
898 } // namespace lyx