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