3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "ErrorList.h"
40 #include "Paragraph.h"
41 #include "ParagraphParameters.h"
42 #include "TextClass.h"
43 #include "TextMetrics.h"
45 #include "insets/InsetCollapsible.h"
47 #include "mathed/InsetMathHull.h"
49 #include "support/lassert.h"
50 #include "support/debug.h"
51 #include "support/gettext.h"
52 #include "support/lyxalgo.h"
53 #include "support/textutils.h"
61 bool Text::isMainText() const
63 return &owner_->buffer().text() == this;
67 // Note that this is supposed to return a fully realized font.
68 FontInfo Text::layoutFont(pit_type const pit) const
70 Layout const & layout = pars_[pit].layout();
72 if (!pars_[pit].getDepth()) {
73 FontInfo lf = layout.resfont;
74 // In case the default family has been customized
75 if (layout.font.family() == INHERIT_FAMILY)
76 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
77 FontInfo icf = owner_->getLayout().font();
82 FontInfo font = layout.font;
83 // Realize with the fonts of lesser depth.
84 //font.realize(outerFont(pit));
85 font.realize(owner_->buffer().params().getFont().fontInfo());
91 // Note that this is supposed to return a fully realized font.
92 FontInfo Text::labelFont(Paragraph const & par) const
94 Buffer const & buffer = owner_->buffer();
95 Layout const & layout = par.layout();
97 if (!par.getDepth()) {
98 FontInfo lf = layout.reslabelfont;
99 // In case the default family has been customized
100 if (layout.labelfont.family() == INHERIT_FAMILY)
101 lf.setFamily(buffer.params().getFont().fontInfo().family());
105 FontInfo font = layout.labelfont;
106 // Realize with the fonts of lesser depth.
107 font.realize(buffer.params().getFont().fontInfo());
113 void Text::setCharFont(pit_type pit,
114 pos_type pos, Font const & fnt, Font const & display_font)
116 Buffer const & buffer = owner_->buffer();
118 Layout const & layout = pars_[pit].layout();
120 // Get concrete layout font to reduce against
123 if (pos < pars_[pit].beginOfBody())
124 layoutfont = layout.labelfont;
126 layoutfont = layout.font;
128 // Realize against environment font information
129 if (pars_[pit].getDepth()) {
131 while (!layoutfont.resolved() &&
132 tp != pit_type(paragraphs().size()) &&
133 pars_[tp].getDepth()) {
135 if (tp != pit_type(paragraphs().size()))
136 layoutfont.realize(pars_[tp].layout().font);
140 // Inside inset, apply the inset's font attributes if any
143 layoutfont.realize(display_font.fontInfo());
145 layoutfont.realize(buffer.params().getFont().fontInfo());
147 // Now, reduce font against full layout font
148 font.fontInfo().reduce(layoutfont);
150 pars_[pit].setFont(pos, font);
154 void Text::setInsetFont(BufferView const & bv, pit_type pit,
155 pos_type pos, Font const & font)
157 Inset * const inset = pars_[pit].getInset(pos);
158 LASSERT(inset && inset->resetFontEdit(), return);
160 CursorSlice::idx_type endidx = inset->nargs();
161 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
162 Text * text = cs.text();
164 // last position of the cell
165 CursorSlice cellend = cs;
166 cellend.pit() = cellend.lastpit();
167 cellend.pos() = cellend.lastpos();
168 text->setFont(bv, cs, cellend, font);
174 void Text::setLayout(pit_type start, pit_type end,
175 docstring const & layout)
177 LASSERT(start != end, return);
179 Buffer const & buffer = owner_->buffer();
180 BufferParams const & bp = buffer.params();
181 Layout const & lyxlayout = bp.documentClass()[layout];
183 for (pit_type pit = start; pit != end; ++pit) {
184 Paragraph & par = pars_[pit];
185 par.applyLayout(lyxlayout);
186 if (lyxlayout.margintype == MARGIN_MANUAL)
187 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
190 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
194 // set layout over selection and make a total rebreak of those paragraphs
195 void Text::setLayout(Cursor & cur, docstring const & layout)
197 LBUFERR(this == cur.text());
199 pit_type start = cur.selBegin().pit();
200 pit_type end = cur.selEnd().pit() + 1;
201 cur.recordUndoSelection();
202 setLayout(start, end, layout);
204 cur.setCurrentFont();
205 cur.forceBufferUpdate();
209 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
210 Paragraph const & par, int max_depth)
212 int const depth = par.params().depth();
213 if (type == Text::INC_DEPTH && depth < max_depth)
215 if (type == Text::DEC_DEPTH && depth > 0)
221 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
223 LBUFERR(this == cur.text());
224 // this happens when selecting several cells in tabular (bug 2630)
225 if (cur.selBegin().idx() != cur.selEnd().idx())
228 pit_type const beg = cur.selBegin().pit();
229 pit_type const end = cur.selEnd().pit() + 1;
230 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
232 for (pit_type pit = beg; pit != end; ++pit) {
233 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
235 max_depth = pars_[pit].getMaxDepthAfter();
241 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
243 LBUFERR(this == cur.text());
244 pit_type const beg = cur.selBegin().pit();
245 pit_type const end = cur.selEnd().pit() + 1;
246 cur.recordUndoSelection();
247 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
249 for (pit_type pit = beg; pit != end; ++pit) {
250 Paragraph & par = pars_[pit];
251 if (lyx::changeDepthAllowed(type, par, max_depth)) {
252 int const depth = par.params().depth();
253 if (type == INC_DEPTH)
254 par.params().depth(depth + 1);
256 par.params().depth(depth - 1);
258 max_depth = par.getMaxDepthAfter();
260 // this handles the counter labels, and also fixes up
261 // depth values for follow-on (child) paragraphs
262 cur.forceBufferUpdate();
266 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
268 LASSERT(this == cur.text(), return);
270 // If there is a selection, record undo before the cursor font is changed.
272 cur.recordUndoSelection();
274 // Set the current_font
275 // Determine basis font
277 pit_type pit = cur.pit();
278 if (cur.pos() < pars_[pit].beginOfBody())
279 layoutfont = labelFont(pars_[pit]);
281 layoutfont = layoutFont(pit);
283 // Update current font
284 cur.real_current_font.update(font,
285 cur.buffer()->params().language,
288 // Reduce to implicit settings
289 cur.current_font = cur.real_current_font;
290 cur.current_font.fontInfo().reduce(layoutfont);
291 // And resolve it completely
292 cur.real_current_font.fontInfo().realize(layoutfont);
294 // if there is no selection that's all we need to do
295 if (!cur.selection())
298 // Ok, we have a selection.
302 // Toggling behaves as follows: We check the first character of the
303 // selection. If it's (say) got EMPH on, then we set to off; if off,
304 // then to on. With families and the like, we set it to INHERIT, if
305 // we already have it.
306 CursorSlice const & sl = cur.selBegin();
307 Text const & text = *sl.text();
308 Paragraph const & par = text.getPar(sl.pit());
310 // get font at the position
311 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
312 text.outerFont(sl.pit()));
313 FontInfo const & oldfi = oldfont.fontInfo();
315 FontInfo & newfi = newfont.fontInfo();
317 FontFamily newfam = newfi.family();
318 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
319 newfam == oldfi.family())
320 newfi.setFamily(INHERIT_FAMILY);
322 FontSeries newser = newfi.series();
323 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
324 newfi.setSeries(INHERIT_SERIES);
326 FontShape newshp = newfi.shape();
327 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
328 newshp == oldfi.shape())
329 newfi.setShape(INHERIT_SHAPE);
331 ColorCode newcol = newfi.color();
332 if (newcol != Color_none && newcol != Color_inherit
333 && newcol != Color_ignore && newcol == oldfi.color())
334 newfi.setColor(Color_none);
337 if (newfi.emph() == FONT_TOGGLE)
338 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
339 if (newfi.underbar() == FONT_TOGGLE)
340 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
341 if (newfi.strikeout() == FONT_TOGGLE)
342 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
343 if (newfi.xout() == FONT_TOGGLE)
344 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
345 if (newfi.uuline() == FONT_TOGGLE)
346 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
347 if (newfi.uwave() == FONT_TOGGLE)
348 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
349 if (newfi.noun() == FONT_TOGGLE)
350 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
351 if (newfi.number() == FONT_TOGGLE)
352 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
353 if (newfi.nospellcheck() == FONT_TOGGLE)
354 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
357 setFont(cur.bv(), cur.selectionBegin().top(),
358 cur.selectionEnd().top(), newfont);
362 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
363 CursorSlice const & end, Font const & font)
365 Buffer const & buffer = bv.buffer();
367 // Don't use forwardChar here as ditend might have
368 // pos() == lastpos() and forwardChar would miss it.
369 // Can't use forwardPos either as this descends into
371 Language const * language = buffer.params().language;
372 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
373 if (dit.pos() == dit.lastpos())
375 pit_type const pit = dit.pit();
376 pos_type const pos = dit.pos();
377 Inset * inset = pars_[pit].getInset(pos);
378 if (inset && inset->resetFontEdit()) {
379 // We need to propagate the font change to all
380 // text cells of the inset (bugs 1973, 6919).
381 setInsetFont(bv, pit, pos, font);
383 TextMetrics const & tm = bv.textMetrics(this);
384 Font f = tm.displayFont(pit, pos);
385 f.update(font, language);
386 setCharFont(pit, pos, f, tm.font_);
387 // font change may change language...
388 // spell checker has to know that
389 pars_[pit].requestSpellCheck(pos);
394 bool Text::cursorTop(Cursor & cur)
396 LBUFERR(this == cur.text());
397 return setCursor(cur, 0, 0);
401 bool Text::cursorBottom(Cursor & cur)
403 LBUFERR(this == cur.text());
404 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
408 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
410 LBUFERR(this == cur.text());
411 // If the mask is completely neutral, tell user
412 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
413 // Could only happen with user style
414 cur.message(_("No font change defined."));
418 // Try implicit word selection
419 // If there is a change in the language the implicit word selection
421 CursorSlice const resetCursor = cur.top();
422 bool const implicitSelection =
423 font.language() == ignore_language
424 && font.fontInfo().number() == FONT_IGNORE
425 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
428 setFont(cur, font, toggleall);
430 // Implicit selections are cleared afterwards
431 // and cursor is set to the original position.
432 if (implicitSelection) {
433 cur.clearSelection();
434 cur.top() = resetCursor;
440 docstring Text::getStringForDialog(Cursor & cur)
442 LBUFERR(this == cur.text());
445 return cur.selectionAsString(false);
447 // Try implicit word selection. If there is a change
448 // in the language the implicit word selection is
450 selectWordWhenUnderCursor(cur, WHOLE_WORD);
451 docstring const & retval = cur.selectionAsString(false);
452 cur.clearSelection();
457 void Text::setLabelWidthStringToSequence(Cursor const & cur,
461 // Find first of same layout in sequence
462 while (!isFirstInSequence(c.pit())) {
463 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
466 // now apply label width string to every par
468 depth_type const depth = c.paragraph().getDepth();
469 Layout const & layout = c.paragraph().layout();
470 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
471 while (c.paragraph().getDepth() > depth) {
473 if (c.pit() > c.lastpit())
476 if (c.paragraph().getDepth() < depth)
478 if (c.paragraph().layout() != layout)
481 c.paragraph().setLabelWidthString(s);
486 void Text::setParagraphs(Cursor & cur, docstring const & arg, bool merge)
491 string const argument = to_utf8(arg);
492 depth_type priordepth = -1;
495 c.setCursor(cur.selectionBegin());
496 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
497 Paragraph & par = c.paragraph();
498 ParagraphParameters params = par.params();
499 params.read(argument, merge);
500 // Changes to label width string apply to all paragraphs
501 // with same layout in a sequence.
502 // Do this only once for a selected range of paragraphs
503 // of the same layout and depth.
505 par.params().apply(params, par.layout());
506 if (par.getDepth() != priordepth || par.layout() != priorlayout)
507 setLabelWidthStringToSequence(c, params.labelWidthString());
508 priordepth = par.getDepth();
509 priorlayout = par.layout();
514 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
518 depth_type priordepth = -1;
521 c.setCursor(cur.selectionBegin());
522 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
523 Paragraph & par = c.paragraph();
524 // Changes to label width string apply to all paragraphs
525 // with same layout in a sequence.
526 // Do this only once for a selected range of paragraphs
527 // of the same layout and depth.
529 par.params().apply(p, par.layout());
530 if (par.getDepth() != priordepth || par.layout() != priorlayout)
531 setLabelWidthStringToSequence(c,
532 par.params().labelWidthString());
533 priordepth = par.getDepth();
534 priorlayout = par.layout();
539 // this really should just insert the inset and not move the cursor.
540 void Text::insertInset(Cursor & cur, Inset * inset)
542 LBUFERR(this == cur.text());
544 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
545 Change(cur.buffer()->params().track_changes
546 ? Change::INSERTED : Change::UNCHANGED));
550 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
551 bool setfont, bool boundary)
553 TextMetrics const & tm = cur.bv().textMetrics(this);
554 bool const update_needed = !tm.contains(pit);
556 setCursorIntern(cur, pit, pos, setfont, boundary);
557 return cur.bv().checkDepm(cur, old) || update_needed;
561 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
562 bool setfont, bool boundary)
564 LBUFERR(this == cur.text());
565 cur.boundary(boundary);
566 cur.top().setPitPos(pit, pos);
568 cur.setCurrentFont();
572 bool Text::checkAndActivateInset(Cursor & cur, bool front)
574 if (front && cur.pos() == cur.lastpos())
576 if (!front && cur.pos() == 0)
578 Inset * inset = front ? cur.nextInset() : cur.prevInset();
579 if (!inset || !inset->editable())
581 if (cur.selection() && cur.realAnchor().find(inset) == -1)
584 * Apparently, when entering an inset we are expected to be positioned
585 * *before* it in the containing paragraph, regardless of the direction
586 * from which we are entering. Otherwise, cursor placement goes awry,
587 * and when we exit from the beginning, we'll be placed *after* the
592 inset->edit(cur, front);
593 cur.setCurrentFont();
599 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
603 if (cur.pos() == cur.lastpos())
605 Paragraph & par = cur.paragraph();
606 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
607 if (!inset || !inset->editable())
609 if (cur.selection() && cur.realAnchor().find(inset) == -1)
611 inset->edit(cur, movingForward,
612 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
613 cur.setCurrentFont();
619 bool Text::cursorBackward(Cursor & cur)
621 // Tell BufferView to test for FitCursor in any case!
622 cur.screenUpdateFlags(Update::FitCursor);
624 // not at paragraph start?
626 // if on right side of boundary (i.e. not at paragraph end, but line end)
627 // -> skip it, i.e. set boundary to true, i.e. go only logically left
628 // there are some exceptions to ignore this: lineseps, newlines, spaces
630 // some effectless debug code to see the values in the debugger
631 bool bound = cur.boundary();
632 int rowpos = cur.textRow().pos();
634 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
635 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
636 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
638 if (!cur.boundary() &&
639 cur.textRow().pos() == cur.pos() &&
640 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
641 !cur.paragraph().isNewline(cur.pos() - 1) &&
642 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
643 !cur.paragraph().isSeparator(cur.pos() - 1)) {
644 return setCursor(cur, cur.pit(), cur.pos(), true, true);
647 // go left and try to enter inset
648 if (checkAndActivateInset(cur, false))
651 // normal character left
652 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
655 // move to the previous paragraph or do nothing
657 Paragraph & par = getPar(cur.pit() - 1);
658 pos_type lastpos = par.size();
659 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
660 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
662 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
668 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
670 Cursor temp_cur = cur;
671 temp_cur.posVisLeft(skip_inset);
672 if (temp_cur.depth() > cur.depth()) {
676 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
677 true, temp_cur.boundary());
681 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
683 Cursor temp_cur = cur;
684 temp_cur.posVisRight(skip_inset);
685 if (temp_cur.depth() > cur.depth()) {
689 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
690 true, temp_cur.boundary());
694 bool Text::cursorForward(Cursor & cur)
696 // Tell BufferView to test for FitCursor in any case!
697 cur.screenUpdateFlags(Update::FitCursor);
699 // not at paragraph end?
700 if (cur.pos() != cur.lastpos()) {
701 // in front of editable inset, i.e. jump into it?
702 if (checkAndActivateInset(cur, true))
705 TextMetrics const & tm = cur.bv().textMetrics(this);
706 // if left of boundary -> just jump to right side
707 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
708 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
709 return setCursor(cur, cur.pit(), cur.pos(), true, false);
711 // next position is left of boundary,
712 // but go to next line for special cases like space, newline, linesep
714 // some effectless debug code to see the values in the debugger
715 int endpos = cur.textRow().endpos();
716 int lastpos = cur.lastpos();
718 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
719 bool newline = cur.paragraph().isNewline(cur.pos());
720 bool sep = cur.paragraph().isSeparator(cur.pos());
721 if (cur.pos() != cur.lastpos()) {
722 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
723 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
724 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
727 if (cur.textRow().endpos() == cur.pos() + 1) {
728 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
729 cur.pos() + 1 == cur.lastpos() &&
730 cur.pit() != cur.lastpit()) {
731 // move to next paragraph
732 return setCursor(cur, cur.pit() + 1, 0, true, false);
733 } else if (cur.textRow().endpos() != cur.lastpos() &&
734 !cur.paragraph().isNewline(cur.pos()) &&
735 !cur.paragraph().isEnvSeparator(cur.pos()) &&
736 !cur.paragraph().isLineSeparator(cur.pos()) &&
737 !cur.paragraph().isSeparator(cur.pos())) {
738 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
742 // in front of RTL boundary? Stay on this side of the boundary because:
743 // ab|cDDEEFFghi -> abc|DDEEFFghi
744 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
745 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
748 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
751 // move to next paragraph
752 if (cur.pit() != cur.lastpit())
753 return setCursor(cur, cur.pit() + 1, 0, true, false);
758 bool Text::cursorUpParagraph(Cursor & cur)
760 bool updated = false;
762 updated = setCursor(cur, cur.pit(), 0);
763 else if (cur.pit() != 0)
764 updated = setCursor(cur, cur.pit() - 1, 0);
769 bool Text::cursorDownParagraph(Cursor & cur)
771 bool updated = false;
772 if (cur.pit() != cur.lastpit())
773 if (lyxrc.mac_like_cursor_movement)
774 if (cur.pos() == cur.lastpos())
775 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
777 updated = setCursor(cur, cur.pit(), cur.lastpos());
779 updated = setCursor(cur, cur.pit() + 1, 0);
781 updated = setCursor(cur, cur.pit(), cur.lastpos());
787 /** delete num_spaces characters between from and to. Return the
788 * number of spaces that got physically deleted (not marked as
790 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
791 int num_spaces, bool const trackChanges)
796 // First, delete spaces marked as inserted
798 while (pos < to && num_spaces > 0) {
799 Change const & change = par.lookupChange(pos);
800 if (change.inserted() && change.currentAuthor()) {
801 par.eraseChar(pos, trackChanges);
808 // Then remove remaining spaces
809 int const psize = par.size();
810 par.eraseChars(from, from + num_spaces, trackChanges);
811 return psize - par.size();
817 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
818 Cursor & old, bool & need_anchor_change)
820 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
822 Paragraph & oldpar = old.paragraph();
823 bool const trackChanges = cur.buffer()->params().track_changes;
826 // We do nothing if cursor did not move
827 if (cur.top() == old.top())
830 // We do not do anything on read-only documents
831 if (cur.buffer()->isReadonly())
834 // We allow all kinds of "mumbo-jumbo" when freespacing.
835 if (oldpar.isFreeSpacing())
838 // Whether a common inset is found and whether the cursor is still in
839 // the same paragraph (possibly nested).
840 int const depth = cur.find(&old.inset());
841 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
842 && old.pit() == cur[depth].pit();
845 * (1) If the chars around the old cursor were spaces, delete some of
846 * them, but only if the cursor has really moved.
849 /* There are still some small problems that can lead to
850 double spaces stored in the document file or space at
851 the beginning of paragraphs(). This happens if you have
852 the cursor between two spaces and then save. Or if you
853 cut and paste and the selection has a space at the
854 beginning and then save right after the paste. (Lgb)
857 // find range of spaces around cursors
858 pos_type from = old.pos();
860 && oldpar.isLineSeparator(from - 1)
861 && !oldpar.isDeleted(from - 1))
863 pos_type to = old.pos();
864 while (to < old.lastpos()
865 && oldpar.isLineSeparator(to)
866 && !oldpar.isDeleted(to))
869 int num_spaces = to - from;
870 // If we are not at the start of the paragraph, keep one space
871 if (from != to && from > 0)
874 // If cursor is inside range, keep one additional space
875 if (same_par && cur.pos() > from && cur.pos() < to)
878 // Remove spaces and adapt cursor.
879 if (num_spaces > 0) {
882 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
883 // correct cur position
884 // FIXME: there can be other cursors pointing there, we should update them
886 if (cur[depth].pos() >= to)
887 cur[depth].pos() -= deleted;
888 else if (cur[depth].pos() > from)
889 cur[depth].pos() = min(from + 1, old.lastpos());
890 need_anchor_change = true;
896 * (2) If the paragraph where the cursor was is empty, delete it
899 // only do our other magic if we changed paragraph
903 // only do our magic if the paragraph is empty
907 // don't delete anything if this is the ONLY paragraph!
908 if (old.lastpit() == 0)
911 // Do not delete empty paragraphs with keepempty set.
912 if (oldpar.allowEmpty())
916 old.recordUndo(max(old.pit() - 1, pit_type(0)),
917 min(old.pit() + 1, old.lastpit()));
918 ParagraphList & plist = old.text()->paragraphs();
919 bool const soa = oldpar.params().startOfAppendix();
920 plist.erase(lyx::next(plist.begin(), old.pit()));
921 // do not lose start of appendix marker (bug 4212)
922 if (soa && old.pit() < pit_type(plist.size()))
923 plist[old.pit()].params().startOfAppendix(true);
925 // see #warning (FIXME?) above
926 if (cur.depth() >= old.depth()) {
927 CursorSlice & curslice = cur[old.depth() - 1];
928 if (&curslice.inset() == &old.inset()
929 && curslice.pit() > old.pit()) {
931 // since a paragraph has been deleted, all the
932 // insets after `old' have been copied and
933 // their address has changed. Therefore we
934 // need to `regenerate' cur. (JMarc)
935 cur.updateInsets(&(cur.bottom().inset()));
936 need_anchor_change = true;
944 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
946 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
948 for (pit_type pit = first; pit <= last; ++pit) {
949 Paragraph & par = pars_[pit];
951 // We allow all kinds of "mumbo-jumbo" when freespacing.
952 if (par.isFreeSpacing())
956 while (from < par.size()) {
958 while (from < par.size()
959 && (!par.isLineSeparator(from) || par.isDeleted(from)))
961 // find string of spaces
963 while (to < par.size()
964 && par.isLineSeparator(to) && !par.isDeleted(to))
966 // empty? We are done
970 int num_spaces = to - from;
972 // If we are not at the extremity of the paragraph, keep one space
973 if (from != to && from > 0 && to < par.size())
976 // Remove spaces if needed
977 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
981 // don't delete anything if this is the only remaining paragraph
982 // within the given range. Note: Text::acceptOrRejectChanges()
983 // sets the cursor to 'first' after calling DEPM
987 // don't delete empty paragraphs with keepempty set
988 if (par.allowEmpty())
991 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
992 pars_.erase(lyx::next(pars_.begin(), pit));