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 // Is this a separating paragraph? If so,
186 // this needs to be standard layout
187 bool const is_separator = par.size() == 1
188 && par.isEnvSeparator(0);
189 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
190 if (lyxlayout.margintype == MARGIN_MANUAL)
191 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
196 // set layout over selection and make a total rebreak of those paragraphs
197 void Text::setLayout(Cursor & cur, docstring const & layout)
199 LBUFERR(this == cur.text());
201 pit_type start = cur.selBegin().pit();
202 pit_type end = cur.selEnd().pit() + 1;
203 cur.recordUndoSelection();
204 setLayout(start, end, layout);
205 cur.setCurrentFont();
206 cur.forceBufferUpdate();
210 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
211 Paragraph const & par, int max_depth)
213 int const depth = par.params().depth();
214 if (type == Text::INC_DEPTH && depth < max_depth)
216 if (type == Text::DEC_DEPTH && depth > 0)
222 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
224 LBUFERR(this == cur.text());
225 // this happens when selecting several cells in tabular (bug 2630)
226 if (cur.selBegin().idx() != cur.selEnd().idx())
229 pit_type const beg = cur.selBegin().pit();
230 pit_type const end = cur.selEnd().pit() + 1;
231 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
233 for (pit_type pit = beg; pit != end; ++pit) {
234 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
236 max_depth = pars_[pit].getMaxDepthAfter();
242 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
244 LBUFERR(this == cur.text());
245 pit_type const beg = cur.selBegin().pit();
246 pit_type const end = cur.selEnd().pit() + 1;
247 cur.recordUndoSelection();
248 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
250 for (pit_type pit = beg; pit != end; ++pit) {
251 Paragraph & par = pars_[pit];
252 if (lyx::changeDepthAllowed(type, par, max_depth)) {
253 int const depth = par.params().depth();
254 if (type == INC_DEPTH)
255 par.params().depth(depth + 1);
257 par.params().depth(depth - 1);
259 max_depth = par.getMaxDepthAfter();
261 // this handles the counter labels, and also fixes up
262 // depth values for follow-on (child) paragraphs
263 cur.forceBufferUpdate();
267 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
269 LASSERT(this == cur.text(), return);
271 // If there is a selection, record undo before the cursor font is changed.
273 cur.recordUndoSelection();
275 // Set the current_font
276 // Determine basis font
278 pit_type pit = cur.pit();
279 if (cur.pos() < pars_[pit].beginOfBody())
280 layoutfont = labelFont(pars_[pit]);
282 layoutfont = layoutFont(pit);
284 // Update current font
285 cur.real_current_font.update(font,
286 cur.buffer()->params().language,
289 // Reduce to implicit settings
290 cur.current_font = cur.real_current_font;
291 cur.current_font.fontInfo().reduce(layoutfont);
292 // And resolve it completely
293 cur.real_current_font.fontInfo().realize(layoutfont);
295 // if there is no selection that's all we need to do
296 if (!cur.selection())
299 // Ok, we have a selection.
303 // Toggling behaves as follows: We check the first character of the
304 // selection. If it's (say) got EMPH on, then we set to off; if off,
305 // then to on. With families and the like, we set it to INHERIT, if
306 // we already have it.
307 CursorSlice const & sl = cur.selBegin();
308 Text const & text = *sl.text();
309 Paragraph const & par = text.getPar(sl.pit());
311 // get font at the position
312 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
313 text.outerFont(sl.pit()));
314 FontInfo const & oldfi = oldfont.fontInfo();
316 FontInfo & newfi = newfont.fontInfo();
318 FontFamily newfam = newfi.family();
319 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
320 newfam == oldfi.family())
321 newfi.setFamily(INHERIT_FAMILY);
323 FontSeries newser = newfi.series();
324 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
325 newfi.setSeries(INHERIT_SERIES);
327 FontShape newshp = newfi.shape();
328 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
329 newshp == oldfi.shape())
330 newfi.setShape(INHERIT_SHAPE);
332 ColorCode newcol = newfi.color();
333 if (newcol != Color_none && newcol != Color_inherit
334 && newcol != Color_ignore && newcol == oldfi.color())
335 newfi.setColor(Color_none);
338 if (newfi.emph() == FONT_TOGGLE)
339 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
340 if (newfi.underbar() == FONT_TOGGLE)
341 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
342 if (newfi.strikeout() == FONT_TOGGLE)
343 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
344 if (newfi.xout() == FONT_TOGGLE)
345 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
346 if (newfi.uuline() == FONT_TOGGLE)
347 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
348 if (newfi.uwave() == FONT_TOGGLE)
349 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
350 if (newfi.noun() == FONT_TOGGLE)
351 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
352 if (newfi.number() == FONT_TOGGLE)
353 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
356 setFont(cur.bv(), cur.selectionBegin().top(),
357 cur.selectionEnd().top(), newfont);
361 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
362 CursorSlice const & end, Font const & font)
364 Buffer const & buffer = bv.buffer();
366 // Don't use forwardChar here as ditend might have
367 // pos() == lastpos() and forwardChar would miss it.
368 // Can't use forwardPos either as this descends into
370 Language const * language = buffer.params().language;
371 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
372 if (dit.pos() == dit.lastpos())
374 pit_type const pit = dit.pit();
375 pos_type const pos = dit.pos();
376 Inset * inset = pars_[pit].getInset(pos);
377 if (inset && inset->resetFontEdit()) {
378 // We need to propagate the font change to all
379 // text cells of the inset (bugs 1973, 6919).
380 setInsetFont(bv, pit, pos, font);
382 TextMetrics const & tm = bv.textMetrics(this);
383 Font f = tm.displayFont(pit, pos);
384 f.update(font, language);
385 setCharFont(pit, pos, f, tm.font_);
386 // font change may change language...
387 // spell checker has to know that
388 pars_[pit].requestSpellCheck(pos);
393 bool Text::cursorTop(Cursor & cur)
395 LBUFERR(this == cur.text());
396 return setCursor(cur, 0, 0);
400 bool Text::cursorBottom(Cursor & cur)
402 LBUFERR(this == cur.text());
403 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
407 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
409 LBUFERR(this == cur.text());
410 // If the mask is completely neutral, tell user
411 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
412 // Could only happen with user style
413 cur.message(_("No font change defined."));
417 // Try implicit word selection
418 // If there is a change in the language the implicit word selection
420 CursorSlice const resetCursor = cur.top();
421 bool const implicitSelection =
422 font.language() == ignore_language
423 && font.fontInfo().number() == FONT_IGNORE
424 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
427 setFont(cur, font, toggleall);
429 // Implicit selections are cleared afterwards
430 // and cursor is set to the original position.
431 if (implicitSelection) {
432 cur.clearSelection();
433 cur.top() = resetCursor;
439 docstring Text::getStringToIndex(Cursor const & cur)
441 LBUFERR(this == cur.text());
444 return cur.selectionAsString(false);
446 // Try implicit word selection. If there is a change
447 // in the language the implicit word selection is
450 selectWord(tmpcur, PREVIOUS_WORD);
452 if (!tmpcur.selection())
453 cur.message(_("Nothing to index!"));
454 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
455 cur.message(_("Cannot index more than one paragraph!"));
457 return tmpcur.selectionAsString(false);
463 void Text::setLabelWidthStringToSequence(Cursor const & cur,
467 // Find first of same layout in sequence
468 while (!isFirstInSequence(c.pit())) {
469 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
472 // now apply label width string to every par
474 depth_type const depth = c.paragraph().getDepth();
475 Layout const & layout = c.paragraph().layout();
476 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
477 while (c.paragraph().getDepth() > depth) {
479 if (c.pit() > c.lastpit())
482 if (c.paragraph().getDepth() < depth)
484 if (c.paragraph().layout() != layout)
487 c.paragraph().setLabelWidthString(s);
492 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
497 string const argument = to_utf8(arg);
498 depth_type priordepth = -1;
501 c.setCursor(cur.selectionBegin());
502 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
503 Paragraph & par = c.paragraph();
504 ParagraphParameters params = par.params();
505 params.read(argument, merge);
506 // Changes to label width string apply to all paragraphs
507 // with same layout in a sequence.
508 // Do this only once for a selected range of paragraphs
509 // of the same layout and depth.
511 par.params().apply(params, par.layout());
512 if (par.getDepth() != priordepth || par.layout() != priorlayout)
513 setLabelWidthStringToSequence(c, params.labelWidthString());
514 priordepth = par.getDepth();
515 priorlayout = par.layout();
520 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
524 depth_type priordepth = -1;
527 c.setCursor(cur.selectionBegin());
528 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
529 Paragraph & par = c.paragraph();
530 // Changes to label width string apply to all paragraphs
531 // with same layout in a sequence.
532 // Do this only once for a selected range of paragraphs
533 // of the same layout and depth.
535 par.params().apply(p, par.layout());
536 if (par.getDepth() != priordepth || par.layout() != priorlayout)
537 setLabelWidthStringToSequence(c,
538 par.params().labelWidthString());
539 priordepth = par.getDepth();
540 priorlayout = par.layout();
545 // this really should just insert the inset and not move the cursor.
546 void Text::insertInset(Cursor & cur, Inset * inset)
548 LBUFERR(this == cur.text());
550 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
551 Change(cur.buffer()->params().track_changes
552 ? Change::INSERTED : Change::UNCHANGED));
556 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
557 bool setfont, bool boundary)
559 TextMetrics const & tm = cur.bv().textMetrics(this);
560 bool const update_needed = !tm.contains(pit);
562 setCursorIntern(cur, pit, pos, setfont, boundary);
563 return cur.bv().checkDepm(cur, old) || update_needed;
567 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
568 bool setfont, bool boundary)
570 LBUFERR(this == cur.text());
571 cur.boundary(boundary);
572 cur.top().setPitPos(pit, pos);
574 cur.setCurrentFont();
578 bool Text::checkAndActivateInset(Cursor & cur, bool front)
580 if (front && cur.pos() == cur.lastpos())
582 if (!front && cur.pos() == 0)
584 Inset * inset = front ? cur.nextInset() : cur.prevInset();
585 if (!inset || !inset->editable())
587 if (cur.selection() && cur.realAnchor().find(inset) == -1)
590 * Apparently, when entering an inset we are expected to be positioned
591 * *before* it in the containing paragraph, regardless of the direction
592 * from which we are entering. Otherwise, cursor placement goes awry,
593 * and when we exit from the beginning, we'll be placed *after* the
598 inset->edit(cur, front);
599 cur.setCurrentFont();
605 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
609 if (cur.pos() == cur.lastpos())
611 Paragraph & par = cur.paragraph();
612 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
613 if (!inset || !inset->editable())
615 if (cur.selection() && cur.realAnchor().find(inset) == -1)
617 inset->edit(cur, movingForward,
618 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
619 cur.setCurrentFont();
625 bool Text::cursorBackward(Cursor & cur)
627 // Tell BufferView to test for FitCursor in any case!
628 cur.screenUpdateFlags(Update::FitCursor);
630 // not at paragraph start?
632 // if on right side of boundary (i.e. not at paragraph end, but line end)
633 // -> skip it, i.e. set boundary to true, i.e. go only logically left
634 // there are some exceptions to ignore this: lineseps, newlines, spaces
636 // some effectless debug code to see the values in the debugger
637 bool bound = cur.boundary();
638 int rowpos = cur.textRow().pos();
640 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
641 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
642 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
644 if (!cur.boundary() &&
645 cur.textRow().pos() == cur.pos() &&
646 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
647 !cur.paragraph().isNewline(cur.pos() - 1) &&
648 !cur.paragraph().isEnvSeparator(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 Paragraph & par = getPar(cur.pit() - 1);
664 pos_type lastpos = par.size();
665 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
666 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
668 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
674 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
676 Cursor temp_cur = cur;
677 temp_cur.posVisLeft(skip_inset);
678 if (temp_cur.depth() > cur.depth()) {
682 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
683 true, temp_cur.boundary());
687 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
689 Cursor temp_cur = cur;
690 temp_cur.posVisRight(skip_inset);
691 if (temp_cur.depth() > cur.depth()) {
695 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
696 true, temp_cur.boundary());
700 bool Text::cursorForward(Cursor & cur)
702 // Tell BufferView to test for FitCursor in any case!
703 cur.screenUpdateFlags(Update::FitCursor);
705 // not at paragraph end?
706 if (cur.pos() != cur.lastpos()) {
707 // in front of editable inset, i.e. jump into it?
708 if (checkAndActivateInset(cur, true))
711 TextMetrics const & tm = cur.bv().textMetrics(this);
712 // if left of boundary -> just jump to right side
713 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
714 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
715 return setCursor(cur, cur.pit(), cur.pos(), true, false);
717 // next position is left of boundary,
718 // but go to next line for special cases like space, newline, linesep
720 // some effectless debug code to see the values in the debugger
721 int endpos = cur.textRow().endpos();
722 int lastpos = cur.lastpos();
724 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
725 bool newline = cur.paragraph().isNewline(cur.pos());
726 bool sep = cur.paragraph().isSeparator(cur.pos());
727 if (cur.pos() != cur.lastpos()) {
728 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
729 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
730 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
733 if (cur.textRow().endpos() == cur.pos() + 1) {
734 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
735 cur.pos() + 1 == cur.lastpos() &&
736 cur.pit() != cur.lastpit()) {
737 // move to next paragraph
738 return setCursor(cur, cur.pit() + 1, 0, true, false);
739 } else if (cur.textRow().endpos() != cur.lastpos() &&
740 !cur.paragraph().isNewline(cur.pos()) &&
741 !cur.paragraph().isEnvSeparator(cur.pos()) &&
742 !cur.paragraph().isLineSeparator(cur.pos()) &&
743 !cur.paragraph().isSeparator(cur.pos())) {
744 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
748 // in front of RTL boundary? Stay on this side of the boundary because:
749 // ab|cDDEEFFghi -> abc|DDEEFFghi
750 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
751 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
754 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
757 // move to next paragraph
758 if (cur.pit() != cur.lastpit())
759 return setCursor(cur, cur.pit() + 1, 0, true, false);
764 bool Text::cursorUpParagraph(Cursor & cur)
766 bool updated = false;
768 updated = setCursor(cur, cur.pit(), 0);
769 else if (cur.pit() != 0)
770 updated = setCursor(cur, cur.pit() - 1, 0);
775 bool Text::cursorDownParagraph(Cursor & cur)
777 bool updated = false;
778 if (cur.pit() != cur.lastpit())
779 if (lyxrc.mac_like_cursor_movement)
780 if (cur.pos() == cur.lastpos())
781 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
783 updated = setCursor(cur, cur.pit(), cur.lastpos());
785 updated = setCursor(cur, cur.pit() + 1, 0);
787 updated = setCursor(cur, cur.pit(), cur.lastpos());
793 // fix the cursor `cur' after characters has been deleted at `where'
794 // position. Called by deleteEmptyParagraphMechanism
795 void fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where,
796 pos_type from, int num_chars)
798 // Do nothing if cursor is not in the paragraph where the
799 // deletion occurred,
800 if (cur.pit() != where.pit())
803 // If cursor position is after the deletion place update it
804 // but if we are not at start of line keep it after the space that was kept.
805 if (cur.pos() > from)
806 cur.pos() = max(from + (from > 0), cur.pos() - num_chars);
808 // Check also if we don't want to set the cursor on a spot behind the
809 // pagragraph because we erased the last character.
810 if (cur.pos() > cur.lastpos())
811 cur.pos() = cur.lastpos();
817 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
818 Cursor & old, bool & need_anchor_change)
820 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
822 Paragraph & oldpar = old.paragraph();
824 // We allow all kinds of "mumbo-jumbo" when freespacing.
825 if (oldpar.isFreeSpacing())
828 /* Ok I'll put some comments here about what is missing.
829 There are still some small problems that can lead to
830 double spaces stored in the document file or space at
831 the beginning of paragraphs(). This happens if you have
832 the cursor between two spaces and then save. Or if you
833 cut and paste and the selection has a space at the
834 beginning and then save right after the paste. (Lgb)
837 // If old.pos() == 0 and old.pos()(1) == LineSeparator
838 // delete the LineSeparator.
841 // If old.pos() == 1 and old.pos()(0) == LineSeparator
842 // delete the LineSeparator.
845 // Find a common inset and the corresponding depth.
847 for (; depth < cur.depth(); ++depth)
848 if (&old.inset() == &cur[depth].inset())
851 // Whether a common inset is found and whether the cursor is still in
852 // the same paragraph (possibly nested).
853 bool const same_par = depth < cur.depth() && old.idx() == cur[depth].idx()
854 && old.pit() == cur[depth].pit();
855 bool const same_par_pos = depth == cur.depth() - 1 && same_par
856 && old.pos() == cur[depth].pos();
858 // If the chars around the old cursor were spaces, delete some of
859 // them, but only if the cursor has really moved.
861 // find range of spaces around cursors
862 int from = old.pos();
864 && oldpar.isLineSeparator(from - 1)
865 && !oldpar.isDeleted(from - 1))
868 while (to < oldpar.size()
869 && oldpar.isLineSeparator(to)
870 && !oldpar.isDeleted(to))
873 // If we are not at the start of the paragraph, keep one space
874 if (from != to && from > 0) {
876 // if the new cursor is inside the sequence of spaces, keep one more space
877 if (same_par && cur.pos() > from && cur.pos() <= to)
881 // Remove spaces and adapt cursor.
883 // we need to remember what the size was because
884 // eraseChars might mark spaces as deleted instead of
886 int const oldsize = oldpar.size();
887 oldpar.eraseChars(from, to, cur.buffer()->params().track_changes);
888 // FIXME: This will not work anymore when we have multiple views of the same buffer
889 // In this case, we will have to correct also the cursors held by
890 // other bufferviews. It will probably be easier to do that in a more
891 // automated way in CursorSlice code. (JMarc 26/09/2001)
892 // correct all cursor parts
894 fixCursorAfterDelete(cur[depth], old.top(), from, oldsize - oldpar.size());
895 need_anchor_change = true;
901 // only do our other magic if we changed paragraph
905 // don't delete anything if this is the ONLY paragraph!
906 if (old.lastpit() == 0)
909 // Do not delete empty paragraphs with keepempty set.
910 if (oldpar.allowEmpty())
913 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
915 old.recordUndo(max(old.pit() - 1, pit_type(0)),
916 min(old.pit() + 1, old.lastpit()));
917 ParagraphList & plist = old.text()->paragraphs();
918 bool const soa = oldpar.params().startOfAppendix();
919 plist.erase(lyx::next(plist.begin(), old.pit()));
920 // do not lose start of appendix marker (bug 4212)
921 if (soa && old.pit() < pit_type(plist.size()))
922 plist[old.pit()].params().startOfAppendix(true);
924 // see #warning (FIXME?) above
925 if (cur.depth() >= old.depth()) {
926 CursorSlice & curslice = cur[old.depth() - 1];
927 if (&curslice.inset() == &old.inset()
928 && curslice.idx() == old.idx()
929 && curslice.pit() > old.pit()) {
931 // since a paragraph has been deleted, all the
932 // insets after `old' have been copied and
933 // their address has changed. Therefore we
934 // need to `regenerate' cur. (JMarc)
935 cur.updateInsets(&(cur.bottom().inset()));
936 need_anchor_change = true;
942 if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
943 need_anchor_change = true;
944 // We return true here because the Paragraph contents changed and
945 // we need a redraw before further action is processed.
953 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
955 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
957 for (pit_type pit = first; pit <= last; ++pit) {
958 Paragraph & par = pars_[pit];
960 // We allow all kinds of "mumbo-jumbo" when freespacing.
961 if (par.isFreeSpacing())
965 while (from < par.size()) {
967 while (from < par.size()
968 && (!par.isLineSeparator(from) || par.isDeleted(from)))
970 // find string of spaces
972 while (to < par.size()
973 && par.isLineSeparator(to) && !par.isDeleted(to))
975 // empty? We are done
978 // if inside the line, keep one space
979 if (from > 0 && to < par.size())
981 // remove the extra spaces
983 par.eraseChars(from, to, trackChanges);
986 // don't delete anything if this is the only remaining paragraph
987 // within the given range. Note: Text::acceptOrRejectChanges()
988 // sets the cursor to 'first' after calling DEPM
992 // don't delete empty paragraphs with keepempty set
993 if (par.allowEmpty())
996 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
997 pars_.erase(lyx::next(pars_.begin(), pit));
1003 par.stripLeadingSpaces(trackChanges);