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 // Is this a separating paragraph?
186 bool const is_separator = par.size() == 1
187 && par.isEnvSeparator(0);
188 par.applyLayout(is_separator ? bp.documentClass().plainLayout() : lyxlayout);
189 if (lyxlayout.margintype == MARGIN_MANUAL)
190 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
193 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
197 // set layout over selection and make a total rebreak of those paragraphs
198 void Text::setLayout(Cursor & cur, docstring const & layout)
200 LBUFERR(this == cur.text());
202 pit_type start = cur.selBegin().pit();
203 pit_type end = cur.selEnd().pit() + 1;
204 cur.recordUndoSelection();
205 setLayout(start, end, layout);
207 cur.setCurrentFont();
208 cur.forceBufferUpdate();
212 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
213 Paragraph const & par, int max_depth)
215 int const depth = par.params().depth();
216 if (type == Text::INC_DEPTH && depth < max_depth)
218 if (type == Text::DEC_DEPTH && depth > 0)
224 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
226 LBUFERR(this == cur.text());
227 // this happens when selecting several cells in tabular (bug 2630)
228 if (cur.selBegin().idx() != cur.selEnd().idx())
231 pit_type const beg = cur.selBegin().pit();
232 pit_type const end = cur.selEnd().pit() + 1;
233 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
235 for (pit_type pit = beg; pit != end; ++pit) {
236 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
238 max_depth = pars_[pit].getMaxDepthAfter();
244 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
246 LBUFERR(this == cur.text());
247 pit_type const beg = cur.selBegin().pit();
248 pit_type const end = cur.selEnd().pit() + 1;
249 cur.recordUndoSelection();
250 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
252 for (pit_type pit = beg; pit != end; ++pit) {
253 Paragraph & par = pars_[pit];
254 if (lyx::changeDepthAllowed(type, par, max_depth)) {
255 int const depth = par.params().depth();
256 if (type == INC_DEPTH)
257 par.params().depth(depth + 1);
259 par.params().depth(depth - 1);
261 max_depth = par.getMaxDepthAfter();
263 // this handles the counter labels, and also fixes up
264 // depth values for follow-on (child) paragraphs
265 cur.forceBufferUpdate();
269 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
271 LASSERT(this == cur.text(), return);
273 // If there is a selection, record undo before the cursor font is changed.
275 cur.recordUndoSelection();
277 // Set the current_font
278 // Determine basis font
280 pit_type pit = cur.pit();
281 if (cur.pos() < pars_[pit].beginOfBody())
282 layoutfont = labelFont(pars_[pit]);
284 layoutfont = layoutFont(pit);
286 // Update current font
287 cur.real_current_font.update(font,
288 cur.buffer()->params().language,
291 // Reduce to implicit settings
292 cur.current_font = cur.real_current_font;
293 cur.current_font.fontInfo().reduce(layoutfont);
294 // And resolve it completely
295 cur.real_current_font.fontInfo().realize(layoutfont);
297 // if there is no selection that's all we need to do
298 if (!cur.selection())
301 // Ok, we have a selection.
305 // Toggling behaves as follows: We check the first character of the
306 // selection. If it's (say) got EMPH on, then we set to off; if off,
307 // then to on. With families and the like, we set it to INHERIT, if
308 // we already have it.
309 CursorSlice const & sl = cur.selBegin();
310 Text const & text = *sl.text();
311 Paragraph const & par = text.getPar(sl.pit());
313 // get font at the position
314 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
315 text.outerFont(sl.pit()));
316 FontInfo const & oldfi = oldfont.fontInfo();
318 FontInfo & newfi = newfont.fontInfo();
320 FontFamily newfam = newfi.family();
321 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
322 newfam == oldfi.family())
323 newfi.setFamily(INHERIT_FAMILY);
325 FontSeries newser = newfi.series();
326 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
327 newfi.setSeries(INHERIT_SERIES);
329 FontShape newshp = newfi.shape();
330 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
331 newshp == oldfi.shape())
332 newfi.setShape(INHERIT_SHAPE);
334 ColorCode newcol = newfi.color();
335 if (newcol != Color_none && newcol != Color_inherit
336 && newcol != Color_ignore && newcol == oldfi.color())
337 newfi.setColor(Color_none);
340 if (newfi.emph() == FONT_TOGGLE)
341 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.underbar() == FONT_TOGGLE)
343 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.strikeout() == FONT_TOGGLE)
345 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.xout() == FONT_TOGGLE)
347 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
348 if (newfi.uuline() == FONT_TOGGLE)
349 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
350 if (newfi.uwave() == FONT_TOGGLE)
351 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
352 if (newfi.noun() == FONT_TOGGLE)
353 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
354 if (newfi.number() == FONT_TOGGLE)
355 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
356 if (newfi.nospellcheck() == FONT_TOGGLE)
357 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
360 setFont(cur.bv(), cur.selectionBegin().top(),
361 cur.selectionEnd().top(), newfont);
365 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
366 CursorSlice const & end, Font const & font)
368 Buffer const & buffer = bv.buffer();
370 // Don't use forwardChar here as ditend might have
371 // pos() == lastpos() and forwardChar would miss it.
372 // Can't use forwardPos either as this descends into
374 Language const * language = buffer.params().language;
375 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
376 if (dit.pos() == dit.lastpos())
378 pit_type const pit = dit.pit();
379 pos_type const pos = dit.pos();
380 Inset * inset = pars_[pit].getInset(pos);
381 if (inset && inset->resetFontEdit()) {
382 // We need to propagate the font change to all
383 // text cells of the inset (bugs 1973, 6919).
384 setInsetFont(bv, pit, pos, font);
386 TextMetrics const & tm = bv.textMetrics(this);
387 Font f = tm.displayFont(pit, pos);
388 f.update(font, language);
389 setCharFont(pit, pos, f, tm.font_);
390 // font change may change language...
391 // spell checker has to know that
392 pars_[pit].requestSpellCheck(pos);
397 bool Text::cursorTop(Cursor & cur)
399 LBUFERR(this == cur.text());
400 return setCursor(cur, 0, 0);
404 bool Text::cursorBottom(Cursor & cur)
406 LBUFERR(this == cur.text());
407 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
411 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
413 LBUFERR(this == cur.text());
414 // If the mask is completely neutral, tell user
415 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
416 // Could only happen with user style
417 cur.message(_("No font change defined."));
421 // Try implicit word selection
422 // If there is a change in the language the implicit word selection
424 CursorSlice const resetCursor = cur.top();
425 bool const implicitSelection =
426 font.language() == ignore_language
427 && font.fontInfo().number() == FONT_IGNORE
428 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
431 setFont(cur, font, toggleall);
433 // Implicit selections are cleared afterwards
434 // and cursor is set to the original position.
435 if (implicitSelection) {
436 cur.clearSelection();
437 cur.top() = resetCursor;
443 docstring Text::getStringForDialog(Cursor & cur)
445 LBUFERR(this == cur.text());
448 return cur.selectionAsString(false);
450 // Try implicit word selection. If there is a change
451 // in the language the implicit word selection is
453 selectWordWhenUnderCursor(cur, WHOLE_WORD);
454 docstring const & retval = cur.selectionAsString(false);
455 cur.clearSelection();
460 void Text::setLabelWidthStringToSequence(Cursor const & cur,
464 // Find first of same layout in sequence
465 while (!isFirstInSequence(c.pit())) {
466 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
469 // now apply label width string to every par
471 depth_type const depth = c.paragraph().getDepth();
472 Layout const & layout = c.paragraph().layout();
473 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
474 while (c.paragraph().getDepth() > depth) {
476 if (c.pit() > c.lastpit())
479 if (c.paragraph().getDepth() < depth)
481 if (c.paragraph().layout() != layout)
484 c.paragraph().setLabelWidthString(s);
489 void Text::setParagraphs(Cursor & cur, docstring const & arg, bool merge)
494 string const argument = to_utf8(arg);
495 depth_type priordepth = -1;
498 c.setCursor(cur.selectionBegin());
499 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
500 Paragraph & par = c.paragraph();
501 ParagraphParameters params = par.params();
502 params.read(argument, merge);
503 // Changes to label width string apply to all paragraphs
504 // with same layout in a sequence.
505 // Do this only once for a selected range of paragraphs
506 // of the same layout and depth.
508 par.params().apply(params, par.layout());
509 if (par.getDepth() != priordepth || par.layout() != priorlayout)
510 setLabelWidthStringToSequence(c, params.labelWidthString());
511 priordepth = par.getDepth();
512 priorlayout = par.layout();
517 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
521 depth_type priordepth = -1;
524 c.setCursor(cur.selectionBegin());
525 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
526 Paragraph & par = c.paragraph();
527 // Changes to label width string apply to all paragraphs
528 // with same layout in a sequence.
529 // Do this only once for a selected range of paragraphs
530 // of the same layout and depth.
532 par.params().apply(p, par.layout());
533 if (par.getDepth() != priordepth || par.layout() != priorlayout)
534 setLabelWidthStringToSequence(c,
535 par.params().labelWidthString());
536 priordepth = par.getDepth();
537 priorlayout = par.layout();
542 // this really should just insert the inset and not move the cursor.
543 void Text::insertInset(Cursor & cur, Inset * inset)
545 LBUFERR(this == cur.text());
547 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
548 Change(cur.buffer()->params().track_changes
549 ? Change::INSERTED : Change::UNCHANGED));
553 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
554 bool setfont, bool boundary)
556 TextMetrics const & tm = cur.bv().textMetrics(this);
557 bool const update_needed = !tm.contains(pit);
559 setCursorIntern(cur, pit, pos, setfont, boundary);
560 return cur.bv().checkDepm(cur, old) || update_needed;
564 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
565 bool setfont, bool boundary)
567 LBUFERR(this == cur.text());
568 cur.boundary(boundary);
569 cur.top().setPitPos(pit, pos);
571 cur.setCurrentFont();
575 bool Text::checkAndActivateInset(Cursor & cur, bool front)
577 if (front && cur.pos() == cur.lastpos())
579 if (!front && cur.pos() == 0)
581 Inset * inset = front ? cur.nextInset() : cur.prevInset();
582 if (!inset || !inset->editable())
584 if (cur.selection() && cur.realAnchor().find(inset) == -1)
587 * Apparently, when entering an inset we are expected to be positioned
588 * *before* it in the containing paragraph, regardless of the direction
589 * from which we are entering. Otherwise, cursor placement goes awry,
590 * and when we exit from the beginning, we'll be placed *after* the
595 inset->edit(cur, front);
596 cur.setCurrentFont();
602 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
606 if (cur.pos() == cur.lastpos())
608 Paragraph & par = cur.paragraph();
609 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
610 if (!inset || !inset->editable())
612 if (cur.selection() && cur.realAnchor().find(inset) == -1)
614 inset->edit(cur, movingForward,
615 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
616 cur.setCurrentFont();
622 bool Text::cursorBackward(Cursor & cur)
624 // Tell BufferView to test for FitCursor in any case!
625 cur.screenUpdateFlags(Update::FitCursor);
627 // not at paragraph start?
629 // if on right side of boundary (i.e. not at paragraph end, but line end)
630 // -> skip it, i.e. set boundary to true, i.e. go only logically left
631 // there are some exceptions to ignore this: lineseps, newlines, spaces
633 // some effectless debug code to see the values in the debugger
634 bool bound = cur.boundary();
635 int rowpos = cur.textRow().pos();
637 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
638 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
639 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
641 if (!cur.boundary() &&
642 cur.textRow().pos() == cur.pos() &&
643 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
644 !cur.paragraph().isNewline(cur.pos() - 1) &&
645 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
646 !cur.paragraph().isSeparator(cur.pos() - 1)) {
647 return setCursor(cur, cur.pit(), cur.pos(), true, true);
650 // go left and try to enter inset
651 if (checkAndActivateInset(cur, false))
654 // normal character left
655 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
658 // move to the previous paragraph or do nothing
660 Paragraph & par = getPar(cur.pit() - 1);
661 pos_type lastpos = par.size();
662 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
663 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
665 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
671 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
673 Cursor temp_cur = cur;
674 temp_cur.posVisLeft(skip_inset);
675 if (temp_cur.depth() > cur.depth()) {
679 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
680 true, temp_cur.boundary());
684 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
686 Cursor temp_cur = cur;
687 temp_cur.posVisRight(skip_inset);
688 if (temp_cur.depth() > cur.depth()) {
692 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
693 true, temp_cur.boundary());
697 bool Text::cursorForward(Cursor & cur)
699 // Tell BufferView to test for FitCursor in any case!
700 cur.screenUpdateFlags(Update::FitCursor);
702 // not at paragraph end?
703 if (cur.pos() != cur.lastpos()) {
704 // in front of editable inset, i.e. jump into it?
705 if (checkAndActivateInset(cur, true))
708 TextMetrics const & tm = cur.bv().textMetrics(this);
709 // if left of boundary -> just jump to right side
710 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
711 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
712 return setCursor(cur, cur.pit(), cur.pos(), true, false);
714 // next position is left of boundary,
715 // but go to next line for special cases like space, newline, linesep
717 // some effectless debug code to see the values in the debugger
718 int endpos = cur.textRow().endpos();
719 int lastpos = cur.lastpos();
721 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
722 bool newline = cur.paragraph().isNewline(cur.pos());
723 bool sep = cur.paragraph().isSeparator(cur.pos());
724 if (cur.pos() != cur.lastpos()) {
725 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
726 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
727 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
730 if (cur.textRow().endpos() == cur.pos() + 1) {
731 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
732 cur.pos() + 1 == cur.lastpos() &&
733 cur.pit() != cur.lastpit()) {
734 // move to next paragraph
735 return setCursor(cur, cur.pit() + 1, 0, true, false);
736 } else if (cur.textRow().endpos() != cur.lastpos() &&
737 !cur.paragraph().isNewline(cur.pos()) &&
738 !cur.paragraph().isEnvSeparator(cur.pos()) &&
739 !cur.paragraph().isLineSeparator(cur.pos()) &&
740 !cur.paragraph().isSeparator(cur.pos())) {
741 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
745 // in front of RTL boundary? Stay on this side of the boundary because:
746 // ab|cDDEEFFghi -> abc|DDEEFFghi
747 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
748 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
751 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
754 // move to next paragraph
755 if (cur.pit() != cur.lastpit())
756 return setCursor(cur, cur.pit() + 1, 0, true, false);
761 bool Text::cursorUpParagraph(Cursor & cur)
763 bool updated = false;
765 updated = setCursor(cur, cur.pit(), 0);
766 else if (cur.pit() != 0)
767 updated = setCursor(cur, cur.pit() - 1, 0);
772 bool Text::cursorDownParagraph(Cursor & cur)
774 bool updated = false;
775 if (cur.pit() != cur.lastpit())
776 if (lyxrc.mac_like_cursor_movement)
777 if (cur.pos() == cur.lastpos())
778 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
780 updated = setCursor(cur, cur.pit(), cur.lastpos());
782 updated = setCursor(cur, cur.pit() + 1, 0);
784 updated = setCursor(cur, cur.pit(), cur.lastpos());
790 /** delete num_spaces characters between from and to. Return the
791 * number of spaces that got physically deleted (not marked as
793 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
794 int num_spaces, bool const trackChanges)
799 // First, delete spaces marked as inserted
801 while (pos < to && num_spaces > 0) {
802 Change const & change = par.lookupChange(pos);
803 if (change.inserted() && change.currentAuthor()) {
804 par.eraseChar(pos, trackChanges);
811 // Then remove remaining spaces
812 int const psize = par.size();
813 par.eraseChars(from, from + num_spaces, trackChanges);
814 return psize - par.size();
820 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
821 Cursor & old, bool & need_anchor_change)
823 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
825 Paragraph & oldpar = old.paragraph();
826 bool const trackChanges = cur.buffer()->params().track_changes;
829 // We do nothing if cursor did not move
830 if (cur.top() == old.top())
833 // We do not do anything on read-only documents
834 if (cur.buffer()->isReadonly())
837 // We allow all kinds of "mumbo-jumbo" when freespacing.
838 if (oldpar.isFreeSpacing())
841 // Whether a common inset is found and whether the cursor is still in
842 // the same paragraph (possibly nested).
843 int const depth = cur.find(&old.inset());
844 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
845 && old.pit() == cur[depth].pit();
848 * (1) If the chars around the old cursor were spaces, delete some of
849 * them, but only if the cursor has really moved.
852 /* There are still some small problems that can lead to
853 double spaces stored in the document file or space at
854 the beginning of paragraphs(). This happens if you have
855 the cursor between two spaces and then save. Or if you
856 cut and paste and the selection has a space at the
857 beginning and then save right after the paste. (Lgb)
860 // find range of spaces around cursors
861 pos_type from = old.pos();
863 && oldpar.isLineSeparator(from - 1)
864 && !oldpar.isDeleted(from - 1))
866 pos_type to = old.pos();
867 while (to < old.lastpos()
868 && oldpar.isLineSeparator(to)
869 && !oldpar.isDeleted(to))
872 int num_spaces = to - from;
873 // If we are not at the start of the paragraph, keep one space
874 if (from != to && from > 0)
877 // If cursor is inside range, keep one additional space
878 if (same_par && cur.pos() > from && cur.pos() < to)
881 // Remove spaces and adapt cursor.
882 if (num_spaces > 0) {
885 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
886 // correct cur position
887 // FIXME: there can be other cursors pointing there, we should update them
889 if (cur[depth].pos() >= to)
890 cur[depth].pos() -= deleted;
891 else if (cur[depth].pos() > from)
892 cur[depth].pos() = min(from + 1, old.lastpos());
893 need_anchor_change = true;
899 * (2) If the paragraph where the cursor was is empty, delete it
902 // only do our other magic if we changed paragraph
906 // only do our magic if the paragraph is empty
910 // don't delete anything if this is the ONLY paragraph!
911 if (old.lastpit() == 0)
914 // Do not delete empty paragraphs with keepempty set.
915 if (oldpar.allowEmpty())
919 old.recordUndo(max(old.pit() - 1, pit_type(0)),
920 min(old.pit() + 1, old.lastpit()));
921 ParagraphList & plist = old.text()->paragraphs();
922 bool const soa = oldpar.params().startOfAppendix();
923 plist.erase(plist.iterator_at(old.pit()));
924 // do not lose start of appendix marker (bug 4212)
925 if (soa && old.pit() < pit_type(plist.size()))
926 plist[old.pit()].params().startOfAppendix(true);
928 // see #warning (FIXME?) above
929 if (cur.depth() >= old.depth()) {
930 CursorSlice & curslice = cur[old.depth() - 1];
931 if (&curslice.inset() == &old.inset()
932 && curslice.pit() > old.pit()) {
934 // since a paragraph has been deleted, all the
935 // insets after `old' have been copied and
936 // their address has changed. Therefore we
937 // need to `regenerate' cur. (JMarc)
938 cur.updateInsets(&(cur.bottom().inset()));
939 need_anchor_change = true;
947 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
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];
954 // We allow all kinds of "mumbo-jumbo" when freespacing.
955 if (par.isFreeSpacing())
959 while (from < par.size()) {
961 while (from < par.size()
962 && (!par.isLineSeparator(from) || par.isDeleted(from)))
964 // find string of spaces
966 while (to < par.size()
967 && par.isLineSeparator(to) && !par.isDeleted(to))
969 // empty? We are done
973 int num_spaces = to - from;
975 // If we are not at the extremity of the paragraph, keep one space
976 if (from != to && from > 0 && to < par.size())
979 // Remove spaces if needed
980 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
984 // don't delete anything if this is the only remaining paragraph
985 // within the given range. Note: Text::acceptOrRejectChanges()
986 // sets the cursor to 'first' after calling DEPM
990 // don't delete empty paragraphs with keepempty set
991 if (par.allowEmpty())
994 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
995 pars_.erase(pars_.iterator_at(pit));