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.
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #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 "insets/InsetCollapsable.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/lassert.h"
54 #include "support/debug.h"
55 #include "support/gettext.h"
56 #include "support/textutils.h"
58 #include <boost/next_prior.hpp>
67 : autoBreakRows_(false)
71 bool Text::isMainText(Buffer const & buffer) const
73 return &buffer.text() == this;
77 // Note that this is supposed to return a fully realized font.
78 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
80 Layout 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());
88 // It ought to be possible here just to use Inset::getLayout() and skip
89 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
90 // now, because Inset::getLayout() will return a default-constructed
91 // InsetLayout, and that e.g. sets the foreground color to red. So we
92 // need to do some work to make that possible.
93 InsetCollapsable const * icp = pars_[pit].inInset().asInsetCollapsable();
96 FontInfo icf = icp->getLayout().font();
101 FontInfo font = layout.font;
102 // Realize with the fonts of lesser depth.
103 //font.realize(outerFont(pit, paragraphs()));
104 font.realize(buffer.params().getFont().fontInfo());
110 // Note that this is supposed to return a fully realized font.
111 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
113 Layout const & layout = par.layout();
115 if (!par.getDepth()) {
116 FontInfo lf = layout.reslabelfont;
117 // In case the default family has been customized
118 if (layout.labelfont.family() == INHERIT_FAMILY)
119 lf.setFamily(buffer.params().getFont().fontInfo().family());
123 FontInfo font = layout.labelfont;
124 // Realize with the fonts of lesser depth.
125 font.realize(buffer.params().getFont().fontInfo());
131 void Text::setCharFont(Buffer const & buffer, pit_type pit,
132 pos_type pos, Font const & fnt, Font const & display_font)
135 Layout const & layout = pars_[pit].layout();
137 // Get concrete layout font to reduce against
140 if (pos < pars_[pit].beginOfBody())
141 layoutfont = layout.labelfont;
143 layoutfont = layout.font;
145 // Realize against environment font information
146 if (pars_[pit].getDepth()) {
148 while (!layoutfont.resolved() &&
149 tp != pit_type(paragraphs().size()) &&
150 pars_[tp].getDepth()) {
151 tp = outerHook(tp, paragraphs());
152 if (tp != pit_type(paragraphs().size()))
153 layoutfont.realize(pars_[tp].layout().font);
157 // Inside inset, apply the inset's font attributes if any
159 if (!isMainText(buffer))
160 layoutfont.realize(display_font.fontInfo());
162 layoutfont.realize(buffer.params().getFont().fontInfo());
164 // Now, reduce font against full layout font
165 font.fontInfo().reduce(layoutfont);
167 pars_[pit].setFont(pos, font);
171 void Text::setInsetFont(BufferView const & bv, pit_type pit,
172 pos_type pos, Font const & font, bool toggleall)
174 Inset * const inset = pars_[pit].getInset(pos);
175 LASSERT(inset && inset->noFontChange(), /**/);
177 CursorSlice::idx_type endidx = inset->nargs();
178 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
179 Text * text = cs.text();
181 // last position of the cell
182 CursorSlice cellend = cs;
183 cellend.pit() = cellend.lastpit();
184 cellend.pos() = cellend.lastpos();
185 text->setFont(bv, cs, cellend, font, toggleall);
191 // return past-the-last paragraph influenced by a layout change on pit
192 pit_type Text::undoSpan(pit_type pit)
194 pit_type const end = paragraphs().size();
195 pit_type nextpit = pit + 1;
198 //because of parindents
199 if (!pars_[pit].getDepth())
200 return boost::next(nextpit);
201 //because of depth constrains
202 for (; nextpit != end; ++pit, ++nextpit) {
203 if (!pars_[pit].getDepth())
210 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
211 docstring const & layout)
213 LASSERT(start != end, /**/);
215 BufferParams const & bp = buffer.params();
216 Layout const & lyxlayout = bp.documentClass()[layout];
218 for (pit_type pit = start; pit != end; ++pit) {
219 Paragraph & par = pars_[pit];
220 par.applyLayout(lyxlayout);
221 if (lyxlayout.margintype == MARGIN_MANUAL)
222 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
227 // set layout over selection and make a total rebreak of those paragraphs
228 void Text::setLayout(Cursor & cur, docstring const & layout)
230 LASSERT(this == cur.text(), /**/);
232 pit_type start = cur.selBegin().pit();
233 pit_type end = cur.selEnd().pit() + 1;
234 pit_type undopit = undoSpan(end - 1);
235 recUndo(cur, start, undopit - 1);
236 setLayout(*cur.buffer(), start, end, layout);
237 cur.buffer()->updateLabels();
241 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
242 Paragraph const & par, int max_depth)
244 if (par.layout().labeltype == LABEL_BIBLIO)
246 int const depth = par.params().depth();
247 if (type == Text::INC_DEPTH && depth < max_depth)
249 if (type == Text::DEC_DEPTH && depth > 0)
255 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
257 LASSERT(this == cur.text(), /**/);
258 // this happens when selecting several cells in tabular (bug 2630)
259 if (cur.selBegin().idx() != cur.selEnd().idx())
262 pit_type const beg = cur.selBegin().pit();
263 pit_type const end = cur.selEnd().pit() + 1;
264 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
266 for (pit_type pit = beg; pit != end; ++pit) {
267 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
269 max_depth = pars_[pit].getMaxDepthAfter();
275 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
277 LASSERT(this == cur.text(), /**/);
278 pit_type const beg = cur.selBegin().pit();
279 pit_type const end = cur.selEnd().pit() + 1;
280 cur.recordUndoSelection();
281 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
283 for (pit_type pit = beg; pit != end; ++pit) {
284 Paragraph & par = pars_[pit];
285 if (lyx::changeDepthAllowed(type, par, max_depth)) {
286 int const depth = par.params().depth();
287 if (type == INC_DEPTH)
288 par.params().depth(depth + 1);
290 par.params().depth(depth - 1);
292 max_depth = par.getMaxDepthAfter();
294 // this handles the counter labels, and also fixes up
295 // depth values for follow-on (child) paragraphs
296 cur.buffer()->updateLabels();
300 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
302 LASSERT(this == cur.text(), /**/);
303 // Set the current_font
304 // Determine basis font
306 pit_type pit = cur.pit();
307 if (cur.pos() < pars_[pit].beginOfBody())
308 layoutfont = labelFont(*cur.buffer(), pars_[pit]);
310 layoutfont = layoutFont(*cur.buffer(), pit);
312 // Update current font
313 cur.real_current_font.update(font,
314 cur.buffer()->params().language,
317 // Reduce to implicit settings
318 cur.current_font = cur.real_current_font;
319 cur.current_font.fontInfo().reduce(layoutfont);
320 // And resolve it completely
321 cur.real_current_font.fontInfo().realize(layoutfont);
323 // if there is no selection that's all we need to do
324 if (!cur.selection())
327 // Ok, we have a selection.
328 cur.recordUndoSelection();
330 setFont(cur.bv(), cur.selectionBegin().top(),
331 cur.selectionEnd().top(), font, toggleall);
335 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
336 CursorSlice const & end, Font const & font,
339 Buffer const & buffer = bv.buffer();
341 // Don't use forwardChar here as ditend might have
342 // pos() == lastpos() and forwardChar would miss it.
343 // Can't use forwardPos either as this descends into
345 Language const * language = buffer.params().language;
346 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
347 if (dit.pos() == dit.lastpos())
349 pit_type const pit = dit.pit();
350 pos_type const pos = dit.pos();
351 Inset * inset = pars_[pit].getInset(pos);
352 if (inset && inset->noFontChange()) {
353 // We need to propagate the font change to all
354 // text cells of the inset (bug 1973).
355 // FIXME: This should change, see documentation
356 // of noFontChange in Inset.h
357 setInsetFont(bv, pit, pos, font, toggleall);
359 TextMetrics const & tm = bv.textMetrics(this);
360 Font f = tm.displayFont(pit, pos);
361 f.update(font, language, toggleall);
362 setCharFont(buffer, pit, pos, f, tm.font_);
367 bool Text::cursorTop(Cursor & cur)
369 LASSERT(this == cur.text(), /**/);
370 return setCursor(cur, 0, 0);
374 bool Text::cursorBottom(Cursor & cur)
376 LASSERT(this == cur.text(), /**/);
377 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
381 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
383 LASSERT(this == cur.text(), /**/);
384 // If the mask is completely neutral, tell user
385 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
386 // Could only happen with user style
387 cur.message(_("No font change defined."));
391 // Try implicit word selection
392 // If there is a change in the language the implicit word selection
394 CursorSlice const resetCursor = cur.top();
395 bool const implicitSelection =
396 font.language() == ignore_language
397 && font.fontInfo().number() == FONT_IGNORE
398 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
401 setFont(cur, font, toggleall);
403 // Implicit selections are cleared afterwards
404 // and cursor is set to the original position.
405 if (implicitSelection) {
406 cur.clearSelection();
407 cur.top() = resetCursor;
413 docstring Text::getStringToIndex(Cursor const & cur)
415 LASSERT(this == cur.text(), /**/);
418 return cur.selectionAsString(false);
420 // Try implicit word selection. If there is a change
421 // in the language the implicit word selection is
424 selectWord(tmpcur, PREVIOUS_WORD);
426 if (!tmpcur.selection())
427 cur.message(_("Nothing to index!"));
428 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
429 cur.message(_("Cannot index more than one paragraph!"));
431 return tmpcur.selectionAsString(false);
437 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
439 LASSERT(cur.text(), /**/);
440 // make sure that the depth behind the selection are restored, too
441 pit_type undopit = undoSpan(cur.selEnd().pit());
442 recUndo(cur, cur.selBegin().pit(), undopit - 1);
445 string const argument = to_utf8(arg);
446 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
448 Paragraph & par = pars_[pit];
449 ParagraphParameters params = par.params();
450 params.read(argument, merge);
451 // changes to label width string apply to all
452 // paragraph with same layout in a sequence
453 setLabelWidthStringToSequence(pit, pars_,
454 params.labelWidthString());
455 par.params().apply(params, par.layout());
460 //FIXME This is a little redundant now, but it's probably worth keeping,
461 //especially if we're going to go away from using serialization internally
463 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
465 LASSERT(cur.text(), /**/);
466 // make sure that the depth behind the selection are restored, too
467 pit_type undopit = undoSpan(cur.selEnd().pit());
468 recUndo(cur, cur.selBegin().pit(), undopit - 1);
470 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
472 Paragraph & par = pars_[pit];
473 // changes to label width string apply to all
474 // paragraph with same layout in a sequence
475 setLabelWidthStringToSequence(pit, pars_,
476 par.params().labelWidthString());
477 par.params().apply(p, par.layout());
482 // this really should just insert the inset and not move the cursor.
483 void Text::insertInset(Cursor & cur, Inset * inset)
485 LASSERT(this == cur.text(), /**/);
486 LASSERT(inset, /**/);
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)
531 TextMetrics const & tm = cur.bv().textMetrics(this);
532 bool const update_needed = !tm.contains(par);
534 setCursorIntern(cur, par, pos, setfont, boundary);
535 return cur.bv().checkDepm(cur, old) || update_needed;
539 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
541 LASSERT(par != int(paragraphs().size()), /**/);
545 // now some strict checking
546 Paragraph & para = getPar(par);
548 // None of these should happen, but we're scaredy-cats
550 lyxerr << "dont like -1" << endl;
551 LASSERT(false, /**/);
554 if (pos > para.size()) {
555 lyxerr << "dont like 1, pos: " << pos
556 << " size: " << para.size()
557 << " par: " << par << endl;
558 LASSERT(false, /**/);
563 void Text::setCursorIntern(Cursor & cur,
564 pit_type par, pos_type pos, bool setfont, bool boundary)
566 LASSERT(this == cur.text(), /**/);
567 cur.boundary(boundary);
568 setCursor(cur.top(), par, pos);
570 cur.setCurrentFont();
574 bool Text::checkAndActivateInset(Cursor & cur, bool front)
578 if (front && cur.pos() == cur.lastpos())
580 if (!front && cur.pos() == 0)
582 Inset * inset = front ? cur.nextInset() : cur.prevInset();
583 if (!inset || !inset->editable())
586 * Apparently, when entering an inset we are expected to be positioned
587 * *before* it in the containing paragraph, regardless of the direction
588 * from which we are entering. Otherwise, cursor placement goes awry,
589 * and when we exit from the beginning, we'll be placed *after* the
594 inset->edit(cur, front);
599 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
605 if (cur.pos() == cur.lastpos())
607 Paragraph & par = cur.paragraph();
608 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
609 if (!inset || !inset->editable())
611 inset->edit(cur, movingForward,
612 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
617 bool Text::cursorBackward(Cursor & cur)
619 // Tell BufferView to test for FitCursor in any case!
620 cur.updateFlags(Update::FitCursor);
622 // not at paragraph start?
624 // if on right side of boundary (i.e. not at paragraph end, but line end)
625 // -> skip it, i.e. set boundary to true, i.e. go only logically left
626 // there are some exceptions to ignore this: lineseps, newlines, spaces
628 // some effectless debug code to see the values in the debugger
629 bool bound = cur.boundary();
630 int rowpos = cur.textRow().pos();
632 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
633 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
634 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
636 if (!cur.boundary() &&
637 cur.textRow().pos() == cur.pos() &&
638 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
639 !cur.paragraph().isNewline(cur.pos() - 1) &&
640 !cur.paragraph().isSeparator(cur.pos() - 1)) {
641 return setCursor(cur, cur.pit(), cur.pos(), true, true);
644 // go left and try to enter inset
645 if (checkAndActivateInset(cur, false))
648 // normal character left
649 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
652 // move to the previous paragraph or do nothing
654 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
659 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
661 Cursor temp_cur = cur;
662 temp_cur.posVisLeft(skip_inset);
663 if (temp_cur.depth() > cur.depth()) {
667 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
668 true, temp_cur.boundary());
672 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
674 Cursor temp_cur = cur;
675 temp_cur.posVisRight(skip_inset);
676 if (temp_cur.depth() > cur.depth()) {
680 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
681 true, temp_cur.boundary());
685 bool Text::cursorForward(Cursor & cur)
687 // Tell BufferView to test for FitCursor in any case!
688 cur.updateFlags(Update::FitCursor);
690 // not at paragraph end?
691 if (cur.pos() != cur.lastpos()) {
692 // in front of editable inset, i.e. jump into it?
693 if (checkAndActivateInset(cur, true))
696 TextMetrics const & tm = cur.bv().textMetrics(this);
697 // if left of boundary -> just jump to right side
698 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
699 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
700 return setCursor(cur, cur.pit(), cur.pos(), true, false);
702 // next position is left of boundary,
703 // but go to next line for special cases like space, newline, linesep
705 // some effectless debug code to see the values in the debugger
706 int endpos = cur.textRow().endpos();
707 int lastpos = cur.lastpos();
709 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
710 bool newline = cur.paragraph().isNewline(cur.pos());
711 bool sep = cur.paragraph().isSeparator(cur.pos());
712 if (cur.pos() != cur.lastpos()) {
713 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
714 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
715 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
718 if (cur.textRow().endpos() == cur.pos() + 1 &&
719 cur.textRow().endpos() != cur.lastpos() &&
720 !cur.paragraph().isNewline(cur.pos()) &&
721 !cur.paragraph().isLineSeparator(cur.pos()) &&
722 !cur.paragraph().isSeparator(cur.pos())) {
723 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
726 // in front of RTL boundary? Stay on this side of the boundary because:
727 // ab|cDDEEFFghi -> abc|DDEEFFghi
728 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
729 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
732 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
735 // move to next paragraph
736 if (cur.pit() != cur.lastpit())
737 return setCursor(cur, cur.pit() + 1, 0, true, false);
742 bool Text::cursorUpParagraph(Cursor & cur)
744 bool updated = false;
746 updated = setCursor(cur, cur.pit(), 0);
747 else if (cur.pit() != 0)
748 updated = setCursor(cur, cur.pit() - 1, 0);
753 bool Text::cursorDownParagraph(Cursor & cur)
755 bool updated = false;
756 if (cur.pit() != cur.lastpit())
757 updated = setCursor(cur, cur.pit() + 1, 0);
759 updated = setCursor(cur, cur.pit(), cur.lastpos());
764 // fix the cursor `cur' after a characters has been deleted at `where'
765 // position. Called by deleteEmptyParagraphMechanism
766 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
768 // Do nothing if cursor is not in the paragraph where the
770 if (cur.pit() != where.pit())
773 // If cursor position is after the deletion place update it
774 if (cur.pos() > where.pos())
777 // Check also if we don't want to set the cursor on a spot behind the
778 // pagragraph because we erased the last character.
779 if (cur.pos() > cur.lastpos())
780 cur.pos() = cur.lastpos();
784 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
785 Cursor & old, bool & need_anchor_change)
787 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
789 Paragraph & oldpar = old.paragraph();
791 // We allow all kinds of "mumbo-jumbo" when freespacing.
792 if (oldpar.isFreeSpacing())
795 /* Ok I'll put some comments here about what is missing.
796 There are still some small problems that can lead to
797 double spaces stored in the document file or space at
798 the beginning of paragraphs(). This happens if you have
799 the cursor between to spaces and then save. Or if you
800 cut and paste and the selection have a space at the
801 beginning and then save right after the paste. (Lgb)
804 // If old.pos() == 0 and old.pos()(1) == LineSeparator
805 // delete the LineSeparator.
808 // If old.pos() == 1 and old.pos()(0) == LineSeparator
809 // delete the LineSeparator.
812 // Find a common inset and the corresponding depth.
814 for (; depth < cur.depth(); ++depth)
815 if (&old.inset() == &cur[depth].inset())
818 // Whether a common inset is found and whether the cursor is still in
819 // the same paragraph (possibly nested).
820 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
821 bool const same_par_pos = depth == cur.depth() - 1 && same_par
822 && old.pos() == cur[depth].pos();
824 // If the chars around the old cursor were spaces, delete one of them.
826 // Only if the cursor has really moved.
828 && old.pos() < oldpar.size()
829 && oldpar.isLineSeparator(old.pos())
830 && oldpar.isLineSeparator(old.pos() - 1)
831 && !oldpar.isDeleted(old.pos() - 1)
832 && !oldpar.isDeleted(old.pos())) {
833 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
834 // FIXME: This will not work anymore when we have multiple views of the same buffer
835 // In this case, we will have to correct also the cursors held by
836 // other bufferviews. It will probably be easier to do that in a more
837 // automated way in CursorSlice code. (JMarc 26/09/2001)
838 // correct all cursor parts
840 fixCursorAfterDelete(cur[depth], old.top());
841 need_anchor_change = true;
847 // only do our magic if we changed paragraph
851 // don't delete anything if this is the ONLY paragraph!
852 if (old.lastpit() == 0)
855 // Do not delete empty paragraphs with keepempty set.
856 if (oldpar.allowEmpty())
859 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
861 old.recordUndo(ATOMIC_UNDO,
862 max(old.pit() - 1, pit_type(0)),
863 min(old.pit() + 1, old.lastpit()));
864 ParagraphList & plist = old.text()->paragraphs();
865 bool const soa = oldpar.params().startOfAppendix();
866 plist.erase(boost::next(plist.begin(), old.pit()));
867 // do not lose start of appendix marker (bug 4212)
868 if (soa && old.pit() < pit_type(plist.size()))
869 plist[old.pit()].params().startOfAppendix(true);
871 // see #warning (FIXME?) above
872 if (cur.depth() >= old.depth()) {
873 CursorSlice & curslice = cur[old.depth() - 1];
874 if (&curslice.inset() == &old.inset()
875 && curslice.pit() > old.pit()) {
877 // since a paragraph has been deleted, all the
878 // insets after `old' have been copied and
879 // their address has changed. Therefore we
880 // need to `regenerate' cur. (JMarc)
881 cur.updateInsets(&(cur.bottom().inset()));
882 need_anchor_change = true;
888 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
889 need_anchor_change = true;
890 // We return true here because the Paragraph contents changed and
891 // we need a redraw before further action is processed.
899 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
901 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
903 for (pit_type pit = first; pit <= last; ++pit) {
904 Paragraph & par = pars_[pit];
906 // We allow all kinds of "mumbo-jumbo" when freespacing.
907 if (par.isFreeSpacing())
910 for (pos_type pos = 1; pos < par.size(); ++pos) {
911 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
912 && !par.isDeleted(pos - 1)) {
913 if (par.eraseChar(pos - 1, trackChanges)) {
919 // don't delete anything if this is the only remaining paragraph within the given range
920 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
924 // don't delete empty paragraphs with keepempty set
925 if (par.allowEmpty())
928 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
929 pars_.erase(boost::next(pars_.begin(), pit));
935 par.stripLeadingSpaces(trackChanges);
940 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
942 cur.recordUndo(ATOMIC_UNDO, first, last);
946 void Text::recUndo(Cursor & cur, pit_type par) const
948 cur.recordUndo(ATOMIC_UNDO, par, par);