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