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"
41 #include "Paragraph.h"
42 #include "ParagraphParameters.h"
43 #include "TextClass.h"
44 #include "TextMetrics.h"
46 #include "insets/InsetCollapsable.h"
48 #include "mathed/InsetMathHull.h"
50 #include "support/lassert.h"
51 #include "support/debug.h"
52 #include "support/gettext.h"
53 #include "support/textutils.h"
55 #include <boost/next_prior.hpp>
63 bool Text::isMainText() const
65 return &owner_->buffer().text() == this;
69 // Note that this is supposed to return a fully realized font.
70 FontInfo Text::layoutFont(pit_type const pit) const
72 Layout const & layout = pars_[pit].layout();
74 if (!pars_[pit].getDepth()) {
75 FontInfo lf = layout.resfont;
76 // In case the default family has been customized
77 if (layout.font.family() == INHERIT_FAMILY)
78 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
79 FontInfo icf = owner_->getLayout().font();
84 FontInfo font = layout.font;
85 // Realize with the fonts of lesser depth.
86 //font.realize(outerFont(pit));
87 font.realize(owner_->buffer().params().getFont().fontInfo());
93 // Note that this is supposed to return a fully realized font.
94 FontInfo Text::labelFont(Paragraph const & par) const
96 Buffer const & buffer = owner_->buffer();
97 Layout const & layout = par.layout();
99 if (!par.getDepth()) {
100 FontInfo lf = layout.reslabelfont;
101 // In case the default family has been customized
102 if (layout.labelfont.family() == INHERIT_FAMILY)
103 lf.setFamily(buffer.params().getFont().fontInfo().family());
107 FontInfo font = layout.labelfont;
108 // Realize with the fonts of lesser depth.
109 font.realize(buffer.params().getFont().fontInfo());
115 void Text::setCharFont(pit_type pit,
116 pos_type pos, Font const & fnt, Font const & display_font)
118 Buffer const & buffer = owner_->buffer();
120 Layout const & layout = pars_[pit].layout();
122 // Get concrete layout font to reduce against
125 if (pos < pars_[pit].beginOfBody())
126 layoutfont = layout.labelfont;
128 layoutfont = layout.font;
130 // Realize against environment font information
131 if (pars_[pit].getDepth()) {
133 while (!layoutfont.resolved() &&
134 tp != pit_type(paragraphs().size()) &&
135 pars_[tp].getDepth()) {
137 if (tp != pit_type(paragraphs().size()))
138 layoutfont.realize(pars_[tp].layout().font);
142 // Inside inset, apply the inset's font attributes if any
145 layoutfont.realize(display_font.fontInfo());
147 layoutfont.realize(buffer.params().getFont().fontInfo());
149 // Now, reduce font against full layout font
150 font.fontInfo().reduce(layoutfont);
152 pars_[pit].setFont(pos, font);
156 void Text::setInsetFont(BufferView const & bv, pit_type pit,
157 pos_type pos, Font const & font)
159 Inset * const inset = pars_[pit].getInset(pos);
160 LASSERT(inset && inset->resetFontEdit(), return);
162 CursorSlice::idx_type endidx = inset->nargs();
163 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
164 Text * text = cs.text();
166 // last position of the cell
167 CursorSlice cellend = cs;
168 cellend.pit() = cellend.lastpit();
169 cellend.pos() = cellend.lastpos();
170 text->setFont(bv, cs, cellend, font);
176 void Text::setLayout(pit_type start, pit_type end,
177 docstring const & layout)
179 LASSERT(start != end, return);
181 Buffer const & buffer = owner_->buffer();
182 BufferParams const & bp = buffer.params();
183 Layout const & lyxlayout = bp.documentClass()[layout];
185 for (pit_type pit = start; pit != end; ++pit) {
186 Paragraph & par = pars_[pit];
187 par.applyLayout(lyxlayout);
188 if (lyxlayout.margintype == MARGIN_MANUAL)
189 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
194 // set layout over selection and make a total rebreak of those paragraphs
195 void Text::setLayout(Cursor & cur, docstring const & layout)
197 LBUFERR(this == cur.text());
199 pit_type start = cur.selBegin().pit();
200 pit_type end = cur.selEnd().pit() + 1;
201 cur.recordUndoSelection();
202 setLayout(start, end, layout);
203 cur.forceBufferUpdate();
207 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
208 Paragraph const & par, int max_depth)
210 int const depth = par.params().depth();
211 if (type == Text::INC_DEPTH && depth < max_depth)
213 if (type == Text::DEC_DEPTH && depth > 0)
219 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
221 LBUFERR(this == cur.text());
222 // this happens when selecting several cells in tabular (bug 2630)
223 if (cur.selBegin().idx() != cur.selEnd().idx())
226 pit_type const beg = cur.selBegin().pit();
227 pit_type const end = cur.selEnd().pit() + 1;
228 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
230 for (pit_type pit = beg; pit != end; ++pit) {
231 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
233 max_depth = pars_[pit].getMaxDepthAfter();
239 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
241 LBUFERR(this == cur.text());
242 pit_type const beg = cur.selBegin().pit();
243 pit_type const end = cur.selEnd().pit() + 1;
244 cur.recordUndoSelection();
245 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
247 for (pit_type pit = beg; pit != end; ++pit) {
248 Paragraph & par = pars_[pit];
249 if (lyx::changeDepthAllowed(type, par, max_depth)) {
250 int const depth = par.params().depth();
251 if (type == INC_DEPTH)
252 par.params().depth(depth + 1);
254 par.params().depth(depth - 1);
256 max_depth = par.getMaxDepthAfter();
258 // this handles the counter labels, and also fixes up
259 // depth values for follow-on (child) paragraphs
260 cur.forceBufferUpdate();
264 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
266 LASSERT(this == cur.text(), return);
267 // Set the current_font
268 // Determine basis font
270 pit_type pit = cur.pit();
271 if (cur.pos() < pars_[pit].beginOfBody())
272 layoutfont = labelFont(pars_[pit]);
274 layoutfont = layoutFont(pit);
276 // Update current font
277 cur.real_current_font.update(font,
278 cur.buffer()->params().language,
281 // Reduce to implicit settings
282 cur.current_font = cur.real_current_font;
283 cur.current_font.fontInfo().reduce(layoutfont);
284 // And resolve it completely
285 cur.real_current_font.fontInfo().realize(layoutfont);
287 // if there is no selection that's all we need to do
288 if (!cur.selection())
291 // Ok, we have a selection.
292 cur.recordUndoSelection();
296 // Toggling behaves as follows: We check the first character of the
297 // selection. If it's (say) got EMPH on, then we set to off; if off,
298 // then to on. With families and the like, we set it to INHERIT, if
299 // we already have it.
300 CursorSlice const & sl = cur.selBegin();
301 Text const & text = *sl.text();
302 Paragraph const & par = text.getPar(sl.pit());
304 // get font at the position
305 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
306 text.outerFont(sl.pit()));
307 FontInfo const & oldfi = oldfont.fontInfo();
309 FontInfo & newfi = newfont.fontInfo();
311 FontFamily newfam = newfi.family();
312 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
313 newfam == oldfi.family())
314 newfi.setFamily(INHERIT_FAMILY);
316 FontSeries newser = newfi.series();
317 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
318 newfi.setSeries(INHERIT_SERIES);
320 FontShape newshp = newfi.shape();
321 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
322 newshp == oldfi.shape())
323 newfi.setShape(INHERIT_SHAPE);
325 ColorCode newcol = newfi.color();
326 if (newcol != Color_none && newcol != Color_inherit
327 && newcol != Color_ignore && newcol == oldfi.color())
328 newfi.setColor(Color_none);
331 if (newfi.emph() == FONT_TOGGLE)
332 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
333 if (newfi.underbar() == FONT_TOGGLE)
334 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
335 if (newfi.strikeout() == FONT_TOGGLE)
336 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
337 if (newfi.uuline() == FONT_TOGGLE)
338 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
339 if (newfi.uwave() == FONT_TOGGLE)
340 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
341 if (newfi.noun() == FONT_TOGGLE)
342 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
343 if (newfi.number() == FONT_TOGGLE)
344 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
347 setFont(cur.bv(), cur.selectionBegin().top(),
348 cur.selectionEnd().top(), newfont);
352 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
353 CursorSlice const & end, Font const & font)
355 Buffer const & buffer = bv.buffer();
357 // Don't use forwardChar here as ditend might have
358 // pos() == lastpos() and forwardChar would miss it.
359 // Can't use forwardPos either as this descends into
361 Language const * language = buffer.params().language;
362 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
363 if (dit.pos() == dit.lastpos())
365 pit_type const pit = dit.pit();
366 pos_type const pos = dit.pos();
367 Inset * inset = pars_[pit].getInset(pos);
368 if (inset && inset->resetFontEdit()) {
369 // We need to propagate the font change to all
370 // text cells of the inset (bugs 1973, 6919).
371 setInsetFont(bv, pit, pos, font);
373 TextMetrics const & tm = bv.textMetrics(this);
374 Font f = tm.displayFont(pit, pos);
375 f.update(font, language);
376 setCharFont(pit, pos, f, tm.font_);
377 // font change may change language...
378 // spell checker has to know that
379 pars_[pit].requestSpellCheck(pos);
384 bool Text::cursorTop(Cursor & cur)
386 LBUFERR(this == cur.text());
387 return setCursor(cur, 0, 0);
391 bool Text::cursorBottom(Cursor & cur)
393 LBUFERR(this == cur.text());
394 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
398 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
400 LBUFERR(this == cur.text());
401 // If the mask is completely neutral, tell user
402 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
403 // Could only happen with user style
404 cur.message(_("No font change defined."));
408 // Try implicit word selection
409 // If there is a change in the language the implicit word selection
411 CursorSlice const resetCursor = cur.top();
412 bool const implicitSelection =
413 font.language() == ignore_language
414 && font.fontInfo().number() == FONT_IGNORE
415 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
418 setFont(cur, font, toggleall);
420 // Implicit selections are cleared afterwards
421 // and cursor is set to the original position.
422 if (implicitSelection) {
423 cur.clearSelection();
424 cur.top() = resetCursor;
430 docstring Text::getStringToIndex(Cursor const & cur)
432 LBUFERR(this == cur.text());
435 return cur.selectionAsString(false);
437 // Try implicit word selection. If there is a change
438 // in the language the implicit word selection is
441 selectWord(tmpcur, PREVIOUS_WORD);
443 if (!tmpcur.selection())
444 cur.message(_("Nothing to index!"));
445 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
446 cur.message(_("Cannot index more than one paragraph!"));
448 return tmpcur.selectionAsString(false);
454 void Text::setLabelWidthStringToSequence(Cursor const & cur,
458 // Find first of same layout in sequence
459 while (!isFirstInSequence(c.pit())) {
460 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
463 // now apply label width string to every par
465 depth_type const depth = c.paragraph().getDepth();
466 Layout const & layout = c.paragraph().layout();
467 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
468 while (c.paragraph().getDepth() > depth) {
470 if (c.pit() > c.lastpit())
473 if (c.paragraph().getDepth() < depth)
475 if (c.paragraph().layout() != layout)
478 c.paragraph().setLabelWidthString(s);
483 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
488 string const argument = to_utf8(arg);
489 depth_type priordepth = -1;
492 c.setCursor(cur.selectionBegin());
493 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
494 Paragraph & par = c.paragraph();
495 ParagraphParameters params = par.params();
496 params.read(argument, merge);
497 // Changes to label width string apply to all paragraphs
498 // with same layout in a sequence.
499 // Do this only once for a selected range of paragraphs
500 // of the same layout and depth.
502 par.params().apply(params, par.layout());
503 if (par.getDepth() != priordepth || par.layout() != priorlayout)
504 setLabelWidthStringToSequence(c, params.labelWidthString());
505 priordepth = par.getDepth();
506 priorlayout = par.layout();
511 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
515 depth_type priordepth = -1;
518 c.setCursor(cur.selectionBegin());
519 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
520 Paragraph & par = c.paragraph();
521 // Changes to label width string apply to all paragraphs
522 // with same layout in a sequence.
523 // Do this only once for a selected range of paragraphs
524 // of the same layout and depth.
526 par.params().apply(p, par.layout());
527 if (par.getDepth() != priordepth || par.layout() != priorlayout)
528 setLabelWidthStringToSequence(c,
529 par.params().labelWidthString());
530 priordepth = par.getDepth();
531 priorlayout = par.layout();
536 // this really should just insert the inset and not move the cursor.
537 void Text::insertInset(Cursor & cur, Inset * inset)
539 LBUFERR(this == cur.text());
541 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
542 Change(cur.buffer()->params().track_changes
543 ? Change::INSERTED : Change::UNCHANGED));
547 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
548 bool setfont, bool boundary)
550 TextMetrics const & tm = cur.bv().textMetrics(this);
551 bool const update_needed = !tm.contains(par);
553 setCursorIntern(cur, par, pos, setfont, boundary);
554 return cur.bv().checkDepm(cur, old) || update_needed;
558 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
560 LASSERT(par != int(paragraphs().size()), return);
564 // now some strict checking
565 Paragraph & para = getPar(par);
567 // None of these should happen, but we're scaredy-cats
569 LYXERR0("Don't like -1!");
573 if (pos > para.size()) {
574 LYXERR0("Don't like 1, pos: " << pos
575 << " size: " << para.size()
582 void Text::setCursorIntern(Cursor & cur,
583 pit_type par, pos_type pos, bool setfont, bool boundary)
585 LBUFERR(this == cur.text());
586 cur.boundary(boundary);
587 setCursor(cur.top(), par, pos);
589 cur.setCurrentFont();
593 bool Text::checkAndActivateInset(Cursor & cur, bool front)
595 if (front && cur.pos() == cur.lastpos())
597 if (!front && cur.pos() == 0)
599 Inset * inset = front ? cur.nextInset() : cur.prevInset();
600 if (!inset || !inset->editable())
602 if (cur.selection() && cur.realAnchor().find(inset) == -1)
605 * Apparently, when entering an inset we are expected to be positioned
606 * *before* it in the containing paragraph, regardless of the direction
607 * from which we are entering. Otherwise, cursor placement goes awry,
608 * and when we exit from the beginning, we'll be placed *after* the
613 inset->edit(cur, front);
618 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
622 if (cur.pos() == cur.lastpos())
624 Paragraph & par = cur.paragraph();
625 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
626 if (!inset || !inset->editable())
628 if (cur.selection() && cur.realAnchor().find(inset) == -1)
630 inset->edit(cur, movingForward,
631 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
636 bool Text::cursorBackward(Cursor & cur)
638 // Tell BufferView to test for FitCursor in any case!
639 cur.screenUpdateFlags(Update::FitCursor);
641 // not at paragraph start?
643 // if on right side of boundary (i.e. not at paragraph end, but line end)
644 // -> skip it, i.e. set boundary to true, i.e. go only logically left
645 // there are some exceptions to ignore this: lineseps, newlines, spaces
647 // some effectless debug code to see the values in the debugger
648 bool bound = cur.boundary();
649 int rowpos = cur.textRow().pos();
651 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
652 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
653 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
655 if (!cur.boundary() &&
656 cur.textRow().pos() == cur.pos() &&
657 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
658 !cur.paragraph().isNewline(cur.pos() - 1) &&
659 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
660 !cur.paragraph().isSeparator(cur.pos() - 1)) {
661 return setCursor(cur, cur.pit(), cur.pos(), true, true);
664 // go left and try to enter inset
665 if (checkAndActivateInset(cur, false))
668 // normal character left
669 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
672 // move to the previous paragraph or do nothing
674 Paragraph & par = getPar(cur.pit() - 1);
675 pos_type lastpos = par.size();
676 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
677 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
679 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
685 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
687 Cursor temp_cur = cur;
688 temp_cur.posVisLeft(skip_inset);
689 if (temp_cur.depth() > cur.depth()) {
693 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
694 true, temp_cur.boundary());
698 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
700 Cursor temp_cur = cur;
701 temp_cur.posVisRight(skip_inset);
702 if (temp_cur.depth() > cur.depth()) {
706 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
707 true, temp_cur.boundary());
711 bool Text::cursorForward(Cursor & cur)
713 // Tell BufferView to test for FitCursor in any case!
714 cur.screenUpdateFlags(Update::FitCursor);
716 // not at paragraph end?
717 if (cur.pos() != cur.lastpos()) {
718 // in front of editable inset, i.e. jump into it?
719 if (checkAndActivateInset(cur, true))
722 TextMetrics const & tm = cur.bv().textMetrics(this);
723 // if left of boundary -> just jump to right side
724 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
725 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
726 return setCursor(cur, cur.pit(), cur.pos(), true, false);
728 // next position is left of boundary,
729 // but go to next line for special cases like space, newline, linesep
731 // some effectless debug code to see the values in the debugger
732 int endpos = cur.textRow().endpos();
733 int lastpos = cur.lastpos();
735 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
736 bool newline = cur.paragraph().isNewline(cur.pos());
737 bool sep = cur.paragraph().isSeparator(cur.pos());
738 if (cur.pos() != cur.lastpos()) {
739 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
740 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
741 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
744 if (cur.textRow().endpos() == cur.pos() + 1) {
745 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
746 cur.pos() + 1 == cur.lastpos() &&
747 cur.pit() != cur.lastpit()) {
748 // move to next paragraph
749 return setCursor(cur, cur.pit() + 1, 0, true, false);
750 } else if (cur.textRow().endpos() != cur.lastpos() &&
751 !cur.paragraph().isNewline(cur.pos()) &&
752 !cur.paragraph().isEnvSeparator(cur.pos()) &&
753 !cur.paragraph().isLineSeparator(cur.pos()) &&
754 !cur.paragraph().isSeparator(cur.pos())) {
755 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
759 // in front of RTL boundary? Stay on this side of the boundary because:
760 // ab|cDDEEFFghi -> abc|DDEEFFghi
761 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
762 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
765 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
768 // move to next paragraph
769 if (cur.pit() != cur.lastpit())
770 return setCursor(cur, cur.pit() + 1, 0, true, false);
775 bool Text::cursorUpParagraph(Cursor & cur)
777 bool updated = false;
779 updated = setCursor(cur, cur.pit(), 0);
780 else if (cur.pit() != 0)
781 updated = setCursor(cur, cur.pit() - 1, 0);
786 bool Text::cursorDownParagraph(Cursor & cur)
788 bool updated = false;
789 if (cur.pit() != cur.lastpit())
790 if (lyxrc.mac_like_cursor_movement)
791 if (cur.pos() == cur.lastpos())
792 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
794 updated = setCursor(cur, cur.pit(), cur.lastpos());
796 updated = setCursor(cur, cur.pit() + 1, 0);
798 updated = setCursor(cur, cur.pit(), cur.lastpos());
803 // fix the cursor `cur' after a characters has been deleted at `where'
804 // position. Called by deleteEmptyParagraphMechanism
805 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
807 // Do nothing if cursor is not in the paragraph where the
808 // deletion occurred,
809 if (cur.pit() != where.pit())
812 // If cursor position is after the deletion place update it
813 if (cur.pos() > where.pos())
816 // Check also if we don't want to set the cursor on a spot behind the
817 // pagragraph because we erased the last character.
818 if (cur.pos() > cur.lastpos())
819 cur.pos() = cur.lastpos();
823 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
824 Cursor & old, bool & need_anchor_change)
826 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
828 Paragraph & oldpar = old.paragraph();
830 // We allow all kinds of "mumbo-jumbo" when freespacing.
831 if (oldpar.isFreeSpacing())
834 /* Ok I'll put some comments here about what is missing.
835 There are still some small problems that can lead to
836 double spaces stored in the document file or space at
837 the beginning of paragraphs(). This happens if you have
838 the cursor between to spaces and then save. Or if you
839 cut and paste and the selection have a space at the
840 beginning and then save right after the paste. (Lgb)
843 // If old.pos() == 0 and old.pos()(1) == LineSeparator
844 // delete the LineSeparator.
847 // If old.pos() == 1 and old.pos()(0) == LineSeparator
848 // delete the LineSeparator.
851 // Find a common inset and the corresponding depth.
853 for (; depth < cur.depth(); ++depth)
854 if (&old.inset() == &cur[depth].inset())
857 // Whether a common inset is found and whether the cursor is still in
858 // the same paragraph (possibly nested).
859 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
860 bool const same_par_pos = depth == cur.depth() - 1 && same_par
861 && old.pos() == cur[depth].pos();
863 // If the chars around the old cursor were spaces, delete one of them.
865 // Only if the cursor has really moved.
867 && old.pos() < oldpar.size()
868 && oldpar.isLineSeparator(old.pos())
869 && oldpar.isLineSeparator(old.pos() - 1)
870 && !oldpar.isDeleted(old.pos() - 1)
871 && !oldpar.isDeleted(old.pos())) {
872 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
873 // FIXME: This will not work anymore when we have multiple views of the same buffer
874 // In this case, we will have to correct also the cursors held by
875 // other bufferviews. It will probably be easier to do that in a more
876 // automated way in CursorSlice code. (JMarc 26/09/2001)
877 // correct all cursor parts
879 fixCursorAfterDelete(cur[depth], old.top());
880 need_anchor_change = true;
886 // only do our magic if we changed paragraph
890 // don't delete anything if this is the ONLY paragraph!
891 if (old.lastpit() == 0)
894 // Do not delete empty paragraphs with keepempty set.
895 if (oldpar.allowEmpty())
898 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
900 old.recordUndo(ATOMIC_UNDO,
901 max(old.pit() - 1, pit_type(0)),
902 min(old.pit() + 1, old.lastpit()));
903 ParagraphList & plist = old.text()->paragraphs();
904 bool const soa = oldpar.params().startOfAppendix();
905 plist.erase(boost::next(plist.begin(), old.pit()));
906 // do not lose start of appendix marker (bug 4212)
907 if (soa && old.pit() < pit_type(plist.size()))
908 plist[old.pit()].params().startOfAppendix(true);
910 // see #warning (FIXME?) above
911 if (cur.depth() >= old.depth()) {
912 CursorSlice & curslice = cur[old.depth() - 1];
913 if (&curslice.inset() == &old.inset()
914 && curslice.pit() > old.pit()) {
916 // since a paragraph has been deleted, all the
917 // insets after `old' have been copied and
918 // their address has changed. Therefore we
919 // need to `regenerate' cur. (JMarc)
920 cur.updateInsets(&(cur.bottom().inset()));
921 need_anchor_change = true;
927 if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
928 need_anchor_change = true;
929 // We return true here because the Paragraph contents changed and
930 // we need a redraw before further action is processed.
938 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
940 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
942 for (pit_type pit = first; pit <= last; ++pit) {
943 Paragraph & par = pars_[pit];
945 // We allow all kinds of "mumbo-jumbo" when freespacing.
946 if (par.isFreeSpacing())
949 for (pos_type pos = 1; pos < par.size(); ++pos) {
950 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
951 && !par.isDeleted(pos - 1)) {
952 if (par.eraseChar(pos - 1, trackChanges)) {
958 // don't delete anything if this is the only remaining paragraph
959 // within the given range. Note: Text::acceptOrRejectChanges()
960 // sets the cursor to 'first' after calling DEPM
964 // don't delete empty paragraphs with keepempty set
965 if (par.allowEmpty())
968 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
969 pars_.erase(boost::next(pars_.begin(), pit));
975 par.stripLeadingSpaces(trackChanges);
980 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
982 cur.recordUndo(ATOMIC_UNDO, first, last);
986 void Text::recUndo(Cursor & cur, pit_type par) const
988 cur.recordUndo(ATOMIC_UNDO, par, par);