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