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