]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Make DEPM respect current cursor position.
[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::getStringToIndex(Cursor const & 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         Cursor tmpcur = cur;
446         selectWord(tmpcur, PREVIOUS_WORD);
447
448         if (!tmpcur.selection())
449                 cur.message(_("Nothing to index!"));
450         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
451                 cur.message(_("Cannot index more than one paragraph!"));
452         else
453                 return tmpcur.selectionAsString(false);
454
455         return docstring();
456 }
457
458
459 void Text::setLabelWidthStringToSequence(Cursor const & cur,
460                 docstring const & s)
461 {
462         Cursor c = cur;
463         // Find first of same layout in sequence
464         while (!isFirstInSequence(c.pit())) {
465                 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
466         }
467
468         // now apply label width string to every par
469         // in sequence
470         depth_type const depth = c.paragraph().getDepth();
471         Layout const & layout = c.paragraph().layout();
472         for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
473                 while (c.paragraph().getDepth() > depth) {
474                         ++c.pit();
475                         if (c.pit() > c.lastpit())
476                                 return;
477                 }
478                 if (c.paragraph().getDepth() < depth)
479                         return;
480                 if (c.paragraph().layout() != layout)
481                         return;
482                 c.recordUndo();
483                 c.paragraph().setLabelWidthString(s);
484         }
485 }
486
487
488 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
489 {
490         LBUFERR(cur.text());
491
492         //FIXME UNICODE
493         string const argument = to_utf8(arg);
494         depth_type priordepth = -1;
495         Layout priorlayout;
496         Cursor c(cur.bv());
497         c.setCursor(cur.selectionBegin());
498         for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
499                 Paragraph & par = c.paragraph();
500                 ParagraphParameters params = par.params();
501                 params.read(argument, merge);
502                 // Changes to label width string apply to all paragraphs
503                 // with same layout in a sequence.
504                 // Do this only once for a selected range of paragraphs
505                 // of the same layout and depth.
506                 c.recordUndo();
507                 par.params().apply(params, par.layout());
508                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
509                         setLabelWidthStringToSequence(c, params.labelWidthString());
510                 priordepth = par.getDepth();
511                 priorlayout = par.layout();
512         }
513 }
514
515
516 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
517 {
518         LBUFERR(cur.text());
519
520         depth_type priordepth = -1;
521         Layout priorlayout;
522         Cursor c(cur.bv());
523         c.setCursor(cur.selectionBegin());
524         for ( ; c < cur.selectionEnd() ; ++c.pit()) {
525                 Paragraph & par = c.paragraph();
526                 // Changes to label width string apply to all paragraphs
527                 // with same layout in a sequence.
528                 // Do this only once for a selected range of paragraphs
529                 // of the same layout and depth.
530                 cur.recordUndo();
531                 par.params().apply(p, par.layout());
532                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
533                         setLabelWidthStringToSequence(c,
534                                 par.params().labelWidthString());
535                 priordepth = par.getDepth();
536                 priorlayout = par.layout();
537         }
538 }
539
540
541 // this really should just insert the inset and not move the cursor.
542 void Text::insertInset(Cursor & cur, Inset * inset)
543 {
544         LBUFERR(this == cur.text());
545         LBUFERR(inset);
546         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
547                 Change(cur.buffer()->params().track_changes
548                 ? Change::INSERTED : Change::UNCHANGED));
549 }
550
551
552 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
553                         bool setfont, bool boundary)
554 {
555         TextMetrics const & tm = cur.bv().textMetrics(this);
556         bool const update_needed = !tm.contains(pit);
557         Cursor old = cur;
558         setCursorIntern(cur, pit, pos, setfont, boundary);
559         return cur.bv().checkDepm(cur, old) || update_needed;
560 }
561
562
563 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
564                            bool setfont, bool boundary)
565 {
566         LBUFERR(this == cur.text());
567         cur.boundary(boundary);
568         cur.top().setPitPos(pit, pos);
569         if (setfont)
570                 cur.setCurrentFont();
571 }
572
573
574 bool Text::checkAndActivateInset(Cursor & cur, bool front)
575 {
576         if (front && cur.pos() == cur.lastpos())
577                 return false;
578         if (!front && cur.pos() == 0)
579                 return false;
580         Inset * inset = front ? cur.nextInset() : cur.prevInset();
581         if (!inset || !inset->editable())
582                 return false;
583         if (cur.selection() && cur.realAnchor().find(inset) == -1)
584                 return false;
585         /*
586          * Apparently, when entering an inset we are expected to be positioned
587          * *before* it in the containing paragraph, regardless of the direction
588          * from which we are entering. Otherwise, cursor placement goes awry,
589          * and when we exit from the beginning, we'll be placed *after* the
590          * inset.
591          */
592         if (!front)
593                 --cur.pos();
594         inset->edit(cur, front);
595         cur.setCurrentFont();
596         cur.boundary(false);
597         return true;
598 }
599
600
601 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
602 {
603         if (cur.pos() == -1)
604                 return false;
605         if (cur.pos() == cur.lastpos())
606                 return false;
607         Paragraph & par = cur.paragraph();
608         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
609         if (!inset || !inset->editable())
610                 return false;
611         if (cur.selection() && cur.realAnchor().find(inset) == -1)
612                 return false;
613         inset->edit(cur, movingForward,
614                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
615         cur.setCurrentFont();
616         cur.boundary(false);
617         return true;
618 }
619
620
621 bool Text::cursorBackward(Cursor & cur)
622 {
623         // Tell BufferView to test for FitCursor in any case!
624         cur.screenUpdateFlags(Update::FitCursor);
625
626         // not at paragraph start?
627         if (cur.pos() > 0) {
628                 // if on right side of boundary (i.e. not at paragraph end, but line end)
629                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
630                 // there are some exceptions to ignore this: lineseps, newlines, spaces
631 #if 0
632                 // some effectless debug code to see the values in the debugger
633                 bool bound = cur.boundary();
634                 int rowpos = cur.textRow().pos();
635                 int pos = cur.pos();
636                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
637                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
638                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
639 #endif
640                 if (!cur.boundary() &&
641                                 cur.textRow().pos() == cur.pos() &&
642                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
643                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
644                                 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
645                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
646                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
647                 }
648
649                 // go left and try to enter inset
650                 if (checkAndActivateInset(cur, false))
651                         return false;
652
653                 // normal character left
654                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
655         }
656
657         // move to the previous paragraph or do nothing
658         if (cur.pit() > 0) {
659                 Paragraph & par = getPar(cur.pit() - 1);
660                 pos_type lastpos = par.size();
661                 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
662                         return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
663                 else
664                         return setCursor(cur, cur.pit() - 1, lastpos, true, false);
665         }
666         return false;
667 }
668
669
670 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
671 {
672         Cursor temp_cur = cur;
673         temp_cur.posVisLeft(skip_inset);
674         if (temp_cur.depth() > cur.depth()) {
675                 cur = temp_cur;
676                 return false;
677         }
678         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
679                 true, temp_cur.boundary());
680 }
681
682
683 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
684 {
685         Cursor temp_cur = cur;
686         temp_cur.posVisRight(skip_inset);
687         if (temp_cur.depth() > cur.depth()) {
688                 cur = temp_cur;
689                 return false;
690         }
691         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
692                 true, temp_cur.boundary());
693 }
694
695
696 bool Text::cursorForward(Cursor & cur)
697 {
698         // Tell BufferView to test for FitCursor in any case!
699         cur.screenUpdateFlags(Update::FitCursor);
700
701         // not at paragraph end?
702         if (cur.pos() != cur.lastpos()) {
703                 // in front of editable inset, i.e. jump into it?
704                 if (checkAndActivateInset(cur, true))
705                         return false;
706
707                 TextMetrics const & tm = cur.bv().textMetrics(this);
708                 // if left of boundary -> just jump to right side
709                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
710                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
711                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
712
713                 // next position is left of boundary,
714                 // but go to next line for special cases like space, newline, linesep
715 #if 0
716                 // some effectless debug code to see the values in the debugger
717                 int endpos = cur.textRow().endpos();
718                 int lastpos = cur.lastpos();
719                 int pos = cur.pos();
720                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
721                 bool newline = cur.paragraph().isNewline(cur.pos());
722                 bool sep = cur.paragraph().isSeparator(cur.pos());
723                 if (cur.pos() != cur.lastpos()) {
724                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
725                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
726                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
727                 }
728 #endif
729                 if (cur.textRow().endpos() == cur.pos() + 1) {
730                         if (cur.paragraph().isEnvSeparator(cur.pos()) &&
731                             cur.pos() + 1 == cur.lastpos() &&
732                             cur.pit() != cur.lastpit()) {
733                                 // move to next paragraph
734                                 return setCursor(cur, cur.pit() + 1, 0, true, false);
735                         } else if (cur.textRow().endpos() != cur.lastpos() &&
736                                    !cur.paragraph().isNewline(cur.pos()) &&
737                                    !cur.paragraph().isEnvSeparator(cur.pos()) &&
738                                    !cur.paragraph().isLineSeparator(cur.pos()) &&
739                                    !cur.paragraph().isSeparator(cur.pos())) {
740                                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
741                         }
742                 }
743
744                 // in front of RTL boundary? Stay on this side of the boundary because:
745                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
746                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
747                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
748
749                 // move right
750                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
751         }
752
753         // move to next paragraph
754         if (cur.pit() != cur.lastpit())
755                 return setCursor(cur, cur.pit() + 1, 0, true, false);
756         return false;
757 }
758
759
760 bool Text::cursorUpParagraph(Cursor & cur)
761 {
762         bool updated = false;
763         if (cur.pos() > 0)
764                 updated = setCursor(cur, cur.pit(), 0);
765         else if (cur.pit() != 0)
766                 updated = setCursor(cur, cur.pit() - 1, 0);
767         return updated;
768 }
769
770
771 bool Text::cursorDownParagraph(Cursor & cur)
772 {
773         bool updated = false;
774         if (cur.pit() != cur.lastpit())
775                 if (lyxrc.mac_like_cursor_movement)
776                         if (cur.pos() == cur.lastpos())
777                                 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
778                         else
779                                 updated = setCursor(cur, cur.pit(), cur.lastpos());
780                 else
781                         updated = setCursor(cur, cur.pit() + 1, 0);
782         else
783                 updated = setCursor(cur, cur.pit(), cur.lastpos());
784         return updated;
785 }
786
787
788 namespace {
789 // fix the cursor `cur' after characters has been deleted at `where'
790 // position. Called by deleteEmptyParagraphMechanism
791 void fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where,
792                                                   pos_type from, pos_type to)
793 {
794         // Do nothing if cursor is not in the paragraph where the
795         // deletion occurred,
796         if (cur.pit() != where.pit())
797                 return;
798
799         // If cursor position is after the deletion place update it
800         if (cur.pos() > from)
801                 cur.pos() = max(from, cur.pos() - (to - from));
802
803         // Check also if we don't want to set the cursor on a spot behind the
804         // pagragraph because we erased the last character.
805         if (cur.pos() > cur.lastpos())
806                 cur.pos() = cur.lastpos();
807 }
808
809 }
810
811
812 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
813                 Cursor & old, bool & need_anchor_change)
814 {
815         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
816
817         Paragraph & oldpar = old.paragraph();
818
819         // We allow all kinds of "mumbo-jumbo" when freespacing.
820         if (oldpar.isFreeSpacing())
821                 return false;
822
823         /* Ok I'll put some comments here about what is missing.
824            There are still some small problems that can lead to
825            double spaces stored in the document file or space at
826            the beginning of paragraphs(). This happens if you have
827            the cursor between to spaces and then save. Or if you
828            cut and paste and the selection have a space at the
829            beginning and then save right after the paste. (Lgb)
830         */
831
832         // If old.pos() == 0 and old.pos()(1) == LineSeparator
833         // delete the LineSeparator.
834         // MISSING
835
836         // If old.pos() == 1 and old.pos()(0) == LineSeparator
837         // delete the LineSeparator.
838         // MISSING
839
840         // Find a common inset and the corresponding depth.
841         size_t depth = 0;
842         for (; depth < cur.depth(); ++depth)
843                 if (&old.inset() == &cur[depth].inset())
844                         break;
845
846         // Whether a common inset is found and whether the cursor is still in
847         // the same paragraph (possibly nested).
848         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
849         bool const same_par_pos = depth == cur.depth() - 1 && same_par
850                 && old.pos() == cur[depth].pos();
851
852         // If the chars around the old cursor were spaces, delete some of
853         // them, but only if the cursor has really moved.
854         if (!same_par_pos) {
855                 // find range of spaces around cursors
856                 int from = old.pos();
857                 while (from > 0
858                        && oldpar.isLineSeparator(from - 1)
859                        && !oldpar.isDeleted(from - 1))
860                         --from;
861                 int to = old.pos();
862                 while (to < oldpar.size() - 1
863                        && oldpar.isLineSeparator(to)
864                        && !oldpar.isDeleted(to))
865                         ++to;
866
867                 // If we are not at the extremity of the paragraph, keep one space
868                 if (from != to && from > 0 && to < oldpar.size())
869                         ++from;
870
871                 if (same_par && cur.pos() > from && cur.pos() < to)
872                         ++from;
873
874                 // Remove spaces and adapt cursor.
875                 if (from < to) {
876                         oldpar.eraseChars(from, to, cur.buffer()->params().track_changes);
877 // FIXME: This will not work anymore when we have multiple views of the same buffer
878 // In this case, we will have to correct also the cursors held by
879 // other bufferviews. It will probably be easier to do that in a more
880 // automated way in CursorSlice code. (JMarc 26/09/2001)
881                         // correct all cursor parts
882                         if (same_par) {
883                                 fixCursorAfterDelete(cur[depth], old.top(), from, to);
884                                 need_anchor_change = true;
885                         }
886                         return true;
887                 }
888         }
889
890         // only do our other magic if we changed paragraph
891         if (same_par)
892                 return false;
893
894         // don't delete anything if this is the ONLY paragraph!
895         if (old.lastpit() == 0)
896                 return false;
897
898         // Do not delete empty paragraphs with keepempty set.
899         if (oldpar.allowEmpty())
900                 return false;
901
902         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
903                 // Delete old par.
904                 old.recordUndo(max(old.pit() - 1, pit_type(0)),
905                                min(old.pit() + 1, old.lastpit()));
906                 ParagraphList & plist = old.text()->paragraphs();
907                 bool const soa = oldpar.params().startOfAppendix();
908                 plist.erase(lyx::next(plist.begin(), old.pit()));
909                 // do not lose start of appendix marker (bug 4212)
910                 if (soa && old.pit() < pit_type(plist.size()))
911                         plist[old.pit()].params().startOfAppendix(true);
912
913                 // see #warning (FIXME?) above
914                 if (cur.depth() >= old.depth()) {
915                         CursorSlice & curslice = cur[old.depth() - 1];
916                         if (&curslice.inset() == &old.inset()
917                             && curslice.pit() > old.pit()) {
918                                 --curslice.pit();
919                                 // since a paragraph has been deleted, all the
920                                 // insets after `old' have been copied and
921                                 // their address has changed. Therefore we
922                                 // need to `regenerate' cur. (JMarc)
923                                 cur.updateInsets(&(cur.bottom().inset()));
924                                 need_anchor_change = true;
925                         }
926                 }
927                 return true;
928         }
929
930         if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
931                 need_anchor_change = true;
932                 // We return true here because the Paragraph contents changed and
933                 // we need a redraw before further action is processed.
934                 return true;
935         }
936
937         return false;
938 }
939
940
941 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
942 {
943         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
944
945         for (pit_type pit = first; pit <= last; ++pit) {
946                 Paragraph & par = pars_[pit];
947
948                 // We allow all kinds of "mumbo-jumbo" when freespacing.
949                 if (par.isFreeSpacing())
950                         continue;
951
952                 pos_type from = 0;
953                 while (from < par.size()) {
954                         // skip non-spaces
955                         while (from < par.size()
956                                && (!par.isLineSeparator(from) || par.isDeleted(from)))
957                                 ++from;
958                         // find string of spaces
959                         pos_type to = from;
960                         while (to < par.size()
961                                && par.isLineSeparator(to) && !par.isDeleted(to))
962                                 ++to;
963                         // empty? We are done
964                         if (from == to)
965                                 break;
966                         // if inside the line, keep one space
967                         if (from > 0 && to < par.size())
968                                 ++from;
969                         // remove the extra spaces
970                         if (from < to)
971                                 par.eraseChars(from, to, trackChanges);
972                 }
973
974                 // don't delete anything if this is the only remaining paragraph
975                 // within the given range. Note: Text::acceptOrRejectChanges()
976                 // sets the cursor to 'first' after calling DEPM
977                 if (first == last)
978                         continue;
979
980                 // don't delete empty paragraphs with keepempty set
981                 if (par.allowEmpty())
982                         continue;
983
984                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
985                         pars_.erase(lyx::next(pars_.begin(), pit));
986                         --pit;
987                         --last;
988                         continue;
989                 }
990
991                 par.stripLeadingSpaces(trackChanges);
992         }
993 }
994
995
996 } // namespace lyx