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;
431 cur.setCurrentFont();
435 docstring Text::getStringForDialog(Cursor & cur)
437 LBUFERR(this == cur.text());
440 return cur.selectionAsString(false);
442 // Try implicit word selection. If there is a change
443 // in the language the implicit word selection is
445 selectWordWhenUnderCursor(cur, WHOLE_WORD);
446 docstring const & retval = cur.selectionAsString(false);
447 cur.clearSelection();
452 void Text::setLabelWidthStringToSequence(Cursor const & cur,
456 // Find first of same layout in sequence
457 while (!isFirstInSequence(c.pit())) {
458 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
461 // now apply label width string to every par
463 depth_type const depth = c.paragraph().getDepth();
464 Layout const & layout = c.paragraph().layout();
465 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
466 while (c.paragraph().getDepth() > depth) {
468 if (c.pit() > c.lastpit())
471 if (c.paragraph().getDepth() < depth)
473 if (c.paragraph().layout() != layout)
476 c.paragraph().setLabelWidthString(s);
481 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
486 string const argument = to_utf8(arg);
487 depth_type priordepth = -1;
490 c.setCursor(cur.selectionBegin());
491 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
492 Paragraph & par = c.paragraph();
493 ParagraphParameters params = par.params();
494 params.read(argument, merge);
495 // Changes to label width string apply to all paragraphs
496 // with same layout in a sequence.
497 // Do this only once for a selected range of paragraphs
498 // of the same layout and depth.
500 par.params().apply(params, par.layout());
501 if (par.getDepth() != priordepth || par.layout() != priorlayout)
502 setLabelWidthStringToSequence(c, params.labelWidthString());
503 priordepth = par.getDepth();
504 priorlayout = par.layout();
509 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
513 depth_type priordepth = -1;
516 c.setCursor(cur.selectionBegin());
517 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
518 Paragraph & par = c.paragraph();
519 // Changes to label width string apply to all paragraphs
520 // with same layout in a sequence.
521 // Do this only once for a selected range of paragraphs
522 // of the same layout and depth.
524 par.params().apply(p, par.layout());
525 if (par.getDepth() != priordepth || par.layout() != priorlayout)
526 setLabelWidthStringToSequence(c,
527 par.params().labelWidthString());
528 priordepth = par.getDepth();
529 priorlayout = par.layout();
534 // this really should just insert the inset and not move the cursor.
535 void Text::insertInset(Cursor & cur, Inset * inset)
537 LBUFERR(this == cur.text());
539 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
540 Change(cur.buffer()->params().track_changes
541 ? Change::INSERTED : Change::UNCHANGED));
545 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
546 bool setfont, bool boundary)
548 TextMetrics const & tm = cur.bv().textMetrics(this);
549 bool const update_needed = !tm.contains(pit);
551 setCursorIntern(cur, pit, pos, setfont, boundary);
552 return cur.bv().checkDepm(cur, old) || update_needed;
556 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
557 bool setfont, bool boundary)
559 LBUFERR(this == cur.text());
560 cur.boundary(boundary);
561 cur.top().setPitPos(pit, pos);
563 cur.setCurrentFont();
567 bool Text::checkAndActivateInset(Cursor & cur, bool front)
569 if (front && cur.pos() == cur.lastpos())
571 if (!front && cur.pos() == 0)
573 Inset * inset = front ? cur.nextInset() : cur.prevInset();
574 if (!inset || !inset->editable())
576 if (cur.selection() && cur.realAnchor().find(inset) == -1)
579 * Apparently, when entering an inset we are expected to be positioned
580 * *before* it in the containing paragraph, regardless of the direction
581 * from which we are entering. Otherwise, cursor placement goes awry,
582 * and when we exit from the beginning, we'll be placed *after* the
587 inset->edit(cur, front);
588 cur.setCurrentFont();
594 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
598 if (cur.pos() == cur.lastpos())
600 Paragraph & par = cur.paragraph();
601 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
602 if (!inset || !inset->editable())
604 if (cur.selection() && cur.realAnchor().find(inset) == -1)
606 inset->edit(cur, movingForward,
607 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
608 cur.setCurrentFont();
614 bool Text::cursorBackward(Cursor & cur)
616 // Tell BufferView to test for FitCursor in any case!
617 cur.screenUpdateFlags(Update::FitCursor);
619 // not at paragraph start?
621 // if on right side of boundary (i.e. not at paragraph end, but line end)
622 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623 // there are some exceptions to ignore this: lineseps, newlines, spaces
625 // some effectless debug code to see the values in the debugger
626 bool bound = cur.boundary();
627 int rowpos = cur.textRow().pos();
629 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
633 if (!cur.boundary() &&
634 cur.textRow().pos() == cur.pos() &&
635 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636 !cur.paragraph().isNewline(cur.pos() - 1) &&
637 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
638 !cur.paragraph().isSeparator(cur.pos() - 1)) {
639 return setCursor(cur, cur.pit(), cur.pos(), true, true);
642 // go left and try to enter inset
643 if (checkAndActivateInset(cur, false))
646 // normal character left
647 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
650 // move to the previous paragraph or do nothing
652 Paragraph & par = getPar(cur.pit() - 1);
653 pos_type lastpos = par.size();
654 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
655 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
657 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
663 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
665 Cursor temp_cur = cur;
666 temp_cur.posVisLeft(skip_inset);
667 if (temp_cur.depth() > cur.depth()) {
671 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
672 true, temp_cur.boundary());
676 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
678 Cursor temp_cur = cur;
679 temp_cur.posVisRight(skip_inset);
680 if (temp_cur.depth() > cur.depth()) {
684 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
685 true, temp_cur.boundary());
689 bool Text::cursorForward(Cursor & cur)
691 // Tell BufferView to test for FitCursor in any case!
692 cur.screenUpdateFlags(Update::FitCursor);
694 // not at paragraph end?
695 if (cur.pos() != cur.lastpos()) {
696 // in front of editable inset, i.e. jump into it?
697 if (checkAndActivateInset(cur, true))
700 TextMetrics const & tm = cur.bv().textMetrics(this);
701 // if left of boundary -> just jump to right side
702 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
703 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
704 return setCursor(cur, cur.pit(), cur.pos(), true, false);
706 // next position is left of boundary,
707 // but go to next line for special cases like space, newline, linesep
709 // some effectless debug code to see the values in the debugger
710 int endpos = cur.textRow().endpos();
711 int lastpos = cur.lastpos();
713 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
714 bool newline = cur.paragraph().isNewline(cur.pos());
715 bool sep = cur.paragraph().isSeparator(cur.pos());
716 if (cur.pos() != cur.lastpos()) {
717 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
718 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
719 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
722 if (cur.textRow().endpos() == cur.pos() + 1) {
723 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
724 cur.pos() + 1 == cur.lastpos() &&
725 cur.pit() != cur.lastpit()) {
726 // move to next paragraph
727 return setCursor(cur, cur.pit() + 1, 0, true, false);
728 } else if (cur.textRow().endpos() != cur.lastpos() &&
729 !cur.paragraph().isNewline(cur.pos()) &&
730 !cur.paragraph().isEnvSeparator(cur.pos()) &&
731 !cur.paragraph().isLineSeparator(cur.pos()) &&
732 !cur.paragraph().isSeparator(cur.pos())) {
733 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
737 // in front of RTL boundary? Stay on this side of the boundary because:
738 // ab|cDDEEFFghi -> abc|DDEEFFghi
739 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
740 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
743 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
746 // move to next paragraph
747 if (cur.pit() != cur.lastpit())
748 return setCursor(cur, cur.pit() + 1, 0, true, false);
753 bool Text::cursorUpParagraph(Cursor & cur)
755 bool updated = false;
757 updated = setCursor(cur, cur.pit(), 0);
758 else if (cur.pit() != 0)
759 updated = setCursor(cur, cur.pit() - 1, 0);
764 bool Text::cursorDownParagraph(Cursor & cur)
766 bool updated = false;
767 if (cur.pit() != cur.lastpit())
768 if (lyxrc.mac_like_cursor_movement)
769 if (cur.pos() == cur.lastpos())
770 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
772 updated = setCursor(cur, cur.pit(), cur.lastpos());
774 updated = setCursor(cur, cur.pit() + 1, 0);
776 updated = setCursor(cur, cur.pit(), cur.lastpos());
782 /** delete num_spaces characters between from and to. Return the
783 * number of spaces that got physically deleted (not marked as
785 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
786 int num_spaces, bool const trackChanges)
791 // First, delete spaces marked as inserted
793 while (pos < to && num_spaces > 0) {
794 Change const & change = par.lookupChange(pos);
795 if (change.inserted() && change.currentAuthor()) {
796 par.eraseChar(pos, trackChanges);
803 // Then remove remaining spaces
804 int const psize = par.size();
805 par.eraseChars(from, from + num_spaces, trackChanges);
806 return psize - par.size();
812 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
813 Cursor & old, bool & need_anchor_change)
815 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
817 Paragraph & oldpar = old.paragraph();
818 bool const trackChanges = cur.buffer()->params().track_changes;
821 // We do nothing if cursor did not move
822 if (cur.top() == old.top())
825 // We do not do anything on read-only documents
826 if (cur.buffer()->isReadonly())
829 // Whether a common inset is found and whether the cursor is still in
830 // the same paragraph (possibly nested).
831 int const depth = cur.find(&old.inset());
832 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
833 && old.pit() == cur[depth].pit();
836 * (1) If the chars around the old cursor were spaces and the
837 * paragraph is not in free spacing mode, delete some of them, but
838 * only if the cursor has really moved.
841 /* There are still some small problems that can lead to
842 double spaces stored in the document file or space at
843 the beginning of paragraphs(). This happens if you have
844 the cursor between two spaces and then save. Or if you
845 cut and paste and the selection has a space at the
846 beginning and then save right after the paste. (Lgb)
848 if (!oldpar.isFreeSpacing()) {
849 // find range of spaces around cursors
850 pos_type from = old.pos();
852 && oldpar.isLineSeparator(from - 1)
853 && !oldpar.isDeleted(from - 1))
855 pos_type to = old.pos();
856 while (to < old.lastpos()
857 && oldpar.isLineSeparator(to)
858 && !oldpar.isDeleted(to))
861 int num_spaces = to - from;
862 // If we are not at the start of the paragraph, keep one space
863 if (from != to && from > 0)
866 // If cursor is inside range, keep one additional space
867 if (same_par && cur.pos() > from && cur.pos() < to)
870 // Remove spaces and adapt cursor.
871 if (num_spaces > 0) {
874 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
875 // correct cur position
876 // FIXME: there can be other cursors pointing there, we should update them
878 if (cur[depth].pos() >= to)
879 cur[depth].pos() -= deleted;
880 else if (cur[depth].pos() > from)
881 cur[depth].pos() = min(from + 1, old.lastpos());
882 need_anchor_change = true;
889 * (2) If the paragraph where the cursor was is empty, delete it
892 // only do our other magic if we changed paragraph
896 // only do our magic if the paragraph is empty
900 // don't delete anything if this is the ONLY paragraph!
901 if (old.lastpit() == 0)
904 // Do not delete empty paragraphs with keepempty set.
905 if (oldpar.allowEmpty())
909 old.recordUndo(max(old.pit() - 1, pit_type(0)),
910 min(old.pit() + 1, old.lastpit()));
911 ParagraphList & plist = old.text()->paragraphs();
912 bool const soa = oldpar.params().startOfAppendix();
913 plist.erase(plist.iterator_at(old.pit()));
914 // do not lose start of appendix marker (bug 4212)
915 if (soa && old.pit() < pit_type(plist.size()))
916 plist[old.pit()].params().startOfAppendix(true);
918 // see #warning (FIXME?) above
919 if (cur.depth() >= old.depth()) {
920 CursorSlice & curslice = cur[old.depth() - 1];
921 if (&curslice.inset() == &old.inset()
922 && curslice.idx() == old.idx()
923 && curslice.pit() > old.pit()) {
925 // since a paragraph has been deleted, all the
926 // insets after `old' have been copied and
927 // their address has changed. Therefore we
928 // need to `regenerate' cur. (JMarc)
929 cur.updateInsets(&(cur.bottom().inset()));
930 need_anchor_change = true;
938 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
940 pos_type last_pos = pars_[last].size() - 1;
941 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
945 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
946 pos_type first_pos, pos_type last_pos,
949 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
951 for (pit_type pit = first; pit <= last; ++pit) {
952 Paragraph & par = pars_[pit];
955 * (1) Delete consecutive spaces
957 if (!par.isFreeSpacing()) {
958 pos_type from = (pit == first) ? first_pos : 0;
959 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
960 while (from < to_pos) {
962 while (from < par.size()
963 && (!par.isLineSeparator(from) || par.isDeleted(from)))
965 // find string of spaces
967 while (to < par.size()
968 && par.isLineSeparator(to) && !par.isDeleted(to))
970 // empty? We are done
974 int num_spaces = to - from;
976 // If we are not at the extremity of the paragraph, keep one space
977 if (from != to && from > 0 && to < par.size())
980 // Remove spaces if needed
981 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
987 * (2) Delete empty pragraphs
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(pars_.iterator_at(pit));