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 "BufferParams.h"
27 #include "BufferView.h"
33 #include "Paragraph.h"
34 #include "ParagraphParameters.h"
35 #include "TextClass.h"
36 #include "TextMetrics.h"
38 #include "insets/InsetText.h"
40 #include "support/lassert.h"
41 #include "support/gettext.h"
49 bool Text::isMainText() const
51 return &owner_->buffer().text() == this;
55 // Note that this is supposed to return a fully realized font.
56 FontInfo Text::layoutFont(pit_type const pit) const
58 Layout const & layout = pars_[pit].layout();
60 if (!pars_[pit].getDepth()) {
61 FontInfo lf = layout.resfont;
62 // In case the default family has been customized
63 if (layout.font.family() == INHERIT_FAMILY)
64 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
65 FontInfo icf = owner_->getLayout().font();
70 FontInfo font = layout.font;
71 // Realize with the fonts of lesser depth.
72 //font.realize(outerFont(pit));
73 font.realize(owner_->buffer().params().getFont().fontInfo());
79 // Note that this is supposed to return a fully realized font.
80 FontInfo Text::labelFont(Paragraph const & par) const
82 Buffer const & buffer = owner_->buffer();
83 Layout const & layout = par.layout();
85 if (!par.getDepth()) {
86 FontInfo lf = layout.reslabelfont;
87 // In case the default family has been customized
88 if (layout.labelfont.family() == INHERIT_FAMILY)
89 lf.setFamily(buffer.params().getFont().fontInfo().family());
93 FontInfo font = layout.labelfont;
94 // Realize with the fonts of lesser depth.
95 font.realize(buffer.params().getFont().fontInfo());
101 void Text::setCharFont(pit_type pit,
102 pos_type pos, Font const & fnt, Font const & display_font)
104 Buffer const & buffer = owner_->buffer();
106 Layout const & layout = pars_[pit].layout();
108 // Get concrete layout font to reduce against
111 if (pos < pars_[pit].beginOfBody())
112 layoutfont = layout.labelfont;
114 layoutfont = layout.font;
116 // Realize against environment font information
117 if (pars_[pit].getDepth()) {
119 while (!layoutfont.resolved() &&
120 tp != pit_type(paragraphs().size()) &&
121 pars_[tp].getDepth()) {
123 if (tp != pit_type(paragraphs().size()))
124 layoutfont.realize(pars_[tp].layout().font);
128 // Inside inset, apply the inset's font attributes if any
131 layoutfont.realize(display_font.fontInfo());
133 layoutfont.realize(buffer.params().getFont().fontInfo());
135 // Now, reduce font against full layout font
136 font.fontInfo().reduce(layoutfont);
138 pars_[pit].setFont(pos, font);
142 void Text::setInsetFont(BufferView const & bv, pit_type pit,
143 pos_type pos, Font const & font)
145 Inset * const inset = pars_[pit].getInset(pos);
146 LASSERT(inset && inset->resetFontEdit(), return);
148 idx_type endidx = inset->nargs();
149 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
150 Text * text = cs.text();
152 // last position of the cell
153 CursorSlice cellend = cs;
154 cellend.pit() = cellend.lastpit();
155 cellend.pos() = cellend.lastpos();
156 text->setFont(bv, cs, cellend, font);
162 void Text::setLayout(pit_type start, pit_type end,
163 docstring const & layout)
165 // FIXME: make this work in multicell selection case
166 LASSERT(start != end, return);
168 Buffer const & buffer = owner_->buffer();
169 BufferParams const & bp = buffer.params();
170 Layout const & lyxlayout = bp.documentClass()[layout];
172 for (pit_type pit = start; pit != end; ++pit) {
173 Paragraph & par = pars_[pit];
174 // Is this a separating paragraph? If so,
175 // this needs to be standard layout
176 bool const is_separator = par.size() == 1
177 && par.isEnvSeparator(0);
178 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
179 if (lyxlayout.margintype == MARGIN_MANUAL)
180 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
183 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
187 // set layout over selection and make a total rebreak of those paragraphs
188 void Text::setLayout(Cursor & cur, docstring const & layout)
190 LBUFERR(this == cur.text());
192 pit_type start = cur.selBegin().pit();
193 pit_type end = cur.selEnd().pit() + 1;
194 cur.recordUndoSelection();
195 setLayout(start, end, layout);
197 cur.setCurrentFont();
198 cur.forceBufferUpdate();
202 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
203 Paragraph const & par, int max_depth)
205 int const depth = par.params().depth();
206 if (type == Text::INC_DEPTH && depth < max_depth)
208 if (type == Text::DEC_DEPTH && depth > 0)
214 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
216 LBUFERR(this == cur.text());
217 // this happens when selecting several cells in tabular (bug 2630)
218 if (cur.selBegin().idx() != cur.selEnd().idx())
221 pit_type const beg = cur.selBegin().pit();
222 pit_type const end = cur.selEnd().pit() + 1;
223 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
225 for (pit_type pit = beg; pit != end; ++pit) {
226 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
228 max_depth = pars_[pit].getMaxDepthAfter();
234 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
236 LBUFERR(this == cur.text());
237 pit_type const beg = cur.selBegin().pit();
238 pit_type const end = cur.selEnd().pit() + 1;
239 cur.recordUndoSelection();
240 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
242 for (pit_type pit = beg; pit != end; ++pit) {
243 Paragraph & par = pars_[pit];
244 if (lyx::changeDepthAllowed(type, par, max_depth)) {
245 int const depth = par.params().depth();
246 if (type == INC_DEPTH)
247 par.params().depth(depth + 1);
249 par.params().depth(depth - 1);
251 max_depth = par.getMaxDepthAfter();
253 // this handles the counter labels, and also fixes up
254 // depth values for follow-on (child) paragraphs
255 cur.forceBufferUpdate();
259 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
261 LASSERT(this == cur.text(), return);
263 // If there is a selection, record undo before the cursor font is changed.
265 cur.recordUndoSelection();
267 // Set the current_font
268 // Determine basis font
270 pit_type pit = cur.pit();
271 if (cur.pos() < pars_[pit].beginOfBody())
272 layoutfont = labelFont(pars_[pit]);
274 layoutfont = layoutFont(pit);
276 // Update current font
277 cur.real_current_font.update(font,
278 cur.buffer()->params().language,
281 // Reduce to implicit settings
282 cur.current_font = cur.real_current_font;
283 cur.current_font.fontInfo().reduce(layoutfont);
284 // And resolve it completely
285 cur.real_current_font.fontInfo().realize(layoutfont);
287 // if there is no selection that's all we need to do
288 if (!cur.selection())
291 // Ok, we have a selection.
295 // Toggling behaves as follows: We check the first character of the
296 // selection. If it's (say) got EMPH on, then we set to off; if off,
297 // then to on. With families and the like, we set it to INHERIT, if
298 // we already have it.
299 CursorSlice const & sl = cur.selBegin();
300 Text const & text = *sl.text();
301 Paragraph const & par = text.getPar(sl.pit());
303 // get font at the position
304 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
305 text.outerFont(sl.pit()));
306 FontInfo const & oldfi = oldfont.fontInfo();
308 FontInfo & newfi = newfont.fontInfo();
310 FontFamily newfam = newfi.family();
311 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
312 newfam == oldfi.family())
313 newfi.setFamily(INHERIT_FAMILY);
315 FontSeries newser = newfi.series();
316 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
317 newfi.setSeries(INHERIT_SERIES);
319 FontShape newshp = newfi.shape();
320 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
321 newshp == oldfi.shape())
322 newfi.setShape(INHERIT_SHAPE);
324 ColorCode newcol = newfi.color();
325 if (newcol != Color_none && newcol != Color_inherit
326 && newcol != Color_ignore && newcol == oldfi.color())
327 newfi.setColor(Color_none);
330 if (newfi.emph() == FONT_TOGGLE)
331 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
332 if (newfi.underbar() == FONT_TOGGLE)
333 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
334 if (newfi.strikeout() == FONT_TOGGLE)
335 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
336 if (newfi.xout() == FONT_TOGGLE)
337 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
338 if (newfi.uuline() == FONT_TOGGLE)
339 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.uwave() == FONT_TOGGLE)
341 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.noun() == FONT_TOGGLE)
343 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.number() == FONT_TOGGLE)
345 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.nospellcheck() == FONT_TOGGLE)
347 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
350 setFont(cur.bv(), cur.selectionBegin().top(),
351 cur.selectionEnd().top(), newfont);
355 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
356 CursorSlice const & end, Font const & font)
358 Buffer const & buffer = bv.buffer();
360 // Don't use forwardChar here as ditend might have
361 // pos() == lastpos() and forwardChar would miss it.
362 // Can't use forwardPos either as this descends into
364 Language const * language = buffer.params().language;
365 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
366 if (dit.pos() == dit.lastpos())
368 pit_type const pit = dit.pit();
369 pos_type const pos = dit.pos();
370 Inset * inset = pars_[pit].getInset(pos);
371 if (inset && inset->resetFontEdit()) {
372 // We need to propagate the font change to all
373 // text cells of the inset (bugs 1973, 6919).
374 setInsetFont(bv, pit, pos, font);
376 TextMetrics const & tm = bv.textMetrics(this);
377 Font f = tm.displayFont(pit, pos);
378 f.update(font, language);
379 setCharFont(pit, pos, f, tm.font_);
380 // font change may change language...
381 // spell checker has to know that
382 pars_[pit].requestSpellCheck(pos);
387 bool Text::cursorTop(Cursor & cur)
389 LBUFERR(this == cur.text());
390 return setCursor(cur, 0, 0);
394 bool Text::cursorBottom(Cursor & cur)
396 LBUFERR(this == cur.text());
397 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
401 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
403 LBUFERR(this == cur.text());
404 // If the mask is completely neutral, tell user
405 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
406 // Could only happen with user style
407 cur.message(_("No font change defined."));
411 // Try implicit word selection
412 // If there is a change in the language the implicit word selection
414 CursorSlice const resetCursor = cur.top();
415 bool const implicitSelection =
416 font.language() == ignore_language
417 && font.fontInfo().number() == FONT_IGNORE
418 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
421 setFont(cur, font, toggleall);
423 // Implicit selections are cleared afterwards
424 // and cursor is set to the original position.
425 if (implicitSelection) {
426 cur.clearSelection();
427 cur.top() = resetCursor;
429 cur.setCurrentFont();
434 docstring Text::getStringForDialog(Cursor & cur)
436 LBUFERR(this == cur.text());
439 return cur.selectionAsString(false);
441 // Try implicit word selection. If there is a change
442 // in the language the implicit word selection is
444 selectWordWhenUnderCursor(cur, WHOLE_WORD);
445 docstring const & retval = cur.selectionAsString(false);
446 cur.clearSelection();
451 void Text::setLabelWidthStringToSequence(Cursor const & cur,
455 // Find first of same layout in sequence
456 while (!isFirstInSequence(c.pit())) {
457 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
460 // now apply label width string to every par
462 depth_type const depth = c.paragraph().getDepth();
463 Layout const & layout = c.paragraph().layout();
464 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
465 while (c.paragraph().getDepth() > depth) {
467 if (c.pit() > c.lastpit())
470 if (c.paragraph().getDepth() < depth)
472 if (c.paragraph().layout() != layout)
475 c.paragraph().setLabelWidthString(s);
480 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
485 string const argument = to_utf8(arg);
486 depth_type priordepth = -1;
489 c.setCursor(cur.selectionBegin());
490 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
491 Paragraph & par = c.paragraph();
492 ParagraphParameters params = par.params();
493 params.read(argument, merge);
494 // Changes to label width string apply to all paragraphs
495 // with same layout in a sequence.
496 // Do this only once for a selected range of paragraphs
497 // of the same layout and depth.
499 par.params().apply(params, par.layout());
500 if (par.getDepth() != priordepth || par.layout() != priorlayout)
501 setLabelWidthStringToSequence(c, params.labelWidthString());
502 priordepth = par.getDepth();
503 priorlayout = par.layout();
508 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
512 depth_type priordepth = -1;
515 c.setCursor(cur.selectionBegin());
516 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
517 Paragraph & par = c.paragraph();
518 // Changes to label width string apply to all paragraphs
519 // with same layout in a sequence.
520 // Do this only once for a selected range of paragraphs
521 // of the same layout and depth.
523 par.params().apply(p, par.layout());
524 if (par.getDepth() != priordepth || par.layout() != priorlayout)
525 setLabelWidthStringToSequence(c,
526 par.params().labelWidthString());
527 priordepth = par.getDepth();
528 priorlayout = par.layout();
533 // this really should just insert the inset and not move the cursor.
534 void Text::insertInset(Cursor & cur, Inset * inset)
536 LBUFERR(this == cur.text());
538 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
539 Change(cur.buffer()->params().track_changes
540 ? Change::INSERTED : Change::UNCHANGED));
544 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
545 bool setfont, bool boundary)
547 TextMetrics const & tm = cur.bv().textMetrics(this);
548 bool const update_needed = !tm.contains(pit);
550 setCursorIntern(cur, pit, pos, setfont, boundary);
551 return cur.bv().checkDepm(cur, old) || update_needed;
555 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
556 bool setfont, bool boundary)
558 LBUFERR(this == cur.text());
559 cur.boundary(boundary);
560 cur.top().setPitPos(pit, pos);
562 cur.setCurrentFont();
566 bool Text::checkAndActivateInset(Cursor & cur, bool front)
568 if (front && cur.pos() == cur.lastpos())
570 if (!front && cur.pos() == 0)
572 Inset * inset = front ? cur.nextInset() : cur.prevInset();
573 if (!inset || !inset->editable())
575 if (cur.selection() && cur.realAnchor().find(inset) == -1)
578 * Apparently, when entering an inset we are expected to be positioned
579 * *before* it in the containing paragraph, regardless of the direction
580 * from which we are entering. Otherwise, cursor placement goes awry,
581 * and when we exit from the beginning, we'll be placed *after* the
586 inset->edit(cur, front);
587 cur.setCurrentFont();
593 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
597 if (cur.pos() == cur.lastpos())
599 Paragraph & par = cur.paragraph();
600 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
601 if (!inset || !inset->editable())
603 if (cur.selection() && cur.realAnchor().find(inset) == -1)
605 inset->edit(cur, movingForward,
606 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
607 cur.setCurrentFont();
613 bool Text::cursorBackward(Cursor & cur)
615 // Tell BufferView to test for FitCursor in any case!
616 cur.screenUpdateFlags(Update::FitCursor);
618 // not at paragraph start?
620 // if on right side of boundary (i.e. not at paragraph end, but line end)
621 // -> skip it, i.e. set boundary to true, i.e. go only logically left
622 // there are some exceptions to ignore this: lineseps, newlines, spaces
624 // some effectless debug code to see the values in the debugger
625 bool bound = cur.boundary();
626 int rowpos = cur.textRow().pos();
628 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
629 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
630 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
632 if (!cur.boundary() &&
633 cur.textRow().pos() == cur.pos() &&
634 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
635 !cur.paragraph().isNewline(cur.pos() - 1) &&
636 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
637 !cur.paragraph().isSeparator(cur.pos() - 1)) {
638 return setCursor(cur, cur.pit(), cur.pos(), true, true);
641 // go left and try to enter inset
642 if (checkAndActivateInset(cur, false))
645 // normal character left
646 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
649 // move to the previous paragraph or do nothing
651 Paragraph & par = getPar(cur.pit() - 1);
652 pos_type lastpos = par.size();
653 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
654 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
656 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
662 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
664 Cursor temp_cur = cur;
665 temp_cur.posVisLeft(skip_inset);
666 if (temp_cur.depth() > cur.depth()) {
670 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
671 true, temp_cur.boundary());
675 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
677 Cursor temp_cur = cur;
678 temp_cur.posVisRight(skip_inset);
679 if (temp_cur.depth() > cur.depth()) {
683 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
684 true, temp_cur.boundary());
688 bool Text::cursorForward(Cursor & cur)
690 // Tell BufferView to test for FitCursor in any case!
691 cur.screenUpdateFlags(Update::FitCursor);
693 // not at paragraph end?
694 if (cur.pos() != cur.lastpos()) {
695 // in front of editable inset, i.e. jump into it?
696 if (checkAndActivateInset(cur, true))
699 TextMetrics const & tm = cur.bv().textMetrics(this);
700 // if left of boundary -> just jump to right side
701 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
702 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
703 return setCursor(cur, cur.pit(), cur.pos(), true, false);
705 // next position is left of boundary,
706 // but go to next line for special cases like space, newline, linesep
708 // some effectless debug code to see the values in the debugger
709 int endpos = cur.textRow().endpos();
710 int lastpos = cur.lastpos();
712 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
713 bool newline = cur.paragraph().isNewline(cur.pos());
714 bool sep = cur.paragraph().isSeparator(cur.pos());
715 if (cur.pos() != cur.lastpos()) {
716 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
717 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
718 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
721 if (cur.textRow().endpos() == cur.pos() + 1) {
722 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
723 cur.pos() + 1 == cur.lastpos() &&
724 cur.pit() != cur.lastpit()) {
725 // move to next paragraph
726 return setCursor(cur, cur.pit() + 1, 0, true, false);
727 } else if (cur.textRow().endpos() != cur.lastpos() &&
728 !cur.paragraph().isNewline(cur.pos()) &&
729 !cur.paragraph().isEnvSeparator(cur.pos()) &&
730 !cur.paragraph().isLineSeparator(cur.pos()) &&
731 !cur.paragraph().isSeparator(cur.pos())) {
732 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
736 // in front of RTL boundary? Stay on this side of the boundary because:
737 // ab|cDDEEFFghi -> abc|DDEEFFghi
738 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
739 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
742 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
745 // move to next paragraph
746 if (cur.pit() != cur.lastpit())
747 return setCursor(cur, cur.pit() + 1, 0, true, false);
752 bool Text::cursorUpParagraph(Cursor & cur)
754 bool updated = false;
756 updated = setCursor(cur, cur.pit(), 0);
757 else if (cur.pit() != 0)
758 updated = setCursor(cur, cur.pit() - 1, 0);
763 bool Text::cursorDownParagraph(Cursor & cur)
765 bool updated = false;
766 if (cur.pit() != cur.lastpit())
767 if (lyxrc.mac_like_cursor_movement)
768 if (cur.pos() == cur.lastpos())
769 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
771 updated = setCursor(cur, cur.pit(), cur.lastpos());
773 updated = setCursor(cur, cur.pit() + 1, 0);
775 updated = setCursor(cur, cur.pit(), cur.lastpos());
781 /** delete num_spaces characters between from and to. Return the
782 * number of spaces that got physically deleted (not marked as
784 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
785 int num_spaces, bool const trackChanges)
790 // First, delete spaces marked as inserted
792 while (pos < to && num_spaces > 0) {
793 Change const & change = par.lookupChange(pos);
794 if (change.inserted() && change.currentAuthor()) {
795 par.eraseChar(pos, trackChanges);
802 // Then remove remaining spaces
803 int const psize = par.size();
804 par.eraseChars(from, from + num_spaces, trackChanges);
805 return psize - par.size();
811 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
812 Cursor & old, bool & need_anchor_change)
814 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
816 Paragraph & oldpar = old.paragraph();
817 bool const trackChanges = cur.buffer()->params().track_changes;
820 // We do nothing if cursor did not move
821 if (cur.top() == old.top())
824 // We do not do anything on read-only documents
825 if (cur.buffer()->isReadonly())
828 // Whether a common inset is found and whether the cursor is still in
829 // the same paragraph (possibly nested).
830 int const depth = cur.find(&old.inset());
831 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
832 && old.pit() == cur[depth].pit();
835 * (1) If the chars around the old cursor were spaces and the
836 * paragraph is not in free spacing mode, delete some of them, but
837 * only if the cursor has really moved.
840 /* There are still some small problems that can lead to
841 double spaces stored in the document file or space at
842 the beginning of paragraphs(). This happens if you have
843 the cursor between two spaces and then save. Or if you
844 cut and paste and the selection has a space at the
845 beginning and then save right after the paste. (Lgb)
847 if (!oldpar.isFreeSpacing()) {
848 // find range of spaces around cursors
849 pos_type from = old.pos();
851 && oldpar.isLineSeparator(from - 1)
852 && !oldpar.isDeleted(from - 1))
854 pos_type to = old.pos();
855 while (to < old.lastpos()
856 && oldpar.isLineSeparator(to)
857 && !oldpar.isDeleted(to))
860 int num_spaces = to - from;
861 // If we are not at the start of the paragraph, keep one space
862 if (from != to && from > 0)
865 // If cursor is inside range, keep one additional space
866 if (same_par && cur.pos() > from && cur.pos() < to)
869 // Remove spaces and adapt cursor.
870 if (num_spaces > 0) {
873 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
874 // correct cur position
875 // FIXME: there can be other cursors pointing there, we should update them
877 if (cur[depth].pos() >= to)
878 cur[depth].pos() -= deleted;
879 else if (cur[depth].pos() > from)
880 cur[depth].pos() = min(from + 1, old.lastpos());
881 need_anchor_change = true;
888 * (2) If the paragraph where the cursor was is empty, delete it
891 // only do our other magic if we changed paragraph
895 // only do our magic if the paragraph is empty
899 // don't delete anything if this is the ONLY paragraph!
900 if (old.lastpit() == 0)
903 // Do not delete empty paragraphs with keepempty set.
904 if (oldpar.allowEmpty())
908 old.recordUndo(max(old.pit() - 1, pit_type(0)),
909 min(old.pit() + 1, old.lastpit()));
910 ParagraphList & plist = old.text()->paragraphs();
911 bool const soa = oldpar.params().startOfAppendix();
912 plist.erase(plist.iterator_at(old.pit()));
913 // do not lose start of appendix marker (bug 4212)
914 if (soa && old.pit() < pit_type(plist.size()))
915 plist[old.pit()].params().startOfAppendix(true);
917 // see #warning (FIXME?) above
918 if (cur.depth() >= old.depth()) {
919 CursorSlice & curslice = cur[old.depth() - 1];
920 if (&curslice.inset() == &old.inset()
921 && curslice.idx() == old.idx()
922 && curslice.pit() > old.pit()) {
924 // since a paragraph has been deleted, all the
925 // insets after `old' have been copied and
926 // their address has changed. Therefore we
927 // need to `regenerate' cur. (JMarc)
928 cur.updateInsets(&(cur.bottom().inset()));
929 need_anchor_change = true;
937 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
939 pos_type last_pos = pars_[last].size() - 1;
940 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
944 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
945 pos_type first_pos, pos_type last_pos,
948 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
950 for (pit_type pit = first; pit <= last; ++pit) {
951 Paragraph & par = pars_[pit];
954 * (1) Delete consecutive spaces
956 if (!par.isFreeSpacing()) {
957 pos_type from = (pit == first) ? first_pos : 0;
958 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
959 while (from < to_pos) {
961 while (from < par.size()
962 && (!par.isLineSeparator(from) || par.isDeleted(from)))
964 // find string of spaces
966 while (to < par.size()
967 && par.isLineSeparator(to) && !par.isDeleted(to))
969 // empty? We are done
973 int num_spaces = to - from;
975 // If we are not at the extremity of the paragraph, keep one space
976 if (from != to && from > 0 && to < par.size())
979 // Remove spaces if needed
980 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
986 * (2) Delete empty pragraphs
989 // don't delete anything if this is the only remaining paragraph
990 // within the given range. Note: Text::acceptOrRejectChanges()
991 // sets the cursor to 'first' after calling DEPM
995 // don't delete empty paragraphs with keepempty set
996 if (par.allowEmpty())
999 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1000 pars_.erase(pars_.iterator_at(pit));