]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
ctests: ignore check_load/templates/attic/latex8
[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 "Bidi.h"
26 #include "Buffer.h"
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "Changes.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "Language.h"
37 #include "Layout.h"
38 #include "Lexer.h"
39 #include "LyX.h"
40 #include "LyXRC.h"
41 #include "Paragraph.h"
42 #include "ParagraphParameters.h"
43 #include "TextClass.h"
44 #include "TextMetrics.h"
45
46 #include "insets/InsetCollapsable.h"
47
48 #include "mathed/InsetMathHull.h"
49
50 #include "support/lassert.h"
51 #include "support/debug.h"
52 #include "support/gettext.h"
53 #include "support/textutils.h"
54
55 #include <boost/next_prior.hpp>
56
57 #include <sstream>
58
59 using namespace std;
60
61 namespace lyx {
62
63 bool Text::isMainText() const
64 {
65         return &owner_->buffer().text() == this;
66 }
67
68
69 // Note that this is supposed to return a fully realized font.
70 FontInfo Text::layoutFont(pit_type const pit) const
71 {
72         Layout const & layout = pars_[pit].layout();
73
74         if (!pars_[pit].getDepth())  {
75                 FontInfo lf = layout.resfont;
76                 // In case the default family has been customized
77                 if (layout.font.family() == INHERIT_FAMILY)
78                         lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
79                 FontInfo icf = owner_->getLayout().font();
80                 icf.realize(lf);
81                 return icf;
82         }
83
84         FontInfo font = layout.font;
85         // Realize with the fonts of lesser depth.
86         //font.realize(outerFont(pit));
87         font.realize(owner_->buffer().params().getFont().fontInfo());
88
89         return font;
90 }
91
92
93 // Note that this is supposed to return a fully realized font.
94 FontInfo Text::labelFont(Paragraph const & par) const
95 {
96         Buffer const & buffer = owner_->buffer();
97         Layout const & layout = par.layout();
98
99         if (!par.getDepth()) {
100                 FontInfo lf = layout.reslabelfont;
101                 // In case the default family has been customized
102                 if (layout.labelfont.family() == INHERIT_FAMILY)
103                         lf.setFamily(buffer.params().getFont().fontInfo().family());
104                 return lf;
105         }
106
107         FontInfo font = layout.labelfont;
108         // Realize with the fonts of lesser depth.
109         font.realize(buffer.params().getFont().fontInfo());
110
111         return font;
112 }
113
114
115 void Text::setCharFont(pit_type pit,
116                 pos_type pos, Font const & fnt, Font const & display_font)
117 {
118         Buffer const & buffer = owner_->buffer();
119         Font font = fnt;
120         Layout const & layout = pars_[pit].layout();
121
122         // Get concrete layout font to reduce against
123         FontInfo layoutfont;
124
125         if (pos < pars_[pit].beginOfBody())
126                 layoutfont = layout.labelfont;
127         else
128                 layoutfont = layout.font;
129
130         // Realize against environment font information
131         if (pars_[pit].getDepth()) {
132                 pit_type tp = pit;
133                 while (!layoutfont.resolved() &&
134                        tp != pit_type(paragraphs().size()) &&
135                        pars_[tp].getDepth()) {
136                         tp = outerHook(tp);
137                         if (tp != pit_type(paragraphs().size()))
138                                 layoutfont.realize(pars_[tp].layout().font);
139                 }
140         }
141
142         // Inside inset, apply the inset's font attributes if any
143         // (charstyle!)
144         if (!isMainText())
145                 layoutfont.realize(display_font.fontInfo());
146
147         layoutfont.realize(buffer.params().getFont().fontInfo());
148
149         // Now, reduce font against full layout font
150         font.fontInfo().reduce(layoutfont);
151
152         pars_[pit].setFont(pos, font);
153 }
154
155
156 void Text::setInsetFont(BufferView const & bv, pit_type pit,
157                 pos_type pos, Font const & font)
158 {
159         Inset * const inset = pars_[pit].getInset(pos);
160         LASSERT(inset && inset->resetFontEdit(), return);
161
162         CursorSlice::idx_type endidx = inset->nargs();
163         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
164                 Text * text = cs.text();
165                 if (text) {
166                         // last position of the cell
167                         CursorSlice cellend = cs;
168                         cellend.pit() = cellend.lastpit();
169                         cellend.pos() = cellend.lastpos();
170                         text->setFont(bv, cs, cellend, font);
171                 }
172         }
173 }
174
175
176 void Text::setLayout(pit_type start, pit_type end,
177                      docstring const & layout)
178 {
179         LASSERT(start != end, return);
180
181         Buffer const & buffer = owner_->buffer();
182         BufferParams const & bp = buffer.params();
183         Layout const & lyxlayout = bp.documentClass()[layout];
184
185         for (pit_type pit = start; pit != end; ++pit) {
186                 Paragraph & par = pars_[pit];
187                 par.applyLayout(lyxlayout);
188                 if (lyxlayout.margintype == MARGIN_MANUAL)
189                         par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
190         }
191 }
192
193
194 // set layout over selection and make a total rebreak of those paragraphs
195 void Text::setLayout(Cursor & cur, docstring const & layout)
196 {
197         LBUFERR(this == cur.text());
198
199         pit_type start = cur.selBegin().pit();
200         pit_type end = cur.selEnd().pit() + 1;
201         cur.recordUndoSelection();
202         setLayout(start, end, layout);
203         cur.forceBufferUpdate();
204 }
205
206
207 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
208                         Paragraph const & par, int max_depth)
209 {
210         int const depth = par.params().depth();
211         if (type == Text::INC_DEPTH && depth < max_depth)
212                 return true;
213         if (type == Text::DEC_DEPTH && depth > 0)
214                 return true;
215         return false;
216 }
217
218
219 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
220 {
221         LBUFERR(this == cur.text());
222         // this happens when selecting several cells in tabular (bug 2630)
223         if (cur.selBegin().idx() != cur.selEnd().idx())
224                 return false;
225
226         pit_type const beg = cur.selBegin().pit();
227         pit_type const end = cur.selEnd().pit() + 1;
228         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
229
230         for (pit_type pit = beg; pit != end; ++pit) {
231                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
232                         return true;
233                 max_depth = pars_[pit].getMaxDepthAfter();
234         }
235         return false;
236 }
237
238
239 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
240 {
241         LBUFERR(this == cur.text());
242         pit_type const beg = cur.selBegin().pit();
243         pit_type const end = cur.selEnd().pit() + 1;
244         cur.recordUndoSelection();
245         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
246
247         for (pit_type pit = beg; pit != end; ++pit) {
248                 Paragraph & par = pars_[pit];
249                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
250                         int const depth = par.params().depth();
251                         if (type == INC_DEPTH)
252                                 par.params().depth(depth + 1);
253                         else
254                                 par.params().depth(depth - 1);
255                 }
256                 max_depth = par.getMaxDepthAfter();
257         }
258         // this handles the counter labels, and also fixes up
259         // depth values for follow-on (child) paragraphs
260         cur.forceBufferUpdate();
261 }
262
263
264 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
265 {
266         LASSERT(this == cur.text(), return);
267         // Set the current_font
268         // Determine basis font
269         FontInfo layoutfont;
270         pit_type pit = cur.pit();
271         if (cur.pos() < pars_[pit].beginOfBody())
272                 layoutfont = labelFont(pars_[pit]);
273         else
274                 layoutfont = layoutFont(pit);
275
276         // Update current font
277         cur.real_current_font.update(font,
278                                         cur.buffer()->params().language,
279                                         toggleall);
280
281         // Reduce to implicit settings
282         cur.current_font = cur.real_current_font;
283         cur.current_font.fontInfo().reduce(layoutfont);
284         // And resolve it completely
285         cur.real_current_font.fontInfo().realize(layoutfont);
286
287         // if there is no selection that's all we need to do
288         if (!cur.selection())
289                 return;
290
291         // Ok, we have a selection.
292         cur.recordUndoSelection();
293         Font newfont = font;
294
295         if (toggleall) {        
296                 // Toggling behaves as follows: We check the first character of the
297                 // selection. If it's (say) got EMPH on, then we set to off; if off,
298                 // then to on. With families and the like, we set it to INHERIT, if
299                 // we already have it.
300                 CursorSlice const & sl = cur.selBegin();
301                 Text const & text = *sl.text();
302                 Paragraph const & par = text.getPar(sl.pit());
303         
304                 // get font at the position
305                 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
306                         text.outerFont(sl.pit()));
307                 FontInfo const & oldfi = oldfont.fontInfo();
308         
309                 FontInfo & newfi = newfont.fontInfo();
310         
311                 FontFamily newfam = newfi.family();
312                 if (newfam !=   INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
313                                 newfam == oldfi.family())
314                         newfi.setFamily(INHERIT_FAMILY);
315                 
316                 FontSeries newser = newfi.series();
317                 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
318                         newfi.setSeries(INHERIT_SERIES);
319         
320                 FontShape newshp = newfi.shape();
321                 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
322                                 newshp == oldfi.shape())
323                         newfi.setShape(INHERIT_SHAPE);
324
325                 ColorCode newcol = newfi.color();
326                 if (newcol != Color_none && newcol != Color_inherit 
327                     && newcol != Color_ignore && newcol == oldfi.color())
328                         newfi.setColor(Color_none);
329
330                 // ON/OFF ones
331                 if (newfi.emph() == FONT_TOGGLE)
332                         newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
333                 if (newfi.underbar() == FONT_TOGGLE)
334                         newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
335                 if (newfi.strikeout() == FONT_TOGGLE)
336                         newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
337                 if (newfi.uuline() == FONT_TOGGLE)
338                         newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
339                 if (newfi.uwave() == FONT_TOGGLE)
340                         newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
341                 if (newfi.noun() == FONT_TOGGLE)
342                         newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
343                 if (newfi.number() == FONT_TOGGLE)
344                         newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
345         }
346
347         setFont(cur.bv(), cur.selectionBegin().top(), 
348                 cur.selectionEnd().top(), newfont);
349 }
350
351
352 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
353                 CursorSlice const & end, Font const & font)
354 {
355         Buffer const & buffer = bv.buffer();
356
357         // Don't use forwardChar here as ditend might have
358         // pos() == lastpos() and forwardChar would miss it.
359         // Can't use forwardPos either as this descends into
360         // nested insets.
361         Language const * language = buffer.params().language;
362         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
363                 if (dit.pos() == dit.lastpos())
364                         continue;
365                 pit_type const pit = dit.pit();
366                 pos_type const pos = dit.pos();
367                 Inset * inset = pars_[pit].getInset(pos);
368                 if (inset && inset->resetFontEdit()) {
369                         // We need to propagate the font change to all
370                         // text cells of the inset (bugs 1973, 6919).
371                         setInsetFont(bv, pit, pos, font);
372                 }
373                 TextMetrics const & tm = bv.textMetrics(this);
374                 Font f = tm.displayFont(pit, pos);
375                 f.update(font, language);
376                 setCharFont(pit, pos, f, tm.font_);
377                 // font change may change language... 
378                 // spell checker has to know that
379                 pars_[pit].requestSpellCheck(pos);
380         }
381 }
382
383
384 bool Text::cursorTop(Cursor & cur)
385 {
386         LBUFERR(this == cur.text());
387         return setCursor(cur, 0, 0);
388 }
389
390
391 bool Text::cursorBottom(Cursor & cur)
392 {
393         LBUFERR(this == cur.text());
394         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
395 }
396
397
398 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
399 {
400         LBUFERR(this == cur.text());
401         // If the mask is completely neutral, tell user
402         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
403                 // Could only happen with user style
404                 cur.message(_("No font change defined."));
405                 return;
406         }
407
408         // Try implicit word selection
409         // If there is a change in the language the implicit word selection
410         // is disabled.
411         CursorSlice const resetCursor = cur.top();
412         bool const implicitSelection =
413                 font.language() == ignore_language
414                 && font.fontInfo().number() == FONT_IGNORE
415                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
416
417         // Set font
418         setFont(cur, font, toggleall);
419
420         // Implicit selections are cleared afterwards
421         // and cursor is set to the original position.
422         if (implicitSelection) {
423                 cur.clearSelection();
424                 cur.top() = resetCursor;
425                 cur.resetAnchor();
426         }
427 }
428
429
430 docstring Text::getStringToIndex(Cursor const & cur)
431 {
432         LBUFERR(this == cur.text());
433
434         if (cur.selection())
435                 return cur.selectionAsString(false);
436
437         // Try implicit word selection. If there is a change
438         // in the language the implicit word selection is
439         // disabled.
440         Cursor tmpcur = cur;
441         selectWord(tmpcur, PREVIOUS_WORD);
442
443         if (!tmpcur.selection())
444                 cur.message(_("Nothing to index!"));
445         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
446                 cur.message(_("Cannot index more than one paragraph!"));
447         else
448                 return tmpcur.selectionAsString(false);
449         
450         return docstring();
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                 cur.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().trackChanges
543                 ? Change::INSERTED : Change::UNCHANGED));
544 }
545
546
547 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
548                         bool setfont, bool boundary)
549 {
550         TextMetrics const & tm = cur.bv().textMetrics(this);
551         bool const update_needed = !tm.contains(par);
552         Cursor old = cur;
553         setCursorIntern(cur, par, pos, setfont, boundary);
554         return cur.bv().checkDepm(cur, old) || update_needed;
555 }
556
557
558 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
559 {
560         LASSERT(par != int(paragraphs().size()), return);
561         cur.pit() = par;
562         cur.pos() = pos;
563
564         // now some strict checking
565         Paragraph & para = getPar(par);
566
567         // None of these should happen, but we're scaredy-cats
568         if (pos < 0) {
569                 LYXERR0("Don't like -1!");
570                 LATTEST(false);
571         }
572
573         if (pos > para.size()) {
574                 LYXERR0("Don't like 1, pos: " << pos
575                        << " size: " << para.size()
576                        << " par: " << par);
577                 LATTEST(false);
578         }
579 }
580
581
582 void Text::setCursorIntern(Cursor & cur,
583                               pit_type par, pos_type pos, bool setfont, bool boundary)
584 {
585         LBUFERR(this == cur.text());
586         cur.boundary(boundary);
587         setCursor(cur.top(), par, pos);
588         if (setfont)
589                 cur.setCurrentFont();
590 }
591
592
593 bool Text::checkAndActivateInset(Cursor & cur, bool front)
594 {
595         if (cur.selection())
596                 return false;
597         if (front && cur.pos() == cur.lastpos())
598                 return false;
599         if (!front && cur.pos() == 0)
600                 return false;
601         Inset * inset = front ? cur.nextInset() : cur.prevInset();
602         if (!inset || !inset->editable())
603                 return false;
604         /*
605          * Apparently, when entering an inset we are expected to be positioned
606          * *before* it in the containing paragraph, regardless of the direction
607          * from which we are entering. Otherwise, cursor placement goes awry,
608          * and when we exit from the beginning, we'll be placed *after* the
609          * inset.
610          */
611         if (!front)
612                 --cur.pos();
613         inset->edit(cur, front);
614         return true;
615 }
616
617
618 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
619 {
620         if (cur.selection())
621                 return false;
622         if (cur.pos() == -1)
623                 return false;
624         if (cur.pos() == cur.lastpos())
625                 return false;
626         Paragraph & par = cur.paragraph();
627         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
628         if (!inset || !inset->editable())
629                 return false;
630         inset->edit(cur, movingForward, 
631                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
632         return true;
633 }
634
635
636 bool Text::cursorBackward(Cursor & cur)
637 {
638         // Tell BufferView to test for FitCursor in any case!
639         cur.screenUpdateFlags(Update::FitCursor);
640
641         // not at paragraph start?
642         if (cur.pos() > 0) {
643                 // if on right side of boundary (i.e. not at paragraph end, but line end)
644                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
645                 // there are some exceptions to ignore this: lineseps, newlines, spaces
646 #if 0
647                 // some effectless debug code to see the values in the debugger
648                 bool bound = cur.boundary();
649                 int rowpos = cur.textRow().pos();
650                 int pos = cur.pos();
651                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
652                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
653                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
654 #endif
655                 if (!cur.boundary() &&
656                                 cur.textRow().pos() == cur.pos() &&
657                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
658                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
659                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
660                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
661                 }
662                 
663                 // go left and try to enter inset
664                 if (checkAndActivateInset(cur, false))
665                         return false;
666                 
667                 // normal character left
668                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
669         }
670
671         // move to the previous paragraph or do nothing
672         if (cur.pit() > 0)
673                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
674         return false;
675 }
676
677
678 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
679 {
680         Cursor temp_cur = cur;
681         temp_cur.posVisLeft(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::cursorVisRight(Cursor & cur, bool skip_inset)
692 {
693         Cursor temp_cur = cur;
694         temp_cur.posVisRight(skip_inset);
695         if (temp_cur.depth() > cur.depth()) {
696                 cur = temp_cur;
697                 return false;
698         }
699         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
700                 true, temp_cur.boundary());
701 }
702
703
704 bool Text::cursorForward(Cursor & cur)
705 {
706         // Tell BufferView to test for FitCursor in any case!
707         cur.screenUpdateFlags(Update::FitCursor);
708
709         // not at paragraph end?
710         if (cur.pos() != cur.lastpos()) {
711                 // in front of editable inset, i.e. jump into it?
712                 if (checkAndActivateInset(cur, true))
713                         return false;
714
715                 TextMetrics const & tm = cur.bv().textMetrics(this);
716                 // if left of boundary -> just jump to right side
717                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
718                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
719                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
720
721                 // next position is left of boundary, 
722                 // but go to next line for special cases like space, newline, linesep
723 #if 0
724                 // some effectless debug code to see the values in the debugger
725                 int endpos = cur.textRow().endpos();
726                 int lastpos = cur.lastpos();
727                 int pos = cur.pos();
728                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
729                 bool newline = cur.paragraph().isNewline(cur.pos());
730                 bool sep = cur.paragraph().isSeparator(cur.pos());
731                 if (cur.pos() != cur.lastpos()) {
732                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
733                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
734                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
735                 }
736 #endif
737                 if (cur.textRow().endpos() == cur.pos() + 1 &&
738                     cur.textRow().endpos() != cur.lastpos() &&
739                                 !cur.paragraph().isNewline(cur.pos()) &&
740                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
741                                 !cur.paragraph().isSeparator(cur.pos())) {
742                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
743                 }
744                 
745                 // in front of RTL boundary? Stay on this side of the boundary because:
746                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
747                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
748                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
749                 
750                 // move right
751                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
752         }
753
754         // move to next paragraph
755         if (cur.pit() != cur.lastpit())
756                 return setCursor(cur, cur.pit() + 1, 0, true, false);
757         return false;
758 }
759
760
761 bool Text::cursorUpParagraph(Cursor & cur)
762 {
763         bool updated = false;
764         if (cur.pos() > 0)
765                 updated = setCursor(cur, cur.pit(), 0);
766         else if (cur.pit() != 0)
767                 updated = setCursor(cur, cur.pit() - 1, 0);
768         return updated;
769 }
770
771
772 bool Text::cursorDownParagraph(Cursor & cur)
773 {
774         bool updated = false;
775         if (cur.pit() != cur.lastpit())
776                 if (lyxrc.mac_like_cursor_movement)
777                         if (cur.pos() == cur.lastpos())
778                                 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
779                         else
780                                 updated = setCursor(cur, cur.pit(), cur.lastpos());
781                 else
782                         updated = setCursor(cur, cur.pit() + 1, 0);
783         else
784                 updated = setCursor(cur, cur.pit(), cur.lastpos());
785         return updated;
786 }
787
788
789 // fix the cursor `cur' after a characters has been deleted at `where'
790 // position. Called by deleteEmptyParagraphMechanism
791 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
792 {
793         // Do nothing if cursor is not in the paragraph where the
794         // deletion occured,
795         if (cur.pit() != where.pit())
796                 return;
797
798         // If cursor position is after the deletion place update it
799         if (cur.pos() > where.pos())
800                 --cur.pos();
801
802         // Check also if we don't want to set the cursor on a spot behind the
803         // pagragraph because we erased the last character.
804         if (cur.pos() > cur.lastpos())
805                 cur.pos() = cur.lastpos();
806 }
807
808
809 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
810                 Cursor & old, bool & need_anchor_change)
811 {
812         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
813
814         Paragraph & oldpar = old.paragraph();
815
816         // We allow all kinds of "mumbo-jumbo" when freespacing.
817         if (oldpar.isFreeSpacing())
818                 return false;
819
820         /* Ok I'll put some comments here about what is missing.
821            There are still some small problems that can lead to
822            double spaces stored in the document file or space at
823            the beginning of paragraphs(). This happens if you have
824            the cursor between to spaces and then save. Or if you
825            cut and paste and the selection have a space at the
826            beginning and then save right after the paste. (Lgb)
827         */
828
829         // If old.pos() == 0 and old.pos()(1) == LineSeparator
830         // delete the LineSeparator.
831         // MISSING
832
833         // If old.pos() == 1 and old.pos()(0) == LineSeparator
834         // delete the LineSeparator.
835         // MISSING
836
837         // Find a common inset and the corresponding depth.
838         size_t depth = 0;
839         for (; depth < cur.depth(); ++depth)
840                 if (&old.inset() == &cur[depth].inset())
841                         break;
842
843         // Whether a common inset is found and whether the cursor is still in 
844         // the same paragraph (possibly nested).
845         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
846         bool const same_par_pos = depth == cur.depth() - 1 && same_par 
847                 && old.pos() == cur[depth].pos();
848         
849         // If the chars around the old cursor were spaces, delete one of them.
850         if (!same_par_pos) {
851                 // Only if the cursor has really moved.
852                 if (old.pos() > 0
853                     && old.pos() < oldpar.size()
854                     && oldpar.isLineSeparator(old.pos())
855                     && oldpar.isLineSeparator(old.pos() - 1)
856                     && !oldpar.isDeleted(old.pos() - 1)
857                     && !oldpar.isDeleted(old.pos())) {
858                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
859 // FIXME: This will not work anymore when we have multiple views of the same buffer
860 // In this case, we will have to correct also the cursors held by
861 // other bufferviews. It will probably be easier to do that in a more
862 // automated way in CursorSlice code. (JMarc 26/09/2001)
863                         // correct all cursor parts
864                         if (same_par) {
865                                 fixCursorAfterDelete(cur[depth], old.top());
866                                 need_anchor_change = true;
867                         }
868                         return true;
869                 }
870         }
871
872         // only do our magic if we changed paragraph
873         if (same_par)
874                 return false;
875
876         // don't delete anything if this is the ONLY paragraph!
877         if (old.lastpit() == 0)
878                 return false;
879
880         // Do not delete empty paragraphs with keepempty set.
881         if (oldpar.allowEmpty())
882                 return false;
883
884         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
885                 // Delete old par.
886                 old.recordUndo(ATOMIC_UNDO,
887                            max(old.pit() - 1, pit_type(0)),
888                            min(old.pit() + 1, old.lastpit()));
889                 ParagraphList & plist = old.text()->paragraphs();
890                 bool const soa = oldpar.params().startOfAppendix();
891                 plist.erase(boost::next(plist.begin(), old.pit()));
892                 // do not lose start of appendix marker (bug 4212)
893                 if (soa && old.pit() < pit_type(plist.size()))
894                         plist[old.pit()].params().startOfAppendix(true);
895
896                 // see #warning (FIXME?) above 
897                 if (cur.depth() >= old.depth()) {
898                         CursorSlice & curslice = cur[old.depth() - 1];
899                         if (&curslice.inset() == &old.inset()
900                             && curslice.pit() > old.pit()) {
901                                 --curslice.pit();
902                                 // since a paragraph has been deleted, all the
903                                 // insets after `old' have been copied and
904                                 // their address has changed. Therefore we
905                                 // need to `regenerate' cur. (JMarc)
906                                 cur.updateInsets(&(cur.bottom().inset()));
907                                 need_anchor_change = true;
908                         }
909                 }
910                 return true;
911         }
912
913         if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
914                 need_anchor_change = true;
915                 // We return true here because the Paragraph contents changed and
916                 // we need a redraw before further action is processed.
917                 return true;
918         }
919
920         return false;
921 }
922
923
924 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
925 {
926         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
927
928         for (pit_type pit = first; pit <= last; ++pit) {
929                 Paragraph & par = pars_[pit];
930
931                 // We allow all kinds of "mumbo-jumbo" when freespacing.
932                 if (par.isFreeSpacing())
933                         continue;
934
935                 for (pos_type pos = 1; pos < par.size(); ++pos) {
936                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
937                             && !par.isDeleted(pos - 1)) {
938                                 if (par.eraseChar(pos - 1, trackChanges)) {
939                                         --pos;
940                                 }
941                         }
942                 }
943
944                 // don't delete anything if this is the only remaining paragraph
945                 // within the given range. Note: Text::acceptOrRejectChanges()
946                 // sets the cursor to 'first' after calling DEPM
947                 if (first == last)
948                         continue;
949
950                 // don't delete empty paragraphs with keepempty set
951                 if (par.allowEmpty())
952                         continue;
953
954                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
955                         pars_.erase(boost::next(pars_.begin(), pit));
956                         --pit;
957                         --last;
958                         continue;
959                 }
960
961                 par.stripLeadingSpaces(trackChanges);
962         }
963 }
964
965
966 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
967 {
968         cur.recordUndo(ATOMIC_UNDO, first, last);
969 }
970
971
972 void Text::recUndo(Cursor & cur, pit_type par) const
973 {
974         cur.recordUndo(ATOMIC_UNDO, par, par);
975 }
976
977 } // namespace lyx