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