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(), /**/);
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, /**/);
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 LASSERT(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 if (par.layout().labeltype == LABEL_BIBLIO)
212 int const depth = par.params().depth();
213 if (type == Text::INC_DEPTH && depth < max_depth)
215 if (type == Text::DEC_DEPTH && depth > 0)
221 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
223 LASSERT(this == cur.text(), /**/);
224 // this happens when selecting several cells in tabular (bug 2630)
225 if (cur.selBegin().idx() != cur.selEnd().idx())
228 pit_type const beg = cur.selBegin().pit();
229 pit_type const end = cur.selEnd().pit() + 1;
230 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
232 for (pit_type pit = beg; pit != end; ++pit) {
233 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
235 max_depth = pars_[pit].getMaxDepthAfter();
241 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
243 LASSERT(this == cur.text(), /**/);
244 pit_type const beg = cur.selBegin().pit();
245 pit_type const end = cur.selEnd().pit() + 1;
246 cur.recordUndoSelection();
247 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
249 for (pit_type pit = beg; pit != end; ++pit) {
250 Paragraph & par = pars_[pit];
251 if (lyx::changeDepthAllowed(type, par, max_depth)) {
252 int const depth = par.params().depth();
253 if (type == INC_DEPTH)
254 par.params().depth(depth + 1);
256 par.params().depth(depth - 1);
258 max_depth = par.getMaxDepthAfter();
260 // this handles the counter labels, and also fixes up
261 // depth values for follow-on (child) paragraphs
262 cur.forceBufferUpdate();
266 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
268 LASSERT(this == cur.text(), /**/);
269 // Set the current_font
270 // Determine basis font
272 pit_type pit = cur.pit();
273 if (cur.pos() < pars_[pit].beginOfBody())
274 layoutfont = labelFont(pars_[pit]);
276 layoutfont = layoutFont(pit);
278 // Update current font
279 cur.real_current_font.update(font,
280 cur.buffer()->params().language,
283 // Reduce to implicit settings
284 cur.current_font = cur.real_current_font;
285 cur.current_font.fontInfo().reduce(layoutfont);
286 // And resolve it completely
287 cur.real_current_font.fontInfo().realize(layoutfont);
289 // if there is no selection that's all we need to do
290 if (!cur.selection())
293 // Ok, we have a selection.
294 cur.recordUndoSelection();
298 // Toggling behaves as follows: We check the first character of the
299 // selection. If it's (say) got EMPH on, then we set to off; if off,
300 // then to on. With families and the like, we set it to INHERIT, if
301 // we already have it.
302 CursorSlice const & sl = cur.selBegin();
303 Text const & text = *sl.text();
304 Paragraph const & par = text.getPar(sl.pit());
306 // get font at the position
307 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
308 text.outerFont(sl.pit()));
309 FontInfo const & oldfi = oldfont.fontInfo();
311 FontInfo & newfi = newfont.fontInfo();
313 FontFamily newfam = newfi.family();
314 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
315 newfam == oldfi.family())
316 newfi.setFamily(INHERIT_FAMILY);
318 FontSeries newser = newfi.series();
319 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
320 newfi.setSeries(INHERIT_SERIES);
322 FontShape newshp = newfi.shape();
323 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
324 newshp == oldfi.shape())
325 newfi.setShape(INHERIT_SHAPE);
327 ColorCode newcol = newfi.color();
328 if (newcol != Color_none && newcol != Color_inherit
329 && newcol != Color_ignore && newcol == oldfi.color())
330 newfi.setColor(Color_none);
333 if (newfi.emph() == FONT_TOGGLE)
334 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
335 if (newfi.underbar() == FONT_TOGGLE)
336 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
337 if (newfi.strikeout() == FONT_TOGGLE)
338 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
339 if (newfi.uuline() == FONT_TOGGLE)
340 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
341 if (newfi.uwave() == FONT_TOGGLE)
342 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
343 if (newfi.noun() == FONT_TOGGLE)
344 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
345 if (newfi.number() == FONT_TOGGLE)
346 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
349 setFont(cur.bv(), cur.selectionBegin().top(),
350 cur.selectionEnd().top(), newfont);
354 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
355 CursorSlice const & end, Font const & font)
357 Buffer const & buffer = bv.buffer();
359 // Don't use forwardChar here as ditend might have
360 // pos() == lastpos() and forwardChar would miss it.
361 // Can't use forwardPos either as this descends into
363 Language const * language = buffer.params().language;
364 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
365 if (dit.pos() == dit.lastpos())
367 pit_type const pit = dit.pit();
368 pos_type const pos = dit.pos();
369 Inset * inset = pars_[pit].getInset(pos);
370 if (inset && inset->resetFontEdit()) {
371 // We need to propagate the font change to all
372 // text cells of the inset (bugs 1973, 6919).
373 setInsetFont(bv, pit, pos, font);
375 TextMetrics const & tm = bv.textMetrics(this);
376 Font f = tm.displayFont(pit, pos);
377 f.update(font, language);
378 setCharFont(pit, pos, f, tm.font_);
379 // font change may change language...
380 // spell checker has to know that
381 pars_[pit].requestSpellCheck(pos);
386 bool Text::cursorTop(Cursor & cur)
388 LASSERT(this == cur.text(), /**/);
389 return setCursor(cur, 0, 0);
393 bool Text::cursorBottom(Cursor & cur)
395 LASSERT(this == cur.text(), /**/);
396 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
400 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
402 LASSERT(this == cur.text(), /**/);
403 // If the mask is completely neutral, tell user
404 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
405 // Could only happen with user style
406 cur.message(_("No font change defined."));
410 // Try implicit word selection
411 // If there is a change in the language the implicit word selection
413 CursorSlice const resetCursor = cur.top();
414 bool const implicitSelection =
415 font.language() == ignore_language
416 && font.fontInfo().number() == FONT_IGNORE
417 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
420 setFont(cur, font, toggleall);
422 // Implicit selections are cleared afterwards
423 // and cursor is set to the original position.
424 if (implicitSelection) {
425 cur.clearSelection();
426 cur.top() = resetCursor;
432 docstring Text::getStringToIndex(Cursor const & cur)
434 LASSERT(this == cur.text(), /**/);
437 return cur.selectionAsString(false);
439 // Try implicit word selection. If there is a change
440 // in the language the implicit word selection is
443 selectWord(tmpcur, PREVIOUS_WORD);
445 if (!tmpcur.selection())
446 cur.message(_("Nothing to index!"));
447 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
448 cur.message(_("Cannot index more than one paragraph!"));
450 return tmpcur.selectionAsString(false);
456 void Text::setLabelWidthStringToSequence(Cursor const & cur,
460 // Find first of same layout in sequence
461 while (!isFirstInSequence(c.pit())) {
462 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
465 // now apply label width string to every par
467 depth_type const depth = c.paragraph().getDepth();
468 Layout const & layout = c.paragraph().layout();
469 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
470 while (c.paragraph().getDepth() > depth) {
472 if (c.pit() > c.lastpit())
475 if (c.paragraph().getDepth() < depth)
477 if (c.paragraph().layout() != layout)
480 c.paragraph().setLabelWidthString(s);
485 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
487 LASSERT(cur.text(), /**/);
490 string const argument = to_utf8(arg);
491 depth_type priordepth = -1;
494 c.setCursor(cur.selectionBegin());
495 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
496 Paragraph & par = c.paragraph();
497 ParagraphParameters params = par.params();
498 params.read(argument, merge);
499 // Changes to label width string apply to all paragraphs
500 // with same layout in a sequence.
501 // Do this only once for a selected range of paragraphs
502 // of the same layout and depth.
504 par.params().apply(params, par.layout());
505 if (par.getDepth() != priordepth || par.layout() != priorlayout)
506 setLabelWidthStringToSequence(c, params.labelWidthString());
507 priordepth = par.getDepth();
508 priorlayout = par.layout();
513 //FIXME This is a little redundant now, but it's probably worth keeping,
514 //especially if we're going to go away from using serialization internally
516 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
518 LASSERT(cur.text(), /**/);
520 depth_type priordepth = -1;
523 c.setCursor(cur.selectionBegin());
524 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
525 Paragraph & par = c.paragraph();
526 // Changes to label width string apply to all paragraphs
527 // with same layout in a sequence.
528 // Do this only once for a selected range of paragraphs
529 // of the same layout and depth.
531 par.params().apply(p, par.layout());
532 if (par.getDepth() != priordepth || par.layout() != priorlayout)
533 setLabelWidthStringToSequence(c,
534 par.params().labelWidthString());
535 priordepth = par.getDepth();
536 priorlayout = par.layout();
541 // this really should just insert the inset and not move the cursor.
542 void Text::insertInset(Cursor & cur, Inset * inset)
544 LASSERT(this == cur.text(), /**/);
545 LASSERT(inset, /**/);
546 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
547 Change(cur.buffer()->params().trackChanges
548 ? Change::INSERTED : Change::UNCHANGED));
552 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
553 bool setfont, bool boundary)
555 TextMetrics const & tm = cur.bv().textMetrics(this);
556 bool const update_needed = !tm.contains(par);
558 setCursorIntern(cur, par, pos, setfont, boundary);
559 // FIXME There is a chance that we'll miss a screen update here.
560 // If so, then do DEPM and then check if cur wants an update and
561 // go ahead and do it, if so.
562 return cur.bv().checkDepm(cur, old) || update_needed;
566 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
568 LASSERT(par != int(paragraphs().size()), /**/);
572 // now some strict checking
573 Paragraph & para = getPar(par);
575 // None of these should happen, but we're scaredy-cats
577 lyxerr << "don't like -1" << endl;
578 LASSERT(false, /**/);
581 if (pos > para.size()) {
582 lyxerr << "don't like 1, pos: " << pos
583 << " size: " << para.size()
584 << " par: " << par << endl;
585 LASSERT(false, /**/);
590 void Text::setCursorIntern(Cursor & cur,
591 pit_type par, pos_type pos, bool setfont, bool boundary)
593 LASSERT(this == cur.text(), /**/);
594 cur.boundary(boundary);
595 setCursor(cur.top(), par, pos);
597 cur.setCurrentFont();
601 bool Text::checkAndActivateInset(Cursor & cur, bool front)
605 if (front && cur.pos() == cur.lastpos())
607 if (!front && cur.pos() == 0)
609 Inset * inset = front ? cur.nextInset() : cur.prevInset();
610 if (!inset || !inset->editable())
613 * Apparently, when entering an inset we are expected to be positioned
614 * *before* it in the containing paragraph, regardless of the direction
615 * from which we are entering. Otherwise, cursor placement goes awry,
616 * and when we exit from the beginning, we'll be placed *after* the
621 inset->edit(cur, front);
626 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
632 if (cur.pos() == cur.lastpos())
634 Paragraph & par = cur.paragraph();
635 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
636 if (!inset || !inset->editable())
638 inset->edit(cur, movingForward,
639 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
644 bool Text::cursorBackward(Cursor & cur)
646 // Tell BufferView to test for FitCursor in any case!
647 cur.screenUpdateFlags(Update::FitCursor);
649 // not at paragraph start?
651 // if on right side of boundary (i.e. not at paragraph end, but line end)
652 // -> skip it, i.e. set boundary to true, i.e. go only logically left
653 // there are some exceptions to ignore this: lineseps, newlines, spaces
655 // some effectless debug code to see the values in the debugger
656 bool bound = cur.boundary();
657 int rowpos = cur.textRow().pos();
659 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
660 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
661 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
663 if (!cur.boundary() &&
664 cur.textRow().pos() == cur.pos() &&
665 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
666 !cur.paragraph().isNewline(cur.pos() - 1) &&
667 !cur.paragraph().isSeparator(cur.pos() - 1)) {
668 return setCursor(cur, cur.pit(), cur.pos(), true, true);
671 // go left and try to enter inset
672 if (checkAndActivateInset(cur, false))
675 // normal character left
676 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
679 // move to the previous paragraph or do nothing
681 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
686 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
688 Cursor temp_cur = cur;
689 temp_cur.posVisLeft(skip_inset);
690 if (temp_cur.depth() > cur.depth()) {
694 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
695 true, temp_cur.boundary());
699 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
701 Cursor temp_cur = cur;
702 temp_cur.posVisRight(skip_inset);
703 if (temp_cur.depth() > cur.depth()) {
707 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
708 true, temp_cur.boundary());
712 bool Text::cursorForward(Cursor & cur)
714 // Tell BufferView to test for FitCursor in any case!
715 cur.screenUpdateFlags(Update::FitCursor);
717 // not at paragraph end?
718 if (cur.pos() != cur.lastpos()) {
719 // in front of editable inset, i.e. jump into it?
720 if (checkAndActivateInset(cur, true))
723 TextMetrics const & tm = cur.bv().textMetrics(this);
724 // if left of boundary -> just jump to right side
725 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
726 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
727 return setCursor(cur, cur.pit(), cur.pos(), true, false);
729 // next position is left of boundary,
730 // but go to next line for special cases like space, newline, linesep
732 // some effectless debug code to see the values in the debugger
733 int endpos = cur.textRow().endpos();
734 int lastpos = cur.lastpos();
736 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
737 bool newline = cur.paragraph().isNewline(cur.pos());
738 bool sep = cur.paragraph().isSeparator(cur.pos());
739 if (cur.pos() != cur.lastpos()) {
740 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
741 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
742 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
745 if (cur.textRow().endpos() == cur.pos() + 1 &&
746 cur.textRow().endpos() != cur.lastpos() &&
747 !cur.paragraph().isNewline(cur.pos()) &&
748 !cur.paragraph().isLineSeparator(cur.pos()) &&
749 !cur.paragraph().isSeparator(cur.pos())) {
750 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
753 // in front of RTL boundary? Stay on this side of the boundary because:
754 // ab|cDDEEFFghi -> abc|DDEEFFghi
755 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
756 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
759 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
762 // move to next paragraph
763 if (cur.pit() != cur.lastpit())
764 return setCursor(cur, cur.pit() + 1, 0, true, false);
769 bool Text::cursorUpParagraph(Cursor & cur)
771 bool updated = false;
773 updated = setCursor(cur, cur.pit(), 0);
774 else if (cur.pit() != 0)
775 updated = setCursor(cur, cur.pit() - 1, 0);
780 bool Text::cursorDownParagraph(Cursor & cur)
782 bool updated = false;
783 if (cur.pit() != cur.lastpit())
784 updated = setCursor(cur, cur.pit() + 1, 0);
786 updated = setCursor(cur, cur.pit(), cur.lastpos());
791 // fix the cursor `cur' after a characters has been deleted at `where'
792 // position. Called by deleteEmptyParagraphMechanism
793 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
795 // Do nothing if cursor is not in the paragraph where the
797 if (cur.pit() != where.pit())
800 // If cursor position is after the deletion place update it
801 if (cur.pos() > where.pos())
804 // Check also if we don't want to set the cursor on a spot behind the
805 // pagragraph because we erased the last character.
806 if (cur.pos() > cur.lastpos())
807 cur.pos() = cur.lastpos();
811 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
812 Cursor & old, bool & need_anchor_change)
814 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
816 Paragraph & oldpar = old.paragraph();
818 // We allow all kinds of "mumbo-jumbo" when freespacing.
819 if (oldpar.isFreeSpacing())
822 /* Ok I'll put some comments here about what is missing.
823 There are still some small problems that can lead to
824 double spaces stored in the document file or space at
825 the beginning of paragraphs(). This happens if you have
826 the cursor between to spaces and then save. Or if you
827 cut and paste and the selection have a space at the
828 beginning and then save right after the paste. (Lgb)
831 // If old.pos() == 0 and old.pos()(1) == LineSeparator
832 // delete the LineSeparator.
835 // If old.pos() == 1 and old.pos()(0) == LineSeparator
836 // delete the LineSeparator.
839 // Find a common inset and the corresponding depth.
841 for (; depth < cur.depth(); ++depth)
842 if (&old.inset() == &cur[depth].inset())
845 // Whether a common inset is found and whether the cursor is still in
846 // the same paragraph (possibly nested).
847 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
848 bool const same_par_pos = depth == cur.depth() - 1 && same_par
849 && old.pos() == cur[depth].pos();
851 // If the chars around the old cursor were spaces, delete one of them.
853 // Only if the cursor has really moved.
855 && old.pos() < oldpar.size()
856 && oldpar.isLineSeparator(old.pos())
857 && oldpar.isLineSeparator(old.pos() - 1)
858 && !oldpar.isDeleted(old.pos() - 1)
859 && !oldpar.isDeleted(old.pos())) {
860 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
861 // FIXME: This will not work anymore when we have multiple views of the same buffer
862 // In this case, we will have to correct also the cursors held by
863 // other bufferviews. It will probably be easier to do that in a more
864 // automated way in CursorSlice code. (JMarc 26/09/2001)
865 // correct all cursor parts
867 fixCursorAfterDelete(cur[depth], old.top());
868 need_anchor_change = true;
874 // only do our magic if we changed paragraph
878 // don't delete anything if this is the ONLY paragraph!
879 if (old.lastpit() == 0)
882 // Do not delete empty paragraphs with keepempty set.
883 if (oldpar.allowEmpty())
886 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
888 old.recordUndo(ATOMIC_UNDO,
889 max(old.pit() - 1, pit_type(0)),
890 min(old.pit() + 1, old.lastpit()));
891 ParagraphList & plist = old.text()->paragraphs();
892 bool const soa = oldpar.params().startOfAppendix();
893 plist.erase(boost::next(plist.begin(), old.pit()));
894 // do not lose start of appendix marker (bug 4212)
895 if (soa && old.pit() < pit_type(plist.size()))
896 plist[old.pit()].params().startOfAppendix(true);
898 // see #warning (FIXME?) above
899 if (cur.depth() >= old.depth()) {
900 CursorSlice & curslice = cur[old.depth() - 1];
901 if (&curslice.inset() == &old.inset()
902 && curslice.pit() > old.pit()) {
904 // since a paragraph has been deleted, all the
905 // insets after `old' have been copied and
906 // their address has changed. Therefore we
907 // need to `regenerate' cur. (JMarc)
908 cur.updateInsets(&(cur.bottom().inset()));
909 need_anchor_change = true;
915 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
916 need_anchor_change = true;
917 // We return true here because the Paragraph contents changed and
918 // we need a redraw before further action is processed.
926 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
928 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
930 for (pit_type pit = first; pit <= last; ++pit) {
931 Paragraph & par = pars_[pit];
933 // We allow all kinds of "mumbo-jumbo" when freespacing.
934 if (par.isFreeSpacing())
937 for (pos_type pos = 1; pos < par.size(); ++pos) {
938 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
939 && !par.isDeleted(pos - 1)) {
940 if (par.eraseChar(pos - 1, trackChanges)) {
946 // don't delete anything if this is the only remaining paragraph
947 // within the given range. Note: Text::acceptOrRejectChanges()
948 // sets the cursor to 'first' after calling DEPM
952 // don't delete empty paragraphs with keepempty set
953 if (par.allowEmpty())
956 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
957 pars_.erase(boost::next(pars_.begin(), pit));
963 par.stripLeadingSpaces(trackChanges);
968 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
970 cur.recordUndo(ATOMIC_UNDO, first, last);
974 void Text::recUndo(Cursor & cur, pit_type par) const
976 cur.recordUndo(ATOMIC_UNDO, par, par);