]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Update my email and status.
[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(), /**/);
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, /**/);
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         LASSERT(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         LASSERT(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         LASSERT(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(), /**/);
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         LASSERT(this == cur.text(), /**/);
387         return setCursor(cur, 0, 0);
388 }
389
390
391 bool Text::cursorBottom(Cursor & cur)
392 {
393         LASSERT(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         LASSERT(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         LASSERT(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         LASSERT(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 //FIXME This is a little redundant now, but it's probably worth keeping,
512 //especially if we're going to go away from using serialization internally
513 //quite so much.
514 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
515 {
516         LASSERT(cur.text(), /**/);
517
518         depth_type priordepth = -1;
519         Layout priorlayout;
520         Cursor c(cur.bv());
521         c.setCursor(cur.selectionBegin());
522         for ( ; c < cur.selectionEnd() ; ++c.pit()) {
523                 Paragraph & par = c.paragraph();
524                 // Changes to label width string apply to all paragraphs
525                 // with same layout in a sequence.
526                 // Do this only once for a selected range of paragraphs
527                 // of the same layout and depth.
528                 cur.recordUndo();
529                 par.params().apply(p, par.layout());
530                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
531                         setLabelWidthStringToSequence(c,
532                                 par.params().labelWidthString());
533                 priordepth = par.getDepth();
534                 priorlayout = par.layout();
535         }
536 }
537
538
539 // this really should just insert the inset and not move the cursor.
540 void Text::insertInset(Cursor & cur, Inset * inset)
541 {
542         LASSERT(this == cur.text(), /**/);
543         LASSERT(inset, /**/);
544         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
545                 Change(cur.buffer()->params().trackChanges
546                 ? Change::INSERTED : Change::UNCHANGED));
547 }
548
549
550 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
551                         bool setfont, bool boundary)
552 {
553         TextMetrics const & tm = cur.bv().textMetrics(this);
554         bool const update_needed = !tm.contains(par);
555         Cursor old = cur;
556         setCursorIntern(cur, par, pos, setfont, boundary);
557         // FIXME There is a chance that we'll miss a screen update here.
558         // If so, then do DEPM and then check if cur wants an update and
559         // go ahead and do it, if so.
560         return cur.bv().checkDepm(cur, old) || update_needed;
561 }
562
563
564 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
565 {
566         LASSERT(par != int(paragraphs().size()), /**/);
567         cur.pit() = par;
568         cur.pos() = pos;
569
570         // now some strict checking
571         Paragraph & para = getPar(par);
572
573         // None of these should happen, but we're scaredy-cats
574         if (pos < 0) {
575                 lyxerr << "don't like -1" << endl;
576                 LASSERT(false, /**/);
577         }
578
579         if (pos > para.size()) {
580                 lyxerr << "don't like 1, pos: " << pos
581                        << " size: " << para.size()
582                        << " par: " << par << endl;
583                 LASSERT(false, /**/);
584         }
585 }
586
587
588 void Text::setCursorIntern(Cursor & cur,
589                               pit_type par, pos_type pos, bool setfont, bool boundary)
590 {
591         LASSERT(this == cur.text(), /**/);
592         cur.boundary(boundary);
593         setCursor(cur.top(), par, pos);
594         if (setfont)
595                 cur.setCurrentFont();
596 }
597
598
599 bool Text::checkAndActivateInset(Cursor & cur, bool front)
600 {
601         if (cur.selection())
602                 return false;
603         if (front && cur.pos() == cur.lastpos())
604                 return false;
605         if (!front && cur.pos() == 0)
606                 return false;
607         Inset * inset = front ? cur.nextInset() : cur.prevInset();
608         if (!inset || !inset->editable())
609                 return false;
610         /*
611          * Apparently, when entering an inset we are expected to be positioned
612          * *before* it in the containing paragraph, regardless of the direction
613          * from which we are entering. Otherwise, cursor placement goes awry,
614          * and when we exit from the beginning, we'll be placed *after* the
615          * inset.
616          */
617         if (!front)
618                 --cur.pos();
619         inset->edit(cur, front);
620         return true;
621 }
622
623
624 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
625 {
626         if (cur.selection())
627                 return false;
628         if (cur.pos() == -1)
629                 return false;
630         if (cur.pos() == cur.lastpos())
631                 return false;
632         Paragraph & par = cur.paragraph();
633         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
634         if (!inset || !inset->editable())
635                 return false;
636         inset->edit(cur, movingForward, 
637                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
638         return true;
639 }
640
641
642 bool Text::cursorBackward(Cursor & cur)
643 {
644         // Tell BufferView to test for FitCursor in any case!
645         cur.screenUpdateFlags(Update::FitCursor);
646
647         // not at paragraph start?
648         if (cur.pos() > 0) {
649                 // if on right side of boundary (i.e. not at paragraph end, but line end)
650                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
651                 // there are some exceptions to ignore this: lineseps, newlines, spaces
652 #if 0
653                 // some effectless debug code to see the values in the debugger
654                 bool bound = cur.boundary();
655                 int rowpos = cur.textRow().pos();
656                 int pos = cur.pos();
657                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
658                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
659                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
660 #endif
661                 if (!cur.boundary() &&
662                                 cur.textRow().pos() == cur.pos() &&
663                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
664                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
665                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
666                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
667                 }
668                 
669                 // go left and try to enter inset
670                 if (checkAndActivateInset(cur, false))
671                         return false;
672                 
673                 // normal character left
674                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
675         }
676
677         // move to the previous paragraph or do nothing
678         if (cur.pit() > 0)
679                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
680         return false;
681 }
682
683
684 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
685 {
686         Cursor temp_cur = cur;
687         temp_cur.posVisLeft(skip_inset);
688         if (temp_cur.depth() > cur.depth()) {
689                 cur = temp_cur;
690                 return false;
691         }
692         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
693                 true, temp_cur.boundary());
694 }
695
696
697 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
698 {
699         Cursor temp_cur = cur;
700         temp_cur.posVisRight(skip_inset);
701         if (temp_cur.depth() > cur.depth()) {
702                 cur = temp_cur;
703                 return false;
704         }
705         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
706                 true, temp_cur.boundary());
707 }
708
709
710 bool Text::cursorForward(Cursor & cur)
711 {
712         // Tell BufferView to test for FitCursor in any case!
713         cur.screenUpdateFlags(Update::FitCursor);
714
715         // not at paragraph end?
716         if (cur.pos() != cur.lastpos()) {
717                 // in front of editable inset, i.e. jump into it?
718                 if (checkAndActivateInset(cur, true))
719                         return false;
720
721                 TextMetrics const & tm = cur.bv().textMetrics(this);
722                 // if left of boundary -> just jump to right side
723                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
724                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
725                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
726
727                 // next position is left of boundary, 
728                 // but go to next line for special cases like space, newline, linesep
729 #if 0
730                 // some effectless debug code to see the values in the debugger
731                 int endpos = cur.textRow().endpos();
732                 int lastpos = cur.lastpos();
733                 int pos = cur.pos();
734                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
735                 bool newline = cur.paragraph().isNewline(cur.pos());
736                 bool sep = cur.paragraph().isSeparator(cur.pos());
737                 if (cur.pos() != cur.lastpos()) {
738                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
739                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
740                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
741                 }
742 #endif
743                 if (cur.textRow().endpos() == cur.pos() + 1 &&
744                     cur.textRow().endpos() != cur.lastpos() &&
745                                 !cur.paragraph().isNewline(cur.pos()) &&
746                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
747                                 !cur.paragraph().isSeparator(cur.pos())) {
748                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
749                 }
750                 
751                 // in front of RTL boundary? Stay on this side of the boundary because:
752                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
753                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
754                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
755                 
756                 // move right
757                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
758         }
759
760         // move to next paragraph
761         if (cur.pit() != cur.lastpit())
762                 return setCursor(cur, cur.pit() + 1, 0, true, false);
763         return false;
764 }
765
766
767 bool Text::cursorUpParagraph(Cursor & cur)
768 {
769         bool updated = false;
770         if (cur.pos() > 0)
771                 updated = setCursor(cur, cur.pit(), 0);
772         else if (cur.pit() != 0)
773                 updated = setCursor(cur, cur.pit() - 1, 0);
774         return updated;
775 }
776
777
778 bool Text::cursorDownParagraph(Cursor & cur)
779 {
780         bool updated = false;
781         if (cur.pit() != cur.lastpit())
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(), /**/);
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