]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Add quote style information to languages
[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 // return past-the-last paragraph influenced by a layout change on pit
177 pit_type Text::undoSpan(pit_type pit)
178 {
179         pit_type const end = paragraphs().size();
180         pit_type nextpit = pit + 1;
181         if (nextpit == end)
182                 return nextpit;
183         //because of parindents
184         if (!pars_[pit].getDepth())
185                 return boost::next(nextpit);
186         //because of depth constrains
187         for (; nextpit != end; ++pit, ++nextpit) {
188                 if (!pars_[pit].getDepth())
189                         break;
190         }
191         return nextpit;
192 }
193
194
195 void Text::setLayout(pit_type start, pit_type end,
196                      docstring const & layout)
197 {
198         LASSERT(start != end, /**/);
199
200         Buffer const & buffer = owner_->buffer();
201         BufferParams const & bp = buffer.params();
202         Layout const & lyxlayout = bp.documentClass()[layout];
203
204         for (pit_type pit = start; pit != end; ++pit) {
205                 Paragraph & par = pars_[pit];
206                 par.applyLayout(lyxlayout);
207                 if (lyxlayout.margintype == MARGIN_MANUAL)
208                         par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
209         }
210 }
211
212
213 // set layout over selection and make a total rebreak of those paragraphs
214 void Text::setLayout(Cursor & cur, docstring const & layout)
215 {
216         LASSERT(this == cur.text(), /**/);
217
218         pit_type start = cur.selBegin().pit();
219         pit_type end = cur.selEnd().pit() + 1;
220         pit_type undopit = undoSpan(end - 1);
221         recUndo(cur, start, undopit - 1);
222         setLayout(start, end, layout);
223         cur.forceBufferUpdate();
224 }
225
226
227 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
228                         Paragraph const & par, int max_depth)
229 {
230         if (par.layout().labeltype == LABEL_BIBLIO)
231                 return false;
232         int const depth = par.params().depth();
233         if (type == Text::INC_DEPTH && depth < max_depth)
234                 return true;
235         if (type == Text::DEC_DEPTH && depth > 0)
236                 return true;
237         return false;
238 }
239
240
241 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
242 {
243         LASSERT(this == cur.text(), /**/);
244         // this happens when selecting several cells in tabular (bug 2630)
245         if (cur.selBegin().idx() != cur.selEnd().idx())
246                 return false;
247
248         pit_type const beg = cur.selBegin().pit();
249         pit_type const end = cur.selEnd().pit() + 1;
250         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
251
252         for (pit_type pit = beg; pit != end; ++pit) {
253                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
254                         return true;
255                 max_depth = pars_[pit].getMaxDepthAfter();
256         }
257         return false;
258 }
259
260
261 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
262 {
263         LASSERT(this == cur.text(), /**/);
264         pit_type const beg = cur.selBegin().pit();
265         pit_type const end = cur.selEnd().pit() + 1;
266         cur.recordUndoSelection();
267         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
268
269         for (pit_type pit = beg; pit != end; ++pit) {
270                 Paragraph & par = pars_[pit];
271                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
272                         int const depth = par.params().depth();
273                         if (type == INC_DEPTH)
274                                 par.params().depth(depth + 1);
275                         else
276                                 par.params().depth(depth - 1);
277                 }
278                 max_depth = par.getMaxDepthAfter();
279         }
280         // this handles the counter labels, and also fixes up
281         // depth values for follow-on (child) paragraphs
282         cur.forceBufferUpdate();
283 }
284
285
286 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
287 {
288         LASSERT(this == cur.text(), /**/);
289         // Set the current_font
290         // Determine basis font
291         FontInfo layoutfont;
292         pit_type pit = cur.pit();
293         if (cur.pos() < pars_[pit].beginOfBody())
294                 layoutfont = labelFont(pars_[pit]);
295         else
296                 layoutfont = layoutFont(pit);
297
298         // Update current font
299         cur.real_current_font.update(font,
300                                         cur.buffer()->params().language,
301                                         toggleall);
302
303         // Reduce to implicit settings
304         cur.current_font = cur.real_current_font;
305         cur.current_font.fontInfo().reduce(layoutfont);
306         // And resolve it completely
307         cur.real_current_font.fontInfo().realize(layoutfont);
308
309         // if there is no selection that's all we need to do
310         if (!cur.selection())
311                 return;
312
313         // Ok, we have a selection.
314         cur.recordUndoSelection();
315         Font newfont = font;
316
317         if (toggleall) {        
318                 // Toggling behaves as follows: We check the first character of the
319                 // selection. If it's (say) got EMPH on, then we set to off; if off,
320                 // then to on. With families and the like, we set it to INHERIT, if
321                 // we already have it.
322                 CursorSlice const & sl = cur.selBegin();
323                 Text const & text = *sl.text();
324                 Paragraph const & par = text.getPar(sl.pit());
325         
326                 // get font at the position
327                 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
328                         text.outerFont(sl.pit()));
329                 FontInfo const & oldfi = oldfont.fontInfo();
330         
331                 FontInfo & newfi = newfont.fontInfo();
332         
333                 FontFamily newfam = newfi.family();
334                 if (newfam !=   INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
335                                 newfam == oldfi.family())
336                         newfi.setFamily(INHERIT_FAMILY);
337                 
338                 FontSeries newser = newfi.series();
339                 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
340                         newfi.setSeries(INHERIT_SERIES);
341         
342                 FontShape newshp = newfi.shape();
343                 if (newshp !=   INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
344                                 newshp == oldfi.shape())
345                         newfi.setShape(INHERIT_SHAPE);
346
347                 ColorCode newcol = newfi.color();
348                 if (newcol != Color_none && newcol != Color_inherit 
349                     && newcol != Color_ignore && newcol == oldfi.color())
350                         newfi.setColor(Color_none);
351
352                 // ON/OFF ones
353                 if (newfi.emph() == FONT_TOGGLE)
354                         newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
355                 if (newfi.underbar() == FONT_TOGGLE)
356                         newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
357                 if (newfi.strikeout() == FONT_TOGGLE)
358                         newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
359                 if (newfi.uuline() == FONT_TOGGLE)
360                         newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
361                 if (newfi.uwave() == FONT_TOGGLE)
362                         newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
363                 if (newfi.noun() == FONT_TOGGLE)
364                         newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
365                 if (newfi.number() == FONT_TOGGLE)
366                         newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
367         }
368
369         setFont(cur.bv(), cur.selectionBegin().top(), 
370                 cur.selectionEnd().top(), newfont);
371 }
372
373
374 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
375                 CursorSlice const & end, Font const & font)
376 {
377         Buffer const & buffer = bv.buffer();
378
379         // Don't use forwardChar here as ditend might have
380         // pos() == lastpos() and forwardChar would miss it.
381         // Can't use forwardPos either as this descends into
382         // nested insets.
383         Language const * language = buffer.params().language;
384         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
385                 if (dit.pos() == dit.lastpos())
386                         continue;
387                 pit_type const pit = dit.pit();
388                 pos_type const pos = dit.pos();
389                 Inset * inset = pars_[pit].getInset(pos);
390                 if (inset && inset->resetFontEdit()) {
391                         // We need to propagate the font change to all
392                         // text cells of the inset (bugs 1973, 6919).
393                         setInsetFont(bv, pit, pos, font);
394                 }
395                 TextMetrics const & tm = bv.textMetrics(this);
396                 Font f = tm.displayFont(pit, pos);
397                 f.update(font, language);
398                 setCharFont(pit, pos, f, tm.font_);
399                 // font change may change language... 
400                 // spell checker has to know that
401                 pars_[pit].requestSpellCheck(pos);
402         }
403 }
404
405
406 bool Text::cursorTop(Cursor & cur)
407 {
408         LASSERT(this == cur.text(), /**/);
409         return setCursor(cur, 0, 0);
410 }
411
412
413 bool Text::cursorBottom(Cursor & cur)
414 {
415         LASSERT(this == cur.text(), /**/);
416         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
417 }
418
419
420 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
421 {
422         LASSERT(this == cur.text(), /**/);
423         // If the mask is completely neutral, tell user
424         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
425                 // Could only happen with user style
426                 cur.message(_("No font change defined."));
427                 return;
428         }
429
430         // Try implicit word selection
431         // If there is a change in the language the implicit word selection
432         // is disabled.
433         CursorSlice const resetCursor = cur.top();
434         bool const implicitSelection =
435                 font.language() == ignore_language
436                 && font.fontInfo().number() == FONT_IGNORE
437                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
438
439         // Set font
440         setFont(cur, font, toggleall);
441
442         // Implicit selections are cleared afterwards
443         // and cursor is set to the original position.
444         if (implicitSelection) {
445                 cur.clearSelection();
446                 cur.top() = resetCursor;
447                 cur.resetAnchor();
448         }
449 }
450
451
452 docstring Text::getStringToIndex(Cursor const & cur)
453 {
454         LASSERT(this == cur.text(), /**/);
455
456         if (cur.selection())
457                 return cur.selectionAsString(false);
458
459         // Try implicit word selection. If there is a change
460         // in the language the implicit word selection is
461         // disabled.
462         Cursor tmpcur = cur;
463         selectWord(tmpcur, PREVIOUS_WORD);
464
465         if (!tmpcur.selection())
466                 cur.message(_("Nothing to index!"));
467         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
468                 cur.message(_("Cannot index more than one paragraph!"));
469         else
470                 return tmpcur.selectionAsString(false);
471         
472         return docstring();
473 }
474
475
476 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
477                 docstring const & s)
478 {
479         pit_type offset = par_offset;
480         // Find first of same layout in sequence
481         while (!isFirstInSequence(offset)) {
482                 offset = depthHook(offset, pars_[offset].getDepth());
483         }
484
485         // now apply label width string to every par
486         // in sequence
487         pit_type const end = pars_.size();
488         depth_type const depth = pars_[offset].getDepth();
489         Layout const & layout = pars_[offset].layout();
490         for (pit_type pit = offset; pit != end; ++pit) {
491                 while (pars_[pit].getDepth() > depth) {
492                         ++pit;
493                         if (pit == end)
494                                 return;
495                 }
496                 if (pars_[pit].getDepth() < depth)
497                         return;
498                 if (pars_[pit].layout() != layout)
499                         return;
500                 pars_[pit].setLabelWidthString(s);
501         }
502 }
503
504
505 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
506 {
507         LASSERT(cur.text(), /**/);
508         // make sure that the depth behind the selection are restored, too
509         pit_type undopit = undoSpan(cur.selEnd().pit());
510         recUndo(cur, cur.selBegin().pit(), undopit - 1);
511
512         //FIXME UNICODE
513         string const argument = to_utf8(arg);
514         depth_type priordepth = -1;
515         Layout priorlayout;
516         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
517              pit <= end; ++pit) {
518                 Paragraph & par = pars_[pit];
519                 ParagraphParameters params = par.params();
520                 params.read(argument, merge);
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                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
526                         setLabelWidthStringToSequence(pit, params.labelWidthString());
527                 par.params().apply(params, par.layout());
528                 priordepth = par.getDepth();
529                 priorlayout = par.layout();
530         }
531 }
532
533
534 //FIXME This is a little redundant now, but it's probably worth keeping,
535 //especially if we're going to go away from using serialization internally
536 //quite so much.
537 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
538 {
539         LASSERT(cur.text(), /**/);
540         // make sure that the depth behind the selection are restored, too
541         pit_type undopit = undoSpan(cur.selEnd().pit());
542         recUndo(cur, cur.selBegin().pit(), undopit - 1);
543
544         depth_type priordepth = -1;
545         Layout priorlayout;
546         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
547              pit <= end; ++pit) {
548                 Paragraph & par = pars_[pit];
549                 // Changes to label width string apply to all paragraphs
550                 // with same layout in a sequence.
551                 // Do this only once for a selected range of paragraphs
552                 // of the same layout and depth.
553                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
554                         setLabelWidthStringToSequence(pit,
555                                 par.params().labelWidthString());
556                 par.params().apply(p, par.layout());
557                 priordepth = par.getDepth();
558                 priorlayout = par.layout();
559         }
560 }
561
562
563 // this really should just insert the inset and not move the cursor.
564 void Text::insertInset(Cursor & cur, Inset * inset)
565 {
566         LASSERT(this == cur.text(), /**/);
567         LASSERT(inset, /**/);
568         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
569                 Change(cur.buffer()->params().trackChanges
570                 ? Change::INSERTED : Change::UNCHANGED));
571 }
572
573
574 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
575                         bool setfont, bool boundary)
576 {
577         TextMetrics const & tm = cur.bv().textMetrics(this);
578         bool const update_needed = !tm.contains(par);
579         Cursor old = cur;
580         setCursorIntern(cur, par, pos, setfont, boundary);
581         // FIXME There is a chance that we'll miss a screen update here.
582         // If so, then do DEPM and then check if cur wants an update and
583         // go ahead and do it, if so.
584         return cur.bv().checkDepm(cur, old) || update_needed;
585 }
586
587
588 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
589 {
590         LASSERT(par != int(paragraphs().size()), /**/);
591         cur.pit() = par;
592         cur.pos() = pos;
593
594         // now some strict checking
595         Paragraph & para = getPar(par);
596
597         // None of these should happen, but we're scaredy-cats
598         if (pos < 0) {
599                 lyxerr << "don't like -1" << endl;
600                 LASSERT(false, /**/);
601         }
602
603         if (pos > para.size()) {
604                 lyxerr << "don't like 1, pos: " << pos
605                        << " size: " << para.size()
606                        << " par: " << par << endl;
607                 LASSERT(false, /**/);
608         }
609 }
610
611
612 void Text::setCursorIntern(Cursor & cur,
613                               pit_type par, pos_type pos, bool setfont, bool boundary)
614 {
615         LASSERT(this == cur.text(), /**/);
616         cur.boundary(boundary);
617         setCursor(cur.top(), par, pos);
618         if (setfont)
619                 cur.setCurrentFont();
620 }
621
622
623 bool Text::checkAndActivateInset(Cursor & cur, bool front)
624 {
625         if (cur.selection())
626                 return false;
627         if (front && cur.pos() == cur.lastpos())
628                 return false;
629         if (!front && cur.pos() == 0)
630                 return false;
631         Inset * inset = front ? cur.nextInset() : cur.prevInset();
632         if (!inset || !inset->editable())
633                 return false;
634         /*
635          * Apparently, when entering an inset we are expected to be positioned
636          * *before* it in the containing paragraph, regardless of the direction
637          * from which we are entering. Otherwise, cursor placement goes awry,
638          * and when we exit from the beginning, we'll be placed *after* the
639          * inset.
640          */
641         if (!front)
642                 --cur.pos();
643         inset->edit(cur, front);
644         return true;
645 }
646
647
648 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
649 {
650         if (cur.selection())
651                 return false;
652         if (cur.pos() == -1)
653                 return false;
654         if (cur.pos() == cur.lastpos())
655                 return false;
656         Paragraph & par = cur.paragraph();
657         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
658         if (!inset || !inset->editable())
659                 return false;
660         inset->edit(cur, movingForward, 
661                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
662         return true;
663 }
664
665
666 bool Text::cursorBackward(Cursor & cur)
667 {
668         // Tell BufferView to test for FitCursor in any case!
669         cur.screenUpdateFlags(Update::FitCursor);
670
671         // not at paragraph start?
672         if (cur.pos() > 0) {
673                 // if on right side of boundary (i.e. not at paragraph end, but line end)
674                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
675                 // there are some exceptions to ignore this: lineseps, newlines, spaces
676 #if 0
677                 // some effectless debug code to see the values in the debugger
678                 bool bound = cur.boundary();
679                 int rowpos = cur.textRow().pos();
680                 int pos = cur.pos();
681                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
682                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
683                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
684 #endif
685                 if (!cur.boundary() &&
686                                 cur.textRow().pos() == cur.pos() &&
687                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
688                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
689                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
690                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
691                 }
692                 
693                 // go left and try to enter inset
694                 if (checkAndActivateInset(cur, false))
695                         return false;
696                 
697                 // normal character left
698                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
699         }
700
701         // move to the previous paragraph or do nothing
702         if (cur.pit() > 0)
703                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
704         return false;
705 }
706
707
708 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
709 {
710         Cursor temp_cur = cur;
711         temp_cur.posVisLeft(skip_inset);
712         if (temp_cur.depth() > cur.depth()) {
713                 cur = temp_cur;
714                 return false;
715         }
716         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
717                 true, temp_cur.boundary());
718 }
719
720
721 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
722 {
723         Cursor temp_cur = cur;
724         temp_cur.posVisRight(skip_inset);
725         if (temp_cur.depth() > cur.depth()) {
726                 cur = temp_cur;
727                 return false;
728         }
729         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
730                 true, temp_cur.boundary());
731 }
732
733
734 bool Text::cursorForward(Cursor & cur)
735 {
736         // Tell BufferView to test for FitCursor in any case!
737         cur.screenUpdateFlags(Update::FitCursor);
738
739         // not at paragraph end?
740         if (cur.pos() != cur.lastpos()) {
741                 // in front of editable inset, i.e. jump into it?
742                 if (checkAndActivateInset(cur, true))
743                         return false;
744
745                 TextMetrics const & tm = cur.bv().textMetrics(this);
746                 // if left of boundary -> just jump to right side
747                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
748                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
749                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
750
751                 // next position is left of boundary, 
752                 // but go to next line for special cases like space, newline, linesep
753 #if 0
754                 // some effectless debug code to see the values in the debugger
755                 int endpos = cur.textRow().endpos();
756                 int lastpos = cur.lastpos();
757                 int pos = cur.pos();
758                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
759                 bool newline = cur.paragraph().isNewline(cur.pos());
760                 bool sep = cur.paragraph().isSeparator(cur.pos());
761                 if (cur.pos() != cur.lastpos()) {
762                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
763                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
764                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
765                 }
766 #endif
767                 if (cur.textRow().endpos() == cur.pos() + 1 &&
768                     cur.textRow().endpos() != cur.lastpos() &&
769                                 !cur.paragraph().isNewline(cur.pos()) &&
770                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
771                                 !cur.paragraph().isSeparator(cur.pos())) {
772                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
773                 }
774                 
775                 // in front of RTL boundary? Stay on this side of the boundary because:
776                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
777                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
778                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
779                 
780                 // move right
781                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
782         }
783
784         // move to next paragraph
785         if (cur.pit() != cur.lastpit())
786                 return setCursor(cur, cur.pit() + 1, 0, true, false);
787         return false;
788 }
789
790
791 bool Text::cursorUpParagraph(Cursor & cur)
792 {
793         bool updated = false;
794         if (cur.pos() > 0)
795                 updated = setCursor(cur, cur.pit(), 0);
796         else if (cur.pit() != 0)
797                 updated = setCursor(cur, cur.pit() - 1, 0);
798         return updated;
799 }
800
801
802 bool Text::cursorDownParagraph(Cursor & cur)
803 {
804         bool updated = false;
805         if (cur.pit() != cur.lastpit())
806                 updated = setCursor(cur, cur.pit() + 1, 0);
807         else
808                 updated = setCursor(cur, cur.pit(), cur.lastpos());
809         return updated;
810 }
811
812
813 // fix the cursor `cur' after a characters has been deleted at `where'
814 // position. Called by deleteEmptyParagraphMechanism
815 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
816 {
817         // Do nothing if cursor is not in the paragraph where the
818         // deletion occured,
819         if (cur.pit() != where.pit())
820                 return;
821
822         // If cursor position is after the deletion place update it
823         if (cur.pos() > where.pos())
824                 --cur.pos();
825
826         // Check also if we don't want to set the cursor on a spot behind the
827         // pagragraph because we erased the last character.
828         if (cur.pos() > cur.lastpos())
829                 cur.pos() = cur.lastpos();
830 }
831
832
833 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
834                 Cursor & old, bool & need_anchor_change)
835 {
836         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
837
838         Paragraph & oldpar = old.paragraph();
839
840         // We allow all kinds of "mumbo-jumbo" when freespacing.
841         if (oldpar.isFreeSpacing())
842                 return false;
843
844         /* Ok I'll put some comments here about what is missing.
845            There are still some small problems that can lead to
846            double spaces stored in the document file or space at
847            the beginning of paragraphs(). This happens if you have
848            the cursor between to spaces and then save. Or if you
849            cut and paste and the selection have a space at the
850            beginning and then save right after the paste. (Lgb)
851         */
852
853         // If old.pos() == 0 and old.pos()(1) == LineSeparator
854         // delete the LineSeparator.
855         // MISSING
856
857         // If old.pos() == 1 and old.pos()(0) == LineSeparator
858         // delete the LineSeparator.
859         // MISSING
860
861         // Find a common inset and the corresponding depth.
862         size_t depth = 0;
863         for (; depth < cur.depth(); ++depth)
864                 if (&old.inset() == &cur[depth].inset())
865                         break;
866
867         // Whether a common inset is found and whether the cursor is still in 
868         // the same paragraph (possibly nested).
869         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
870         bool const same_par_pos = depth == cur.depth() - 1 && same_par 
871                 && old.pos() == cur[depth].pos();
872         
873         // If the chars around the old cursor were spaces, delete one of them.
874         if (!same_par_pos) {
875                 // Only if the cursor has really moved.
876                 if (old.pos() > 0
877                     && old.pos() < oldpar.size()
878                     && oldpar.isLineSeparator(old.pos())
879                     && oldpar.isLineSeparator(old.pos() - 1)
880                     && !oldpar.isDeleted(old.pos() - 1)
881                     && !oldpar.isDeleted(old.pos())) {
882                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
883 // FIXME: This will not work anymore when we have multiple views of the same buffer
884 // In this case, we will have to correct also the cursors held by
885 // other bufferviews. It will probably be easier to do that in a more
886 // automated way in CursorSlice code. (JMarc 26/09/2001)
887                         // correct all cursor parts
888                         if (same_par) {
889                                 fixCursorAfterDelete(cur[depth], old.top());
890                                 need_anchor_change = true;
891                         }
892                         return true;
893                 }
894         }
895
896         // only do our magic if we changed paragraph
897         if (same_par)
898                 return false;
899
900         // don't delete anything if this is the ONLY paragraph!
901         if (old.lastpit() == 0)
902                 return false;
903
904         // Do not delete empty paragraphs with keepempty set.
905         if (oldpar.allowEmpty())
906                 return false;
907
908         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
909                 // Delete old par.
910                 old.recordUndo(ATOMIC_UNDO,
911                            max(old.pit() - 1, pit_type(0)),
912                            min(old.pit() + 1, old.lastpit()));
913                 ParagraphList & plist = old.text()->paragraphs();
914                 bool const soa = oldpar.params().startOfAppendix();
915                 plist.erase(boost::next(plist.begin(), old.pit()));
916                 // do not lose start of appendix marker (bug 4212)
917                 if (soa && old.pit() < pit_type(plist.size()))
918                         plist[old.pit()].params().startOfAppendix(true);
919
920                 // see #warning (FIXME?) above 
921                 if (cur.depth() >= old.depth()) {
922                         CursorSlice & curslice = cur[old.depth() - 1];
923                         if (&curslice.inset() == &old.inset()
924                             && curslice.pit() > old.pit()) {
925                                 --curslice.pit();
926                                 // since a paragraph has been deleted, all the
927                                 // insets after `old' have been copied and
928                                 // their address has changed. Therefore we
929                                 // need to `regenerate' cur. (JMarc)
930                                 cur.updateInsets(&(cur.bottom().inset()));
931                                 need_anchor_change = true;
932                         }
933                 }
934                 return true;
935         }
936
937         if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
938                 need_anchor_change = true;
939                 // We return true here because the Paragraph contents changed and
940                 // we need a redraw before further action is processed.
941                 return true;
942         }
943
944         return false;
945 }
946
947
948 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
949 {
950         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
951
952         for (pit_type pit = first; pit <= last; ++pit) {
953                 Paragraph & par = pars_[pit];
954
955                 // We allow all kinds of "mumbo-jumbo" when freespacing.
956                 if (par.isFreeSpacing())
957                         continue;
958
959                 for (pos_type pos = 1; pos < par.size(); ++pos) {
960                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
961                             && !par.isDeleted(pos - 1)) {
962                                 if (par.eraseChar(pos - 1, trackChanges)) {
963                                         --pos;
964                                 }
965                         }
966                 }
967
968                 // don't delete anything if this is the only remaining paragraph
969                 // within the given range. Note: Text::acceptOrRejectChanges()
970                 // sets the cursor to 'first' after calling DEPM
971                 if (first == last)
972                         continue;
973
974                 // don't delete empty paragraphs with keepempty set
975                 if (par.allowEmpty())
976                         continue;
977
978                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
979                         pars_.erase(boost::next(pars_.begin(), pit));
980                         --pit;
981                         --last;
982                         continue;
983                 }
984
985                 par.stripLeadingSpaces(trackChanges);
986         }
987 }
988
989
990 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
991 {
992         cur.recordUndo(ATOMIC_UNDO, first, last);
993 }
994
995
996 void Text::recUndo(Cursor & cur, pit_type par) const
997 {
998         cur.recordUndo(ATOMIC_UNDO, par, par);
999 }
1000
1001 } // namespace lyx