3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "BufferParams.h"
27 #include "BufferView.h"
33 #include "Paragraph.h"
34 #include "ParagraphParameters.h"
35 #include "TextClass.h"
36 #include "TextMetrics.h"
38 #include "insets/InsetText.h"
40 #include "support/lassert.h"
41 #include "support/gettext.h"
49 bool Text::isMainText() const
51 return &owner_->buffer().text() == this;
55 // Note that this is supposed to return a fully realized font.
56 FontInfo Text::layoutFont(pit_type const pit) const
58 Layout const & layout = pars_[pit].layout();
60 if (!pars_[pit].getDepth()) {
61 FontInfo lf = layout.resfont;
62 // In case the default family has been customized
63 if (layout.font.family() == INHERIT_FAMILY)
64 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
65 FontInfo icf = owner_->getLayout().font();
70 FontInfo font = layout.font;
71 // Realize with the fonts of lesser depth.
72 //font.realize(outerFont(pit));
73 font.realize(owner_->buffer().params().getFont().fontInfo());
79 // Note that this is supposed to return a fully realized font.
80 FontInfo Text::labelFont(Paragraph const & par) const
82 Buffer const & buffer = owner_->buffer();
83 Layout const & layout = par.layout();
85 if (!par.getDepth()) {
86 FontInfo lf = layout.reslabelfont;
87 // In case the default family has been customized
88 if (layout.labelfont.family() == INHERIT_FAMILY)
89 lf.setFamily(buffer.params().getFont().fontInfo().family());
93 FontInfo font = layout.labelfont;
94 // Realize with the fonts of lesser depth.
95 font.realize(buffer.params().getFont().fontInfo());
101 void Text::setCharFont(pit_type pit,
102 pos_type pos, Font const & fnt, Font const & display_font)
104 Buffer const & buffer = owner_->buffer();
106 Layout const & layout = pars_[pit].layout();
108 // Get concrete layout font to reduce against
111 if (pos < pars_[pit].beginOfBody())
112 layoutfont = layout.labelfont;
114 layoutfont = layout.font;
116 // Realize against environment font information
117 if (pars_[pit].getDepth()) {
119 while (!layoutfont.resolved() &&
120 tp != pit_type(paragraphs().size()) &&
121 pars_[tp].getDepth()) {
123 if (tp != pit_type(paragraphs().size()))
124 layoutfont.realize(pars_[tp].layout().font);
128 // Inside inset, apply the inset's font attributes if any
131 layoutfont.realize(display_font.fontInfo());
133 layoutfont.realize(buffer.params().getFont().fontInfo());
135 // Now, reduce font against full layout font
136 font.fontInfo().reduce(layoutfont);
138 pars_[pit].setFont(pos, font);
142 void Text::setInsetFont(BufferView const & bv, pit_type pit,
143 pos_type pos, Font const & font)
145 Inset * const inset = pars_[pit].getInset(pos);
146 LASSERT(inset && inset->resetFontEdit(), return);
148 idx_type endidx = inset->nargs();
149 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
150 Text * text = cs.text();
152 // last position of the cell
153 CursorSlice cellend = cs;
154 cellend.pit() = cellend.lastpit();
155 cellend.pos() = cellend.lastpos();
156 text->setFont(bv, cs, cellend, font);
162 void Text::setLayout(pit_type start, pit_type end,
163 docstring const & layout)
165 // FIXME: make this work in multicell selection case
166 LASSERT(start != end, return);
168 Buffer const & buffer = owner_->buffer();
169 BufferParams const & bp = buffer.params();
170 Layout const & lyxlayout = bp.documentClass()[layout];
172 for (pit_type pit = start; pit != end; ++pit) {
173 Paragraph & par = pars_[pit];
174 // Is this a separating paragraph? If so,
175 // this needs to be standard layout
176 bool const is_separator = par.size() == 1
177 && par.isEnvSeparator(0);
178 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
179 if (lyxlayout.margintype == MARGIN_MANUAL)
180 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
183 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
187 // set layout over selection and make a total rebreak of those paragraphs
188 void Text::setLayout(Cursor & cur, docstring const & layout)
190 LBUFERR(this == cur.text());
192 pit_type start = cur.selBegin().pit();
193 pit_type end = cur.selEnd().pit() + 1;
194 cur.recordUndoSelection();
195 setLayout(start, end, layout);
197 cur.setCurrentFont();
198 cur.forceBufferUpdate();
202 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
203 Paragraph const & par, int max_depth)
205 int const depth = par.params().depth();
206 if (type == Text::INC_DEPTH && depth < max_depth)
208 if (type == Text::DEC_DEPTH && depth > 0)
214 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
216 LBUFERR(this == cur.text());
217 // this happens when selecting several cells in tabular (bug 2630)
218 if (cur.selBegin().idx() != cur.selEnd().idx())
221 pit_type const beg = cur.selBegin().pit();
222 pit_type const end = cur.selEnd().pit() + 1;
223 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
225 for (pit_type pit = beg; pit != end; ++pit) {
226 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
228 max_depth = pars_[pit].getMaxDepthAfter();
234 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
236 LBUFERR(this == cur.text());
237 pit_type const beg = cur.selBegin().pit();
238 pit_type const end = cur.selEnd().pit() + 1;
239 cur.recordUndoSelection();
240 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
242 for (pit_type pit = beg; pit != end; ++pit) {
243 Paragraph & par = pars_[pit];
244 if (lyx::changeDepthAllowed(type, par, max_depth)) {
245 int const depth = par.params().depth();
246 if (type == INC_DEPTH)
247 par.params().depth(depth + 1);
249 par.params().depth(depth - 1);
251 max_depth = par.getMaxDepthAfter();
253 // this handles the counter labels, and also fixes up
254 // depth values for follow-on (child) paragraphs
255 cur.forceBufferUpdate();
259 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
261 LASSERT(this == cur.text(), return);
263 // If there is a selection, record undo before the cursor font is changed.
265 cur.recordUndoSelection();
267 // Set the current_font
268 // Determine basis font
270 pit_type pit = cur.pit();
271 if (cur.pos() < pars_[pit].beginOfBody())
272 layoutfont = labelFont(pars_[pit]);
274 layoutfont = layoutFont(pit);
276 // Update current font
277 cur.real_current_font.update(font,
278 cur.buffer()->params().language,
281 // Reduce to implicit settings
282 cur.current_font = cur.real_current_font;
283 cur.current_font.fontInfo().reduce(layoutfont);
284 // And resolve it completely
285 cur.real_current_font.fontInfo().realize(layoutfont);
287 // if there is no selection that's all we need to do
288 if (!cur.selection())
291 // Ok, we have a selection.
295 // Toggling behaves as follows: We check the first character of the
296 // selection. If it's (say) got EMPH on, then we set to off; if off,
297 // then to on. With families and the like, we set it to INHERIT, if
298 // we already have it.
299 CursorSlice const & sl = cur.selBegin();
300 Text const & text = *sl.text();
301 Paragraph const & par = text.getPar(sl.pit());
303 // get font at the position
304 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
305 text.outerFont(sl.pit()));
306 FontInfo const & oldfi = oldfont.fontInfo();
308 FontInfo & newfi = newfont.fontInfo();
310 FontFamily newfam = newfi.family();
311 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
312 newfam == oldfi.family())
313 newfi.setFamily(INHERIT_FAMILY);
315 FontSeries newser = newfi.series();
316 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
317 newfi.setSeries(INHERIT_SERIES);
319 FontShape newshp = newfi.shape();
320 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
321 newshp == oldfi.shape())
322 newfi.setShape(INHERIT_SHAPE);
324 ColorCode newcol = newfi.color();
325 if (newcol != Color_none && newcol != Color_inherit
326 && newcol != Color_ignore && newcol == oldfi.color())
327 newfi.setColor(Color_none);
330 if (newfi.emph() == FONT_TOGGLE)
331 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
332 if (newfi.underbar() == FONT_TOGGLE)
333 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
334 if (newfi.strikeout() == FONT_TOGGLE)
335 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
336 if (newfi.xout() == FONT_TOGGLE)
337 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
338 if (newfi.uuline() == FONT_TOGGLE)
339 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.uwave() == FONT_TOGGLE)
341 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.noun() == FONT_TOGGLE)
343 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.number() == FONT_TOGGLE)
345 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.nospellcheck() == FONT_TOGGLE)
347 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
350 setFont(cur.bv(), cur.selectionBegin().top(),
351 cur.selectionEnd().top(), newfont);
355 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
356 CursorSlice const & end, Font const & font)
358 Buffer const & buffer = bv.buffer();
360 // Don't use forwardChar here as ditend might have
361 // pos() == lastpos() and forwardChar would miss it.
362 // Can't use forwardPos either as this descends into
364 Language const * language = buffer.params().language;
365 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
366 if (dit.pos() == dit.lastpos())
368 pit_type const pit = dit.pit();
369 pos_type const pos = dit.pos();
370 Inset * inset = pars_[pit].getInset(pos);
371 if (inset && inset->resetFontEdit()) {
372 // We need to propagate the font change to all
373 // text cells of the inset (bugs 1973, 6919).
374 setInsetFont(bv, pit, pos, font);
376 TextMetrics const & tm = bv.textMetrics(this);
377 Font f = tm.displayFont(pit, pos);
378 f.update(font, language);
379 setCharFont(pit, pos, f, tm.font_);
380 // font change may change language...
381 // spell checker has to know that
382 pars_[pit].requestSpellCheck(pos);
387 bool Text::cursorTop(Cursor & cur)
389 LBUFERR(this == cur.text());
390 return setCursor(cur, 0, 0);
394 bool Text::cursorBottom(Cursor & cur)
396 LBUFERR(this == cur.text());
397 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
401 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
403 LBUFERR(this == cur.text());
404 // If the mask is completely neutral, tell user
405 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
406 // Could only happen with user style
407 cur.message(_("No font change defined."));
411 // Try implicit word selection
412 // If there is a change in the language the implicit word selection
414 CursorSlice const resetCursor = cur.top();
415 bool const implicitSelection =
416 font.language() == ignore_language
417 && font.fontInfo().number() == FONT_IGNORE
418 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
421 setFont(cur, font, toggleall);
423 // Implicit selections are cleared afterwards
424 // and cursor is set to the original position.
425 if (implicitSelection) {
426 cur.clearSelection();
427 cur.top() = resetCursor;
431 // if there was no selection at all, the point was to change cursor font.
432 // Otherwise, we want to reset it to local text font.
433 if (cur.selection() || implicitSelection)
434 cur.setCurrentFont();
438 docstring Text::getStringForDialog(Cursor & cur)
440 LBUFERR(this == cur.text());
443 return cur.selectionAsString(false);
445 // Try implicit word selection. If there is a change
446 // in the language the implicit word selection is
448 selectWordWhenUnderCursor(cur, WHOLE_WORD);
449 docstring const & retval = cur.selectionAsString(false);
450 cur.clearSelection();
455 void Text::setLabelWidthStringToSequence(Cursor const & cur,
459 // Find first of same layout in sequence
460 while (!isFirstInSequence(c.pit())) {
461 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
464 // now apply label width string to every par
466 depth_type const depth = c.paragraph().getDepth();
467 Layout const & layout = c.paragraph().layout();
468 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
469 while (c.paragraph().getDepth() > depth) {
471 if (c.pit() > c.lastpit())
474 if (c.paragraph().getDepth() < depth)
476 if (c.paragraph().layout() != layout)
479 c.paragraph().setLabelWidthString(s);
484 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
489 string const argument = to_utf8(arg);
490 depth_type priordepth = -1;
493 c.setCursor(cur.selectionBegin());
494 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
495 Paragraph & par = c.paragraph();
496 ParagraphParameters params = par.params();
497 params.read(argument, merge);
498 // Changes to label width string apply to all paragraphs
499 // with same layout in a sequence.
500 // Do this only once for a selected range of paragraphs
501 // of the same layout and depth.
503 par.params().apply(params, par.layout());
504 if (par.getDepth() != priordepth || par.layout() != priorlayout)
505 setLabelWidthStringToSequence(c, params.labelWidthString());
506 priordepth = par.getDepth();
507 priorlayout = par.layout();
512 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
516 depth_type priordepth = -1;
519 c.setCursor(cur.selectionBegin());
520 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
521 Paragraph & par = c.paragraph();
522 // Changes to label width string apply to all paragraphs
523 // with same layout in a sequence.
524 // Do this only once for a selected range of paragraphs
525 // of the same layout and depth.
527 par.params().apply(p, par.layout());
528 if (par.getDepth() != priordepth || par.layout() != priorlayout)
529 setLabelWidthStringToSequence(c,
530 par.params().labelWidthString());
531 priordepth = par.getDepth();
532 priorlayout = par.layout();
537 // this really should just insert the inset and not move the cursor.
538 void Text::insertInset(Cursor & cur, Inset * inset)
540 LBUFERR(this == cur.text());
542 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
543 Change(cur.buffer()->params().track_changes
544 ? Change::INSERTED : Change::UNCHANGED));
548 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
549 bool setfont, bool boundary)
551 TextMetrics const & tm = cur.bv().textMetrics(this);
552 bool const update_needed = !tm.contains(pit);
554 setCursorIntern(cur, pit, pos, setfont, boundary);
555 return cur.bv().checkDepm(cur, old) || update_needed;
559 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
560 bool setfont, bool boundary)
562 LBUFERR(this == cur.text());
563 cur.boundary(boundary);
564 cur.top().setPitPos(pit, pos);
566 cur.setCurrentFont();
570 bool Text::checkAndActivateInset(Cursor & cur, bool front)
572 if (front && cur.pos() == cur.lastpos())
574 if (!front && cur.pos() == 0)
576 Inset * inset = front ? cur.nextInset() : cur.prevInset();
577 if (!inset || !inset->editable())
579 if (cur.selection() && cur.realAnchor().find(inset) == -1)
582 * Apparently, when entering an inset we are expected to be positioned
583 * *before* it in the containing paragraph, regardless of the direction
584 * from which we are entering. Otherwise, cursor placement goes awry,
585 * and when we exit from the beginning, we'll be placed *after* the
590 inset->edit(cur, front);
591 cur.setCurrentFont();
597 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
601 if (cur.pos() == cur.lastpos())
603 Paragraph & par = cur.paragraph();
604 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
605 if (!inset || !inset->editable())
607 if (cur.selection() && cur.realAnchor().find(inset) == -1)
609 inset->edit(cur, movingForward,
610 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
611 cur.setCurrentFont();
617 bool Text::cursorBackward(Cursor & cur)
619 // Tell BufferView to test for FitCursor in any case!
620 cur.screenUpdateFlags(Update::FitCursor);
622 // not at paragraph start?
624 // if on right side of boundary (i.e. not at paragraph end, but line end)
625 // -> skip it, i.e. set boundary to true, i.e. go only logically left
626 // there are some exceptions to ignore this: lineseps, newlines, spaces
628 // some effectless debug code to see the values in the debugger
629 bool bound = cur.boundary();
630 int rowpos = cur.textRow().pos();
632 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
633 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
634 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
636 if (!cur.boundary() &&
637 cur.textRow().pos() == cur.pos() &&
638 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
639 !cur.paragraph().isNewline(cur.pos() - 1) &&
640 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
641 !cur.paragraph().isSeparator(cur.pos() - 1)) {
642 return setCursor(cur, cur.pit(), cur.pos(), true, true);
645 // go left and try to enter inset
646 if (checkAndActivateInset(cur, false))
649 // normal character left
650 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
653 // move to the previous paragraph or do nothing
655 Paragraph & par = getPar(cur.pit() - 1);
656 pos_type lastpos = par.size();
657 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
658 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
660 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
666 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
668 Cursor temp_cur = cur;
669 temp_cur.posVisLeft(skip_inset);
670 if (temp_cur.depth() > cur.depth()) {
674 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
675 true, temp_cur.boundary());
679 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
681 Cursor temp_cur = cur;
682 temp_cur.posVisRight(skip_inset);
683 if (temp_cur.depth() > cur.depth()) {
687 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
688 true, temp_cur.boundary());
692 bool Text::cursorForward(Cursor & cur)
694 // Tell BufferView to test for FitCursor in any case!
695 cur.screenUpdateFlags(Update::FitCursor);
697 // not at paragraph end?
698 if (cur.pos() != cur.lastpos()) {
699 // in front of editable inset, i.e. jump into it?
700 if (checkAndActivateInset(cur, true))
703 TextMetrics const & tm = cur.bv().textMetrics(this);
704 // if left of boundary -> just jump to right side
705 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
706 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
707 return setCursor(cur, cur.pit(), cur.pos(), true, false);
709 // next position is left of boundary,
710 // but go to next line for special cases like space, newline, linesep
712 // some effectless debug code to see the values in the debugger
713 int endpos = cur.textRow().endpos();
714 int lastpos = cur.lastpos();
716 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
717 bool newline = cur.paragraph().isNewline(cur.pos());
718 bool sep = cur.paragraph().isSeparator(cur.pos());
719 if (cur.pos() != cur.lastpos()) {
720 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
721 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
722 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
725 if (cur.textRow().endpos() == cur.pos() + 1) {
726 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
727 cur.pos() + 1 == cur.lastpos() &&
728 cur.pit() != cur.lastpit()) {
729 // move to next paragraph
730 return setCursor(cur, cur.pit() + 1, 0, true, false);
731 } else if (cur.textRow().endpos() != cur.lastpos() &&
732 !cur.paragraph().isNewline(cur.pos()) &&
733 !cur.paragraph().isEnvSeparator(cur.pos()) &&
734 !cur.paragraph().isLineSeparator(cur.pos()) &&
735 !cur.paragraph().isSeparator(cur.pos())) {
736 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
740 // in front of RTL boundary? Stay on this side of the boundary because:
741 // ab|cDDEEFFghi -> abc|DDEEFFghi
742 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
743 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
746 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
749 // move to next paragraph
750 if (cur.pit() != cur.lastpit())
751 return setCursor(cur, cur.pit() + 1, 0, true, false);
756 bool Text::cursorUpParagraph(Cursor & cur)
758 bool updated = false;
760 updated = setCursor(cur, cur.pit(), 0);
761 else if (cur.pit() != 0)
762 updated = setCursor(cur, cur.pit() - 1, 0);
767 bool Text::cursorDownParagraph(Cursor & cur)
769 bool updated = false;
770 if (cur.pit() != cur.lastpit())
771 if (lyxrc.mac_like_cursor_movement)
772 if (cur.pos() == cur.lastpos())
773 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
775 updated = setCursor(cur, cur.pit(), cur.lastpos());
777 updated = setCursor(cur, cur.pit() + 1, 0);
779 updated = setCursor(cur, cur.pit(), cur.lastpos());
785 /** delete num_spaces characters between from and to. Return the
786 * number of spaces that got physically deleted (not marked as
788 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
789 int num_spaces, bool const trackChanges)
794 // First, delete spaces marked as inserted
796 while (pos < to && num_spaces > 0) {
797 Change const & change = par.lookupChange(pos);
798 if (change.inserted() && change.currentAuthor()) {
799 par.eraseChar(pos, trackChanges);
806 // Then remove remaining spaces
807 int const psize = par.size();
808 par.eraseChars(from, from + num_spaces, trackChanges);
809 return psize - par.size();
815 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
816 Cursor & old, bool & need_anchor_change)
818 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
820 Paragraph & oldpar = old.paragraph();
821 bool const trackChanges = cur.buffer()->params().track_changes;
824 // We do nothing if cursor did not move
825 if (cur.top() == old.top())
828 // We do not do anything on read-only documents
829 if (cur.buffer()->isReadonly())
832 // Whether a common inset is found and whether the cursor is still in
833 // the same paragraph (possibly nested).
834 int const depth = cur.find(&old.inset());
835 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
836 && old.pit() == cur[depth].pit();
839 * (1) If the chars around the old cursor were spaces and the
840 * paragraph is not in free spacing mode, delete some of them, but
841 * only if the cursor has really moved.
844 /* There are still some small problems that can lead to
845 double spaces stored in the document file or space at
846 the beginning of paragraphs(). This happens if you have
847 the cursor between two spaces and then save. Or if you
848 cut and paste and the selection has a space at the
849 beginning and then save right after the paste. (Lgb)
851 if (!oldpar.isFreeSpacing()) {
852 // find range of spaces around cursors
853 pos_type from = old.pos();
855 && oldpar.isLineSeparator(from - 1)
856 && !oldpar.isDeleted(from - 1))
858 pos_type to = old.pos();
859 while (to < old.lastpos()
860 && oldpar.isLineSeparator(to)
861 && !oldpar.isDeleted(to))
864 int num_spaces = to - from;
865 // If we are not at the start of the paragraph, keep one space
866 if (from != to && from > 0)
869 // If cursor is inside range, keep one additional space
870 if (same_par && cur.pos() > from && cur.pos() < to)
873 // Remove spaces and adapt cursor.
874 if (num_spaces > 0) {
877 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
878 // correct cur position
879 // FIXME: there can be other cursors pointing there, we should update them
881 if (cur[depth].pos() >= to)
882 cur[depth].pos() -= deleted;
883 else if (cur[depth].pos() > from)
884 cur[depth].pos() = min(from + 1, old.lastpos());
885 need_anchor_change = true;
892 * (2) If the paragraph where the cursor was is empty, delete it
895 // only do our other magic if we changed paragraph
899 // only do our magic if the paragraph is empty
903 // don't delete anything if this is the ONLY paragraph!
904 if (old.lastpit() == 0)
907 // Do not delete empty paragraphs with keepempty set.
908 if (oldpar.allowEmpty())
912 old.recordUndo(max(old.pit() - 1, pit_type(0)),
913 min(old.pit() + 1, old.lastpit()));
914 ParagraphList & plist = old.text()->paragraphs();
915 bool const soa = oldpar.params().startOfAppendix();
916 plist.erase(plist.iterator_at(old.pit()));
917 // do not lose start of appendix marker (bug 4212)
918 if (soa && old.pit() < pit_type(plist.size()))
919 plist[old.pit()].params().startOfAppendix(true);
921 // see #warning (FIXME?) above
922 if (cur.depth() >= old.depth()) {
923 CursorSlice & curslice = cur[old.depth() - 1];
924 if (&curslice.inset() == &old.inset()
925 && curslice.idx() == old.idx()
926 && curslice.pit() > old.pit()) {
928 // since a paragraph has been deleted, all the
929 // insets after `old' have been copied and
930 // their address has changed. Therefore we
931 // need to `regenerate' cur. (JMarc)
932 cur.updateInsets(&(cur.bottom().inset()));
933 need_anchor_change = true;
941 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
943 pos_type last_pos = pars_[last].size() - 1;
944 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
948 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
949 pos_type first_pos, pos_type last_pos,
952 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
954 for (pit_type pit = first; pit <= last; ++pit) {
955 Paragraph & par = pars_[pit];
958 * (1) Delete consecutive spaces
960 if (!par.isFreeSpacing()) {
961 pos_type from = (pit == first) ? first_pos : 0;
962 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
963 while (from < to_pos) {
965 while (from < par.size()
966 && (!par.isLineSeparator(from) || par.isDeleted(from)))
968 // find string of spaces
970 while (to < par.size()
971 && par.isLineSeparator(to) && !par.isDeleted(to))
973 // empty? We are done
977 int num_spaces = to - from;
979 // If we are not at the extremity of the paragraph, keep one space
980 if (from != to && from > 0 && to < par.size())
983 // Remove spaces if needed
984 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
990 * (2) Delete empty pragraphs
993 // don't delete anything if this is the only remaining paragraph
994 // within the given range. Note: Text::acceptOrRejectChanges()
995 // sets the cursor to 'first' after calling DEPM
999 // don't delete empty paragraphs with keepempty set
1000 if (par.allowEmpty())
1003 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1004 pars_.erase(pars_.iterator_at(pit));