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