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"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
44 #include "Paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "ParIterator.h"
49 #include "ServerSocket.h"
50 #include "TextClass.h"
51 #include "TextMetrics.h"
54 #include "frontends/FontMetrics.h"
56 #include "insets/InsetEnvironment.h"
58 #include "mathed/InsetMathHull.h"
60 #include "support/textutils.h"
62 #include <boost/current_function.hpp>
63 #include <boost/next_prior.hpp>
68 using std::ostringstream;
72 using std::istringstream;
77 : autoBreakRows_(false)
81 bool Text::isMainText(Buffer const & buffer) const
83 return &buffer.text() == this;
87 FontInfo Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
89 LayoutPtr const & layout = pars_[pit].layout();
91 if (!pars_[pit].getDepth()) {
92 FontInfo lf = layout->resfont;
93 // In case the default family has been customized
94 if (layout->font.family() == INHERIT_FAMILY)
95 lf.setFamily(buffer.params().getFont().fontInfo().family());
99 FontInfo font = layout->font;
100 // Realize with the fonts of lesser depth.
101 //font.realize(outerFont(pit, paragraphs()));
102 font.realize(buffer.params().getFont().fontInfo());
108 FontInfo Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
110 LayoutPtr const & layout = par.layout();
112 if (!par.getDepth()) {
113 FontInfo lf = layout->reslabelfont;
114 // In case the default family has been customized
115 if (layout->labelfont.family() == INHERIT_FAMILY)
116 lf.setFamily(buffer.params().getFont().fontInfo().family());
120 FontInfo font = layout->labelfont;
121 // Realize with the fonts of lesser depth.
122 font.realize(buffer.params().getFont().fontInfo());
128 void Text::setCharFont(Buffer const & buffer, pit_type pit,
129 pos_type pos, Font const & fnt, Font const & display_font)
132 LayoutPtr const & layout = pars_[pit].layout();
134 // Get concrete layout font to reduce against
137 if (pos < pars_[pit].beginOfBody())
138 layoutfont = layout->labelfont;
140 layoutfont = layout->font;
142 // Realize against environment font information
143 if (pars_[pit].getDepth()) {
145 while (!layoutfont.resolved() &&
146 tp != pit_type(paragraphs().size()) &&
147 pars_[tp].getDepth()) {
148 tp = outerHook(tp, paragraphs());
149 if (tp != pit_type(paragraphs().size()))
150 layoutfont.realize(pars_[tp].layout()->font);
154 // Inside inset, apply the inset's font attributes if any
156 if (!isMainText(buffer))
157 layoutfont.realize(display_font.fontInfo());
159 layoutfont.realize(buffer.params().getFont().fontInfo());
161 // Now, reduce font against full layout font
162 font.fontInfo().reduce(layoutfont);
164 pars_[pit].setFont(pos, font);
168 void Text::setInsetFont(BufferView const & bv, pit_type pit,
169 pos_type pos, Font const & font, bool toggleall)
171 BOOST_ASSERT(pars_[pit].isInset(pos) &&
172 pars_[pit].getInset(pos)->noFontChange());
174 Inset * const inset = pars_[pit].getInset(pos);
175 CursorSlice::idx_type endidx = inset->nargs();
176 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
177 Text * text = cs.text();
179 // last position of the cell
180 CursorSlice cellend = cs;
181 cellend.pit() = cellend.lastpit();
182 cellend.pos() = cellend.lastpos();
183 text->setFont(bv, cs, cellend, font, toggleall);
189 // return past-the-last paragraph influenced by a layout change on pit
190 pit_type Text::undoSpan(pit_type pit)
192 pit_type end = paragraphs().size();
193 pit_type nextpit = pit + 1;
196 //because of parindents
197 if (!pars_[pit].getDepth())
198 return boost::next(nextpit);
199 //because of depth constrains
200 for (; nextpit != end; ++pit, ++nextpit) {
201 if (!pars_[pit].getDepth())
208 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
209 docstring const & layout)
211 BOOST_ASSERT(start != end);
213 BufferParams const & bufparams = buffer.params();
214 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
216 for (pit_type pit = start; pit != end; ++pit) {
217 Paragraph & par = pars_[pit];
218 par.applyLayout(lyxlayout);
219 if (lyxlayout->margintype == MARGIN_MANUAL)
220 par.setLabelWidthString(par.translateIfPossible(
221 lyxlayout->labelstring(), buffer.params()));
226 // set layout over selection and make a total rebreak of those paragraphs
227 void Text::setLayout(Cursor & cur, docstring const & layout)
229 BOOST_ASSERT(this == cur.text());
230 // special handling of new environment insets
231 BufferView & bv = cur.bv();
232 BufferParams const & params = bv.buffer().params();
233 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
234 if (lyxlayout->is_environment) {
235 // move everything in a new environment inset
236 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
237 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
238 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
239 lyx::dispatch(FuncRequest(LFUN_CUT));
240 Inset * inset = new InsetEnvironment(params, layout);
241 insertInset(cur, inset);
242 //inset->edit(cur, true);
243 //lyx::dispatch(FuncRequest(LFUN_PASTE));
247 pit_type start = cur.selBegin().pit();
248 pit_type end = cur.selEnd().pit() + 1;
249 pit_type undopit = undoSpan(end - 1);
250 recUndo(cur, start, undopit - 1);
251 setLayout(cur.buffer(), start, end, layout);
252 updateLabels(cur.buffer());
256 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
257 Paragraph const & par, int max_depth)
259 if (par.layout()->labeltype == LABEL_BIBLIO)
261 int const depth = par.params().depth();
262 if (type == Text::INC_DEPTH && depth < max_depth)
264 if (type == Text::DEC_DEPTH && depth > 0)
270 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
272 BOOST_ASSERT(this == cur.text());
273 // this happens when selecting several cells in tabular (bug 2630)
274 if (cur.selBegin().idx() != cur.selEnd().idx())
277 pit_type const beg = cur.selBegin().pit();
278 pit_type const end = cur.selEnd().pit() + 1;
279 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
281 for (pit_type pit = beg; pit != end; ++pit) {
282 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
284 max_depth = pars_[pit].getMaxDepthAfter();
290 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
292 BOOST_ASSERT(this == cur.text());
293 pit_type const beg = cur.selBegin().pit();
294 pit_type const end = cur.selEnd().pit() + 1;
295 cur.recordUndoSelection();
296 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
298 for (pit_type pit = beg; pit != end; ++pit) {
299 Paragraph & par = pars_[pit];
300 if (lyx::changeDepthAllowed(type, par, max_depth)) {
301 int const depth = par.params().depth();
302 if (type == INC_DEPTH)
303 par.params().depth(depth + 1);
305 par.params().depth(depth - 1);
307 max_depth = par.getMaxDepthAfter();
309 // this handles the counter labels, and also fixes up
310 // depth values for follow-on (child) paragraphs
311 updateLabels(cur.buffer());
315 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
317 BOOST_ASSERT(this == cur.text());
318 // Set the current_font
319 // Determine basis font
321 pit_type pit = cur.pit();
322 if (cur.pos() < pars_[pit].beginOfBody())
323 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
325 layoutfont = getLayoutFont(cur.buffer(), pit);
327 // Update current font
328 cur.real_current_font.update(font,
329 cur.buffer().params().language,
332 // Reduce to implicit settings
333 cur.current_font = cur.real_current_font;
334 cur.current_font.fontInfo().reduce(layoutfont);
335 // And resolve it completely
336 cur.real_current_font.fontInfo().realize(layoutfont);
338 // if there is no selection that's all we need to do
339 if (!cur.selection())
342 // Ok, we have a selection.
343 cur.recordUndoSelection();
345 setFont(cur.bv(), cur.selectionBegin().top(),
346 cur.selectionEnd().top(), font, toggleall);
350 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
351 CursorSlice const & end, Font const & font,
354 Buffer const & buffer = bv.buffer();
356 // Don't use forwardChar here as ditend might have
357 // pos() == lastpos() and forwardChar would miss it.
358 // Can't use forwardPos either as this descends into
360 Language const * language = buffer.params().language;
361 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
362 if (dit.pos() != dit.lastpos()) {
363 pit_type const pit = dit.pit();
364 pos_type const pos = dit.pos();
365 if (pars_[pit].isInset(pos) &&
366 pars_[pit].getInset(pos)->noFontChange())
367 // We need to propagate the font change to all
368 // text cells of the inset (bug 1973).
369 // FIXME: This should change, see documentation
370 // of noFontChange in Inset.h
371 setInsetFont(bv, pit, pos, font, toggleall);
372 TextMetrics const & tm = bv.textMetrics(this);
373 Font f = tm.getDisplayFont(pit, pos);
374 f.update(font, language, toggleall);
375 setCharFont(buffer, pit, pos, f, tm.font_);
381 bool Text::cursorTop(Cursor & cur)
383 BOOST_ASSERT(this == cur.text());
384 return setCursor(cur, 0, 0);
388 bool Text::cursorBottom(Cursor & cur)
390 BOOST_ASSERT(this == cur.text());
391 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
395 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
397 BOOST_ASSERT(this == cur.text());
398 // If the mask is completely neutral, tell user
399 if (font.fontInfo() == ignore_font &&
400 (font.language() == 0 || font.language() == ignore_language)) {
401 // Could only happen with user style
402 cur.message(_("No font change defined."));
406 // Try implicit word selection
407 // If there is a change in the language the implicit word selection
409 CursorSlice resetCursor = cur.top();
410 bool implicitSelection =
411 font.language() == ignore_language
412 && font.fontInfo().number() == FONT_IGNORE
413 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
416 setFont(cur, font, toggleall);
418 // Implicit selections are cleared afterwards
419 // and cursor is set to the original position.
420 if (implicitSelection) {
421 cur.clearSelection();
422 cur.top() = resetCursor;
428 docstring Text::getStringToIndex(Cursor const & cur)
430 BOOST_ASSERT(this == cur.text());
434 idxstring = cur.selectionAsString(false);
436 // Try implicit word selection. If there is a change
437 // in the language the implicit word selection is
440 selectWord(tmpcur, PREVIOUS_WORD);
442 if (!tmpcur.selection())
443 cur.message(_("Nothing to index!"));
444 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
445 cur.message(_("Cannot index more than one paragraph!"));
447 idxstring = tmpcur.selectionAsString(false);
454 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
456 BOOST_ASSERT(cur.text());
457 // make sure that the depth behind the selection are restored, too
458 pit_type undopit = undoSpan(cur.selEnd().pit());
459 recUndo(cur, cur.selBegin().pit(), undopit - 1);
461 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
463 Paragraph & par = pars_[pit];
464 ParagraphParameters params = par.params();
465 params.read(to_utf8(arg), merge);
466 Layout const & layout = *(par.layout());
467 par.params().apply(params, layout);
472 //FIXME This is a little redundant now, but it's probably worth keeping,
473 //especially if we're going to go away from using serialization internally
475 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
477 BOOST_ASSERT(cur.text());
478 // make sure that the depth behind the selection are restored, too
479 pit_type undopit = undoSpan(cur.selEnd().pit());
480 recUndo(cur, cur.selBegin().pit(), undopit - 1);
482 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
484 Paragraph & par = pars_[pit];
485 Layout const & layout = *(par.layout());
486 par.params().apply(p, layout);
491 // this really should just insert the inset and not move the cursor.
492 void Text::insertInset(Cursor & cur, Inset * inset)
494 BOOST_ASSERT(this == cur.text());
496 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
497 Change(cur.buffer().params().trackChanges ?
498 Change::INSERTED : Change::UNCHANGED));
502 // needed to insert the selection
503 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
505 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
506 cur.current_font, str, autoBreakRows_);
510 // turn double CR to single CR, others are converted into one
511 // blank. Then insertStringAsLines is called
512 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
514 docstring linestr = str;
515 bool newline_inserted = false;
517 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
518 if (linestr[i] == '\n') {
519 if (newline_inserted) {
520 // we know that \r will be ignored by
521 // insertStringAsLines. Of course, it is a dirty
522 // trick, but it works...
523 linestr[i - 1] = '\r';
527 newline_inserted = true;
529 } else if (isPrintable(linestr[i])) {
530 newline_inserted = false;
533 insertStringAsLines(cur, linestr);
537 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
538 bool setfont, bool boundary)
541 setCursorIntern(cur, par, pos, setfont, boundary);
542 return cur.bv().checkDepm(cur, old);
546 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
548 BOOST_ASSERT(par != int(paragraphs().size()));
552 // now some strict checking
553 Paragraph & para = getPar(par);
555 // None of these should happen, but we're scaredy-cats
557 lyxerr << "dont like -1" << endl;
561 if (pos > para.size()) {
562 lyxerr << "dont like 1, pos: " << pos
563 << " size: " << para.size()
564 << " par: " << par << endl;
570 void Text::setCursorIntern(Cursor & cur,
571 pit_type par, pos_type pos, bool setfont, bool boundary)
573 BOOST_ASSERT(this == cur.text());
574 cur.boundary(boundary);
575 setCursor(cur.top(), par, pos);
577 cur.setCurrentFont();
581 bool Text::checkAndActivateInset(Cursor & cur, bool front)
585 if (front && cur.pos() == cur.lastpos())
587 if (!front && cur.pos() == 0)
589 Inset * inset = front ? cur.nextInset() : cur.prevInset();
590 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
593 * Apparently, when entering an inset we are expected to be positioned
594 * *before* it in the containing paragraph, regardless of the direction
595 * from which we are entering. Otherwise, cursor placement goes awry,
596 * and when we exit from the beginning, we'll be placed *after* the
601 inset->edit(cur, front);
606 bool Text::cursorBackward(Cursor & cur)
608 // Tell BufferView to test for FitCursor in any case!
609 cur.updateFlags(Update::FitCursor);
611 // not at paragraph start?
613 // if on right side of boundary (i.e. not at paragraph end, but line end)
614 // -> skip it, i.e. set boundary to true, i.e. go only logically left
615 // there are some exceptions to ignore this: lineseps, newlines, spaces
617 // some effectless debug code to see the values in the debugger
618 bool bound = cur.boundary();
619 int rowpos = cur.textRow().pos();
621 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
622 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
623 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
625 if (!cur.boundary() &&
626 cur.textRow().pos() == cur.pos() &&
627 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
628 !cur.paragraph().isNewline(cur.pos() - 1) &&
629 !cur.paragraph().isSeparator(cur.pos() - 1)) {
630 return setCursor(cur, cur.pit(), cur.pos(), true, true);
633 // go left and try to enter inset
634 if (checkAndActivateInset(cur, false))
637 // normal character left
638 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
641 // move to the previous paragraph or do nothing
643 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
648 bool Text::cursorForward(Cursor & cur)
650 // Tell BufferView to test for FitCursor in any case!
651 cur.updateFlags(Update::FitCursor);
653 // not at paragraph end?
654 if (cur.pos() != cur.lastpos()) {
655 // in front of editable inset, i.e. jump into it?
656 if (checkAndActivateInset(cur, true))
659 TextMetrics const & tm = cur.bv().textMetrics(this);
660 // if left of boundary -> just jump to right side
661 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
662 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
663 return setCursor(cur, cur.pit(), cur.pos(), true, false);
665 // next position is left of boundary,
666 // but go to next line for special cases like space, newline, linesep
668 // some effectless debug code to see the values in the debugger
669 int endpos = cur.textRow().endpos();
670 int lastpos = cur.lastpos();
672 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
673 bool newline = cur.paragraph().isNewline(cur.pos());
674 bool sep = cur.paragraph().isSeparator(cur.pos());
675 if (cur.pos() != cur.lastpos()) {
676 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
677 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
678 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
681 if (cur.textRow().endpos() == cur.pos() + 1 &&
682 cur.textRow().endpos() != cur.lastpos() &&
683 !cur.paragraph().isNewline(cur.pos()) &&
684 !cur.paragraph().isLineSeparator(cur.pos()) &&
685 !cur.paragraph().isSeparator(cur.pos())) {
686 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
689 // in front of RTL boundary? Stay on this side of the boundary because:
690 // ab|cDDEEFFghi -> abc|DDEEFFghi
691 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
692 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
695 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
698 // move to next paragraph
699 if (cur.pit() != cur.lastpit())
700 return setCursor(cur, cur.pit() + 1, 0, true, false);
705 bool Text::cursorUpParagraph(Cursor & cur)
707 bool updated = false;
709 updated = setCursor(cur, cur.pit(), 0);
710 else if (cur.pit() != 0)
711 updated = setCursor(cur, cur.pit() - 1, 0);
716 bool Text::cursorDownParagraph(Cursor & cur)
718 bool updated = false;
719 if (cur.pit() != cur.lastpit())
720 updated = setCursor(cur, cur.pit() + 1, 0);
722 updated = setCursor(cur, cur.pit(), cur.lastpos());
727 // fix the cursor `cur' after a characters has been deleted at `where'
728 // position. Called by deleteEmptyParagraphMechanism
729 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
731 // Do nothing if cursor is not in the paragraph where the
733 if (cur.pit() != where.pit())
736 // If cursor position is after the deletion place update it
737 if (cur.pos() > where.pos())
740 // Check also if we don't want to set the cursor on a spot behind the
741 // pagragraph because we erased the last character.
742 if (cur.pos() > cur.lastpos())
743 cur.pos() = cur.lastpos();
747 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
748 Cursor & old, bool & need_anchor_change)
750 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
752 Paragraph & oldpar = old.paragraph();
754 // We allow all kinds of "mumbo-jumbo" when freespacing.
755 if (oldpar.isFreeSpacing())
758 /* Ok I'll put some comments here about what is missing.
759 There are still some small problems that can lead to
760 double spaces stored in the document file or space at
761 the beginning of paragraphs(). This happens if you have
762 the cursor between to spaces and then save. Or if you
763 cut and paste and the selection have a space at the
764 beginning and then save right after the paste. (Lgb)
767 // If old.pos() == 0 and old.pos()(1) == LineSeparator
768 // delete the LineSeparator.
771 // If old.pos() == 1 and old.pos()(0) == LineSeparator
772 // delete the LineSeparator.
775 bool const same_inset = &old.inset() == &cur.inset();
776 bool const same_par = same_inset && old.pit() == cur.pit();
777 bool const same_par_pos = same_par && old.pos() == cur.pos();
779 // If the chars around the old cursor were spaces, delete one of them.
781 // Only if the cursor has really moved.
783 && old.pos() < oldpar.size()
784 && oldpar.isLineSeparator(old.pos())
785 && oldpar.isLineSeparator(old.pos() - 1)
786 && !oldpar.isDeleted(old.pos() - 1)
787 && !oldpar.isDeleted(old.pos())) {
788 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
789 // FIXME: This will not work anymore when we have multiple views of the same buffer
790 // In this case, we will have to correct also the cursors held by
791 // other bufferviews. It will probably be easier to do that in a more
792 // automated way in CursorSlice code. (JMarc 26/09/2001)
793 // correct all cursor parts
795 fixCursorAfterDelete(cur.top(), old.top());
796 need_anchor_change = true;
802 // only do our magic if we changed paragraph
806 // don't delete anything if this is the ONLY paragraph!
807 if (old.lastpit() == 0)
810 // Do not delete empty paragraphs with keepempty set.
811 if (oldpar.allowEmpty())
814 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
816 old.recordUndo(ATOMIC_UNDO,
817 max(old.pit() - 1, pit_type(0)),
818 min(old.pit() + 1, old.lastpit()));
819 ParagraphList & plist = old.text()->paragraphs();
820 bool const soa = oldpar.params().startOfAppendix();
821 plist.erase(boost::next(plist.begin(), old.pit()));
822 // do not lose start of appendix marker (bug 4212)
824 plist[old.pit()].params().startOfAppendix(true);
826 // see #warning (FIXME?) above
827 if (cur.depth() >= old.depth()) {
828 CursorSlice & curslice = cur[old.depth() - 1];
829 if (&curslice.inset() == &old.inset()
830 && curslice.pit() > old.pit()) {
832 // since a paragraph has been deleted, all the
833 // insets after `old' have been copied and
834 // their address has changed. Therefore we
835 // need to `regenerate' cur. (JMarc)
836 cur.updateInsets(&(cur.bottom().inset()));
837 need_anchor_change = true;
843 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
844 need_anchor_change = true;
845 // We return true here because the Paragraph contents changed and
846 // we need a redraw before further action is processed.
854 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
856 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
858 for (pit_type pit = first; pit <= last; ++pit) {
859 Paragraph & par = pars_[pit];
861 // We allow all kinds of "mumbo-jumbo" when freespacing.
862 if (par.isFreeSpacing())
865 for (pos_type pos = 1; pos < par.size(); ++pos) {
866 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
867 && !par.isDeleted(pos - 1)) {
868 if (par.eraseChar(pos - 1, trackChanges)) {
874 // don't delete anything if this is the only remaining paragraph within the given range
875 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
879 // don't delete empty paragraphs with keepempty set
880 if (par.allowEmpty())
883 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
884 pars_.erase(boost::next(pars_.begin(), pit));
890 par.stripLeadingSpaces(trackChanges);
895 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
897 cur.recordUndo(ATOMIC_UNDO, first, last);
901 void Text::recUndo(Cursor & cur, pit_type par) const
903 cur.recordUndo(ATOMIC_UNDO, par, par);