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;
432 docstring Text::getStringForDialog(Cursor & cur)
434 LBUFERR(this == cur.text());
437 return cur.selectionAsString(false);
439 // Try implicit word selection. If there is a change
440 // in the language the implicit word selection is
442 selectWordWhenUnderCursor(cur, WHOLE_WORD);
443 docstring const & retval = cur.selectionAsString(false);
444 cur.clearSelection();
449 void Text::setLabelWidthStringToSequence(Cursor const & cur,
453 // Find first of same layout in sequence
454 while (!isFirstInSequence(c.pit())) {
455 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
458 // now apply label width string to every par
460 depth_type const depth = c.paragraph().getDepth();
461 Layout const & layout = c.paragraph().layout();
462 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
463 while (c.paragraph().getDepth() > depth) {
465 if (c.pit() > c.lastpit())
468 if (c.paragraph().getDepth() < depth)
470 if (c.paragraph().layout() != layout)
473 c.paragraph().setLabelWidthString(s);
478 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
483 string const argument = to_utf8(arg);
484 depth_type priordepth = -1;
487 c.setCursor(cur.selectionBegin());
488 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
489 Paragraph & par = c.paragraph();
490 ParagraphParameters params = par.params();
491 params.read(argument, merge);
492 // Changes to label width string apply to all paragraphs
493 // with same layout in a sequence.
494 // Do this only once for a selected range of paragraphs
495 // of the same layout and depth.
497 par.params().apply(params, par.layout());
498 if (par.getDepth() != priordepth || par.layout() != priorlayout)
499 setLabelWidthStringToSequence(c, params.labelWidthString());
500 priordepth = par.getDepth();
501 priorlayout = par.layout();
506 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
510 depth_type priordepth = -1;
513 c.setCursor(cur.selectionBegin());
514 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
515 Paragraph & par = c.paragraph();
516 // Changes to label width string apply to all paragraphs
517 // with same layout in a sequence.
518 // Do this only once for a selected range of paragraphs
519 // of the same layout and depth.
521 par.params().apply(p, par.layout());
522 if (par.getDepth() != priordepth || par.layout() != priorlayout)
523 setLabelWidthStringToSequence(c,
524 par.params().labelWidthString());
525 priordepth = par.getDepth();
526 priorlayout = par.layout();
531 // this really should just insert the inset and not move the cursor.
532 void Text::insertInset(Cursor & cur, Inset * inset)
534 LBUFERR(this == cur.text());
536 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
537 Change(cur.buffer()->params().track_changes
538 ? Change::INSERTED : Change::UNCHANGED));
542 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
543 bool setfont, bool boundary)
545 TextMetrics const & tm = cur.bv().textMetrics(this);
546 bool const update_needed = !tm.contains(pit);
548 setCursorIntern(cur, pit, pos, setfont, boundary);
549 return cur.bv().checkDepm(cur, old) || update_needed;
553 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
554 bool setfont, bool boundary)
556 LBUFERR(this == cur.text());
557 cur.boundary(boundary);
558 cur.top().setPitPos(pit, pos);
560 cur.setCurrentFont();
564 bool Text::checkAndActivateInset(Cursor & cur, bool front)
566 if (front && cur.pos() == cur.lastpos())
568 if (!front && cur.pos() == 0)
570 Inset * inset = front ? cur.nextInset() : cur.prevInset();
571 if (!inset || !inset->editable())
573 if (cur.selection() && cur.realAnchor().find(inset) == -1)
576 * Apparently, when entering an inset we are expected to be positioned
577 * *before* it in the containing paragraph, regardless of the direction
578 * from which we are entering. Otherwise, cursor placement goes awry,
579 * and when we exit from the beginning, we'll be placed *after* the
584 inset->edit(cur, front);
585 cur.setCurrentFont();
591 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
595 if (cur.pos() == cur.lastpos())
597 Paragraph & par = cur.paragraph();
598 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
599 if (!inset || !inset->editable())
601 if (cur.selection() && cur.realAnchor().find(inset) == -1)
603 inset->edit(cur, movingForward,
604 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
605 cur.setCurrentFont();
611 bool Text::cursorBackward(Cursor & cur)
613 // Tell BufferView to test for FitCursor in any case!
614 cur.screenUpdateFlags(Update::FitCursor);
616 // not at paragraph start?
618 // if on right side of boundary (i.e. not at paragraph end, but line end)
619 // -> skip it, i.e. set boundary to true, i.e. go only logically left
620 // there are some exceptions to ignore this: lineseps, newlines, spaces
622 // some effectless debug code to see the values in the debugger
623 bool bound = cur.boundary();
624 int rowpos = cur.textRow().pos();
626 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
627 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
628 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
630 if (!cur.boundary() &&
631 cur.textRow().pos() == cur.pos() &&
632 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
633 !cur.paragraph().isNewline(cur.pos() - 1) &&
634 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
635 !cur.paragraph().isSeparator(cur.pos() - 1)) {
636 return setCursor(cur, cur.pit(), cur.pos(), true, true);
639 // go left and try to enter inset
640 if (checkAndActivateInset(cur, false))
643 // normal character left
644 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
647 // move to the previous paragraph or do nothing
649 Paragraph & par = getPar(cur.pit() - 1);
650 pos_type lastpos = par.size();
651 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
652 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
654 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
660 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
662 Cursor temp_cur = cur;
663 temp_cur.posVisLeft(skip_inset);
664 if (temp_cur.depth() > cur.depth()) {
668 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
669 true, temp_cur.boundary());
673 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
675 Cursor temp_cur = cur;
676 temp_cur.posVisRight(skip_inset);
677 if (temp_cur.depth() > cur.depth()) {
681 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
682 true, temp_cur.boundary());
686 bool Text::cursorForward(Cursor & cur)
688 // Tell BufferView to test for FitCursor in any case!
689 cur.screenUpdateFlags(Update::FitCursor);
691 // not at paragraph end?
692 if (cur.pos() != cur.lastpos()) {
693 // in front of editable inset, i.e. jump into it?
694 if (checkAndActivateInset(cur, true))
697 TextMetrics const & tm = cur.bv().textMetrics(this);
698 // if left of boundary -> just jump to right side
699 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
700 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
701 return setCursor(cur, cur.pit(), cur.pos(), true, false);
703 // next position is left of boundary,
704 // but go to next line for special cases like space, newline, linesep
706 // some effectless debug code to see the values in the debugger
707 int endpos = cur.textRow().endpos();
708 int lastpos = cur.lastpos();
710 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
711 bool newline = cur.paragraph().isNewline(cur.pos());
712 bool sep = cur.paragraph().isSeparator(cur.pos());
713 if (cur.pos() != cur.lastpos()) {
714 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
715 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
716 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
719 if (cur.textRow().endpos() == cur.pos() + 1) {
720 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
721 cur.pos() + 1 == cur.lastpos() &&
722 cur.pit() != cur.lastpit()) {
723 // move to next paragraph
724 return setCursor(cur, cur.pit() + 1, 0, true, false);
725 } else if (cur.textRow().endpos() != cur.lastpos() &&
726 !cur.paragraph().isNewline(cur.pos()) &&
727 !cur.paragraph().isEnvSeparator(cur.pos()) &&
728 !cur.paragraph().isLineSeparator(cur.pos()) &&
729 !cur.paragraph().isSeparator(cur.pos())) {
730 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
734 // in front of RTL boundary? Stay on this side of the boundary because:
735 // ab|cDDEEFFghi -> abc|DDEEFFghi
736 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
737 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
740 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
743 // move to next paragraph
744 if (cur.pit() != cur.lastpit())
745 return setCursor(cur, cur.pit() + 1, 0, true, false);
750 bool Text::cursorUpParagraph(Cursor & cur)
752 bool updated = false;
754 updated = setCursor(cur, cur.pit(), 0);
755 else if (cur.pit() != 0)
756 updated = setCursor(cur, cur.pit() - 1, 0);
761 bool Text::cursorDownParagraph(Cursor & cur)
763 bool updated = false;
764 if (cur.pit() != cur.lastpit())
765 if (lyxrc.mac_like_cursor_movement)
766 if (cur.pos() == cur.lastpos())
767 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
769 updated = setCursor(cur, cur.pit(), cur.lastpos());
771 updated = setCursor(cur, cur.pit() + 1, 0);
773 updated = setCursor(cur, cur.pit(), cur.lastpos());
779 /** delete num_spaces characters between from and to. Return the
780 * number of spaces that got physically deleted (not marked as
782 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
783 int num_spaces, bool const trackChanges)
788 // First, delete spaces marked as inserted
790 while (pos < to && num_spaces > 0) {
791 Change const & change = par.lookupChange(pos);
792 if (change.inserted() && change.currentAuthor()) {
793 par.eraseChar(pos, trackChanges);
800 // Then remove remaining spaces
801 int const psize = par.size();
802 par.eraseChars(from, from + num_spaces, trackChanges);
803 return psize - par.size();
809 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
810 Cursor & old, bool & need_anchor_change)
812 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
814 Paragraph & oldpar = old.paragraph();
815 bool const trackChanges = cur.buffer()->params().track_changes;
818 // We do nothing if cursor did not move
819 if (cur.top() == old.top())
822 // We do not do anything on read-only documents
823 if (cur.buffer()->isReadonly())
826 // Whether a common inset is found and whether the cursor is still in
827 // the same paragraph (possibly nested).
828 int const depth = cur.find(&old.inset());
829 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
830 && old.pit() == cur[depth].pit();
833 * (1) If the chars around the old cursor were spaces and the
834 * paragraph is not in free spacing mode, delete some of them, but
835 * only if the cursor has really moved.
838 /* There are still some small problems that can lead to
839 double spaces stored in the document file or space at
840 the beginning of paragraphs(). This happens if you have
841 the cursor between two spaces and then save. Or if you
842 cut and paste and the selection has a space at the
843 beginning and then save right after the paste. (Lgb)
845 if (!oldpar.isFreeSpacing()) {
846 // find range of spaces around cursors
847 pos_type from = old.pos();
849 && oldpar.isLineSeparator(from - 1)
850 && !oldpar.isDeleted(from - 1))
852 pos_type to = old.pos();
853 while (to < old.lastpos()
854 && oldpar.isLineSeparator(to)
855 && !oldpar.isDeleted(to))
858 int num_spaces = to - from;
859 // If we are not at the start of the paragraph, keep one space
860 if (from != to && from > 0)
863 // If cursor is inside range, keep one additional space
864 if (same_par && cur.pos() > from && cur.pos() < to)
867 // Remove spaces and adapt cursor.
868 if (num_spaces > 0) {
871 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
872 // correct cur position
873 // FIXME: there can be other cursors pointing there, we should update them
875 if (cur[depth].pos() >= to)
876 cur[depth].pos() -= deleted;
877 else if (cur[depth].pos() > from)
878 cur[depth].pos() = min(from + 1, old.lastpos());
879 need_anchor_change = true;
886 * (2) If the paragraph where the cursor was is empty, delete it
889 // only do our other magic if we changed paragraph
893 // only do our magic if the paragraph is empty
897 // don't delete anything if this is the ONLY paragraph!
898 if (old.lastpit() == 0)
901 // Do not delete empty paragraphs with keepempty set.
902 if (oldpar.allowEmpty())
906 old.recordUndo(max(old.pit() - 1, pit_type(0)),
907 min(old.pit() + 1, old.lastpit()));
908 ParagraphList & plist = old.text()->paragraphs();
909 bool const soa = oldpar.params().startOfAppendix();
910 plist.erase(plist.iterator_at(old.pit()));
911 // do not lose start of appendix marker (bug 4212)
912 if (soa && old.pit() < pit_type(plist.size()))
913 plist[old.pit()].params().startOfAppendix(true);
915 // see #warning (FIXME?) above
916 if (cur.depth() >= old.depth()) {
917 CursorSlice & curslice = cur[old.depth() - 1];
918 if (&curslice.inset() == &old.inset()
919 && curslice.idx() == old.idx()
920 && curslice.pit() > old.pit()) {
922 // since a paragraph has been deleted, all the
923 // insets after `old' have been copied and
924 // their address has changed. Therefore we
925 // need to `regenerate' cur. (JMarc)
926 cur.updateInsets(&(cur.bottom().inset()));
927 need_anchor_change = true;
935 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
937 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
939 for (pit_type pit = first; pit <= last; ++pit) {
940 Paragraph & par = pars_[pit];
943 * (1) Delete consecutive spaces
945 if (!par.isFreeSpacing()) {
947 while (from < par.size()) {
949 while (from < par.size()
950 && (!par.isLineSeparator(from) || par.isDeleted(from)))
952 // find string of spaces
954 while (to < par.size()
955 && par.isLineSeparator(to) && !par.isDeleted(to))
957 // empty? We are done
961 int num_spaces = to - from;
963 // If we are not at the extremity of the paragraph, keep one space
964 if (from != to && from > 0 && to < par.size())
967 // Remove spaces if needed
968 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
974 * (2) Delete empty pragraphs
977 // don't delete anything if this is the only remaining paragraph
978 // within the given range. Note: Text::acceptOrRejectChanges()
979 // sets the cursor to 'first' after calling DEPM
983 // don't delete empty paragraphs with keepempty set
984 if (par.allowEmpty())
987 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
988 pars_.erase(pars_.iterator_at(pit));