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());
80 // It ought to be possible here just to use Inset::getLayout() and skip
81 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
82 // now, because Inset::getLayout() will return a default-constructed
83 // InsetLayout, and that e.g. sets the foreground color to red. So we
84 // need to do some work to make that possible.
85 InsetCollapsable const * icp = owner_->asInsetCollapsable();
88 FontInfo icf = icp->getLayout().font();
93 FontInfo font = layout.font;
94 // Realize with the fonts of lesser depth.
95 //font.realize(outerFont(pit));
96 font.realize(owner_->buffer().params().getFont().fontInfo());
102 // Note that this is supposed to return a fully realized font.
103 FontInfo Text::labelFont(Paragraph const & par) const
105 Buffer const & buffer = owner_->buffer();
106 Layout const & layout = par.layout();
108 if (!par.getDepth()) {
109 FontInfo lf = layout.reslabelfont;
110 // In case the default family has been customized
111 if (layout.labelfont.family() == INHERIT_FAMILY)
112 lf.setFamily(buffer.params().getFont().fontInfo().family());
116 FontInfo font = layout.labelfont;
117 // Realize with the fonts of lesser depth.
118 font.realize(buffer.params().getFont().fontInfo());
124 void Text::setCharFont(pit_type pit,
125 pos_type pos, Font const & fnt, Font const & display_font)
127 Buffer const & buffer = owner_->buffer();
129 Layout const & layout = pars_[pit].layout();
131 // Get concrete layout font to reduce against
134 if (pos < pars_[pit].beginOfBody())
135 layoutfont = layout.labelfont;
137 layoutfont = layout.font;
139 // Realize against environment font information
140 if (pars_[pit].getDepth()) {
142 while (!layoutfont.resolved() &&
143 tp != pit_type(paragraphs().size()) &&
144 pars_[tp].getDepth()) {
146 if (tp != pit_type(paragraphs().size()))
147 layoutfont.realize(pars_[tp].layout().font);
151 // Inside inset, apply the inset's font attributes if any
154 layoutfont.realize(display_font.fontInfo());
156 layoutfont.realize(buffer.params().getFont().fontInfo());
158 // Now, reduce font against full layout font
159 font.fontInfo().reduce(layoutfont);
161 pars_[pit].setFont(pos, font);
165 void Text::setInsetFont(BufferView const & bv, pit_type pit,
166 pos_type pos, Font const & font)
168 Inset * const inset = pars_[pit].getInset(pos);
169 LASSERT(inset && inset->resetFontEdit(), /**/);
171 CursorSlice::idx_type endidx = inset->nargs();
172 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
173 Text * text = cs.text();
175 // last position of the cell
176 CursorSlice cellend = cs;
177 cellend.pit() = cellend.lastpit();
178 cellend.pos() = cellend.lastpos();
179 text->setFont(bv, cs, cellend, font);
185 // return past-the-last paragraph influenced by a layout change on pit
186 pit_type Text::undoSpan(pit_type pit)
188 pit_type const end = paragraphs().size();
189 pit_type nextpit = pit + 1;
192 //because of parindents
193 if (!pars_[pit].getDepth())
194 return boost::next(nextpit);
195 //because of depth constrains
196 for (; nextpit != end; ++pit, ++nextpit) {
197 if (!pars_[pit].getDepth())
204 void Text::setLayout(pit_type start, pit_type end,
205 docstring const & layout)
207 LASSERT(start != end, /**/);
209 Buffer const & buffer = owner_->buffer();
210 BufferParams const & bp = buffer.params();
211 Layout const & lyxlayout = bp.documentClass()[layout];
213 for (pit_type pit = start; pit != end; ++pit) {
214 Paragraph & par = pars_[pit];
215 par.applyLayout(lyxlayout);
216 if (lyxlayout.margintype == MARGIN_MANUAL)
217 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
222 // set layout over selection and make a total rebreak of those paragraphs
223 void Text::setLayout(Cursor & cur, docstring const & layout)
225 LASSERT(this == cur.text(), /**/);
227 pit_type start = cur.selBegin().pit();
228 pit_type end = cur.selEnd().pit() + 1;
229 pit_type undopit = undoSpan(end - 1);
230 recUndo(cur, start, undopit - 1);
231 setLayout(start, end, layout);
232 cur.forceBufferUpdate();
236 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
237 Paragraph const & par, int max_depth)
239 if (par.layout().labeltype == LABEL_BIBLIO)
241 int const depth = par.params().depth();
242 if (type == Text::INC_DEPTH && depth < max_depth)
244 if (type == Text::DEC_DEPTH && depth > 0)
250 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
252 LASSERT(this == cur.text(), /**/);
253 // this happens when selecting several cells in tabular (bug 2630)
254 if (cur.selBegin().idx() != cur.selEnd().idx())
257 pit_type const beg = cur.selBegin().pit();
258 pit_type const end = cur.selEnd().pit() + 1;
259 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
261 for (pit_type pit = beg; pit != end; ++pit) {
262 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
264 max_depth = pars_[pit].getMaxDepthAfter();
270 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
272 LASSERT(this == cur.text(), /**/);
273 pit_type const beg = cur.selBegin().pit();
274 pit_type const end = cur.selEnd().pit() + 1;
275 cur.recordUndoSelection();
276 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
278 for (pit_type pit = beg; pit != end; ++pit) {
279 Paragraph & par = pars_[pit];
280 if (lyx::changeDepthAllowed(type, par, max_depth)) {
281 int const depth = par.params().depth();
282 if (type == INC_DEPTH)
283 par.params().depth(depth + 1);
285 par.params().depth(depth - 1);
287 max_depth = par.getMaxDepthAfter();
289 // this handles the counter labels, and also fixes up
290 // depth values for follow-on (child) paragraphs
291 cur.forceBufferUpdate();
295 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
297 LASSERT(this == cur.text(), /**/);
298 // Set the current_font
299 // Determine basis font
301 pit_type pit = cur.pit();
302 if (cur.pos() < pars_[pit].beginOfBody())
303 layoutfont = labelFont(pars_[pit]);
305 layoutfont = layoutFont(pit);
307 // Update current font
308 cur.real_current_font.update(font,
309 cur.buffer()->params().language,
312 // Reduce to implicit settings
313 cur.current_font = cur.real_current_font;
314 cur.current_font.fontInfo().reduce(layoutfont);
315 // And resolve it completely
316 cur.real_current_font.fontInfo().realize(layoutfont);
318 // if there is no selection that's all we need to do
319 if (!cur.selection())
322 // Ok, we have a selection.
323 cur.recordUndoSelection();
327 // Toggling behaves as follows: We check the first character of the
328 // selection. If it's (say) got EMPH on, then we set to off; if off,
329 // then to on. With families and the like, we set it to INHERIT, if
330 // we already have it.
331 CursorSlice const & sl = cur.selBegin();
332 Text const & text = *sl.text();
333 Paragraph const & par = text.getPar(sl.pit());
335 // get font at the position
336 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
337 text.outerFont(sl.pit()));
338 FontInfo const & oldfi = oldfont.fontInfo();
340 FontInfo & newfi = newfont.fontInfo();
342 FontFamily newfam = newfi.family();
343 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
344 newfam == oldfi.family())
345 newfi.setFamily(INHERIT_FAMILY);
347 FontSeries newser = newfi.series();
348 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
349 newfi.setSeries(INHERIT_SERIES);
351 FontShape newshp = newfi.shape();
352 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
353 newshp == oldfi.shape())
354 newfi.setShape(INHERIT_SHAPE);
356 ColorCode newcol = newfi.color();
357 if (newcol != Color_none && newcol != Color_inherit
358 && newcol != Color_ignore && newcol == oldfi.color())
359 newfi.setColor(Color_none);
362 if (newfi.emph() == FONT_TOGGLE)
363 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
364 if (newfi.underbar() == FONT_TOGGLE)
365 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
366 if (newfi.strikeout() == FONT_TOGGLE)
367 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
368 if (newfi.uuline() == FONT_TOGGLE)
369 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
370 if (newfi.uwave() == FONT_TOGGLE)
371 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
372 if (newfi.noun() == FONT_TOGGLE)
373 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
374 if (newfi.number() == FONT_TOGGLE)
375 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
378 setFont(cur.bv(), cur.selectionBegin().top(),
379 cur.selectionEnd().top(), newfont);
383 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
384 CursorSlice const & end, Font const & font)
386 Buffer const & buffer = bv.buffer();
388 // Don't use forwardChar here as ditend might have
389 // pos() == lastpos() and forwardChar would miss it.
390 // Can't use forwardPos either as this descends into
392 Language const * language = buffer.params().language;
393 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
394 if (dit.pos() == dit.lastpos())
396 pit_type const pit = dit.pit();
397 pos_type const pos = dit.pos();
398 Inset * inset = pars_[pit].getInset(pos);
399 if (inset && inset->resetFontEdit()) {
400 // We need to propagate the font change to all
401 // text cells of the inset (bugs 1973, 6919).
402 setInsetFont(bv, pit, pos, font);
404 TextMetrics const & tm = bv.textMetrics(this);
405 Font f = tm.displayFont(pit, pos);
406 f.update(font, language);
407 setCharFont(pit, pos, f, tm.font_);
408 // font change may change language...
409 // spell checker has to know that
410 pars_[pit].requestSpellCheck(pos);
415 bool Text::cursorTop(Cursor & cur)
417 LASSERT(this == cur.text(), /**/);
418 return setCursor(cur, 0, 0);
422 bool Text::cursorBottom(Cursor & cur)
424 LASSERT(this == cur.text(), /**/);
425 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
429 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
431 LASSERT(this == cur.text(), /**/);
432 // If the mask is completely neutral, tell user
433 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
434 // Could only happen with user style
435 cur.message(_("No font change defined."));
439 // Try implicit word selection
440 // If there is a change in the language the implicit word selection
442 CursorSlice const resetCursor = cur.top();
443 bool const implicitSelection =
444 font.language() == ignore_language
445 && font.fontInfo().number() == FONT_IGNORE
446 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
449 setFont(cur, font, toggleall);
451 // Implicit selections are cleared afterwards
452 // and cursor is set to the original position.
453 if (implicitSelection) {
454 cur.clearSelection();
455 cur.top() = resetCursor;
461 docstring Text::getStringToIndex(Cursor const & cur)
463 LASSERT(this == cur.text(), /**/);
466 return cur.selectionAsString(false);
468 // Try implicit word selection. If there is a change
469 // in the language the implicit word selection is
472 selectWord(tmpcur, PREVIOUS_WORD);
474 if (!tmpcur.selection())
475 cur.message(_("Nothing to index!"));
476 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
477 cur.message(_("Cannot index more than one paragraph!"));
479 return tmpcur.selectionAsString(false);
485 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
488 pit_type offset = par_offset;
489 // Find first of same layout in sequence
490 while (!isFirstInSequence(offset)) {
491 offset = depthHook(offset, pars_[offset].getDepth());
494 // now apply label width string to every par
496 pit_type const end = pars_.size();
497 depth_type const depth = pars_[offset].getDepth();
498 Layout const & layout = pars_[offset].layout();
499 for (pit_type pit = offset; pit != end; ++pit) {
500 while (pars_[pit].getDepth() > depth) {
505 if (pars_[pit].getDepth() < depth)
507 if (pars_[pit].layout() != layout)
509 pars_[pit].setLabelWidthString(s);
514 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
516 LASSERT(cur.text(), /**/);
517 // make sure that the depth behind the selection are restored, too
518 pit_type undopit = undoSpan(cur.selEnd().pit());
519 recUndo(cur, cur.selBegin().pit(), undopit - 1);
522 string const argument = to_utf8(arg);
523 depth_type priordepth = -1;
525 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
527 Paragraph & par = pars_[pit];
528 ParagraphParameters params = par.params();
529 params.read(argument, merge);
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.
534 if (par.getDepth() != priordepth || par.layout() != priorlayout)
535 setLabelWidthStringToSequence(pit, params.labelWidthString());
536 par.params().apply(params, par.layout());
537 priordepth = par.getDepth();
538 priorlayout = par.layout();
543 //FIXME This is a little redundant now, but it's probably worth keeping,
544 //especially if we're going to go away from using serialization internally
546 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
548 LASSERT(cur.text(), /**/);
549 // make sure that the depth behind the selection are restored, too
550 pit_type undopit = undoSpan(cur.selEnd().pit());
551 recUndo(cur, cur.selBegin().pit(), undopit - 1);
553 depth_type priordepth = -1;
555 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
557 Paragraph & par = pars_[pit];
558 // Changes to label width string apply to all paragraphs
559 // with same layout in a sequence.
560 // Do this only once for a selected range of paragraphs
561 // of the same layout and depth.
562 if (par.getDepth() != priordepth || par.layout() != priorlayout)
563 setLabelWidthStringToSequence(pit,
564 par.params().labelWidthString());
565 par.params().apply(p, par.layout());
566 priordepth = par.getDepth();
567 priorlayout = par.layout();
572 // this really should just insert the inset and not move the cursor.
573 void Text::insertInset(Cursor & cur, Inset * inset)
575 LASSERT(this == cur.text(), /**/);
576 LASSERT(inset, /**/);
577 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
578 Change(cur.buffer()->params().trackChanges
579 ? Change::INSERTED : Change::UNCHANGED));
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 // FIXME There is a chance that we'll miss a screen update here.
591 // If so, then do DEPM and then check if cur wants an update and
592 // go ahead and do it, if so.
593 return cur.bv().checkDepm(cur, old) || update_needed;
597 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
599 LASSERT(par != int(paragraphs().size()), /**/);
603 // now some strict checking
604 Paragraph & para = getPar(par);
606 // None of these should happen, but we're scaredy-cats
608 lyxerr << "don't like -1" << endl;
609 LASSERT(false, /**/);
612 if (pos > para.size()) {
613 lyxerr << "don't like 1, pos: " << pos
614 << " size: " << para.size()
615 << " par: " << par << endl;
616 LASSERT(false, /**/);
621 void Text::setCursorIntern(Cursor & cur,
622 pit_type par, pos_type pos, bool setfont, bool boundary)
624 LASSERT(this == cur.text(), /**/);
625 cur.boundary(boundary);
626 setCursor(cur.top(), par, pos);
628 cur.setCurrentFont();
632 bool Text::checkAndActivateInset(Cursor & cur, bool front)
636 if (front && cur.pos() == cur.lastpos())
638 if (!front && cur.pos() == 0)
640 Inset * inset = front ? cur.nextInset() : cur.prevInset();
641 if (!inset || !inset->editable())
644 * Apparently, when entering an inset we are expected to be positioned
645 * *before* it in the containing paragraph, regardless of the direction
646 * from which we are entering. Otherwise, cursor placement goes awry,
647 * and when we exit from the beginning, we'll be placed *after* the
652 inset->edit(cur, front);
657 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
663 if (cur.pos() == cur.lastpos())
665 Paragraph & par = cur.paragraph();
666 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
667 if (!inset || !inset->editable())
669 inset->edit(cur, movingForward,
670 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
675 bool Text::cursorBackward(Cursor & cur)
677 // Tell BufferView to test for FitCursor in any case!
678 cur.screenUpdateFlags(Update::FitCursor);
680 // not at paragraph start?
682 // if on right side of boundary (i.e. not at paragraph end, but line end)
683 // -> skip it, i.e. set boundary to true, i.e. go only logically left
684 // there are some exceptions to ignore this: lineseps, newlines, spaces
686 // some effectless debug code to see the values in the debugger
687 bool bound = cur.boundary();
688 int rowpos = cur.textRow().pos();
690 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
691 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
692 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
694 if (!cur.boundary() &&
695 cur.textRow().pos() == cur.pos() &&
696 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
697 !cur.paragraph().isNewline(cur.pos() - 1) &&
698 !cur.paragraph().isSeparator(cur.pos() - 1)) {
699 return setCursor(cur, cur.pit(), cur.pos(), true, true);
702 // go left and try to enter inset
703 if (checkAndActivateInset(cur, false))
706 // normal character left
707 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
710 // move to the previous paragraph or do nothing
712 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
717 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
719 Cursor temp_cur = cur;
720 temp_cur.posVisLeft(skip_inset);
721 if (temp_cur.depth() > cur.depth()) {
725 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
726 true, temp_cur.boundary());
730 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
732 Cursor temp_cur = cur;
733 temp_cur.posVisRight(skip_inset);
734 if (temp_cur.depth() > cur.depth()) {
738 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
739 true, temp_cur.boundary());
743 bool Text::cursorForward(Cursor & cur)
745 // Tell BufferView to test for FitCursor in any case!
746 cur.screenUpdateFlags(Update::FitCursor);
748 // not at paragraph end?
749 if (cur.pos() != cur.lastpos()) {
750 // in front of editable inset, i.e. jump into it?
751 if (checkAndActivateInset(cur, true))
754 TextMetrics const & tm = cur.bv().textMetrics(this);
755 // if left of boundary -> just jump to right side
756 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
757 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
758 return setCursor(cur, cur.pit(), cur.pos(), true, false);
760 // next position is left of boundary,
761 // but go to next line for special cases like space, newline, linesep
763 // some effectless debug code to see the values in the debugger
764 int endpos = cur.textRow().endpos();
765 int lastpos = cur.lastpos();
767 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
768 bool newline = cur.paragraph().isNewline(cur.pos());
769 bool sep = cur.paragraph().isSeparator(cur.pos());
770 if (cur.pos() != cur.lastpos()) {
771 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
772 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
773 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
776 if (cur.textRow().endpos() == cur.pos() + 1 &&
777 cur.textRow().endpos() != cur.lastpos() &&
778 !cur.paragraph().isNewline(cur.pos()) &&
779 !cur.paragraph().isLineSeparator(cur.pos()) &&
780 !cur.paragraph().isSeparator(cur.pos())) {
781 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
784 // in front of RTL boundary? Stay on this side of the boundary because:
785 // ab|cDDEEFFghi -> abc|DDEEFFghi
786 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
787 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
790 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
793 // move to next paragraph
794 if (cur.pit() != cur.lastpit())
795 return setCursor(cur, cur.pit() + 1, 0, true, false);
800 bool Text::cursorUpParagraph(Cursor & cur)
802 bool updated = false;
804 updated = setCursor(cur, cur.pit(), 0);
805 else if (cur.pit() != 0)
806 updated = setCursor(cur, cur.pit() - 1, 0);
811 bool Text::cursorDownParagraph(Cursor & cur)
813 bool updated = false;
814 if (cur.pit() != cur.lastpit())
815 updated = setCursor(cur, cur.pit() + 1, 0);
817 updated = setCursor(cur, cur.pit(), cur.lastpos());
822 // fix the cursor `cur' after a characters has been deleted at `where'
823 // position. Called by deleteEmptyParagraphMechanism
824 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
826 // Do nothing if cursor is not in the paragraph where the
828 if (cur.pit() != where.pit())
831 // If cursor position is after the deletion place update it
832 if (cur.pos() > where.pos())
835 // Check also if we don't want to set the cursor on a spot behind the
836 // pagragraph because we erased the last character.
837 if (cur.pos() > cur.lastpos())
838 cur.pos() = cur.lastpos();
842 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
843 Cursor & old, bool & need_anchor_change)
845 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
847 Paragraph & oldpar = old.paragraph();
849 // We allow all kinds of "mumbo-jumbo" when freespacing.
850 if (oldpar.isFreeSpacing())
853 /* Ok I'll put some comments here about what is missing.
854 There are still some small problems that can lead to
855 double spaces stored in the document file or space at
856 the beginning of paragraphs(). This happens if you have
857 the cursor between to spaces and then save. Or if you
858 cut and paste and the selection have a space at the
859 beginning and then save right after the paste. (Lgb)
862 // If old.pos() == 0 and old.pos()(1) == LineSeparator
863 // delete the LineSeparator.
866 // If old.pos() == 1 and old.pos()(0) == LineSeparator
867 // delete the LineSeparator.
870 // Find a common inset and the corresponding depth.
872 for (; depth < cur.depth(); ++depth)
873 if (&old.inset() == &cur[depth].inset())
876 // Whether a common inset is found and whether the cursor is still in
877 // the same paragraph (possibly nested).
878 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
879 bool const same_par_pos = depth == cur.depth() - 1 && same_par
880 && old.pos() == cur[depth].pos();
882 // If the chars around the old cursor were spaces, delete one of them.
884 // Only if the cursor has really moved.
886 && old.pos() < oldpar.size()
887 && oldpar.isLineSeparator(old.pos())
888 && oldpar.isLineSeparator(old.pos() - 1)
889 && !oldpar.isDeleted(old.pos() - 1)
890 && !oldpar.isDeleted(old.pos())) {
891 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
892 // FIXME: This will not work anymore when we have multiple views of the same buffer
893 // In this case, we will have to correct also the cursors held by
894 // other bufferviews. It will probably be easier to do that in a more
895 // automated way in CursorSlice code. (JMarc 26/09/2001)
896 // correct all cursor parts
898 fixCursorAfterDelete(cur[depth], old.top());
899 need_anchor_change = true;
905 // only do our magic if we changed paragraph
909 // don't delete anything if this is the ONLY paragraph!
910 if (old.lastpit() == 0)
913 // Do not delete empty paragraphs with keepempty set.
914 if (oldpar.allowEmpty())
917 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
919 old.recordUndo(ATOMIC_UNDO,
920 max(old.pit() - 1, pit_type(0)),
921 min(old.pit() + 1, old.lastpit()));
922 ParagraphList & plist = old.text()->paragraphs();
923 bool const soa = oldpar.params().startOfAppendix();
924 plist.erase(boost::next(plist.begin(), old.pit()));
925 // do not lose start of appendix marker (bug 4212)
926 if (soa && old.pit() < pit_type(plist.size()))
927 plist[old.pit()].params().startOfAppendix(true);
929 // see #warning (FIXME?) above
930 if (cur.depth() >= old.depth()) {
931 CursorSlice & curslice = cur[old.depth() - 1];
932 if (&curslice.inset() == &old.inset()
933 && curslice.pit() > old.pit()) {
935 // since a paragraph has been deleted, all the
936 // insets after `old' have been copied and
937 // their address has changed. Therefore we
938 // need to `regenerate' cur. (JMarc)
939 cur.updateInsets(&(cur.bottom().inset()));
940 need_anchor_change = true;
946 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
947 need_anchor_change = true;
948 // We return true here because the Paragraph contents changed and
949 // we need a redraw before further action is processed.
957 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
959 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
961 for (pit_type pit = first; pit <= last; ++pit) {
962 Paragraph & par = pars_[pit];
964 // We allow all kinds of "mumbo-jumbo" when freespacing.
965 if (par.isFreeSpacing())
968 for (pos_type pos = 1; pos < par.size(); ++pos) {
969 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
970 && !par.isDeleted(pos - 1)) {
971 if (par.eraseChar(pos - 1, trackChanges)) {
977 // don't delete anything if this is the only remaining paragraph
978 // within the given range. Note: Text::acceptOrRejectChanges()
979 // sets the cursor to 'first' after calling DEPM
983 // don't delete empty paragraphs with keepempty set
984 if (par.allowEmpty())
987 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
988 pars_.erase(boost::next(pars_.begin(), pit));
994 par.stripLeadingSpaces(trackChanges);
999 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1001 cur.recordUndo(ATOMIC_UNDO, first, last);
1005 void Text::recUndo(Cursor & cur, pit_type par) const
1007 cur.recordUndo(ATOMIC_UNDO, par, par);