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