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