]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
9f247a55435ff951b38245dcb26b21e15843ee8e
[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().track_changes
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 (front && cur.pos() == cur.lastpos())
596                 return false;
597         if (!front && cur.pos() == 0)
598                 return false;
599         Inset * inset = front ? cur.nextInset() : cur.prevInset();
600         if (!inset || !inset->editable())
601                 return false;
602         if (cur.selection() && cur.realAnchor().find(inset) == -1)
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.pos() == -1)
621                 return false;
622         if (cur.pos() == cur.lastpos())
623                 return false;
624         Paragraph & par = cur.paragraph();
625         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
626         if (!inset || !inset->editable())
627                 return false;
628         if (cur.selection() && cur.realAnchor().find(inset) == -1)
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().isEnvSeparator(cur.pos() - 1) &&
660                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
661                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
662                 }
663                 
664                 // go left and try to enter inset
665                 if (checkAndActivateInset(cur, false))
666                         return false;
667                 
668                 // normal character left
669                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
670         }
671
672         // move to the previous paragraph or do nothing
673         if (cur.pit() > 0) {
674                 Paragraph & par = getPar(cur.pit() - 1);
675                 pos_type lastpos = par.size();
676                 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
677                         return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
678                 else
679                         return setCursor(cur, cur.pit() - 1, lastpos, true, false);
680         }
681         return false;
682 }
683
684
685 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
686 {
687         Cursor temp_cur = cur;
688         temp_cur.posVisLeft(skip_inset);
689         if (temp_cur.depth() > cur.depth()) {
690                 cur = temp_cur;
691                 return false;
692         }
693         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
694                 true, temp_cur.boundary());
695 }
696
697
698 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
699 {
700         Cursor temp_cur = cur;
701         temp_cur.posVisRight(skip_inset);
702         if (temp_cur.depth() > cur.depth()) {
703                 cur = temp_cur;
704                 return false;
705         }
706         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
707                 true, temp_cur.boundary());
708 }
709
710
711 bool Text::cursorForward(Cursor & cur)
712 {
713         // Tell BufferView to test for FitCursor in any case!
714         cur.screenUpdateFlags(Update::FitCursor);
715
716         // not at paragraph end?
717         if (cur.pos() != cur.lastpos()) {
718                 // in front of editable inset, i.e. jump into it?
719                 if (checkAndActivateInset(cur, true))
720                         return false;
721
722                 TextMetrics const & tm = cur.bv().textMetrics(this);
723                 // if left of boundary -> just jump to right side
724                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
725                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
726                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
727
728                 // next position is left of boundary, 
729                 // but go to next line for special cases like space, newline, linesep
730 #if 0
731                 // some effectless debug code to see the values in the debugger
732                 int endpos = cur.textRow().endpos();
733                 int lastpos = cur.lastpos();
734                 int pos = cur.pos();
735                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
736                 bool newline = cur.paragraph().isNewline(cur.pos());
737                 bool sep = cur.paragraph().isSeparator(cur.pos());
738                 if (cur.pos() != cur.lastpos()) {
739                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
740                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
741                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
742                 }
743 #endif
744                 if (cur.textRow().endpos() == cur.pos() + 1) {
745                         if (cur.paragraph().isEnvSeparator(cur.pos()) &&
746                             cur.pos() + 1 == cur.lastpos() &&
747                             cur.pit() != cur.lastpit()) {
748                                 // move to next paragraph
749                                 return setCursor(cur, cur.pit() + 1, 0, true, false);
750                         } else if (cur.textRow().endpos() != cur.lastpos() &&
751                                    !cur.paragraph().isNewline(cur.pos()) &&
752                                    !cur.paragraph().isEnvSeparator(cur.pos()) &&
753                                    !cur.paragraph().isLineSeparator(cur.pos()) &&
754                                    !cur.paragraph().isSeparator(cur.pos())) {
755                                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
756                         }
757                 }
758                 
759                 // in front of RTL boundary? Stay on this side of the boundary because:
760                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
761                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
762                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
763                 
764                 // move right
765                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
766         }
767
768         // move to next paragraph
769         if (cur.pit() != cur.lastpit())
770                 return setCursor(cur, cur.pit() + 1, 0, true, false);
771         return false;
772 }
773
774
775 bool Text::cursorUpParagraph(Cursor & cur)
776 {
777         bool updated = false;
778         if (cur.pos() > 0)
779                 updated = setCursor(cur, cur.pit(), 0);
780         else if (cur.pit() != 0)
781                 updated = setCursor(cur, cur.pit() - 1, 0);
782         return updated;
783 }
784
785
786 bool Text::cursorDownParagraph(Cursor & cur)
787 {
788         bool updated = false;
789         if (cur.pit() != cur.lastpit())
790                 if (lyxrc.mac_like_cursor_movement)
791                         if (cur.pos() == cur.lastpos())
792                                 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
793                         else
794                                 updated = setCursor(cur, cur.pit(), cur.lastpos());
795                 else
796                         updated = setCursor(cur, cur.pit() + 1, 0);
797         else
798                 updated = setCursor(cur, cur.pit(), cur.lastpos());
799         return updated;
800 }
801
802
803 // fix the cursor `cur' after a characters has been deleted at `where'
804 // position. Called by deleteEmptyParagraphMechanism
805 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
806 {
807         // Do nothing if cursor is not in the paragraph where the
808         // deletion occured,
809         if (cur.pit() != where.pit())
810                 return;
811
812         // If cursor position is after the deletion place update it
813         if (cur.pos() > where.pos())
814                 --cur.pos();
815
816         // Check also if we don't want to set the cursor on a spot behind the
817         // pagragraph because we erased the last character.
818         if (cur.pos() > cur.lastpos())
819                 cur.pos() = cur.lastpos();
820 }
821
822
823 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
824                 Cursor & old, bool & need_anchor_change)
825 {
826         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
827
828         Paragraph & oldpar = old.paragraph();
829
830         // We allow all kinds of "mumbo-jumbo" when freespacing.
831         if (oldpar.isFreeSpacing())
832                 return false;
833
834         /* Ok I'll put some comments here about what is missing.
835            There are still some small problems that can lead to
836            double spaces stored in the document file or space at
837            the beginning of paragraphs(). This happens if you have
838            the cursor between to spaces and then save. Or if you
839            cut and paste and the selection have a space at the
840            beginning and then save right after the paste. (Lgb)
841         */
842
843         // If old.pos() == 0 and old.pos()(1) == LineSeparator
844         // delete the LineSeparator.
845         // MISSING
846
847         // If old.pos() == 1 and old.pos()(0) == LineSeparator
848         // delete the LineSeparator.
849         // MISSING
850
851         // Find a common inset and the corresponding depth.
852         size_t depth = 0;
853         for (; depth < cur.depth(); ++depth)
854                 if (&old.inset() == &cur[depth].inset())
855                         break;
856
857         // Whether a common inset is found and whether the cursor is still in 
858         // the same paragraph (possibly nested).
859         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
860         bool const same_par_pos = depth == cur.depth() - 1 && same_par 
861                 && old.pos() == cur[depth].pos();
862         
863         // If the chars around the old cursor were spaces, delete one of them.
864         if (!same_par_pos) {
865                 // Only if the cursor has really moved.
866                 if (old.pos() > 0
867                     && old.pos() < oldpar.size()
868                     && oldpar.isLineSeparator(old.pos())
869                     && oldpar.isLineSeparator(old.pos() - 1)
870                     && !oldpar.isDeleted(old.pos() - 1)
871                     && !oldpar.isDeleted(old.pos())) {
872                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
873 // FIXME: This will not work anymore when we have multiple views of the same buffer
874 // In this case, we will have to correct also the cursors held by
875 // other bufferviews. It will probably be easier to do that in a more
876 // automated way in CursorSlice code. (JMarc 26/09/2001)
877                         // correct all cursor parts
878                         if (same_par) {
879                                 fixCursorAfterDelete(cur[depth], old.top());
880                                 need_anchor_change = true;
881                         }
882                         return true;
883                 }
884         }
885
886         // only do our magic if we changed paragraph
887         if (same_par)
888                 return false;
889
890         // don't delete anything if this is the ONLY paragraph!
891         if (old.lastpit() == 0)
892                 return false;
893
894         // Do not delete empty paragraphs with keepempty set.
895         if (oldpar.allowEmpty())
896                 return false;
897
898         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
899                 // Delete old par.
900                 old.recordUndo(ATOMIC_UNDO,
901                            max(old.pit() - 1, pit_type(0)),
902                            min(old.pit() + 1, old.lastpit()));
903                 ParagraphList & plist = old.text()->paragraphs();
904                 bool const soa = oldpar.params().startOfAppendix();
905                 plist.erase(boost::next(plist.begin(), old.pit()));
906                 // do not lose start of appendix marker (bug 4212)
907                 if (soa && old.pit() < pit_type(plist.size()))
908                         plist[old.pit()].params().startOfAppendix(true);
909
910                 // see #warning (FIXME?) above 
911                 if (cur.depth() >= old.depth()) {
912                         CursorSlice & curslice = cur[old.depth() - 1];
913                         if (&curslice.inset() == &old.inset()
914                             && curslice.pit() > old.pit()) {
915                                 --curslice.pit();
916                                 // since a paragraph has been deleted, all the
917                                 // insets after `old' have been copied and
918                                 // their address has changed. Therefore we
919                                 // need to `regenerate' cur. (JMarc)
920                                 cur.updateInsets(&(cur.bottom().inset()));
921                                 need_anchor_change = true;
922                         }
923                 }
924                 return true;
925         }
926
927         if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
928                 need_anchor_change = true;
929                 // We return true here because the Paragraph contents changed and
930                 // we need a redraw before further action is processed.
931                 return true;
932         }
933
934         return false;
935 }
936
937
938 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
939 {
940         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
941
942         for (pit_type pit = first; pit <= last; ++pit) {
943                 Paragraph & par = pars_[pit];
944
945                 // We allow all kinds of "mumbo-jumbo" when freespacing.
946                 if (par.isFreeSpacing())
947                         continue;
948
949                 for (pos_type pos = 1; pos < par.size(); ++pos) {
950                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
951                             && !par.isDeleted(pos - 1)) {
952                                 if (par.eraseChar(pos - 1, trackChanges)) {
953                                         --pos;
954                                 }
955                         }
956                 }
957
958                 // don't delete anything if this is the only remaining paragraph
959                 // within the given range. Note: Text::acceptOrRejectChanges()
960                 // sets the cursor to 'first' after calling DEPM
961                 if (first == last)
962                         continue;
963
964                 // don't delete empty paragraphs with keepempty set
965                 if (par.allowEmpty())
966                         continue;
967
968                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
969                         pars_.erase(boost::next(pars_.begin(), pit));
970                         --pit;
971                         --last;
972                         continue;
973                 }
974
975                 par.stripLeadingSpaces(trackChanges);
976         }
977 }
978
979
980 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
981 {
982         cur.recordUndo(ATOMIC_UNDO, first, last);
983 }
984
985
986 void Text::recUndo(Cursor & cur, pit_type par) const
987 {
988         cur.recordUndo(ATOMIC_UNDO, par, par);
989 }
990
991 } // namespace lyx