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