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