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