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