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