]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Fix commented out code
[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/InsetCollapsible.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         deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
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.fixIfBroken();
204         cur.setCurrentFont();
205         cur.forceBufferUpdate();
206 }
207
208
209 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
210                         Paragraph const & par, int max_depth)
211 {
212         int const depth = par.params().depth();
213         if (type == Text::INC_DEPTH && depth < max_depth)
214                 return true;
215         if (type == Text::DEC_DEPTH && depth > 0)
216                 return true;
217         return false;
218 }
219
220
221 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
222 {
223         LBUFERR(this == cur.text());
224         // this happens when selecting several cells in tabular (bug 2630)
225         if (cur.selBegin().idx() != cur.selEnd().idx())
226                 return false;
227
228         pit_type const beg = cur.selBegin().pit();
229         pit_type const end = cur.selEnd().pit() + 1;
230         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
231
232         for (pit_type pit = beg; pit != end; ++pit) {
233                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
234                         return true;
235                 max_depth = pars_[pit].getMaxDepthAfter();
236         }
237         return false;
238 }
239
240
241 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
242 {
243         LBUFERR(this == cur.text());
244         pit_type const beg = cur.selBegin().pit();
245         pit_type const end = cur.selEnd().pit() + 1;
246         cur.recordUndoSelection();
247         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
248
249         for (pit_type pit = beg; pit != end; ++pit) {
250                 Paragraph & par = pars_[pit];
251                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
252                         int const depth = par.params().depth();
253                         if (type == INC_DEPTH)
254                                 par.params().depth(depth + 1);
255                         else
256                                 par.params().depth(depth - 1);
257                 }
258                 max_depth = par.getMaxDepthAfter();
259         }
260         // this handles the counter labels, and also fixes up
261         // depth values for follow-on (child) paragraphs
262         cur.forceBufferUpdate();
263 }
264
265
266 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
267 {
268         LASSERT(this == cur.text(), return);
269
270         // If there is a selection, record undo before the cursor font is changed.
271         if (cur.selection())
272                 cur.recordUndoSelection();
273
274         // Set the current_font
275         // Determine basis font
276         FontInfo layoutfont;
277         pit_type pit = cur.pit();
278         if (cur.pos() < pars_[pit].beginOfBody())
279                 layoutfont = labelFont(pars_[pit]);
280         else
281                 layoutfont = layoutFont(pit);
282
283         // Update current font
284         cur.real_current_font.update(font,
285                                         cur.buffer()->params().language,
286                                         toggleall);
287
288         // Reduce to implicit settings
289         cur.current_font = cur.real_current_font;
290         cur.current_font.fontInfo().reduce(layoutfont);
291         // And resolve it completely
292         cur.real_current_font.fontInfo().realize(layoutfont);
293
294         // if there is no selection that's all we need to do
295         if (!cur.selection())
296                 return;
297
298         // Ok, we have a selection.
299         Font newfont = font;
300
301         if (toggleall) {
302                 // Toggling behaves as follows: We check the first character of the
303                 // selection. If it's (say) got EMPH on, then we set to off; if off,
304                 // then to on. With families and the like, we set it to INHERIT, if
305                 // we already have it.
306                 CursorSlice const & sl = cur.selBegin();
307                 Text const & text = *sl.text();
308                 Paragraph const & par = text.getPar(sl.pit());
309
310                 // get font at the position
311                 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
312                         text.outerFont(sl.pit()));
313                 FontInfo const & oldfi = oldfont.fontInfo();
314
315                 FontInfo & newfi = newfont.fontInfo();
316
317                 FontFamily newfam = newfi.family();
318                 if (newfam !=   INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
319                                 newfam == oldfi.family())
320                         newfi.setFamily(INHERIT_FAMILY);
321
322                 FontSeries newser = newfi.series();
323                 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
324                         newfi.setSeries(INHERIT_SERIES);
325
326                 FontShape newshp = newfi.shape();
327                 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
328                                 newshp == oldfi.shape())
329                         newfi.setShape(INHERIT_SHAPE);
330
331                 ColorCode newcol = newfi.color();
332                 if (newcol != Color_none && newcol != Color_inherit
333                     && newcol != Color_ignore && newcol == oldfi.color())
334                         newfi.setColor(Color_none);
335
336                 // ON/OFF ones
337                 if (newfi.emph() == FONT_TOGGLE)
338                         newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
339                 if (newfi.underbar() == FONT_TOGGLE)
340                         newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
341                 if (newfi.strikeout() == FONT_TOGGLE)
342                         newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
343                 if (newfi.xout() == FONT_TOGGLE)
344                         newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
345                 if (newfi.uuline() == FONT_TOGGLE)
346                         newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
347                 if (newfi.uwave() == FONT_TOGGLE)
348                         newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
349                 if (newfi.noun() == FONT_TOGGLE)
350                         newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
351                 if (newfi.number() == FONT_TOGGLE)
352                         newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
353                 if (newfi.nospellcheck() == FONT_TOGGLE)
354                         newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
355         }
356
357         setFont(cur.bv(), cur.selectionBegin().top(),
358                 cur.selectionEnd().top(), newfont);
359 }
360
361
362 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
363                 CursorSlice const & end, Font const & font)
364 {
365         Buffer const & buffer = bv.buffer();
366
367         // Don't use forwardChar here as ditend might have
368         // pos() == lastpos() and forwardChar would miss it.
369         // Can't use forwardPos either as this descends into
370         // nested insets.
371         Language const * language = buffer.params().language;
372         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
373                 if (dit.pos() == dit.lastpos())
374                         continue;
375                 pit_type const pit = dit.pit();
376                 pos_type const pos = dit.pos();
377                 Inset * inset = pars_[pit].getInset(pos);
378                 if (inset && inset->resetFontEdit()) {
379                         // We need to propagate the font change to all
380                         // text cells of the inset (bugs 1973, 6919).
381                         setInsetFont(bv, pit, pos, font);
382                 }
383                 TextMetrics const & tm = bv.textMetrics(this);
384                 Font f = tm.displayFont(pit, pos);
385                 f.update(font, language);
386                 setCharFont(pit, pos, f, tm.font_);
387                 // font change may change language...
388                 // spell checker has to know that
389                 pars_[pit].requestSpellCheck(pos);
390         }
391 }
392
393
394 bool Text::cursorTop(Cursor & cur)
395 {
396         LBUFERR(this == cur.text());
397         return setCursor(cur, 0, 0);
398 }
399
400
401 bool Text::cursorBottom(Cursor & cur)
402 {
403         LBUFERR(this == cur.text());
404         return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
405 }
406
407
408 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
409 {
410         LBUFERR(this == cur.text());
411         // If the mask is completely neutral, tell user
412         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
413                 // Could only happen with user style
414                 cur.message(_("No font change defined."));
415                 return;
416         }
417
418         // Try implicit word selection
419         // If there is a change in the language the implicit word selection
420         // is disabled.
421         CursorSlice const resetCursor = cur.top();
422         bool const implicitSelection =
423                 font.language() == ignore_language
424                 && font.fontInfo().number() == FONT_IGNORE
425                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
426
427         // Set font
428         setFont(cur, font, toggleall);
429
430         // Implicit selections are cleared afterwards
431         // and cursor is set to the original position.
432         if (implicitSelection) {
433                 cur.clearSelection();
434                 cur.top() = resetCursor;
435                 cur.resetAnchor();
436         }
437 }
438
439
440 docstring Text::getStringForDialog(Cursor & cur)
441 {
442         LBUFERR(this == cur.text());
443
444         if (cur.selection())
445                 return cur.selectionAsString(false);
446
447         // Try implicit word selection. If there is a change
448         // in the language the implicit word selection is
449         // disabled.
450         selectWordWhenUnderCursor(cur, WHOLE_WORD);
451         docstring const & retval = cur.selectionAsString(false);
452         cur.clearSelection();
453         return retval;
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 const & 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 pit, pos_type pos,
551                         bool setfont, bool boundary)
552 {
553         TextMetrics const & tm = cur.bv().textMetrics(this);
554         bool const update_needed = !tm.contains(pit);
555         Cursor old = cur;
556         setCursorIntern(cur, pit, pos, setfont, boundary);
557         return cur.bv().checkDepm(cur, old) || update_needed;
558 }
559
560
561 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
562                            bool setfont, bool boundary)
563 {
564         LBUFERR(this == cur.text());
565         cur.boundary(boundary);
566         cur.top().setPitPos(pit, pos);
567         if (setfont)
568                 cur.setCurrentFont();
569 }
570
571
572 bool Text::checkAndActivateInset(Cursor & cur, bool front)
573 {
574         if (front && cur.pos() == cur.lastpos())
575                 return false;
576         if (!front && cur.pos() == 0)
577                 return false;
578         Inset * inset = front ? cur.nextInset() : cur.prevInset();
579         if (!inset || !inset->editable())
580                 return false;
581         if (cur.selection() && cur.realAnchor().find(inset) == -1)
582                 return false;
583         /*
584          * Apparently, when entering an inset we are expected to be positioned
585          * *before* it in the containing paragraph, regardless of the direction
586          * from which we are entering. Otherwise, cursor placement goes awry,
587          * and when we exit from the beginning, we'll be placed *after* the
588          * inset.
589          */
590         if (!front)
591                 --cur.pos();
592         inset->edit(cur, front);
593         cur.setCurrentFont();
594         cur.boundary(false);
595         return true;
596 }
597
598
599 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
600 {
601         if (cur.pos() == -1)
602                 return false;
603         if (cur.pos() == cur.lastpos())
604                 return false;
605         Paragraph & par = cur.paragraph();
606         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
607         if (!inset || !inset->editable())
608                 return false;
609         if (cur.selection() && cur.realAnchor().find(inset) == -1)
610                 return false;
611         inset->edit(cur, movingForward,
612                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
613         cur.setCurrentFont();
614         cur.boundary(false);
615         return true;
616 }
617
618
619 bool Text::cursorBackward(Cursor & cur)
620 {
621         // Tell BufferView to test for FitCursor in any case!
622         cur.screenUpdateFlags(Update::FitCursor);
623
624         // not at paragraph start?
625         if (cur.pos() > 0) {
626                 // if on right side of boundary (i.e. not at paragraph end, but line end)
627                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
628                 // there are some exceptions to ignore this: lineseps, newlines, spaces
629 #if 0
630                 // some effectless debug code to see the values in the debugger
631                 bool bound = cur.boundary();
632                 int rowpos = cur.textRow().pos();
633                 int pos = cur.pos();
634                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
635                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
636                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
637 #endif
638                 if (!cur.boundary() &&
639                                 cur.textRow().pos() == cur.pos() &&
640                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
641                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
642                                 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
643                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
644                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
645                 }
646
647                 // go left and try to enter inset
648                 if (checkAndActivateInset(cur, false))
649                         return false;
650
651                 // normal character left
652                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
653         }
654
655         // move to the previous paragraph or do nothing
656         if (cur.pit() > 0) {
657                 Paragraph & par = getPar(cur.pit() - 1);
658                 pos_type lastpos = par.size();
659                 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
660                         return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
661                 else
662                         return setCursor(cur, cur.pit() - 1, lastpos, true, false);
663         }
664         return false;
665 }
666
667
668 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
669 {
670         Cursor temp_cur = cur;
671         temp_cur.posVisLeft(skip_inset);
672         if (temp_cur.depth() > cur.depth()) {
673                 cur = temp_cur;
674                 return false;
675         }
676         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
677                 true, temp_cur.boundary());
678 }
679
680
681 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
682 {
683         Cursor temp_cur = cur;
684         temp_cur.posVisRight(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::cursorForward(Cursor & cur)
695 {
696         // Tell BufferView to test for FitCursor in any case!
697         cur.screenUpdateFlags(Update::FitCursor);
698
699         // not at paragraph end?
700         if (cur.pos() != cur.lastpos()) {
701                 // in front of editable inset, i.e. jump into it?
702                 if (checkAndActivateInset(cur, true))
703                         return false;
704
705                 TextMetrics const & tm = cur.bv().textMetrics(this);
706                 // if left of boundary -> just jump to right side
707                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
708                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
709                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
710
711                 // next position is left of boundary,
712                 // but go to next line for special cases like space, newline, linesep
713 #if 0
714                 // some effectless debug code to see the values in the debugger
715                 int endpos = cur.textRow().endpos();
716                 int lastpos = cur.lastpos();
717                 int pos = cur.pos();
718                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
719                 bool newline = cur.paragraph().isNewline(cur.pos());
720                 bool sep = cur.paragraph().isSeparator(cur.pos());
721                 if (cur.pos() != cur.lastpos()) {
722                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
723                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
724                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
725                 }
726 #endif
727                 if (cur.textRow().endpos() == cur.pos() + 1) {
728                         if (cur.paragraph().isEnvSeparator(cur.pos()) &&
729                             cur.pos() + 1 == cur.lastpos() &&
730                             cur.pit() != cur.lastpit()) {
731                                 // move to next paragraph
732                                 return setCursor(cur, cur.pit() + 1, 0, true, false);
733                         } else if (cur.textRow().endpos() != cur.lastpos() &&
734                                    !cur.paragraph().isNewline(cur.pos()) &&
735                                    !cur.paragraph().isEnvSeparator(cur.pos()) &&
736                                    !cur.paragraph().isLineSeparator(cur.pos()) &&
737                                    !cur.paragraph().isSeparator(cur.pos())) {
738                                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
739                         }
740                 }
741
742                 // in front of RTL boundary? Stay on this side of the boundary because:
743                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
744                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
745                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
746
747                 // move right
748                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
749         }
750
751         // move to next paragraph
752         if (cur.pit() != cur.lastpit())
753                 return setCursor(cur, cur.pit() + 1, 0, true, false);
754         return false;
755 }
756
757
758 bool Text::cursorUpParagraph(Cursor & cur)
759 {
760         bool updated = false;
761         if (cur.pos() > 0)
762                 updated = setCursor(cur, cur.pit(), 0);
763         else if (cur.pit() != 0)
764                 updated = setCursor(cur, cur.pit() - 1, 0);
765         return updated;
766 }
767
768
769 bool Text::cursorDownParagraph(Cursor & cur)
770 {
771         bool updated = false;
772         if (cur.pit() != cur.lastpit())
773                 if (lyxrc.mac_like_cursor_movement)
774                         if (cur.pos() == cur.lastpos())
775                                 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
776                         else
777                                 updated = setCursor(cur, cur.pit(), cur.lastpos());
778                 else
779                         updated = setCursor(cur, cur.pit() + 1, 0);
780         else
781                 updated = setCursor(cur, cur.pit(), cur.lastpos());
782         return updated;
783 }
784
785 namespace {
786
787 /** delete num_spaces characters between from and to. Return the
788  * number of spaces that got physically deleted (not marked as
789  * deleted) */
790 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
791                                   int num_spaces, bool const trackChanges)
792 {
793         if (num_spaces <= 0)
794                 return 0;
795
796         // First, delete spaces marked as inserted
797         int pos = from;
798         while (pos < to && num_spaces > 0) {
799                 Change const & change = par.lookupChange(pos);
800                 if (change.inserted() && change.currentAuthor()) {
801                         par.eraseChar(pos, trackChanges);
802                         --num_spaces;
803                         --to;
804                 } else
805                         ++pos;
806         }
807
808         // Then remove remaining spaces
809         int const psize = par.size();
810         par.eraseChars(from, from + num_spaces, trackChanges);
811         return psize - par.size();
812 }
813
814 }
815
816
817 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
818                 Cursor & old, bool & need_anchor_change)
819 {
820         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
821
822         Paragraph & oldpar = old.paragraph();
823         bool const trackChanges = cur.buffer()->params().track_changes;
824         bool result = false;
825
826         // We do nothing if cursor did not move
827         if (cur.top() == old.top())
828                 return false;
829
830         // We do not do anything on read-only documents
831         if (cur.buffer()->isReadonly())
832                 return false;
833
834         // We allow all kinds of "mumbo-jumbo" when freespacing.
835         if (oldpar.isFreeSpacing())
836                 return false;
837
838         // Whether a common inset is found and whether the cursor is still in
839         // the same paragraph (possibly nested).
840         int const depth = cur.find(&old.inset());
841         bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
842                 && old.pit() == cur[depth].pit();
843
844         /*
845          * (1) If the chars around the old cursor were spaces, delete some of
846          * them, but only if the cursor has really moved.
847          */
848
849         /* There are still some small problems that can lead to
850            double spaces stored in the document file or space at
851            the beginning of paragraphs(). This happens if you have
852            the cursor between two spaces and then save. Or if you
853            cut and paste and the selection has a space at the
854            beginning and then save right after the paste. (Lgb)
855         */
856
857         // find range of spaces around cursors
858         pos_type from = old.pos();
859         while (from > 0
860                    && oldpar.isLineSeparator(from - 1)
861                    && !oldpar.isDeleted(from - 1))
862                 --from;
863         pos_type to = old.pos();
864         while (to < old.lastpos()
865                    && oldpar.isLineSeparator(to)
866                    && !oldpar.isDeleted(to))
867                 ++to;
868
869         int num_spaces = to - from;
870         // If we are not at the start of the paragraph, keep one space
871         if (from != to && from > 0)
872                 --num_spaces;
873
874         // If cursor is inside range, keep one additional space
875         if (same_par && cur.pos() > from && cur.pos() < to)
876                 --num_spaces;
877
878         // Remove spaces and adapt cursor.
879         if (num_spaces > 0) {
880                 old.recordUndo();
881                 int const deleted =
882                         deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
883                 // correct cur position
884                 // FIXME: there can be other cursors pointing there, we should update them
885                 if (same_par) {
886                         if (cur[depth].pos() >= to)
887                                 cur[depth].pos() -= deleted;
888                         else if (cur[depth].pos() > from)
889                                 cur[depth].pos() = min(from + 1, old.lastpos());
890                         need_anchor_change = true;
891                 }
892                 result = true;
893         }
894
895         /*
896          * (2) If the paragraph where the cursor was is empty, delete it
897          */
898
899         // only do our other magic if we changed paragraph
900         if (same_par)
901                 return result;
902
903         // only do our magic if the paragraph is empty
904         if (!oldpar.empty())
905                 return result;
906
907         // don't delete anything if this is the ONLY paragraph!
908         if (old.lastpit() == 0)
909                 return result;
910
911         // Do not delete empty paragraphs with keepempty set.
912         if (oldpar.allowEmpty())
913                 return result;
914
915         // Delete old par.
916         old.recordUndo(max(old.pit() - 1, pit_type(0)),
917                        min(old.pit() + 1, old.lastpit()));
918         ParagraphList & plist = old.text()->paragraphs();
919         bool const soa = oldpar.params().startOfAppendix();
920         plist.erase(lyx::next(plist.begin(), old.pit()));
921         // do not lose start of appendix marker (bug 4212)
922         if (soa && old.pit() < pit_type(plist.size()))
923                 plist[old.pit()].params().startOfAppendix(true);
924
925         // see #warning (FIXME?) above
926         if (cur.depth() >= old.depth()) {
927                 CursorSlice & curslice = cur[old.depth() - 1];
928                 if (&curslice.inset() == &old.inset()
929                     && curslice.pit() > old.pit()) {
930                         --curslice.pit();
931                         // since a paragraph has been deleted, all the
932                         // insets after `old' have been copied and
933                         // their address has changed. Therefore we
934                         // need to `regenerate' cur. (JMarc)
935                         cur.updateInsets(&(cur.bottom().inset()));
936                         need_anchor_change = true;
937                 }
938         }
939
940         return true;
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                 pos_type from = 0;
956                 while (from < par.size()) {
957                         // skip non-spaces
958                         while (from < par.size()
959                                && (!par.isLineSeparator(from) || par.isDeleted(from)))
960                                 ++from;
961                         // find string of spaces
962                         pos_type to = from;
963                         while (to < par.size()
964                                && par.isLineSeparator(to) && !par.isDeleted(to))
965                                 ++to;
966                         // empty? We are done
967                         if (from == to)
968                                 break;
969
970                         int num_spaces = to - from;
971
972                         // If we are not at the extremity of the paragraph, keep one space
973                         if (from != to && from > 0 && to < par.size())
974                                 --num_spaces;
975
976                         // Remove spaces if needed
977                         int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
978                         from = to - deleted;
979                 }
980
981                 // don't delete anything if this is the only remaining paragraph
982                 // within the given range. Note: Text::acceptOrRejectChanges()
983                 // sets the cursor to 'first' after calling DEPM
984                 if (first == last)
985                         continue;
986
987                 // don't delete empty paragraphs with keepempty set
988                 if (par.allowEmpty())
989                         continue;
990
991                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
992                         pars_.erase(lyx::next(pars_.begin(), pit));
993                         --pit;
994                         --last;
995                         continue;
996                 }
997         }
998 }
999
1000
1001 } // namespace lyx