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 par.params().apply(params, par.layout());
457 //FIXME This is a little redundant now, but it's probably worth keeping,
458 //especially if we're going to go away from using serialization internally
460 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
462 LASSERT(cur.text(), /**/);
463 // make sure that the depth behind the selection are restored, too
464 pit_type undopit = undoSpan(cur.selEnd().pit());
465 recUndo(cur, cur.selBegin().pit(), undopit - 1);
467 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
469 Paragraph & par = pars_[pit];
470 par.params().apply(p, par.layout());
475 // this really should just insert the inset and not move the cursor.
476 void Text::insertInset(Cursor & cur, Inset * inset)
478 LASSERT(this == cur.text(), /**/);
479 LASSERT(inset, /**/);
480 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
481 Change(cur.buffer()->params().trackChanges
482 ? Change::INSERTED : Change::UNCHANGED));
486 // needed to insert the selection
487 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
489 cur.buffer()->insertStringAsLines(pars_, cur.pit(), cur.pos(),
490 cur.current_font, str, autoBreakRows_);
494 // turn double CR to single CR, others are converted into one
495 // blank. Then insertStringAsLines is called
496 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
498 docstring linestr = str;
499 bool newline_inserted = false;
501 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
502 if (linestr[i] == '\n') {
503 if (newline_inserted) {
504 // we know that \r will be ignored by
505 // insertStringAsLines. Of course, it is a dirty
506 // trick, but it works...
507 linestr[i - 1] = '\r';
511 newline_inserted = true;
513 } else if (isPrintable(linestr[i])) {
514 newline_inserted = false;
517 insertStringAsLines(cur, linestr);
521 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
522 bool setfont, bool boundary)
524 TextMetrics const & tm = cur.bv().textMetrics(this);
525 bool const update_needed = !tm.contains(par);
527 setCursorIntern(cur, par, pos, setfont, boundary);
528 return cur.bv().checkDepm(cur, old) || update_needed;
532 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
534 LASSERT(par != int(paragraphs().size()), /**/);
538 // now some strict checking
539 Paragraph & para = getPar(par);
541 // None of these should happen, but we're scaredy-cats
543 lyxerr << "dont like -1" << endl;
544 LASSERT(false, /**/);
547 if (pos > para.size()) {
548 lyxerr << "dont like 1, pos: " << pos
549 << " size: " << para.size()
550 << " par: " << par << endl;
551 LASSERT(false, /**/);
556 void Text::setCursorIntern(Cursor & cur,
557 pit_type par, pos_type pos, bool setfont, bool boundary)
559 LASSERT(this == cur.text(), /**/);
560 cur.boundary(boundary);
561 setCursor(cur.top(), par, pos);
563 cur.setCurrentFont();
567 bool Text::checkAndActivateInset(Cursor & cur, bool front)
571 if (front && cur.pos() == cur.lastpos())
573 if (!front && cur.pos() == 0)
575 Inset * inset = front ? cur.nextInset() : cur.prevInset();
576 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
579 * Apparently, when entering an inset we are expected to be positioned
580 * *before* it in the containing paragraph, regardless of the direction
581 * from which we are entering. Otherwise, cursor placement goes awry,
582 * and when we exit from the beginning, we'll be placed *after* the
587 inset->edit(cur, front);
592 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
598 if (cur.pos() == cur.lastpos())
600 Paragraph & par = cur.paragraph();
601 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
602 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
604 inset->edit(cur, movingForward,
605 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
610 bool Text::cursorBackward(Cursor & cur)
612 // Tell BufferView to test for FitCursor in any case!
613 cur.updateFlags(Update::FitCursor);
615 // not at paragraph start?
617 // if on right side of boundary (i.e. not at paragraph end, but line end)
618 // -> skip it, i.e. set boundary to true, i.e. go only logically left
619 // there are some exceptions to ignore this: lineseps, newlines, spaces
621 // some effectless debug code to see the values in the debugger
622 bool bound = cur.boundary();
623 int rowpos = cur.textRow().pos();
625 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
626 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
627 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
629 if (!cur.boundary() &&
630 cur.textRow().pos() == cur.pos() &&
631 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
632 !cur.paragraph().isNewline(cur.pos() - 1) &&
633 !cur.paragraph().isSeparator(cur.pos() - 1)) {
634 return setCursor(cur, cur.pit(), cur.pos(), true, true);
637 // go left and try to enter inset
638 if (checkAndActivateInset(cur, false))
641 // normal character left
642 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
645 // move to the previous paragraph or do nothing
647 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
652 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
654 Cursor temp_cur = cur;
655 temp_cur.posVisLeft(skip_inset);
656 if (temp_cur.depth() > cur.depth()) {
660 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
661 true, temp_cur.boundary());
665 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
667 Cursor temp_cur = cur;
668 temp_cur.posVisRight(skip_inset);
669 if (temp_cur.depth() > cur.depth()) {
673 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
674 true, temp_cur.boundary());
678 bool Text::cursorForward(Cursor & cur)
680 // Tell BufferView to test for FitCursor in any case!
681 cur.updateFlags(Update::FitCursor);
683 // not at paragraph end?
684 if (cur.pos() != cur.lastpos()) {
685 // in front of editable inset, i.e. jump into it?
686 if (checkAndActivateInset(cur, true))
689 TextMetrics const & tm = cur.bv().textMetrics(this);
690 // if left of boundary -> just jump to right side
691 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
692 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
693 return setCursor(cur, cur.pit(), cur.pos(), true, false);
695 // next position is left of boundary,
696 // but go to next line for special cases like space, newline, linesep
698 // some effectless debug code to see the values in the debugger
699 int endpos = cur.textRow().endpos();
700 int lastpos = cur.lastpos();
702 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
703 bool newline = cur.paragraph().isNewline(cur.pos());
704 bool sep = cur.paragraph().isSeparator(cur.pos());
705 if (cur.pos() != cur.lastpos()) {
706 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
707 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
708 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
711 if (cur.textRow().endpos() == cur.pos() + 1 &&
712 cur.textRow().endpos() != cur.lastpos() &&
713 !cur.paragraph().isNewline(cur.pos()) &&
714 !cur.paragraph().isLineSeparator(cur.pos()) &&
715 !cur.paragraph().isSeparator(cur.pos())) {
716 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
719 // in front of RTL boundary? Stay on this side of the boundary because:
720 // ab|cDDEEFFghi -> abc|DDEEFFghi
721 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
722 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
725 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
728 // move to next paragraph
729 if (cur.pit() != cur.lastpit())
730 return setCursor(cur, cur.pit() + 1, 0, true, false);
735 bool Text::cursorUpParagraph(Cursor & cur)
737 bool updated = false;
739 updated = setCursor(cur, cur.pit(), 0);
740 else if (cur.pit() != 0)
741 updated = setCursor(cur, cur.pit() - 1, 0);
746 bool Text::cursorDownParagraph(Cursor & cur)
748 bool updated = false;
749 if (cur.pit() != cur.lastpit())
750 updated = setCursor(cur, cur.pit() + 1, 0);
752 updated = setCursor(cur, cur.pit(), cur.lastpos());
757 // fix the cursor `cur' after a characters has been deleted at `where'
758 // position. Called by deleteEmptyParagraphMechanism
759 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
761 // Do nothing if cursor is not in the paragraph where the
763 if (cur.pit() != where.pit())
766 // If cursor position is after the deletion place update it
767 if (cur.pos() > where.pos())
770 // Check also if we don't want to set the cursor on a spot behind the
771 // pagragraph because we erased the last character.
772 if (cur.pos() > cur.lastpos())
773 cur.pos() = cur.lastpos();
777 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
778 Cursor & old, bool & need_anchor_change)
780 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
782 Paragraph & oldpar = old.paragraph();
784 // We allow all kinds of "mumbo-jumbo" when freespacing.
785 if (oldpar.isFreeSpacing())
788 /* Ok I'll put some comments here about what is missing.
789 There are still some small problems that can lead to
790 double spaces stored in the document file or space at
791 the beginning of paragraphs(). This happens if you have
792 the cursor between to spaces and then save. Or if you
793 cut and paste and the selection have a space at the
794 beginning and then save right after the paste. (Lgb)
797 // If old.pos() == 0 and old.pos()(1) == LineSeparator
798 // delete the LineSeparator.
801 // If old.pos() == 1 and old.pos()(0) == LineSeparator
802 // delete the LineSeparator.
805 // Find a common inset and the corresponding depth.
807 for (; depth < cur.depth(); ++depth)
808 if (&old.inset() == &cur[depth].inset())
811 // Whether a common inset is found and whether the cursor is still in
812 // the same paragraph (possibly nested).
813 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
814 bool const same_par_pos = depth == cur.depth() - 1 && same_par
815 && old.pos() == cur[depth].pos();
817 // If the chars around the old cursor were spaces, delete one of them.
819 // Only if the cursor has really moved.
821 && old.pos() < oldpar.size()
822 && oldpar.isLineSeparator(old.pos())
823 && oldpar.isLineSeparator(old.pos() - 1)
824 && !oldpar.isDeleted(old.pos() - 1)
825 && !oldpar.isDeleted(old.pos())) {
826 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
827 // FIXME: This will not work anymore when we have multiple views of the same buffer
828 // In this case, we will have to correct also the cursors held by
829 // other bufferviews. It will probably be easier to do that in a more
830 // automated way in CursorSlice code. (JMarc 26/09/2001)
831 // correct all cursor parts
833 fixCursorAfterDelete(cur[depth], old.top());
834 need_anchor_change = true;
840 // only do our magic if we changed paragraph
844 // don't delete anything if this is the ONLY paragraph!
845 if (old.lastpit() == 0)
848 // Do not delete empty paragraphs with keepempty set.
849 if (oldpar.allowEmpty())
852 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
854 old.recordUndo(ATOMIC_UNDO,
855 max(old.pit() - 1, pit_type(0)),
856 min(old.pit() + 1, old.lastpit()));
857 ParagraphList & plist = old.text()->paragraphs();
858 bool const soa = oldpar.params().startOfAppendix();
859 plist.erase(boost::next(plist.begin(), old.pit()));
860 // do not lose start of appendix marker (bug 4212)
861 if (soa && old.pit() < pit_type(plist.size()))
862 plist[old.pit()].params().startOfAppendix(true);
864 // see #warning (FIXME?) above
865 if (cur.depth() >= old.depth()) {
866 CursorSlice & curslice = cur[old.depth() - 1];
867 if (&curslice.inset() == &old.inset()
868 && curslice.pit() > old.pit()) {
870 // since a paragraph has been deleted, all the
871 // insets after `old' have been copied and
872 // their address has changed. Therefore we
873 // need to `regenerate' cur. (JMarc)
874 cur.updateInsets(&(cur.bottom().inset()));
875 need_anchor_change = true;
881 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
882 need_anchor_change = true;
883 // We return true here because the Paragraph contents changed and
884 // we need a redraw before further action is processed.
892 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
894 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
896 for (pit_type pit = first; pit <= last; ++pit) {
897 Paragraph & par = pars_[pit];
899 // We allow all kinds of "mumbo-jumbo" when freespacing.
900 if (par.isFreeSpacing())
903 for (pos_type pos = 1; pos < par.size(); ++pos) {
904 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
905 && !par.isDeleted(pos - 1)) {
906 if (par.eraseChar(pos - 1, trackChanges)) {
912 // don't delete anything if this is the only remaining paragraph within the given range
913 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
917 // don't delete empty paragraphs with keepempty set
918 if (par.allowEmpty())
921 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
922 pars_.erase(boost::next(pars_.begin(), pit));
928 par.stripLeadingSpaces(trackChanges);
933 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
935 cur.recordUndo(ATOMIC_UNDO, first, last);
939 void Text::recUndo(Cursor & cur, pit_type par) const
941 cur.recordUndo(ATOMIC_UNDO, par, par);