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"
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 "ParIterator.h"
46 #include "TextClass.h"
47 #include "TextMetrics.h"
50 #include "frontends/FontMetrics.h"
52 #include "insets/InsetEnvironment.h"
54 #include "mathed/InsetMathHull.h"
56 #include "support/debug.h"
57 #include "support/gettext.h"
58 #include "support/textutils.h"
60 #include <boost/next_prior.hpp>
69 : autoBreakRows_(false)
73 bool Text::isMainText(Buffer const & buffer) const
75 return &buffer.text() == this;
79 FontInfo Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
81 LayoutPtr const & layout = pars_[pit].layout();
83 if (!pars_[pit].getDepth()) {
84 FontInfo lf = layout->resfont;
85 // In case the default family has been customized
86 if (layout->font.family() == INHERIT_FAMILY)
87 lf.setFamily(buffer.params().getFont().fontInfo().family());
91 FontInfo font = layout->font;
92 // Realize with the fonts of lesser depth.
93 //font.realize(outerFont(pit, paragraphs()));
94 font.realize(buffer.params().getFont().fontInfo());
100 FontInfo Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
102 LayoutPtr const & layout = par.layout();
104 if (!par.getDepth()) {
105 FontInfo lf = layout->reslabelfont;
106 // In case the default family has been customized
107 if (layout->labelfont.family() == INHERIT_FAMILY)
108 lf.setFamily(buffer.params().getFont().fontInfo().family());
112 FontInfo font = layout->labelfont;
113 // Realize with the fonts of lesser depth.
114 font.realize(buffer.params().getFont().fontInfo());
120 void Text::setCharFont(Buffer const & buffer, pit_type pit,
121 pos_type pos, Font const & fnt, Font const & display_font)
124 LayoutPtr const & layout = pars_[pit].layout();
126 // Get concrete layout font to reduce against
129 if (pos < pars_[pit].beginOfBody())
130 layoutfont = layout->labelfont;
132 layoutfont = layout->font;
134 // Realize against environment font information
135 if (pars_[pit].getDepth()) {
137 while (!layoutfont.resolved() &&
138 tp != pit_type(paragraphs().size()) &&
139 pars_[tp].getDepth()) {
140 tp = outerHook(tp, paragraphs());
141 if (tp != pit_type(paragraphs().size()))
142 layoutfont.realize(pars_[tp].layout()->font);
146 // Inside inset, apply the inset's font attributes if any
148 if (!isMainText(buffer))
149 layoutfont.realize(display_font.fontInfo());
151 layoutfont.realize(buffer.params().getFont().fontInfo());
153 // Now, reduce font against full layout font
154 font.fontInfo().reduce(layoutfont);
156 pars_[pit].setFont(pos, font);
160 void Text::setInsetFont(BufferView const & bv, pit_type pit,
161 pos_type pos, Font const & font, bool toggleall)
163 BOOST_ASSERT(pars_[pit].isInset(pos) &&
164 pars_[pit].getInset(pos)->noFontChange());
166 Inset * const inset = pars_[pit].getInset(pos);
167 CursorSlice::idx_type endidx = inset->nargs();
168 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
169 Text * text = cs.text();
171 // last position of the cell
172 CursorSlice cellend = cs;
173 cellend.pit() = cellend.lastpit();
174 cellend.pos() = cellend.lastpos();
175 text->setFont(bv, cs, cellend, font, toggleall);
181 // return past-the-last paragraph influenced by a layout change on pit
182 pit_type Text::undoSpan(pit_type pit)
184 pit_type end = paragraphs().size();
185 pit_type nextpit = pit + 1;
188 //because of parindents
189 if (!pars_[pit].getDepth())
190 return boost::next(nextpit);
191 //because of depth constrains
192 for (; nextpit != end; ++pit, ++nextpit) {
193 if (!pars_[pit].getDepth())
200 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
201 docstring const & layout)
203 BOOST_ASSERT(start != end);
205 BufferParams const & bufparams = buffer.params();
206 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
208 for (pit_type pit = start; pit != end; ++pit) {
209 Paragraph & par = pars_[pit];
210 par.applyLayout(lyxlayout);
211 if (lyxlayout->margintype == MARGIN_MANUAL)
212 par.setLabelWidthString(par.translateIfPossible(
213 lyxlayout->labelstring(), buffer.params()));
218 // set layout over selection and make a total rebreak of those paragraphs
219 void Text::setLayout(Cursor & cur, docstring const & layout)
221 BOOST_ASSERT(this == cur.text());
222 // special handling of new environment insets
223 BufferView & bv = cur.bv();
224 BufferParams const & params = bv.buffer().params();
225 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
226 if (lyxlayout->is_environment) {
227 // move everything in a new environment inset
228 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
229 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
230 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
231 lyx::dispatch(FuncRequest(LFUN_CUT));
232 Inset * inset = new InsetEnvironment(params, layout);
233 insertInset(cur, inset);
234 //inset->edit(cur, true);
235 //lyx::dispatch(FuncRequest(LFUN_PASTE));
239 pit_type start = cur.selBegin().pit();
240 pit_type end = cur.selEnd().pit() + 1;
241 pit_type undopit = undoSpan(end - 1);
242 recUndo(cur, start, undopit - 1);
243 setLayout(cur.buffer(), start, end, layout);
244 updateLabels(cur.buffer());
248 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
249 Paragraph const & par, int max_depth)
251 if (par.layout()->labeltype == LABEL_BIBLIO)
253 int const depth = par.params().depth();
254 if (type == Text::INC_DEPTH && depth < max_depth)
256 if (type == Text::DEC_DEPTH && depth > 0)
262 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
264 BOOST_ASSERT(this == cur.text());
265 // this happens when selecting several cells in tabular (bug 2630)
266 if (cur.selBegin().idx() != cur.selEnd().idx())
269 pit_type const beg = cur.selBegin().pit();
270 pit_type const end = cur.selEnd().pit() + 1;
271 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
273 for (pit_type pit = beg; pit != end; ++pit) {
274 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
276 max_depth = pars_[pit].getMaxDepthAfter();
282 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
284 BOOST_ASSERT(this == cur.text());
285 pit_type const beg = cur.selBegin().pit();
286 pit_type const end = cur.selEnd().pit() + 1;
287 cur.recordUndoSelection();
288 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
290 for (pit_type pit = beg; pit != end; ++pit) {
291 Paragraph & par = pars_[pit];
292 if (lyx::changeDepthAllowed(type, par, max_depth)) {
293 int const depth = par.params().depth();
294 if (type == INC_DEPTH)
295 par.params().depth(depth + 1);
297 par.params().depth(depth - 1);
299 max_depth = par.getMaxDepthAfter();
301 // this handles the counter labels, and also fixes up
302 // depth values for follow-on (child) paragraphs
303 updateLabels(cur.buffer());
307 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
309 BOOST_ASSERT(this == cur.text());
310 // Set the current_font
311 // Determine basis font
313 pit_type pit = cur.pit();
314 if (cur.pos() < pars_[pit].beginOfBody())
315 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
317 layoutfont = getLayoutFont(cur.buffer(), pit);
319 // Update current font
320 cur.real_current_font.update(font,
321 cur.buffer().params().language,
324 // Reduce to implicit settings
325 cur.current_font = cur.real_current_font;
326 cur.current_font.fontInfo().reduce(layoutfont);
327 // And resolve it completely
328 cur.real_current_font.fontInfo().realize(layoutfont);
330 // if there is no selection that's all we need to do
331 if (!cur.selection())
334 // Ok, we have a selection.
335 cur.recordUndoSelection();
337 setFont(cur.bv(), cur.selectionBegin().top(),
338 cur.selectionEnd().top(), font, toggleall);
342 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
343 CursorSlice const & end, Font const & font,
346 Buffer const & buffer = bv.buffer();
348 // Don't use forwardChar here as ditend might have
349 // pos() == lastpos() and forwardChar would miss it.
350 // Can't use forwardPos either as this descends into
352 Language const * language = buffer.params().language;
353 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
354 if (dit.pos() != dit.lastpos()) {
355 pit_type const pit = dit.pit();
356 pos_type const pos = dit.pos();
357 if (pars_[pit].isInset(pos) &&
358 pars_[pit].getInset(pos)->noFontChange())
359 // We need to propagate the font change to all
360 // text cells of the inset (bug 1973).
361 // FIXME: This should change, see documentation
362 // of noFontChange in Inset.h
363 setInsetFont(bv, pit, pos, font, toggleall);
364 TextMetrics const & tm = bv.textMetrics(this);
365 Font f = tm.getDisplayFont(pit, pos);
366 f.update(font, language, toggleall);
367 setCharFont(buffer, pit, pos, f, tm.font_);
373 bool Text::cursorTop(Cursor & cur)
375 BOOST_ASSERT(this == cur.text());
376 return setCursor(cur, 0, 0);
380 bool Text::cursorBottom(Cursor & cur)
382 BOOST_ASSERT(this == cur.text());
383 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
387 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
389 BOOST_ASSERT(this == cur.text());
390 // If the mask is completely neutral, tell user
391 if (font.fontInfo() == ignore_font &&
392 (font.language() == 0 || font.language() == ignore_language)) {
393 // Could only happen with user style
394 cur.message(_("No font change defined."));
398 // Try implicit word selection
399 // If there is a change in the language the implicit word selection
401 CursorSlice resetCursor = cur.top();
402 bool implicitSelection =
403 font.language() == ignore_language
404 && font.fontInfo().number() == FONT_IGNORE
405 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
408 setFont(cur, font, toggleall);
410 // Implicit selections are cleared afterwards
411 // and cursor is set to the original position.
412 if (implicitSelection) {
413 cur.clearSelection();
414 cur.top() = resetCursor;
420 docstring Text::getStringToIndex(Cursor const & cur)
422 BOOST_ASSERT(this == cur.text());
426 idxstring = cur.selectionAsString(false);
428 // Try implicit word selection. If there is a change
429 // in the language the implicit word selection is
432 selectWord(tmpcur, PREVIOUS_WORD);
434 if (!tmpcur.selection())
435 cur.message(_("Nothing to index!"));
436 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
437 cur.message(_("Cannot index more than one paragraph!"));
439 idxstring = tmpcur.selectionAsString(false);
446 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
448 BOOST_ASSERT(cur.text());
449 // make sure that the depth behind the selection are restored, too
450 pit_type undopit = undoSpan(cur.selEnd().pit());
451 recUndo(cur, cur.selBegin().pit(), undopit - 1);
453 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
455 Paragraph & par = pars_[pit];
456 ParagraphParameters params = par.params();
457 params.read(to_utf8(arg), merge);
458 Layout const & layout = *(par.layout());
459 par.params().apply(params, 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 BOOST_ASSERT(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 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
476 Paragraph & par = pars_[pit];
477 Layout const & layout = *(par.layout());
478 par.params().apply(p, layout);
483 // this really should just insert the inset and not move the cursor.
484 void Text::insertInset(Cursor & cur, Inset * inset)
486 BOOST_ASSERT(this == cur.text());
488 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
489 Change(cur.buffer().params().trackChanges ?
490 Change::INSERTED : Change::UNCHANGED));
494 // needed to insert the selection
495 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
497 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
498 cur.current_font, str, autoBreakRows_);
502 // turn double CR to single CR, others are converted into one
503 // blank. Then insertStringAsLines is called
504 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
506 docstring linestr = str;
507 bool newline_inserted = false;
509 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
510 if (linestr[i] == '\n') {
511 if (newline_inserted) {
512 // we know that \r will be ignored by
513 // insertStringAsLines. Of course, it is a dirty
514 // trick, but it works...
515 linestr[i - 1] = '\r';
519 newline_inserted = true;
521 } else if (isPrintable(linestr[i])) {
522 newline_inserted = false;
525 insertStringAsLines(cur, linestr);
529 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
530 bool setfont, bool boundary)
533 setCursorIntern(cur, par, pos, setfont, boundary);
534 return cur.bv().checkDepm(cur, old);
538 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
540 BOOST_ASSERT(par != int(paragraphs().size()));
544 // now some strict checking
545 Paragraph & para = getPar(par);
547 // None of these should happen, but we're scaredy-cats
549 lyxerr << "dont like -1" << endl;
553 if (pos > para.size()) {
554 lyxerr << "dont like 1, pos: " << pos
555 << " size: " << para.size()
556 << " par: " << par << endl;
562 void Text::setCursorIntern(Cursor & cur,
563 pit_type par, pos_type pos, bool setfont, bool boundary)
565 BOOST_ASSERT(this == cur.text());
566 cur.boundary(boundary);
567 setCursor(cur.top(), par, pos);
569 cur.setCurrentFont();
573 bool Text::checkAndActivateInset(Cursor & cur, bool front)
577 if (front && cur.pos() == cur.lastpos())
579 if (!front && cur.pos() == 0)
581 Inset * inset = front ? cur.nextInset() : cur.prevInset();
582 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
585 * Apparently, when entering an inset we are expected to be positioned
586 * *before* it in the containing paragraph, regardless of the direction
587 * from which we are entering. Otherwise, cursor placement goes awry,
588 * and when we exit from the beginning, we'll be placed *after* the
593 inset->edit(cur, front);
598 bool Text::cursorBackward(Cursor & cur)
600 // Tell BufferView to test for FitCursor in any case!
601 cur.updateFlags(Update::FitCursor);
603 // not at paragraph start?
605 // if on right side of boundary (i.e. not at paragraph end, but line end)
606 // -> skip it, i.e. set boundary to true, i.e. go only logically left
607 // there are some exceptions to ignore this: lineseps, newlines, spaces
609 // some effectless debug code to see the values in the debugger
610 bool bound = cur.boundary();
611 int rowpos = cur.textRow().pos();
613 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
614 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
615 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
617 if (!cur.boundary() &&
618 cur.textRow().pos() == cur.pos() &&
619 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
620 !cur.paragraph().isNewline(cur.pos() - 1) &&
621 !cur.paragraph().isSeparator(cur.pos() - 1)) {
622 return setCursor(cur, cur.pit(), cur.pos(), true, true);
625 // go left and try to enter inset
626 if (checkAndActivateInset(cur, false))
629 // normal character left
630 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
633 // move to the previous paragraph or do nothing
635 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
640 bool Text::cursorForward(Cursor & cur)
642 // Tell BufferView to test for FitCursor in any case!
643 cur.updateFlags(Update::FitCursor);
645 // not at paragraph end?
646 if (cur.pos() != cur.lastpos()) {
647 // in front of editable inset, i.e. jump into it?
648 if (checkAndActivateInset(cur, true))
651 TextMetrics const & tm = cur.bv().textMetrics(this);
652 // if left of boundary -> just jump to right side
653 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
654 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
655 return setCursor(cur, cur.pit(), cur.pos(), true, false);
657 // next position is left of boundary,
658 // but go to next line for special cases like space, newline, linesep
660 // some effectless debug code to see the values in the debugger
661 int endpos = cur.textRow().endpos();
662 int lastpos = cur.lastpos();
664 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
665 bool newline = cur.paragraph().isNewline(cur.pos());
666 bool sep = cur.paragraph().isSeparator(cur.pos());
667 if (cur.pos() != cur.lastpos()) {
668 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
669 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
670 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
673 if (cur.textRow().endpos() == cur.pos() + 1 &&
674 cur.textRow().endpos() != cur.lastpos() &&
675 !cur.paragraph().isNewline(cur.pos()) &&
676 !cur.paragraph().isLineSeparator(cur.pos()) &&
677 !cur.paragraph().isSeparator(cur.pos())) {
678 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
681 // in front of RTL boundary? Stay on this side of the boundary because:
682 // ab|cDDEEFFghi -> abc|DDEEFFghi
683 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
684 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
687 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
690 // move to next paragraph
691 if (cur.pit() != cur.lastpit())
692 return setCursor(cur, cur.pit() + 1, 0, true, false);
697 bool Text::cursorUpParagraph(Cursor & cur)
699 bool updated = false;
701 updated = setCursor(cur, cur.pit(), 0);
702 else if (cur.pit() != 0)
703 updated = setCursor(cur, cur.pit() - 1, 0);
708 bool Text::cursorDownParagraph(Cursor & cur)
710 bool updated = false;
711 if (cur.pit() != cur.lastpit())
712 updated = setCursor(cur, cur.pit() + 1, 0);
714 updated = setCursor(cur, cur.pit(), cur.lastpos());
719 // fix the cursor `cur' after a characters has been deleted at `where'
720 // position. Called by deleteEmptyParagraphMechanism
721 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
723 // Do nothing if cursor is not in the paragraph where the
725 if (cur.pit() != where.pit())
728 // If cursor position is after the deletion place update it
729 if (cur.pos() > where.pos())
732 // Check also if we don't want to set the cursor on a spot behind the
733 // pagragraph because we erased the last character.
734 if (cur.pos() > cur.lastpos())
735 cur.pos() = cur.lastpos();
739 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
740 Cursor & old, bool & need_anchor_change)
742 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
744 Paragraph & oldpar = old.paragraph();
746 // We allow all kinds of "mumbo-jumbo" when freespacing.
747 if (oldpar.isFreeSpacing())
750 /* Ok I'll put some comments here about what is missing.
751 There are still some small problems that can lead to
752 double spaces stored in the document file or space at
753 the beginning of paragraphs(). This happens if you have
754 the cursor between to spaces and then save. Or if you
755 cut and paste and the selection have a space at the
756 beginning and then save right after the paste. (Lgb)
759 // If old.pos() == 0 and old.pos()(1) == LineSeparator
760 // delete the LineSeparator.
763 // If old.pos() == 1 and old.pos()(0) == LineSeparator
764 // delete the LineSeparator.
767 bool const same_inset = &old.inset() == &cur.inset();
768 bool const same_par = same_inset && old.pit() == cur.pit();
769 bool const same_par_pos = same_par && old.pos() == cur.pos();
771 // If the chars around the old cursor were spaces, delete one of them.
773 // Only if the cursor has really moved.
775 && old.pos() < oldpar.size()
776 && oldpar.isLineSeparator(old.pos())
777 && oldpar.isLineSeparator(old.pos() - 1)
778 && !oldpar.isDeleted(old.pos() - 1)
779 && !oldpar.isDeleted(old.pos())) {
780 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
781 // FIXME: This will not work anymore when we have multiple views of the same buffer
782 // In this case, we will have to correct also the cursors held by
783 // other bufferviews. It will probably be easier to do that in a more
784 // automated way in CursorSlice code. (JMarc 26/09/2001)
785 // correct all cursor parts
787 fixCursorAfterDelete(cur.top(), old.top());
788 need_anchor_change = true;
794 // only do our magic if we changed paragraph
798 // don't delete anything if this is the ONLY paragraph!
799 if (old.lastpit() == 0)
802 // Do not delete empty paragraphs with keepempty set.
803 if (oldpar.allowEmpty())
806 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
808 old.recordUndo(ATOMIC_UNDO,
809 max(old.pit() - 1, pit_type(0)),
810 min(old.pit() + 1, old.lastpit()));
811 ParagraphList & plist = old.text()->paragraphs();
812 bool const soa = oldpar.params().startOfAppendix();
813 plist.erase(boost::next(plist.begin(), old.pit()));
814 // do not lose start of appendix marker (bug 4212)
815 if (soa && old.pit() < pit_type(plist.size()))
816 plist[old.pit()].params().startOfAppendix(true);
818 // see #warning (FIXME?) above
819 if (cur.depth() >= old.depth()) {
820 CursorSlice & curslice = cur[old.depth() - 1];
821 if (&curslice.inset() == &old.inset()
822 && curslice.pit() > old.pit()) {
824 // since a paragraph has been deleted, all the
825 // insets after `old' have been copied and
826 // their address has changed. Therefore we
827 // need to `regenerate' cur. (JMarc)
828 cur.updateInsets(&(cur.bottom().inset()));
829 need_anchor_change = true;
835 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
836 need_anchor_change = true;
837 // We return true here because the Paragraph contents changed and
838 // we need a redraw before further action is processed.
846 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
848 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
850 for (pit_type pit = first; pit <= last; ++pit) {
851 Paragraph & par = pars_[pit];
853 // We allow all kinds of "mumbo-jumbo" when freespacing.
854 if (par.isFreeSpacing())
857 for (pos_type pos = 1; pos < par.size(); ++pos) {
858 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
859 && !par.isDeleted(pos - 1)) {
860 if (par.eraseChar(pos - 1, trackChanges)) {
866 // don't delete anything if this is the only remaining paragraph within the given range
867 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
871 // don't delete empty paragraphs with keepempty set
872 if (par.allowEmpty())
875 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
876 pars_.erase(boost::next(pars_.begin(), pit));
882 par.stripLeadingSpaces(trackChanges);
887 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
889 cur.recordUndo(ATOMIC_UNDO, first, last);
893 void Text::recUndo(Cursor & cur, pit_type par) const
895 cur.recordUndo(ATOMIC_UNDO, par, par);