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 : current_font(Font::ALL_INHERIT),
75 background_color_(Color::background),
80 bool Text::isMainText(Buffer const & buffer) const
82 return &buffer.text() == this;
86 // Gets the fully instantiated font at a given position in a paragraph
87 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
88 // The difference is that this one is used for displaying, and thus we
89 // are allowed to make cosmetic improvements. For instance make footnotes
91 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
92 pos_type const pos) const
94 BOOST_ASSERT(pos >= 0);
96 LayoutPtr const & layout = par.layout();
98 BufferParams const & params = buffer.params();
99 pos_type const body_pos = par.beginOfBody();
101 // We specialize the 95% common case:
102 if (!par.getDepth()) {
103 Font f = par.getFontSettings(params, pos);
104 if (!isMainText(buffer))
105 applyOuterFont(buffer, f);
108 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
109 lf = layout->labelfont;
110 rlf = layout->reslabelfont;
113 rlf = layout->resfont;
115 // In case the default family has been customized
116 if (lf.family() == Font::INHERIT_FAMILY)
117 rlf.setFamily(params.getFont().family());
118 return f.realize(rlf);
121 // The uncommon case need not be optimized as much
124 layoutfont = layout->labelfont;
126 layoutfont = layout->font;
128 Font font = par.getFontSettings(params, pos);
129 font.realize(layoutfont);
131 if (!isMainText(buffer))
132 applyOuterFont(buffer, font);
134 // Find the pit value belonging to paragraph. This will not break
135 // even if pars_ would not be a vector anymore.
136 // Performance appears acceptable.
138 pit_type pit = pars_.size();
139 for (pit_type it = 0; it < pit; ++it)
140 if (&pars_[it] == &par) {
144 // Realize against environment font information
145 // NOTE: the cast to pit_type should be removed when pit_type
146 // changes to a unsigned integer.
147 if (pit < pit_type(pars_.size()))
148 font.realize(outerFont(pit, pars_));
150 // Realize with the fonts of lesser depth.
151 font.realize(params.getFont());
156 // There are currently two font mechanisms in LyX:
157 // 1. The font attributes in a lyxtext, and
158 // 2. The inset-specific font properties, defined in an inset's
159 // metrics() and draw() methods and handed down the inset chain through
160 // the pi/mi parameters, and stored locally in a lyxtext in font_.
161 // This is where the two are integrated in the final fully realized
163 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
165 lf.reduce(buffer.params().getFont());
167 lf.setLanguage(font.language());
172 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
174 LayoutPtr const & layout = pars_[pit].layout();
176 if (!pars_[pit].getDepth()) {
177 Font lf = layout->resfont;
178 // In case the default family has been customized
179 if (layout->font.family() == Font::INHERIT_FAMILY)
180 lf.setFamily(buffer.params().getFont().family());
184 Font font = layout->font;
185 // Realize with the fonts of lesser depth.
186 //font.realize(outerFont(pit, paragraphs()));
187 font.realize(buffer.params().getFont());
193 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
195 LayoutPtr const & layout = par.layout();
197 if (!par.getDepth()) {
198 Font lf = layout->reslabelfont;
199 // In case the default family has been customized
200 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
201 lf.setFamily(buffer.params().getFont().family());
205 Font font = layout->labelfont;
206 // Realize with the fonts of lesser depth.
207 font.realize(buffer.params().getFont());
213 void Text::setCharFont(Buffer const & buffer, pit_type pit,
214 pos_type pos, Font const & fnt)
217 LayoutPtr const & layout = pars_[pit].layout();
219 // Get concrete layout font to reduce against
222 if (pos < pars_[pit].beginOfBody())
223 layoutfont = layout->labelfont;
225 layoutfont = layout->font;
227 // Realize against environment font information
228 if (pars_[pit].getDepth()) {
230 while (!layoutfont.resolved() &&
231 tp != pit_type(paragraphs().size()) &&
232 pars_[tp].getDepth()) {
233 tp = outerHook(tp, paragraphs());
234 if (tp != pit_type(paragraphs().size()))
235 layoutfont.realize(pars_[tp].layout()->font);
239 // Inside inset, apply the inset's font attributes if any
241 if (!isMainText(buffer))
242 layoutfont.realize(font_);
244 layoutfont.realize(buffer.params().getFont());
246 // Now, reduce font against full layout font
247 font.reduce(layoutfont);
249 pars_[pit].setFont(pos, font);
253 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
254 pos_type pos, Font const & font, bool toggleall)
256 BOOST_ASSERT(pars_[pit].isInset(pos) &&
257 pars_[pit].getInset(pos)->noFontChange());
259 Inset * const inset = pars_[pit].getInset(pos);
260 CursorSlice::idx_type endidx = inset->nargs();
261 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
262 Text * text = cs.text();
264 // last position of the cell
265 CursorSlice cellend = cs;
266 cellend.pit() = cellend.lastpit();
267 cellend.pos() = cellend.lastpos();
268 text->setFont(buffer, cs, cellend, font, toggleall);
274 // return past-the-last paragraph influenced by a layout change on pit
275 pit_type Text::undoSpan(pit_type pit)
277 pit_type end = paragraphs().size();
278 pit_type nextpit = pit + 1;
281 //because of parindents
282 if (!pars_[pit].getDepth())
283 return boost::next(nextpit);
284 //because of depth constrains
285 for (; nextpit != end; ++pit, ++nextpit) {
286 if (!pars_[pit].getDepth())
293 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
294 docstring const & layout)
296 BOOST_ASSERT(start != end);
298 BufferParams const & bufparams = buffer.params();
299 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
301 for (pit_type pit = start; pit != end; ++pit) {
302 Paragraph & par = pars_[pit];
303 par.applyLayout(lyxlayout);
304 if (lyxlayout->margintype == MARGIN_MANUAL)
305 par.setLabelWidthString(par.translateIfPossible(
306 lyxlayout->labelstring(), buffer.params()));
311 // set layout over selection and make a total rebreak of those paragraphs
312 void Text::setLayout(Cursor & cur, docstring const & layout)
314 BOOST_ASSERT(this == cur.text());
315 // special handling of new environment insets
316 BufferView & bv = cur.bv();
317 BufferParams const & params = bv.buffer().params();
318 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
319 if (lyxlayout->is_environment) {
320 // move everything in a new environment inset
321 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
322 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
323 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
324 lyx::dispatch(FuncRequest(LFUN_CUT));
325 Inset * inset = new InsetEnvironment(params, layout);
326 insertInset(cur, inset);
327 //inset->edit(cur, true);
328 //lyx::dispatch(FuncRequest(LFUN_PASTE));
332 pit_type start = cur.selBegin().pit();
333 pit_type end = cur.selEnd().pit() + 1;
334 pit_type undopit = undoSpan(end - 1);
335 recUndo(cur, start, undopit - 1);
336 setLayout(cur.buffer(), start, end, layout);
337 updateLabels(cur.buffer());
341 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
342 Paragraph const & par, int max_depth)
344 if (par.layout()->labeltype == LABEL_BIBLIO)
346 int const depth = par.params().depth();
347 if (type == Text::INC_DEPTH && depth < max_depth)
349 if (type == Text::DEC_DEPTH && depth > 0)
355 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
357 BOOST_ASSERT(this == cur.text());
358 // this happens when selecting several cells in tabular (bug 2630)
359 if (cur.selBegin().idx() != cur.selEnd().idx())
362 pit_type const beg = cur.selBegin().pit();
363 pit_type const end = cur.selEnd().pit() + 1;
364 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
366 for (pit_type pit = beg; pit != end; ++pit) {
367 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
369 max_depth = pars_[pit].getMaxDepthAfter();
375 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
377 BOOST_ASSERT(this == cur.text());
378 pit_type const beg = cur.selBegin().pit();
379 pit_type const end = cur.selEnd().pit() + 1;
380 recordUndoSelection(cur);
381 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
383 for (pit_type pit = beg; pit != end; ++pit) {
384 Paragraph & par = pars_[pit];
385 if (lyx::changeDepthAllowed(type, par, max_depth)) {
386 int const depth = par.params().depth();
387 if (type == INC_DEPTH)
388 par.params().depth(depth + 1);
390 par.params().depth(depth - 1);
392 max_depth = par.getMaxDepthAfter();
394 // this handles the counter labels, and also fixes up
395 // depth values for follow-on (child) paragraphs
396 updateLabels(cur.buffer());
400 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
402 BOOST_ASSERT(this == cur.text());
403 // Set the current_font
404 // Determine basis font
406 pit_type pit = cur.pit();
407 if (cur.pos() < pars_[pit].beginOfBody())
408 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
410 layoutfont = getLayoutFont(cur.buffer(), pit);
412 // Update current font
413 real_current_font.update(font,
414 cur.buffer().params().language,
417 // Reduce to implicit settings
418 current_font = real_current_font;
419 current_font.reduce(layoutfont);
420 // And resolve it completely
421 real_current_font.realize(layoutfont);
423 // if there is no selection that's all we need to do
424 if (!cur.selection())
427 // Ok, we have a selection.
428 recordUndoSelection(cur);
430 setFont(cur.buffer(), cur.selectionBegin().top(),
431 cur.selectionEnd().top(), font, toggleall);
435 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
436 CursorSlice const & end, Font const & font,
439 // Don't use forwardChar here as ditend might have
440 // pos() == lastpos() and forwardChar would miss it.
441 // Can't use forwardPos either as this descends into
443 Language const * language = buffer.params().language;
444 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
445 if (dit.pos() != dit.lastpos()) {
446 pit_type const pit = dit.pit();
447 pos_type const pos = dit.pos();
448 if (pars_[pit].isInset(pos) &&
449 pars_[pit].getInset(pos)->noFontChange())
450 // We need to propagate the font change to all
451 // text cells of the inset (bug 1973).
452 // FIXME: This should change, see documentation
453 // of noFontChange in Inset.h
454 setInsetFont(buffer, pit, pos, font, toggleall);
455 Font f = getFont(buffer, dit.paragraph(), pos);
456 f.update(font, language, toggleall);
457 setCharFont(buffer, pit, pos, f);
463 bool Text::cursorTop(Cursor & cur)
465 BOOST_ASSERT(this == cur.text());
466 return setCursor(cur, 0, 0);
470 bool Text::cursorBottom(Cursor & cur)
472 BOOST_ASSERT(this == cur.text());
473 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
477 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
479 BOOST_ASSERT(this == cur.text());
480 // If the mask is completely neutral, tell user
481 if (font == Font(Font::ALL_IGNORE)) {
482 // Could only happen with user style
483 cur.message(_("No font change defined."));
487 // Try implicit word selection
488 // If there is a change in the language the implicit word selection
490 CursorSlice resetCursor = cur.top();
491 bool implicitSelection =
492 font.language() == ignore_language
493 && font.number() == Font::IGNORE
494 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
497 setFont(cur, font, toggleall);
499 // Implicit selections are cleared afterwards
500 // and cursor is set to the original position.
501 if (implicitSelection) {
502 cur.clearSelection();
503 cur.top() = resetCursor;
509 docstring Text::getStringToIndex(Cursor const & cur)
511 BOOST_ASSERT(this == cur.text());
515 idxstring = cur.selectionAsString(false);
517 // Try implicit word selection. If there is a change
518 // in the language the implicit word selection is
521 selectWord(tmpcur, PREVIOUS_WORD);
523 if (!tmpcur.selection())
524 cur.message(_("Nothing to index!"));
525 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
526 cur.message(_("Cannot index more than one paragraph!"));
528 idxstring = tmpcur.selectionAsString(false);
535 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
537 BOOST_ASSERT(cur.text());
538 // make sure that the depth behind the selection are restored, too
539 pit_type undopit = undoSpan(cur.selEnd().pit());
540 recUndo(cur, cur.selBegin().pit(), undopit - 1);
542 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
544 Paragraph & par = pars_[pit];
545 ParagraphParameters params = par.params();
546 params.read(to_utf8(arg), merge);
547 Layout const & layout = *(par.layout());
548 par.params().apply(params, layout);
553 //FIXME This is a little redundant now, but it's probably worth keeping,
554 //especially if we're going to go away from using serialization internally
556 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
558 BOOST_ASSERT(cur.text());
559 // make sure that the depth behind the selection are restored, too
560 pit_type undopit = undoSpan(cur.selEnd().pit());
561 recUndo(cur, cur.selBegin().pit(), undopit - 1);
563 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
565 Paragraph & par = pars_[pit];
566 Layout const & layout = *(par.layout());
567 par.params().apply(p, layout);
572 // this really should just insert the inset and not move the cursor.
573 void Text::insertInset(Cursor & cur, Inset * inset)
575 BOOST_ASSERT(this == cur.text());
577 cur.paragraph().insertInset(cur.pos(), inset, current_font,
578 Change(cur.buffer().params().trackChanges ?
579 Change::INSERTED : Change::UNCHANGED));
583 // needed to insert the selection
584 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
586 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
587 current_font, str, autoBreakRows_);
591 // turn double CR to single CR, others are converted into one
592 // blank. Then insertStringAsLines is called
593 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
595 docstring linestr = str;
596 bool newline_inserted = false;
598 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
599 if (linestr[i] == '\n') {
600 if (newline_inserted) {
601 // we know that \r will be ignored by
602 // insertStringAsLines. Of course, it is a dirty
603 // trick, but it works...
604 linestr[i - 1] = '\r';
608 newline_inserted = true;
610 } else if (isPrintable(linestr[i])) {
611 newline_inserted = false;
614 insertStringAsLines(cur, linestr);
618 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
619 bool setfont, bool boundary)
622 setCursorIntern(cur, par, pos, setfont, boundary);
623 return cur.bv().checkDepm(cur, old);
627 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
629 BOOST_ASSERT(par != int(paragraphs().size()));
633 // now some strict checking
634 Paragraph & para = getPar(par);
636 // None of these should happen, but we're scaredy-cats
638 lyxerr << "dont like -1" << endl;
642 if (pos > para.size()) {
643 lyxerr << "dont like 1, pos: " << pos
644 << " size: " << para.size()
645 << " par: " << par << endl;
651 void Text::setCursorIntern(Cursor & cur,
652 pit_type par, pos_type pos, bool setfont, bool boundary)
654 BOOST_ASSERT(this == cur.text());
655 cur.boundary(boundary);
656 setCursor(cur.top(), par, pos);
662 void Text::setCurrentFont(Cursor & cur)
664 BOOST_ASSERT(this == cur.text());
665 pos_type pos = cur.pos();
666 Paragraph & par = cur.paragraph();
668 // are we behind previous char in fact? -> go to that char
669 if (pos > 0 && cur.boundary())
672 // find position to take the font from
674 // paragraph end? -> font of last char
675 if (pos == cur.lastpos())
677 // on space? -> look at the words in front of space
678 else if (pos > 0 && par.isSeparator(pos)) {
679 // abc| def -> font of c
680 // abc |[WERBEH], i.e. boundary==true -> font of c
681 // abc [WERBEH]| def, font of the space
682 if (!isRTLBoundary(cur.buffer(), par, pos))
688 BufferParams const & bufparams = cur.buffer().params();
689 current_font = par.getFontSettings(bufparams, pos);
690 real_current_font = getFont(cur.buffer(), par, pos);
692 // special case for paragraph end
693 if (cur.pos() == cur.lastpos()
694 && isRTLBoundary(cur.buffer(), par, cur.pos())
695 && !cur.boundary()) {
696 Language const * lang = par.getParLanguage(bufparams);
697 current_font.setLanguage(lang);
698 current_font.setNumber(Font::OFF);
699 real_current_font.setLanguage(lang);
700 real_current_font.setNumber(Font::OFF);
705 bool Text::checkAndActivateInset(Cursor & cur, bool front)
709 if (front && cur.pos() == cur.lastpos())
711 if (!front && cur.pos() == 0)
713 Inset * inset = front ? cur.nextInset() : cur.prevInset();
714 if (!isHighlyEditableInset(inset))
717 * Apparently, when entering an inset we are expected to be positioned
718 * *before* it in the containing paragraph, regardless of the direction
719 * from which we are entering. Otherwise, cursor placement goes awry,
720 * and when we exit from the beginning, we'll be placed *after* the
725 inset->edit(cur, front);
730 bool Text::cursorLeft(Cursor & cur)
732 // Tell BufferView to test for FitCursor in any case!
733 cur.updateFlags(Update::FitCursor);
735 // not at paragraph start?
737 // if on right side of boundary (i.e. not at paragraph end, but line end)
738 // -> skip it, i.e. set boundary to true, i.e. go only logically left
739 // there are some exceptions to ignore this: lineseps, newlines, spaces
741 // some effectless debug code to see the values in the debugger
742 bool bound = cur.boundary();
743 int rowpos = cur.textRow().pos();
745 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
746 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
747 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
749 if (!cur.boundary() &&
750 cur.textRow().pos() == cur.pos() &&
751 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
752 !cur.paragraph().isNewline(cur.pos() - 1) &&
753 !cur.paragraph().isSeparator(cur.pos() - 1)) {
754 return setCursor(cur, cur.pit(), cur.pos(), true, true);
757 // go left and try to enter inset
758 if (checkAndActivateInset(cur, false))
761 // normal character left
762 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
765 // move to the previous paragraph or do nothing
767 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
772 bool Text::cursorRight(Cursor & cur)
774 // Tell BufferView to test for FitCursor in any case!
775 cur.updateFlags(Update::FitCursor);
777 // not at paragraph end?
778 if (cur.pos() != cur.lastpos()) {
779 // in front of editable inset, i.e. jump into it?
780 if (checkAndActivateInset(cur, true))
783 // if left of boundary -> just jump to right side
784 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
785 if (cur.boundary() &&
786 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
787 return setCursor(cur, cur.pit(), cur.pos(), true, false);
789 // next position is left of boundary,
790 // but go to next line for special cases like space, newline, linesep
792 // some effectless debug code to see the values in the debugger
793 int endpos = cur.textRow().endpos();
794 int lastpos = cur.lastpos();
796 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
797 bool newline = cur.paragraph().isNewline(cur.pos());
798 bool sep = cur.paragraph().isSeparator(cur.pos());
799 if (cur.pos() != cur.lastpos()) {
800 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
801 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
802 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
805 if (cur.textRow().endpos() == cur.pos() + 1 &&
806 cur.textRow().endpos() != cur.lastpos() &&
807 !cur.paragraph().isNewline(cur.pos()) &&
808 !cur.paragraph().isLineSeparator(cur.pos()) &&
809 !cur.paragraph().isSeparator(cur.pos())) {
810 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
813 // in front of RTL boundary? Stay on this side of the boundary because:
814 // ab|cDDEEFFghi -> abc|DDEEFFghi
815 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
816 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
819 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
822 // move to next paragraph
823 if (cur.pit() != cur.lastpit())
824 return setCursor(cur, cur.pit() + 1, 0);
829 bool Text::cursorUpParagraph(Cursor & cur)
831 bool updated = false;
833 updated = setCursor(cur, cur.pit(), 0);
834 else if (cur.pit() != 0)
835 updated = setCursor(cur, cur.pit() - 1, 0);
840 bool Text::cursorDownParagraph(Cursor & cur)
842 bool updated = false;
843 if (cur.pit() != cur.lastpit())
844 updated = setCursor(cur, cur.pit() + 1, 0);
846 updated = setCursor(cur, cur.pit(), cur.lastpos());
851 // fix the cursor `cur' after a characters has been deleted at `where'
852 // position. Called by deleteEmptyParagraphMechanism
853 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
855 // Do nothing if cursor is not in the paragraph where the
857 if (cur.pit() != where.pit())
860 // If cursor position is after the deletion place update it
861 if (cur.pos() > where.pos())
864 // Check also if we don't want to set the cursor on a spot behind the
865 // pagragraph because we erased the last character.
866 if (cur.pos() > cur.lastpos())
867 cur.pos() = cur.lastpos();
871 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
872 Cursor & old, bool & need_anchor_change)
874 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
876 Paragraph & oldpar = old.paragraph();
878 // We allow all kinds of "mumbo-jumbo" when freespacing.
879 if (oldpar.isFreeSpacing())
882 /* Ok I'll put some comments here about what is missing.
883 There are still some small problems that can lead to
884 double spaces stored in the document file or space at
885 the beginning of paragraphs(). This happens if you have
886 the cursor between to spaces and then save. Or if you
887 cut and paste and the selection have a space at the
888 beginning and then save right after the paste. (Lgb)
891 // If old.pos() == 0 and old.pos()(1) == LineSeparator
892 // delete the LineSeparator.
895 // If old.pos() == 1 and old.pos()(0) == LineSeparator
896 // delete the LineSeparator.
899 bool const same_inset = &old.inset() == &cur.inset();
900 bool const same_par = same_inset && old.pit() == cur.pit();
901 bool const same_par_pos = same_par && old.pos() == cur.pos();
903 // If the chars around the old cursor were spaces, delete one of them.
905 // Only if the cursor has really moved.
907 && old.pos() < oldpar.size()
908 && oldpar.isLineSeparator(old.pos())
909 && oldpar.isLineSeparator(old.pos() - 1)
910 && !oldpar.isDeleted(old.pos() - 1)
911 && !oldpar.isDeleted(old.pos())) {
912 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
913 // FIXME: This will not work anymore when we have multiple views of the same buffer
914 // In this case, we will have to correct also the cursors held by
915 // other bufferviews. It will probably be easier to do that in a more
916 // automated way in CursorSlice code. (JMarc 26/09/2001)
917 // correct all cursor parts
919 fixCursorAfterDelete(cur.top(), old.top());
920 need_anchor_change = true;
926 // only do our magic if we changed paragraph
930 // don't delete anything if this is the ONLY paragraph!
931 if (old.lastpit() == 0)
934 // Do not delete empty paragraphs with keepempty set.
935 if (oldpar.allowEmpty())
938 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
940 recordUndo(old, Undo::ATOMIC,
941 max(old.pit() - 1, pit_type(0)),
942 min(old.pit() + 1, old.lastpit()));
943 ParagraphList & plist = old.text()->paragraphs();
944 plist.erase(boost::next(plist.begin(), old.pit()));
946 // see #warning (FIXME?) above
947 if (cur.depth() >= old.depth()) {
948 CursorSlice & curslice = cur[old.depth() - 1];
949 if (&curslice.inset() == &old.inset()
950 && curslice.pit() > old.pit()) {
952 // since a paragraph has been deleted, all the
953 // insets after `old' have been copied and
954 // their address has changed. Therefore we
955 // need to `regenerate' cur. (JMarc)
956 cur.updateInsets(&(cur.bottom().inset()));
957 need_anchor_change = true;
963 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
964 need_anchor_change = true;
965 // We return true here because the Paragraph contents changed and
966 // we need a redraw before further action is processed.
974 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
976 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
978 for (pit_type pit = first; pit <= last; ++pit) {
979 Paragraph & par = pars_[pit];
981 // We allow all kinds of "mumbo-jumbo" when freespacing.
982 if (par.isFreeSpacing())
985 for (pos_type pos = 1; pos < par.size(); ++pos) {
986 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
987 && !par.isDeleted(pos - 1)) {
988 if (par.eraseChar(pos - 1, trackChanges)) {
994 // don't delete anything if this is the only remaining paragraph within the given range
995 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
999 // don't delete empty paragraphs with keepempty set
1000 if (par.allowEmpty())
1003 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1004 pars_.erase(boost::next(pars_.begin(), pit));
1010 par.stripLeadingSpaces(trackChanges);
1015 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1017 recordUndo(cur, Undo::ATOMIC, first, last);
1021 void Text::recUndo(Cursor & cur, pit_type par) const
1023 recordUndo(cur, Undo::ATOMIC, par, par);