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"
43 #include "Paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "ParIterator.h"
48 #include "ServerSocket.h"
52 #include "frontends/FontMetrics.h"
54 #include "insets/InsetEnvironment.h"
56 #include "mathed/InsetMathHull.h"
58 #include "support/textutils.h"
60 #include <boost/current_function.hpp>
65 using std::ostringstream;
69 using std::istringstream;
74 : autoBreakRows_(false)
78 bool Text::isMainText(Buffer const & buffer) const
80 return &buffer.text() == this;
84 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
86 LayoutPtr const & layout = pars_[pit].layout();
88 if (!pars_[pit].getDepth()) {
89 Font lf = layout->resfont;
90 // In case the default family has been customized
91 if (layout->font.family() == Font::INHERIT_FAMILY)
92 lf.setFamily(buffer.params().getFont().family());
96 Font font = layout->font;
97 // Realize with the fonts of lesser depth.
98 //font.realize(outerFont(pit, paragraphs()));
99 font.realize(buffer.params().getFont());
105 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
107 LayoutPtr const & layout = par.layout();
109 if (!par.getDepth()) {
110 Font lf = layout->reslabelfont;
111 // In case the default family has been customized
112 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
113 lf.setFamily(buffer.params().getFont().family());
117 Font font = layout->labelfont;
118 // Realize with the fonts of lesser depth.
119 font.realize(buffer.params().getFont());
125 void Text::setCharFont(Buffer const & buffer, pit_type pit,
126 pos_type pos, Font const & fnt, Font const & display_font)
129 LayoutPtr const & layout = pars_[pit].layout();
131 // Get concrete layout font to reduce against
134 if (pos < pars_[pit].beginOfBody())
135 layoutfont = layout->labelfont;
137 layoutfont = layout->font;
139 // Realize against environment font information
140 if (pars_[pit].getDepth()) {
142 while (!layoutfont.resolved() &&
143 tp != pit_type(paragraphs().size()) &&
144 pars_[tp].getDepth()) {
145 tp = outerHook(tp, paragraphs());
146 if (tp != pit_type(paragraphs().size()))
147 layoutfont.realize(pars_[tp].layout()->font);
151 // Inside inset, apply the inset's font attributes if any
153 if (!isMainText(buffer))
154 layoutfont.realize(display_font);
156 layoutfont.realize(buffer.params().getFont());
158 // Now, reduce font against full layout font
159 font.reduce(layoutfont);
161 pars_[pit].setFont(pos, font);
165 void Text::setInsetFont(BufferView const & bv, pit_type pit,
166 pos_type pos, Font const & font, bool toggleall)
168 BOOST_ASSERT(pars_[pit].isInset(pos) &&
169 pars_[pit].getInset(pos)->noFontChange());
171 Inset * const inset = pars_[pit].getInset(pos);
172 CursorSlice::idx_type endidx = inset->nargs();
173 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
174 Text * text = cs.text();
176 // last position of the cell
177 CursorSlice cellend = cs;
178 cellend.pit() = cellend.lastpit();
179 cellend.pos() = cellend.lastpos();
180 text->setFont(bv, cs, cellend, font, toggleall);
186 // return past-the-last paragraph influenced by a layout change on pit
187 pit_type Text::undoSpan(pit_type pit)
189 pit_type end = paragraphs().size();
190 pit_type nextpit = pit + 1;
193 //because of parindents
194 if (!pars_[pit].getDepth())
195 return boost::next(nextpit);
196 //because of depth constrains
197 for (; nextpit != end; ++pit, ++nextpit) {
198 if (!pars_[pit].getDepth())
205 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
206 docstring const & layout)
208 BOOST_ASSERT(start != end);
210 BufferParams const & bufparams = buffer.params();
211 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
213 for (pit_type pit = start; pit != end; ++pit) {
214 Paragraph & par = pars_[pit];
215 par.applyLayout(lyxlayout);
216 if (lyxlayout->margintype == MARGIN_MANUAL)
217 par.setLabelWidthString(par.translateIfPossible(
218 lyxlayout->labelstring(), buffer.params()));
223 // set layout over selection and make a total rebreak of those paragraphs
224 void Text::setLayout(Cursor & cur, docstring const & layout)
226 BOOST_ASSERT(this == cur.text());
227 // special handling of new environment insets
228 BufferView & bv = cur.bv();
229 BufferParams const & params = bv.buffer().params();
230 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
231 if (lyxlayout->is_environment) {
232 // move everything in a new environment inset
233 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
234 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
235 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
236 lyx::dispatch(FuncRequest(LFUN_CUT));
237 Inset * inset = new InsetEnvironment(params, layout);
238 insertInset(cur, inset);
239 //inset->edit(cur, true);
240 //lyx::dispatch(FuncRequest(LFUN_PASTE));
244 pit_type start = cur.selBegin().pit();
245 pit_type end = cur.selEnd().pit() + 1;
246 pit_type undopit = undoSpan(end - 1);
247 recUndo(cur, start, undopit - 1);
248 setLayout(cur.buffer(), start, end, layout);
249 updateLabels(cur.buffer());
253 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
254 Paragraph const & par, int max_depth)
256 if (par.layout()->labeltype == LABEL_BIBLIO)
258 int const depth = par.params().depth();
259 if (type == Text::INC_DEPTH && depth < max_depth)
261 if (type == Text::DEC_DEPTH && depth > 0)
267 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
269 BOOST_ASSERT(this == cur.text());
270 // this happens when selecting several cells in tabular (bug 2630)
271 if (cur.selBegin().idx() != cur.selEnd().idx())
274 pit_type const beg = cur.selBegin().pit();
275 pit_type const end = cur.selEnd().pit() + 1;
276 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
278 for (pit_type pit = beg; pit != end; ++pit) {
279 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
281 max_depth = pars_[pit].getMaxDepthAfter();
287 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
289 BOOST_ASSERT(this == cur.text());
290 pit_type const beg = cur.selBegin().pit();
291 pit_type const end = cur.selEnd().pit() + 1;
292 recordUndoSelection(cur);
293 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
295 for (pit_type pit = beg; pit != end; ++pit) {
296 Paragraph & par = pars_[pit];
297 if (lyx::changeDepthAllowed(type, par, max_depth)) {
298 int const depth = par.params().depth();
299 if (type == INC_DEPTH)
300 par.params().depth(depth + 1);
302 par.params().depth(depth - 1);
304 max_depth = par.getMaxDepthAfter();
306 // this handles the counter labels, and also fixes up
307 // depth values for follow-on (child) paragraphs
308 updateLabels(cur.buffer());
312 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
314 BOOST_ASSERT(this == cur.text());
315 // Set the current_font
316 // Determine basis font
318 pit_type pit = cur.pit();
319 if (cur.pos() < pars_[pit].beginOfBody())
320 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
322 layoutfont = getLayoutFont(cur.buffer(), pit);
324 // Update current font
325 cur.real_current_font.update(font,
326 cur.buffer().params().language,
329 // Reduce to implicit settings
330 cur.current_font = cur.real_current_font;
331 cur.current_font.reduce(layoutfont);
332 // And resolve it completely
333 cur.real_current_font.realize(layoutfont);
335 // if there is no selection that's all we need to do
336 if (!cur.selection())
339 // Ok, we have a selection.
340 recordUndoSelection(cur);
342 setFont(cur.bv(), cur.selectionBegin().top(),
343 cur.selectionEnd().top(), font, toggleall);
347 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
348 CursorSlice const & end, Font const & font,
351 Buffer const & buffer = bv.buffer();
353 // Don't use forwardChar here as ditend might have
354 // pos() == lastpos() and forwardChar would miss it.
355 // Can't use forwardPos either as this descends into
357 Language const * language = buffer.params().language;
358 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
359 if (dit.pos() != dit.lastpos()) {
360 pit_type const pit = dit.pit();
361 pos_type const pos = dit.pos();
362 if (pars_[pit].isInset(pos) &&
363 pars_[pit].getInset(pos)->noFontChange())
364 // We need to propagate the font change to all
365 // text cells of the inset (bug 1973).
366 // FIXME: This should change, see documentation
367 // of noFontChange in Inset.h
368 setInsetFont(bv, pit, pos, font, toggleall);
369 TextMetrics const & tm = bv.textMetrics(this);
370 Font f = tm.getDisplayFont(pit, pos);
371 f.update(font, language, toggleall);
372 setCharFont(buffer, pit, pos, f, tm.font_);
378 bool Text::cursorTop(Cursor & cur)
380 BOOST_ASSERT(this == cur.text());
381 return setCursor(cur, 0, 0);
385 bool Text::cursorBottom(Cursor & cur)
387 BOOST_ASSERT(this == cur.text());
388 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
392 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
394 BOOST_ASSERT(this == cur.text());
395 // If the mask is completely neutral, tell user
396 if (font == Font(Font::ALL_IGNORE)) {
397 // Could only happen with user style
398 cur.message(_("No font change defined."));
402 // Try implicit word selection
403 // If there is a change in the language the implicit word selection
405 CursorSlice resetCursor = cur.top();
406 bool implicitSelection =
407 font.language() == ignore_language
408 && font.number() == Font::IGNORE
409 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
412 setFont(cur, font, toggleall);
414 // Implicit selections are cleared afterwards
415 // and cursor is set to the original position.
416 if (implicitSelection) {
417 cur.clearSelection();
418 cur.top() = resetCursor;
424 docstring Text::getStringToIndex(Cursor const & cur)
426 BOOST_ASSERT(this == cur.text());
430 idxstring = cur.selectionAsString(false);
432 // Try implicit word selection. If there is a change
433 // in the language the implicit word selection is
436 selectWord(tmpcur, PREVIOUS_WORD);
438 if (!tmpcur.selection())
439 cur.message(_("Nothing to index!"));
440 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
441 cur.message(_("Cannot index more than one paragraph!"));
443 idxstring = tmpcur.selectionAsString(false);
450 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
452 BOOST_ASSERT(cur.text());
453 // make sure that the depth behind the selection are restored, too
454 pit_type undopit = undoSpan(cur.selEnd().pit());
455 recUndo(cur, cur.selBegin().pit(), undopit - 1);
457 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
459 Paragraph & par = pars_[pit];
460 ParagraphParameters params = par.params();
461 params.read(to_utf8(arg), merge);
462 Layout const & layout = *(par.layout());
463 par.params().apply(params, layout);
468 //FIXME This is a little redundant now, but it's probably worth keeping,
469 //especially if we're going to go away from using serialization internally
471 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
473 BOOST_ASSERT(cur.text());
474 // make sure that the depth behind the selection are restored, too
475 pit_type undopit = undoSpan(cur.selEnd().pit());
476 recUndo(cur, cur.selBegin().pit(), undopit - 1);
478 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
480 Paragraph & par = pars_[pit];
481 Layout const & layout = *(par.layout());
482 par.params().apply(p, layout);
487 // this really should just insert the inset and not move the cursor.
488 void Text::insertInset(Cursor & cur, Inset * inset)
490 BOOST_ASSERT(this == cur.text());
492 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
493 Change(cur.buffer().params().trackChanges ?
494 Change::INSERTED : Change::UNCHANGED));
498 // needed to insert the selection
499 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
501 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
502 cur.current_font, str, autoBreakRows_);
506 // turn double CR to single CR, others are converted into one
507 // blank. Then insertStringAsLines is called
508 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
510 docstring linestr = str;
511 bool newline_inserted = false;
513 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
514 if (linestr[i] == '\n') {
515 if (newline_inserted) {
516 // we know that \r will be ignored by
517 // insertStringAsLines. Of course, it is a dirty
518 // trick, but it works...
519 linestr[i - 1] = '\r';
523 newline_inserted = true;
525 } else if (isPrintable(linestr[i])) {
526 newline_inserted = false;
529 insertStringAsLines(cur, linestr);
533 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
534 bool setfont, bool boundary)
537 setCursorIntern(cur, par, pos, setfont, boundary);
538 return cur.bv().checkDepm(cur, old);
542 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
544 BOOST_ASSERT(par != int(paragraphs().size()));
548 // now some strict checking
549 Paragraph & para = getPar(par);
551 // None of these should happen, but we're scaredy-cats
553 lyxerr << "dont like -1" << endl;
557 if (pos > para.size()) {
558 lyxerr << "dont like 1, pos: " << pos
559 << " size: " << para.size()
560 << " par: " << par << endl;
566 void Text::setCursorIntern(Cursor & cur,
567 pit_type par, pos_type pos, bool setfont, bool boundary)
569 BOOST_ASSERT(this == cur.text());
570 cur.boundary(boundary);
571 setCursor(cur.top(), par, pos);
573 cur.setCurrentFont();
577 bool Text::checkAndActivateInset(Cursor & cur, bool front)
581 if (front && cur.pos() == cur.lastpos())
583 if (!front && cur.pos() == 0)
585 Inset * inset = front ? cur.nextInset() : cur.prevInset();
586 if (!isHighlyEditableInset(inset))
589 * Apparently, when entering an inset we are expected to be positioned
590 * *before* it in the containing paragraph, regardless of the direction
591 * from which we are entering. Otherwise, cursor placement goes awry,
592 * and when we exit from the beginning, we'll be placed *after* the
597 inset->edit(cur, front);
602 bool Text::cursorLeft(Cursor & cur)
604 // Tell BufferView to test for FitCursor in any case!
605 cur.updateFlags(Update::FitCursor);
607 // not at paragraph start?
609 // if on right side of boundary (i.e. not at paragraph end, but line end)
610 // -> skip it, i.e. set boundary to true, i.e. go only logically left
611 // there are some exceptions to ignore this: lineseps, newlines, spaces
613 // some effectless debug code to see the values in the debugger
614 bool bound = cur.boundary();
615 int rowpos = cur.textRow().pos();
617 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
618 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
619 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
621 if (!cur.boundary() &&
622 cur.textRow().pos() == cur.pos() &&
623 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
624 !cur.paragraph().isNewline(cur.pos() - 1) &&
625 !cur.paragraph().isSeparator(cur.pos() - 1)) {
626 return setCursor(cur, cur.pit(), cur.pos(), true, true);
629 // go left and try to enter inset
630 if (checkAndActivateInset(cur, false))
633 // normal character left
634 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
637 // move to the previous paragraph or do nothing
639 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
644 bool Text::cursorRight(Cursor & cur)
646 // Tell BufferView to test for FitCursor in any case!
647 cur.updateFlags(Update::FitCursor);
649 // not at paragraph end?
650 if (cur.pos() != cur.lastpos()) {
651 // in front of editable inset, i.e. jump into it?
652 if (checkAndActivateInset(cur, true))
655 TextMetrics const & tm = cur.bv().textMetrics(this);
656 // if left of boundary -> just jump to right side
657 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
658 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
659 return setCursor(cur, cur.pit(), cur.pos(), true, false);
661 // next position is left of boundary,
662 // but go to next line for special cases like space, newline, linesep
664 // some effectless debug code to see the values in the debugger
665 int endpos = cur.textRow().endpos();
666 int lastpos = cur.lastpos();
668 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
669 bool newline = cur.paragraph().isNewline(cur.pos());
670 bool sep = cur.paragraph().isSeparator(cur.pos());
671 if (cur.pos() != cur.lastpos()) {
672 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
673 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
674 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
677 if (cur.textRow().endpos() == cur.pos() + 1 &&
678 cur.textRow().endpos() != cur.lastpos() &&
679 !cur.paragraph().isNewline(cur.pos()) &&
680 !cur.paragraph().isLineSeparator(cur.pos()) &&
681 !cur.paragraph().isSeparator(cur.pos())) {
682 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
685 // in front of RTL boundary? Stay on this side of the boundary because:
686 // ab|cDDEEFFghi -> abc|DDEEFFghi
687 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
688 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
691 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
694 // move to next paragraph
695 if (cur.pit() != cur.lastpit())
696 return setCursor(cur, cur.pit() + 1, 0);
701 bool Text::cursorUpParagraph(Cursor & cur)
703 bool updated = false;
705 updated = setCursor(cur, cur.pit(), 0);
706 else if (cur.pit() != 0)
707 updated = setCursor(cur, cur.pit() - 1, 0);
712 bool Text::cursorDownParagraph(Cursor & cur)
714 bool updated = false;
715 if (cur.pit() != cur.lastpit())
716 updated = setCursor(cur, cur.pit() + 1, 0);
718 updated = setCursor(cur, cur.pit(), cur.lastpos());
723 // fix the cursor `cur' after a characters has been deleted at `where'
724 // position. Called by deleteEmptyParagraphMechanism
725 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
727 // Do nothing if cursor is not in the paragraph where the
729 if (cur.pit() != where.pit())
732 // If cursor position is after the deletion place update it
733 if (cur.pos() > where.pos())
736 // Check also if we don't want to set the cursor on a spot behind the
737 // pagragraph because we erased the last character.
738 if (cur.pos() > cur.lastpos())
739 cur.pos() = cur.lastpos();
743 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
744 Cursor & old, bool & need_anchor_change)
746 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
748 Paragraph & oldpar = old.paragraph();
750 // We allow all kinds of "mumbo-jumbo" when freespacing.
751 if (oldpar.isFreeSpacing())
754 /* Ok I'll put some comments here about what is missing.
755 There are still some small problems that can lead to
756 double spaces stored in the document file or space at
757 the beginning of paragraphs(). This happens if you have
758 the cursor between to spaces and then save. Or if you
759 cut and paste and the selection have a space at the
760 beginning and then save right after the paste. (Lgb)
763 // If old.pos() == 0 and old.pos()(1) == LineSeparator
764 // delete the LineSeparator.
767 // If old.pos() == 1 and old.pos()(0) == LineSeparator
768 // delete the LineSeparator.
771 bool const same_inset = &old.inset() == &cur.inset();
772 bool const same_par = same_inset && old.pit() == cur.pit();
773 bool const same_par_pos = same_par && old.pos() == cur.pos();
775 // If the chars around the old cursor were spaces, delete one of them.
777 // Only if the cursor has really moved.
779 && old.pos() < oldpar.size()
780 && oldpar.isLineSeparator(old.pos())
781 && oldpar.isLineSeparator(old.pos() - 1)
782 && !oldpar.isDeleted(old.pos() - 1)
783 && !oldpar.isDeleted(old.pos())) {
784 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
785 // FIXME: This will not work anymore when we have multiple views of the same buffer
786 // In this case, we will have to correct also the cursors held by
787 // other bufferviews. It will probably be easier to do that in a more
788 // automated way in CursorSlice code. (JMarc 26/09/2001)
789 // correct all cursor parts
791 fixCursorAfterDelete(cur.top(), old.top());
792 need_anchor_change = true;
798 // only do our magic if we changed paragraph
802 // don't delete anything if this is the ONLY paragraph!
803 if (old.lastpit() == 0)
806 // Do not delete empty paragraphs with keepempty set.
807 if (oldpar.allowEmpty())
810 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
812 recordUndo(old, Undo::ATOMIC,
813 max(old.pit() - 1, pit_type(0)),
814 min(old.pit() + 1, old.lastpit()));
815 ParagraphList & plist = old.text()->paragraphs();
816 bool const soa = oldpar.params().startOfAppendix();
817 plist.erase(boost::next(plist.begin(), old.pit()));
818 // do not lose start of appendix marker (bug 4212)
820 plist[old.pit()].params().startOfAppendix(true);
822 // see #warning (FIXME?) above
823 if (cur.depth() >= old.depth()) {
824 CursorSlice & curslice = cur[old.depth() - 1];
825 if (&curslice.inset() == &old.inset()
826 && curslice.pit() > old.pit()) {
828 // since a paragraph has been deleted, all the
829 // insets after `old' have been copied and
830 // their address has changed. Therefore we
831 // need to `regenerate' cur. (JMarc)
832 cur.updateInsets(&(cur.bottom().inset()));
833 need_anchor_change = true;
839 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
840 need_anchor_change = true;
841 // We return true here because the Paragraph contents changed and
842 // we need a redraw before further action is processed.
850 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
852 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
854 for (pit_type pit = first; pit <= last; ++pit) {
855 Paragraph & par = pars_[pit];
857 // We allow all kinds of "mumbo-jumbo" when freespacing.
858 if (par.isFreeSpacing())
861 for (pos_type pos = 1; pos < par.size(); ++pos) {
862 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
863 && !par.isDeleted(pos - 1)) {
864 if (par.eraseChar(pos - 1, trackChanges)) {
870 // don't delete anything if this is the only remaining paragraph within the given range
871 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
875 // don't delete empty paragraphs with keepempty set
876 if (par.allowEmpty())
879 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
880 pars_.erase(boost::next(pars_.begin(), pit));
886 par.stripLeadingSpaces(trackChanges);
891 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
893 recordUndo(cur, Undo::ATOMIC, first, last);
897 void Text::recUndo(Cursor & cur, pit_type par) const
899 recordUndo(cur, Undo::ATOMIC, par, par);