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 // Note that this is supposed to return a fully realized font.
78 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
80 Layout const & layout = pars_[pit].layout();
82 if (!pars_[pit].getDepth()) {
83 FontInfo lf = layout.resfont;
84 // In case the default family has been customized
85 if (layout.font.family() == INHERIT_FAMILY)
86 lf.setFamily(buffer.params().getFont().fontInfo().family());
87 InsetCollapsable const * icp = pars_[pit].inInset()->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(buffer.params().getFont().fontInfo());
104 // Note that this is supposed to return a fully realized font.
105 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
107 Layout const & layout = par.layout();
109 if (!par.getDepth()) {
110 FontInfo lf = layout.reslabelfont;
111 // In case the default family has been customized
112 if (layout.labelfont.family() == INHERIT_FAMILY)
113 lf.setFamily(buffer.params().getFont().fontInfo().family());
114 InsetCollapsable const * icp = par.inInset()->asInsetCollapsable();
117 FontInfo icf = icp->getLayout().labelfont();
122 FontInfo font = layout.labelfont;
123 // Realize with the fonts of lesser depth.
124 font.realize(buffer.params().getFont().fontInfo());
130 void Text::setCharFont(Buffer const & buffer, pit_type pit,
131 pos_type pos, Font const & fnt, Font const & display_font)
134 Layout const & layout = pars_[pit].layout();
136 // Get concrete layout font to reduce against
139 if (pos < pars_[pit].beginOfBody())
140 layoutfont = layout.labelfont;
142 layoutfont = layout.font;
144 // Realize against environment font information
145 if (pars_[pit].getDepth()) {
147 while (!layoutfont.resolved() &&
148 tp != pit_type(paragraphs().size()) &&
149 pars_[tp].getDepth()) {
150 tp = outerHook(tp, paragraphs());
151 if (tp != pit_type(paragraphs().size()))
152 layoutfont.realize(pars_[tp].layout().font);
156 // Inside inset, apply the inset's font attributes if any
158 if (!isMainText(buffer))
159 layoutfont.realize(display_font.fontInfo());
161 layoutfont.realize(buffer.params().getFont().fontInfo());
163 // Now, reduce font against full layout font
164 font.fontInfo().reduce(layoutfont);
166 pars_[pit].setFont(pos, font);
170 void Text::setInsetFont(BufferView const & bv, pit_type pit,
171 pos_type pos, Font const & font, bool toggleall)
173 Inset * const inset = pars_[pit].getInset(pos);
174 LASSERT(inset && inset->noFontChange(), /**/);
176 CursorSlice::idx_type endidx = inset->nargs();
177 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
178 Text * text = cs.text();
180 // last position of the cell
181 CursorSlice cellend = cs;
182 cellend.pit() = cellend.lastpit();
183 cellend.pos() = cellend.lastpos();
184 text->setFont(bv, cs, cellend, font, toggleall);
190 // return past-the-last paragraph influenced by a layout change on pit
191 pit_type Text::undoSpan(pit_type pit)
193 pit_type end = paragraphs().size();
194 pit_type nextpit = pit + 1;
197 //because of parindents
198 if (!pars_[pit].getDepth())
199 return boost::next(nextpit);
200 //because of depth constrains
201 for (; nextpit != end; ++pit, ++nextpit) {
202 if (!pars_[pit].getDepth())
209 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
210 docstring const & layout)
212 LASSERT(start != end, /**/);
214 BufferParams const & bufparams = buffer.params();
215 Layout const & lyxlayout = bufparams.documentClass()[layout];
217 for (pit_type pit = start; pit != end; ++pit) {
218 Paragraph & par = pars_[pit];
219 par.applyLayout(lyxlayout);
220 if (lyxlayout.margintype == MARGIN_MANUAL)
221 par.setLabelWidthString(par.translateIfPossible(
222 lyxlayout.labelstring(), buffer.params()));
227 // set layout over selection and make a total rebreak of those paragraphs
228 void Text::setLayout(Cursor & cur, docstring const & layout)
230 LASSERT(this == cur.text(), /**/);
232 pit_type start = cur.selBegin().pit();
233 pit_type end = cur.selEnd().pit() + 1;
234 pit_type undopit = undoSpan(end - 1);
235 recUndo(cur, start, undopit - 1);
236 setLayout(cur.buffer(), start, end, layout);
237 updateLabels(cur.buffer());
241 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
242 Paragraph const & par, int max_depth)
244 if (par.layout().labeltype == LABEL_BIBLIO)
246 int const depth = par.params().depth();
247 if (type == Text::INC_DEPTH && depth < max_depth)
249 if (type == Text::DEC_DEPTH && depth > 0)
255 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
257 LASSERT(this == cur.text(), /**/);
258 // this happens when selecting several cells in tabular (bug 2630)
259 if (cur.selBegin().idx() != cur.selEnd().idx())
262 pit_type const beg = cur.selBegin().pit();
263 pit_type const end = cur.selEnd().pit() + 1;
264 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
266 for (pit_type pit = beg; pit != end; ++pit) {
267 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
269 max_depth = pars_[pit].getMaxDepthAfter();
275 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
277 LASSERT(this == cur.text(), /**/);
278 pit_type const beg = cur.selBegin().pit();
279 pit_type const end = cur.selEnd().pit() + 1;
280 cur.recordUndoSelection();
281 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
283 for (pit_type pit = beg; pit != end; ++pit) {
284 Paragraph & par = pars_[pit];
285 if (lyx::changeDepthAllowed(type, par, max_depth)) {
286 int const depth = par.params().depth();
287 if (type == INC_DEPTH)
288 par.params().depth(depth + 1);
290 par.params().depth(depth - 1);
292 max_depth = par.getMaxDepthAfter();
294 // this handles the counter labels, and also fixes up
295 // depth values for follow-on (child) paragraphs
296 updateLabels(cur.buffer());
300 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
302 LASSERT(this == cur.text(), /**/);
303 // Set the current_font
304 // Determine basis font
306 pit_type pit = cur.pit();
307 if (cur.pos() < pars_[pit].beginOfBody())
308 layoutfont = labelFont(cur.buffer(), pars_[pit]);
310 layoutfont = layoutFont(cur.buffer(), pit);
312 // Update current font
313 cur.real_current_font.update(font,
314 cur.buffer().params().language,
317 // Reduce to implicit settings
318 cur.current_font = cur.real_current_font;
319 cur.current_font.fontInfo().reduce(layoutfont);
320 // And resolve it completely
321 cur.real_current_font.fontInfo().realize(layoutfont);
323 // if there is no selection that's all we need to do
324 if (!cur.selection())
327 // Ok, we have a selection.
328 cur.recordUndoSelection();
330 setFont(cur.bv(), cur.selectionBegin().top(),
331 cur.selectionEnd().top(), font, toggleall);
335 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
336 CursorSlice const & end, Font const & font,
339 Buffer const & buffer = bv.buffer();
341 // Don't use forwardChar here as ditend might have
342 // pos() == lastpos() and forwardChar would miss it.
343 // Can't use forwardPos either as this descends into
345 Language const * language = buffer.params().language;
346 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
347 if (dit.pos() == dit.lastpos())
349 pit_type const pit = dit.pit();
350 pos_type const pos = dit.pos();
351 Inset * inset = pars_[pit].getInset(pos);
352 if (inset && inset->noFontChange()) {
353 // We need to propagate the font change to all
354 // text cells of the inset (bug 1973).
355 // FIXME: This should change, see documentation
356 // of noFontChange in Inset.h
357 setInsetFont(bv, pit, pos, font, toggleall);
359 TextMetrics const & tm = bv.textMetrics(this);
360 Font f = tm.displayFont(pit, pos);
361 f.update(font, language, toggleall);
362 setCharFont(buffer, pit, pos, f, tm.font_);
367 bool Text::cursorTop(Cursor & cur)
369 LASSERT(this == cur.text(), /**/);
370 return setCursor(cur, 0, 0);
374 bool Text::cursorBottom(Cursor & cur)
376 LASSERT(this == cur.text(), /**/);
377 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
381 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
383 LASSERT(this == cur.text(), /**/);
384 // If the mask is completely neutral, tell user
385 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
386 // Could only happen with user style
387 cur.message(_("No font change defined."));
391 // Try implicit word selection
392 // If there is a change in the language the implicit word selection
394 CursorSlice resetCursor = cur.top();
395 bool implicitSelection =
396 font.language() == ignore_language
397 && font.fontInfo().number() == FONT_IGNORE
398 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
401 setFont(cur, font, toggleall);
403 // Implicit selections are cleared afterwards
404 // and cursor is set to the original position.
405 if (implicitSelection) {
406 cur.clearSelection();
407 cur.top() = resetCursor;
413 docstring Text::getStringToIndex(Cursor const & cur)
415 LASSERT(this == cur.text(), /**/);
418 return cur.selectionAsString(false);
420 // Try implicit word selection. If there is a change
421 // in the language the implicit word selection is
424 selectWord(tmpcur, PREVIOUS_WORD);
426 if (!tmpcur.selection())
427 cur.message(_("Nothing to index!"));
428 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
429 cur.message(_("Cannot index more than one paragraph!"));
431 return tmpcur.selectionAsString(false);
437 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
439 LASSERT(cur.text(), /**/);
440 // make sure that the depth behind the selection are restored, too
441 pit_type undopit = undoSpan(cur.selEnd().pit());
442 recUndo(cur, cur.selBegin().pit(), undopit - 1);
445 string const argument = to_utf8(arg);
446 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
448 Paragraph & par = pars_[pit];
449 ParagraphParameters params = par.params();
450 params.read(argument, merge);
451 par.params().apply(params, par.layout());
456 //FIXME This is a little redundant now, but it's probably worth keeping,
457 //especially if we're going to go away from using serialization internally
459 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
461 LASSERT(cur.text(), /**/);
462 // make sure that the depth behind the selection are restored, too
463 pit_type undopit = undoSpan(cur.selEnd().pit());
464 recUndo(cur, cur.selBegin().pit(), undopit - 1);
466 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
468 Paragraph & par = pars_[pit];
469 par.params().apply(p, par.layout());
474 // this really should just insert the inset and not move the cursor.
475 void Text::insertInset(Cursor & cur, Inset * inset)
477 LASSERT(this == cur.text(), /**/);
478 LASSERT(inset, /**/);
479 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
480 Change(cur.buffer().params().trackChanges
481 ? Change::INSERTED : Change::UNCHANGED));
485 // needed to insert the selection
486 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
488 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
489 cur.current_font, str, autoBreakRows_);
493 // turn double CR to single CR, others are converted into one
494 // blank. Then insertStringAsLines is called
495 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
497 docstring linestr = str;
498 bool newline_inserted = false;
500 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
501 if (linestr[i] == '\n') {
502 if (newline_inserted) {
503 // we know that \r will be ignored by
504 // insertStringAsLines. Of course, it is a dirty
505 // trick, but it works...
506 linestr[i - 1] = '\r';
510 newline_inserted = true;
512 } else if (isPrintable(linestr[i])) {
513 newline_inserted = false;
516 insertStringAsLines(cur, linestr);
520 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
521 bool setfont, bool boundary)
523 TextMetrics const & tm = cur.bv().textMetrics(this);
524 bool const update_needed = !tm.contains(par);
526 setCursorIntern(cur, par, pos, setfont, boundary);
527 return cur.bv().checkDepm(cur, old) || update_needed;
531 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
533 LASSERT(par != int(paragraphs().size()), /**/);
537 // now some strict checking
538 Paragraph & para = getPar(par);
540 // None of these should happen, but we're scaredy-cats
542 lyxerr << "dont like -1" << endl;
543 LASSERT(false, /**/);
546 if (pos > para.size()) {
547 lyxerr << "dont like 1, pos: " << pos
548 << " size: " << para.size()
549 << " par: " << par << endl;
550 LASSERT(false, /**/);
555 void Text::setCursorIntern(Cursor & cur,
556 pit_type par, pos_type pos, bool setfont, bool boundary)
558 LASSERT(this == cur.text(), /**/);
559 cur.boundary(boundary);
560 setCursor(cur.top(), par, pos);
562 cur.setCurrentFont();
566 bool Text::checkAndActivateInset(Cursor & cur, bool front)
570 if (front && cur.pos() == cur.lastpos())
572 if (!front && cur.pos() == 0)
574 Inset * inset = front ? cur.nextInset() : cur.prevInset();
575 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
578 * Apparently, when entering an inset we are expected to be positioned
579 * *before* it in the containing paragraph, regardless of the direction
580 * from which we are entering. Otherwise, cursor placement goes awry,
581 * and when we exit from the beginning, we'll be placed *after* the
586 inset->edit(cur, front);
591 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
597 if (cur.pos() == cur.lastpos())
599 Paragraph & par = cur.paragraph();
600 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
601 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
603 inset->edit(cur, movingForward,
604 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
609 bool Text::cursorBackward(Cursor & cur)
611 // Tell BufferView to test for FitCursor in any case!
612 cur.updateFlags(Update::FitCursor);
614 // not at paragraph start?
616 // if on right side of boundary (i.e. not at paragraph end, but line end)
617 // -> skip it, i.e. set boundary to true, i.e. go only logically left
618 // there are some exceptions to ignore this: lineseps, newlines, spaces
620 // some effectless debug code to see the values in the debugger
621 bool bound = cur.boundary();
622 int rowpos = cur.textRow().pos();
624 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
625 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
626 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
628 if (!cur.boundary() &&
629 cur.textRow().pos() == cur.pos() &&
630 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
631 !cur.paragraph().isNewline(cur.pos() - 1) &&
632 !cur.paragraph().isSeparator(cur.pos() - 1)) {
633 return setCursor(cur, cur.pit(), cur.pos(), true, true);
636 // go left and try to enter inset
637 if (checkAndActivateInset(cur, false))
640 // normal character left
641 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
644 // move to the previous paragraph or do nothing
646 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
651 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
653 Cursor temp_cur = cur;
654 temp_cur.posVisLeft(skip_inset);
655 if (temp_cur.depth() > cur.depth()) {
659 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
660 true, temp_cur.boundary());
664 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
666 Cursor temp_cur = cur;
667 temp_cur.posVisRight(skip_inset);
668 if (temp_cur.depth() > cur.depth()) {
672 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
673 true, temp_cur.boundary());
677 bool Text::cursorForward(Cursor & cur)
679 // Tell BufferView to test for FitCursor in any case!
680 cur.updateFlags(Update::FitCursor);
682 // not at paragraph end?
683 if (cur.pos() != cur.lastpos()) {
684 // in front of editable inset, i.e. jump into it?
685 if (checkAndActivateInset(cur, true))
688 TextMetrics const & tm = cur.bv().textMetrics(this);
689 // if left of boundary -> just jump to right side
690 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
691 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
692 return setCursor(cur, cur.pit(), cur.pos(), true, false);
694 // next position is left of boundary,
695 // but go to next line for special cases like space, newline, linesep
697 // some effectless debug code to see the values in the debugger
698 int endpos = cur.textRow().endpos();
699 int lastpos = cur.lastpos();
701 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
702 bool newline = cur.paragraph().isNewline(cur.pos());
703 bool sep = cur.paragraph().isSeparator(cur.pos());
704 if (cur.pos() != cur.lastpos()) {
705 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
706 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
707 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
710 if (cur.textRow().endpos() == cur.pos() + 1 &&
711 cur.textRow().endpos() != cur.lastpos() &&
712 !cur.paragraph().isNewline(cur.pos()) &&
713 !cur.paragraph().isLineSeparator(cur.pos()) &&
714 !cur.paragraph().isSeparator(cur.pos())) {
715 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
718 // in front of RTL boundary? Stay on this side of the boundary because:
719 // ab|cDDEEFFghi -> abc|DDEEFFghi
720 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
721 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
724 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
727 // move to next paragraph
728 if (cur.pit() != cur.lastpit())
729 return setCursor(cur, cur.pit() + 1, 0, true, false);
734 bool Text::cursorUpParagraph(Cursor & cur)
736 bool updated = false;
738 updated = setCursor(cur, cur.pit(), 0);
739 else if (cur.pit() != 0)
740 updated = setCursor(cur, cur.pit() - 1, 0);
745 bool Text::cursorDownParagraph(Cursor & cur)
747 bool updated = false;
748 if (cur.pit() != cur.lastpit())
749 updated = setCursor(cur, cur.pit() + 1, 0);
751 updated = setCursor(cur, cur.pit(), cur.lastpos());
756 // fix the cursor `cur' after a characters has been deleted at `where'
757 // position. Called by deleteEmptyParagraphMechanism
758 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
760 // Do nothing if cursor is not in the paragraph where the
762 if (cur.pit() != where.pit())
765 // If cursor position is after the deletion place update it
766 if (cur.pos() > where.pos())
769 // Check also if we don't want to set the cursor on a spot behind the
770 // pagragraph because we erased the last character.
771 if (cur.pos() > cur.lastpos())
772 cur.pos() = cur.lastpos();
776 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
777 Cursor & old, bool & need_anchor_change)
779 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
781 Paragraph & oldpar = old.paragraph();
783 // We allow all kinds of "mumbo-jumbo" when freespacing.
784 if (oldpar.isFreeSpacing())
787 /* Ok I'll put some comments here about what is missing.
788 There are still some small problems that can lead to
789 double spaces stored in the document file or space at
790 the beginning of paragraphs(). This happens if you have
791 the cursor between to spaces and then save. Or if you
792 cut and paste and the selection have a space at the
793 beginning and then save right after the paste. (Lgb)
796 // If old.pos() == 0 and old.pos()(1) == LineSeparator
797 // delete the LineSeparator.
800 // If old.pos() == 1 and old.pos()(0) == LineSeparator
801 // delete the LineSeparator.
804 bool const same_inset = &old.inset() == &cur.inset();
805 bool const same_par = same_inset && old.pit() == cur.pit();
806 bool const same_par_pos = same_par && old.pos() == cur.pos();
808 // If the chars around the old cursor were spaces, delete one of them.
810 // Only if the cursor has really moved.
812 && old.pos() < oldpar.size()
813 && oldpar.isLineSeparator(old.pos())
814 && oldpar.isLineSeparator(old.pos() - 1)
815 && !oldpar.isDeleted(old.pos() - 1)
816 && !oldpar.isDeleted(old.pos())) {
817 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
818 // FIXME: This will not work anymore when we have multiple views of the same buffer
819 // In this case, we will have to correct also the cursors held by
820 // other bufferviews. It will probably be easier to do that in a more
821 // automated way in CursorSlice code. (JMarc 26/09/2001)
822 // correct all cursor parts
824 fixCursorAfterDelete(cur.top(), old.top());
825 need_anchor_change = true;
831 // only do our magic if we changed paragraph
835 // don't delete anything if this is the ONLY paragraph!
836 if (old.lastpit() == 0)
839 // Do not delete empty paragraphs with keepempty set.
840 if (oldpar.allowEmpty())
843 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
845 old.recordUndo(ATOMIC_UNDO,
846 max(old.pit() - 1, pit_type(0)),
847 min(old.pit() + 1, old.lastpit()));
848 ParagraphList & plist = old.text()->paragraphs();
849 bool const soa = oldpar.params().startOfAppendix();
850 plist.erase(boost::next(plist.begin(), old.pit()));
851 // do not lose start of appendix marker (bug 4212)
852 if (soa && old.pit() < pit_type(plist.size()))
853 plist[old.pit()].params().startOfAppendix(true);
855 // see #warning (FIXME?) above
856 if (cur.depth() >= old.depth()) {
857 CursorSlice & curslice = cur[old.depth() - 1];
858 if (&curslice.inset() == &old.inset()
859 && curslice.pit() > old.pit()) {
861 // since a paragraph has been deleted, all the
862 // insets after `old' have been copied and
863 // their address has changed. Therefore we
864 // need to `regenerate' cur. (JMarc)
865 cur.updateInsets(&(cur.bottom().inset()));
866 need_anchor_change = true;
872 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
873 need_anchor_change = true;
874 // We return true here because the Paragraph contents changed and
875 // we need a redraw before further action is processed.
883 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
885 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
887 for (pit_type pit = first; pit <= last; ++pit) {
888 Paragraph & par = pars_[pit];
890 // We allow all kinds of "mumbo-jumbo" when freespacing.
891 if (par.isFreeSpacing())
894 for (pos_type pos = 1; pos < par.size(); ++pos) {
895 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
896 && !par.isDeleted(pos - 1)) {
897 if (par.eraseChar(pos - 1, trackChanges)) {
903 // don't delete anything if this is the only remaining paragraph within the given range
904 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
908 // don't delete empty paragraphs with keepempty set
909 if (par.allowEmpty())
912 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
913 pars_.erase(boost::next(pars_.begin(), pit));
919 par.stripLeadingSpaces(trackChanges);
924 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
926 cur.recordUndo(ATOMIC_UNDO, first, last);
930 void Text::recUndo(Cursor & cur, pit_type par) const
932 cur.recordUndo(ATOMIC_UNDO, par, par);