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