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