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 & bufparams = buffer.params();
216 Layout const & lyxlayout = bufparams.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.translateIfPossible(
223 lyxlayout.labelstring(), buffer.params()));
228 // set layout over selection and make a total rebreak of those paragraphs
229 void Text::setLayout(Cursor & cur, docstring const & layout)
231 LASSERT(this == cur.text(), /**/);
233 pit_type start = cur.selBegin().pit();
234 pit_type end = cur.selEnd().pit() + 1;
235 pit_type undopit = undoSpan(end - 1);
236 recUndo(cur, start, undopit - 1);
237 setLayout(*cur.buffer(), start, end, layout);
238 cur.buffer()->updateLabels();
242 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
243 Paragraph const & par, int max_depth)
245 if (par.layout().labeltype == LABEL_BIBLIO)
247 int const depth = par.params().depth();
248 if (type == Text::INC_DEPTH && depth < max_depth)
250 if (type == Text::DEC_DEPTH && depth > 0)
256 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
258 LASSERT(this == cur.text(), /**/);
259 // this happens when selecting several cells in tabular (bug 2630)
260 if (cur.selBegin().idx() != cur.selEnd().idx())
263 pit_type const beg = cur.selBegin().pit();
264 pit_type const end = cur.selEnd().pit() + 1;
265 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
267 for (pit_type pit = beg; pit != end; ++pit) {
268 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
270 max_depth = pars_[pit].getMaxDepthAfter();
276 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
278 LASSERT(this == cur.text(), /**/);
279 pit_type const beg = cur.selBegin().pit();
280 pit_type const end = cur.selEnd().pit() + 1;
281 cur.recordUndoSelection();
282 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
284 for (pit_type pit = beg; pit != end; ++pit) {
285 Paragraph & par = pars_[pit];
286 if (lyx::changeDepthAllowed(type, par, max_depth)) {
287 int const depth = par.params().depth();
288 if (type == INC_DEPTH)
289 par.params().depth(depth + 1);
291 par.params().depth(depth - 1);
293 max_depth = par.getMaxDepthAfter();
295 // this handles the counter labels, and also fixes up
296 // depth values for follow-on (child) paragraphs
297 cur.buffer()->updateLabels();
301 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
303 LASSERT(this == cur.text(), /**/);
304 // Set the current_font
305 // Determine basis font
307 pit_type pit = cur.pit();
308 if (cur.pos() < pars_[pit].beginOfBody())
309 layoutfont = labelFont(*cur.buffer(), pars_[pit]);
311 layoutfont = layoutFont(*cur.buffer(), pit);
313 // Update current font
314 cur.real_current_font.update(font,
315 cur.buffer()->params().language,
318 // Reduce to implicit settings
319 cur.current_font = cur.real_current_font;
320 cur.current_font.fontInfo().reduce(layoutfont);
321 // And resolve it completely
322 cur.real_current_font.fontInfo().realize(layoutfont);
324 // if there is no selection that's all we need to do
325 if (!cur.selection())
328 // Ok, we have a selection.
329 cur.recordUndoSelection();
331 setFont(cur.bv(), cur.selectionBegin().top(),
332 cur.selectionEnd().top(), font, toggleall);
336 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
337 CursorSlice const & end, Font const & font,
340 Buffer const & buffer = bv.buffer();
342 // Don't use forwardChar here as ditend might have
343 // pos() == lastpos() and forwardChar would miss it.
344 // Can't use forwardPos either as this descends into
346 Language const * language = buffer.params().language;
347 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
348 if (dit.pos() == dit.lastpos())
350 pit_type const pit = dit.pit();
351 pos_type const pos = dit.pos();
352 Inset * inset = pars_[pit].getInset(pos);
353 if (inset && inset->noFontChange()) {
354 // We need to propagate the font change to all
355 // text cells of the inset (bug 1973).
356 // FIXME: This should change, see documentation
357 // of noFontChange in Inset.h
358 setInsetFont(bv, pit, pos, font, toggleall);
360 TextMetrics const & tm = bv.textMetrics(this);
361 Font f = tm.displayFont(pit, pos);
362 f.update(font, language, toggleall);
363 setCharFont(buffer, pit, pos, f, tm.font_);
368 bool Text::cursorTop(Cursor & cur)
370 LASSERT(this == cur.text(), /**/);
371 return setCursor(cur, 0, 0);
375 bool Text::cursorBottom(Cursor & cur)
377 LASSERT(this == cur.text(), /**/);
378 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
382 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
384 LASSERT(this == cur.text(), /**/);
385 // If the mask is completely neutral, tell user
386 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
387 // Could only happen with user style
388 cur.message(_("No font change defined."));
392 // Try implicit word selection
393 // If there is a change in the language the implicit word selection
395 CursorSlice resetCursor = cur.top();
396 bool implicitSelection =
397 font.language() == ignore_language
398 && font.fontInfo().number() == FONT_IGNORE
399 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
402 setFont(cur, font, toggleall);
404 // Implicit selections are cleared afterwards
405 // and cursor is set to the original position.
406 if (implicitSelection) {
407 cur.clearSelection();
408 cur.top() = resetCursor;
414 docstring Text::getStringToIndex(Cursor const & cur)
416 LASSERT(this == cur.text(), /**/);
419 return cur.selectionAsString(false);
421 // Try implicit word selection. If there is a change
422 // in the language the implicit word selection is
425 selectWord(tmpcur, PREVIOUS_WORD);
427 if (!tmpcur.selection())
428 cur.message(_("Nothing to index!"));
429 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
430 cur.message(_("Cannot index more than one paragraph!"));
432 return tmpcur.selectionAsString(false);
438 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
440 LASSERT(cur.text(), /**/);
441 // make sure that the depth behind the selection are restored, too
442 pit_type undopit = undoSpan(cur.selEnd().pit());
443 recUndo(cur, cur.selBegin().pit(), undopit - 1);
446 string const argument = to_utf8(arg);
447 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
449 Paragraph & par = pars_[pit];
450 ParagraphParameters params = par.params();
451 params.read(argument, merge);
452 // changes to label width string apply to all
453 // paragraph with same layout in a sequence
454 setLabelWidthStringToSequence(pit, pars_,
455 params.labelWidthString());
456 par.params().apply(params, par.layout());
461 //FIXME This is a little redundant now, but it's probably worth keeping,
462 //especially if we're going to go away from using serialization internally
464 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
466 LASSERT(cur.text(), /**/);
467 // make sure that the depth behind the selection are restored, too
468 pit_type undopit = undoSpan(cur.selEnd().pit());
469 recUndo(cur, cur.selBegin().pit(), undopit - 1);
471 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
473 Paragraph & par = pars_[pit];
474 // changes to label width string apply to all
475 // paragraph with same layout in a sequence
476 setLabelWidthStringToSequence(pit, pars_,
477 par.params().labelWidthString());
478 par.params().apply(p, par.layout());
483 // this really should just insert the inset and not move the cursor.
484 void Text::insertInset(Cursor & cur, Inset * inset)
486 LASSERT(this == cur.text(), /**/);
487 LASSERT(inset, /**/);
488 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
489 Change(cur.buffer()->params().trackChanges
490 ? Change::INSERTED : Change::UNCHANGED));
494 // needed to insert the selection
495 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
497 cur.buffer()->insertStringAsLines(pars_, cur.pit(), cur.pos(),
498 cur.current_font, str, autoBreakRows_);
502 // turn double CR to single CR, others are converted into one
503 // blank. Then insertStringAsLines is called
504 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
506 docstring linestr = str;
507 bool newline_inserted = false;
509 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
510 if (linestr[i] == '\n') {
511 if (newline_inserted) {
512 // we know that \r will be ignored by
513 // insertStringAsLines. Of course, it is a dirty
514 // trick, but it works...
515 linestr[i - 1] = '\r';
519 newline_inserted = true;
521 } else if (isPrintable(linestr[i])) {
522 newline_inserted = false;
525 insertStringAsLines(cur, linestr);
529 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
530 bool setfont, bool boundary)
532 TextMetrics const & tm = cur.bv().textMetrics(this);
533 bool const update_needed = !tm.contains(par);
535 setCursorIntern(cur, par, pos, setfont, boundary);
536 return cur.bv().checkDepm(cur, old) || update_needed;
540 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
542 LASSERT(par != int(paragraphs().size()), /**/);
546 // now some strict checking
547 Paragraph & para = getPar(par);
549 // None of these should happen, but we're scaredy-cats
551 lyxerr << "dont like -1" << endl;
552 LASSERT(false, /**/);
555 if (pos > para.size()) {
556 lyxerr << "dont like 1, pos: " << pos
557 << " size: " << para.size()
558 << " par: " << par << endl;
559 LASSERT(false, /**/);
564 void Text::setCursorIntern(Cursor & cur,
565 pit_type par, pos_type pos, bool setfont, bool boundary)
567 LASSERT(this == cur.text(), /**/);
568 cur.boundary(boundary);
569 setCursor(cur.top(), par, pos);
571 cur.setCurrentFont();
575 bool Text::checkAndActivateInset(Cursor & cur, bool front)
579 if (front && cur.pos() == cur.lastpos())
581 if (!front && cur.pos() == 0)
583 Inset * inset = front ? cur.nextInset() : cur.prevInset();
584 if (!inset || !inset->editable())
587 * Apparently, when entering an inset we are expected to be positioned
588 * *before* it in the containing paragraph, regardless of the direction
589 * from which we are entering. Otherwise, cursor placement goes awry,
590 * and when we exit from the beginning, we'll be placed *after* the
595 inset->edit(cur, front);
600 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
606 if (cur.pos() == cur.lastpos())
608 Paragraph & par = cur.paragraph();
609 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
610 if (!inset || !inset->editable())
612 inset->edit(cur, movingForward,
613 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
618 bool Text::cursorBackward(Cursor & cur)
620 // Tell BufferView to test for FitCursor in any case!
621 cur.updateFlags(Update::FitCursor);
623 // not at paragraph start?
625 // if on right side of boundary (i.e. not at paragraph end, but line end)
626 // -> skip it, i.e. set boundary to true, i.e. go only logically left
627 // there are some exceptions to ignore this: lineseps, newlines, spaces
629 // some effectless debug code to see the values in the debugger
630 bool bound = cur.boundary();
631 int rowpos = cur.textRow().pos();
633 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
634 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
635 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
637 if (!cur.boundary() &&
638 cur.textRow().pos() == cur.pos() &&
639 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
640 !cur.paragraph().isNewline(cur.pos() - 1) &&
641 !cur.paragraph().isSeparator(cur.pos() - 1)) {
642 return setCursor(cur, cur.pit(), cur.pos(), true, true);
645 // go left and try to enter inset
646 if (checkAndActivateInset(cur, false))
649 // normal character left
650 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
653 // move to the previous paragraph or do nothing
655 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
660 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
662 Cursor temp_cur = cur;
663 temp_cur.posVisLeft(skip_inset);
664 if (temp_cur.depth() > cur.depth()) {
668 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
669 true, temp_cur.boundary());
673 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
675 Cursor temp_cur = cur;
676 temp_cur.posVisRight(skip_inset);
677 if (temp_cur.depth() > cur.depth()) {
681 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
682 true, temp_cur.boundary());
686 bool Text::cursorForward(Cursor & cur)
688 // Tell BufferView to test for FitCursor in any case!
689 cur.updateFlags(Update::FitCursor);
691 // not at paragraph end?
692 if (cur.pos() != cur.lastpos()) {
693 // in front of editable inset, i.e. jump into it?
694 if (checkAndActivateInset(cur, true))
697 TextMetrics const & tm = cur.bv().textMetrics(this);
698 // if left of boundary -> just jump to right side
699 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
700 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
701 return setCursor(cur, cur.pit(), cur.pos(), true, false);
703 // next position is left of boundary,
704 // but go to next line for special cases like space, newline, linesep
706 // some effectless debug code to see the values in the debugger
707 int endpos = cur.textRow().endpos();
708 int lastpos = cur.lastpos();
710 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
711 bool newline = cur.paragraph().isNewline(cur.pos());
712 bool sep = cur.paragraph().isSeparator(cur.pos());
713 if (cur.pos() != cur.lastpos()) {
714 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
715 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
716 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
719 if (cur.textRow().endpos() == cur.pos() + 1 &&
720 cur.textRow().endpos() != cur.lastpos() &&
721 !cur.paragraph().isNewline(cur.pos()) &&
722 !cur.paragraph().isLineSeparator(cur.pos()) &&
723 !cur.paragraph().isSeparator(cur.pos())) {
724 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
727 // in front of RTL boundary? Stay on this side of the boundary because:
728 // ab|cDDEEFFghi -> abc|DDEEFFghi
729 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
730 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
733 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
736 // move to next paragraph
737 if (cur.pit() != cur.lastpit())
738 return setCursor(cur, cur.pit() + 1, 0, true, false);
743 bool Text::cursorUpParagraph(Cursor & cur)
745 bool updated = false;
747 updated = setCursor(cur, cur.pit(), 0);
748 else if (cur.pit() != 0)
749 updated = setCursor(cur, cur.pit() - 1, 0);
754 bool Text::cursorDownParagraph(Cursor & cur)
756 bool updated = false;
757 if (cur.pit() != cur.lastpit())
758 updated = setCursor(cur, cur.pit() + 1, 0);
760 updated = setCursor(cur, cur.pit(), cur.lastpos());
765 // fix the cursor `cur' after a characters has been deleted at `where'
766 // position. Called by deleteEmptyParagraphMechanism
767 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
769 // Do nothing if cursor is not in the paragraph where the
771 if (cur.pit() != where.pit())
774 // If cursor position is after the deletion place update it
775 if (cur.pos() > where.pos())
778 // Check also if we don't want to set the cursor on a spot behind the
779 // pagragraph because we erased the last character.
780 if (cur.pos() > cur.lastpos())
781 cur.pos() = cur.lastpos();
785 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
786 Cursor & old, bool & need_anchor_change)
788 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
790 Paragraph & oldpar = old.paragraph();
792 // We allow all kinds of "mumbo-jumbo" when freespacing.
793 if (oldpar.isFreeSpacing())
796 /* Ok I'll put some comments here about what is missing.
797 There are still some small problems that can lead to
798 double spaces stored in the document file or space at
799 the beginning of paragraphs(). This happens if you have
800 the cursor between to spaces and then save. Or if you
801 cut and paste and the selection have a space at the
802 beginning and then save right after the paste. (Lgb)
805 // If old.pos() == 0 and old.pos()(1) == LineSeparator
806 // delete the LineSeparator.
809 // If old.pos() == 1 and old.pos()(0) == LineSeparator
810 // delete the LineSeparator.
813 // Find a common inset and the corresponding depth.
815 for (; depth < cur.depth(); ++depth)
816 if (&old.inset() == &cur[depth].inset())
819 // Whether a common inset is found and whether the cursor is still in
820 // the same paragraph (possibly nested).
821 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
822 bool const same_par_pos = depth == cur.depth() - 1 && same_par
823 && old.pos() == cur[depth].pos();
825 // If the chars around the old cursor were spaces, delete one of them.
827 // Only if the cursor has really moved.
829 && old.pos() < oldpar.size()
830 && oldpar.isLineSeparator(old.pos())
831 && oldpar.isLineSeparator(old.pos() - 1)
832 && !oldpar.isDeleted(old.pos() - 1)
833 && !oldpar.isDeleted(old.pos())) {
834 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
835 // FIXME: This will not work anymore when we have multiple views of the same buffer
836 // In this case, we will have to correct also the cursors held by
837 // other bufferviews. It will probably be easier to do that in a more
838 // automated way in CursorSlice code. (JMarc 26/09/2001)
839 // correct all cursor parts
841 fixCursorAfterDelete(cur[depth], old.top());
842 need_anchor_change = true;
848 // only do our magic if we changed paragraph
852 // don't delete anything if this is the ONLY paragraph!
853 if (old.lastpit() == 0)
856 // Do not delete empty paragraphs with keepempty set.
857 if (oldpar.allowEmpty())
860 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
862 old.recordUndo(ATOMIC_UNDO,
863 max(old.pit() - 1, pit_type(0)),
864 min(old.pit() + 1, old.lastpit()));
865 ParagraphList & plist = old.text()->paragraphs();
866 bool const soa = oldpar.params().startOfAppendix();
867 plist.erase(boost::next(plist.begin(), old.pit()));
868 // do not lose start of appendix marker (bug 4212)
869 if (soa && old.pit() < pit_type(plist.size()))
870 plist[old.pit()].params().startOfAppendix(true);
872 // see #warning (FIXME?) above
873 if (cur.depth() >= old.depth()) {
874 CursorSlice & curslice = cur[old.depth() - 1];
875 if (&curslice.inset() == &old.inset()
876 && curslice.pit() > old.pit()) {
878 // since a paragraph has been deleted, all the
879 // insets after `old' have been copied and
880 // their address has changed. Therefore we
881 // need to `regenerate' cur. (JMarc)
882 cur.updateInsets(&(cur.bottom().inset()));
883 need_anchor_change = true;
889 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
890 need_anchor_change = true;
891 // We return true here because the Paragraph contents changed and
892 // we need a redraw before further action is processed.
900 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
902 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
904 for (pit_type pit = first; pit <= last; ++pit) {
905 Paragraph & par = pars_[pit];
907 // We allow all kinds of "mumbo-jumbo" when freespacing.
908 if (par.isFreeSpacing())
911 for (pos_type pos = 1; pos < par.size(); ++pos) {
912 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
913 && !par.isDeleted(pos - 1)) {
914 if (par.eraseChar(pos - 1, trackChanges)) {
920 // don't delete anything if this is the only remaining paragraph within the given range
921 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
925 // don't delete empty paragraphs with keepempty set
926 if (par.allowEmpty())
929 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
930 pars_.erase(boost::next(pars_.begin(), pit));
936 par.stripLeadingSpaces(trackChanges);
941 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
943 cur.recordUndo(ATOMIC_UNDO, first, last);
947 void Text::recUndo(Cursor & cur, pit_type par) const
949 cur.recordUndo(ATOMIC_UNDO, par, par);