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