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