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 // return past-the-last paragraph influenced by a layout change on pit
177 pit_type Text::undoSpan(pit_type pit)
179 pit_type const end = paragraphs().size();
180 pit_type nextpit = pit + 1;
183 //because of parindents
184 if (!pars_[pit].getDepth())
185 return boost::next(nextpit);
186 //because of depth constrains
187 for (; nextpit != end; ++pit, ++nextpit) {
188 if (!pars_[pit].getDepth())
195 void Text::setLayout(pit_type start, pit_type end,
196 docstring const & layout)
198 LASSERT(start != end, /**/);
200 Buffer const & buffer = owner_->buffer();
201 BufferParams const & bp = buffer.params();
202 Layout const & lyxlayout = bp.documentClass()[layout];
204 for (pit_type pit = start; pit != end; ++pit) {
205 Paragraph & par = pars_[pit];
206 par.applyLayout(lyxlayout);
207 if (lyxlayout.margintype == MARGIN_MANUAL)
208 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
213 // set layout over selection and make a total rebreak of those paragraphs
214 void Text::setLayout(Cursor & cur, docstring const & layout)
216 LASSERT(this == cur.text(), /**/);
218 pit_type start = cur.selBegin().pit();
219 pit_type end = cur.selEnd().pit() + 1;
220 pit_type undopit = undoSpan(end - 1);
221 recUndo(cur, start, undopit - 1);
222 setLayout(start, end, layout);
223 cur.forceBufferUpdate();
227 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
228 Paragraph const & par, int max_depth)
230 if (par.layout().labeltype == LABEL_BIBLIO)
232 int const depth = par.params().depth();
233 if (type == Text::INC_DEPTH && depth < max_depth)
235 if (type == Text::DEC_DEPTH && depth > 0)
241 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
243 LASSERT(this == cur.text(), /**/);
244 // this happens when selecting several cells in tabular (bug 2630)
245 if (cur.selBegin().idx() != cur.selEnd().idx())
248 pit_type const beg = cur.selBegin().pit();
249 pit_type const end = cur.selEnd().pit() + 1;
250 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
252 for (pit_type pit = beg; pit != end; ++pit) {
253 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
255 max_depth = pars_[pit].getMaxDepthAfter();
261 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
263 LASSERT(this == cur.text(), /**/);
264 pit_type const beg = cur.selBegin().pit();
265 pit_type const end = cur.selEnd().pit() + 1;
266 cur.recordUndoSelection();
267 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
269 for (pit_type pit = beg; pit != end; ++pit) {
270 Paragraph & par = pars_[pit];
271 if (lyx::changeDepthAllowed(type, par, max_depth)) {
272 int const depth = par.params().depth();
273 if (type == INC_DEPTH)
274 par.params().depth(depth + 1);
276 par.params().depth(depth - 1);
278 max_depth = par.getMaxDepthAfter();
280 // this handles the counter labels, and also fixes up
281 // depth values for follow-on (child) paragraphs
282 cur.forceBufferUpdate();
286 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
288 LASSERT(this == cur.text(), /**/);
289 // Set the current_font
290 // Determine basis font
292 pit_type pit = cur.pit();
293 if (cur.pos() < pars_[pit].beginOfBody())
294 layoutfont = labelFont(pars_[pit]);
296 layoutfont = layoutFont(pit);
298 // Update current font
299 cur.real_current_font.update(font,
300 cur.buffer()->params().language,
303 // Reduce to implicit settings
304 cur.current_font = cur.real_current_font;
305 cur.current_font.fontInfo().reduce(layoutfont);
306 // And resolve it completely
307 cur.real_current_font.fontInfo().realize(layoutfont);
309 // if there is no selection that's all we need to do
310 if (!cur.selection())
313 // Ok, we have a selection.
314 cur.recordUndoSelection();
318 // Toggling behaves as follows: We check the first character of the
319 // selection. If it's (say) got EMPH on, then we set to off; if off,
320 // then to on. With families and the like, we set it to INHERIT, if
321 // we already have it.
322 CursorSlice const & sl = cur.selBegin();
323 Text const & text = *sl.text();
324 Paragraph const & par = text.getPar(sl.pit());
326 // get font at the position
327 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
328 text.outerFont(sl.pit()));
329 FontInfo const & oldfi = oldfont.fontInfo();
331 FontInfo & newfi = newfont.fontInfo();
333 FontFamily newfam = newfi.family();
334 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
335 newfam == oldfi.family())
336 newfi.setFamily(INHERIT_FAMILY);
338 FontSeries newser = newfi.series();
339 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
340 newfi.setSeries(INHERIT_SERIES);
342 FontShape newshp = newfi.shape();
343 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
344 newshp == oldfi.shape())
345 newfi.setShape(INHERIT_SHAPE);
347 ColorCode newcol = newfi.color();
348 if (newcol != Color_none && newcol != Color_inherit
349 && newcol != Color_ignore && newcol == oldfi.color())
350 newfi.setColor(Color_none);
353 if (newfi.emph() == FONT_TOGGLE)
354 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
355 if (newfi.underbar() == FONT_TOGGLE)
356 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
357 if (newfi.strikeout() == FONT_TOGGLE)
358 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
359 if (newfi.uuline() == FONT_TOGGLE)
360 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
361 if (newfi.uwave() == FONT_TOGGLE)
362 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
363 if (newfi.noun() == FONT_TOGGLE)
364 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
365 if (newfi.number() == FONT_TOGGLE)
366 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
369 setFont(cur.bv(), cur.selectionBegin().top(),
370 cur.selectionEnd().top(), newfont);
374 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
375 CursorSlice const & end, Font const & font)
377 Buffer const & buffer = bv.buffer();
379 // Don't use forwardChar here as ditend might have
380 // pos() == lastpos() and forwardChar would miss it.
381 // Can't use forwardPos either as this descends into
383 Language const * language = buffer.params().language;
384 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
385 if (dit.pos() == dit.lastpos())
387 pit_type const pit = dit.pit();
388 pos_type const pos = dit.pos();
389 Inset * inset = pars_[pit].getInset(pos);
390 if (inset && inset->resetFontEdit()) {
391 // We need to propagate the font change to all
392 // text cells of the inset (bugs 1973, 6919).
393 setInsetFont(bv, pit, pos, font);
395 TextMetrics const & tm = bv.textMetrics(this);
396 Font f = tm.displayFont(pit, pos);
397 f.update(font, language);
398 setCharFont(pit, pos, f, tm.font_);
399 // font change may change language...
400 // spell checker has to know that
401 pars_[pit].requestSpellCheck(pos);
406 bool Text::cursorTop(Cursor & cur)
408 LASSERT(this == cur.text(), /**/);
409 return setCursor(cur, 0, 0);
413 bool Text::cursorBottom(Cursor & cur)
415 LASSERT(this == cur.text(), /**/);
416 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
420 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
422 LASSERT(this == cur.text(), /**/);
423 // If the mask is completely neutral, tell user
424 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
425 // Could only happen with user style
426 cur.message(_("No font change defined."));
430 // Try implicit word selection
431 // If there is a change in the language the implicit word selection
433 CursorSlice const resetCursor = cur.top();
434 bool const implicitSelection =
435 font.language() == ignore_language
436 && font.fontInfo().number() == FONT_IGNORE
437 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
440 setFont(cur, font, toggleall);
442 // Implicit selections are cleared afterwards
443 // and cursor is set to the original position.
444 if (implicitSelection) {
445 cur.clearSelection();
446 cur.top() = resetCursor;
452 docstring Text::getStringToIndex(Cursor const & cur)
454 LASSERT(this == cur.text(), /**/);
457 return cur.selectionAsString(false);
459 // Try implicit word selection. If there is a change
460 // in the language the implicit word selection is
463 selectWord(tmpcur, PREVIOUS_WORD);
465 if (!tmpcur.selection())
466 cur.message(_("Nothing to index!"));
467 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
468 cur.message(_("Cannot index more than one paragraph!"));
470 return tmpcur.selectionAsString(false);
476 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
479 pit_type offset = par_offset;
480 // Find first of same layout in sequence
481 while (!isFirstInSequence(offset)) {
482 offset = depthHook(offset, pars_[offset].getDepth());
485 // now apply label width string to every par
487 pit_type const end = pars_.size();
488 depth_type const depth = pars_[offset].getDepth();
489 Layout const & layout = pars_[offset].layout();
490 for (pit_type pit = offset; pit != end; ++pit) {
491 while (pars_[pit].getDepth() > depth) {
496 if (pars_[pit].getDepth() < depth)
498 if (pars_[pit].layout() != layout)
500 pars_[pit].setLabelWidthString(s);
505 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
507 LASSERT(cur.text(), /**/);
508 // make sure that the depth behind the selection are restored, too
509 pit_type undopit = undoSpan(cur.selEnd().pit());
510 recUndo(cur, cur.selBegin().pit(), undopit - 1);
513 string const argument = to_utf8(arg);
514 depth_type priordepth = -1;
516 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
518 Paragraph & par = pars_[pit];
519 ParagraphParameters params = par.params();
520 params.read(argument, merge);
521 // Changes to label width string apply to all paragraphs
522 // with same layout in a sequence.
523 // Do this only once for a selected range of paragraphs
524 // of the same layout and depth.
525 if (par.getDepth() != priordepth || par.layout() != priorlayout)
526 setLabelWidthStringToSequence(pit, params.labelWidthString());
527 par.params().apply(params, par.layout());
528 priordepth = par.getDepth();
529 priorlayout = par.layout();
534 //FIXME This is a little redundant now, but it's probably worth keeping,
535 //especially if we're going to go away from using serialization internally
537 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
539 LASSERT(cur.text(), /**/);
540 // make sure that the depth behind the selection are restored, too
541 pit_type undopit = undoSpan(cur.selEnd().pit());
542 recUndo(cur, cur.selBegin().pit(), undopit - 1);
544 depth_type priordepth = -1;
546 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
548 Paragraph & par = pars_[pit];
549 // Changes to label width string apply to all paragraphs
550 // with same layout in a sequence.
551 // Do this only once for a selected range of paragraphs
552 // of the same layout and depth.
553 if (par.getDepth() != priordepth || par.layout() != priorlayout)
554 setLabelWidthStringToSequence(pit,
555 par.params().labelWidthString());
556 par.params().apply(p, par.layout());
557 priordepth = par.getDepth();
558 priorlayout = par.layout();
563 // this really should just insert the inset and not move the cursor.
564 void Text::insertInset(Cursor & cur, Inset * inset)
566 LASSERT(this == cur.text(), /**/);
567 LASSERT(inset, /**/);
568 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
569 Change(cur.buffer()->params().trackChanges
570 ? Change::INSERTED : Change::UNCHANGED));
574 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
575 bool setfont, bool boundary)
577 TextMetrics const & tm = cur.bv().textMetrics(this);
578 bool const update_needed = !tm.contains(par);
580 setCursorIntern(cur, par, pos, setfont, boundary);
581 // FIXME There is a chance that we'll miss a screen update here.
582 // If so, then do DEPM and then check if cur wants an update and
583 // go ahead and do it, if so.
584 return cur.bv().checkDepm(cur, old) || update_needed;
588 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
590 LASSERT(par != int(paragraphs().size()), /**/);
594 // now some strict checking
595 Paragraph & para = getPar(par);
597 // None of these should happen, but we're scaredy-cats
599 lyxerr << "don't like -1" << endl;
600 LASSERT(false, /**/);
603 if (pos > para.size()) {
604 lyxerr << "don't like 1, pos: " << pos
605 << " size: " << para.size()
606 << " par: " << par << endl;
607 LASSERT(false, /**/);
612 void Text::setCursorIntern(Cursor & cur,
613 pit_type par, pos_type pos, bool setfont, bool boundary)
615 LASSERT(this == cur.text(), /**/);
616 cur.boundary(boundary);
617 setCursor(cur.top(), par, pos);
619 cur.setCurrentFont();
623 bool Text::checkAndActivateInset(Cursor & cur, bool front)
627 if (front && cur.pos() == cur.lastpos())
629 if (!front && cur.pos() == 0)
631 Inset * inset = front ? cur.nextInset() : cur.prevInset();
632 if (!inset || !inset->editable())
635 * Apparently, when entering an inset we are expected to be positioned
636 * *before* it in the containing paragraph, regardless of the direction
637 * from which we are entering. Otherwise, cursor placement goes awry,
638 * and when we exit from the beginning, we'll be placed *after* the
643 inset->edit(cur, front);
648 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
654 if (cur.pos() == cur.lastpos())
656 Paragraph & par = cur.paragraph();
657 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
658 if (!inset || !inset->editable())
660 inset->edit(cur, movingForward,
661 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
666 bool Text::cursorBackward(Cursor & cur)
668 // Tell BufferView to test for FitCursor in any case!
669 cur.screenUpdateFlags(Update::FitCursor);
671 // not at paragraph start?
673 // if on right side of boundary (i.e. not at paragraph end, but line end)
674 // -> skip it, i.e. set boundary to true, i.e. go only logically left
675 // there are some exceptions to ignore this: lineseps, newlines, spaces
677 // some effectless debug code to see the values in the debugger
678 bool bound = cur.boundary();
679 int rowpos = cur.textRow().pos();
681 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
682 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
683 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
685 if (!cur.boundary() &&
686 cur.textRow().pos() == cur.pos() &&
687 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
688 !cur.paragraph().isNewline(cur.pos() - 1) &&
689 !cur.paragraph().isSeparator(cur.pos() - 1)) {
690 return setCursor(cur, cur.pit(), cur.pos(), true, true);
693 // go left and try to enter inset
694 if (checkAndActivateInset(cur, false))
697 // normal character left
698 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
701 // move to the previous paragraph or do nothing
703 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
708 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
710 Cursor temp_cur = cur;
711 temp_cur.posVisLeft(skip_inset);
712 if (temp_cur.depth() > cur.depth()) {
716 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
717 true, temp_cur.boundary());
721 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
723 Cursor temp_cur = cur;
724 temp_cur.posVisRight(skip_inset);
725 if (temp_cur.depth() > cur.depth()) {
729 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
730 true, temp_cur.boundary());
734 bool Text::cursorForward(Cursor & cur)
736 // Tell BufferView to test for FitCursor in any case!
737 cur.screenUpdateFlags(Update::FitCursor);
739 // not at paragraph end?
740 if (cur.pos() != cur.lastpos()) {
741 // in front of editable inset, i.e. jump into it?
742 if (checkAndActivateInset(cur, true))
745 TextMetrics const & tm = cur.bv().textMetrics(this);
746 // if left of boundary -> just jump to right side
747 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
748 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
749 return setCursor(cur, cur.pit(), cur.pos(), true, false);
751 // next position is left of boundary,
752 // but go to next line for special cases like space, newline, linesep
754 // some effectless debug code to see the values in the debugger
755 int endpos = cur.textRow().endpos();
756 int lastpos = cur.lastpos();
758 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
759 bool newline = cur.paragraph().isNewline(cur.pos());
760 bool sep = cur.paragraph().isSeparator(cur.pos());
761 if (cur.pos() != cur.lastpos()) {
762 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
763 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
764 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
767 if (cur.textRow().endpos() == cur.pos() + 1 &&
768 cur.textRow().endpos() != cur.lastpos() &&
769 !cur.paragraph().isNewline(cur.pos()) &&
770 !cur.paragraph().isLineSeparator(cur.pos()) &&
771 !cur.paragraph().isSeparator(cur.pos())) {
772 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
775 // in front of RTL boundary? Stay on this side of the boundary because:
776 // ab|cDDEEFFghi -> abc|DDEEFFghi
777 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
778 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
781 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
784 // move to next paragraph
785 if (cur.pit() != cur.lastpit())
786 return setCursor(cur, cur.pit() + 1, 0, true, false);
791 bool Text::cursorUpParagraph(Cursor & cur)
793 bool updated = false;
795 updated = setCursor(cur, cur.pit(), 0);
796 else if (cur.pit() != 0)
797 updated = setCursor(cur, cur.pit() - 1, 0);
802 bool Text::cursorDownParagraph(Cursor & cur)
804 bool updated = false;
805 if (cur.pit() != cur.lastpit())
806 updated = setCursor(cur, cur.pit() + 1, 0);
808 updated = setCursor(cur, cur.pit(), cur.lastpos());
813 // fix the cursor `cur' after a characters has been deleted at `where'
814 // position. Called by deleteEmptyParagraphMechanism
815 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
817 // Do nothing if cursor is not in the paragraph where the
819 if (cur.pit() != where.pit())
822 // If cursor position is after the deletion place update it
823 if (cur.pos() > where.pos())
826 // Check also if we don't want to set the cursor on a spot behind the
827 // pagragraph because we erased the last character.
828 if (cur.pos() > cur.lastpos())
829 cur.pos() = cur.lastpos();
833 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
834 Cursor & old, bool & need_anchor_change)
836 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
838 Paragraph & oldpar = old.paragraph();
840 // We allow all kinds of "mumbo-jumbo" when freespacing.
841 if (oldpar.isFreeSpacing())
844 /* Ok I'll put some comments here about what is missing.
845 There are still some small problems that can lead to
846 double spaces stored in the document file or space at
847 the beginning of paragraphs(). This happens if you have
848 the cursor between to spaces and then save. Or if you
849 cut and paste and the selection have a space at the
850 beginning and then save right after the paste. (Lgb)
853 // If old.pos() == 0 and old.pos()(1) == LineSeparator
854 // delete the LineSeparator.
857 // If old.pos() == 1 and old.pos()(0) == LineSeparator
858 // delete the LineSeparator.
861 // Find a common inset and the corresponding depth.
863 for (; depth < cur.depth(); ++depth)
864 if (&old.inset() == &cur[depth].inset())
867 // Whether a common inset is found and whether the cursor is still in
868 // the same paragraph (possibly nested).
869 bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
870 bool const same_par_pos = depth == cur.depth() - 1 && same_par
871 && old.pos() == cur[depth].pos();
873 // If the chars around the old cursor were spaces, delete one of them.
875 // Only if the cursor has really moved.
877 && old.pos() < oldpar.size()
878 && oldpar.isLineSeparator(old.pos())
879 && oldpar.isLineSeparator(old.pos() - 1)
880 && !oldpar.isDeleted(old.pos() - 1)
881 && !oldpar.isDeleted(old.pos())) {
882 oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
883 // FIXME: This will not work anymore when we have multiple views of the same buffer
884 // In this case, we will have to correct also the cursors held by
885 // other bufferviews. It will probably be easier to do that in a more
886 // automated way in CursorSlice code. (JMarc 26/09/2001)
887 // correct all cursor parts
889 fixCursorAfterDelete(cur[depth], old.top());
890 need_anchor_change = true;
896 // only do our magic if we changed paragraph
900 // don't delete anything if this is the ONLY paragraph!
901 if (old.lastpit() == 0)
904 // Do not delete empty paragraphs with keepempty set.
905 if (oldpar.allowEmpty())
908 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
910 old.recordUndo(ATOMIC_UNDO,
911 max(old.pit() - 1, pit_type(0)),
912 min(old.pit() + 1, old.lastpit()));
913 ParagraphList & plist = old.text()->paragraphs();
914 bool const soa = oldpar.params().startOfAppendix();
915 plist.erase(boost::next(plist.begin(), old.pit()));
916 // do not lose start of appendix marker (bug 4212)
917 if (soa && old.pit() < pit_type(plist.size()))
918 plist[old.pit()].params().startOfAppendix(true);
920 // see #warning (FIXME?) above
921 if (cur.depth() >= old.depth()) {
922 CursorSlice & curslice = cur[old.depth() - 1];
923 if (&curslice.inset() == &old.inset()
924 && curslice.pit() > old.pit()) {
926 // since a paragraph has been deleted, all the
927 // insets after `old' have been copied and
928 // their address has changed. Therefore we
929 // need to `regenerate' cur. (JMarc)
930 cur.updateInsets(&(cur.bottom().inset()));
931 need_anchor_change = true;
937 if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
938 need_anchor_change = true;
939 // We return true here because the Paragraph contents changed and
940 // we need a redraw before further action is processed.
948 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
950 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
952 for (pit_type pit = first; pit <= last; ++pit) {
953 Paragraph & par = pars_[pit];
955 // We allow all kinds of "mumbo-jumbo" when freespacing.
956 if (par.isFreeSpacing())
959 for (pos_type pos = 1; pos < par.size(); ++pos) {
960 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
961 && !par.isDeleted(pos - 1)) {
962 if (par.eraseChar(pos - 1, trackChanges)) {
968 // don't delete anything if this is the only remaining paragraph
969 // within the given range. Note: Text::acceptOrRejectChanges()
970 // sets the cursor to 'first' after calling DEPM
974 // don't delete empty paragraphs with keepempty set
975 if (par.allowEmpty())
978 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
979 pars_.erase(boost::next(pars_.begin(), pit));
985 par.stripLeadingSpaces(trackChanges);
990 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
992 cur.recordUndo(ATOMIC_UNDO, first, last);
996 void Text::recUndo(Cursor & cur, pit_type par) const
998 cur.recordUndo(ATOMIC_UNDO, par, par);