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>
66 bool Text::isMainText(Buffer const & buffer) const
68 return &buffer.text() == this;
72 // Note that this is supposed to return a fully realized font.
73 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
75 Layout const & layout = pars_[pit].layout();
77 if (!pars_[pit].getDepth()) {
78 FontInfo lf = layout.resfont;
79 // In case the default family has been customized
80 if (layout.font.family() == INHERIT_FAMILY)
81 lf.setFamily(buffer.params().getFont().fontInfo().family());
83 // It ought to be possible here just to use Inset::getLayout() and skip
84 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
85 // now, because Inset::getLayout() will return a default-constructed
86 // InsetLayout, and that e.g. sets the foreground color to red. So we
87 // need to do some work to make that possible.
88 InsetCollapsable const * icp = pars_[pit].inInset().asInsetCollapsable();
91 FontInfo icf = icp->getLayout().font();
96 FontInfo font = layout.font;
97 // Realize with the fonts of lesser depth.
98 //font.realize(outerFont(pit, paragraphs()));
99 font.realize(buffer.params().getFont().fontInfo());
105 // Note that this is supposed to return a fully realized font.
106 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
108 Layout const & layout = par.layout();
110 if (!par.getDepth()) {
111 FontInfo lf = layout.reslabelfont;
112 // In case the default family has been customized
113 if (layout.labelfont.family() == INHERIT_FAMILY)
114 lf.setFamily(buffer.params().getFont().fontInfo().family());
118 FontInfo font = layout.labelfont;
119 // Realize with the fonts of lesser depth.
120 font.realize(buffer.params().getFont().fontInfo());
126 void Text::setCharFont(Buffer const & buffer, pit_type pit,
127 pos_type pos, Font const & fnt, Font const & display_font)
130 Layout 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.fontInfo());
157 layoutfont.realize(buffer.params().getFont().fontInfo());
159 // Now, reduce font against full layout font
160 font.fontInfo().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 Inset * const inset = pars_[pit].getInset(pos);
170 LASSERT(inset && inset->noFontChange(), /**/);
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 const 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 LASSERT(start != end, /**/);
210 BufferParams const & bp = buffer.params();
211 Layout const & lyxlayout = bp.documentClass()[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.expandLabel(lyxlayout, bp));
222 // set layout over selection and make a total rebreak of those paragraphs
223 void Text::setLayout(Cursor & cur, docstring const & layout)
225 LASSERT(this == cur.text(), /**/);
227 pit_type start = cur.selBegin().pit();
228 pit_type end = cur.selEnd().pit() + 1;
229 pit_type undopit = undoSpan(end - 1);
230 recUndo(cur, start, undopit - 1);
231 setLayout(*cur.buffer(), start, end, layout);
232 cur.buffer()->updateLabels();
236 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
237 Paragraph const & par, int max_depth)
239 if (par.layout().labeltype == LABEL_BIBLIO)
241 int const depth = par.params().depth();
242 if (type == Text::INC_DEPTH && depth < max_depth)
244 if (type == Text::DEC_DEPTH && depth > 0)
250 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
252 LASSERT(this == cur.text(), /**/);
253 // this happens when selecting several cells in tabular (bug 2630)
254 if (cur.selBegin().idx() != cur.selEnd().idx())
257 pit_type const beg = cur.selBegin().pit();
258 pit_type const end = cur.selEnd().pit() + 1;
259 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
261 for (pit_type pit = beg; pit != end; ++pit) {
262 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
264 max_depth = pars_[pit].getMaxDepthAfter();
270 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
272 LASSERT(this == cur.text(), /**/);
273 pit_type const beg = cur.selBegin().pit();
274 pit_type const end = cur.selEnd().pit() + 1;
275 cur.recordUndoSelection();
276 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
278 for (pit_type pit = beg; pit != end; ++pit) {
279 Paragraph & par = pars_[pit];
280 if (lyx::changeDepthAllowed(type, par, max_depth)) {
281 int const depth = par.params().depth();
282 if (type == INC_DEPTH)
283 par.params().depth(depth + 1);
285 par.params().depth(depth - 1);
287 max_depth = par.getMaxDepthAfter();
289 // this handles the counter labels, and also fixes up
290 // depth values for follow-on (child) paragraphs
291 cur.buffer()->updateLabels();
295 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
297 LASSERT(this == cur.text(), /**/);
298 // Set the current_font
299 // Determine basis font
301 pit_type pit = cur.pit();
302 if (cur.pos() < pars_[pit].beginOfBody())
303 layoutfont = labelFont(*cur.buffer(), pars_[pit]);
305 layoutfont = layoutFont(*cur.buffer(), pit);
307 // Update current font
308 cur.real_current_font.update(font,
309 cur.buffer()->params().language,
312 // Reduce to implicit settings
313 cur.current_font = cur.real_current_font;
314 cur.current_font.fontInfo().reduce(layoutfont);
315 // And resolve it completely
316 cur.real_current_font.fontInfo().realize(layoutfont);
318 // if there is no selection that's all we need to do
319 if (!cur.selection())
322 // Ok, we have a selection.
323 cur.recordUndoSelection();
325 setFont(cur.bv(), cur.selectionBegin().top(),
326 cur.selectionEnd().top(), font, toggleall);
330 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
331 CursorSlice const & end, Font const & font,
334 Buffer const & buffer = bv.buffer();
336 // Don't use forwardChar here as ditend might have
337 // pos() == lastpos() and forwardChar would miss it.
338 // Can't use forwardPos either as this descends into
340 Language const * language = buffer.params().language;
341 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
342 if (dit.pos() == dit.lastpos())
344 pit_type const pit = dit.pit();
345 pos_type const pos = dit.pos();
346 Inset * inset = pars_[pit].getInset(pos);
347 if (inset && inset->noFontChange()) {
348 // We need to propagate the font change to all
349 // text cells of the inset (bug 1973).
350 // FIXME: This should change, see documentation
351 // of noFontChange in Inset.h
352 setInsetFont(bv, pit, pos, font, toggleall);
354 TextMetrics const & tm = bv.textMetrics(this);
355 Font f = tm.displayFont(pit, pos);
356 f.update(font, language, toggleall);
357 setCharFont(buffer, pit, pos, f, tm.font_);
362 bool Text::cursorTop(Cursor & cur)
364 LASSERT(this == cur.text(), /**/);
365 return setCursor(cur, 0, 0);
369 bool Text::cursorBottom(Cursor & cur)
371 LASSERT(this == cur.text(), /**/);
372 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
376 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
378 LASSERT(this == cur.text(), /**/);
379 // If the mask is completely neutral, tell user
380 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
381 // Could only happen with user style
382 cur.message(_("No font change defined."));
386 // Try implicit word selection
387 // If there is a change in the language the implicit word selection
389 CursorSlice const resetCursor = cur.top();
390 bool const implicitSelection =
391 font.language() == ignore_language
392 && font.fontInfo().number() == FONT_IGNORE
393 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
396 setFont(cur, font, toggleall);
398 // Implicit selections are cleared afterwards
399 // and cursor is set to the original position.
400 if (implicitSelection) {
401 cur.clearSelection();
402 cur.top() = resetCursor;
408 docstring Text::getStringToIndex(Cursor const & cur)
410 LASSERT(this == cur.text(), /**/);
413 return cur.selectionAsString(false);
415 // Try implicit word selection. If there is a change
416 // in the language the implicit word selection is
419 selectWord(tmpcur, PREVIOUS_WORD);
421 if (!tmpcur.selection())
422 cur.message(_("Nothing to index!"));
423 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
424 cur.message(_("Cannot index more than one paragraph!"));
426 return tmpcur.selectionAsString(false);
432 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
434 LASSERT(cur.text(), /**/);
435 // make sure that the depth behind the selection are restored, too
436 pit_type undopit = undoSpan(cur.selEnd().pit());
437 recUndo(cur, cur.selBegin().pit(), undopit - 1);
440 string const argument = to_utf8(arg);
441 depth_type priordepth = -1;
443 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
445 Paragraph & par = pars_[pit];
446 ParagraphParameters params = par.params();
447 params.read(argument, merge);
448 // Changes to label width string apply to all paragraphs
449 // with same layout in a sequence.
450 // Do this only once for a selected range of paragraphs
451 // of the same layout and depth.
452 if (par.getDepth() != priordepth || par.layout() != priorlayout)
453 setLabelWidthStringToSequence(pit, pars_,
454 params.labelWidthString());
455 par.params().apply(params, par.layout());
456 priordepth = par.getDepth();
457 priorlayout = par.layout();
462 //FIXME This is a little redundant now, but it's probably worth keeping,
463 //especially if we're going to go away from using serialization internally
465 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
467 LASSERT(cur.text(), /**/);
468 // make sure that the depth behind the selection are restored, too
469 pit_type undopit = undoSpan(cur.selEnd().pit());
470 recUndo(cur, cur.selBegin().pit(), undopit - 1);
472 depth_type priordepth = -1;
474 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
476 Paragraph & par = pars_[pit];
477 // Changes to label width string apply to all paragraphs
478 // with same layout in a sequence.
479 // Do this only once for a selected range of paragraphs
480 // of the same layout and depth.
481 if (par.getDepth() != priordepth || par.layout() != priorlayout)
482 setLabelWidthStringToSequence(pit, pars_,
483 par.params().labelWidthString());
484 par.params().apply(p, par.layout());
485 priordepth = par.getDepth();
486 priorlayout = par.layout();
491 // this really should just insert the inset and not move the cursor.
492 void Text::insertInset(Cursor & cur, Inset * inset)
494 LASSERT(this == cur.text(), /**/);
495 LASSERT(inset, /**/);
496 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
497 Change(cur.buffer()->params().trackChanges
498 ? Change::INSERTED : Change::UNCHANGED));
502 // needed to insert the selection
503 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
505 cur.buffer()->insertStringAsLines(pars_, cur.pit(), cur.pos(),
506 cur.current_font, str, autoBreakRows_);
510 // turn double CR to single CR, others are converted into one
511 // blank. Then insertStringAsLines is called
512 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
514 docstring linestr = str;
515 bool newline_inserted = false;
517 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
518 if (linestr[i] == '\n') {
519 if (newline_inserted) {
520 // we know that \r will be ignored by
521 // insertStringAsLines. Of course, it is a dirty
522 // trick, but it works...
523 linestr[i - 1] = '\r';
527 newline_inserted = true;
529 } else if (isPrintable(linestr[i])) {
530 newline_inserted = false;
533 insertStringAsLines(cur, linestr);
537 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
538 bool setfont, bool boundary)
540 TextMetrics const & tm = cur.bv().textMetrics(this);
541 bool const update_needed = !tm.contains(par);
543 setCursorIntern(cur, par, pos, setfont, boundary);
544 return cur.bv().checkDepm(cur, old) || update_needed;
548 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
550 LASSERT(par != int(paragraphs().size()), /**/);
554 // now some strict checking
555 Paragraph & para = getPar(par);
557 // None of these should happen, but we're scaredy-cats
559 lyxerr << "dont like -1" << endl;
560 LASSERT(false, /**/);
563 if (pos > para.size()) {
564 lyxerr << "dont like 1, pos: " << pos
565 << " size: " << para.size()
566 << " par: " << par << endl;
567 LASSERT(false, /**/);
572 void Text::setCursorIntern(Cursor & cur,
573 pit_type par, pos_type pos, bool setfont, bool boundary)
575 LASSERT(this == cur.text(), /**/);
576 cur.boundary(boundary);
577 setCursor(cur.top(), par, pos);
579 cur.setCurrentFont();
583 bool Text::checkAndActivateInset(Cursor & cur, bool front)
587 if (front && cur.pos() == cur.lastpos())
589 if (!front && cur.pos() == 0)
591 Inset * inset = front ? cur.nextInset() : cur.prevInset();
592 if (!inset || !inset->editable())
595 * Apparently, when entering an inset we are expected to be positioned
596 * *before* it in the containing paragraph, regardless of the direction
597 * from which we are entering. Otherwise, cursor placement goes awry,
598 * and when we exit from the beginning, we'll be placed *after* the
603 inset->edit(cur, front);
608 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
614 if (cur.pos() == cur.lastpos())
616 Paragraph & par = cur.paragraph();
617 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
618 if (!inset || !inset->editable())
620 inset->edit(cur, movingForward,
621 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
626 bool Text::cursorBackward(Cursor & cur)
628 // Tell BufferView to test for FitCursor in any case!
629 cur.updateFlags(Update::FitCursor);
631 // not at paragraph start?
633 // if on right side of boundary (i.e. not at paragraph end, but line end)
634 // -> skip it, i.e. set boundary to true, i.e. go only logically left
635 // there are some exceptions to ignore this: lineseps, newlines, spaces
637 // some effectless debug code to see the values in the debugger
638 bool bound = cur.boundary();
639 int rowpos = cur.textRow().pos();
641 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
642 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
643 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
645 if (!cur.boundary() &&
646 cur.textRow().pos() == cur.pos() &&
647 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
648 !cur.paragraph().isNewline(cur.pos() - 1) &&
649 !cur.paragraph().isSeparator(cur.pos() - 1)) {
650 return setCursor(cur, cur.pit(), cur.pos(), true, true);
653 // go left and try to enter inset
654 if (checkAndActivateInset(cur, false))
657 // normal character left
658 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
661 // move to the previous paragraph or do nothing
663 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
668 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
670 Cursor temp_cur = cur;
671 temp_cur.posVisLeft(skip_inset);
672 if (temp_cur.depth() > cur.depth()) {
676 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
677 true, temp_cur.boundary());
681 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
683 Cursor temp_cur = cur;
684 temp_cur.posVisRight(skip_inset);
685 if (temp_cur.depth() > cur.depth()) {
689 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
690 true, temp_cur.boundary());
694 bool Text::cursorForward(Cursor & cur)
696 // Tell BufferView to test for FitCursor in any case!
697 cur.updateFlags(Update::FitCursor);
699 // not at paragraph end?
700 if (cur.pos() != cur.lastpos()) {
701 // in front of editable inset, i.e. jump into it?
702 if (checkAndActivateInset(cur, true))
705 TextMetrics const & tm = cur.bv().textMetrics(this);
706 // if left of boundary -> just jump to right side
707 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
708 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
709 return setCursor(cur, cur.pit(), cur.pos(), true, false);
711 // next position is left of boundary,
712 // but go to next line for special cases like space, newline, linesep
714 // some effectless debug code to see the values in the debugger
715 int endpos = cur.textRow().endpos();
716 int lastpos = cur.lastpos();
718 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
719 bool newline = cur.paragraph().isNewline(cur.pos());
720 bool sep = cur.paragraph().isSeparator(cur.pos());
721 if (cur.pos() != cur.lastpos()) {
722 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
723 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
724 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
727 if (cur.textRow().endpos() == cur.pos() + 1 &&
728 cur.textRow().endpos() != cur.lastpos() &&
729 !cur.paragraph().isNewline(cur.pos()) &&
730 !cur.paragraph().isLineSeparator(cur.pos()) &&
731 !cur.paragraph().isSeparator(cur.pos())) {
732 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
735 // in front of RTL boundary? Stay on this side of the boundary because:
736 // ab|cDDEEFFghi -> abc|DDEEFFghi
737 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
738 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
741 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
744 // move to next paragraph
745 if (cur.pit() != cur.lastpit())
746 return setCursor(cur, cur.pit() + 1, 0, true, false);
751 bool Text::cursorUpParagraph(Cursor & cur)
753 bool updated = false;
755 updated = setCursor(cur, cur.pit(), 0);
756 else if (cur.pit() != 0)
757 updated = setCursor(cur, cur.pit() - 1, 0);
762 bool Text::cursorDownParagraph(Cursor & cur)
764 bool updated = false;
765 if (cur.pit() != cur.lastpit())
766 updated = setCursor(cur, cur.pit() + 1, 0);
768 updated = setCursor(cur, cur.pit(), cur.lastpos());
773 // fix the cursor `cur' after a characters has been deleted at `where'
774 // position. Called by deleteEmptyParagraphMechanism
775 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
777 // Do nothing if cursor is not in the paragraph where the
779 if (cur.pit() != where.pit())
782 // If cursor position is after the deletion place update it
783 if (cur.pos() > where.pos())
786 // Check also if we don't want to set the cursor on a spot behind the
787 // pagragraph because we erased the last character.
788 if (cur.pos() > cur.lastpos())
789 cur.pos() = cur.lastpos();
793 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
794 Cursor & old, bool & need_anchor_change)
796 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
798 Paragraph & oldpar = old.paragraph();
800 // We allow all kinds of "mumbo-jumbo" when freespacing.
801 if (oldpar.isFreeSpacing())
804 /* Ok I'll put some comments here about what is missing.
805 There are still some small problems that can lead to
806 double spaces stored in the document file or space at
807 the beginning of paragraphs(). This happens if you have
808 the cursor between to spaces and then save. Or if you
809 cut and paste and the selection have a space at the
810 beginning and then save right after the paste. (Lgb)
813 // If old.pos() == 0 and old.pos()(1) == LineSeparator
814 // delete the LineSeparator.
817 // If old.pos() == 1 and old.pos()(0) == LineSeparator
818 // delete the LineSeparator.
821 // Find a common inset and the corresponding depth.
823 for (; depth < cur.depth(); ++depth)
824 if (&old.inset() == &cur[depth].inset())
827 // Whether a common inset is found and whether the cursor is still in
828 // the same paragraph (possibly nested).
829 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
830 bool const same_par_pos = depth == cur.depth() - 1 && same_par
831 && old.pos() == cur[depth].pos();
833 // If the chars around the old cursor were spaces, delete one of them.
835 // Only if the cursor has really moved.
837 && old.pos() < oldpar.size()
838 && oldpar.isLineSeparator(old.pos())
839 && oldpar.isLineSeparator(old.pos() - 1)
840 && !oldpar.isDeleted(old.pos() - 1)
841 && !oldpar.isDeleted(old.pos())) {
842 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
843 // FIXME: This will not work anymore when we have multiple views of the same buffer
844 // In this case, we will have to correct also the cursors held by
845 // other bufferviews. It will probably be easier to do that in a more
846 // automated way in CursorSlice code. (JMarc 26/09/2001)
847 // correct all cursor parts
849 fixCursorAfterDelete(cur[depth], old.top());
850 need_anchor_change = true;
856 // only do our magic if we changed paragraph
860 // don't delete anything if this is the ONLY paragraph!
861 if (old.lastpit() == 0)
864 // Do not delete empty paragraphs with keepempty set.
865 if (oldpar.allowEmpty())
868 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
870 old.recordUndo(ATOMIC_UNDO,
871 max(old.pit() - 1, pit_type(0)),
872 min(old.pit() + 1, old.lastpit()));
873 ParagraphList & plist = old.text()->paragraphs();
874 bool const soa = oldpar.params().startOfAppendix();
875 plist.erase(boost::next(plist.begin(), old.pit()));
876 // do not lose start of appendix marker (bug 4212)
877 if (soa && old.pit() < pit_type(plist.size()))
878 plist[old.pit()].params().startOfAppendix(true);
880 // see #warning (FIXME?) above
881 if (cur.depth() >= old.depth()) {
882 CursorSlice & curslice = cur[old.depth() - 1];
883 if (&curslice.inset() == &old.inset()
884 && curslice.pit() > old.pit()) {
886 // since a paragraph has been deleted, all the
887 // insets after `old' have been copied and
888 // their address has changed. Therefore we
889 // need to `regenerate' cur. (JMarc)
890 cur.updateInsets(&(cur.bottom().inset()));
891 need_anchor_change = true;
897 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
898 need_anchor_change = true;
899 // We return true here because the Paragraph contents changed and
900 // we need a redraw before further action is processed.
908 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
910 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
912 for (pit_type pit = first; pit <= last; ++pit) {
913 Paragraph & par = pars_[pit];
915 // We allow all kinds of "mumbo-jumbo" when freespacing.
916 if (par.isFreeSpacing())
919 for (pos_type pos = 1; pos < par.size(); ++pos) {
920 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
921 && !par.isDeleted(pos - 1)) {
922 if (par.eraseChar(pos - 1, trackChanges)) {
928 // don't delete anything if this is the only remaining paragraph within the given range
929 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
933 // don't delete empty paragraphs with keepempty set
934 if (par.allowEmpty())
937 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
938 pars_.erase(boost::next(pars_.begin(), pit));
944 par.stripLeadingSpaces(trackChanges);
949 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
951 cur.recordUndo(ATOMIC_UNDO, first, last);
955 void Text::recUndo(Cursor & cur, pit_type par) const
957 cur.recordUndo(ATOMIC_UNDO, par, par);