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 LASSERT(start != end, return);
167 Buffer const & buffer = owner_->buffer();
168 BufferParams const & bp = buffer.params();
169 Layout const & lyxlayout = bp.documentClass()[layout];
171 for (pit_type pit = start; pit != end; ++pit) {
172 Paragraph & par = pars_[pit];
173 // Is this a separating paragraph? If so,
174 // this needs to be standard layout
175 bool const is_separator = par.size() == 1
176 && par.isEnvSeparator(0);
177 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
178 if (lyxlayout.margintype == MARGIN_MANUAL)
179 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
182 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
186 // set layout over selection and make a total rebreak of those paragraphs
187 void Text::setLayout(Cursor & cur, docstring const & layout)
189 LBUFERR(this == cur.text());
191 pit_type start = cur.selBegin().pit();
192 pit_type end = cur.selEnd().pit() + 1;
193 cur.recordUndoSelection();
194 setLayout(start, end, layout);
196 cur.setCurrentFont();
197 cur.forceBufferUpdate();
201 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
202 Paragraph const & par, int max_depth)
204 int const depth = par.params().depth();
205 if (type == Text::INC_DEPTH && depth < max_depth)
207 if (type == Text::DEC_DEPTH && depth > 0)
213 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
215 LBUFERR(this == cur.text());
216 // this happens when selecting several cells in tabular (bug 2630)
217 if (cur.selBegin().idx() != cur.selEnd().idx())
220 pit_type const beg = cur.selBegin().pit();
221 pit_type const end = cur.selEnd().pit() + 1;
222 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
224 for (pit_type pit = beg; pit != end; ++pit) {
225 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
227 max_depth = pars_[pit].getMaxDepthAfter();
233 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
235 LBUFERR(this == cur.text());
236 pit_type const beg = cur.selBegin().pit();
237 pit_type const end = cur.selEnd().pit() + 1;
238 cur.recordUndoSelection();
239 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
241 for (pit_type pit = beg; pit != end; ++pit) {
242 Paragraph & par = pars_[pit];
243 if (lyx::changeDepthAllowed(type, par, max_depth)) {
244 int const depth = par.params().depth();
245 if (type == INC_DEPTH)
246 par.params().depth(depth + 1);
248 par.params().depth(depth - 1);
250 max_depth = par.getMaxDepthAfter();
252 // this handles the counter labels, and also fixes up
253 // depth values for follow-on (child) paragraphs
254 cur.forceBufferUpdate();
258 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
260 LASSERT(this == cur.text(), return);
262 // If there is a selection, record undo before the cursor font is changed.
264 cur.recordUndoSelection();
266 // Set the current_font
267 // Determine basis font
269 pit_type pit = cur.pit();
270 if (cur.pos() < pars_[pit].beginOfBody())
271 layoutfont = labelFont(pars_[pit]);
273 layoutfont = layoutFont(pit);
275 // Update current font
276 cur.real_current_font.update(font,
277 cur.buffer()->params().language,
280 // Reduce to implicit settings
281 cur.current_font = cur.real_current_font;
282 cur.current_font.fontInfo().reduce(layoutfont);
283 // And resolve it completely
284 cur.real_current_font.fontInfo().realize(layoutfont);
286 // if there is no selection that's all we need to do
287 if (!cur.selection())
290 // Ok, we have a selection.
294 // Toggling behaves as follows: We check the first character of the
295 // selection. If it's (say) got EMPH on, then we set to off; if off,
296 // then to on. With families and the like, we set it to INHERIT, if
297 // we already have it.
298 CursorSlice const & sl = cur.selBegin();
299 Text const & text = *sl.text();
300 Paragraph const & par = text.getPar(sl.pit());
302 // get font at the position
303 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
304 text.outerFont(sl.pit()));
305 FontInfo const & oldfi = oldfont.fontInfo();
307 FontInfo & newfi = newfont.fontInfo();
309 FontFamily newfam = newfi.family();
310 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
311 newfam == oldfi.family())
312 newfi.setFamily(INHERIT_FAMILY);
314 FontSeries newser = newfi.series();
315 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
316 newfi.setSeries(INHERIT_SERIES);
318 FontShape newshp = newfi.shape();
319 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
320 newshp == oldfi.shape())
321 newfi.setShape(INHERIT_SHAPE);
323 ColorCode newcol = newfi.color();
324 if (newcol != Color_none && newcol != Color_inherit
325 && newcol != Color_ignore && newcol == oldfi.color())
326 newfi.setColor(Color_none);
329 if (newfi.emph() == FONT_TOGGLE)
330 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
331 if (newfi.underbar() == FONT_TOGGLE)
332 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
333 if (newfi.strikeout() == FONT_TOGGLE)
334 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
335 if (newfi.xout() == FONT_TOGGLE)
336 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
337 if (newfi.uuline() == FONT_TOGGLE)
338 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
339 if (newfi.uwave() == FONT_TOGGLE)
340 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
341 if (newfi.noun() == FONT_TOGGLE)
342 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
343 if (newfi.number() == FONT_TOGGLE)
344 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
345 if (newfi.nospellcheck() == FONT_TOGGLE)
346 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
349 setFont(cur.bv(), cur.selectionBegin().top(),
350 cur.selectionEnd().top(), newfont);
354 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
355 CursorSlice const & end, Font const & font)
357 Buffer const & buffer = bv.buffer();
359 // Don't use forwardChar here as ditend might have
360 // pos() == lastpos() and forwardChar would miss it.
361 // Can't use forwardPos either as this descends into
363 Language const * language = buffer.params().language;
364 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
365 if (dit.pos() == dit.lastpos())
367 pit_type const pit = dit.pit();
368 pos_type const pos = dit.pos();
369 Inset * inset = pars_[pit].getInset(pos);
370 if (inset && inset->resetFontEdit()) {
371 // We need to propagate the font change to all
372 // text cells of the inset (bugs 1973, 6919).
373 setInsetFont(bv, pit, pos, font);
375 TextMetrics const & tm = bv.textMetrics(this);
376 Font f = tm.displayFont(pit, pos);
377 f.update(font, language);
378 setCharFont(pit, pos, f, tm.font_);
379 // font change may change language...
380 // spell checker has to know that
381 pars_[pit].requestSpellCheck(pos);
386 bool Text::cursorTop(Cursor & cur)
388 LBUFERR(this == cur.text());
389 return setCursor(cur, 0, 0);
393 bool Text::cursorBottom(Cursor & cur)
395 LBUFERR(this == cur.text());
396 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
400 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
402 LBUFERR(this == cur.text());
403 // If the mask is completely neutral, tell user
404 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
405 // Could only happen with user style
406 cur.message(_("No font change defined."));
410 // Try implicit word selection
411 // If there is a change in the language the implicit word selection
413 CursorSlice const resetCursor = cur.top();
414 bool const implicitSelection =
415 font.language() == ignore_language
416 && font.fontInfo().number() == FONT_IGNORE
417 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
420 setFont(cur, font, toggleall);
422 // Implicit selections are cleared afterwards
423 // and cursor is set to the original position.
424 if (implicitSelection) {
425 cur.clearSelection();
426 cur.top() = resetCursor;
428 cur.setCurrentFont();
433 docstring Text::getStringForDialog(Cursor & cur)
435 LBUFERR(this == cur.text());
438 return cur.selectionAsString(false);
440 // Try implicit word selection. If there is a change
441 // in the language the implicit word selection is
443 selectWordWhenUnderCursor(cur, WHOLE_WORD);
444 docstring const & retval = cur.selectionAsString(false);
445 cur.clearSelection();
450 void Text::setLabelWidthStringToSequence(Cursor const & cur,
454 // Find first of same layout in sequence
455 while (!isFirstInSequence(c.pit())) {
456 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
459 // now apply label width string to every par
461 depth_type const depth = c.paragraph().getDepth();
462 Layout const & layout = c.paragraph().layout();
463 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
464 while (c.paragraph().getDepth() > depth) {
466 if (c.pit() > c.lastpit())
469 if (c.paragraph().getDepth() < depth)
471 if (c.paragraph().layout() != layout)
474 c.paragraph().setLabelWidthString(s);
479 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
484 string const argument = to_utf8(arg);
485 depth_type priordepth = -1;
488 c.setCursor(cur.selectionBegin());
489 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
490 Paragraph & par = c.paragraph();
491 ParagraphParameters params = par.params();
492 params.read(argument, merge);
493 // Changes to label width string apply to all paragraphs
494 // with same layout in a sequence.
495 // Do this only once for a selected range of paragraphs
496 // of the same layout and depth.
498 par.params().apply(params, par.layout());
499 if (par.getDepth() != priordepth || par.layout() != priorlayout)
500 setLabelWidthStringToSequence(c, params.labelWidthString());
501 priordepth = par.getDepth();
502 priorlayout = par.layout();
507 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
511 depth_type priordepth = -1;
514 c.setCursor(cur.selectionBegin());
515 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
516 Paragraph & par = c.paragraph();
517 // Changes to label width string apply to all paragraphs
518 // with same layout in a sequence.
519 // Do this only once for a selected range of paragraphs
520 // of the same layout and depth.
522 par.params().apply(p, par.layout());
523 if (par.getDepth() != priordepth || par.layout() != priorlayout)
524 setLabelWidthStringToSequence(c,
525 par.params().labelWidthString());
526 priordepth = par.getDepth();
527 priorlayout = par.layout();
532 // this really should just insert the inset and not move the cursor.
533 void Text::insertInset(Cursor & cur, Inset * inset)
535 LBUFERR(this == cur.text());
537 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
538 Change(cur.buffer()->params().track_changes
539 ? Change::INSERTED : Change::UNCHANGED));
543 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
544 bool setfont, bool boundary)
546 TextMetrics const & tm = cur.bv().textMetrics(this);
547 bool const update_needed = !tm.contains(pit);
549 setCursorIntern(cur, pit, pos, setfont, boundary);
550 return cur.bv().checkDepm(cur, old) || update_needed;
554 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
555 bool setfont, bool boundary)
557 LBUFERR(this == cur.text());
558 cur.boundary(boundary);
559 cur.top().setPitPos(pit, pos);
561 cur.setCurrentFont();
565 bool Text::checkAndActivateInset(Cursor & cur, bool front)
567 if (front && cur.pos() == cur.lastpos())
569 if (!front && cur.pos() == 0)
571 Inset * inset = front ? cur.nextInset() : cur.prevInset();
572 if (!inset || !inset->editable())
574 if (cur.selection() && cur.realAnchor().find(inset) == -1)
577 * Apparently, when entering an inset we are expected to be positioned
578 * *before* it in the containing paragraph, regardless of the direction
579 * from which we are entering. Otherwise, cursor placement goes awry,
580 * and when we exit from the beginning, we'll be placed *after* the
585 inset->edit(cur, front);
586 cur.setCurrentFont();
592 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
596 if (cur.pos() == cur.lastpos())
598 Paragraph & par = cur.paragraph();
599 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
600 if (!inset || !inset->editable())
602 if (cur.selection() && cur.realAnchor().find(inset) == -1)
604 inset->edit(cur, movingForward,
605 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
606 cur.setCurrentFont();
612 bool Text::cursorBackward(Cursor & cur)
614 // Tell BufferView to test for FitCursor in any case!
615 cur.screenUpdateFlags(Update::FitCursor);
617 // not at paragraph start?
619 // if on right side of boundary (i.e. not at paragraph end, but line end)
620 // -> skip it, i.e. set boundary to true, i.e. go only logically left
621 // there are some exceptions to ignore this: lineseps, newlines, spaces
623 // some effectless debug code to see the values in the debugger
624 bool bound = cur.boundary();
625 int rowpos = cur.textRow().pos();
627 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
628 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
629 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
631 if (!cur.boundary() &&
632 cur.textRow().pos() == cur.pos() &&
633 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
634 !cur.paragraph().isNewline(cur.pos() - 1) &&
635 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
636 !cur.paragraph().isSeparator(cur.pos() - 1)) {
637 return setCursor(cur, cur.pit(), cur.pos(), true, true);
640 // go left and try to enter inset
641 if (checkAndActivateInset(cur, false))
644 // normal character left
645 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
648 // move to the previous paragraph or do nothing
650 Paragraph & par = getPar(cur.pit() - 1);
651 pos_type lastpos = par.size();
652 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
653 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
655 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
661 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
663 Cursor temp_cur = cur;
664 temp_cur.posVisLeft(skip_inset);
665 if (temp_cur.depth() > cur.depth()) {
669 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
670 true, temp_cur.boundary());
674 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
676 Cursor temp_cur = cur;
677 temp_cur.posVisRight(skip_inset);
678 if (temp_cur.depth() > cur.depth()) {
682 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
683 true, temp_cur.boundary());
687 bool Text::cursorForward(Cursor & cur)
689 // Tell BufferView to test for FitCursor in any case!
690 cur.screenUpdateFlags(Update::FitCursor);
692 // not at paragraph end?
693 if (cur.pos() != cur.lastpos()) {
694 // in front of editable inset, i.e. jump into it?
695 if (checkAndActivateInset(cur, true))
698 TextMetrics const & tm = cur.bv().textMetrics(this);
699 // if left of boundary -> just jump to right side
700 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
701 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
702 return setCursor(cur, cur.pit(), cur.pos(), true, false);
704 // next position is left of boundary,
705 // but go to next line for special cases like space, newline, linesep
707 // some effectless debug code to see the values in the debugger
708 int endpos = cur.textRow().endpos();
709 int lastpos = cur.lastpos();
711 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
712 bool newline = cur.paragraph().isNewline(cur.pos());
713 bool sep = cur.paragraph().isSeparator(cur.pos());
714 if (cur.pos() != cur.lastpos()) {
715 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
716 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
717 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
720 if (cur.textRow().endpos() == cur.pos() + 1) {
721 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
722 cur.pos() + 1 == cur.lastpos() &&
723 cur.pit() != cur.lastpit()) {
724 // move to next paragraph
725 return setCursor(cur, cur.pit() + 1, 0, true, false);
726 } else if (cur.textRow().endpos() != cur.lastpos() &&
727 !cur.paragraph().isNewline(cur.pos()) &&
728 !cur.paragraph().isEnvSeparator(cur.pos()) &&
729 !cur.paragraph().isLineSeparator(cur.pos()) &&
730 !cur.paragraph().isSeparator(cur.pos())) {
731 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
735 // in front of RTL boundary? Stay on this side of the boundary because:
736 // ab|cDDEEFFghi -> abc|DDEEFFghi
737 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
738 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
741 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
744 // move to next paragraph
745 if (cur.pit() != cur.lastpit())
746 return setCursor(cur, cur.pit() + 1, 0, true, false);
751 bool Text::cursorUpParagraph(Cursor & cur)
753 bool updated = false;
755 updated = setCursor(cur, cur.pit(), 0);
756 else if (cur.pit() != 0)
757 updated = setCursor(cur, cur.pit() - 1, 0);
762 bool Text::cursorDownParagraph(Cursor & cur)
764 bool updated = false;
765 if (cur.pit() != cur.lastpit())
766 if (lyxrc.mac_like_cursor_movement)
767 if (cur.pos() == cur.lastpos())
768 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
770 updated = setCursor(cur, cur.pit(), cur.lastpos());
772 updated = setCursor(cur, cur.pit() + 1, 0);
774 updated = setCursor(cur, cur.pit(), cur.lastpos());
780 /** delete num_spaces characters between from and to. Return the
781 * number of spaces that got physically deleted (not marked as
783 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
784 int num_spaces, bool const trackChanges)
789 // First, delete spaces marked as inserted
791 while (pos < to && num_spaces > 0) {
792 Change const & change = par.lookupChange(pos);
793 if (change.inserted() && change.currentAuthor()) {
794 par.eraseChar(pos, trackChanges);
801 // Then remove remaining spaces
802 int const psize = par.size();
803 par.eraseChars(from, from + num_spaces, trackChanges);
804 return psize - par.size();
810 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
811 Cursor & old, bool & need_anchor_change)
813 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
815 Paragraph & oldpar = old.paragraph();
816 bool const trackChanges = cur.buffer()->params().track_changes;
819 // We do nothing if cursor did not move
820 if (cur.top() == old.top())
823 // We do not do anything on read-only documents
824 if (cur.buffer()->isReadonly())
827 // Whether a common inset is found and whether the cursor is still in
828 // the same paragraph (possibly nested).
829 int const depth = cur.find(&old.inset());
830 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
831 && old.pit() == cur[depth].pit();
834 * (1) If the chars around the old cursor were spaces and the
835 * paragraph is not in free spacing mode, delete some of them, but
836 * only if the cursor has really moved.
839 /* There are still some small problems that can lead to
840 double spaces stored in the document file or space at
841 the beginning of paragraphs(). This happens if you have
842 the cursor between two spaces and then save. Or if you
843 cut and paste and the selection has a space at the
844 beginning and then save right after the paste. (Lgb)
846 if (!oldpar.isFreeSpacing()) {
847 // find range of spaces around cursors
848 pos_type from = old.pos();
850 && oldpar.isLineSeparator(from - 1)
851 && !oldpar.isDeleted(from - 1))
853 pos_type to = old.pos();
854 while (to < old.lastpos()
855 && oldpar.isLineSeparator(to)
856 && !oldpar.isDeleted(to))
859 int num_spaces = to - from;
860 // If we are not at the start of the paragraph, keep one space
861 if (from != to && from > 0)
864 // If cursor is inside range, keep one additional space
865 if (same_par && cur.pos() > from && cur.pos() < to)
868 // Remove spaces and adapt cursor.
869 if (num_spaces > 0) {
872 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
873 // correct cur position
874 // FIXME: there can be other cursors pointing there, we should update them
876 if (cur[depth].pos() >= to)
877 cur[depth].pos() -= deleted;
878 else if (cur[depth].pos() > from)
879 cur[depth].pos() = min(from + 1, old.lastpos());
880 need_anchor_change = true;
887 * (2) If the paragraph where the cursor was is empty, delete it
890 // only do our other magic if we changed paragraph
894 // only do our magic if the paragraph is empty
898 // don't delete anything if this is the ONLY paragraph!
899 if (old.lastpit() == 0)
902 // Do not delete empty paragraphs with keepempty set.
903 if (oldpar.allowEmpty())
907 old.recordUndo(max(old.pit() - 1, pit_type(0)),
908 min(old.pit() + 1, old.lastpit()));
909 ParagraphList & plist = old.text()->paragraphs();
910 bool const soa = oldpar.params().startOfAppendix();
911 plist.erase(plist.iterator_at(old.pit()));
912 // do not lose start of appendix marker (bug 4212)
913 if (soa && old.pit() < pit_type(plist.size()))
914 plist[old.pit()].params().startOfAppendix(true);
916 // see #warning (FIXME?) above
917 if (cur.depth() >= old.depth()) {
918 CursorSlice & curslice = cur[old.depth() - 1];
919 if (&curslice.inset() == &old.inset()
920 && curslice.idx() == old.idx()
921 && curslice.pit() > old.pit()) {
923 // since a paragraph has been deleted, all the
924 // insets after `old' have been copied and
925 // their address has changed. Therefore we
926 // need to `regenerate' cur. (JMarc)
927 cur.updateInsets(&(cur.bottom().inset()));
928 need_anchor_change = true;
936 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
938 pos_type last_pos = pars_[last].size() - 1;
939 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
943 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
944 pos_type first_pos, pos_type last_pos,
947 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
949 for (pit_type pit = first; pit <= last; ++pit) {
950 Paragraph & par = pars_[pit];
953 * (1) Delete consecutive spaces
955 if (!par.isFreeSpacing()) {
956 pos_type from = (pit == first) ? first_pos : 0;
957 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
958 while (from < to_pos) {
960 while (from < par.size()
961 && (!par.isLineSeparator(from) || par.isDeleted(from)))
963 // find string of spaces
965 while (to < par.size()
966 && par.isLineSeparator(to) && !par.isDeleted(to))
968 // empty? We are done
972 int num_spaces = to - from;
974 // If we are not at the extremity of the paragraph, keep one space
975 if (from != to && from > 0 && to < par.size())
978 // Remove spaces if needed
979 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
985 * (2) Delete empty pragraphs
988 // don't delete anything if this is the only remaining paragraph
989 // within the given range. Note: Text::acceptOrRejectChanges()
990 // sets the cursor to 'first' after calling DEPM
994 // don't delete empty paragraphs with keepempty set
995 if (par.allowEmpty())
998 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
999 pars_.erase(pars_.iterator_at(pit));