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.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "ErrorList.h"
40 #include "Paragraph.h"
41 #include "ParagraphParameters.h"
42 #include "TextClass.h"
43 #include "TextMetrics.h"
45 #include "insets/InsetCollapsible.h"
47 #include "mathed/InsetMathHull.h"
49 #include "support/lassert.h"
50 #include "support/debug.h"
51 #include "support/gettext.h"
52 #include "support/lyxalgo.h"
53 #include "support/textutils.h"
61 bool Text::isMainText() const
63 return &owner_->buffer().text() == this;
67 // Note that this is supposed to return a fully realized font.
68 FontInfo Text::layoutFont(pit_type const pit) const
70 Layout const & layout = pars_[pit].layout();
72 if (!pars_[pit].getDepth()) {
73 FontInfo lf = layout.resfont;
74 // In case the default family has been customized
75 if (layout.font.family() == INHERIT_FAMILY)
76 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
77 FontInfo icf = owner_->getLayout().font();
82 FontInfo font = layout.font;
83 // Realize with the fonts of lesser depth.
84 //font.realize(outerFont(pit));
85 font.realize(owner_->buffer().params().getFont().fontInfo());
91 // Note that this is supposed to return a fully realized font.
92 FontInfo Text::labelFont(Paragraph const & par) const
94 Buffer const & buffer = owner_->buffer();
95 Layout const & layout = par.layout();
97 if (!par.getDepth()) {
98 FontInfo lf = layout.reslabelfont;
99 // In case the default family has been customized
100 if (layout.labelfont.family() == INHERIT_FAMILY)
101 lf.setFamily(buffer.params().getFont().fontInfo().family());
105 FontInfo font = layout.labelfont;
106 // Realize with the fonts of lesser depth.
107 font.realize(buffer.params().getFont().fontInfo());
113 void Text::setCharFont(pit_type pit,
114 pos_type pos, Font const & fnt, Font const & display_font)
116 Buffer const & buffer = owner_->buffer();
118 Layout const & layout = pars_[pit].layout();
120 // Get concrete layout font to reduce against
123 if (pos < pars_[pit].beginOfBody())
124 layoutfont = layout.labelfont;
126 layoutfont = layout.font;
128 // Realize against environment font information
129 if (pars_[pit].getDepth()) {
131 while (!layoutfont.resolved() &&
132 tp != pit_type(paragraphs().size()) &&
133 pars_[tp].getDepth()) {
135 if (tp != pit_type(paragraphs().size()))
136 layoutfont.realize(pars_[tp].layout().font);
140 // Inside inset, apply the inset's font attributes if any
143 layoutfont.realize(display_font.fontInfo());
145 layoutfont.realize(buffer.params().getFont().fontInfo());
147 // Now, reduce font against full layout font
148 font.fontInfo().reduce(layoutfont);
150 pars_[pit].setFont(pos, font);
154 void Text::setInsetFont(BufferView const & bv, pit_type pit,
155 pos_type pos, Font const & font)
157 Inset * const inset = pars_[pit].getInset(pos);
158 LASSERT(inset && inset->resetFontEdit(), return);
160 CursorSlice::idx_type endidx = inset->nargs();
161 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
162 Text * text = cs.text();
164 // last position of the cell
165 CursorSlice cellend = cs;
166 cellend.pit() = cellend.lastpit();
167 cellend.pos() = cellend.lastpos();
168 text->setFont(bv, cs, cellend, font);
174 void Text::setLayout(pit_type start, pit_type end,
175 docstring const & layout)
177 LASSERT(start != end, return);
179 Buffer const & buffer = owner_->buffer();
180 BufferParams const & bp = buffer.params();
181 Layout const & lyxlayout = bp.documentClass()[layout];
183 for (pit_type pit = start; pit != end; ++pit) {
184 Paragraph & par = pars_[pit];
185 par.applyLayout(lyxlayout);
186 if (lyxlayout.margintype == MARGIN_MANUAL)
187 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
192 // set layout over selection and make a total rebreak of those paragraphs
193 void Text::setLayout(Cursor & cur, docstring const & layout)
195 LBUFERR(this == cur.text());
197 pit_type start = cur.selBegin().pit();
198 pit_type end = cur.selEnd().pit() + 1;
199 cur.recordUndoSelection();
200 setLayout(start, end, layout);
201 cur.setCurrentFont();
202 cur.forceBufferUpdate();
206 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
207 Paragraph const & par, int max_depth)
209 int const depth = par.params().depth();
210 if (type == Text::INC_DEPTH && depth < max_depth)
212 if (type == Text::DEC_DEPTH && depth > 0)
218 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
220 LBUFERR(this == cur.text());
221 // this happens when selecting several cells in tabular (bug 2630)
222 if (cur.selBegin().idx() != cur.selEnd().idx())
225 pit_type const beg = cur.selBegin().pit();
226 pit_type const end = cur.selEnd().pit() + 1;
227 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
229 for (pit_type pit = beg; pit != end; ++pit) {
230 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
232 max_depth = pars_[pit].getMaxDepthAfter();
238 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
240 LBUFERR(this == cur.text());
241 pit_type const beg = cur.selBegin().pit();
242 pit_type const end = cur.selEnd().pit() + 1;
243 cur.recordUndoSelection();
244 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
246 for (pit_type pit = beg; pit != end; ++pit) {
247 Paragraph & par = pars_[pit];
248 if (lyx::changeDepthAllowed(type, par, max_depth)) {
249 int const depth = par.params().depth();
250 if (type == INC_DEPTH)
251 par.params().depth(depth + 1);
253 par.params().depth(depth - 1);
255 max_depth = par.getMaxDepthAfter();
257 // this handles the counter labels, and also fixes up
258 // depth values for follow-on (child) paragraphs
259 cur.forceBufferUpdate();
263 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
265 LASSERT(this == cur.text(), return);
267 // If there is a selection, record undo before the cursor font is changed.
269 cur.recordUndoSelection();
271 // Set the current_font
272 // Determine basis font
274 pit_type pit = cur.pit();
275 if (cur.pos() < pars_[pit].beginOfBody())
276 layoutfont = labelFont(pars_[pit]);
278 layoutfont = layoutFont(pit);
280 // Update current font
281 cur.real_current_font.update(font,
282 cur.buffer()->params().language,
285 // Reduce to implicit settings
286 cur.current_font = cur.real_current_font;
287 cur.current_font.fontInfo().reduce(layoutfont);
288 // And resolve it completely
289 cur.real_current_font.fontInfo().realize(layoutfont);
291 // if there is no selection that's all we need to do
292 if (!cur.selection())
295 // Ok, we have a selection.
299 // Toggling behaves as follows: We check the first character of the
300 // selection. If it's (say) got EMPH on, then we set to off; if off,
301 // then to on. With families and the like, we set it to INHERIT, if
302 // we already have it.
303 CursorSlice const & sl = cur.selBegin();
304 Text const & text = *sl.text();
305 Paragraph const & par = text.getPar(sl.pit());
307 // get font at the position
308 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
309 text.outerFont(sl.pit()));
310 FontInfo const & oldfi = oldfont.fontInfo();
312 FontInfo & newfi = newfont.fontInfo();
314 FontFamily newfam = newfi.family();
315 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
316 newfam == oldfi.family())
317 newfi.setFamily(INHERIT_FAMILY);
319 FontSeries newser = newfi.series();
320 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
321 newfi.setSeries(INHERIT_SERIES);
323 FontShape newshp = newfi.shape();
324 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
325 newshp == oldfi.shape())
326 newfi.setShape(INHERIT_SHAPE);
328 ColorCode newcol = newfi.color();
329 if (newcol != Color_none && newcol != Color_inherit
330 && newcol != Color_ignore && newcol == oldfi.color())
331 newfi.setColor(Color_none);
334 if (newfi.emph() == FONT_TOGGLE)
335 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
336 if (newfi.underbar() == FONT_TOGGLE)
337 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
338 if (newfi.strikeout() == FONT_TOGGLE)
339 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.xout() == FONT_TOGGLE)
341 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.uuline() == FONT_TOGGLE)
343 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.uwave() == FONT_TOGGLE)
345 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.noun() == FONT_TOGGLE)
347 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
348 if (newfi.number() == FONT_TOGGLE)
349 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
352 setFont(cur.bv(), cur.selectionBegin().top(),
353 cur.selectionEnd().top(), newfont);
357 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
358 CursorSlice const & end, Font const & font)
360 Buffer const & buffer = bv.buffer();
362 // Don't use forwardChar here as ditend might have
363 // pos() == lastpos() and forwardChar would miss it.
364 // Can't use forwardPos either as this descends into
366 Language const * language = buffer.params().language;
367 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
368 if (dit.pos() == dit.lastpos())
370 pit_type const pit = dit.pit();
371 pos_type const pos = dit.pos();
372 Inset * inset = pars_[pit].getInset(pos);
373 if (inset && inset->resetFontEdit()) {
374 // We need to propagate the font change to all
375 // text cells of the inset (bugs 1973, 6919).
376 setInsetFont(bv, pit, pos, font);
378 TextMetrics const & tm = bv.textMetrics(this);
379 Font f = tm.displayFont(pit, pos);
380 f.update(font, language);
381 setCharFont(pit, pos, f, tm.font_);
382 // font change may change language...
383 // spell checker has to know that
384 pars_[pit].requestSpellCheck(pos);
389 bool Text::cursorTop(Cursor & cur)
391 LBUFERR(this == cur.text());
392 return setCursor(cur, 0, 0);
396 bool Text::cursorBottom(Cursor & cur)
398 LBUFERR(this == cur.text());
399 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
403 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
405 LBUFERR(this == cur.text());
406 // If the mask is completely neutral, tell user
407 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
408 // Could only happen with user style
409 cur.message(_("No font change defined."));
413 // Try implicit word selection
414 // If there is a change in the language the implicit word selection
416 CursorSlice const resetCursor = cur.top();
417 bool const implicitSelection =
418 font.language() == ignore_language
419 && font.fontInfo().number() == FONT_IGNORE
420 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
423 setFont(cur, font, toggleall);
425 // Implicit selections are cleared afterwards
426 // and cursor is set to the original position.
427 if (implicitSelection) {
428 cur.clearSelection();
429 cur.top() = resetCursor;
435 docstring Text::getStringForDialog(Cursor & cur)
437 LBUFERR(this == cur.text());
440 return cur.selectionAsString(false);
442 // Try implicit word selection. If there is a change
443 // in the language the implicit word selection is
445 selectWordWhenUnderCursor(cur, WHOLE_WORD);
446 docstring const & retval = cur.selectionAsString(false);
447 cur.clearSelection();
452 void Text::setLabelWidthStringToSequence(Cursor const & cur,
456 // Find first of same layout in sequence
457 while (!isFirstInSequence(c.pit())) {
458 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
461 // now apply label width string to every par
463 depth_type const depth = c.paragraph().getDepth();
464 Layout const & layout = c.paragraph().layout();
465 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
466 while (c.paragraph().getDepth() > depth) {
468 if (c.pit() > c.lastpit())
471 if (c.paragraph().getDepth() < depth)
473 if (c.paragraph().layout() != layout)
476 c.paragraph().setLabelWidthString(s);
481 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
486 string const argument = to_utf8(arg);
487 depth_type priordepth = -1;
490 c.setCursor(cur.selectionBegin());
491 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
492 Paragraph & par = c.paragraph();
493 ParagraphParameters params = par.params();
494 params.read(argument, merge);
495 // Changes to label width string apply to all paragraphs
496 // with same layout in a sequence.
497 // Do this only once for a selected range of paragraphs
498 // of the same layout and depth.
500 par.params().apply(params, par.layout());
501 if (par.getDepth() != priordepth || par.layout() != priorlayout)
502 setLabelWidthStringToSequence(c, params.labelWidthString());
503 priordepth = par.getDepth();
504 priorlayout = par.layout();
509 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
513 depth_type priordepth = -1;
516 c.setCursor(cur.selectionBegin());
517 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
518 Paragraph & par = c.paragraph();
519 // Changes to label width string apply to all paragraphs
520 // with same layout in a sequence.
521 // Do this only once for a selected range of paragraphs
522 // of the same layout and depth.
524 par.params().apply(p, par.layout());
525 if (par.getDepth() != priordepth || par.layout() != priorlayout)
526 setLabelWidthStringToSequence(c,
527 par.params().labelWidthString());
528 priordepth = par.getDepth();
529 priorlayout = par.layout();
534 // this really should just insert the inset and not move the cursor.
535 void Text::insertInset(Cursor & cur, Inset * inset)
537 LBUFERR(this == cur.text());
539 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
540 Change(cur.buffer()->params().track_changes
541 ? Change::INSERTED : Change::UNCHANGED));
545 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
546 bool setfont, bool boundary)
548 TextMetrics const & tm = cur.bv().textMetrics(this);
549 bool const update_needed = !tm.contains(pit);
551 setCursorIntern(cur, pit, pos, setfont, boundary);
552 return cur.bv().checkDepm(cur, old) || update_needed;
556 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
557 bool setfont, bool boundary)
559 LBUFERR(this == cur.text());
560 cur.boundary(boundary);
561 cur.top().setPitPos(pit, pos);
563 cur.setCurrentFont();
567 bool Text::checkAndActivateInset(Cursor & cur, bool front)
569 if (front && cur.pos() == cur.lastpos())
571 if (!front && cur.pos() == 0)
573 Inset * inset = front ? cur.nextInset() : cur.prevInset();
574 if (!inset || !inset->editable())
576 if (cur.selection() && cur.realAnchor().find(inset) == -1)
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);
588 cur.setCurrentFont();
594 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())
604 if (cur.selection() && cur.realAnchor().find(inset) == -1)
606 inset->edit(cur, movingForward,
607 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
608 cur.setCurrentFont();
614 bool Text::cursorBackward(Cursor & cur)
616 // Tell BufferView to test for FitCursor in any case!
617 cur.screenUpdateFlags(Update::FitCursor);
619 // not at paragraph start?
621 // if on right side of boundary (i.e. not at paragraph end, but line end)
622 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623 // there are some exceptions to ignore this: lineseps, newlines, spaces
625 // some effectless debug code to see the values in the debugger
626 bool bound = cur.boundary();
627 int rowpos = cur.textRow().pos();
629 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
633 if (!cur.boundary() &&
634 cur.textRow().pos() == cur.pos() &&
635 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636 !cur.paragraph().isNewline(cur.pos() - 1) &&
637 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
638 !cur.paragraph().isSeparator(cur.pos() - 1)) {
639 return setCursor(cur, cur.pit(), cur.pos(), true, true);
642 // go left and try to enter inset
643 if (checkAndActivateInset(cur, false))
646 // normal character left
647 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
650 // move to the previous paragraph or do nothing
652 Paragraph & par = getPar(cur.pit() - 1);
653 pos_type lastpos = par.size();
654 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
655 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
657 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
663 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
665 Cursor temp_cur = cur;
666 temp_cur.posVisLeft(skip_inset);
667 if (temp_cur.depth() > cur.depth()) {
671 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
672 true, temp_cur.boundary());
676 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
678 Cursor temp_cur = cur;
679 temp_cur.posVisRight(skip_inset);
680 if (temp_cur.depth() > cur.depth()) {
684 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
685 true, temp_cur.boundary());
689 bool Text::cursorForward(Cursor & cur)
691 // Tell BufferView to test for FitCursor in any case!
692 cur.screenUpdateFlags(Update::FitCursor);
694 // not at paragraph end?
695 if (cur.pos() != cur.lastpos()) {
696 // in front of editable inset, i.e. jump into it?
697 if (checkAndActivateInset(cur, true))
700 TextMetrics const & tm = cur.bv().textMetrics(this);
701 // if left of boundary -> just jump to right side
702 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
703 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
704 return setCursor(cur, cur.pit(), cur.pos(), true, false);
706 // next position is left of boundary,
707 // but go to next line for special cases like space, newline, linesep
709 // some effectless debug code to see the values in the debugger
710 int endpos = cur.textRow().endpos();
711 int lastpos = cur.lastpos();
713 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
714 bool newline = cur.paragraph().isNewline(cur.pos());
715 bool sep = cur.paragraph().isSeparator(cur.pos());
716 if (cur.pos() != cur.lastpos()) {
717 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
718 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
719 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
722 if (cur.textRow().endpos() == cur.pos() + 1) {
723 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
724 cur.pos() + 1 == cur.lastpos() &&
725 cur.pit() != cur.lastpit()) {
726 // move to next paragraph
727 return setCursor(cur, cur.pit() + 1, 0, true, false);
728 } else if (cur.textRow().endpos() != cur.lastpos() &&
729 !cur.paragraph().isNewline(cur.pos()) &&
730 !cur.paragraph().isEnvSeparator(cur.pos()) &&
731 !cur.paragraph().isLineSeparator(cur.pos()) &&
732 !cur.paragraph().isSeparator(cur.pos())) {
733 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
737 // in front of RTL boundary? Stay on this side of the boundary because:
738 // ab|cDDEEFFghi -> abc|DDEEFFghi
739 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
740 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
743 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
746 // move to next paragraph
747 if (cur.pit() != cur.lastpit())
748 return setCursor(cur, cur.pit() + 1, 0, true, false);
753 bool Text::cursorUpParagraph(Cursor & cur)
755 bool updated = false;
757 updated = setCursor(cur, cur.pit(), 0);
758 else if (cur.pit() != 0)
759 updated = setCursor(cur, cur.pit() - 1, 0);
764 bool Text::cursorDownParagraph(Cursor & cur)
766 bool updated = false;
767 if (cur.pit() != cur.lastpit())
768 if (lyxrc.mac_like_cursor_movement)
769 if (cur.pos() == cur.lastpos())
770 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
772 updated = setCursor(cur, cur.pit(), cur.lastpos());
774 updated = setCursor(cur, cur.pit() + 1, 0);
776 updated = setCursor(cur, cur.pit(), cur.lastpos());
781 // fix the cursor `cur' after a characters has been deleted at `where'
782 // position. Called by deleteEmptyParagraphMechanism
783 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
785 // Do nothing if cursor is not in the paragraph where the
786 // deletion occurred,
787 if (cur.pit() != where.pit())
790 // If cursor position is after the deletion place update it
791 if (cur.pos() > where.pos())
794 // Check also if we don't want to set the cursor on a spot behind the
795 // pagragraph because we erased the last character.
796 if (cur.pos() > cur.lastpos())
797 cur.pos() = cur.lastpos();
801 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
802 Cursor & old, bool & need_anchor_change)
804 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
806 Paragraph & oldpar = old.paragraph();
808 // We allow all kinds of "mumbo-jumbo" when freespacing.
809 if (oldpar.isFreeSpacing())
812 /* Ok I'll put some comments here about what is missing.
813 There are still some small problems that can lead to
814 double spaces stored in the document file or space at
815 the beginning of paragraphs(). This happens if you have
816 the cursor between to spaces and then save. Or if you
817 cut and paste and the selection have a space at the
818 beginning and then save right after the paste. (Lgb)
821 // If old.pos() == 0 and old.pos()(1) == LineSeparator
822 // delete the LineSeparator.
825 // If old.pos() == 1 and old.pos()(0) == LineSeparator
826 // delete the LineSeparator.
829 // Find a common inset and the corresponding depth.
831 for (; depth < cur.depth(); ++depth)
832 if (&old.inset() == &cur[depth].inset())
835 // Whether a common inset is found and whether the cursor is still in
836 // the same paragraph (possibly nested).
837 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
838 bool const same_par_pos = depth == cur.depth() - 1 && same_par
839 && old.pos() == cur[depth].pos();
841 // If the chars around the old cursor were spaces, delete one of them.
843 // Only if the cursor has really moved.
845 && old.pos() < oldpar.size()
846 && oldpar.isLineSeparator(old.pos())
847 && oldpar.isLineSeparator(old.pos() - 1)
848 && !oldpar.isDeleted(old.pos() - 1)
849 && !oldpar.isDeleted(old.pos())) {
850 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
851 // FIXME: This will not work anymore when we have multiple views of the same buffer
852 // In this case, we will have to correct also the cursors held by
853 // other bufferviews. It will probably be easier to do that in a more
854 // automated way in CursorSlice code. (JMarc 26/09/2001)
855 // correct all cursor parts
857 fixCursorAfterDelete(cur[depth], old.top());
858 need_anchor_change = true;
864 // only do our magic if we changed paragraph
868 // don't delete anything if this is the ONLY paragraph!
869 if (old.lastpit() == 0)
872 // Do not delete empty paragraphs with keepempty set.
873 if (oldpar.allowEmpty())
876 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
878 old.recordUndo(max(old.pit() - 1, pit_type(0)),
879 min(old.pit() + 1, old.lastpit()));
880 ParagraphList & plist = old.text()->paragraphs();
881 bool const soa = oldpar.params().startOfAppendix();
882 plist.erase(lyx::next(plist.begin(), old.pit()));
883 // do not lose start of appendix marker (bug 4212)
884 if (soa && old.pit() < pit_type(plist.size()))
885 plist[old.pit()].params().startOfAppendix(true);
887 // see #warning (FIXME?) above
888 if (cur.depth() >= old.depth()) {
889 CursorSlice & curslice = cur[old.depth() - 1];
890 if (&curslice.inset() == &old.inset()
891 && curslice.pit() > old.pit()) {
893 // since a paragraph has been deleted, all the
894 // insets after `old' have been copied and
895 // their address has changed. Therefore we
896 // need to `regenerate' cur. (JMarc)
897 cur.updateInsets(&(cur.bottom().inset()));
898 need_anchor_change = true;
904 if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
905 need_anchor_change = true;
906 // We return true here because the Paragraph contents changed and
907 // we need a redraw before further action is processed.
915 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
917 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
919 for (pit_type pit = first; pit <= last; ++pit) {
920 Paragraph & par = pars_[pit];
922 // We allow all kinds of "mumbo-jumbo" when freespacing.
923 if (par.isFreeSpacing())
926 for (pos_type pos = 1; pos < par.size(); ++pos) {
927 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
928 && !par.isDeleted(pos - 1)) {
929 if (par.eraseChar(pos - 1, trackChanges)) {
935 // don't delete anything if this is the only remaining paragraph
936 // within the given range. Note: Text::acceptOrRejectChanges()
937 // sets the cursor to 'first' after calling DEPM
941 // don't delete empty paragraphs with keepempty set
942 if (par.allowEmpty())
945 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
946 pars_.erase(lyx::next(pars_.begin(), pit));
952 par.stripLeadingSpaces(trackChanges);