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"
53 #include "frontends/FontMetrics.h"
55 #include "insets/InsetEnvironment.h"
57 #include "mathed/InsetMathHull.h"
59 #include "support/textutils.h"
61 #include <boost/current_function.hpp>
66 using std::ostringstream;
70 using std::istringstream;
75 : autoBreakRows_(false)
79 bool Text::isMainText(Buffer const & buffer) const
81 return &buffer.text() == this;
85 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
87 LayoutPtr const & layout = pars_[pit].layout();
89 if (!pars_[pit].getDepth()) {
90 Font lf = layout->resfont;
91 // In case the default family has been customized
92 if (layout->font.family() == Font::INHERIT_FAMILY)
93 lf.setFamily(buffer.params().getFont().family());
97 Font font = layout->font;
98 // Realize with the fonts of lesser depth.
99 //font.realize(outerFont(pit, paragraphs()));
100 font.realize(buffer.params().getFont());
106 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
108 LayoutPtr const & layout = par.layout();
110 if (!par.getDepth()) {
111 Font lf = layout->reslabelfont;
112 // In case the default family has been customized
113 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
114 lf.setFamily(buffer.params().getFont().family());
118 Font font = layout->labelfont;
119 // Realize with the fonts of lesser depth.
120 font.realize(buffer.params().getFont());
126 void Text::setCharFont(Buffer const & buffer, pit_type pit,
127 pos_type pos, Font const & fnt, Font const & display_font)
130 LayoutPtr const & layout = pars_[pit].layout();
132 // Get concrete layout font to reduce against
135 if (pos < pars_[pit].beginOfBody())
136 layoutfont = layout->labelfont;
138 layoutfont = layout->font;
140 // Realize against environment font information
141 if (pars_[pit].getDepth()) {
143 while (!layoutfont.resolved() &&
144 tp != pit_type(paragraphs().size()) &&
145 pars_[tp].getDepth()) {
146 tp = outerHook(tp, paragraphs());
147 if (tp != pit_type(paragraphs().size()))
148 layoutfont.realize(pars_[tp].layout()->font);
152 // Inside inset, apply the inset's font attributes if any
154 if (!isMainText(buffer))
155 layoutfont.realize(display_font);
157 layoutfont.realize(buffer.params().getFont());
159 // Now, reduce font against full layout font
160 font.reduce(layoutfont);
162 pars_[pit].setFont(pos, font);
166 void Text::setInsetFont(BufferView const & bv, pit_type pit,
167 pos_type pos, Font const & font, bool toggleall)
169 BOOST_ASSERT(pars_[pit].isInset(pos) &&
170 pars_[pit].getInset(pos)->noFontChange());
172 Inset * const inset = pars_[pit].getInset(pos);
173 CursorSlice::idx_type endidx = inset->nargs();
174 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
175 Text * text = cs.text();
177 // last position of the cell
178 CursorSlice cellend = cs;
179 cellend.pit() = cellend.lastpit();
180 cellend.pos() = cellend.lastpos();
181 text->setFont(bv, cs, cellend, font, toggleall);
187 // return past-the-last paragraph influenced by a layout change on pit
188 pit_type Text::undoSpan(pit_type pit)
190 pit_type end = paragraphs().size();
191 pit_type nextpit = pit + 1;
194 //because of parindents
195 if (!pars_[pit].getDepth())
196 return boost::next(nextpit);
197 //because of depth constrains
198 for (; nextpit != end; ++pit, ++nextpit) {
199 if (!pars_[pit].getDepth())
206 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
207 docstring const & layout)
209 BOOST_ASSERT(start != end);
211 BufferParams const & bufparams = buffer.params();
212 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
214 for (pit_type pit = start; pit != end; ++pit) {
215 Paragraph & par = pars_[pit];
216 par.applyLayout(lyxlayout);
217 if (lyxlayout->margintype == MARGIN_MANUAL)
218 par.setLabelWidthString(par.translateIfPossible(
219 lyxlayout->labelstring(), buffer.params()));
224 // set layout over selection and make a total rebreak of those paragraphs
225 void Text::setLayout(Cursor & cur, docstring const & layout)
227 BOOST_ASSERT(this == cur.text());
228 // special handling of new environment insets
229 BufferView & bv = cur.bv();
230 BufferParams const & params = bv.buffer().params();
231 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
232 if (lyxlayout->is_environment) {
233 // move everything in a new environment inset
234 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
235 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
236 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
237 lyx::dispatch(FuncRequest(LFUN_CUT));
238 Inset * inset = new InsetEnvironment(params, layout);
239 insertInset(cur, inset);
240 //inset->edit(cur, true);
241 //lyx::dispatch(FuncRequest(LFUN_PASTE));
245 pit_type start = cur.selBegin().pit();
246 pit_type end = cur.selEnd().pit() + 1;
247 pit_type undopit = undoSpan(end - 1);
248 recUndo(cur, start, undopit - 1);
249 setLayout(cur.buffer(), start, end, layout);
250 updateLabels(cur.buffer());
254 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
255 Paragraph const & par, int max_depth)
257 if (par.layout()->labeltype == LABEL_BIBLIO)
259 int const depth = par.params().depth();
260 if (type == Text::INC_DEPTH && depth < max_depth)
262 if (type == Text::DEC_DEPTH && depth > 0)
268 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
270 BOOST_ASSERT(this == cur.text());
271 // this happens when selecting several cells in tabular (bug 2630)
272 if (cur.selBegin().idx() != cur.selEnd().idx())
275 pit_type const beg = cur.selBegin().pit();
276 pit_type const end = cur.selEnd().pit() + 1;
277 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
279 for (pit_type pit = beg; pit != end; ++pit) {
280 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
282 max_depth = pars_[pit].getMaxDepthAfter();
288 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
290 BOOST_ASSERT(this == cur.text());
291 pit_type const beg = cur.selBegin().pit();
292 pit_type const end = cur.selEnd().pit() + 1;
293 recordUndoSelection(cur);
294 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
296 for (pit_type pit = beg; pit != end; ++pit) {
297 Paragraph & par = pars_[pit];
298 if (lyx::changeDepthAllowed(type, par, max_depth)) {
299 int const depth = par.params().depth();
300 if (type == INC_DEPTH)
301 par.params().depth(depth + 1);
303 par.params().depth(depth - 1);
305 max_depth = par.getMaxDepthAfter();
307 // this handles the counter labels, and also fixes up
308 // depth values for follow-on (child) paragraphs
309 updateLabels(cur.buffer());
313 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
315 BOOST_ASSERT(this == cur.text());
316 // Set the current_font
317 // Determine basis font
319 pit_type pit = cur.pit();
320 if (cur.pos() < pars_[pit].beginOfBody())
321 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
323 layoutfont = getLayoutFont(cur.buffer(), pit);
325 // Update current font
326 cur.real_current_font.update(font,
327 cur.buffer().params().language,
330 // Reduce to implicit settings
331 cur.current_font = cur.real_current_font;
332 cur.current_font.reduce(layoutfont);
333 // And resolve it completely
334 cur.real_current_font.realize(layoutfont);
336 // if there is no selection that's all we need to do
337 if (!cur.selection())
340 // Ok, we have a selection.
341 recordUndoSelection(cur);
343 setFont(cur.bv(), cur.selectionBegin().top(),
344 cur.selectionEnd().top(), font, toggleall);
348 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
349 CursorSlice const & end, Font const & font,
352 Buffer const & buffer = bv.buffer();
354 // Don't use forwardChar here as ditend might have
355 // pos() == lastpos() and forwardChar would miss it.
356 // Can't use forwardPos either as this descends into
358 Language const * language = buffer.params().language;
359 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
360 if (dit.pos() != dit.lastpos()) {
361 pit_type const pit = dit.pit();
362 pos_type const pos = dit.pos();
363 if (pars_[pit].isInset(pos) &&
364 pars_[pit].getInset(pos)->noFontChange())
365 // We need to propagate the font change to all
366 // text cells of the inset (bug 1973).
367 // FIXME: This should change, see documentation
368 // of noFontChange in Inset.h
369 setInsetFont(bv, pit, pos, font, toggleall);
370 TextMetrics const & tm = bv.textMetrics(this);
371 Font f = tm.getDisplayFont(pit, pos);
372 f.update(font, language, toggleall);
373 setCharFont(buffer, pit, pos, f, tm.font_);
379 bool Text::cursorTop(Cursor & cur)
381 BOOST_ASSERT(this == cur.text());
382 return setCursor(cur, 0, 0);
386 bool Text::cursorBottom(Cursor & cur)
388 BOOST_ASSERT(this == cur.text());
389 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
393 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
395 BOOST_ASSERT(this == cur.text());
396 // If the mask is completely neutral, tell user
397 if (font == Font(Font::ALL_IGNORE)) {
398 // Could only happen with user style
399 cur.message(_("No font change defined."));
403 // Try implicit word selection
404 // If there is a change in the language the implicit word selection
406 CursorSlice resetCursor = cur.top();
407 bool implicitSelection =
408 font.language() == ignore_language
409 && font.number() == Font::IGNORE
410 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
413 setFont(cur, font, toggleall);
415 // Implicit selections are cleared afterwards
416 // and cursor is set to the original position.
417 if (implicitSelection) {
418 cur.clearSelection();
419 cur.top() = resetCursor;
425 docstring Text::getStringToIndex(Cursor const & cur)
427 BOOST_ASSERT(this == cur.text());
431 idxstring = cur.selectionAsString(false);
433 // Try implicit word selection. If there is a change
434 // in the language the implicit word selection is
437 selectWord(tmpcur, PREVIOUS_WORD);
439 if (!tmpcur.selection())
440 cur.message(_("Nothing to index!"));
441 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
442 cur.message(_("Cannot index more than one paragraph!"));
444 idxstring = tmpcur.selectionAsString(false);
451 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
453 BOOST_ASSERT(cur.text());
454 // make sure that the depth behind the selection are restored, too
455 pit_type undopit = undoSpan(cur.selEnd().pit());
456 recUndo(cur, cur.selBegin().pit(), undopit - 1);
458 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
460 Paragraph & par = pars_[pit];
461 ParagraphParameters params = par.params();
462 params.read(to_utf8(arg), merge);
463 Layout const & layout = *(par.layout());
464 par.params().apply(params, layout);
469 //FIXME This is a little redundant now, but it's probably worth keeping,
470 //especially if we're going to go away from using serialization internally
472 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
474 BOOST_ASSERT(cur.text());
475 // make sure that the depth behind the selection are restored, too
476 pit_type undopit = undoSpan(cur.selEnd().pit());
477 recUndo(cur, cur.selBegin().pit(), undopit - 1);
479 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
481 Paragraph & par = pars_[pit];
482 Layout const & layout = *(par.layout());
483 par.params().apply(p, layout);
488 // this really should just insert the inset and not move the cursor.
489 void Text::insertInset(Cursor & cur, Inset * inset)
491 BOOST_ASSERT(this == cur.text());
493 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
494 Change(cur.buffer().params().trackChanges ?
495 Change::INSERTED : Change::UNCHANGED));
499 // needed to insert the selection
500 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
502 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
503 cur.current_font, str, autoBreakRows_);
507 // turn double CR to single CR, others are converted into one
508 // blank. Then insertStringAsLines is called
509 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
511 docstring linestr = str;
512 bool newline_inserted = false;
514 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
515 if (linestr[i] == '\n') {
516 if (newline_inserted) {
517 // we know that \r will be ignored by
518 // insertStringAsLines. Of course, it is a dirty
519 // trick, but it works...
520 linestr[i - 1] = '\r';
524 newline_inserted = true;
526 } else if (isPrintable(linestr[i])) {
527 newline_inserted = false;
530 insertStringAsLines(cur, linestr);
534 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
535 bool setfont, bool boundary)
538 setCursorIntern(cur, par, pos, setfont, boundary);
539 return cur.bv().checkDepm(cur, old);
543 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
545 BOOST_ASSERT(par != int(paragraphs().size()));
549 // now some strict checking
550 Paragraph & para = getPar(par);
552 // None of these should happen, but we're scaredy-cats
554 lyxerr << "dont like -1" << endl;
558 if (pos > para.size()) {
559 lyxerr << "dont like 1, pos: " << pos
560 << " size: " << para.size()
561 << " par: " << par << endl;
567 void Text::setCursorIntern(Cursor & cur,
568 pit_type par, pos_type pos, bool setfont, bool boundary)
570 BOOST_ASSERT(this == cur.text());
571 cur.boundary(boundary);
572 setCursor(cur.top(), par, pos);
574 cur.setCurrentFont();
578 bool Text::checkAndActivateInset(Cursor & cur, bool front)
582 if (front && cur.pos() == cur.lastpos())
584 if (!front && cur.pos() == 0)
586 Inset * inset = front ? cur.nextInset() : cur.prevInset();
587 if (!isHighlyEditableInset(inset))
590 * Apparently, when entering an inset we are expected to be positioned
591 * *before* it in the containing paragraph, regardless of the direction
592 * from which we are entering. Otherwise, cursor placement goes awry,
593 * and when we exit from the beginning, we'll be placed *after* the
598 inset->edit(cur, front);
603 bool Text::cursorLeft(Cursor & cur)
605 // Tell BufferView to test for FitCursor in any case!
606 cur.updateFlags(Update::FitCursor);
608 // not at paragraph start?
610 // if on right side of boundary (i.e. not at paragraph end, but line end)
611 // -> skip it, i.e. set boundary to true, i.e. go only logically left
612 // there are some exceptions to ignore this: lineseps, newlines, spaces
614 // some effectless debug code to see the values in the debugger
615 bool bound = cur.boundary();
616 int rowpos = cur.textRow().pos();
618 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
619 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
620 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
622 if (!cur.boundary() &&
623 cur.textRow().pos() == cur.pos() &&
624 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
625 !cur.paragraph().isNewline(cur.pos() - 1) &&
626 !cur.paragraph().isSeparator(cur.pos() - 1)) {
627 return setCursor(cur, cur.pit(), cur.pos(), true, true);
630 // go left and try to enter inset
631 if (checkAndActivateInset(cur, false))
634 // normal character left
635 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
638 // move to the previous paragraph or do nothing
640 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
645 bool Text::cursorRight(Cursor & cur)
647 // Tell BufferView to test for FitCursor in any case!
648 cur.updateFlags(Update::FitCursor);
650 // not at paragraph end?
651 if (cur.pos() != cur.lastpos()) {
652 // in front of editable inset, i.e. jump into it?
653 if (checkAndActivateInset(cur, true))
656 TextMetrics const & tm = cur.bv().textMetrics(this);
657 // if left of boundary -> just jump to right side
658 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
659 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
660 return setCursor(cur, cur.pit(), cur.pos(), true, false);
662 // next position is left of boundary,
663 // but go to next line for special cases like space, newline, linesep
665 // some effectless debug code to see the values in the debugger
666 int endpos = cur.textRow().endpos();
667 int lastpos = cur.lastpos();
669 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
670 bool newline = cur.paragraph().isNewline(cur.pos());
671 bool sep = cur.paragraph().isSeparator(cur.pos());
672 if (cur.pos() != cur.lastpos()) {
673 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
674 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
675 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
678 if (cur.textRow().endpos() == cur.pos() + 1 &&
679 cur.textRow().endpos() != cur.lastpos() &&
680 !cur.paragraph().isNewline(cur.pos()) &&
681 !cur.paragraph().isLineSeparator(cur.pos()) &&
682 !cur.paragraph().isSeparator(cur.pos())) {
683 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
686 // in front of RTL boundary? Stay on this side of the boundary because:
687 // ab|cDDEEFFghi -> abc|DDEEFFghi
688 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
689 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
692 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
695 // move to next paragraph
696 if (cur.pit() != cur.lastpit())
697 return setCursor(cur, cur.pit() + 1, 0, true, false);
702 bool Text::cursorUpParagraph(Cursor & cur)
704 bool updated = false;
706 updated = setCursor(cur, cur.pit(), 0);
707 else if (cur.pit() != 0)
708 updated = setCursor(cur, cur.pit() - 1, 0);
713 bool Text::cursorDownParagraph(Cursor & cur)
715 bool updated = false;
716 if (cur.pit() != cur.lastpit())
717 updated = setCursor(cur, cur.pit() + 1, 0);
719 updated = setCursor(cur, cur.pit(), cur.lastpos());
724 // fix the cursor `cur' after a characters has been deleted at `where'
725 // position. Called by deleteEmptyParagraphMechanism
726 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
728 // Do nothing if cursor is not in the paragraph where the
730 if (cur.pit() != where.pit())
733 // If cursor position is after the deletion place update it
734 if (cur.pos() > where.pos())
737 // Check also if we don't want to set the cursor on a spot behind the
738 // pagragraph because we erased the last character.
739 if (cur.pos() > cur.lastpos())
740 cur.pos() = cur.lastpos();
744 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
745 Cursor & old, bool & need_anchor_change)
747 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
749 Paragraph & oldpar = old.paragraph();
751 // We allow all kinds of "mumbo-jumbo" when freespacing.
752 if (oldpar.isFreeSpacing())
755 /* Ok I'll put some comments here about what is missing.
756 There are still some small problems that can lead to
757 double spaces stored in the document file or space at
758 the beginning of paragraphs(). This happens if you have
759 the cursor between to spaces and then save. Or if you
760 cut and paste and the selection have a space at the
761 beginning and then save right after the paste. (Lgb)
764 // If old.pos() == 0 and old.pos()(1) == LineSeparator
765 // delete the LineSeparator.
768 // If old.pos() == 1 and old.pos()(0) == LineSeparator
769 // delete the LineSeparator.
772 bool const same_inset = &old.inset() == &cur.inset();
773 bool const same_par = same_inset && old.pit() == cur.pit();
774 bool const same_par_pos = same_par && old.pos() == cur.pos();
776 // If the chars around the old cursor were spaces, delete one of them.
778 // Only if the cursor has really moved.
780 && old.pos() < oldpar.size()
781 && oldpar.isLineSeparator(old.pos())
782 && oldpar.isLineSeparator(old.pos() - 1)
783 && !oldpar.isDeleted(old.pos() - 1)
784 && !oldpar.isDeleted(old.pos())) {
785 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
786 // FIXME: This will not work anymore when we have multiple views of the same buffer
787 // In this case, we will have to correct also the cursors held by
788 // other bufferviews. It will probably be easier to do that in a more
789 // automated way in CursorSlice code. (JMarc 26/09/2001)
790 // correct all cursor parts
792 fixCursorAfterDelete(cur.top(), old.top());
793 need_anchor_change = true;
799 // only do our magic if we changed paragraph
803 // don't delete anything if this is the ONLY paragraph!
804 if (old.lastpit() == 0)
807 // Do not delete empty paragraphs with keepempty set.
808 if (oldpar.allowEmpty())
811 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
813 recordUndo(old, Undo::ATOMIC,
814 max(old.pit() - 1, pit_type(0)),
815 min(old.pit() + 1, old.lastpit()));
816 ParagraphList & plist = old.text()->paragraphs();
817 bool const soa = oldpar.params().startOfAppendix();
818 plist.erase(boost::next(plist.begin(), old.pit()));
819 // do not lose start of appendix marker (bug 4212)
821 plist[old.pit()].params().startOfAppendix(true);
823 // see #warning (FIXME?) above
824 if (cur.depth() >= old.depth()) {
825 CursorSlice & curslice = cur[old.depth() - 1];
826 if (&curslice.inset() == &old.inset()
827 && curslice.pit() > old.pit()) {
829 // since a paragraph has been deleted, all the
830 // insets after `old' have been copied and
831 // their address has changed. Therefore we
832 // need to `regenerate' cur. (JMarc)
833 cur.updateInsets(&(cur.bottom().inset()));
834 need_anchor_change = true;
840 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
841 need_anchor_change = true;
842 // We return true here because the Paragraph contents changed and
843 // we need a redraw before further action is processed.
851 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
853 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
855 for (pit_type pit = first; pit <= last; ++pit) {
856 Paragraph & par = pars_[pit];
858 // We allow all kinds of "mumbo-jumbo" when freespacing.
859 if (par.isFreeSpacing())
862 for (pos_type pos = 1; pos < par.size(); ++pos) {
863 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
864 && !par.isDeleted(pos - 1)) {
865 if (par.eraseChar(pos - 1, trackChanges)) {
871 // don't delete anything if this is the only remaining paragraph within the given range
872 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
876 // don't delete empty paragraphs with keepempty set
877 if (par.allowEmpty())
880 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
881 pars_.erase(boost::next(pars_.begin(), pit));
887 par.stripLeadingSpaces(trackChanges);
892 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
894 recordUndo(cur, Undo::ATOMIC, first, last);
898 void Text::recUndo(Cursor & cur, pit_type par) const
900 recordUndo(cur, Undo::ATOMIC, par, par);