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 "TextClass.h"
46 #include "TextMetrics.h"
49 #include "frontends/FontMetrics.h"
51 #include "insets/InsetEnvironment.h"
53 #include "mathed/InsetMathHull.h"
55 #include "support/debug.h"
56 #include "support/gettext.h"
57 #include "support/textutils.h"
59 #include <boost/next_prior.hpp>
68 : autoBreakRows_(false)
72 bool Text::isMainText(Buffer const & buffer) const
74 return &buffer.text() == this;
78 FontInfo Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
80 LayoutPtr 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());
90 FontInfo font = layout->font;
91 // Realize with the fonts of lesser depth.
92 //font.realize(outerFont(pit, paragraphs()));
93 font.realize(buffer.params().getFont().fontInfo());
99 FontInfo Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
101 LayoutPtr const & layout = par.layout();
103 if (!par.getDepth()) {
104 FontInfo lf = layout->reslabelfont;
105 // In case the default family has been customized
106 if (layout->labelfont.family() == INHERIT_FAMILY)
107 lf.setFamily(buffer.params().getFont().fontInfo().family());
111 FontInfo font = layout->labelfont;
112 // Realize with the fonts of lesser depth.
113 font.realize(buffer.params().getFont().fontInfo());
119 void Text::setCharFont(Buffer const & buffer, pit_type pit,
120 pos_type pos, Font const & fnt, Font const & display_font)
123 LayoutPtr const & layout = pars_[pit].layout();
125 // Get concrete layout font to reduce against
128 if (pos < pars_[pit].beginOfBody())
129 layoutfont = layout->labelfont;
131 layoutfont = layout->font;
133 // Realize against environment font information
134 if (pars_[pit].getDepth()) {
136 while (!layoutfont.resolved() &&
137 tp != pit_type(paragraphs().size()) &&
138 pars_[tp].getDepth()) {
139 tp = outerHook(tp, paragraphs());
140 if (tp != pit_type(paragraphs().size()))
141 layoutfont.realize(pars_[tp].layout()->font);
145 // Inside inset, apply the inset's font attributes if any
147 if (!isMainText(buffer))
148 layoutfont.realize(display_font.fontInfo());
150 layoutfont.realize(buffer.params().getFont().fontInfo());
152 // Now, reduce font against full layout font
153 font.fontInfo().reduce(layoutfont);
155 pars_[pit].setFont(pos, font);
159 void Text::setInsetFont(BufferView const & bv, pit_type pit,
160 pos_type pos, Font const & font, bool toggleall)
162 Inset * const inset = pars_[pit].getInset(pos);
163 BOOST_ASSERT(inset && inset->noFontChange());
165 CursorSlice::idx_type endidx = inset->nargs();
166 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
167 Text * text = cs.text();
169 // last position of the cell
170 CursorSlice cellend = cs;
171 cellend.pit() = cellend.lastpit();
172 cellend.pos() = cellend.lastpos();
173 text->setFont(bv, cs, cellend, font, toggleall);
179 // return past-the-last paragraph influenced by a layout change on pit
180 pit_type Text::undoSpan(pit_type pit)
182 pit_type end = paragraphs().size();
183 pit_type nextpit = pit + 1;
186 //because of parindents
187 if (!pars_[pit].getDepth())
188 return boost::next(nextpit);
189 //because of depth constrains
190 for (; nextpit != end; ++pit, ++nextpit) {
191 if (!pars_[pit].getDepth())
198 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
199 docstring const & layout)
201 BOOST_ASSERT(start != end);
203 BufferParams const & bufparams = buffer.params();
204 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
206 for (pit_type pit = start; pit != end; ++pit) {
207 Paragraph & par = pars_[pit];
208 par.applyLayout(lyxlayout);
209 if (lyxlayout->margintype == MARGIN_MANUAL)
210 par.setLabelWidthString(par.translateIfPossible(
211 lyxlayout->labelstring(), buffer.params()));
216 // set layout over selection and make a total rebreak of those paragraphs
217 void Text::setLayout(Cursor & cur, docstring const & layout)
219 BOOST_ASSERT(this == cur.text());
220 // special handling of new environment insets
221 BufferView & bv = cur.bv();
222 BufferParams const & params = bv.buffer().params();
223 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
224 if (lyxlayout->is_environment) {
225 // move everything in a new environment inset
226 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
227 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
228 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
229 lyx::dispatch(FuncRequest(LFUN_CUT));
230 Inset * inset = new InsetEnvironment(params, layout);
231 insertInset(cur, inset);
232 //inset->edit(cur, true);
233 //lyx::dispatch(FuncRequest(LFUN_PASTE));
237 pit_type start = cur.selBegin().pit();
238 pit_type end = cur.selEnd().pit() + 1;
239 pit_type undopit = undoSpan(end - 1);
240 recUndo(cur, start, undopit - 1);
241 setLayout(cur.buffer(), start, end, layout);
242 updateLabels(cur.buffer());
246 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
247 Paragraph const & par, int max_depth)
249 if (par.layout()->labeltype == LABEL_BIBLIO)
251 int const depth = par.params().depth();
252 if (type == Text::INC_DEPTH && depth < max_depth)
254 if (type == Text::DEC_DEPTH && depth > 0)
260 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
262 BOOST_ASSERT(this == cur.text());
263 // this happens when selecting several cells in tabular (bug 2630)
264 if (cur.selBegin().idx() != cur.selEnd().idx())
267 pit_type const beg = cur.selBegin().pit();
268 pit_type const end = cur.selEnd().pit() + 1;
269 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
271 for (pit_type pit = beg; pit != end; ++pit) {
272 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
274 max_depth = pars_[pit].getMaxDepthAfter();
280 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
282 BOOST_ASSERT(this == cur.text());
283 pit_type const beg = cur.selBegin().pit();
284 pit_type const end = cur.selEnd().pit() + 1;
285 cur.recordUndoSelection();
286 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
288 for (pit_type pit = beg; pit != end; ++pit) {
289 Paragraph & par = pars_[pit];
290 if (lyx::changeDepthAllowed(type, par, max_depth)) {
291 int const depth = par.params().depth();
292 if (type == INC_DEPTH)
293 par.params().depth(depth + 1);
295 par.params().depth(depth - 1);
297 max_depth = par.getMaxDepthAfter();
299 // this handles the counter labels, and also fixes up
300 // depth values for follow-on (child) paragraphs
301 updateLabels(cur.buffer());
305 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
307 BOOST_ASSERT(this == cur.text());
308 // Set the current_font
309 // Determine basis font
311 pit_type pit = cur.pit();
312 if (cur.pos() < pars_[pit].beginOfBody())
313 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
315 layoutfont = getLayoutFont(cur.buffer(), pit);
317 // Update current font
318 cur.real_current_font.update(font,
319 cur.buffer().params().language,
322 // Reduce to implicit settings
323 cur.current_font = cur.real_current_font;
324 cur.current_font.fontInfo().reduce(layoutfont);
325 // And resolve it completely
326 cur.real_current_font.fontInfo().realize(layoutfont);
328 // if there is no selection that's all we need to do
329 if (!cur.selection())
332 // Ok, we have a selection.
333 cur.recordUndoSelection();
335 setFont(cur.bv(), cur.selectionBegin().top(),
336 cur.selectionEnd().top(), font, toggleall);
340 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
341 CursorSlice const & end, Font const & font,
344 Buffer const & buffer = bv.buffer();
346 // Don't use forwardChar here as ditend might have
347 // pos() == lastpos() and forwardChar would miss it.
348 // Can't use forwardPos either as this descends into
350 Language const * language = buffer.params().language;
351 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
352 if (dit.pos() != dit.lastpos()) {
353 pit_type const pit = dit.pit();
354 pos_type const pos = dit.pos();
355 Inset * inset = pars_[pit].getInset(pos);
356 if (inset && inset->noFontChange()) {
357 // We need to propagate the font change to all
358 // text cells of the inset (bug 1973).
359 // FIXME: This should change, see documentation
360 // of noFontChange in Inset.h
361 setInsetFont(bv, pit, pos, font, toggleall);
363 TextMetrics const & tm = bv.textMetrics(this);
364 Font f = tm.getDisplayFont(pit, pos);
365 f.update(font, language, toggleall);
366 setCharFont(buffer, pit, pos, f, tm.font_);
372 bool Text::cursorTop(Cursor & cur)
374 BOOST_ASSERT(this == cur.text());
375 return setCursor(cur, 0, 0);
379 bool Text::cursorBottom(Cursor & cur)
381 BOOST_ASSERT(this == cur.text());
382 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
386 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
388 BOOST_ASSERT(this == cur.text());
389 // If the mask is completely neutral, tell user
390 if (font.fontInfo() == ignore_font &&
391 (font.language() == 0 || font.language() == ignore_language)) {
392 // Could only happen with user style
393 cur.message(_("No font change defined."));
397 // Try implicit word selection
398 // If there is a change in the language the implicit word selection
400 CursorSlice resetCursor = cur.top();
401 bool implicitSelection =
402 font.language() == ignore_language
403 && font.fontInfo().number() == FONT_IGNORE
404 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
407 setFont(cur, font, toggleall);
409 // Implicit selections are cleared afterwards
410 // and cursor is set to the original position.
411 if (implicitSelection) {
412 cur.clearSelection();
413 cur.top() = resetCursor;
419 docstring Text::getStringToIndex(Cursor const & cur)
421 BOOST_ASSERT(this == cur.text());
425 idxstring = cur.selectionAsString(false);
427 // Try implicit word selection. If there is a change
428 // in the language the implicit word selection is
431 selectWord(tmpcur, PREVIOUS_WORD);
433 if (!tmpcur.selection())
434 cur.message(_("Nothing to index!"));
435 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
436 cur.message(_("Cannot index more than one paragraph!"));
438 idxstring = tmpcur.selectionAsString(false);
445 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
447 BOOST_ASSERT(cur.text());
448 // make sure that the depth behind the selection are restored, too
449 pit_type undopit = undoSpan(cur.selEnd().pit());
450 recUndo(cur, cur.selBegin().pit(), undopit - 1);
452 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
454 Paragraph & par = pars_[pit];
455 ParagraphParameters params = par.params();
456 params.read(to_utf8(arg), merge);
457 Layout const & layout = *(par.layout());
458 par.params().apply(params, layout);
463 //FIXME This is a little redundant now, but it's probably worth keeping,
464 //especially if we're going to go away from using serialization internally
466 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
468 BOOST_ASSERT(cur.text());
469 // make sure that the depth behind the selection are restored, too
470 pit_type undopit = undoSpan(cur.selEnd().pit());
471 recUndo(cur, cur.selBegin().pit(), undopit - 1);
473 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
475 Paragraph & par = pars_[pit];
476 Layout const & layout = *(par.layout());
477 par.params().apply(p, layout);
482 // this really should just insert the inset and not move the cursor.
483 void Text::insertInset(Cursor & cur, Inset * inset)
485 BOOST_ASSERT(this == cur.text());
487 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
488 Change(cur.buffer().params().trackChanges ?
489 Change::INSERTED : Change::UNCHANGED));
493 // needed to insert the selection
494 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
496 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
497 cur.current_font, str, autoBreakRows_);
501 // turn double CR to single CR, others are converted into one
502 // blank. Then insertStringAsLines is called
503 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
505 docstring linestr = str;
506 bool newline_inserted = false;
508 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
509 if (linestr[i] == '\n') {
510 if (newline_inserted) {
511 // we know that \r will be ignored by
512 // insertStringAsLines. Of course, it is a dirty
513 // trick, but it works...
514 linestr[i - 1] = '\r';
518 newline_inserted = true;
520 } else if (isPrintable(linestr[i])) {
521 newline_inserted = false;
524 insertStringAsLines(cur, linestr);
528 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
529 bool setfont, bool boundary)
532 setCursorIntern(cur, par, pos, setfont, boundary);
533 return cur.bv().checkDepm(cur, old);
537 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
539 BOOST_ASSERT(par != int(paragraphs().size()));
543 // now some strict checking
544 Paragraph & para = getPar(par);
546 // None of these should happen, but we're scaredy-cats
548 lyxerr << "dont like -1" << endl;
552 if (pos > para.size()) {
553 lyxerr << "dont like 1, pos: " << pos
554 << " size: " << para.size()
555 << " par: " << par << endl;
561 void Text::setCursorIntern(Cursor & cur,
562 pit_type par, pos_type pos, bool setfont, bool boundary)
564 BOOST_ASSERT(this == cur.text());
565 cur.boundary(boundary);
566 setCursor(cur.top(), par, pos);
568 cur.setCurrentFont();
572 bool Text::checkAndActivateInset(Cursor & cur, bool front)
576 if (front && cur.pos() == cur.lastpos())
578 if (!front && cur.pos() == 0)
580 Inset * inset = front ? cur.nextInset() : cur.prevInset();
581 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
584 * Apparently, when entering an inset we are expected to be positioned
585 * *before* it in the containing paragraph, regardless of the direction
586 * from which we are entering. Otherwise, cursor placement goes awry,
587 * and when we exit from the beginning, we'll be placed *after* the
592 inset->edit(cur, front);
597 bool Text::cursorBackward(Cursor & cur)
599 // Tell BufferView to test for FitCursor in any case!
600 cur.updateFlags(Update::FitCursor);
602 // not at paragraph start?
604 // if on right side of boundary (i.e. not at paragraph end, but line end)
605 // -> skip it, i.e. set boundary to true, i.e. go only logically left
606 // there are some exceptions to ignore this: lineseps, newlines, spaces
608 // some effectless debug code to see the values in the debugger
609 bool bound = cur.boundary();
610 int rowpos = cur.textRow().pos();
612 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
613 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
614 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
616 if (!cur.boundary() &&
617 cur.textRow().pos() == cur.pos() &&
618 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
619 !cur.paragraph().isNewline(cur.pos() - 1) &&
620 !cur.paragraph().isSeparator(cur.pos() - 1)) {
621 return setCursor(cur, cur.pit(), cur.pos(), true, true);
624 // go left and try to enter inset
625 if (checkAndActivateInset(cur, false))
628 // normal character left
629 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
632 // move to the previous paragraph or do nothing
634 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
639 bool Text::cursorForward(Cursor & cur)
641 // Tell BufferView to test for FitCursor in any case!
642 cur.updateFlags(Update::FitCursor);
644 // not at paragraph end?
645 if (cur.pos() != cur.lastpos()) {
646 // in front of editable inset, i.e. jump into it?
647 if (checkAndActivateInset(cur, true))
650 TextMetrics const & tm = cur.bv().textMetrics(this);
651 // if left of boundary -> just jump to right side
652 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
653 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
654 return setCursor(cur, cur.pit(), cur.pos(), true, false);
656 // next position is left of boundary,
657 // but go to next line for special cases like space, newline, linesep
659 // some effectless debug code to see the values in the debugger
660 int endpos = cur.textRow().endpos();
661 int lastpos = cur.lastpos();
663 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
664 bool newline = cur.paragraph().isNewline(cur.pos());
665 bool sep = cur.paragraph().isSeparator(cur.pos());
666 if (cur.pos() != cur.lastpos()) {
667 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
668 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
669 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
672 if (cur.textRow().endpos() == cur.pos() + 1 &&
673 cur.textRow().endpos() != cur.lastpos() &&
674 !cur.paragraph().isNewline(cur.pos()) &&
675 !cur.paragraph().isLineSeparator(cur.pos()) &&
676 !cur.paragraph().isSeparator(cur.pos())) {
677 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
680 // in front of RTL boundary? Stay on this side of the boundary because:
681 // ab|cDDEEFFghi -> abc|DDEEFFghi
682 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
683 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
686 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
689 // move to next paragraph
690 if (cur.pit() != cur.lastpit())
691 return setCursor(cur, cur.pit() + 1, 0, true, false);
696 bool Text::cursorUpParagraph(Cursor & cur)
698 bool updated = false;
700 updated = setCursor(cur, cur.pit(), 0);
701 else if (cur.pit() != 0)
702 updated = setCursor(cur, cur.pit() - 1, 0);
707 bool Text::cursorDownParagraph(Cursor & cur)
709 bool updated = false;
710 if (cur.pit() != cur.lastpit())
711 updated = setCursor(cur, cur.pit() + 1, 0);
713 updated = setCursor(cur, cur.pit(), cur.lastpos());
718 // fix the cursor `cur' after a characters has been deleted at `where'
719 // position. Called by deleteEmptyParagraphMechanism
720 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
722 // Do nothing if cursor is not in the paragraph where the
724 if (cur.pit() != where.pit())
727 // If cursor position is after the deletion place update it
728 if (cur.pos() > where.pos())
731 // Check also if we don't want to set the cursor on a spot behind the
732 // pagragraph because we erased the last character.
733 if (cur.pos() > cur.lastpos())
734 cur.pos() = cur.lastpos();
738 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
739 Cursor & old, bool & need_anchor_change)
741 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
743 Paragraph & oldpar = old.paragraph();
745 // We allow all kinds of "mumbo-jumbo" when freespacing.
746 if (oldpar.isFreeSpacing())
749 /* Ok I'll put some comments here about what is missing.
750 There are still some small problems that can lead to
751 double spaces stored in the document file or space at
752 the beginning of paragraphs(). This happens if you have
753 the cursor between to spaces and then save. Or if you
754 cut and paste and the selection have a space at the
755 beginning and then save right after the paste. (Lgb)
758 // If old.pos() == 0 and old.pos()(1) == LineSeparator
759 // delete the LineSeparator.
762 // If old.pos() == 1 and old.pos()(0) == LineSeparator
763 // delete the LineSeparator.
766 bool const same_inset = &old.inset() == &cur.inset();
767 bool const same_par = same_inset && old.pit() == cur.pit();
768 bool const same_par_pos = same_par && old.pos() == cur.pos();
770 // If the chars around the old cursor were spaces, delete one of them.
772 // Only if the cursor has really moved.
774 && old.pos() < oldpar.size()
775 && oldpar.isLineSeparator(old.pos())
776 && oldpar.isLineSeparator(old.pos() - 1)
777 && !oldpar.isDeleted(old.pos() - 1)
778 && !oldpar.isDeleted(old.pos())) {
779 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
780 // FIXME: This will not work anymore when we have multiple views of the same buffer
781 // In this case, we will have to correct also the cursors held by
782 // other bufferviews. It will probably be easier to do that in a more
783 // automated way in CursorSlice code. (JMarc 26/09/2001)
784 // correct all cursor parts
786 fixCursorAfterDelete(cur.top(), old.top());
787 need_anchor_change = true;
793 // only do our magic if we changed paragraph
797 // don't delete anything if this is the ONLY paragraph!
798 if (old.lastpit() == 0)
801 // Do not delete empty paragraphs with keepempty set.
802 if (oldpar.allowEmpty())
805 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
807 old.recordUndo(ATOMIC_UNDO,
808 max(old.pit() - 1, pit_type(0)),
809 min(old.pit() + 1, old.lastpit()));
810 ParagraphList & plist = old.text()->paragraphs();
811 bool const soa = oldpar.params().startOfAppendix();
812 plist.erase(boost::next(plist.begin(), old.pit()));
813 // do not lose start of appendix marker (bug 4212)
814 if (soa && old.pit() < pit_type(plist.size()))
815 plist[old.pit()].params().startOfAppendix(true);
817 // see #warning (FIXME?) above
818 if (cur.depth() >= old.depth()) {
819 CursorSlice & curslice = cur[old.depth() - 1];
820 if (&curslice.inset() == &old.inset()
821 && curslice.pit() > old.pit()) {
823 // since a paragraph has been deleted, all the
824 // insets after `old' have been copied and
825 // their address has changed. Therefore we
826 // need to `regenerate' cur. (JMarc)
827 cur.updateInsets(&(cur.bottom().inset()));
828 need_anchor_change = true;
834 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
835 need_anchor_change = true;
836 // We return true here because the Paragraph contents changed and
837 // we need a redraw before further action is processed.
845 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
847 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
849 for (pit_type pit = first; pit <= last; ++pit) {
850 Paragraph & par = pars_[pit];
852 // We allow all kinds of "mumbo-jumbo" when freespacing.
853 if (par.isFreeSpacing())
856 for (pos_type pos = 1; pos < par.size(); ++pos) {
857 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
858 && !par.isDeleted(pos - 1)) {
859 if (par.eraseChar(pos - 1, trackChanges)) {
865 // don't delete anything if this is the only remaining paragraph within the given range
866 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
870 // don't delete empty paragraphs with keepempty set
871 if (par.allowEmpty())
874 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
875 pars_.erase(boost::next(pars_.begin(), pit));
881 par.stripLeadingSpaces(trackChanges);
886 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
888 cur.recordUndo(ATOMIC_UNDO, first, last);
892 void Text::recUndo(Cursor & cur, pit_type par) const
894 cur.recordUndo(ATOMIC_UNDO, par, par);