]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Adjust comments to the truth.
[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->noFontChange(), /**/);
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->noFontChange()) {
349                         // We need to propagate the font change to all
350                         // text cells of the inset (bug 1973).
351                         // FIXME: This should change, see documentation
352                         // of noFontChange in Inset.h
353                         setInsetFont(bv, pit, pos, font, toggleall);
354                 }
355                 TextMetrics const & tm = bv.textMetrics(this);
356                 Font f = tm.displayFont(pit, pos);
357                 f.update(font, language, toggleall);
358                 setCharFont(pit, pos, f, tm.font_);
359                 // font change may change language... 
360                 // spell checker has to know that
361                 pars_[pit].requestSpellCheck(pos);
362         }
363 }
364
365
366 bool Text::cursorTop(Cursor & cur)
367 {
368         LASSERT(this == cur.text(), /**/);
369         return setCursor(cur, 0, 0);
370 }
371
372
373 bool Text::cursorBottom(Cursor & cur)
374 {
375         LASSERT(this == cur.text(), /**/);
376         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
377 }
378
379
380 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
381 {
382         LASSERT(this == cur.text(), /**/);
383         // If the mask is completely neutral, tell user
384         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
385                 // Could only happen with user style
386                 cur.message(_("No font change defined."));
387                 return;
388         }
389
390         // Try implicit word selection
391         // If there is a change in the language the implicit word selection
392         // is disabled.
393         CursorSlice const resetCursor = cur.top();
394         bool const implicitSelection =
395                 font.language() == ignore_language
396                 && font.fontInfo().number() == FONT_IGNORE
397                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
398
399         // Set font
400         setFont(cur, font, toggleall);
401
402         // Implicit selections are cleared afterwards
403         // and cursor is set to the original position.
404         if (implicitSelection) {
405                 cur.clearSelection();
406                 cur.top() = resetCursor;
407                 cur.resetAnchor();
408         }
409 }
410
411
412 docstring Text::getStringToIndex(Cursor const & cur)
413 {
414         LASSERT(this == cur.text(), /**/);
415
416         if (cur.selection())
417                 return cur.selectionAsString(false);
418
419         // Try implicit word selection. If there is a change
420         // in the language the implicit word selection is
421         // disabled.
422         Cursor tmpcur = cur;
423         selectWord(tmpcur, PREVIOUS_WORD);
424
425         if (!tmpcur.selection())
426                 cur.message(_("Nothing to index!"));
427         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
428                 cur.message(_("Cannot index more than one paragraph!"));
429         else
430                 return tmpcur.selectionAsString(false);
431         
432         return docstring();
433 }
434
435
436 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
437                 docstring const & s)
438 {
439         pit_type offset = par_offset;
440         // Find first of same layout in sequence
441         while (!isFirstInSequence(offset)) {
442                 offset = depthHook(offset, pars_[offset].getDepth());
443         }
444
445         // now apply label width string to every par
446         // in sequence
447         pit_type const end = pars_.size();
448         depth_type const depth = pars_[offset].getDepth();
449         Layout const & layout = pars_[offset].layout();
450         for (pit_type pit = offset; pit != end; ++pit) {
451                 while (pars_[pit].getDepth() > depth)
452                         ++pit;
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         return cur.bv().checkDepm(cur, old) || update_needed;
539 }
540
541
542 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
543 {
544         LASSERT(par != int(paragraphs().size()), /**/);
545         cur.pit() = par;
546         cur.pos() = pos;
547
548         // now some strict checking
549         Paragraph & para = getPar(par);
550
551         // None of these should happen, but we're scaredy-cats
552         if (pos < 0) {
553                 lyxerr << "don't like -1" << endl;
554                 LASSERT(false, /**/);
555         }
556
557         if (pos > para.size()) {
558                 lyxerr << "don't like 1, pos: " << pos
559                        << " size: " << para.size()
560                        << " par: " << par << endl;
561                 LASSERT(false, /**/);
562         }
563 }
564
565
566 void Text::setCursorIntern(Cursor & cur,
567                               pit_type par, pos_type pos, bool setfont, bool boundary)
568 {
569         LASSERT(this == cur.text(), /**/);
570         cur.boundary(boundary);
571         setCursor(cur.top(), par, pos);
572         if (setfont)
573                 cur.setCurrentFont();
574 }
575
576
577 bool Text::checkAndActivateInset(Cursor & cur, bool front)
578 {
579         if (cur.selection())
580                 return false;
581         if (front && cur.pos() == cur.lastpos())
582                 return false;
583         if (!front && cur.pos() == 0)
584                 return false;
585         Inset * inset = front ? cur.nextInset() : cur.prevInset();
586         if (!inset || !inset->editable())
587                 return false;
588         /*
589          * Apparently, when entering an inset we are expected to be positioned
590          * *before* it in the containing paragraph, regardless of the direction
591          * from which we are entering. Otherwise, cursor placement goes awry,
592          * and when we exit from the beginning, we'll be placed *after* the
593          * inset.
594          */
595         if (!front)
596                 --cur.pos();
597         inset->edit(cur, front);
598         return true;
599 }
600
601
602 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
603 {
604         if (cur.selection())
605                 return false;
606         if (cur.pos() == -1)
607                 return false;
608         if (cur.pos() == cur.lastpos())
609                 return false;
610         Paragraph & par = cur.paragraph();
611         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
612         if (!inset || !inset->editable())
613                 return false;
614         inset->edit(cur, movingForward, 
615                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
616         return true;
617 }
618
619
620 bool Text::cursorBackward(Cursor & cur)
621 {
622         // Tell BufferView to test for FitCursor in any case!
623         cur.screenUpdateFlags(Update::FitCursor);
624
625         // not at paragraph start?
626         if (cur.pos() > 0) {
627                 // if on right side of boundary (i.e. not at paragraph end, but line end)
628                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
629                 // there are some exceptions to ignore this: lineseps, newlines, spaces
630 #if 0
631                 // some effectless debug code to see the values in the debugger
632                 bool bound = cur.boundary();
633                 int rowpos = cur.textRow().pos();
634                 int pos = cur.pos();
635                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
636                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
637                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
638 #endif
639                 if (!cur.boundary() &&
640                                 cur.textRow().pos() == cur.pos() &&
641                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
642                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
643                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
644                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
645                 }
646                 
647                 // go left and try to enter inset
648                 if (checkAndActivateInset(cur, false))
649                         return false;
650                 
651                 // normal character left
652                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
653         }
654
655         // move to the previous paragraph or do nothing
656         if (cur.pit() > 0)
657                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
658         return false;
659 }
660
661
662 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
663 {
664         Cursor temp_cur = cur;
665         temp_cur.posVisLeft(skip_inset);
666         if (temp_cur.depth() > cur.depth()) {
667                 cur = temp_cur;
668                 return false;
669         }
670         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
671                 true, temp_cur.boundary());
672 }
673
674
675 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
676 {
677         Cursor temp_cur = cur;
678         temp_cur.posVisRight(skip_inset);
679         if (temp_cur.depth() > cur.depth()) {
680                 cur = temp_cur;
681                 return false;
682         }
683         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
684                 true, temp_cur.boundary());
685 }
686
687
688 bool Text::cursorForward(Cursor & cur)
689 {
690         // Tell BufferView to test for FitCursor in any case!
691         cur.screenUpdateFlags(Update::FitCursor);
692
693         // not at paragraph end?
694         if (cur.pos() != cur.lastpos()) {
695                 // in front of editable inset, i.e. jump into it?
696                 if (checkAndActivateInset(cur, true))
697                         return false;
698
699                 TextMetrics const & tm = cur.bv().textMetrics(this);
700                 // if left of boundary -> just jump to right side
701                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
702                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
703                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
704
705                 // next position is left of boundary, 
706                 // but go to next line for special cases like space, newline, linesep
707 #if 0
708                 // some effectless debug code to see the values in the debugger
709                 int endpos = cur.textRow().endpos();
710                 int lastpos = cur.lastpos();
711                 int pos = cur.pos();
712                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
713                 bool newline = cur.paragraph().isNewline(cur.pos());
714                 bool sep = cur.paragraph().isSeparator(cur.pos());
715                 if (cur.pos() != cur.lastpos()) {
716                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
717                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
718                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
719                 }
720 #endif
721                 if (cur.textRow().endpos() == cur.pos() + 1 &&
722                     cur.textRow().endpos() != cur.lastpos() &&
723                                 !cur.paragraph().isNewline(cur.pos()) &&
724                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
725                                 !cur.paragraph().isSeparator(cur.pos())) {
726                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
727                 }
728                 
729                 // in front of RTL boundary? Stay on this side of the boundary because:
730                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
731                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
732                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
733                 
734                 // move right
735                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
736         }
737
738         // move to next paragraph
739         if (cur.pit() != cur.lastpit())
740                 return setCursor(cur, cur.pit() + 1, 0, true, false);
741         return false;
742 }
743
744
745 bool Text::cursorUpParagraph(Cursor & cur)
746 {
747         bool updated = false;
748         if (cur.pos() > 0)
749                 updated = setCursor(cur, cur.pit(), 0);
750         else if (cur.pit() != 0)
751                 updated = setCursor(cur, cur.pit() - 1, 0);
752         return updated;
753 }
754
755
756 bool Text::cursorDownParagraph(Cursor & cur)
757 {
758         bool updated = false;
759         if (cur.pit() != cur.lastpit())
760                 updated = setCursor(cur, cur.pit() + 1, 0);
761         else
762                 updated = setCursor(cur, cur.pit(), cur.lastpos());
763         return updated;
764 }
765
766
767 // fix the cursor `cur' after a characters has been deleted at `where'
768 // position. Called by deleteEmptyParagraphMechanism
769 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
770 {
771         // Do nothing if cursor is not in the paragraph where the
772         // deletion occured,
773         if (cur.pit() != where.pit())
774                 return;
775
776         // If cursor position is after the deletion place update it
777         if (cur.pos() > where.pos())
778                 --cur.pos();
779
780         // Check also if we don't want to set the cursor on a spot behind the
781         // pagragraph because we erased the last character.
782         if (cur.pos() > cur.lastpos())
783                 cur.pos() = cur.lastpos();
784 }
785
786
787 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
788                 Cursor & old, bool & need_anchor_change)
789 {
790         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
791
792         Paragraph & oldpar = old.paragraph();
793
794         // We allow all kinds of "mumbo-jumbo" when freespacing.
795         if (oldpar.isFreeSpacing())
796                 return false;
797
798         /* Ok I'll put some comments here about what is missing.
799            There are still some small problems that can lead to
800            double spaces stored in the document file or space at
801            the beginning of paragraphs(). This happens if you have
802            the cursor between to spaces and then save. Or if you
803            cut and paste and the selection have a space at the
804            beginning and then save right after the paste. (Lgb)
805         */
806
807         // If old.pos() == 0 and old.pos()(1) == LineSeparator
808         // delete the LineSeparator.
809         // MISSING
810
811         // If old.pos() == 1 and old.pos()(0) == LineSeparator
812         // delete the LineSeparator.
813         // MISSING
814
815         // Find a common inset and the corresponding depth.
816         size_t depth = 0;
817         for (; depth < cur.depth(); ++depth)
818                 if (&old.inset() == &cur[depth].inset())
819                         break;
820
821         // Whether a common inset is found and whether the cursor is still in 
822         // the same paragraph (possibly nested).
823         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
824         bool const same_par_pos = depth == cur.depth() - 1 && same_par 
825                 && old.pos() == cur[depth].pos();
826         
827         // If the chars around the old cursor were spaces, delete one of them.
828         if (!same_par_pos) {
829                 // Only if the cursor has really moved.
830                 if (old.pos() > 0
831                     && old.pos() < oldpar.size()
832                     && oldpar.isLineSeparator(old.pos())
833                     && oldpar.isLineSeparator(old.pos() - 1)
834                     && !oldpar.isDeleted(old.pos() - 1)
835                     && !oldpar.isDeleted(old.pos())) {
836                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
837 // FIXME: This will not work anymore when we have multiple views of the same buffer
838 // In this case, we will have to correct also the cursors held by
839 // other bufferviews. It will probably be easier to do that in a more
840 // automated way in CursorSlice code. (JMarc 26/09/2001)
841                         // correct all cursor parts
842                         if (same_par) {
843                                 fixCursorAfterDelete(cur[depth], old.top());
844                                 need_anchor_change = true;
845                         }
846                         return true;
847                 }
848         }
849
850         // only do our magic if we changed paragraph
851         if (same_par)
852                 return false;
853
854         // don't delete anything if this is the ONLY paragraph!
855         if (old.lastpit() == 0)
856                 return false;
857
858         // Do not delete empty paragraphs with keepempty set.
859         if (oldpar.allowEmpty())
860                 return false;
861
862         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
863                 // Delete old par.
864                 old.recordUndo(ATOMIC_UNDO,
865                            max(old.pit() - 1, pit_type(0)),
866                            min(old.pit() + 1, old.lastpit()));
867                 ParagraphList & plist = old.text()->paragraphs();
868                 bool const soa = oldpar.params().startOfAppendix();
869                 plist.erase(boost::next(plist.begin(), old.pit()));
870                 // do not lose start of appendix marker (bug 4212)
871                 if (soa && old.pit() < pit_type(plist.size()))
872                         plist[old.pit()].params().startOfAppendix(true);
873
874                 // see #warning (FIXME?) above 
875                 if (cur.depth() >= old.depth()) {
876                         CursorSlice & curslice = cur[old.depth() - 1];
877                         if (&curslice.inset() == &old.inset()
878                             && curslice.pit() > old.pit()) {
879                                 --curslice.pit();
880                                 // since a paragraph has been deleted, all the
881                                 // insets after `old' have been copied and
882                                 // their address has changed. Therefore we
883                                 // need to `regenerate' cur. (JMarc)
884                                 cur.updateInsets(&(cur.bottom().inset()));
885                                 need_anchor_change = true;
886                         }
887                 }
888                 return true;
889         }
890
891         if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
892                 need_anchor_change = true;
893                 // We return true here because the Paragraph contents changed and
894                 // we need a redraw before further action is processed.
895                 return true;
896         }
897
898         return false;
899 }
900
901
902 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
903 {
904         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
905
906         for (pit_type pit = first; pit <= last; ++pit) {
907                 Paragraph & par = pars_[pit];
908
909                 // We allow all kinds of "mumbo-jumbo" when freespacing.
910                 if (par.isFreeSpacing())
911                         continue;
912
913                 for (pos_type pos = 1; pos < par.size(); ++pos) {
914                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
915                             && !par.isDeleted(pos - 1)) {
916                                 if (par.eraseChar(pos - 1, trackChanges)) {
917                                         --pos;
918                                 }
919                         }
920                 }
921
922                 // don't delete anything if this is the only remaining paragraph
923                 // within the given range. Note: Text::acceptOrRejectChanges()
924                 // sets the cursor to 'first' after calling DEPM
925                 if (first == last)
926                         continue;
927
928                 // don't delete empty paragraphs with keepempty set
929                 if (par.allowEmpty())
930                         continue;
931
932                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
933                         pars_.erase(boost::next(pars_.begin(), pit));
934                         --pit;
935                         --last;
936                         continue;
937                 }
938
939                 par.stripLeadingSpaces(trackChanges);
940         }
941 }
942
943
944 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
945 {
946         cur.recordUndo(ATOMIC_UNDO, first, last);
947 }
948
949
950 void Text::recUndo(Cursor & cur, pit_type par) const
951 {
952         cur.recordUndo(ATOMIC_UNDO, par, par);
953 }
954
955 } // namespace lyx