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.
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "FuncRequest.h"
42 #include "Paragraph.h"
43 #include "ParagraphParameters.h"
44 #include "TextClass.h"
45 #include "TextMetrics.h"
48 #include "insets/InsetCollapsable.h"
50 #include "mathed/InsetMathHull.h"
52 #include "support/lassert.h"
53 #include "support/debug.h"
54 #include "support/gettext.h"
55 #include "support/textutils.h"
57 #include <boost/next_prior.hpp>
65 bool Text::isMainText() const
67 return &owner_->buffer().text() == this;
71 // Note that this is supposed to return a fully realized font.
72 FontInfo Text::layoutFont(pit_type const pit) const
74 Layout const & layout = pars_[pit].layout();
76 if (!pars_[pit].getDepth()) {
77 FontInfo lf = layout.resfont;
78 // In case the default family has been customized
79 if (layout.font.family() == INHERIT_FAMILY)
80 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
82 // It ought to be possible here just to use Inset::getLayout() and skip
83 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
84 // now, because Inset::getLayout() will return a default-constructed
85 // InsetLayout, and that e.g. sets the foreground color to red. So we
86 // need to do some work to make that possible.
87 InsetCollapsable const * icp = owner_->asInsetCollapsable();
90 FontInfo icf = icp->getLayout().font();
95 FontInfo font = layout.font;
96 // Realize with the fonts of lesser depth.
97 //font.realize(outerFont(pit, paragraphs()));
98 font.realize(owner_->buffer().params().getFont().fontInfo());
104 // Note that this is supposed to return a fully realized font.
105 FontInfo Text::labelFont(Paragraph const & par) const
107 Buffer const & buffer = owner_->buffer();
108 Layout const & layout = par.layout();
110 if (!par.getDepth()) {
111 FontInfo lf = layout.reslabelfont;
112 // In case the default family has been customized
113 if (layout.labelfont.family() == INHERIT_FAMILY)
114 lf.setFamily(buffer.params().getFont().fontInfo().family());
118 FontInfo font = layout.labelfont;
119 // Realize with the fonts of lesser depth.
120 font.realize(buffer.params().getFont().fontInfo());
126 void Text::setCharFont(pit_type pit,
127 pos_type pos, Font const & fnt, Font const & display_font)
129 Buffer const & buffer = owner_->buffer();
131 Layout const & layout = pars_[pit].layout();
133 // Get concrete layout font to reduce against
136 if (pos < pars_[pit].beginOfBody())
137 layoutfont = layout.labelfont;
139 layoutfont = layout.font;
141 // Realize against environment font information
142 if (pars_[pit].getDepth()) {
144 while (!layoutfont.resolved() &&
145 tp != pit_type(paragraphs().size()) &&
146 pars_[tp].getDepth()) {
147 tp = outerHook(tp, paragraphs());
148 if (tp != pit_type(paragraphs().size()))
149 layoutfont.realize(pars_[tp].layout().font);
153 // Inside inset, apply the inset's font attributes if any
156 layoutfont.realize(display_font.fontInfo());
158 layoutfont.realize(buffer.params().getFont().fontInfo());
160 // Now, reduce font against full layout font
161 font.fontInfo().reduce(layoutfont);
163 pars_[pit].setFont(pos, font);
167 void Text::setInsetFont(BufferView const & bv, pit_type pit,
168 pos_type pos, Font const & font, bool toggleall)
170 Inset * const inset = pars_[pit].getInset(pos);
171 LASSERT(inset && inset->noFontChange(), /**/);
173 CursorSlice::idx_type endidx = inset->nargs();
174 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
175 Text * text = cs.text();
177 // last position of the cell
178 CursorSlice cellend = cs;
179 cellend.pit() = cellend.lastpit();
180 cellend.pos() = cellend.lastpos();
181 text->setFont(bv, cs, cellend, font, toggleall);
187 // return past-the-last paragraph influenced by a layout change on pit
188 pit_type Text::undoSpan(pit_type pit)
190 pit_type const end = paragraphs().size();
191 pit_type nextpit = pit + 1;
194 //because of parindents
195 if (!pars_[pit].getDepth())
196 return boost::next(nextpit);
197 //because of depth constrains
198 for (; nextpit != end; ++pit, ++nextpit) {
199 if (!pars_[pit].getDepth())
206 void Text::setLayout(pit_type start, pit_type end,
207 docstring const & layout)
209 LASSERT(start != end, /**/);
211 Buffer const & buffer = owner_->buffer();
212 BufferParams const & bp = buffer.params();
213 Layout const & lyxlayout = bp.documentClass()[layout];
215 for (pit_type pit = start; pit != end; ++pit) {
216 Paragraph & par = pars_[pit];
217 par.applyLayout(lyxlayout);
218 if (lyxlayout.margintype == MARGIN_MANUAL)
219 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
224 // set layout over selection and make a total rebreak of those paragraphs
225 void Text::setLayout(Cursor & cur, docstring const & layout)
227 LASSERT(this == cur.text(), /**/);
229 pit_type start = cur.selBegin().pit();
230 pit_type end = cur.selEnd().pit() + 1;
231 pit_type undopit = undoSpan(end - 1);
232 recUndo(cur, start, undopit - 1);
233 setLayout(start, end, layout);
234 cur.buffer()->updateLabels();
238 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
239 Paragraph const & par, int max_depth)
241 if (par.layout().labeltype == LABEL_BIBLIO)
243 int const depth = par.params().depth();
244 if (type == Text::INC_DEPTH && depth < max_depth)
246 if (type == Text::DEC_DEPTH && depth > 0)
252 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
254 LASSERT(this == cur.text(), /**/);
255 // this happens when selecting several cells in tabular (bug 2630)
256 if (cur.selBegin().idx() != cur.selEnd().idx())
259 pit_type const beg = cur.selBegin().pit();
260 pit_type const end = cur.selEnd().pit() + 1;
261 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
263 for (pit_type pit = beg; pit != end; ++pit) {
264 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
266 max_depth = pars_[pit].getMaxDepthAfter();
272 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
274 LASSERT(this == cur.text(), /**/);
275 pit_type const beg = cur.selBegin().pit();
276 pit_type const end = cur.selEnd().pit() + 1;
277 cur.recordUndoSelection();
278 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
280 for (pit_type pit = beg; pit != end; ++pit) {
281 Paragraph & par = pars_[pit];
282 if (lyx::changeDepthAllowed(type, par, max_depth)) {
283 int const depth = par.params().depth();
284 if (type == INC_DEPTH)
285 par.params().depth(depth + 1);
287 par.params().depth(depth - 1);
289 max_depth = par.getMaxDepthAfter();
291 // this handles the counter labels, and also fixes up
292 // depth values for follow-on (child) paragraphs
293 cur.buffer()->updateLabels();
297 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
299 LASSERT(this == cur.text(), /**/);
300 // Set the current_font
301 // Determine basis font
303 pit_type pit = cur.pit();
304 if (cur.pos() < pars_[pit].beginOfBody())
305 layoutfont = labelFont(pars_[pit]);
307 layoutfont = layoutFont(pit);
309 // Update current font
310 cur.real_current_font.update(font,
311 cur.buffer()->params().language,
314 // Reduce to implicit settings
315 cur.current_font = cur.real_current_font;
316 cur.current_font.fontInfo().reduce(layoutfont);
317 // And resolve it completely
318 cur.real_current_font.fontInfo().realize(layoutfont);
320 // if there is no selection that's all we need to do
321 if (!cur.selection())
324 // Ok, we have a selection.
325 cur.recordUndoSelection();
327 setFont(cur.bv(), cur.selectionBegin().top(),
328 cur.selectionEnd().top(), font, toggleall);
332 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
333 CursorSlice const & end, Font const & font,
336 Buffer const & buffer = bv.buffer();
338 // Don't use forwardChar here as ditend might have
339 // pos() == lastpos() and forwardChar would miss it.
340 // Can't use forwardPos either as this descends into
342 Language const * language = buffer.params().language;
343 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
344 if (dit.pos() == dit.lastpos())
346 pit_type const pit = dit.pit();
347 pos_type const pos = dit.pos();
348 Inset * inset = pars_[pit].getInset(pos);
349 if (inset && inset->noFontChange()) {
350 // We need to propagate the font change to all
351 // text cells of the inset (bug 1973).
352 // FIXME: This should change, see documentation
353 // of noFontChange in Inset.h
354 setInsetFont(bv, pit, pos, font, toggleall);
356 TextMetrics const & tm = bv.textMetrics(this);
357 Font f = tm.displayFont(pit, pos);
358 f.update(font, language, toggleall);
359 setCharFont(pit, pos, f, tm.font_);
364 bool Text::cursorTop(Cursor & cur)
366 LASSERT(this == cur.text(), /**/);
367 return setCursor(cur, 0, 0);
371 bool Text::cursorBottom(Cursor & cur)
373 LASSERT(this == cur.text(), /**/);
374 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
378 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
380 LASSERT(this == cur.text(), /**/);
381 // If the mask is completely neutral, tell user
382 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
383 // Could only happen with user style
384 cur.message(_("No font change defined."));
388 // Try implicit word selection
389 // If there is a change in the language the implicit word selection
391 CursorSlice const resetCursor = cur.top();
392 bool const implicitSelection =
393 font.language() == ignore_language
394 && font.fontInfo().number() == FONT_IGNORE
395 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
398 setFont(cur, font, toggleall);
400 // Implicit selections are cleared afterwards
401 // and cursor is set to the original position.
402 if (implicitSelection) {
403 cur.clearSelection();
404 cur.top() = resetCursor;
410 docstring Text::getStringToIndex(Cursor const & cur)
412 LASSERT(this == cur.text(), /**/);
415 return cur.selectionAsString(false);
417 // Try implicit word selection. If there is a change
418 // in the language the implicit word selection is
421 selectWord(tmpcur, PREVIOUS_WORD);
423 if (!tmpcur.selection())
424 cur.message(_("Nothing to index!"));
425 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
426 cur.message(_("Cannot index more than one paragraph!"));
428 return tmpcur.selectionAsString(false);
434 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
436 LASSERT(cur.text(), /**/);
437 // make sure that the depth behind the selection are restored, too
438 pit_type undopit = undoSpan(cur.selEnd().pit());
439 recUndo(cur, cur.selBegin().pit(), undopit - 1);
442 string const argument = to_utf8(arg);
443 depth_type priordepth = -1;
445 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
447 Paragraph & par = pars_[pit];
448 ParagraphParameters params = par.params();
449 params.read(argument, merge);
450 // Changes to label width string apply to all paragraphs
451 // with same layout in a sequence.
452 // Do this only once for a selected range of paragraphs
453 // of the same layout and depth.
454 if (par.getDepth() != priordepth || par.layout() != priorlayout)
455 setLabelWidthStringToSequence(pit, pars_,
456 params.labelWidthString());
457 par.params().apply(params, par.layout());
458 priordepth = par.getDepth();
459 priorlayout = par.layout();
464 //FIXME This is a little redundant now, but it's probably worth keeping,
465 //especially if we're going to go away from using serialization internally
467 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
469 LASSERT(cur.text(), /**/);
470 // make sure that the depth behind the selection are restored, too
471 pit_type undopit = undoSpan(cur.selEnd().pit());
472 recUndo(cur, cur.selBegin().pit(), undopit - 1);
474 depth_type priordepth = -1;
476 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
478 Paragraph & par = pars_[pit];
479 // Changes to label width string apply to all paragraphs
480 // with same layout in a sequence.
481 // Do this only once for a selected range of paragraphs
482 // of the same layout and depth.
483 if (par.getDepth() != priordepth || par.layout() != priorlayout)
484 setLabelWidthStringToSequence(pit, pars_,
485 par.params().labelWidthString());
486 par.params().apply(p, par.layout());
487 priordepth = par.getDepth();
488 priorlayout = par.layout();
493 // this really should just insert the inset and not move the cursor.
494 void Text::insertInset(Cursor & cur, Inset * inset)
496 LASSERT(this == cur.text(), /**/);
497 LASSERT(inset, /**/);
498 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
499 Change(cur.buffer()->params().trackChanges
500 ? Change::INSERTED : Change::UNCHANGED));
504 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
505 bool setfont, bool boundary)
507 TextMetrics const & tm = cur.bv().textMetrics(this);
508 bool const update_needed = !tm.contains(par);
510 setCursorIntern(cur, par, pos, setfont, boundary);
511 return cur.bv().checkDepm(cur, old) || update_needed;
515 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
517 LASSERT(par != int(paragraphs().size()), /**/);
521 // now some strict checking
522 Paragraph & para = getPar(par);
524 // None of these should happen, but we're scaredy-cats
526 lyxerr << "dont like -1" << endl;
527 LASSERT(false, /**/);
530 if (pos > para.size()) {
531 lyxerr << "dont like 1, pos: " << pos
532 << " size: " << para.size()
533 << " par: " << par << endl;
534 LASSERT(false, /**/);
539 void Text::setCursorIntern(Cursor & cur,
540 pit_type par, pos_type pos, bool setfont, bool boundary)
542 LASSERT(this == cur.text(), /**/);
543 cur.boundary(boundary);
544 setCursor(cur.top(), par, pos);
546 cur.setCurrentFont();
550 bool Text::checkAndActivateInset(Cursor & cur, bool front)
554 if (front && cur.pos() == cur.lastpos())
556 if (!front && cur.pos() == 0)
558 Inset * inset = front ? cur.nextInset() : cur.prevInset();
559 if (!inset || !inset->editable())
562 * Apparently, when entering an inset we are expected to be positioned
563 * *before* it in the containing paragraph, regardless of the direction
564 * from which we are entering. Otherwise, cursor placement goes awry,
565 * and when we exit from the beginning, we'll be placed *after* the
570 inset->edit(cur, front);
575 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
581 if (cur.pos() == cur.lastpos())
583 Paragraph & par = cur.paragraph();
584 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
585 if (!inset || !inset->editable())
587 inset->edit(cur, movingForward,
588 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
593 bool Text::cursorBackward(Cursor & cur)
595 // Tell BufferView to test for FitCursor in any case!
596 cur.updateFlags(Update::FitCursor);
598 // not at paragraph start?
600 // if on right side of boundary (i.e. not at paragraph end, but line end)
601 // -> skip it, i.e. set boundary to true, i.e. go only logically left
602 // there are some exceptions to ignore this: lineseps, newlines, spaces
604 // some effectless debug code to see the values in the debugger
605 bool bound = cur.boundary();
606 int rowpos = cur.textRow().pos();
608 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
609 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
610 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
612 if (!cur.boundary() &&
613 cur.textRow().pos() == cur.pos() &&
614 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
615 !cur.paragraph().isNewline(cur.pos() - 1) &&
616 !cur.paragraph().isSeparator(cur.pos() - 1)) {
617 return setCursor(cur, cur.pit(), cur.pos(), true, true);
620 // go left and try to enter inset
621 if (checkAndActivateInset(cur, false))
624 // normal character left
625 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
628 // move to the previous paragraph or do nothing
630 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
635 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
637 Cursor temp_cur = cur;
638 temp_cur.posVisLeft(skip_inset);
639 if (temp_cur.depth() > cur.depth()) {
643 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
644 true, temp_cur.boundary());
648 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
650 Cursor temp_cur = cur;
651 temp_cur.posVisRight(skip_inset);
652 if (temp_cur.depth() > cur.depth()) {
656 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
657 true, temp_cur.boundary());
661 bool Text::cursorForward(Cursor & cur)
663 // Tell BufferView to test for FitCursor in any case!
664 cur.updateFlags(Update::FitCursor);
666 // not at paragraph end?
667 if (cur.pos() != cur.lastpos()) {
668 // in front of editable inset, i.e. jump into it?
669 if (checkAndActivateInset(cur, true))
672 TextMetrics const & tm = cur.bv().textMetrics(this);
673 // if left of boundary -> just jump to right side
674 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
675 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
676 return setCursor(cur, cur.pit(), cur.pos(), true, false);
678 // next position is left of boundary,
679 // but go to next line for special cases like space, newline, linesep
681 // some effectless debug code to see the values in the debugger
682 int endpos = cur.textRow().endpos();
683 int lastpos = cur.lastpos();
685 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
686 bool newline = cur.paragraph().isNewline(cur.pos());
687 bool sep = cur.paragraph().isSeparator(cur.pos());
688 if (cur.pos() != cur.lastpos()) {
689 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
690 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
691 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
694 if (cur.textRow().endpos() == cur.pos() + 1 &&
695 cur.textRow().endpos() != cur.lastpos() &&
696 !cur.paragraph().isNewline(cur.pos()) &&
697 !cur.paragraph().isLineSeparator(cur.pos()) &&
698 !cur.paragraph().isSeparator(cur.pos())) {
699 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
702 // in front of RTL boundary? Stay on this side of the boundary because:
703 // ab|cDDEEFFghi -> abc|DDEEFFghi
704 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
705 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
708 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
711 // move to next paragraph
712 if (cur.pit() != cur.lastpit())
713 return setCursor(cur, cur.pit() + 1, 0, true, false);
718 bool Text::cursorUpParagraph(Cursor & cur)
720 bool updated = false;
722 updated = setCursor(cur, cur.pit(), 0);
723 else if (cur.pit() != 0)
724 updated = setCursor(cur, cur.pit() - 1, 0);
729 bool Text::cursorDownParagraph(Cursor & cur)
731 bool updated = false;
732 if (cur.pit() != cur.lastpit())
733 updated = setCursor(cur, cur.pit() + 1, 0);
735 updated = setCursor(cur, cur.pit(), cur.lastpos());
740 // fix the cursor `cur' after a characters has been deleted at `where'
741 // position. Called by deleteEmptyParagraphMechanism
742 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
744 // Do nothing if cursor is not in the paragraph where the
746 if (cur.pit() != where.pit())
749 // If cursor position is after the deletion place update it
750 if (cur.pos() > where.pos())
753 // Check also if we don't want to set the cursor on a spot behind the
754 // pagragraph because we erased the last character.
755 if (cur.pos() > cur.lastpos())
756 cur.pos() = cur.lastpos();
760 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
761 Cursor & old, bool & need_anchor_change)
763 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
765 Paragraph & oldpar = old.paragraph();
767 // We allow all kinds of "mumbo-jumbo" when freespacing.
768 if (oldpar.isFreeSpacing())
771 /* Ok I'll put some comments here about what is missing.
772 There are still some small problems that can lead to
773 double spaces stored in the document file or space at
774 the beginning of paragraphs(). This happens if you have
775 the cursor between to spaces and then save. Or if you
776 cut and paste and the selection have a space at the
777 beginning and then save right after the paste. (Lgb)
780 // If old.pos() == 0 and old.pos()(1) == LineSeparator
781 // delete the LineSeparator.
784 // If old.pos() == 1 and old.pos()(0) == LineSeparator
785 // delete the LineSeparator.
788 // Find a common inset and the corresponding depth.
790 for (; depth < cur.depth(); ++depth)
791 if (&old.inset() == &cur[depth].inset())
794 // Whether a common inset is found and whether the cursor is still in
795 // the same paragraph (possibly nested).
796 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
797 bool const same_par_pos = depth == cur.depth() - 1 && same_par
798 && old.pos() == cur[depth].pos();
800 // If the chars around the old cursor were spaces, delete one of them.
802 // Only if the cursor has really moved.
804 && old.pos() < oldpar.size()
805 && oldpar.isLineSeparator(old.pos())
806 && oldpar.isLineSeparator(old.pos() - 1)
807 && !oldpar.isDeleted(old.pos() - 1)
808 && !oldpar.isDeleted(old.pos())) {
809 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
810 // FIXME: This will not work anymore when we have multiple views of the same buffer
811 // In this case, we will have to correct also the cursors held by
812 // other bufferviews. It will probably be easier to do that in a more
813 // automated way in CursorSlice code. (JMarc 26/09/2001)
814 // correct all cursor parts
816 fixCursorAfterDelete(cur[depth], old.top());
817 need_anchor_change = true;
823 // only do our magic if we changed paragraph
827 // don't delete anything if this is the ONLY paragraph!
828 if (old.lastpit() == 0)
831 // Do not delete empty paragraphs with keepempty set.
832 if (oldpar.allowEmpty())
835 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
837 old.recordUndo(ATOMIC_UNDO,
838 max(old.pit() - 1, pit_type(0)),
839 min(old.pit() + 1, old.lastpit()));
840 ParagraphList & plist = old.text()->paragraphs();
841 bool const soa = oldpar.params().startOfAppendix();
842 plist.erase(boost::next(plist.begin(), old.pit()));
843 // do not lose start of appendix marker (bug 4212)
844 if (soa && old.pit() < pit_type(plist.size()))
845 plist[old.pit()].params().startOfAppendix(true);
847 // see #warning (FIXME?) above
848 if (cur.depth() >= old.depth()) {
849 CursorSlice & curslice = cur[old.depth() - 1];
850 if (&curslice.inset() == &old.inset()
851 && curslice.pit() > old.pit()) {
853 // since a paragraph has been deleted, all the
854 // insets after `old' have been copied and
855 // their address has changed. Therefore we
856 // need to `regenerate' cur. (JMarc)
857 cur.updateInsets(&(cur.bottom().inset()));
858 need_anchor_change = true;
864 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
865 need_anchor_change = true;
866 // We return true here because the Paragraph contents changed and
867 // we need a redraw before further action is processed.
875 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
877 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
879 for (pit_type pit = first; pit <= last; ++pit) {
880 Paragraph & par = pars_[pit];
882 // We allow all kinds of "mumbo-jumbo" when freespacing.
883 if (par.isFreeSpacing())
886 for (pos_type pos = 1; pos < par.size(); ++pos) {
887 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
888 && !par.isDeleted(pos - 1)) {
889 if (par.eraseChar(pos - 1, trackChanges)) {
895 // don't delete anything if this is the only remaining paragraph within the given range
896 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
900 // don't delete empty paragraphs with keepempty set
901 if (par.allowEmpty())
904 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
905 pars_.erase(boost::next(pars_.begin(), pit));
911 par.stripLeadingSpaces(trackChanges);
916 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
918 cur.recordUndo(ATOMIC_UNDO, first, last);
922 void Text::recUndo(Cursor & cur, pit_type par) const
924 cur.recordUndo(ATOMIC_UNDO, par, par);