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"
30 #include "bufferview_funcs.h"
33 #include "CoordCache.h"
35 #include "CutAndPaste.h"
37 #include "DispatchResult.h"
38 #include "ErrorList.h"
39 #include "FuncRequest.h"
45 #include "Paragraph.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "ParIterator.h"
50 #include "ServerSocket.h"
54 #include "frontends/FontMetrics.h"
56 #include "insets/InsetEnvironment.h"
58 #include "mathed/InsetMathHull.h"
60 #include "support/textutils.h"
62 #include <boost/current_function.hpp>
70 using std::ostringstream;
74 using std::istringstream;
77 : current_font(Font::ALL_INHERIT),
78 background_color_(Color::background),
83 bool Text::isMainText(Buffer const & buffer) const
85 return &buffer.text() == this;
89 // Gets the fully instantiated font at a given position in a paragraph
90 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
91 // The difference is that this one is used for displaying, and thus we
92 // are allowed to make cosmetic improvements. For instance make footnotes
94 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
95 pos_type const pos) const
97 BOOST_ASSERT(pos >= 0);
99 LayoutPtr const & layout = par.layout();
101 BufferParams const & params = buffer.params();
102 pos_type const body_pos = par.beginOfBody();
104 // We specialize the 95% common case:
105 if (!par.getDepth()) {
106 Font f = par.getFontSettings(params, pos);
107 if (!isMainText(buffer))
108 applyOuterFont(buffer, f);
111 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
112 lf = layout->labelfont;
113 rlf = layout->reslabelfont;
116 rlf = layout->resfont;
118 // In case the default family has been customized
119 if (lf.family() == Font::INHERIT_FAMILY)
120 rlf.setFamily(params.getFont().family());
121 return f.realize(rlf);
124 // The uncommon case need not be optimized as much
127 layoutfont = layout->labelfont;
129 layoutfont = layout->font;
131 Font font = par.getFontSettings(params, pos);
132 font.realize(layoutfont);
134 if (!isMainText(buffer))
135 applyOuterFont(buffer, font);
137 // Find the pit value belonging to paragraph. This will not break
138 // even if pars_ would not be a vector anymore.
139 // Performance appears acceptable.
141 pit_type pit = pars_.size();
142 for (pit_type it = 0; it < pit; ++it)
143 if (&pars_[it] == &par) {
147 // Realize against environment font information
148 // NOTE: the cast to pit_type should be removed when pit_type
149 // changes to a unsigned integer.
150 if (pit < pit_type(pars_.size()))
151 font.realize(outerFont(pit, pars_));
153 // Realize with the fonts of lesser depth.
154 font.realize(params.getFont());
159 // There are currently two font mechanisms in LyX:
160 // 1. The font attributes in a lyxtext, and
161 // 2. The inset-specific font properties, defined in an inset's
162 // metrics() and draw() methods and handed down the inset chain through
163 // the pi/mi parameters, and stored locally in a lyxtext in font_.
164 // This is where the two are integrated in the final fully realized
166 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
168 lf.reduce(buffer.params().getFont());
170 lf.setLanguage(font.language());
175 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
177 LayoutPtr const & layout = pars_[pit].layout();
179 if (!pars_[pit].getDepth()) {
180 Font lf = layout->resfont;
181 // In case the default family has been customized
182 if (layout->font.family() == Font::INHERIT_FAMILY)
183 lf.setFamily(buffer.params().getFont().family());
187 Font font = layout->font;
188 // Realize with the fonts of lesser depth.
189 //font.realize(outerFont(pit, paragraphs()));
190 font.realize(buffer.params().getFont());
196 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
198 LayoutPtr const & layout = par.layout();
200 if (!par.getDepth()) {
201 Font lf = layout->reslabelfont;
202 // In case the default family has been customized
203 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
204 lf.setFamily(buffer.params().getFont().family());
208 Font font = layout->labelfont;
209 // Realize with the fonts of lesser depth.
210 font.realize(buffer.params().getFont());
216 void Text::setCharFont(Buffer const & buffer, pit_type pit,
217 pos_type pos, Font const & fnt)
220 LayoutPtr const & layout = pars_[pit].layout();
222 // Get concrete layout font to reduce against
225 if (pos < pars_[pit].beginOfBody())
226 layoutfont = layout->labelfont;
228 layoutfont = layout->font;
230 // Realize against environment font information
231 if (pars_[pit].getDepth()) {
233 while (!layoutfont.resolved() &&
234 tp != pit_type(paragraphs().size()) &&
235 pars_[tp].getDepth()) {
236 tp = outerHook(tp, paragraphs());
237 if (tp != pit_type(paragraphs().size()))
238 layoutfont.realize(pars_[tp].layout()->font);
242 // Inside inset, apply the inset's font attributes if any
244 if (!isMainText(buffer))
245 layoutfont.realize(font_);
247 layoutfont.realize(buffer.params().getFont());
249 // Now, reduce font against full layout font
250 font.reduce(layoutfont);
252 pars_[pit].setFont(pos, font);
256 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
257 pos_type pos, Font const & font, bool toggleall)
259 BOOST_ASSERT(pars_[pit].isInset(pos) &&
260 pars_[pit].getInset(pos)->noFontChange());
262 Inset * const inset = pars_[pit].getInset(pos);
263 CursorSlice::idx_type endidx = inset->nargs();
264 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
265 Text * text = cs.text();
267 // last position of the cell
268 CursorSlice cellend = cs;
269 cellend.pit() = cellend.lastpit();
270 cellend.pos() = cellend.lastpos();
271 text->setFont(buffer, cs, cellend, font, toggleall);
277 // return past-the-last paragraph influenced by a layout change on pit
278 pit_type Text::undoSpan(pit_type pit)
280 pit_type end = paragraphs().size();
281 pit_type nextpit = pit + 1;
284 //because of parindents
285 if (!pars_[pit].getDepth())
286 return boost::next(nextpit);
287 //because of depth constrains
288 for (; nextpit != end; ++pit, ++nextpit) {
289 if (!pars_[pit].getDepth())
296 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
297 docstring const & layout)
299 BOOST_ASSERT(start != end);
301 BufferParams const & bufparams = buffer.params();
302 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
304 for (pit_type pit = start; pit != end; ++pit) {
305 Paragraph & par = pars_[pit];
306 par.applyLayout(lyxlayout);
307 if (lyxlayout->margintype == MARGIN_MANUAL)
308 par.setLabelWidthString(par.translateIfPossible(
309 lyxlayout->labelstring(), buffer.params()));
314 // set layout over selection and make a total rebreak of those paragraphs
315 void Text::setLayout(Cursor & cur, docstring const & layout)
317 BOOST_ASSERT(this == cur.text());
318 // special handling of new environment insets
319 BufferView & bv = cur.bv();
320 BufferParams const & params = bv.buffer().params();
321 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
322 if (lyxlayout->is_environment) {
323 // move everything in a new environment inset
324 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
325 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
326 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
327 lyx::dispatch(FuncRequest(LFUN_CUT));
328 Inset * inset = new InsetEnvironment(params, layout);
329 insertInset(cur, inset);
330 //inset->edit(cur, true);
331 //lyx::dispatch(FuncRequest(LFUN_PASTE));
335 pit_type start = cur.selBegin().pit();
336 pit_type end = cur.selEnd().pit() + 1;
337 pit_type undopit = undoSpan(end - 1);
338 recUndo(cur, start, undopit - 1);
339 setLayout(cur.buffer(), start, end, layout);
340 updateLabels(cur.buffer());
344 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
345 Paragraph const & par, int max_depth)
347 if (par.layout()->labeltype == LABEL_BIBLIO)
349 int const depth = par.params().depth();
350 if (type == Text::INC_DEPTH && depth < max_depth)
352 if (type == Text::DEC_DEPTH && depth > 0)
358 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
360 BOOST_ASSERT(this == cur.text());
361 // this happens when selecting several cells in tabular (bug 2630)
362 if (cur.selBegin().idx() != cur.selEnd().idx())
365 pit_type const beg = cur.selBegin().pit();
366 pit_type const end = cur.selEnd().pit() + 1;
367 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
369 for (pit_type pit = beg; pit != end; ++pit) {
370 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
372 max_depth = pars_[pit].getMaxDepthAfter();
378 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
380 BOOST_ASSERT(this == cur.text());
381 pit_type const beg = cur.selBegin().pit();
382 pit_type const end = cur.selEnd().pit() + 1;
383 recordUndoSelection(cur);
384 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
386 for (pit_type pit = beg; pit != end; ++pit) {
387 Paragraph & par = pars_[pit];
388 if (lyx::changeDepthAllowed(type, par, max_depth)) {
389 int const depth = par.params().depth();
390 if (type == INC_DEPTH)
391 par.params().depth(depth + 1);
393 par.params().depth(depth - 1);
395 max_depth = par.getMaxDepthAfter();
397 // this handles the counter labels, and also fixes up
398 // depth values for follow-on (child) paragraphs
399 updateLabels(cur.buffer());
403 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
405 BOOST_ASSERT(this == cur.text());
406 // Set the current_font
407 // Determine basis font
409 pit_type pit = cur.pit();
410 if (cur.pos() < pars_[pit].beginOfBody())
411 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
413 layoutfont = getLayoutFont(cur.buffer(), pit);
415 // Update current font
416 real_current_font.update(font,
417 cur.buffer().params().language,
420 // Reduce to implicit settings
421 current_font = real_current_font;
422 current_font.reduce(layoutfont);
423 // And resolve it completely
424 real_current_font.realize(layoutfont);
426 // if there is no selection that's all we need to do
427 if (!cur.selection())
430 // Ok, we have a selection.
431 recordUndoSelection(cur);
433 setFont(cur.buffer(), cur.selectionBegin().top(),
434 cur.selectionEnd().top(), font, toggleall);
438 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
439 CursorSlice const & end, Font const & font,
442 // Don't use forwardChar here as ditend might have
443 // pos() == lastpos() and forwardChar would miss it.
444 // Can't use forwardPos either as this descends into
446 Language const * language = buffer.params().language;
447 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
448 if (dit.pos() != dit.lastpos()) {
449 pit_type const pit = dit.pit();
450 pos_type const pos = dit.pos();
451 if (pars_[pit].isInset(pos) &&
452 pars_[pit].getInset(pos)->noFontChange())
453 // We need to propagate the font change to all
454 // text cells of the inset (bug 1973).
455 // FIXME: This should change, see documentation
456 // of noFontChange in Inset.h
457 setInsetFont(buffer, pit, pos, font, toggleall);
458 Font f = getFont(buffer, dit.paragraph(), pos);
459 f.update(font, language, toggleall);
460 setCharFont(buffer, pit, pos, f);
466 bool Text::cursorTop(Cursor & cur)
468 BOOST_ASSERT(this == cur.text());
469 return setCursor(cur, 0, 0);
473 bool Text::cursorBottom(Cursor & cur)
475 BOOST_ASSERT(this == cur.text());
476 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
480 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
482 BOOST_ASSERT(this == cur.text());
483 // If the mask is completely neutral, tell user
484 if (font == Font(Font::ALL_IGNORE)) {
485 // Could only happen with user style
486 cur.message(_("No font change defined."));
490 // Try implicit word selection
491 // If there is a change in the language the implicit word selection
493 CursorSlice resetCursor = cur.top();
494 bool implicitSelection =
495 font.language() == ignore_language
496 && font.number() == Font::IGNORE
497 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
500 setFont(cur, font, toggleall);
502 // Implicit selections are cleared afterwards
503 // and cursor is set to the original position.
504 if (implicitSelection) {
505 cur.clearSelection();
506 cur.top() = resetCursor;
512 docstring Text::getStringToIndex(Cursor const & cur)
514 BOOST_ASSERT(this == cur.text());
518 idxstring = cur.selectionAsString(false);
520 // Try implicit word selection. If there is a change
521 // in the language the implicit word selection is
524 selectWord(tmpcur, PREVIOUS_WORD);
526 if (!tmpcur.selection())
527 cur.message(_("Nothing to index!"));
528 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
529 cur.message(_("Cannot index more than one paragraph!"));
531 idxstring = tmpcur.selectionAsString(false);
538 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
540 BOOST_ASSERT(cur.text());
541 // make sure that the depth behind the selection are restored, too
542 pit_type undopit = undoSpan(cur.selEnd().pit());
543 recUndo(cur, cur.selBegin().pit(), undopit - 1);
545 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
547 Paragraph & par = pars_[pit];
548 ParagraphParameters params = par.params();
549 params.read(to_utf8(arg), merge);
550 Layout const & layout = *(par.layout());
551 par.params().apply(params, layout);
556 //FIXME This is a little redundant now, but it's probably worth keeping,
557 //especially if we're going to go away from using serialization internally
559 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
561 BOOST_ASSERT(cur.text());
562 // make sure that the depth behind the selection are restored, too
563 pit_type undopit = undoSpan(cur.selEnd().pit());
564 recUndo(cur, cur.selBegin().pit(), undopit - 1);
566 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
568 Paragraph & par = pars_[pit];
569 Layout const & layout = *(par.layout());
570 par.params().apply(p, layout);
575 // this really should just insert the inset and not move the cursor.
576 void Text::insertInset(Cursor & cur, Inset * inset)
578 BOOST_ASSERT(this == cur.text());
580 cur.paragraph().insertInset(cur.pos(), inset, current_font,
581 Change(cur.buffer().params().trackChanges ?
582 Change::INSERTED : Change::UNCHANGED));
586 // needed to insert the selection
587 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
589 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
590 current_font, str, autoBreakRows_);
594 // turn double CR to single CR, others are converted into one
595 // blank. Then insertStringAsLines is called
596 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
598 docstring linestr = str;
599 bool newline_inserted = false;
601 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
602 if (linestr[i] == '\n') {
603 if (newline_inserted) {
604 // we know that \r will be ignored by
605 // insertStringAsLines. Of course, it is a dirty
606 // trick, but it works...
607 linestr[i - 1] = '\r';
611 newline_inserted = true;
613 } else if (isPrintable(linestr[i])) {
614 newline_inserted = false;
617 insertStringAsLines(cur, linestr);
621 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
622 bool setfont, bool boundary)
625 setCursorIntern(cur, par, pos, setfont, boundary);
626 return cur.bv().checkDepm(cur, old);
630 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
632 BOOST_ASSERT(par != int(paragraphs().size()));
636 // now some strict checking
637 Paragraph & para = getPar(par);
639 // None of these should happen, but we're scaredy-cats
641 lyxerr << "dont like -1" << endl;
645 if (pos > para.size()) {
646 lyxerr << "dont like 1, pos: " << pos
647 << " size: " << para.size()
648 << " par: " << par << endl;
654 void Text::setCursorIntern(Cursor & cur,
655 pit_type par, pos_type pos, bool setfont, bool boundary)
657 BOOST_ASSERT(this == cur.text());
658 cur.boundary(boundary);
659 setCursor(cur.top(), par, pos);
665 void Text::setCurrentFont(Cursor & cur)
667 BOOST_ASSERT(this == cur.text());
668 pos_type pos = cur.pos();
669 Paragraph & par = cur.paragraph();
671 // are we behind previous char in fact? -> go to that char
672 if (pos > 0 && cur.boundary())
675 // find position to take the font from
677 // paragraph end? -> font of last char
678 if (pos == cur.lastpos())
680 // on space? -> look at the words in front of space
681 else if (pos > 0 && par.isSeparator(pos)) {
682 // abc| def -> font of c
683 // abc |[WERBEH], i.e. boundary==true -> font of c
684 // abc [WERBEH]| def, font of the space
685 if (!isRTLBoundary(cur.buffer(), par, pos))
691 BufferParams const & bufparams = cur.buffer().params();
692 current_font = par.getFontSettings(bufparams, pos);
693 real_current_font = getFont(cur.buffer(), par, pos);
695 // special case for paragraph end
696 if (cur.pos() == cur.lastpos()
697 && isRTLBoundary(cur.buffer(), par, cur.pos())
698 && !cur.boundary()) {
699 Language const * lang = par.getParLanguage(bufparams);
700 current_font.setLanguage(lang);
701 current_font.setNumber(Font::OFF);
702 real_current_font.setLanguage(lang);
703 real_current_font.setNumber(Font::OFF);
708 bool Text::checkAndActivateInset(Cursor & cur, bool front)
712 if (front && cur.pos() == cur.lastpos())
714 if (!front && cur.pos() == 0)
716 Inset * inset = front ? cur.nextInset() : cur.prevInset();
717 if (!isHighlyEditableInset(inset))
720 * Apparently, when entering an inset we are expected to be positioned
721 * *before* it in the containing paragraph, regardless of the direction
722 * from which we are entering. Otherwise, cursor placement goes awry,
723 * and when we exit from the beginning, we'll be placed *after* the
728 inset->edit(cur, front);
733 bool Text::cursorLeft(Cursor & cur)
735 // Tell BufferView to test for FitCursor in any case!
736 cur.updateFlags(Update::FitCursor);
738 // not at paragraph start?
740 // if on right side of boundary (i.e. not at paragraph end, but line end)
741 // -> skip it, i.e. set boundary to true, i.e. go only logically left
742 // there are some exceptions to ignore this: lineseps, newlines, spaces
744 // some effectless debug code to see the values in the debugger
745 bool bound = cur.boundary();
746 int rowpos = cur.textRow().pos();
748 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
749 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
750 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
752 if (!cur.boundary() &&
753 cur.textRow().pos() == cur.pos() &&
754 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
755 !cur.paragraph().isNewline(cur.pos() - 1) &&
756 !cur.paragraph().isSeparator(cur.pos() - 1)) {
757 return setCursor(cur, cur.pit(), cur.pos(), true, true);
760 // go left and try to enter inset
761 if (checkAndActivateInset(cur, false))
764 // normal character left
765 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
768 // move to the previous paragraph or do nothing
770 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
775 bool Text::cursorRight(Cursor & cur)
777 // Tell BufferView to test for FitCursor in any case!
778 cur.updateFlags(Update::FitCursor);
780 // not at paragraph end?
781 if (cur.pos() != cur.lastpos()) {
782 // in front of editable inset, i.e. jump into it?
783 if (checkAndActivateInset(cur, true))
786 // if left of boundary -> just jump to right side
787 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
788 if (cur.boundary() &&
789 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
790 return setCursor(cur, cur.pit(), cur.pos(), true, false);
792 // next position is left of boundary,
793 // but go to next line for special cases like space, newline, linesep
795 // some effectless debug code to see the values in the debugger
796 int endpos = cur.textRow().endpos();
797 int lastpos = cur.lastpos();
799 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
800 bool newline = cur.paragraph().isNewline(cur.pos());
801 bool sep = cur.paragraph().isSeparator(cur.pos());
802 if (cur.pos() != cur.lastpos()) {
803 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
804 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
805 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
808 if (cur.textRow().endpos() == cur.pos() + 1 &&
809 cur.textRow().endpos() != cur.lastpos() &&
810 !cur.paragraph().isNewline(cur.pos()) &&
811 !cur.paragraph().isLineSeparator(cur.pos()) &&
812 !cur.paragraph().isSeparator(cur.pos())) {
813 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
816 // in front of RTL boundary? Stay on this side of the boundary because:
817 // ab|cDDEEFFghi -> abc|DDEEFFghi
818 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
819 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
822 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
825 // move to next paragraph
826 if (cur.pit() != cur.lastpit())
827 return setCursor(cur, cur.pit() + 1, 0);
832 bool Text::cursorUpParagraph(Cursor & cur)
834 bool updated = false;
836 updated = setCursor(cur, cur.pit(), 0);
837 else if (cur.pit() != 0)
838 updated = setCursor(cur, cur.pit() - 1, 0);
843 bool Text::cursorDownParagraph(Cursor & cur)
845 bool updated = false;
846 if (cur.pit() != cur.lastpit())
847 updated = setCursor(cur, cur.pit() + 1, 0);
849 updated = setCursor(cur, cur.pit(), cur.lastpos());
854 // fix the cursor `cur' after a characters has been deleted at `where'
855 // position. Called by deleteEmptyParagraphMechanism
856 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
858 // Do nothing if cursor is not in the paragraph where the
860 if (cur.pit() != where.pit())
863 // If cursor position is after the deletion place update it
864 if (cur.pos() > where.pos())
867 // Check also if we don't want to set the cursor on a spot behind the
868 // pagragraph because we erased the last character.
869 if (cur.pos() > cur.lastpos())
870 cur.pos() = cur.lastpos();
874 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
875 Cursor & old, bool & need_anchor_change)
877 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
879 Paragraph & oldpar = old.paragraph();
881 // We allow all kinds of "mumbo-jumbo" when freespacing.
882 if (oldpar.isFreeSpacing())
885 /* Ok I'll put some comments here about what is missing.
886 There are still some small problems that can lead to
887 double spaces stored in the document file or space at
888 the beginning of paragraphs(). This happens if you have
889 the cursor between to spaces and then save. Or if you
890 cut and paste and the selection have a space at the
891 beginning and then save right after the paste. (Lgb)
894 // If old.pos() == 0 and old.pos()(1) == LineSeparator
895 // delete the LineSeparator.
898 // If old.pos() == 1 and old.pos()(0) == LineSeparator
899 // delete the LineSeparator.
902 bool const same_inset = &old.inset() == &cur.inset();
903 bool const same_par = same_inset && old.pit() == cur.pit();
904 bool const same_par_pos = same_par && old.pos() == cur.pos();
906 // If the chars around the old cursor were spaces, delete one of them.
908 // Only if the cursor has really moved.
910 && old.pos() < oldpar.size()
911 && oldpar.isLineSeparator(old.pos())
912 && oldpar.isLineSeparator(old.pos() - 1)
913 && !oldpar.isDeleted(old.pos() - 1)
914 && !oldpar.isDeleted(old.pos())) {
915 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
916 // FIXME: This will not work anymore when we have multiple views of the same buffer
917 // In this case, we will have to correct also the cursors held by
918 // other bufferviews. It will probably be easier to do that in a more
919 // automated way in CursorSlice code. (JMarc 26/09/2001)
920 // correct all cursor parts
922 fixCursorAfterDelete(cur.top(), old.top());
923 need_anchor_change = true;
929 // only do our magic if we changed paragraph
933 // don't delete anything if this is the ONLY paragraph!
934 if (old.lastpit() == 0)
937 // Do not delete empty paragraphs with keepempty set.
938 if (oldpar.allowEmpty())
941 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
943 recordUndo(old, Undo::ATOMIC,
944 max(old.pit() - 1, pit_type(0)),
945 min(old.pit() + 1, old.lastpit()));
946 ParagraphList & plist = old.text()->paragraphs();
947 plist.erase(boost::next(plist.begin(), old.pit()));
949 // see #warning (FIXME?) above
950 if (cur.depth() >= old.depth()) {
951 CursorSlice & curslice = cur[old.depth() - 1];
952 if (&curslice.inset() == &old.inset()
953 && curslice.pit() > old.pit()) {
955 // since a paragraph has been deleted, all the
956 // insets after `old' have been copied and
957 // their address has changed. Therefore we
958 // need to `regenerate' cur. (JMarc)
959 cur.updateInsets(&(cur.bottom().inset()));
960 need_anchor_change = true;
966 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
967 need_anchor_change = true;
968 // We return true here because the Paragraph contents changed and
969 // we need a redraw before further action is processed.
977 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
979 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
981 for (pit_type pit = first; pit <= last; ++pit) {
982 Paragraph & par = pars_[pit];
984 // We allow all kinds of "mumbo-jumbo" when freespacing.
985 if (par.isFreeSpacing())
988 for (pos_type pos = 1; pos < par.size(); ++pos) {
989 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
990 && !par.isDeleted(pos - 1)) {
991 if (par.eraseChar(pos - 1, trackChanges)) {
997 // don't delete anything if this is the only remaining paragraph within the given range
998 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1002 // don't delete empty paragraphs with keepempty set
1003 if (par.allowEmpty())
1006 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1007 pars_.erase(boost::next(pars_.begin(), pit));
1013 par.stripLeadingSpaces(trackChanges);
1018 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1020 recordUndo(cur, Undo::ATOMIC, first, last);
1024 void Text::recUndo(Cursor & cur, pit_type par) const
1026 recordUndo(cur, Undo::ATOMIC, par, par);