3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "FuncRequest.h"
42 #include "Paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "TextClass.h"
46 #include "TextMetrics.h"
49 #include "insets/InsetCollapsable.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/lassert.h"
54 #include "support/debug.h"
55 #include "support/gettext.h"
56 #include "support/textutils.h"
58 #include <boost/next_prior.hpp>
66 bool Text::isMainText() const
68 return &owner_->buffer().text() == this;
72 // Note that this is supposed to return a fully realized font.
73 FontInfo Text::layoutFont(pit_type const pit) const
75 Layout const & layout = pars_[pit].layout();
77 if (!pars_[pit].getDepth()) {
78 FontInfo lf = layout.resfont;
79 // In case the default family has been customized
80 if (layout.font.family() == INHERIT_FAMILY)
81 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
83 // It ought to be possible here just to use Inset::getLayout() and skip
84 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
85 // now, because Inset::getLayout() will return a default-constructed
86 // InsetLayout, and that e.g. sets the foreground color to red. So we
87 // need to do some work to make that possible.
88 InsetCollapsable const * icp = pars_[pit].inInset().asInsetCollapsable();
91 FontInfo icf = icp->getLayout().font();
96 FontInfo font = layout.font;
97 // Realize with the fonts of lesser depth.
98 //font.realize(outerFont(pit, paragraphs()));
99 font.realize(owner_->buffer().params().getFont().fontInfo());
105 // Note that this is supposed to return a fully realized font.
106 FontInfo Text::labelFont(Paragraph const & par) const
108 Buffer const & buffer = owner_->buffer();
109 Layout const & layout = par.layout();
111 if (!par.getDepth()) {
112 FontInfo lf = layout.reslabelfont;
113 // In case the default family has been customized
114 if (layout.labelfont.family() == INHERIT_FAMILY)
115 lf.setFamily(buffer.params().getFont().fontInfo().family());
119 FontInfo font = layout.labelfont;
120 // Realize with the fonts of lesser depth.
121 font.realize(buffer.params().getFont().fontInfo());
127 void Text::setCharFont(pit_type pit,
128 pos_type pos, Font const & fnt, Font const & display_font)
130 Buffer const & buffer = owner_->buffer();
132 Layout const & layout = pars_[pit].layout();
134 // Get concrete layout font to reduce against
137 if (pos < pars_[pit].beginOfBody())
138 layoutfont = layout.labelfont;
140 layoutfont = layout.font;
142 // Realize against environment font information
143 if (pars_[pit].getDepth()) {
145 while (!layoutfont.resolved() &&
146 tp != pit_type(paragraphs().size()) &&
147 pars_[tp].getDepth()) {
148 tp = outerHook(tp, paragraphs());
149 if (tp != pit_type(paragraphs().size()))
150 layoutfont.realize(pars_[tp].layout().font);
154 // Inside inset, apply the inset's font attributes if any
157 layoutfont.realize(display_font.fontInfo());
159 layoutfont.realize(buffer.params().getFont().fontInfo());
161 // Now, reduce font against full layout font
162 font.fontInfo().reduce(layoutfont);
164 pars_[pit].setFont(pos, font);
168 void Text::setInsetFont(BufferView const & bv, pit_type pit,
169 pos_type pos, Font const & font, bool toggleall)
171 Inset * const inset = pars_[pit].getInset(pos);
172 LASSERT(inset && inset->noFontChange(), /**/);
174 CursorSlice::idx_type endidx = inset->nargs();
175 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
176 Text * text = cs.text();
178 // last position of the cell
179 CursorSlice cellend = cs;
180 cellend.pit() = cellend.lastpit();
181 cellend.pos() = cellend.lastpos();
182 text->setFont(bv, cs, cellend, font, toggleall);
188 // return past-the-last paragraph influenced by a layout change on pit
189 pit_type Text::undoSpan(pit_type pit)
191 pit_type const end = paragraphs().size();
192 pit_type nextpit = pit + 1;
195 //because of parindents
196 if (!pars_[pit].getDepth())
197 return boost::next(nextpit);
198 //because of depth constrains
199 for (; nextpit != end; ++pit, ++nextpit) {
200 if (!pars_[pit].getDepth())
207 void Text::setLayout(pit_type start, pit_type end,
208 docstring const & layout)
210 LASSERT(start != end, /**/);
212 Buffer const & buffer = owner_->buffer();
213 BufferParams const & bp = buffer.params();
214 Layout const & lyxlayout = bp.documentClass()[layout];
216 for (pit_type pit = start; pit != end; ++pit) {
217 Paragraph & par = pars_[pit];
218 par.applyLayout(lyxlayout);
219 if (lyxlayout.margintype == MARGIN_MANUAL)
220 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
225 // set layout over selection and make a total rebreak of those paragraphs
226 void Text::setLayout(Cursor & cur, docstring const & layout)
228 LASSERT(this == cur.text(), /**/);
230 pit_type start = cur.selBegin().pit();
231 pit_type end = cur.selEnd().pit() + 1;
232 pit_type undopit = undoSpan(end - 1);
233 recUndo(cur, start, undopit - 1);
234 setLayout(start, end, layout);
235 cur.buffer()->updateLabels();
239 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
240 Paragraph const & par, int max_depth)
242 if (par.layout().labeltype == LABEL_BIBLIO)
244 int const depth = par.params().depth();
245 if (type == Text::INC_DEPTH && depth < max_depth)
247 if (type == Text::DEC_DEPTH && depth > 0)
253 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
255 LASSERT(this == cur.text(), /**/);
256 // this happens when selecting several cells in tabular (bug 2630)
257 if (cur.selBegin().idx() != cur.selEnd().idx())
260 pit_type const beg = cur.selBegin().pit();
261 pit_type const end = cur.selEnd().pit() + 1;
262 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
264 for (pit_type pit = beg; pit != end; ++pit) {
265 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
267 max_depth = pars_[pit].getMaxDepthAfter();
273 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
275 LASSERT(this == cur.text(), /**/);
276 pit_type const beg = cur.selBegin().pit();
277 pit_type const end = cur.selEnd().pit() + 1;
278 cur.recordUndoSelection();
279 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
281 for (pit_type pit = beg; pit != end; ++pit) {
282 Paragraph & par = pars_[pit];
283 if (lyx::changeDepthAllowed(type, par, max_depth)) {
284 int const depth = par.params().depth();
285 if (type == INC_DEPTH)
286 par.params().depth(depth + 1);
288 par.params().depth(depth - 1);
290 max_depth = par.getMaxDepthAfter();
292 // this handles the counter labels, and also fixes up
293 // depth values for follow-on (child) paragraphs
294 cur.buffer()->updateLabels();
298 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
300 LASSERT(this == cur.text(), /**/);
301 // Set the current_font
302 // Determine basis font
304 pit_type pit = cur.pit();
305 if (cur.pos() < pars_[pit].beginOfBody())
306 layoutfont = labelFont(pars_[pit]);
308 layoutfont = layoutFont(pit);
310 // Update current font
311 cur.real_current_font.update(font,
312 cur.buffer()->params().language,
315 // Reduce to implicit settings
316 cur.current_font = cur.real_current_font;
317 cur.current_font.fontInfo().reduce(layoutfont);
318 // And resolve it completely
319 cur.real_current_font.fontInfo().realize(layoutfont);
321 // if there is no selection that's all we need to do
322 if (!cur.selection())
325 // Ok, we have a selection.
326 cur.recordUndoSelection();
328 setFont(cur.bv(), cur.selectionBegin().top(),
329 cur.selectionEnd().top(), font, toggleall);
333 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
334 CursorSlice const & end, Font const & font,
337 Buffer const & buffer = bv.buffer();
339 // Don't use forwardChar here as ditend might have
340 // pos() == lastpos() and forwardChar would miss it.
341 // Can't use forwardPos either as this descends into
343 Language const * language = buffer.params().language;
344 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
345 if (dit.pos() == dit.lastpos())
347 pit_type const pit = dit.pit();
348 pos_type const pos = dit.pos();
349 Inset * inset = pars_[pit].getInset(pos);
350 if (inset && inset->noFontChange()) {
351 // We need to propagate the font change to all
352 // text cells of the inset (bug 1973).
353 // FIXME: This should change, see documentation
354 // of noFontChange in Inset.h
355 setInsetFont(bv, pit, pos, font, toggleall);
357 TextMetrics const & tm = bv.textMetrics(this);
358 Font f = tm.displayFont(pit, pos);
359 f.update(font, language, toggleall);
360 setCharFont(pit, pos, f, tm.font_);
365 bool Text::cursorTop(Cursor & cur)
367 LASSERT(this == cur.text(), /**/);
368 return setCursor(cur, 0, 0);
372 bool Text::cursorBottom(Cursor & cur)
374 LASSERT(this == cur.text(), /**/);
375 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
379 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
381 LASSERT(this == cur.text(), /**/);
382 // If the mask is completely neutral, tell user
383 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
384 // Could only happen with user style
385 cur.message(_("No font change defined."));
389 // Try implicit word selection
390 // If there is a change in the language the implicit word selection
392 CursorSlice const resetCursor = cur.top();
393 bool const implicitSelection =
394 font.language() == ignore_language
395 && font.fontInfo().number() == FONT_IGNORE
396 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
399 setFont(cur, font, toggleall);
401 // Implicit selections are cleared afterwards
402 // and cursor is set to the original position.
403 if (implicitSelection) {
404 cur.clearSelection();
405 cur.top() = resetCursor;
411 docstring Text::getStringToIndex(Cursor const & cur)
413 LASSERT(this == cur.text(), /**/);
416 return cur.selectionAsString(false);
418 // Try implicit word selection. If there is a change
419 // in the language the implicit word selection is
422 selectWord(tmpcur, PREVIOUS_WORD);
424 if (!tmpcur.selection())
425 cur.message(_("Nothing to index!"));
426 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
427 cur.message(_("Cannot index more than one paragraph!"));
429 return tmpcur.selectionAsString(false);
435 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
437 LASSERT(cur.text(), /**/);
438 // make sure that the depth behind the selection are restored, too
439 pit_type undopit = undoSpan(cur.selEnd().pit());
440 recUndo(cur, cur.selBegin().pit(), undopit - 1);
443 string const argument = to_utf8(arg);
444 depth_type priordepth = -1;
446 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
448 Paragraph & par = pars_[pit];
449 ParagraphParameters params = par.params();
450 params.read(argument, merge);
451 // Changes to label width string apply to all paragraphs
452 // with same layout in a sequence.
453 // Do this only once for a selected range of paragraphs
454 // of the same layout and depth.
455 if (par.getDepth() != priordepth || par.layout() != priorlayout)
456 setLabelWidthStringToSequence(pit, pars_,
457 params.labelWidthString());
458 par.params().apply(params, par.layout());
459 priordepth = par.getDepth();
460 priorlayout = par.layout();
465 //FIXME This is a little redundant now, but it's probably worth keeping,
466 //especially if we're going to go away from using serialization internally
468 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
470 LASSERT(cur.text(), /**/);
471 // make sure that the depth behind the selection are restored, too
472 pit_type undopit = undoSpan(cur.selEnd().pit());
473 recUndo(cur, cur.selBegin().pit(), undopit - 1);
475 depth_type priordepth = -1;
477 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
479 Paragraph & par = pars_[pit];
480 // Changes to label width string apply to all paragraphs
481 // with same layout in a sequence.
482 // Do this only once for a selected range of paragraphs
483 // of the same layout and depth.
484 if (par.getDepth() != priordepth || par.layout() != priorlayout)
485 setLabelWidthStringToSequence(pit, pars_,
486 par.params().labelWidthString());
487 par.params().apply(p, par.layout());
488 priordepth = par.getDepth();
489 priorlayout = par.layout();
494 // this really should just insert the inset and not move the cursor.
495 void Text::insertInset(Cursor & cur, Inset * inset)
497 LASSERT(this == cur.text(), /**/);
498 LASSERT(inset, /**/);
499 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
500 Change(cur.buffer()->params().trackChanges
501 ? Change::INSERTED : Change::UNCHANGED));
505 // needed to insert the selection
506 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
508 BufferParams const & bparams = owner_->buffer().params();
509 pit_type pit = cur.pit();
510 pos_type pos = cur.pos();
511 Font font = cur.current_font;
513 // insert the string, don't insert doublespace
514 bool space_inserted = true;
515 for (docstring::const_iterator cit = str.begin();
516 cit != str.end(); ++cit) {
517 Paragraph & par = pars_[pit];
519 if (autoBreakRows_ && (!par.empty() || par.allowEmpty())) {
520 lyx::breakParagraph(bparams, pars_, pit, pos,
521 par.layout().isEnvironment());
524 space_inserted = true;
528 // do not insert consecutive spaces if !free_spacing
529 } else if ((*cit == ' ' || *cit == '\t') &&
530 space_inserted && !par.isFreeSpacing()) {
532 } else if (*cit == '\t') {
533 if (!par.isFreeSpacing()) {
534 // tabs are like spaces here
535 par.insertChar(pos, ' ', font, bparams.trackChanges);
537 space_inserted = true;
539 par.insertChar(pos, *cit, font, bparams.trackChanges);
541 space_inserted = true;
543 } else if (!isPrintable(*cit)) {
544 // Ignore unprintables
547 // just insert the character
548 par.insertChar(pos, *cit, font, bparams.trackChanges);
550 space_inserted = (*cit == ' ');
556 // turn double CR to single CR, others are converted into one
557 // blank. Then insertStringAsLines is called
558 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
560 docstring linestr = str;
561 bool newline_inserted = false;
563 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
564 if (linestr[i] == '\n') {
565 if (newline_inserted) {
566 // we know that \r will be ignored by
567 // insertStringAsLines. Of course, it is a dirty
568 // trick, but it works...
569 linestr[i - 1] = '\r';
573 newline_inserted = true;
575 } else if (isPrintable(linestr[i])) {
576 newline_inserted = false;
579 insertStringAsLines(cur, linestr);
583 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
584 bool setfont, bool boundary)
586 TextMetrics const & tm = cur.bv().textMetrics(this);
587 bool const update_needed = !tm.contains(par);
589 setCursorIntern(cur, par, pos, setfont, boundary);
590 return cur.bv().checkDepm(cur, old) || update_needed;
594 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
596 LASSERT(par != int(paragraphs().size()), /**/);
600 // now some strict checking
601 Paragraph & para = getPar(par);
603 // None of these should happen, but we're scaredy-cats
605 lyxerr << "dont like -1" << endl;
606 LASSERT(false, /**/);
609 if (pos > para.size()) {
610 lyxerr << "dont like 1, pos: " << pos
611 << " size: " << para.size()
612 << " par: " << par << endl;
613 LASSERT(false, /**/);
618 void Text::setCursorIntern(Cursor & cur,
619 pit_type par, pos_type pos, bool setfont, bool boundary)
621 LASSERT(this == cur.text(), /**/);
622 cur.boundary(boundary);
623 setCursor(cur.top(), par, pos);
625 cur.setCurrentFont();
629 bool Text::checkAndActivateInset(Cursor & cur, bool front)
633 if (front && cur.pos() == cur.lastpos())
635 if (!front && cur.pos() == 0)
637 Inset * inset = front ? cur.nextInset() : cur.prevInset();
638 if (!inset || !inset->editable())
641 * Apparently, when entering an inset we are expected to be positioned
642 * *before* it in the containing paragraph, regardless of the direction
643 * from which we are entering. Otherwise, cursor placement goes awry,
644 * and when we exit from the beginning, we'll be placed *after* the
649 inset->edit(cur, front);
654 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
660 if (cur.pos() == cur.lastpos())
662 Paragraph & par = cur.paragraph();
663 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
664 if (!inset || !inset->editable())
666 inset->edit(cur, movingForward,
667 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
672 bool Text::cursorBackward(Cursor & cur)
674 // Tell BufferView to test for FitCursor in any case!
675 cur.updateFlags(Update::FitCursor);
677 // not at paragraph start?
679 // if on right side of boundary (i.e. not at paragraph end, but line end)
680 // -> skip it, i.e. set boundary to true, i.e. go only logically left
681 // there are some exceptions to ignore this: lineseps, newlines, spaces
683 // some effectless debug code to see the values in the debugger
684 bool bound = cur.boundary();
685 int rowpos = cur.textRow().pos();
687 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
688 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
689 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
691 if (!cur.boundary() &&
692 cur.textRow().pos() == cur.pos() &&
693 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
694 !cur.paragraph().isNewline(cur.pos() - 1) &&
695 !cur.paragraph().isSeparator(cur.pos() - 1)) {
696 return setCursor(cur, cur.pit(), cur.pos(), true, true);
699 // go left and try to enter inset
700 if (checkAndActivateInset(cur, false))
703 // normal character left
704 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
707 // move to the previous paragraph or do nothing
709 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
714 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
716 Cursor temp_cur = cur;
717 temp_cur.posVisLeft(skip_inset);
718 if (temp_cur.depth() > cur.depth()) {
722 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
723 true, temp_cur.boundary());
727 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
729 Cursor temp_cur = cur;
730 temp_cur.posVisRight(skip_inset);
731 if (temp_cur.depth() > cur.depth()) {
735 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
736 true, temp_cur.boundary());
740 bool Text::cursorForward(Cursor & cur)
742 // Tell BufferView to test for FitCursor in any case!
743 cur.updateFlags(Update::FitCursor);
745 // not at paragraph end?
746 if (cur.pos() != cur.lastpos()) {
747 // in front of editable inset, i.e. jump into it?
748 if (checkAndActivateInset(cur, true))
751 TextMetrics const & tm = cur.bv().textMetrics(this);
752 // if left of boundary -> just jump to right side
753 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
754 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
755 return setCursor(cur, cur.pit(), cur.pos(), true, false);
757 // next position is left of boundary,
758 // but go to next line for special cases like space, newline, linesep
760 // some effectless debug code to see the values in the debugger
761 int endpos = cur.textRow().endpos();
762 int lastpos = cur.lastpos();
764 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
765 bool newline = cur.paragraph().isNewline(cur.pos());
766 bool sep = cur.paragraph().isSeparator(cur.pos());
767 if (cur.pos() != cur.lastpos()) {
768 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
769 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
770 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
773 if (cur.textRow().endpos() == cur.pos() + 1 &&
774 cur.textRow().endpos() != cur.lastpos() &&
775 !cur.paragraph().isNewline(cur.pos()) &&
776 !cur.paragraph().isLineSeparator(cur.pos()) &&
777 !cur.paragraph().isSeparator(cur.pos())) {
778 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
781 // in front of RTL boundary? Stay on this side of the boundary because:
782 // ab|cDDEEFFghi -> abc|DDEEFFghi
783 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
784 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
787 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
790 // move to next paragraph
791 if (cur.pit() != cur.lastpit())
792 return setCursor(cur, cur.pit() + 1, 0, true, false);
797 bool Text::cursorUpParagraph(Cursor & cur)
799 bool updated = false;
801 updated = setCursor(cur, cur.pit(), 0);
802 else if (cur.pit() != 0)
803 updated = setCursor(cur, cur.pit() - 1, 0);
808 bool Text::cursorDownParagraph(Cursor & cur)
810 bool updated = false;
811 if (cur.pit() != cur.lastpit())
812 updated = setCursor(cur, cur.pit() + 1, 0);
814 updated = setCursor(cur, cur.pit(), cur.lastpos());
819 // fix the cursor `cur' after a characters has been deleted at `where'
820 // position. Called by deleteEmptyParagraphMechanism
821 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
823 // Do nothing if cursor is not in the paragraph where the
825 if (cur.pit() != where.pit())
828 // If cursor position is after the deletion place update it
829 if (cur.pos() > where.pos())
832 // Check also if we don't want to set the cursor on a spot behind the
833 // pagragraph because we erased the last character.
834 if (cur.pos() > cur.lastpos())
835 cur.pos() = cur.lastpos();
839 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
840 Cursor & old, bool & need_anchor_change)
842 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
844 Paragraph & oldpar = old.paragraph();
846 // We allow all kinds of "mumbo-jumbo" when freespacing.
847 if (oldpar.isFreeSpacing())
850 /* Ok I'll put some comments here about what is missing.
851 There are still some small problems that can lead to
852 double spaces stored in the document file or space at
853 the beginning of paragraphs(). This happens if you have
854 the cursor between to spaces and then save. Or if you
855 cut and paste and the selection have a space at the
856 beginning and then save right after the paste. (Lgb)
859 // If old.pos() == 0 and old.pos()(1) == LineSeparator
860 // delete the LineSeparator.
863 // If old.pos() == 1 and old.pos()(0) == LineSeparator
864 // delete the LineSeparator.
867 // Find a common inset and the corresponding depth.
869 for (; depth < cur.depth(); ++depth)
870 if (&old.inset() == &cur[depth].inset())
873 // Whether a common inset is found and whether the cursor is still in
874 // the same paragraph (possibly nested).
875 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
876 bool const same_par_pos = depth == cur.depth() - 1 && same_par
877 && old.pos() == cur[depth].pos();
879 // If the chars around the old cursor were spaces, delete one of them.
881 // Only if the cursor has really moved.
883 && old.pos() < oldpar.size()
884 && oldpar.isLineSeparator(old.pos())
885 && oldpar.isLineSeparator(old.pos() - 1)
886 && !oldpar.isDeleted(old.pos() - 1)
887 && !oldpar.isDeleted(old.pos())) {
888 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
889 // FIXME: This will not work anymore when we have multiple views of the same buffer
890 // In this case, we will have to correct also the cursors held by
891 // other bufferviews. It will probably be easier to do that in a more
892 // automated way in CursorSlice code. (JMarc 26/09/2001)
893 // correct all cursor parts
895 fixCursorAfterDelete(cur[depth], old.top());
896 need_anchor_change = true;
902 // only do our magic if we changed paragraph
906 // don't delete anything if this is the ONLY paragraph!
907 if (old.lastpit() == 0)
910 // Do not delete empty paragraphs with keepempty set.
911 if (oldpar.allowEmpty())
914 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
916 old.recordUndo(ATOMIC_UNDO,
917 max(old.pit() - 1, pit_type(0)),
918 min(old.pit() + 1, old.lastpit()));
919 ParagraphList & plist = old.text()->paragraphs();
920 bool const soa = oldpar.params().startOfAppendix();
921 plist.erase(boost::next(plist.begin(), old.pit()));
922 // do not lose start of appendix marker (bug 4212)
923 if (soa && old.pit() < pit_type(plist.size()))
924 plist[old.pit()].params().startOfAppendix(true);
926 // see #warning (FIXME?) above
927 if (cur.depth() >= old.depth()) {
928 CursorSlice & curslice = cur[old.depth() - 1];
929 if (&curslice.inset() == &old.inset()
930 && curslice.pit() > old.pit()) {
932 // since a paragraph has been deleted, all the
933 // insets after `old' have been copied and
934 // their address has changed. Therefore we
935 // need to `regenerate' cur. (JMarc)
936 cur.updateInsets(&(cur.bottom().inset()));
937 need_anchor_change = true;
943 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
944 need_anchor_change = true;
945 // We return true here because the Paragraph contents changed and
946 // we need a redraw before further action is processed.
954 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
956 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
958 for (pit_type pit = first; pit <= last; ++pit) {
959 Paragraph & par = pars_[pit];
961 // We allow all kinds of "mumbo-jumbo" when freespacing.
962 if (par.isFreeSpacing())
965 for (pos_type pos = 1; pos < par.size(); ++pos) {
966 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
967 && !par.isDeleted(pos - 1)) {
968 if (par.eraseChar(pos - 1, trackChanges)) {
974 // don't delete anything if this is the only remaining paragraph within the given range
975 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
979 // don't delete empty paragraphs with keepempty set
980 if (par.allowEmpty())
983 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
984 pars_.erase(boost::next(pars_.begin(), pit));
990 par.stripLeadingSpaces(trackChanges);
995 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
997 cur.recordUndo(ATOMIC_UNDO, first, last);
1001 void Text::recUndo(Cursor & cur, pit_type par) const
1003 cur.recordUndo(ATOMIC_UNDO, par, par);