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 : background_color_(Color::background),
79 bool Text::isMainText(Buffer const & buffer) const
81 return &buffer.text() == this;
85 // Gets the fully instantiated font at a given position in a paragraph
86 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
87 // The difference is that this one is used for displaying, and thus we
88 // are allowed to make cosmetic improvements. For instance make footnotes
90 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
91 pos_type const pos) const
93 BOOST_ASSERT(pos >= 0);
95 LayoutPtr const & layout = par.layout();
97 BufferParams const & params = buffer.params();
98 pos_type const body_pos = par.beginOfBody();
100 // We specialize the 95% common case:
101 if (!par.getDepth()) {
102 Font f = par.getFontSettings(params, pos);
103 if (!isMainText(buffer))
104 applyOuterFont(buffer, f);
107 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
108 lf = layout->labelfont;
109 rlf = layout->reslabelfont;
112 rlf = layout->resfont;
114 // In case the default family has been customized
115 if (lf.family() == Font::INHERIT_FAMILY)
116 rlf.setFamily(params.getFont().family());
117 return f.realize(rlf);
120 // The uncommon case need not be optimized as much
123 layoutfont = layout->labelfont;
125 layoutfont = layout->font;
127 Font font = par.getFontSettings(params, pos);
128 font.realize(layoutfont);
130 if (!isMainText(buffer))
131 applyOuterFont(buffer, font);
133 // Find the pit value belonging to paragraph. This will not break
134 // even if pars_ would not be a vector anymore.
135 // Performance appears acceptable.
137 pit_type pit = pars_.size();
138 for (pit_type it = 0; it < pit; ++it)
139 if (&pars_[it] == &par) {
143 // Realize against environment font information
144 // NOTE: the cast to pit_type should be removed when pit_type
145 // changes to a unsigned integer.
146 if (pit < pit_type(pars_.size()))
147 font.realize(outerFont(pit, pars_));
149 // Realize with the fonts of lesser depth.
150 font.realize(params.getFont());
155 // There are currently two font mechanisms in LyX:
156 // 1. The font attributes in a lyxtext, and
157 // 2. The inset-specific font properties, defined in an inset's
158 // metrics() and draw() methods and handed down the inset chain through
159 // the pi/mi parameters, and stored locally in a lyxtext in font_.
160 // This is where the two are integrated in the final fully realized
162 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
164 lf.reduce(buffer.params().getFont());
166 lf.setLanguage(font.language());
171 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
173 LayoutPtr const & layout = pars_[pit].layout();
175 if (!pars_[pit].getDepth()) {
176 Font lf = layout->resfont;
177 // In case the default family has been customized
178 if (layout->font.family() == Font::INHERIT_FAMILY)
179 lf.setFamily(buffer.params().getFont().family());
183 Font font = layout->font;
184 // Realize with the fonts of lesser depth.
185 //font.realize(outerFont(pit, paragraphs()));
186 font.realize(buffer.params().getFont());
192 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
194 LayoutPtr const & layout = par.layout();
196 if (!par.getDepth()) {
197 Font lf = layout->reslabelfont;
198 // In case the default family has been customized
199 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
200 lf.setFamily(buffer.params().getFont().family());
204 Font font = layout->labelfont;
205 // Realize with the fonts of lesser depth.
206 font.realize(buffer.params().getFont());
212 void Text::setCharFont(Buffer const & buffer, pit_type pit,
213 pos_type pos, Font const & fnt)
216 LayoutPtr const & layout = pars_[pit].layout();
218 // Get concrete layout font to reduce against
221 if (pos < pars_[pit].beginOfBody())
222 layoutfont = layout->labelfont;
224 layoutfont = layout->font;
226 // Realize against environment font information
227 if (pars_[pit].getDepth()) {
229 while (!layoutfont.resolved() &&
230 tp != pit_type(paragraphs().size()) &&
231 pars_[tp].getDepth()) {
232 tp = outerHook(tp, paragraphs());
233 if (tp != pit_type(paragraphs().size()))
234 layoutfont.realize(pars_[tp].layout()->font);
238 // Inside inset, apply the inset's font attributes if any
240 if (!isMainText(buffer))
241 layoutfont.realize(font_);
243 layoutfont.realize(buffer.params().getFont());
245 // Now, reduce font against full layout font
246 font.reduce(layoutfont);
248 pars_[pit].setFont(pos, font);
252 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
253 pos_type pos, Font const & font, bool toggleall)
255 BOOST_ASSERT(pars_[pit].isInset(pos) &&
256 pars_[pit].getInset(pos)->noFontChange());
258 Inset * const inset = pars_[pit].getInset(pos);
259 CursorSlice::idx_type endidx = inset->nargs();
260 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
261 Text * text = cs.text();
263 // last position of the cell
264 CursorSlice cellend = cs;
265 cellend.pit() = cellend.lastpit();
266 cellend.pos() = cellend.lastpos();
267 text->setFont(buffer, cs, cellend, font, toggleall);
273 // return past-the-last paragraph influenced by a layout change on pit
274 pit_type Text::undoSpan(pit_type pit)
276 pit_type end = paragraphs().size();
277 pit_type nextpit = pit + 1;
280 //because of parindents
281 if (!pars_[pit].getDepth())
282 return boost::next(nextpit);
283 //because of depth constrains
284 for (; nextpit != end; ++pit, ++nextpit) {
285 if (!pars_[pit].getDepth())
292 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
293 docstring const & layout)
295 BOOST_ASSERT(start != end);
297 BufferParams const & bufparams = buffer.params();
298 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
300 for (pit_type pit = start; pit != end; ++pit) {
301 Paragraph & par = pars_[pit];
302 par.applyLayout(lyxlayout);
303 if (lyxlayout->margintype == MARGIN_MANUAL)
304 par.setLabelWidthString(par.translateIfPossible(
305 lyxlayout->labelstring(), buffer.params()));
310 // set layout over selection and make a total rebreak of those paragraphs
311 void Text::setLayout(Cursor & cur, docstring const & layout)
313 BOOST_ASSERT(this == cur.text());
314 // special handling of new environment insets
315 BufferView & bv = cur.bv();
316 BufferParams const & params = bv.buffer().params();
317 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
318 if (lyxlayout->is_environment) {
319 // move everything in a new environment inset
320 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
321 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
322 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
323 lyx::dispatch(FuncRequest(LFUN_CUT));
324 Inset * inset = new InsetEnvironment(params, layout);
325 insertInset(cur, inset);
326 //inset->edit(cur, true);
327 //lyx::dispatch(FuncRequest(LFUN_PASTE));
331 pit_type start = cur.selBegin().pit();
332 pit_type end = cur.selEnd().pit() + 1;
333 pit_type undopit = undoSpan(end - 1);
334 recUndo(cur, start, undopit - 1);
335 setLayout(cur.buffer(), start, end, layout);
336 updateLabels(cur.buffer());
340 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
341 Paragraph const & par, int max_depth)
343 if (par.layout()->labeltype == LABEL_BIBLIO)
345 int const depth = par.params().depth();
346 if (type == Text::INC_DEPTH && depth < max_depth)
348 if (type == Text::DEC_DEPTH && depth > 0)
354 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
356 BOOST_ASSERT(this == cur.text());
357 // this happens when selecting several cells in tabular (bug 2630)
358 if (cur.selBegin().idx() != cur.selEnd().idx())
361 pit_type const beg = cur.selBegin().pit();
362 pit_type const end = cur.selEnd().pit() + 1;
363 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
365 for (pit_type pit = beg; pit != end; ++pit) {
366 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
368 max_depth = pars_[pit].getMaxDepthAfter();
374 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
376 BOOST_ASSERT(this == cur.text());
377 pit_type const beg = cur.selBegin().pit();
378 pit_type const end = cur.selEnd().pit() + 1;
379 recordUndoSelection(cur);
380 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
382 for (pit_type pit = beg; pit != end; ++pit) {
383 Paragraph & par = pars_[pit];
384 if (lyx::changeDepthAllowed(type, par, max_depth)) {
385 int const depth = par.params().depth();
386 if (type == INC_DEPTH)
387 par.params().depth(depth + 1);
389 par.params().depth(depth - 1);
391 max_depth = par.getMaxDepthAfter();
393 // this handles the counter labels, and also fixes up
394 // depth values for follow-on (child) paragraphs
395 updateLabels(cur.buffer());
399 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
401 BOOST_ASSERT(this == cur.text());
402 // Set the current_font
403 // Determine basis font
405 pit_type pit = cur.pit();
406 if (cur.pos() < pars_[pit].beginOfBody())
407 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
409 layoutfont = getLayoutFont(cur.buffer(), pit);
411 // Update current font
412 cur.real_current_font.update(font,
413 cur.buffer().params().language,
416 // Reduce to implicit settings
417 cur.current_font = cur.real_current_font;
418 cur.current_font.reduce(layoutfont);
419 // And resolve it completely
420 cur.real_current_font.realize(layoutfont);
422 // if there is no selection that's all we need to do
423 if (!cur.selection())
426 // Ok, we have a selection.
427 recordUndoSelection(cur);
429 setFont(cur.buffer(), cur.selectionBegin().top(),
430 cur.selectionEnd().top(), font, toggleall);
434 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
435 CursorSlice const & end, Font const & font,
438 // Don't use forwardChar here as ditend might have
439 // pos() == lastpos() and forwardChar would miss it.
440 // Can't use forwardPos either as this descends into
442 Language const * language = buffer.params().language;
443 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
444 if (dit.pos() != dit.lastpos()) {
445 pit_type const pit = dit.pit();
446 pos_type const pos = dit.pos();
447 if (pars_[pit].isInset(pos) &&
448 pars_[pit].getInset(pos)->noFontChange())
449 // We need to propagate the font change to all
450 // text cells of the inset (bug 1973).
451 // FIXME: This should change, see documentation
452 // of noFontChange in Inset.h
453 setInsetFont(buffer, pit, pos, font, toggleall);
454 Font f = getFont(buffer, dit.paragraph(), pos);
455 f.update(font, language, toggleall);
456 setCharFont(buffer, pit, pos, f);
462 bool Text::cursorTop(Cursor & cur)
464 BOOST_ASSERT(this == cur.text());
465 return setCursor(cur, 0, 0);
469 bool Text::cursorBottom(Cursor & cur)
471 BOOST_ASSERT(this == cur.text());
472 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
476 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
478 BOOST_ASSERT(this == cur.text());
479 // If the mask is completely neutral, tell user
480 if (font == Font(Font::ALL_IGNORE)) {
481 // Could only happen with user style
482 cur.message(_("No font change defined."));
486 // Try implicit word selection
487 // If there is a change in the language the implicit word selection
489 CursorSlice resetCursor = cur.top();
490 bool implicitSelection =
491 font.language() == ignore_language
492 && font.number() == Font::IGNORE
493 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
496 setFont(cur, font, toggleall);
498 // Implicit selections are cleared afterwards
499 // and cursor is set to the original position.
500 if (implicitSelection) {
501 cur.clearSelection();
502 cur.top() = resetCursor;
508 docstring Text::getStringToIndex(Cursor const & cur)
510 BOOST_ASSERT(this == cur.text());
514 idxstring = cur.selectionAsString(false);
516 // Try implicit word selection. If there is a change
517 // in the language the implicit word selection is
520 selectWord(tmpcur, PREVIOUS_WORD);
522 if (!tmpcur.selection())
523 cur.message(_("Nothing to index!"));
524 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
525 cur.message(_("Cannot index more than one paragraph!"));
527 idxstring = tmpcur.selectionAsString(false);
534 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
536 BOOST_ASSERT(cur.text());
537 // make sure that the depth behind the selection are restored, too
538 pit_type undopit = undoSpan(cur.selEnd().pit());
539 recUndo(cur, cur.selBegin().pit(), undopit - 1);
541 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
543 Paragraph & par = pars_[pit];
544 ParagraphParameters params = par.params();
545 params.read(to_utf8(arg), merge);
546 Layout const & layout = *(par.layout());
547 par.params().apply(params, layout);
552 //FIXME This is a little redundant now, but it's probably worth keeping,
553 //especially if we're going to go away from using serialization internally
555 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
557 BOOST_ASSERT(cur.text());
558 // make sure that the depth behind the selection are restored, too
559 pit_type undopit = undoSpan(cur.selEnd().pit());
560 recUndo(cur, cur.selBegin().pit(), undopit - 1);
562 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
564 Paragraph & par = pars_[pit];
565 Layout const & layout = *(par.layout());
566 par.params().apply(p, layout);
571 // this really should just insert the inset and not move the cursor.
572 void Text::insertInset(Cursor & cur, Inset * inset)
574 BOOST_ASSERT(this == cur.text());
576 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
577 Change(cur.buffer().params().trackChanges ?
578 Change::INSERTED : Change::UNCHANGED));
582 // needed to insert the selection
583 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
585 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
586 cur.current_font, str, autoBreakRows_);
590 // turn double CR to single CR, others are converted into one
591 // blank. Then insertStringAsLines is called
592 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
594 docstring linestr = str;
595 bool newline_inserted = false;
597 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
598 if (linestr[i] == '\n') {
599 if (newline_inserted) {
600 // we know that \r will be ignored by
601 // insertStringAsLines. Of course, it is a dirty
602 // trick, but it works...
603 linestr[i - 1] = '\r';
607 newline_inserted = true;
609 } else if (isPrintable(linestr[i])) {
610 newline_inserted = false;
613 insertStringAsLines(cur, linestr);
617 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
618 bool setfont, bool boundary)
621 setCursorIntern(cur, par, pos, setfont, boundary);
622 return cur.bv().checkDepm(cur, old);
626 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
628 BOOST_ASSERT(par != int(paragraphs().size()));
632 // now some strict checking
633 Paragraph & para = getPar(par);
635 // None of these should happen, but we're scaredy-cats
637 lyxerr << "dont like -1" << endl;
641 if (pos > para.size()) {
642 lyxerr << "dont like 1, pos: " << pos
643 << " size: " << para.size()
644 << " par: " << par << endl;
650 void Text::setCursorIntern(Cursor & cur,
651 pit_type par, pos_type pos, bool setfont, bool boundary)
653 BOOST_ASSERT(this == cur.text());
654 cur.boundary(boundary);
655 setCursor(cur.top(), par, pos);
657 cur.setCurrentFont();
661 bool Text::checkAndActivateInset(Cursor & cur, bool front)
665 if (front && cur.pos() == cur.lastpos())
667 if (!front && cur.pos() == 0)
669 Inset * inset = front ? cur.nextInset() : cur.prevInset();
670 if (!isHighlyEditableInset(inset))
673 * Apparently, when entering an inset we are expected to be positioned
674 * *before* it in the containing paragraph, regardless of the direction
675 * from which we are entering. Otherwise, cursor placement goes awry,
676 * and when we exit from the beginning, we'll be placed *after* the
681 inset->edit(cur, front);
686 bool Text::cursorLeft(Cursor & cur)
688 // Tell BufferView to test for FitCursor in any case!
689 cur.updateFlags(Update::FitCursor);
691 // not at paragraph start?
693 // if on right side of boundary (i.e. not at paragraph end, but line end)
694 // -> skip it, i.e. set boundary to true, i.e. go only logically left
695 // there are some exceptions to ignore this: lineseps, newlines, spaces
697 // some effectless debug code to see the values in the debugger
698 bool bound = cur.boundary();
699 int rowpos = cur.textRow().pos();
701 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
702 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
703 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
705 if (!cur.boundary() &&
706 cur.textRow().pos() == cur.pos() &&
707 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
708 !cur.paragraph().isNewline(cur.pos() - 1) &&
709 !cur.paragraph().isSeparator(cur.pos() - 1)) {
710 return setCursor(cur, cur.pit(), cur.pos(), true, true);
713 // go left and try to enter inset
714 if (checkAndActivateInset(cur, false))
717 // normal character left
718 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
721 // move to the previous paragraph or do nothing
723 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
728 bool Text::cursorRight(Cursor & cur)
730 // Tell BufferView to test for FitCursor in any case!
731 cur.updateFlags(Update::FitCursor);
733 // not at paragraph end?
734 if (cur.pos() != cur.lastpos()) {
735 // in front of editable inset, i.e. jump into it?
736 if (checkAndActivateInset(cur, true))
739 // if left of boundary -> just jump to right side
740 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
741 if (cur.boundary() &&
742 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
743 return setCursor(cur, cur.pit(), cur.pos(), true, false);
745 // next position is left of boundary,
746 // but go to next line for special cases like space, newline, linesep
748 // some effectless debug code to see the values in the debugger
749 int endpos = cur.textRow().endpos();
750 int lastpos = cur.lastpos();
752 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
753 bool newline = cur.paragraph().isNewline(cur.pos());
754 bool sep = cur.paragraph().isSeparator(cur.pos());
755 if (cur.pos() != cur.lastpos()) {
756 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
757 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
758 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
761 if (cur.textRow().endpos() == cur.pos() + 1 &&
762 cur.textRow().endpos() != cur.lastpos() &&
763 !cur.paragraph().isNewline(cur.pos()) &&
764 !cur.paragraph().isLineSeparator(cur.pos()) &&
765 !cur.paragraph().isSeparator(cur.pos())) {
766 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
769 // in front of RTL boundary? Stay on this side of the boundary because:
770 // ab|cDDEEFFghi -> abc|DDEEFFghi
771 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
772 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
775 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
778 // move to next paragraph
779 if (cur.pit() != cur.lastpit())
780 return setCursor(cur, cur.pit() + 1, 0);
785 bool Text::cursorUpParagraph(Cursor & cur)
787 bool updated = false;
789 updated = setCursor(cur, cur.pit(), 0);
790 else if (cur.pit() != 0)
791 updated = setCursor(cur, cur.pit() - 1, 0);
796 bool Text::cursorDownParagraph(Cursor & cur)
798 bool updated = false;
799 if (cur.pit() != cur.lastpit())
800 updated = setCursor(cur, cur.pit() + 1, 0);
802 updated = setCursor(cur, cur.pit(), cur.lastpos());
807 // fix the cursor `cur' after a characters has been deleted at `where'
808 // position. Called by deleteEmptyParagraphMechanism
809 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
811 // Do nothing if cursor is not in the paragraph where the
813 if (cur.pit() != where.pit())
816 // If cursor position is after the deletion place update it
817 if (cur.pos() > where.pos())
820 // Check also if we don't want to set the cursor on a spot behind the
821 // pagragraph because we erased the last character.
822 if (cur.pos() > cur.lastpos())
823 cur.pos() = cur.lastpos();
827 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
828 Cursor & old, bool & need_anchor_change)
830 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
832 Paragraph & oldpar = old.paragraph();
834 // We allow all kinds of "mumbo-jumbo" when freespacing.
835 if (oldpar.isFreeSpacing())
838 /* Ok I'll put some comments here about what is missing.
839 There are still some small problems that can lead to
840 double spaces stored in the document file or space at
841 the beginning of paragraphs(). This happens if you have
842 the cursor between to spaces and then save. Or if you
843 cut and paste and the selection have a space at the
844 beginning and then save right after the paste. (Lgb)
847 // If old.pos() == 0 and old.pos()(1) == LineSeparator
848 // delete the LineSeparator.
851 // If old.pos() == 1 and old.pos()(0) == LineSeparator
852 // delete the LineSeparator.
855 bool const same_inset = &old.inset() == &cur.inset();
856 bool const same_par = same_inset && old.pit() == cur.pit();
857 bool const same_par_pos = same_par && old.pos() == cur.pos();
859 // If the chars around the old cursor were spaces, delete one of them.
861 // Only if the cursor has really moved.
863 && old.pos() < oldpar.size()
864 && oldpar.isLineSeparator(old.pos())
865 && oldpar.isLineSeparator(old.pos() - 1)
866 && !oldpar.isDeleted(old.pos() - 1)
867 && !oldpar.isDeleted(old.pos())) {
868 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
869 // FIXME: This will not work anymore when we have multiple views of the same buffer
870 // In this case, we will have to correct also the cursors held by
871 // other bufferviews. It will probably be easier to do that in a more
872 // automated way in CursorSlice code. (JMarc 26/09/2001)
873 // correct all cursor parts
875 fixCursorAfterDelete(cur.top(), old.top());
876 need_anchor_change = true;
882 // only do our magic if we changed paragraph
886 // don't delete anything if this is the ONLY paragraph!
887 if (old.lastpit() == 0)
890 // Do not delete empty paragraphs with keepempty set.
891 if (oldpar.allowEmpty())
894 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
896 recordUndo(old, Undo::ATOMIC,
897 max(old.pit() - 1, pit_type(0)),
898 min(old.pit() + 1, old.lastpit()));
899 ParagraphList & plist = old.text()->paragraphs();
900 plist.erase(boost::next(plist.begin(), old.pit()));
902 // see #warning (FIXME?) above
903 if (cur.depth() >= old.depth()) {
904 CursorSlice & curslice = cur[old.depth() - 1];
905 if (&curslice.inset() == &old.inset()
906 && curslice.pit() > old.pit()) {
908 // since a paragraph has been deleted, all the
909 // insets after `old' have been copied and
910 // their address has changed. Therefore we
911 // need to `regenerate' cur. (JMarc)
912 cur.updateInsets(&(cur.bottom().inset()));
913 need_anchor_change = true;
919 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
920 need_anchor_change = true;
921 // We return true here because the Paragraph contents changed and
922 // we need a redraw before further action is processed.
930 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
932 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
934 for (pit_type pit = first; pit <= last; ++pit) {
935 Paragraph & par = pars_[pit];
937 // We allow all kinds of "mumbo-jumbo" when freespacing.
938 if (par.isFreeSpacing())
941 for (pos_type pos = 1; pos < par.size(); ++pos) {
942 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
943 && !par.isDeleted(pos - 1)) {
944 if (par.eraseChar(pos - 1, trackChanges)) {
950 // don't delete anything if this is the only remaining paragraph within the given range
951 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
955 // don't delete empty paragraphs with keepempty set
956 if (par.allowEmpty())
959 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
960 pars_.erase(boost::next(pars_.begin(), pit));
966 par.stripLeadingSpaces(trackChanges);
971 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
973 recordUndo(cur, Undo::ATOMIC, first, last);
977 void Text::recUndo(Cursor & cur, pit_type par) const
979 recordUndo(cur, Undo::ATOMIC, par, par);