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