3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
33 #include "CutAndPaste.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
43 #include "Paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "ParIterator.h"
48 #include "ServerSocket.h"
52 #include "frontends/FontMetrics.h"
54 #include "insets/InsetEnvironment.h"
56 #include "mathed/InsetMathHull.h"
58 #include "support/textutils.h"
60 #include <boost/current_function.hpp>
65 using std::ostringstream;
69 using std::istringstream;
74 : autoBreakRows_(false)
78 bool Text::isMainText(Buffer const & buffer) const
80 return &buffer.text() == this;
84 // Gets the fully instantiated font at a given position in a paragraph
85 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
86 // The difference is that this one is used for displaying, and thus we
87 // are allowed to make cosmetic improvements. For instance make footnotes
89 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
90 pos_type const pos) const
92 BOOST_ASSERT(pos >= 0);
94 LayoutPtr const & layout = par.layout();
96 BufferParams const & params = buffer.params();
97 pos_type const body_pos = par.beginOfBody();
99 // We specialize the 95% common case:
100 if (!par.getDepth()) {
101 Font f = par.getFontSettings(params, pos);
102 if (!isMainText(buffer))
103 applyOuterFont(buffer, f);
106 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
107 lf = layout->labelfont;
108 rlf = layout->reslabelfont;
111 rlf = layout->resfont;
113 // In case the default family has been customized
114 if (lf.family() == Font::INHERIT_FAMILY)
115 rlf.setFamily(params.getFont().family());
116 return f.realize(rlf);
119 // The uncommon case need not be optimized as much
122 layoutfont = layout->labelfont;
124 layoutfont = layout->font;
126 Font font = par.getFontSettings(params, pos);
127 font.realize(layoutfont);
129 if (!isMainText(buffer))
130 applyOuterFont(buffer, font);
132 // Find the pit value belonging to paragraph. This will not break
133 // even if pars_ would not be a vector anymore.
134 // Performance appears acceptable.
136 pit_type pit = pars_.size();
137 for (pit_type it = 0; it < pit; ++it)
138 if (&pars_[it] == &par) {
142 // Realize against environment font information
143 // NOTE: the cast to pit_type should be removed when pit_type
144 // changes to a unsigned integer.
145 if (pit < pit_type(pars_.size()))
146 font.realize(outerFont(pit, pars_));
148 // Realize with the fonts of lesser depth.
149 font.realize(params.getFont());
154 // There are currently two font mechanisms in LyX:
155 // 1. The font attributes in a lyxtext, and
156 // 2. The inset-specific font properties, defined in an inset's
157 // metrics() and draw() methods and handed down the inset chain through
158 // the pi/mi parameters, and stored locally in a lyxtext in font_.
159 // This is where the two are integrated in the final fully realized
161 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
163 lf.reduce(buffer.params().getFont());
165 lf.setLanguage(font.language());
170 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
172 LayoutPtr const & layout = pars_[pit].layout();
174 if (!pars_[pit].getDepth()) {
175 Font lf = layout->resfont;
176 // In case the default family has been customized
177 if (layout->font.family() == Font::INHERIT_FAMILY)
178 lf.setFamily(buffer.params().getFont().family());
182 Font font = layout->font;
183 // Realize with the fonts of lesser depth.
184 //font.realize(outerFont(pit, paragraphs()));
185 font.realize(buffer.params().getFont());
191 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
193 LayoutPtr const & layout = par.layout();
195 if (!par.getDepth()) {
196 Font lf = layout->reslabelfont;
197 // In case the default family has been customized
198 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
199 lf.setFamily(buffer.params().getFont().family());
203 Font font = layout->labelfont;
204 // Realize with the fonts of lesser depth.
205 font.realize(buffer.params().getFont());
211 void Text::setCharFont(Buffer const & buffer, pit_type pit,
212 pos_type pos, Font const & fnt)
215 LayoutPtr const & layout = pars_[pit].layout();
217 // Get concrete layout font to reduce against
220 if (pos < pars_[pit].beginOfBody())
221 layoutfont = layout->labelfont;
223 layoutfont = layout->font;
225 // Realize against environment font information
226 if (pars_[pit].getDepth()) {
228 while (!layoutfont.resolved() &&
229 tp != pit_type(paragraphs().size()) &&
230 pars_[tp].getDepth()) {
231 tp = outerHook(tp, paragraphs());
232 if (tp != pit_type(paragraphs().size()))
233 layoutfont.realize(pars_[tp].layout()->font);
237 // Inside inset, apply the inset's font attributes if any
239 if (!isMainText(buffer))
240 layoutfont.realize(font_);
242 layoutfont.realize(buffer.params().getFont());
244 // Now, reduce font against full layout font
245 font.reduce(layoutfont);
247 pars_[pit].setFont(pos, font);
251 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
252 pos_type pos, Font const & font, bool toggleall)
254 BOOST_ASSERT(pars_[pit].isInset(pos) &&
255 pars_[pit].getInset(pos)->noFontChange());
257 Inset * const inset = pars_[pit].getInset(pos);
258 CursorSlice::idx_type endidx = inset->nargs();
259 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
260 Text * text = cs.text();
262 // last position of the cell
263 CursorSlice cellend = cs;
264 cellend.pit() = cellend.lastpit();
265 cellend.pos() = cellend.lastpos();
266 text->setFont(buffer, cs, cellend, font, toggleall);
272 // return past-the-last paragraph influenced by a layout change on pit
273 pit_type Text::undoSpan(pit_type pit)
275 pit_type end = paragraphs().size();
276 pit_type nextpit = pit + 1;
279 //because of parindents
280 if (!pars_[pit].getDepth())
281 return boost::next(nextpit);
282 //because of depth constrains
283 for (; nextpit != end; ++pit, ++nextpit) {
284 if (!pars_[pit].getDepth())
291 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
292 docstring const & layout)
294 BOOST_ASSERT(start != end);
296 BufferParams const & bufparams = buffer.params();
297 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
299 for (pit_type pit = start; pit != end; ++pit) {
300 Paragraph & par = pars_[pit];
301 par.applyLayout(lyxlayout);
302 if (lyxlayout->margintype == MARGIN_MANUAL)
303 par.setLabelWidthString(par.translateIfPossible(
304 lyxlayout->labelstring(), buffer.params()));
309 // set layout over selection and make a total rebreak of those paragraphs
310 void Text::setLayout(Cursor & cur, docstring const & layout)
312 BOOST_ASSERT(this == cur.text());
313 // special handling of new environment insets
314 BufferView & bv = cur.bv();
315 BufferParams const & params = bv.buffer().params();
316 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
317 if (lyxlayout->is_environment) {
318 // move everything in a new environment inset
319 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
320 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
321 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
322 lyx::dispatch(FuncRequest(LFUN_CUT));
323 Inset * inset = new InsetEnvironment(params, layout);
324 insertInset(cur, inset);
325 //inset->edit(cur, true);
326 //lyx::dispatch(FuncRequest(LFUN_PASTE));
330 pit_type start = cur.selBegin().pit();
331 pit_type end = cur.selEnd().pit() + 1;
332 pit_type undopit = undoSpan(end - 1);
333 recUndo(cur, start, undopit - 1);
334 setLayout(cur.buffer(), start, end, layout);
335 updateLabels(cur.buffer());
339 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
340 Paragraph const & par, int max_depth)
342 if (par.layout()->labeltype == LABEL_BIBLIO)
344 int const depth = par.params().depth();
345 if (type == Text::INC_DEPTH && depth < max_depth)
347 if (type == Text::DEC_DEPTH && depth > 0)
353 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
355 BOOST_ASSERT(this == cur.text());
356 // this happens when selecting several cells in tabular (bug 2630)
357 if (cur.selBegin().idx() != cur.selEnd().idx())
360 pit_type const beg = cur.selBegin().pit();
361 pit_type const end = cur.selEnd().pit() + 1;
362 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
364 for (pit_type pit = beg; pit != end; ++pit) {
365 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
367 max_depth = pars_[pit].getMaxDepthAfter();
373 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
375 BOOST_ASSERT(this == cur.text());
376 pit_type const beg = cur.selBegin().pit();
377 pit_type const end = cur.selEnd().pit() + 1;
378 recordUndoSelection(cur);
379 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
381 for (pit_type pit = beg; pit != end; ++pit) {
382 Paragraph & par = pars_[pit];
383 if (lyx::changeDepthAllowed(type, par, max_depth)) {
384 int const depth = par.params().depth();
385 if (type == INC_DEPTH)
386 par.params().depth(depth + 1);
388 par.params().depth(depth - 1);
390 max_depth = par.getMaxDepthAfter();
392 // this handles the counter labels, and also fixes up
393 // depth values for follow-on (child) paragraphs
394 updateLabels(cur.buffer());
398 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
400 BOOST_ASSERT(this == cur.text());
401 // Set the current_font
402 // Determine basis font
404 pit_type pit = cur.pit();
405 if (cur.pos() < pars_[pit].beginOfBody())
406 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
408 layoutfont = getLayoutFont(cur.buffer(), pit);
410 // Update current font
411 cur.real_current_font.update(font,
412 cur.buffer().params().language,
415 // Reduce to implicit settings
416 cur.current_font = cur.real_current_font;
417 cur.current_font.reduce(layoutfont);
418 // And resolve it completely
419 cur.real_current_font.realize(layoutfont);
421 // if there is no selection that's all we need to do
422 if (!cur.selection())
425 // Ok, we have a selection.
426 recordUndoSelection(cur);
428 setFont(cur.buffer(), cur.selectionBegin().top(),
429 cur.selectionEnd().top(), font, toggleall);
433 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
434 CursorSlice const & end, Font const & font,
437 // Don't use forwardChar here as ditend might have
438 // pos() == lastpos() and forwardChar would miss it.
439 // Can't use forwardPos either as this descends into
441 Language const * language = buffer.params().language;
442 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
443 if (dit.pos() != dit.lastpos()) {
444 pit_type const pit = dit.pit();
445 pos_type const pos = dit.pos();
446 if (pars_[pit].isInset(pos) &&
447 pars_[pit].getInset(pos)->noFontChange())
448 // We need to propagate the font change to all
449 // text cells of the inset (bug 1973).
450 // FIXME: This should change, see documentation
451 // of noFontChange in Inset.h
452 setInsetFont(buffer, pit, pos, font, toggleall);
453 Font f = getFont(buffer, dit.paragraph(), pos);
454 f.update(font, language, toggleall);
455 setCharFont(buffer, pit, pos, f);
461 bool Text::cursorTop(Cursor & cur)
463 BOOST_ASSERT(this == cur.text());
464 return setCursor(cur, 0, 0);
468 bool Text::cursorBottom(Cursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
475 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
477 BOOST_ASSERT(this == cur.text());
478 // If the mask is completely neutral, tell user
479 if (font == Font(Font::ALL_IGNORE)) {
480 // Could only happen with user style
481 cur.message(_("No font change defined."));
485 // Try implicit word selection
486 // If there is a change in the language the implicit word selection
488 CursorSlice resetCursor = cur.top();
489 bool implicitSelection =
490 font.language() == ignore_language
491 && font.number() == Font::IGNORE
492 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
495 setFont(cur, font, toggleall);
497 // Implicit selections are cleared afterwards
498 // and cursor is set to the original position.
499 if (implicitSelection) {
500 cur.clearSelection();
501 cur.top() = resetCursor;
507 docstring Text::getStringToIndex(Cursor const & cur)
509 BOOST_ASSERT(this == cur.text());
513 idxstring = cur.selectionAsString(false);
515 // Try implicit word selection. If there is a change
516 // in the language the implicit word selection is
519 selectWord(tmpcur, PREVIOUS_WORD);
521 if (!tmpcur.selection())
522 cur.message(_("Nothing to index!"));
523 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
524 cur.message(_("Cannot index more than one paragraph!"));
526 idxstring = tmpcur.selectionAsString(false);
533 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
535 BOOST_ASSERT(cur.text());
536 // make sure that the depth behind the selection are restored, too
537 pit_type undopit = undoSpan(cur.selEnd().pit());
538 recUndo(cur, cur.selBegin().pit(), undopit - 1);
540 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
542 Paragraph & par = pars_[pit];
543 ParagraphParameters params = par.params();
544 params.read(to_utf8(arg), merge);
545 Layout const & layout = *(par.layout());
546 par.params().apply(params, layout);
551 //FIXME This is a little redundant now, but it's probably worth keeping,
552 //especially if we're going to go away from using serialization internally
554 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
556 BOOST_ASSERT(cur.text());
557 // make sure that the depth behind the selection are restored, too
558 pit_type undopit = undoSpan(cur.selEnd().pit());
559 recUndo(cur, cur.selBegin().pit(), undopit - 1);
561 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
563 Paragraph & par = pars_[pit];
564 Layout const & layout = *(par.layout());
565 par.params().apply(p, layout);
570 // this really should just insert the inset and not move the cursor.
571 void Text::insertInset(Cursor & cur, Inset * inset)
573 BOOST_ASSERT(this == cur.text());
575 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
576 Change(cur.buffer().params().trackChanges ?
577 Change::INSERTED : Change::UNCHANGED));
581 // needed to insert the selection
582 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
584 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
585 cur.current_font, str, autoBreakRows_);
589 // turn double CR to single CR, others are converted into one
590 // blank. Then insertStringAsLines is called
591 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
593 docstring linestr = str;
594 bool newline_inserted = false;
596 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
597 if (linestr[i] == '\n') {
598 if (newline_inserted) {
599 // we know that \r will be ignored by
600 // insertStringAsLines. Of course, it is a dirty
601 // trick, but it works...
602 linestr[i - 1] = '\r';
606 newline_inserted = true;
608 } else if (isPrintable(linestr[i])) {
609 newline_inserted = false;
612 insertStringAsLines(cur, linestr);
616 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
617 bool setfont, bool boundary)
620 setCursorIntern(cur, par, pos, setfont, boundary);
621 return cur.bv().checkDepm(cur, old);
625 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
627 BOOST_ASSERT(par != int(paragraphs().size()));
631 // now some strict checking
632 Paragraph & para = getPar(par);
634 // None of these should happen, but we're scaredy-cats
636 lyxerr << "dont like -1" << endl;
640 if (pos > para.size()) {
641 lyxerr << "dont like 1, pos: " << pos
642 << " size: " << para.size()
643 << " par: " << par << endl;
649 void Text::setCursorIntern(Cursor & cur,
650 pit_type par, pos_type pos, bool setfont, bool boundary)
652 BOOST_ASSERT(this == cur.text());
653 cur.boundary(boundary);
654 setCursor(cur.top(), par, pos);
656 cur.setCurrentFont();
660 bool Text::checkAndActivateInset(Cursor & cur, bool front)
664 if (front && cur.pos() == cur.lastpos())
666 if (!front && cur.pos() == 0)
668 Inset * inset = front ? cur.nextInset() : cur.prevInset();
669 if (!isHighlyEditableInset(inset))
672 * Apparently, when entering an inset we are expected to be positioned
673 * *before* it in the containing paragraph, regardless of the direction
674 * from which we are entering. Otherwise, cursor placement goes awry,
675 * and when we exit from the beginning, we'll be placed *after* the
680 inset->edit(cur, front);
685 bool Text::cursorLeft(Cursor & cur)
687 // Tell BufferView to test for FitCursor in any case!
688 cur.updateFlags(Update::FitCursor);
690 // not at paragraph start?
692 // if on right side of boundary (i.e. not at paragraph end, but line end)
693 // -> skip it, i.e. set boundary to true, i.e. go only logically left
694 // there are some exceptions to ignore this: lineseps, newlines, spaces
696 // some effectless debug code to see the values in the debugger
697 bool bound = cur.boundary();
698 int rowpos = cur.textRow().pos();
700 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
701 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
702 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
704 if (!cur.boundary() &&
705 cur.textRow().pos() == cur.pos() &&
706 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
707 !cur.paragraph().isNewline(cur.pos() - 1) &&
708 !cur.paragraph().isSeparator(cur.pos() - 1)) {
709 return setCursor(cur, cur.pit(), cur.pos(), true, true);
712 // go left and try to enter inset
713 if (checkAndActivateInset(cur, false))
716 // normal character left
717 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
720 // move to the previous paragraph or do nothing
722 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
727 bool Text::cursorRight(Cursor & cur)
729 // Tell BufferView to test for FitCursor in any case!
730 cur.updateFlags(Update::FitCursor);
732 // not at paragraph end?
733 if (cur.pos() != cur.lastpos()) {
734 // in front of editable inset, i.e. jump into it?
735 if (checkAndActivateInset(cur, true))
738 // if left of boundary -> just jump to right side
739 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
740 if (cur.boundary() &&
741 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
742 return setCursor(cur, cur.pit(), cur.pos(), true, false);
744 // next position is left of boundary,
745 // but go to next line for special cases like space, newline, linesep
747 // some effectless debug code to see the values in the debugger
748 int endpos = cur.textRow().endpos();
749 int lastpos = cur.lastpos();
751 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
752 bool newline = cur.paragraph().isNewline(cur.pos());
753 bool sep = cur.paragraph().isSeparator(cur.pos());
754 if (cur.pos() != cur.lastpos()) {
755 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
756 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
757 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
760 if (cur.textRow().endpos() == cur.pos() + 1 &&
761 cur.textRow().endpos() != cur.lastpos() &&
762 !cur.paragraph().isNewline(cur.pos()) &&
763 !cur.paragraph().isLineSeparator(cur.pos()) &&
764 !cur.paragraph().isSeparator(cur.pos())) {
765 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
768 // in front of RTL boundary? Stay on this side of the boundary because:
769 // ab|cDDEEFFghi -> abc|DDEEFFghi
770 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
771 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
774 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
777 // move to next paragraph
778 if (cur.pit() != cur.lastpit())
779 return setCursor(cur, cur.pit() + 1, 0);
784 bool Text::cursorUpParagraph(Cursor & cur)
786 bool updated = false;
788 updated = setCursor(cur, cur.pit(), 0);
789 else if (cur.pit() != 0)
790 updated = setCursor(cur, cur.pit() - 1, 0);
795 bool Text::cursorDownParagraph(Cursor & cur)
797 bool updated = false;
798 if (cur.pit() != cur.lastpit())
799 updated = setCursor(cur, cur.pit() + 1, 0);
801 updated = setCursor(cur, cur.pit(), cur.lastpos());
806 // fix the cursor `cur' after a characters has been deleted at `where'
807 // position. Called by deleteEmptyParagraphMechanism
808 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
810 // Do nothing if cursor is not in the paragraph where the
812 if (cur.pit() != where.pit())
815 // If cursor position is after the deletion place update it
816 if (cur.pos() > where.pos())
819 // Check also if we don't want to set the cursor on a spot behind the
820 // pagragraph because we erased the last character.
821 if (cur.pos() > cur.lastpos())
822 cur.pos() = cur.lastpos();
826 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
827 Cursor & old, bool & need_anchor_change)
829 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
831 Paragraph & oldpar = old.paragraph();
833 // We allow all kinds of "mumbo-jumbo" when freespacing.
834 if (oldpar.isFreeSpacing())
837 /* Ok I'll put some comments here about what is missing.
838 There are still some small problems that can lead to
839 double spaces stored in the document file or space at
840 the beginning of paragraphs(). This happens if you have
841 the cursor between to spaces and then save. Or if you
842 cut and paste and the selection have a space at the
843 beginning and then save right after the paste. (Lgb)
846 // If old.pos() == 0 and old.pos()(1) == LineSeparator
847 // delete the LineSeparator.
850 // If old.pos() == 1 and old.pos()(0) == LineSeparator
851 // delete the LineSeparator.
854 bool const same_inset = &old.inset() == &cur.inset();
855 bool const same_par = same_inset && old.pit() == cur.pit();
856 bool const same_par_pos = same_par && old.pos() == cur.pos();
858 // If the chars around the old cursor were spaces, delete one of them.
860 // Only if the cursor has really moved.
862 && old.pos() < oldpar.size()
863 && oldpar.isLineSeparator(old.pos())
864 && oldpar.isLineSeparator(old.pos() - 1)
865 && !oldpar.isDeleted(old.pos() - 1)
866 && !oldpar.isDeleted(old.pos())) {
867 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
868 // FIXME: This will not work anymore when we have multiple views of the same buffer
869 // In this case, we will have to correct also the cursors held by
870 // other bufferviews. It will probably be easier to do that in a more
871 // automated way in CursorSlice code. (JMarc 26/09/2001)
872 // correct all cursor parts
874 fixCursorAfterDelete(cur.top(), old.top());
875 need_anchor_change = true;
881 // only do our magic if we changed paragraph
885 // don't delete anything if this is the ONLY paragraph!
886 if (old.lastpit() == 0)
889 // Do not delete empty paragraphs with keepempty set.
890 if (oldpar.allowEmpty())
893 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
895 recordUndo(old, Undo::ATOMIC,
896 max(old.pit() - 1, pit_type(0)),
897 min(old.pit() + 1, old.lastpit()));
898 ParagraphList & plist = old.text()->paragraphs();
899 plist.erase(boost::next(plist.begin(), old.pit()));
901 // see #warning (FIXME?) above
902 if (cur.depth() >= old.depth()) {
903 CursorSlice & curslice = cur[old.depth() - 1];
904 if (&curslice.inset() == &old.inset()
905 && curslice.pit() > old.pit()) {
907 // since a paragraph has been deleted, all the
908 // insets after `old' have been copied and
909 // their address has changed. Therefore we
910 // need to `regenerate' cur. (JMarc)
911 cur.updateInsets(&(cur.bottom().inset()));
912 need_anchor_change = true;
918 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
919 need_anchor_change = true;
920 // We return true here because the Paragraph contents changed and
921 // we need a redraw before further action is processed.
929 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
931 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
933 for (pit_type pit = first; pit <= last; ++pit) {
934 Paragraph & par = pars_[pit];
936 // We allow all kinds of "mumbo-jumbo" when freespacing.
937 if (par.isFreeSpacing())
940 for (pos_type pos = 1; pos < par.size(); ++pos) {
941 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
942 && !par.isDeleted(pos - 1)) {
943 if (par.eraseChar(pos - 1, trackChanges)) {
949 // don't delete anything if this is the only remaining paragraph within the given range
950 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
954 // don't delete empty paragraphs with keepempty set
955 if (par.allowEmpty())
958 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
959 pars_.erase(boost::next(pars_.begin(), pit));
965 par.stripLeadingSpaces(trackChanges);
970 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
972 recordUndo(cur, Undo::ATOMIC, first, last);
976 void Text::recUndo(Cursor & cur, pit_type par) const
978 recordUndo(cur, Undo::ATOMIC, par, par);