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 "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "TextClass.h"
46 #include "TextMetrics.h"
49 #include "insets/InsetCollapsable.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/lassert.h"
54 #include "support/debug.h"
55 #include "support/gettext.h"
56 #include "support/textutils.h"
58 #include <boost/next_prior.hpp>
67 : autoBreakRows_(false)
71 bool Text::isMainText(Buffer const & buffer) const
73 return &buffer.text() == this;
77 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
79 Layout const & layout = pars_[pit].layout();
81 if (!pars_[pit].getDepth()) {
82 FontInfo lf = layout.resfont;
83 // In case the default family has been customized
84 if (layout.font.family() == INHERIT_FAMILY)
85 lf.setFamily(buffer.params().getFont().fontInfo().family());
86 InsetCollapsable const * icp = pars_[pit].inInset()->asInsetCollapsable();
88 lf.update(icp->getLayout().font(), false);
92 FontInfo font = layout.font;
93 // Realize with the fonts of lesser depth.
94 //font.realize(outerFont(pit, paragraphs()));
95 font.realize(buffer.params().getFont().fontInfo());
101 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
103 Layout const & layout = par.layout();
105 if (!par.getDepth()) {
106 FontInfo lf = layout.reslabelfont;
107 // In case the default family has been customized
108 if (layout.labelfont.family() == INHERIT_FAMILY)
109 lf.setFamily(buffer.params().getFont().fontInfo().family());
110 InsetCollapsable const * icp = par.inInset()->asInsetCollapsable();
112 lf.update(icp->getLayout().labelfont(), false);
116 FontInfo font = layout.labelfont;
117 // Realize with the fonts of lesser depth.
118 font.realize(buffer.params().getFont().fontInfo());
124 void Text::setCharFont(Buffer const & buffer, pit_type pit,
125 pos_type pos, Font const & fnt, Font const & display_font)
128 Layout const & layout = pars_[pit].layout();
130 // Get concrete layout font to reduce against
133 if (pos < pars_[pit].beginOfBody())
134 layoutfont = layout.labelfont;
136 layoutfont = layout.font;
138 // Realize against environment font information
139 if (pars_[pit].getDepth()) {
141 while (!layoutfont.resolved() &&
142 tp != pit_type(paragraphs().size()) &&
143 pars_[tp].getDepth()) {
144 tp = outerHook(tp, paragraphs());
145 if (tp != pit_type(paragraphs().size()))
146 layoutfont.realize(pars_[tp].layout().font);
150 // Inside inset, apply the inset's font attributes if any
152 if (!isMainText(buffer))
153 layoutfont.realize(display_font.fontInfo());
155 layoutfont.realize(buffer.params().getFont().fontInfo());
157 // Now, reduce font against full layout font
158 font.fontInfo().reduce(layoutfont);
160 pars_[pit].setFont(pos, font);
164 void Text::setInsetFont(BufferView const & bv, pit_type pit,
165 pos_type pos, Font const & font, bool toggleall)
167 Inset * const inset = pars_[pit].getInset(pos);
168 LASSERT(inset && inset->noFontChange(), /**/);
170 CursorSlice::idx_type endidx = inset->nargs();
171 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
172 Text * text = cs.text();
174 // last position of the cell
175 CursorSlice cellend = cs;
176 cellend.pit() = cellend.lastpit();
177 cellend.pos() = cellend.lastpos();
178 text->setFont(bv, cs, cellend, font, toggleall);
184 // return past-the-last paragraph influenced by a layout change on pit
185 pit_type Text::undoSpan(pit_type pit)
187 pit_type end = paragraphs().size();
188 pit_type nextpit = pit + 1;
191 //because of parindents
192 if (!pars_[pit].getDepth())
193 return boost::next(nextpit);
194 //because of depth constrains
195 for (; nextpit != end; ++pit, ++nextpit) {
196 if (!pars_[pit].getDepth())
203 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
204 docstring const & layout)
206 LASSERT(start != end, /**/);
208 BufferParams const & bufparams = buffer.params();
209 Layout const & lyxlayout = bufparams.documentClass()[layout];
211 for (pit_type pit = start; pit != end; ++pit) {
212 Paragraph & par = pars_[pit];
213 par.applyLayout(lyxlayout);
214 if (lyxlayout.margintype == MARGIN_MANUAL)
215 par.setLabelWidthString(par.translateIfPossible(
216 lyxlayout.labelstring(), buffer.params()));
221 // set layout over selection and make a total rebreak of those paragraphs
222 void Text::setLayout(Cursor & cur, docstring const & layout)
224 LASSERT(this == cur.text(), /**/);
226 pit_type start = cur.selBegin().pit();
227 pit_type end = cur.selEnd().pit() + 1;
228 pit_type undopit = undoSpan(end - 1);
229 recUndo(cur, start, undopit - 1);
230 setLayout(cur.buffer(), start, end, layout);
231 updateLabels(cur.buffer());
235 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
236 Paragraph const & par, int max_depth)
238 if (par.layout().labeltype == LABEL_BIBLIO)
240 int const depth = par.params().depth();
241 if (type == Text::INC_DEPTH && depth < max_depth)
243 if (type == Text::DEC_DEPTH && depth > 0)
249 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
251 LASSERT(this == cur.text(), /**/);
252 // this happens when selecting several cells in tabular (bug 2630)
253 if (cur.selBegin().idx() != cur.selEnd().idx())
256 pit_type const beg = cur.selBegin().pit();
257 pit_type const end = cur.selEnd().pit() + 1;
258 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
260 for (pit_type pit = beg; pit != end; ++pit) {
261 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
263 max_depth = pars_[pit].getMaxDepthAfter();
269 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
271 LASSERT(this == cur.text(), /**/);
272 pit_type const beg = cur.selBegin().pit();
273 pit_type const end = cur.selEnd().pit() + 1;
274 cur.recordUndoSelection();
275 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
277 for (pit_type pit = beg; pit != end; ++pit) {
278 Paragraph & par = pars_[pit];
279 if (lyx::changeDepthAllowed(type, par, max_depth)) {
280 int const depth = par.params().depth();
281 if (type == INC_DEPTH)
282 par.params().depth(depth + 1);
284 par.params().depth(depth - 1);
286 max_depth = par.getMaxDepthAfter();
288 // this handles the counter labels, and also fixes up
289 // depth values for follow-on (child) paragraphs
290 updateLabels(cur.buffer());
294 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
296 LASSERT(this == cur.text(), /**/);
297 // Set the current_font
298 // Determine basis font
300 pit_type pit = cur.pit();
301 if (cur.pos() < pars_[pit].beginOfBody())
302 layoutfont = labelFont(cur.buffer(), pars_[pit]);
304 layoutfont = layoutFont(cur.buffer(), pit);
306 // Update current font
307 cur.real_current_font.update(font,
308 cur.buffer().params().language,
311 // Reduce to implicit settings
312 cur.current_font = cur.real_current_font;
313 cur.current_font.fontInfo().reduce(layoutfont);
314 // And resolve it completely
315 cur.real_current_font.fontInfo().realize(layoutfont);
317 // if there is no selection that's all we need to do
318 if (!cur.selection())
321 // Ok, we have a selection.
322 cur.recordUndoSelection();
324 setFont(cur.bv(), cur.selectionBegin().top(),
325 cur.selectionEnd().top(), font, toggleall);
329 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
330 CursorSlice const & end, Font const & font,
333 Buffer const & buffer = bv.buffer();
335 // Don't use forwardChar here as ditend might have
336 // pos() == lastpos() and forwardChar would miss it.
337 // Can't use forwardPos either as this descends into
339 Language const * language = buffer.params().language;
340 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
341 if (dit.pos() == dit.lastpos())
343 pit_type const pit = dit.pit();
344 pos_type const pos = dit.pos();
345 Inset * inset = pars_[pit].getInset(pos);
346 if (inset && inset->noFontChange()) {
347 // We need to propagate the font change to all
348 // text cells of the inset (bug 1973).
349 // FIXME: This should change, see documentation
350 // of noFontChange in Inset.h
351 setInsetFont(bv, pit, pos, font, toggleall);
353 TextMetrics const & tm = bv.textMetrics(this);
354 Font f = tm.displayFont(pit, pos);
355 f.update(font, language, toggleall);
356 setCharFont(buffer, pit, pos, f, tm.font_);
361 bool Text::cursorTop(Cursor & cur)
363 LASSERT(this == cur.text(), /**/);
364 return setCursor(cur, 0, 0);
368 bool Text::cursorBottom(Cursor & cur)
370 LASSERT(this == cur.text(), /**/);
371 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
375 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
377 LASSERT(this == cur.text(), /**/);
378 // If the mask is completely neutral, tell user
379 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
380 // Could only happen with user style
381 cur.message(_("No font change defined."));
385 // Try implicit word selection
386 // If there is a change in the language the implicit word selection
388 CursorSlice resetCursor = cur.top();
389 bool implicitSelection =
390 font.language() == ignore_language
391 && font.fontInfo().number() == FONT_IGNORE
392 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
395 setFont(cur, font, toggleall);
397 // Implicit selections are cleared afterwards
398 // and cursor is set to the original position.
399 if (implicitSelection) {
400 cur.clearSelection();
401 cur.top() = resetCursor;
407 docstring Text::getStringToIndex(Cursor const & cur)
409 LASSERT(this == cur.text(), /**/);
412 return cur.selectionAsString(false);
414 // Try implicit word selection. If there is a change
415 // in the language the implicit word selection is
418 selectWord(tmpcur, PREVIOUS_WORD);
420 if (!tmpcur.selection())
421 cur.message(_("Nothing to index!"));
422 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
423 cur.message(_("Cannot index more than one paragraph!"));
425 return tmpcur.selectionAsString(false);
431 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
433 LASSERT(cur.text(), /**/);
434 // make sure that the depth behind the selection are restored, too
435 pit_type undopit = undoSpan(cur.selEnd().pit());
436 recUndo(cur, cur.selBegin().pit(), undopit - 1);
439 string const argument = to_utf8(arg);
440 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
442 Paragraph & par = pars_[pit];
443 ParagraphParameters params = par.params();
444 params.read(argument, merge);
445 par.params().apply(params, par.layout());
450 //FIXME This is a little redundant now, but it's probably worth keeping,
451 //especially if we're going to go away from using serialization internally
453 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
455 LASSERT(cur.text(), /**/);
456 // make sure that the depth behind the selection are restored, too
457 pit_type undopit = undoSpan(cur.selEnd().pit());
458 recUndo(cur, cur.selBegin().pit(), undopit - 1);
460 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
462 Paragraph & par = pars_[pit];
463 par.params().apply(p, par.layout());
468 // this really should just insert the inset and not move the cursor.
469 void Text::insertInset(Cursor & cur, Inset * inset)
471 LASSERT(this == cur.text(), /**/);
472 LASSERT(inset, /**/);
473 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
474 Change(cur.buffer().params().trackChanges
475 ? Change::INSERTED : Change::UNCHANGED));
479 // needed to insert the selection
480 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
482 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
483 cur.current_font, str, autoBreakRows_);
487 // turn double CR to single CR, others are converted into one
488 // blank. Then insertStringAsLines is called
489 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
491 docstring linestr = str;
492 bool newline_inserted = false;
494 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
495 if (linestr[i] == '\n') {
496 if (newline_inserted) {
497 // we know that \r will be ignored by
498 // insertStringAsLines. Of course, it is a dirty
499 // trick, but it works...
500 linestr[i - 1] = '\r';
504 newline_inserted = true;
506 } else if (isPrintable(linestr[i])) {
507 newline_inserted = false;
510 insertStringAsLines(cur, linestr);
514 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
515 bool setfont, bool boundary)
517 TextMetrics const & tm = cur.bv().textMetrics(this);
518 bool const update_needed = !tm.contains(par);
520 setCursorIntern(cur, par, pos, setfont, boundary);
521 return cur.bv().checkDepm(cur, old) || update_needed;
525 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
527 LASSERT(par != int(paragraphs().size()), /**/);
531 // now some strict checking
532 Paragraph & para = getPar(par);
534 // None of these should happen, but we're scaredy-cats
536 lyxerr << "dont like -1" << endl;
537 LASSERT(false, /**/);
540 if (pos > para.size()) {
541 lyxerr << "dont like 1, pos: " << pos
542 << " size: " << para.size()
543 << " par: " << par << endl;
544 LASSERT(false, /**/);
549 void Text::setCursorIntern(Cursor & cur,
550 pit_type par, pos_type pos, bool setfont, bool boundary)
552 LASSERT(this == cur.text(), /**/);
553 cur.boundary(boundary);
554 setCursor(cur.top(), par, pos);
556 cur.setCurrentFont();
560 bool Text::checkAndActivateInset(Cursor & cur, bool front)
564 if (front && cur.pos() == cur.lastpos())
566 if (!front && cur.pos() == 0)
568 Inset * inset = front ? cur.nextInset() : cur.prevInset();
569 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
572 * Apparently, when entering an inset we are expected to be positioned
573 * *before* it in the containing paragraph, regardless of the direction
574 * from which we are entering. Otherwise, cursor placement goes awry,
575 * and when we exit from the beginning, we'll be placed *after* the
580 inset->edit(cur, front);
585 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
591 if (cur.pos() == cur.lastpos())
593 Paragraph & par = cur.paragraph();
594 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
595 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
597 inset->edit(cur, movingForward,
598 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
603 bool Text::cursorBackward(Cursor & cur)
605 // Tell BufferView to test for FitCursor in any case!
606 cur.updateFlags(Update::FitCursor);
608 // not at paragraph start?
610 // if on right side of boundary (i.e. not at paragraph end, but line end)
611 // -> skip it, i.e. set boundary to true, i.e. go only logically left
612 // there are some exceptions to ignore this: lineseps, newlines, spaces
614 // some effectless debug code to see the values in the debugger
615 bool bound = cur.boundary();
616 int rowpos = cur.textRow().pos();
618 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
619 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
620 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
622 if (!cur.boundary() &&
623 cur.textRow().pos() == cur.pos() &&
624 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
625 !cur.paragraph().isNewline(cur.pos() - 1) &&
626 !cur.paragraph().isSeparator(cur.pos() - 1)) {
627 return setCursor(cur, cur.pit(), cur.pos(), true, true);
630 // go left and try to enter inset
631 if (checkAndActivateInset(cur, false))
634 // normal character left
635 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
638 // move to the previous paragraph or do nothing
640 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
645 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
647 Cursor temp_cur = cur;
648 temp_cur.posVisLeft(skip_inset);
649 if (temp_cur.depth() > cur.depth()) {
653 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
654 true, temp_cur.boundary());
658 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
660 Cursor temp_cur = cur;
661 temp_cur.posVisRight(skip_inset);
662 if (temp_cur.depth() > cur.depth()) {
666 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
667 true, temp_cur.boundary());
671 bool Text::cursorForward(Cursor & cur)
673 // Tell BufferView to test for FitCursor in any case!
674 cur.updateFlags(Update::FitCursor);
676 // not at paragraph end?
677 if (cur.pos() != cur.lastpos()) {
678 // in front of editable inset, i.e. jump into it?
679 if (checkAndActivateInset(cur, true))
682 TextMetrics const & tm = cur.bv().textMetrics(this);
683 // if left of boundary -> just jump to right side
684 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
685 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
686 return setCursor(cur, cur.pit(), cur.pos(), true, false);
688 // next position is left of boundary,
689 // but go to next line for special cases like space, newline, linesep
691 // some effectless debug code to see the values in the debugger
692 int endpos = cur.textRow().endpos();
693 int lastpos = cur.lastpos();
695 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
696 bool newline = cur.paragraph().isNewline(cur.pos());
697 bool sep = cur.paragraph().isSeparator(cur.pos());
698 if (cur.pos() != cur.lastpos()) {
699 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
700 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
701 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
704 if (cur.textRow().endpos() == cur.pos() + 1 &&
705 cur.textRow().endpos() != cur.lastpos() &&
706 !cur.paragraph().isNewline(cur.pos()) &&
707 !cur.paragraph().isLineSeparator(cur.pos()) &&
708 !cur.paragraph().isSeparator(cur.pos())) {
709 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
712 // in front of RTL boundary? Stay on this side of the boundary because:
713 // ab|cDDEEFFghi -> abc|DDEEFFghi
714 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
715 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
718 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
721 // move to next paragraph
722 if (cur.pit() != cur.lastpit())
723 return setCursor(cur, cur.pit() + 1, 0, true, false);
728 bool Text::cursorUpParagraph(Cursor & cur)
730 bool updated = false;
732 updated = setCursor(cur, cur.pit(), 0);
733 else if (cur.pit() != 0)
734 updated = setCursor(cur, cur.pit() - 1, 0);
739 bool Text::cursorDownParagraph(Cursor & cur)
741 bool updated = false;
742 if (cur.pit() != cur.lastpit())
743 updated = setCursor(cur, cur.pit() + 1, 0);
745 updated = setCursor(cur, cur.pit(), cur.lastpos());
750 // fix the cursor `cur' after a characters has been deleted at `where'
751 // position. Called by deleteEmptyParagraphMechanism
752 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
754 // Do nothing if cursor is not in the paragraph where the
756 if (cur.pit() != where.pit())
759 // If cursor position is after the deletion place update it
760 if (cur.pos() > where.pos())
763 // Check also if we don't want to set the cursor on a spot behind the
764 // pagragraph because we erased the last character.
765 if (cur.pos() > cur.lastpos())
766 cur.pos() = cur.lastpos();
770 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
771 Cursor & old, bool & need_anchor_change)
773 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
775 Paragraph & oldpar = old.paragraph();
777 // We allow all kinds of "mumbo-jumbo" when freespacing.
778 if (oldpar.isFreeSpacing())
781 /* Ok I'll put some comments here about what is missing.
782 There are still some small problems that can lead to
783 double spaces stored in the document file or space at
784 the beginning of paragraphs(). This happens if you have
785 the cursor between to spaces and then save. Or if you
786 cut and paste and the selection have a space at the
787 beginning and then save right after the paste. (Lgb)
790 // If old.pos() == 0 and old.pos()(1) == LineSeparator
791 // delete the LineSeparator.
794 // If old.pos() == 1 and old.pos()(0) == LineSeparator
795 // delete the LineSeparator.
798 bool const same_inset = &old.inset() == &cur.inset();
799 bool const same_par = same_inset && old.pit() == cur.pit();
800 bool const same_par_pos = same_par && old.pos() == cur.pos();
802 // If the chars around the old cursor were spaces, delete one of them.
804 // Only if the cursor has really moved.
806 && old.pos() < oldpar.size()
807 && oldpar.isLineSeparator(old.pos())
808 && oldpar.isLineSeparator(old.pos() - 1)
809 && !oldpar.isDeleted(old.pos() - 1)
810 && !oldpar.isDeleted(old.pos())) {
811 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
812 // FIXME: This will not work anymore when we have multiple views of the same buffer
813 // In this case, we will have to correct also the cursors held by
814 // other bufferviews. It will probably be easier to do that in a more
815 // automated way in CursorSlice code. (JMarc 26/09/2001)
816 // correct all cursor parts
818 fixCursorAfterDelete(cur.top(), old.top());
819 need_anchor_change = true;
825 // only do our magic if we changed paragraph
829 // don't delete anything if this is the ONLY paragraph!
830 if (old.lastpit() == 0)
833 // Do not delete empty paragraphs with keepempty set.
834 if (oldpar.allowEmpty())
837 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
839 old.recordUndo(ATOMIC_UNDO,
840 max(old.pit() - 1, pit_type(0)),
841 min(old.pit() + 1, old.lastpit()));
842 ParagraphList & plist = old.text()->paragraphs();
843 bool const soa = oldpar.params().startOfAppendix();
844 plist.erase(boost::next(plist.begin(), old.pit()));
845 // do not lose start of appendix marker (bug 4212)
846 if (soa && old.pit() < pit_type(plist.size()))
847 plist[old.pit()].params().startOfAppendix(true);
849 // see #warning (FIXME?) above
850 if (cur.depth() >= old.depth()) {
851 CursorSlice & curslice = cur[old.depth() - 1];
852 if (&curslice.inset() == &old.inset()
853 && curslice.pit() > old.pit()) {
855 // since a paragraph has been deleted, all the
856 // insets after `old' have been copied and
857 // their address has changed. Therefore we
858 // need to `regenerate' cur. (JMarc)
859 cur.updateInsets(&(cur.bottom().inset()));
860 need_anchor_change = true;
866 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
867 need_anchor_change = true;
868 // We return true here because the Paragraph contents changed and
869 // we need a redraw before further action is processed.
877 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
879 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
881 for (pit_type pit = first; pit <= last; ++pit) {
882 Paragraph & par = pars_[pit];
884 // We allow all kinds of "mumbo-jumbo" when freespacing.
885 if (par.isFreeSpacing())
888 for (pos_type pos = 1; pos < par.size(); ++pos) {
889 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
890 && !par.isDeleted(pos - 1)) {
891 if (par.eraseChar(pos - 1, trackChanges)) {
897 // don't delete anything if this is the only remaining paragraph within the given range
898 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
902 // don't delete empty paragraphs with keepempty set
903 if (par.allowEmpty())
906 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
907 pars_.erase(boost::next(pars_.begin(), pit));
913 par.stripLeadingSpaces(trackChanges);
918 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
920 cur.recordUndo(ATOMIC_UNDO, first, last);
924 void Text::recUndo(Cursor & cur, pit_type par) const
926 cur.recordUndo(ATOMIC_UNDO, par, par);