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