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/InsetCollapsable.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));
192 // set layout over selection and make a total rebreak of those paragraphs
193 void Text::setLayout(Cursor & cur, docstring const & layout)
195 LBUFERR(this == cur.text());
197 pit_type start = cur.selBegin().pit();
198 pit_type end = cur.selEnd().pit() + 1;
199 cur.recordUndoSelection();
200 setLayout(start, end, layout);
201 cur.setCurrentFont();
202 cur.forceBufferUpdate();
206 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
207 Paragraph const & par, int max_depth)
209 int const depth = par.params().depth();
210 if (type == Text::INC_DEPTH && depth < max_depth)
212 if (type == Text::DEC_DEPTH && depth > 0)
218 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
220 LBUFERR(this == cur.text());
221 // this happens when selecting several cells in tabular (bug 2630)
222 if (cur.selBegin().idx() != cur.selEnd().idx())
225 pit_type const beg = cur.selBegin().pit();
226 pit_type const end = cur.selEnd().pit() + 1;
227 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
229 for (pit_type pit = beg; pit != end; ++pit) {
230 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
232 max_depth = pars_[pit].getMaxDepthAfter();
238 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
240 LBUFERR(this == cur.text());
241 pit_type const beg = cur.selBegin().pit();
242 pit_type const end = cur.selEnd().pit() + 1;
243 cur.recordUndoSelection();
244 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
246 for (pit_type pit = beg; pit != end; ++pit) {
247 Paragraph & par = pars_[pit];
248 if (lyx::changeDepthAllowed(type, par, max_depth)) {
249 int const depth = par.params().depth();
250 if (type == INC_DEPTH)
251 par.params().depth(depth + 1);
253 par.params().depth(depth - 1);
255 max_depth = par.getMaxDepthAfter();
257 // this handles the counter labels, and also fixes up
258 // depth values for follow-on (child) paragraphs
259 cur.forceBufferUpdate();
263 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
265 LASSERT(this == cur.text(), return);
267 // If there is a selection, record undo before the cursor font is changed.
269 cur.recordUndoSelection();
271 // Set the current_font
272 // Determine basis font
274 pit_type pit = cur.pit();
275 if (cur.pos() < pars_[pit].beginOfBody())
276 layoutfont = labelFont(pars_[pit]);
278 layoutfont = layoutFont(pit);
280 // Update current font
281 cur.real_current_font.update(font,
282 cur.buffer()->params().language,
285 // Reduce to implicit settings
286 cur.current_font = cur.real_current_font;
287 cur.current_font.fontInfo().reduce(layoutfont);
288 // And resolve it completely
289 cur.real_current_font.fontInfo().realize(layoutfont);
291 // if there is no selection that's all we need to do
292 if (!cur.selection())
295 // Ok, we have a selection.
299 // Toggling behaves as follows: We check the first character of the
300 // selection. If it's (say) got EMPH on, then we set to off; if off,
301 // then to on. With families and the like, we set it to INHERIT, if
302 // we already have it.
303 CursorSlice const & sl = cur.selBegin();
304 Text const & text = *sl.text();
305 Paragraph const & par = text.getPar(sl.pit());
307 // get font at the position
308 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
309 text.outerFont(sl.pit()));
310 FontInfo const & oldfi = oldfont.fontInfo();
312 FontInfo & newfi = newfont.fontInfo();
314 FontFamily newfam = newfi.family();
315 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
316 newfam == oldfi.family())
317 newfi.setFamily(INHERIT_FAMILY);
319 FontSeries newser = newfi.series();
320 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
321 newfi.setSeries(INHERIT_SERIES);
323 FontShape newshp = newfi.shape();
324 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
325 newshp == oldfi.shape())
326 newfi.setShape(INHERIT_SHAPE);
328 ColorCode newcol = newfi.color();
329 if (newcol != Color_none && newcol != Color_inherit
330 && newcol != Color_ignore && newcol == oldfi.color())
331 newfi.setColor(Color_none);
334 if (newfi.emph() == FONT_TOGGLE)
335 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
336 if (newfi.underbar() == FONT_TOGGLE)
337 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
338 if (newfi.strikeout() == FONT_TOGGLE)
339 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.uuline() == FONT_TOGGLE)
341 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.uwave() == FONT_TOGGLE)
343 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.noun() == FONT_TOGGLE)
345 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.number() == FONT_TOGGLE)
347 newfi.setNumber(oldfi.number() == 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;
433 docstring Text::getStringToIndex(Cursor const & cur)
435 LBUFERR(this == cur.text());
438 return cur.selectionAsString(false);
440 // Try implicit word selection. If there is a change
441 // in the language the implicit word selection is
444 selectWord(tmpcur, PREVIOUS_WORD);
446 if (!tmpcur.selection())
447 cur.message(_("Nothing to index!"));
448 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
449 cur.message(_("Cannot index more than one paragraph!"));
451 return tmpcur.selectionAsString(false);
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 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 par, pos_type pos,
551 bool setfont, bool boundary)
553 TextMetrics const & tm = cur.bv().textMetrics(this);
554 bool const update_needed = !tm.contains(par);
556 setCursorIntern(cur, par, pos, setfont, boundary);
557 return cur.bv().checkDepm(cur, old) || update_needed;
561 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
563 LASSERT(par != int(paragraphs().size()), return);
567 // now some strict checking
568 Paragraph & para = getPar(par);
570 // None of these should happen, but we're scaredy-cats
572 LYXERR0("Don't like -1!");
576 if (pos > para.size()) {
577 LYXERR0("Don't like 1, pos: " << pos
578 << " size: " << para.size()
585 void Text::setCursorIntern(Cursor & cur,
586 pit_type par, pos_type pos, bool setfont, bool boundary)
588 LBUFERR(this == cur.text());
589 cur.boundary(boundary);
590 setCursor(cur.top(), par, pos);
592 cur.setCurrentFont();
596 bool Text::checkAndActivateInset(Cursor & cur, bool front)
598 if (front && cur.pos() == cur.lastpos())
600 if (!front && cur.pos() == 0)
602 Inset * inset = front ? cur.nextInset() : cur.prevInset();
603 if (!inset || !inset->editable())
605 if (cur.selection() && cur.realAnchor().find(inset) == -1)
608 * Apparently, when entering an inset we are expected to be positioned
609 * *before* it in the containing paragraph, regardless of the direction
610 * from which we are entering. Otherwise, cursor placement goes awry,
611 * and when we exit from the beginning, we'll be placed *after* the
616 inset->edit(cur, front);
617 cur.setCurrentFont();
623 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
627 if (cur.pos() == cur.lastpos())
629 Paragraph & par = cur.paragraph();
630 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
631 if (!inset || !inset->editable())
633 if (cur.selection() && cur.realAnchor().find(inset) == -1)
635 inset->edit(cur, movingForward,
636 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
637 cur.setCurrentFont();
643 bool Text::cursorBackward(Cursor & cur)
645 // Tell BufferView to test for FitCursor in any case!
646 cur.screenUpdateFlags(Update::FitCursor);
648 // not at paragraph start?
650 // if on right side of boundary (i.e. not at paragraph end, but line end)
651 // -> skip it, i.e. set boundary to true, i.e. go only logically left
652 // there are some exceptions to ignore this: lineseps, newlines, spaces
654 // some effectless debug code to see the values in the debugger
655 bool bound = cur.boundary();
656 int rowpos = cur.textRow().pos();
658 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
659 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
660 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
662 if (!cur.boundary() &&
663 cur.textRow().pos() == cur.pos() &&
664 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
665 !cur.paragraph().isNewline(cur.pos() - 1) &&
666 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
667 !cur.paragraph().isSeparator(cur.pos() - 1)) {
668 return setCursor(cur, cur.pit(), cur.pos(), true, true);
671 // go left and try to enter inset
672 if (checkAndActivateInset(cur, false))
675 // normal character left
676 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
679 // move to the previous paragraph or do nothing
681 Paragraph & par = getPar(cur.pit() - 1);
682 pos_type lastpos = par.size();
683 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
684 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
686 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
692 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
694 Cursor temp_cur = cur;
695 temp_cur.posVisLeft(skip_inset);
696 if (temp_cur.depth() > cur.depth()) {
700 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
701 true, temp_cur.boundary());
705 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
707 Cursor temp_cur = cur;
708 temp_cur.posVisRight(skip_inset);
709 if (temp_cur.depth() > cur.depth()) {
713 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
714 true, temp_cur.boundary());
718 bool Text::cursorForward(Cursor & cur)
720 // Tell BufferView to test for FitCursor in any case!
721 cur.screenUpdateFlags(Update::FitCursor);
723 // not at paragraph end?
724 if (cur.pos() != cur.lastpos()) {
725 // in front of editable inset, i.e. jump into it?
726 if (checkAndActivateInset(cur, true))
729 TextMetrics const & tm = cur.bv().textMetrics(this);
730 // if left of boundary -> just jump to right side
731 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
732 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
733 return setCursor(cur, cur.pit(), cur.pos(), true, false);
735 // next position is left of boundary,
736 // but go to next line for special cases like space, newline, linesep
738 // some effectless debug code to see the values in the debugger
739 int endpos = cur.textRow().endpos();
740 int lastpos = cur.lastpos();
742 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
743 bool newline = cur.paragraph().isNewline(cur.pos());
744 bool sep = cur.paragraph().isSeparator(cur.pos());
745 if (cur.pos() != cur.lastpos()) {
746 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
747 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
748 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
751 if (cur.textRow().endpos() == cur.pos() + 1) {
752 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
753 cur.pos() + 1 == cur.lastpos() &&
754 cur.pit() != cur.lastpit()) {
755 // move to next paragraph
756 return setCursor(cur, cur.pit() + 1, 0, true, false);
757 } else if (cur.textRow().endpos() != cur.lastpos() &&
758 !cur.paragraph().isNewline(cur.pos()) &&
759 !cur.paragraph().isEnvSeparator(cur.pos()) &&
760 !cur.paragraph().isLineSeparator(cur.pos()) &&
761 !cur.paragraph().isSeparator(cur.pos())) {
762 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
766 // in front of RTL boundary? Stay on this side of the boundary because:
767 // ab|cDDEEFFghi -> abc|DDEEFFghi
768 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
769 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
772 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
775 // move to next paragraph
776 if (cur.pit() != cur.lastpit())
777 return setCursor(cur, cur.pit() + 1, 0, true, false);
782 bool Text::cursorUpParagraph(Cursor & cur)
784 bool updated = false;
786 updated = setCursor(cur, cur.pit(), 0);
787 else if (cur.pit() != 0)
788 updated = setCursor(cur, cur.pit() - 1, 0);
793 bool Text::cursorDownParagraph(Cursor & cur)
795 bool updated = false;
796 if (cur.pit() != cur.lastpit())
797 if (lyxrc.mac_like_cursor_movement)
798 if (cur.pos() == cur.lastpos())
799 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
801 updated = setCursor(cur, cur.pit(), cur.lastpos());
803 updated = setCursor(cur, cur.pit() + 1, 0);
805 updated = setCursor(cur, cur.pit(), cur.lastpos());
810 // fix the cursor `cur' after a characters has been deleted at `where'
811 // position. Called by deleteEmptyParagraphMechanism
812 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
814 // Do nothing if cursor is not in the paragraph where the
815 // deletion occurred,
816 if (cur.pit() != where.pit())
819 // If cursor position is after the deletion place update it
820 if (cur.pos() > where.pos())
823 // Check also if we don't want to set the cursor on a spot behind the
824 // pagragraph because we erased the last character.
825 if (cur.pos() > cur.lastpos())
826 cur.pos() = cur.lastpos();
830 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
831 Cursor & old, bool & need_anchor_change)
833 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
835 Paragraph & oldpar = old.paragraph();
837 // We allow all kinds of "mumbo-jumbo" when freespacing.
838 if (oldpar.isFreeSpacing())
841 /* Ok I'll put some comments here about what is missing.
842 There are still some small problems that can lead to
843 double spaces stored in the document file or space at
844 the beginning of paragraphs(). This happens if you have
845 the cursor between to spaces and then save. Or if you
846 cut and paste and the selection have a space at the
847 beginning and then save right after the paste. (Lgb)
850 // If old.pos() == 0 and old.pos()(1) == LineSeparator
851 // delete the LineSeparator.
854 // If old.pos() == 1 and old.pos()(0) == LineSeparator
855 // delete the LineSeparator.
858 // Find a common inset and the corresponding depth.
860 for (; depth < cur.depth(); ++depth)
861 if (&old.inset() == &cur[depth].inset())
864 // Whether a common inset is found and whether the cursor is still in
865 // the same paragraph (possibly nested).
866 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
867 bool const same_par_pos = depth == cur.depth() - 1 && same_par
868 && old.pos() == cur[depth].pos();
870 // If the chars around the old cursor were spaces, delete one of them.
872 // Only if the cursor has really moved.
874 && old.pos() < oldpar.size()
875 && oldpar.isLineSeparator(old.pos())
876 && oldpar.isLineSeparator(old.pos() - 1)
877 && !oldpar.isDeleted(old.pos() - 1)
878 && !oldpar.isDeleted(old.pos())) {
879 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
880 // FIXME: This will not work anymore when we have multiple views of the same buffer
881 // In this case, we will have to correct also the cursors held by
882 // other bufferviews. It will probably be easier to do that in a more
883 // automated way in CursorSlice code. (JMarc 26/09/2001)
884 // correct all cursor parts
886 fixCursorAfterDelete(cur[depth], old.top());
887 need_anchor_change = true;
893 // only do our magic if we changed paragraph
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())
905 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
907 old.recordUndo(max(old.pit() - 1, pit_type(0)),
908 min(old.pit() + 1, old.lastpit()));
909 ParagraphList & plist = old.text()->paragraphs();
910 bool const soa = oldpar.params().startOfAppendix();
911 plist.erase(lyx::next(plist.begin(), old.pit()));
912 // do not lose start of appendix marker (bug 4212)
913 if (soa && old.pit() < pit_type(plist.size()))
914 plist[old.pit()].params().startOfAppendix(true);
916 // see #warning (FIXME?) above
917 if (cur.depth() >= old.depth()) {
918 CursorSlice & curslice = cur[old.depth() - 1];
919 if (&curslice.inset() == &old.inset()
920 && curslice.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;
933 if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
934 need_anchor_change = true;
935 // We return true here because the Paragraph contents changed and
936 // we need a redraw before further action is processed.
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())
955 for (pos_type pos = 1; pos < par.size(); ++pos) {
956 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
957 && !par.isDeleted(pos - 1)) {
958 if (par.eraseChar(pos - 1, trackChanges)) {
964 // don't delete anything if this is the only remaining paragraph
965 // within the given range. Note: Text::acceptOrRejectChanges()
966 // sets the cursor to 'first' after calling DEPM
970 // don't delete empty paragraphs with keepempty set
971 if (par.allowEmpty())
974 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
975 pars_.erase(lyx::next(pars_.begin(), pit));
981 par.stripLeadingSpaces(trackChanges);