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