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