3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "ErrorList.h"
40 #include "Paragraph.h"
41 #include "ParagraphParameters.h"
42 #include "TextClass.h"
43 #include "TextMetrics.h"
45 #include "insets/InsetCollapsible.h"
47 #include "mathed/InsetMathHull.h"
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"
61 bool Text::isMainText() const
63 return &owner_->buffer().text() == this;
67 // Note that this is supposed to return a fully realized font.
68 FontInfo Text::layoutFont(pit_type const pit) const
70 Layout const & layout = pars_[pit].layout();
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();
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());
91 // Note that this is supposed to return a fully realized font.
92 FontInfo Text::labelFont(Paragraph const & par) const
94 Buffer const & buffer = owner_->buffer();
95 Layout const & layout = par.layout();
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());
105 FontInfo font = layout.labelfont;
106 // Realize with the fonts of lesser depth.
107 font.realize(buffer.params().getFont().fontInfo());
113 void Text::setCharFont(pit_type pit,
114 pos_type pos, Font const & fnt, Font const & display_font)
116 Buffer const & buffer = owner_->buffer();
118 Layout const & layout = pars_[pit].layout();
120 // Get concrete layout font to reduce against
123 if (pos < pars_[pit].beginOfBody())
124 layoutfont = layout.labelfont;
126 layoutfont = layout.font;
128 // Realize against environment font information
129 if (pars_[pit].getDepth()) {
131 while (!layoutfont.resolved() &&
132 tp != pit_type(paragraphs().size()) &&
133 pars_[tp].getDepth()) {
135 if (tp != pit_type(paragraphs().size()))
136 layoutfont.realize(pars_[tp].layout().font);
140 // Inside inset, apply the inset's font attributes if any
143 layoutfont.realize(display_font.fontInfo());
145 layoutfont.realize(buffer.params().getFont().fontInfo());
147 // Now, reduce font against full layout font
148 font.fontInfo().reduce(layoutfont);
150 pars_[pit].setFont(pos, font);
154 void Text::setInsetFont(BufferView const & bv, pit_type pit,
155 pos_type pos, Font const & font)
157 Inset * const inset = pars_[pit].getInset(pos);
158 LASSERT(inset && inset->resetFontEdit(), return);
160 CursorSlice::idx_type endidx = inset->nargs();
161 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
162 Text * text = cs.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);
174 void Text::setLayout(pit_type start, pit_type end,
175 docstring const & layout)
177 LASSERT(start != end, return);
179 Buffer const & buffer = owner_->buffer();
180 BufferParams const & bp = buffer.params();
181 Layout const & lyxlayout = bp.documentClass()[layout];
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));
192 // set layout over selection and make a total rebreak of those paragraphs
193 void Text::setLayout(Cursor & cur, docstring const & layout)
195 LBUFERR(this == cur.text());
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();
206 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
207 Paragraph const & par, int max_depth)
209 int const depth = par.params().depth();
210 if (type == Text::INC_DEPTH && depth < max_depth)
212 if (type == Text::DEC_DEPTH && depth > 0)
218 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
220 LBUFERR(this == cur.text());
221 // this happens when selecting several cells in tabular (bug 2630)
222 if (cur.selBegin().idx() != cur.selEnd().idx())
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);
229 for (pit_type pit = beg; pit != end; ++pit) {
230 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
232 max_depth = pars_[pit].getMaxDepthAfter();
238 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
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);
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);
253 par.params().depth(depth - 1);
255 max_depth = par.getMaxDepthAfter();
257 // this handles the counter labels, and also fixes up
258 // depth values for follow-on (child) paragraphs
259 cur.forceBufferUpdate();
263 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
265 LASSERT(this == cur.text(), return);
267 // If there is a selection, record undo before the cursor font is changed.
269 cur.recordUndoSelection();
271 // Set the current_font
272 // Determine basis font
274 pit_type pit = cur.pit();
275 if (cur.pos() < pars_[pit].beginOfBody())
276 layoutfont = labelFont(pars_[pit]);
278 layoutfont = layoutFont(pit);
280 // Update current font
281 cur.real_current_font.update(font,
282 cur.buffer()->params().language,
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);
291 // if there is no selection that's all we need to do
292 if (!cur.selection())
295 // Ok, we have a selection.
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());
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();
312 FontInfo & newfi = newfont.fontInfo();
314 FontFamily newfam = newfi.family();
315 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
316 newfam == oldfi.family())
317 newfi.setFamily(INHERIT_FAMILY);
319 FontSeries newser = newfi.series();
320 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
321 newfi.setSeries(INHERIT_SERIES);
323 FontShape newshp = newfi.shape();
324 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
325 newshp == oldfi.shape())
326 newfi.setShape(INHERIT_SHAPE);
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);
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.xout() == FONT_TOGGLE)
341 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.uuline() == FONT_TOGGLE)
343 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.uwave() == FONT_TOGGLE)
345 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.noun() == FONT_TOGGLE)
347 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
348 if (newfi.number() == FONT_TOGGLE)
349 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
350 if (newfi.nospellcheck() == FONT_TOGGLE)
351 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
354 setFont(cur.bv(), cur.selectionBegin().top(),
355 cur.selectionEnd().top(), newfont);
359 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
360 CursorSlice const & end, Font const & font)
362 Buffer const & buffer = bv.buffer();
364 // Don't use forwardChar here as ditend might have
365 // pos() == lastpos() and forwardChar would miss it.
366 // Can't use forwardPos either as this descends into
368 Language const * language = buffer.params().language;
369 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
370 if (dit.pos() == dit.lastpos())
372 pit_type const pit = dit.pit();
373 pos_type const pos = dit.pos();
374 Inset * inset = pars_[pit].getInset(pos);
375 if (inset && inset->resetFontEdit()) {
376 // We need to propagate the font change to all
377 // text cells of the inset (bugs 1973, 6919).
378 setInsetFont(bv, pit, pos, font);
380 TextMetrics const & tm = bv.textMetrics(this);
381 Font f = tm.displayFont(pit, pos);
382 f.update(font, language);
383 setCharFont(pit, pos, f, tm.font_);
384 // font change may change language...
385 // spell checker has to know that
386 pars_[pit].requestSpellCheck(pos);
391 bool Text::cursorTop(Cursor & cur)
393 LBUFERR(this == cur.text());
394 return setCursor(cur, 0, 0);
398 bool Text::cursorBottom(Cursor & cur)
400 LBUFERR(this == cur.text());
401 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
405 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
407 LBUFERR(this == cur.text());
408 // If the mask is completely neutral, tell user
409 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
410 // Could only happen with user style
411 cur.message(_("No font change defined."));
415 // Try implicit word selection
416 // If there is a change in the language the implicit word selection
418 CursorSlice const resetCursor = cur.top();
419 bool const implicitSelection =
420 font.language() == ignore_language
421 && font.fontInfo().number() == FONT_IGNORE
422 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
425 setFont(cur, font, toggleall);
427 // Implicit selections are cleared afterwards
428 // and cursor is set to the original position.
429 if (implicitSelection) {
430 cur.clearSelection();
431 cur.top() = resetCursor;
437 docstring Text::getStringForDialog(Cursor & cur)
439 LBUFERR(this == cur.text());
442 return cur.selectionAsString(false);
444 // Try implicit word selection. If there is a change
445 // in the language the implicit word selection is
447 selectWordWhenUnderCursor(cur, WHOLE_WORD);
448 docstring const & retval = cur.selectionAsString(false);
449 cur.clearSelection();
454 void Text::setLabelWidthStringToSequence(Cursor const & cur,
458 // Find first of same layout in sequence
459 while (!isFirstInSequence(c.pit())) {
460 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
463 // now apply label width string to every par
465 depth_type const depth = c.paragraph().getDepth();
466 Layout const & layout = c.paragraph().layout();
467 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
468 while (c.paragraph().getDepth() > depth) {
470 if (c.pit() > c.lastpit())
473 if (c.paragraph().getDepth() < depth)
475 if (c.paragraph().layout() != layout)
478 c.paragraph().setLabelWidthString(s);
483 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
488 string const argument = to_utf8(arg);
489 depth_type priordepth = -1;
492 c.setCursor(cur.selectionBegin());
493 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
494 Paragraph & par = c.paragraph();
495 ParagraphParameters params = par.params();
496 params.read(argument, merge);
497 // Changes to label width string apply to all paragraphs
498 // with same layout in a sequence.
499 // Do this only once for a selected range of paragraphs
500 // of the same layout and depth.
502 par.params().apply(params, par.layout());
503 if (par.getDepth() != priordepth || par.layout() != priorlayout)
504 setLabelWidthStringToSequence(c, params.labelWidthString());
505 priordepth = par.getDepth();
506 priorlayout = par.layout();
511 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
515 depth_type priordepth = -1;
518 c.setCursor(cur.selectionBegin());
519 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
520 Paragraph & par = c.paragraph();
521 // Changes to label width string apply to all paragraphs
522 // with same layout in a sequence.
523 // Do this only once for a selected range of paragraphs
524 // of the same layout and depth.
526 par.params().apply(p, par.layout());
527 if (par.getDepth() != priordepth || par.layout() != priorlayout)
528 setLabelWidthStringToSequence(c,
529 par.params().labelWidthString());
530 priordepth = par.getDepth();
531 priorlayout = par.layout();
536 // this really should just insert the inset and not move the cursor.
537 void Text::insertInset(Cursor & cur, Inset * inset)
539 LBUFERR(this == cur.text());
541 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
542 Change(cur.buffer()->params().track_changes
543 ? Change::INSERTED : Change::UNCHANGED));
547 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
548 bool setfont, bool boundary)
550 TextMetrics const & tm = cur.bv().textMetrics(this);
551 bool const update_needed = !tm.contains(pit);
553 setCursorIntern(cur, pit, pos, setfont, boundary);
554 return cur.bv().checkDepm(cur, old) || update_needed;
558 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
559 bool setfont, bool boundary)
561 LBUFERR(this == cur.text());
562 cur.boundary(boundary);
563 cur.top().setPitPos(pit, pos);
565 cur.setCurrentFont();
569 bool Text::checkAndActivateInset(Cursor & cur, bool front)
571 if (front && cur.pos() == cur.lastpos())
573 if (!front && cur.pos() == 0)
575 Inset * inset = front ? cur.nextInset() : cur.prevInset();
576 if (!inset || !inset->editable())
578 if (cur.selection() && cur.realAnchor().find(inset) == -1)
581 * Apparently, when entering an inset we are expected to be positioned
582 * *before* it in the containing paragraph, regardless of the direction
583 * from which we are entering. Otherwise, cursor placement goes awry,
584 * and when we exit from the beginning, we'll be placed *after* the
589 inset->edit(cur, front);
590 cur.setCurrentFont();
596 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
600 if (cur.pos() == cur.lastpos())
602 Paragraph & par = cur.paragraph();
603 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
604 if (!inset || !inset->editable())
606 if (cur.selection() && cur.realAnchor().find(inset) == -1)
608 inset->edit(cur, movingForward,
609 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
610 cur.setCurrentFont();
616 bool Text::cursorBackward(Cursor & cur)
618 // Tell BufferView to test for FitCursor in any case!
619 cur.screenUpdateFlags(Update::FitCursor);
621 // not at paragraph start?
623 // if on right side of boundary (i.e. not at paragraph end, but line end)
624 // -> skip it, i.e. set boundary to true, i.e. go only logically left
625 // there are some exceptions to ignore this: lineseps, newlines, spaces
627 // some effectless debug code to see the values in the debugger
628 bool bound = cur.boundary();
629 int rowpos = cur.textRow().pos();
631 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
632 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
633 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
635 if (!cur.boundary() &&
636 cur.textRow().pos() == cur.pos() &&
637 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
638 !cur.paragraph().isNewline(cur.pos() - 1) &&
639 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
640 !cur.paragraph().isSeparator(cur.pos() - 1)) {
641 return setCursor(cur, cur.pit(), cur.pos(), true, true);
644 // go left and try to enter inset
645 if (checkAndActivateInset(cur, false))
648 // normal character left
649 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
652 // move to the previous paragraph or do nothing
654 Paragraph & par = getPar(cur.pit() - 1);
655 pos_type lastpos = par.size();
656 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
657 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
659 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
665 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
667 Cursor temp_cur = cur;
668 temp_cur.posVisLeft(skip_inset);
669 if (temp_cur.depth() > cur.depth()) {
673 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
674 true, temp_cur.boundary());
678 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
680 Cursor temp_cur = cur;
681 temp_cur.posVisRight(skip_inset);
682 if (temp_cur.depth() > cur.depth()) {
686 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
687 true, temp_cur.boundary());
691 bool Text::cursorForward(Cursor & cur)
693 // Tell BufferView to test for FitCursor in any case!
694 cur.screenUpdateFlags(Update::FitCursor);
696 // not at paragraph end?
697 if (cur.pos() != cur.lastpos()) {
698 // in front of editable inset, i.e. jump into it?
699 if (checkAndActivateInset(cur, true))
702 TextMetrics const & tm = cur.bv().textMetrics(this);
703 // if left of boundary -> just jump to right side
704 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
705 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
706 return setCursor(cur, cur.pit(), cur.pos(), true, false);
708 // next position is left of boundary,
709 // but go to next line for special cases like space, newline, linesep
711 // some effectless debug code to see the values in the debugger
712 int endpos = cur.textRow().endpos();
713 int lastpos = cur.lastpos();
715 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
716 bool newline = cur.paragraph().isNewline(cur.pos());
717 bool sep = cur.paragraph().isSeparator(cur.pos());
718 if (cur.pos() != cur.lastpos()) {
719 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
720 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
721 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
724 if (cur.textRow().endpos() == cur.pos() + 1) {
725 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
726 cur.pos() + 1 == cur.lastpos() &&
727 cur.pit() != cur.lastpit()) {
728 // move to next paragraph
729 return setCursor(cur, cur.pit() + 1, 0, true, false);
730 } else if (cur.textRow().endpos() != cur.lastpos() &&
731 !cur.paragraph().isNewline(cur.pos()) &&
732 !cur.paragraph().isEnvSeparator(cur.pos()) &&
733 !cur.paragraph().isLineSeparator(cur.pos()) &&
734 !cur.paragraph().isSeparator(cur.pos())) {
735 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
739 // in front of RTL boundary? Stay on this side of the boundary because:
740 // ab|cDDEEFFghi -> abc|DDEEFFghi
741 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
742 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
745 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
748 // move to next paragraph
749 if (cur.pit() != cur.lastpit())
750 return setCursor(cur, cur.pit() + 1, 0, true, false);
755 bool Text::cursorUpParagraph(Cursor & cur)
757 bool updated = false;
759 updated = setCursor(cur, cur.pit(), 0);
760 else if (cur.pit() != 0)
761 updated = setCursor(cur, cur.pit() - 1, 0);
766 bool Text::cursorDownParagraph(Cursor & cur)
768 bool updated = false;
769 if (cur.pit() != cur.lastpit())
770 if (lyxrc.mac_like_cursor_movement)
771 if (cur.pos() == cur.lastpos())
772 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
774 updated = setCursor(cur, cur.pit(), cur.lastpos());
776 updated = setCursor(cur, cur.pit() + 1, 0);
778 updated = setCursor(cur, cur.pit(), cur.lastpos());
784 /** delete num_spaces characters between from and to. Return the
785 * number of spaces that got physically deleted (not marked as
787 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
788 int num_spaces, bool const trackChanges)
793 // First, delete spaces marked as inserted
795 while (pos < to && num_spaces > 0) {
796 Change const & change = par.lookupChange(pos);
797 if (change.inserted() && change.currentAuthor()) {
798 par.eraseChar(pos, trackChanges);
805 // Then remove remaining spaces
806 int const psize = par.size();
807 par.eraseChars(from, from + num_spaces, trackChanges);
808 return psize - par.size();
814 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
815 Cursor & old, bool & need_anchor_change)
817 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
819 Paragraph & oldpar = old.paragraph();
820 bool const trackChanges = cur.buffer()->params().track_changes;
822 // We do not do anything on read-only documents
823 if (cur.buffer()->isReadonly())
826 // We allow all kinds of "mumbo-jumbo" when freespacing.
827 if (oldpar.isFreeSpacing())
830 /* Ok I'll put some comments here about what is missing.
831 There are still some small problems that can lead to
832 double spaces stored in the document file or space at
833 the beginning of paragraphs(). This happens if you have
834 the cursor between two spaces and then save. Or if you
835 cut and paste and the selection has a space at the
836 beginning and then save right after the paste. (Lgb)
839 // If old.pos() == 0 and old.pos()(1) == LineSeparator
840 // delete the LineSeparator.
843 // If old.pos() == 1 and old.pos()(0) == LineSeparator
844 // delete the LineSeparator.
847 // Find a common inset and the corresponding depth.
849 for (; depth < cur.depth(); ++depth)
850 if (&old.inset() == &cur[depth].inset())
853 // Whether a common inset is found and whether the cursor is still in
854 // the same paragraph (possibly nested).
855 bool const same_par = depth < cur.depth() && old.idx() == cur[depth].idx()
856 && old.pit() == cur[depth].pit();
857 bool const same_par_pos = depth == cur.depth() - 1 && same_par
858 && old.pos() == cur[depth].pos();
860 // If the chars around the old cursor were spaces, delete some of
861 // them, but only if the cursor has really moved.
863 // find range of spaces around cursors
864 pos_type from = old.pos();
866 && oldpar.isLineSeparator(from - 1)
867 && !oldpar.isDeleted(from - 1))
869 pos_type to = old.pos();
870 while (to < old.lastpos()
871 && oldpar.isLineSeparator(to)
872 && !oldpar.isDeleted(to))
875 int num_spaces = to - from;
876 // If we are not at the start of the paragraph, keep one space
877 if (from != to && from > 0)
880 // If cursor is inside range, keep one additional space
881 if (same_par && cur.pos() > from && cur.pos() < to)
884 // Remove spaces and adapt cursor.
885 if (num_spaces > 0) {
888 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
889 // correct cur position
890 // FIXME: there can be other cursors pointing there, we should update them
892 if (cur[depth].pos() >= to)
893 cur[depth].pos() -= deleted;
894 else if (cur[depth].pos() > from)
895 cur[depth].pos() = min(from + 1, old.lastpos());
896 need_anchor_change = true;
902 // only do our other magic if we changed paragraph
906 // don't delete anything if this is the ONLY paragraph!
907 if (old.lastpit() == 0)
910 // Do not delete empty paragraphs with keepempty set.
911 if (oldpar.allowEmpty())
914 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
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);
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()) {
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;
942 if (oldpar.stripLeadingSpaces(trackChanges)) {
943 need_anchor_change = true;
944 // We return true here because the Paragraph contents changed and
945 // we need a redraw before further action is processed.
953 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
955 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
957 for (pit_type pit = first; pit <= last; ++pit) {
958 Paragraph & par = pars_[pit];
960 // We allow all kinds of "mumbo-jumbo" when freespacing.
961 if (par.isFreeSpacing())
965 while (from < par.size()) {
967 while (from < par.size()
968 && (!par.isLineSeparator(from) || par.isDeleted(from)))
970 // find string of spaces
972 while (to < par.size()
973 && par.isLineSeparator(to) && !par.isDeleted(to))
975 // empty? We are done
979 int num_spaces = to - from;
981 // If we are not at the extremity of the paragraph, keep one space
982 if (from != to && from > 0 && to < par.size())
985 // Remove spaces if needed
986 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
990 // don't delete anything if this is the only remaining paragraph
991 // within the given range. Note: Text::acceptOrRejectChanges()
992 // sets the cursor to 'first' after calling DEPM
996 // don't delete empty paragraphs with keepempty set
997 if (par.allowEmpty())
1000 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1001 pars_.erase(lyx::next(pars_.begin(), pit));
1007 par.stripLeadingSpaces(trackChanges);