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