]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Do not step equation numbers in deleted equations.
[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         }
351
352         setFont(cur.bv(), cur.selectionBegin().top(),
353                 cur.selectionEnd().top(), newfont);
354 }
355
356
357 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
358                 CursorSlice const & end, Font const & font)
359 {
360         Buffer const & buffer = bv.buffer();
361
362         // Don't use forwardChar here as ditend might have
363         // pos() == lastpos() and forwardChar would miss it.
364         // Can't use forwardPos either as this descends into
365         // nested insets.
366         Language const * language = buffer.params().language;
367         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
368                 if (dit.pos() == dit.lastpos())
369                         continue;
370                 pit_type const pit = dit.pit();
371                 pos_type const pos = dit.pos();
372                 Inset * inset = pars_[pit].getInset(pos);
373                 if (inset && inset->resetFontEdit()) {
374                         // We need to propagate the font change to all
375                         // text cells of the inset (bugs 1973, 6919).
376                         setInsetFont(bv, pit, pos, font);
377                 }
378                 TextMetrics const & tm = bv.textMetrics(this);
379                 Font f = tm.displayFont(pit, pos);
380                 f.update(font, language);
381                 setCharFont(pit, pos, f, tm.font_);
382                 // font change may change language...
383                 // spell checker has to know that
384                 pars_[pit].requestSpellCheck(pos);
385         }
386 }
387
388
389 bool Text::cursorTop(Cursor & cur)
390 {
391         LBUFERR(this == cur.text());
392         return setCursor(cur, 0, 0);
393 }
394
395
396 bool Text::cursorBottom(Cursor & cur)
397 {
398         LBUFERR(this == cur.text());
399         return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
400 }
401
402
403 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
404 {
405         LBUFERR(this == cur.text());
406         // If the mask is completely neutral, tell user
407         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
408                 // Could only happen with user style
409                 cur.message(_("No font change defined."));
410                 return;
411         }
412
413         // Try implicit word selection
414         // If there is a change in the language the implicit word selection
415         // is disabled.
416         CursorSlice const resetCursor = cur.top();
417         bool const implicitSelection =
418                 font.language() == ignore_language
419                 && font.fontInfo().number() == FONT_IGNORE
420                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
421
422         // Set font
423         setFont(cur, font, toggleall);
424
425         // Implicit selections are cleared afterwards
426         // and cursor is set to the original position.
427         if (implicitSelection) {
428                 cur.clearSelection();
429                 cur.top() = resetCursor;
430                 cur.resetAnchor();
431         }
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 & cur, docstring 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 & 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()) : 0;
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
781 // fix the cursor `cur' after a characters has been deleted at `where'
782 // position. Called by deleteEmptyParagraphMechanism
783 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
784 {
785         // Do nothing if cursor is not in the paragraph where the
786         // deletion occurred,
787         if (cur.pit() != where.pit())
788                 return;
789
790         // If cursor position is after the deletion place update it
791         if (cur.pos() > where.pos())
792                 --cur.pos();
793
794         // Check also if we don't want to set the cursor on a spot behind the
795         // pagragraph because we erased the last character.
796         if (cur.pos() > cur.lastpos())
797                 cur.pos() = cur.lastpos();
798 }
799
800
801 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
802                 Cursor & old, bool & need_anchor_change)
803 {
804         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
805
806         Paragraph & oldpar = old.paragraph();
807
808         // We allow all kinds of "mumbo-jumbo" when freespacing.
809         if (oldpar.isFreeSpacing())
810                 return false;
811
812         /* Ok I'll put some comments here about what is missing.
813            There are still some small problems that can lead to
814            double spaces stored in the document file or space at
815            the beginning of paragraphs(). This happens if you have
816            the cursor between to spaces and then save. Or if you
817            cut and paste and the selection have a space at the
818            beginning and then save right after the paste. (Lgb)
819         */
820
821         // If old.pos() == 0 and old.pos()(1) == LineSeparator
822         // delete the LineSeparator.
823         // MISSING
824
825         // If old.pos() == 1 and old.pos()(0) == LineSeparator
826         // delete the LineSeparator.
827         // MISSING
828
829         // Find a common inset and the corresponding depth.
830         size_t depth = 0;
831         for (; depth < cur.depth(); ++depth)
832                 if (&old.inset() == &cur[depth].inset())
833                         break;
834
835         // Whether a common inset is found and whether the cursor is still in
836         // the same paragraph (possibly nested).
837         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
838         bool const same_par_pos = depth == cur.depth() - 1 && same_par
839                 && old.pos() == cur[depth].pos();
840
841         // If the chars around the old cursor were spaces, delete one of them.
842         if (!same_par_pos) {
843                 // Only if the cursor has really moved.
844                 if (old.pos() > 0
845                     && old.pos() < oldpar.size()
846                     && oldpar.isLineSeparator(old.pos())
847                     && oldpar.isLineSeparator(old.pos() - 1)
848                     && !oldpar.isDeleted(old.pos() - 1)
849                     && !oldpar.isDeleted(old.pos())) {
850                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
851 // FIXME: This will not work anymore when we have multiple views of the same buffer
852 // In this case, we will have to correct also the cursors held by
853 // other bufferviews. It will probably be easier to do that in a more
854 // automated way in CursorSlice code. (JMarc 26/09/2001)
855                         // correct all cursor parts
856                         if (same_par) {
857                                 fixCursorAfterDelete(cur[depth], old.top());
858                                 need_anchor_change = true;
859                         }
860                         return true;
861                 }
862         }
863
864         // only do our magic if we changed paragraph
865         if (same_par)
866                 return false;
867
868         // don't delete anything if this is the ONLY paragraph!
869         if (old.lastpit() == 0)
870                 return false;
871
872         // Do not delete empty paragraphs with keepempty set.
873         if (oldpar.allowEmpty())
874                 return false;
875
876         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
877                 // Delete old par.
878                 old.recordUndo(max(old.pit() - 1, pit_type(0)),
879                                min(old.pit() + 1, old.lastpit()));
880                 ParagraphList & plist = old.text()->paragraphs();
881                 bool const soa = oldpar.params().startOfAppendix();
882                 plist.erase(lyx::next(plist.begin(), old.pit()));
883                 // do not lose start of appendix marker (bug 4212)
884                 if (soa && old.pit() < pit_type(plist.size()))
885                         plist[old.pit()].params().startOfAppendix(true);
886
887                 // see #warning (FIXME?) above
888                 if (cur.depth() >= old.depth()) {
889                         CursorSlice & curslice = cur[old.depth() - 1];
890                         if (&curslice.inset() == &old.inset()
891                             && curslice.pit() > old.pit()) {
892                                 --curslice.pit();
893                                 // since a paragraph has been deleted, all the
894                                 // insets after `old' have been copied and
895                                 // their address has changed. Therefore we
896                                 // need to `regenerate' cur. (JMarc)
897                                 cur.updateInsets(&(cur.bottom().inset()));
898                                 need_anchor_change = true;
899                         }
900                 }
901                 return true;
902         }
903
904         if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
905                 need_anchor_change = true;
906                 // We return true here because the Paragraph contents changed and
907                 // we need a redraw before further action is processed.
908                 return true;
909         }
910
911         return false;
912 }
913
914
915 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
916 {
917         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
918
919         for (pit_type pit = first; pit <= last; ++pit) {
920                 Paragraph & par = pars_[pit];
921
922                 // We allow all kinds of "mumbo-jumbo" when freespacing.
923                 if (par.isFreeSpacing())
924                         continue;
925
926                 for (pos_type pos = 1; pos < par.size(); ++pos) {
927                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
928                             && !par.isDeleted(pos - 1)) {
929                                 if (par.eraseChar(pos - 1, trackChanges)) {
930                                         --pos;
931                                 }
932                         }
933                 }
934
935                 // don't delete anything if this is the only remaining paragraph
936                 // within the given range. Note: Text::acceptOrRejectChanges()
937                 // sets the cursor to 'first' after calling DEPM
938                 if (first == last)
939                         continue;
940
941                 // don't delete empty paragraphs with keepempty set
942                 if (par.allowEmpty())
943                         continue;
944
945                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
946                         pars_.erase(lyx::next(pars_.begin(), pit));
947                         --pit;
948                         --last;
949                         continue;
950                 }
951
952                 par.stripLeadingSpaces(trackChanges);
953         }
954 }
955
956
957 } // namespace lyx