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