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"
46 #include "Paragraph.h"
47 #include "TextMetrics.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
52 #include "ServerSocket.h"
56 #include "frontends/FontMetrics.h"
58 #include "insets/InsetEnvironment.h"
60 #include "mathed/InsetMathHull.h"
62 #include "support/textutils.h"
64 #include <boost/current_function.hpp>
72 using std::ostringstream;
76 using std::istringstream;
79 : current_font(Font::ALL_INHERIT),
80 background_color_(Color::background),
85 bool Text::isMainText(Buffer const & buffer) const
87 return &buffer.text() == this;
91 // Gets the fully instantiated font at a given position in a paragraph
92 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
93 // The difference is that this one is used for displaying, and thus we
94 // are allowed to make cosmetic improvements. For instance make footnotes
96 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
97 pos_type const pos) const
99 BOOST_ASSERT(pos >= 0);
101 LayoutPtr const & layout = par.layout();
103 BufferParams const & params = buffer.params();
104 pos_type const body_pos = par.beginOfBody();
106 // We specialize the 95% common case:
107 if (!par.getDepth()) {
108 Font f = par.getFontSettings(params, pos);
109 if (!isMainText(buffer))
110 applyOuterFont(buffer, f);
113 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
114 lf = layout->labelfont;
115 rlf = layout->reslabelfont;
118 rlf = layout->resfont;
120 // In case the default family has been customized
121 if (lf.family() == Font::INHERIT_FAMILY)
122 rlf.setFamily(params.getFont().family());
123 return f.realize(rlf);
126 // The uncommon case need not be optimized as much
129 layoutfont = layout->labelfont;
131 layoutfont = layout->font;
133 Font font = par.getFontSettings(params, pos);
134 font.realize(layoutfont);
136 if (!isMainText(buffer))
137 applyOuterFont(buffer, font);
139 // Find the pit value belonging to paragraph. This will not break
140 // even if pars_ would not be a vector anymore.
141 // Performance appears acceptable.
143 pit_type pit = pars_.size();
144 for (pit_type it = 0; it < pit; ++it)
145 if (&pars_[it] == &par) {
149 // Realize against environment font information
150 // NOTE: the cast to pit_type should be removed when pit_type
151 // changes to a unsigned integer.
152 if (pit < pit_type(pars_.size()))
153 font.realize(outerFont(pit, pars_));
155 // Realize with the fonts of lesser depth.
156 font.realize(params.getFont());
161 // There are currently two font mechanisms in LyX:
162 // 1. The font attributes in a lyxtext, and
163 // 2. The inset-specific font properties, defined in an inset's
164 // metrics() and draw() methods and handed down the inset chain through
165 // the pi/mi parameters, and stored locally in a lyxtext in font_.
166 // This is where the two are integrated in the final fully realized
168 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
170 lf.reduce(buffer.params().getFont());
172 lf.setLanguage(font.language());
177 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
179 LayoutPtr const & layout = pars_[pit].layout();
181 if (!pars_[pit].getDepth()) {
182 Font lf = layout->resfont;
183 // In case the default family has been customized
184 if (layout->font.family() == Font::INHERIT_FAMILY)
185 lf.setFamily(buffer.params().getFont().family());
189 Font font = layout->font;
190 // Realize with the fonts of lesser depth.
191 //font.realize(outerFont(pit, paragraphs()));
192 font.realize(buffer.params().getFont());
198 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
200 LayoutPtr const & layout = par.layout();
202 if (!par.getDepth()) {
203 Font lf = layout->reslabelfont;
204 // In case the default family has been customized
205 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
206 lf.setFamily(buffer.params().getFont().family());
210 Font font = layout->labelfont;
211 // Realize with the fonts of lesser depth.
212 font.realize(buffer.params().getFont());
218 void Text::setCharFont(Buffer const & buffer, pit_type pit,
219 pos_type pos, Font const & fnt)
222 LayoutPtr const & layout = pars_[pit].layout();
224 // Get concrete layout font to reduce against
227 if (pos < pars_[pit].beginOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 // Realize against environment font information
233 if (pars_[pit].getDepth()) {
235 while (!layoutfont.resolved() &&
236 tp != pit_type(paragraphs().size()) &&
237 pars_[tp].getDepth()) {
238 tp = outerHook(tp, paragraphs());
239 if (tp != pit_type(paragraphs().size()))
240 layoutfont.realize(pars_[tp].layout()->font);
244 // Inside inset, apply the inset's font attributes if any
246 if (!isMainText(buffer))
247 layoutfont.realize(font_);
249 layoutfont.realize(buffer.params().getFont());
251 // Now, reduce font against full layout font
252 font.reduce(layoutfont);
254 pars_[pit].setFont(pos, font);
258 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
259 pos_type pos, Font const & font, bool toggleall)
261 BOOST_ASSERT(pars_[pit].isInset(pos) &&
262 pars_[pit].getInset(pos)->noFontChange());
264 Inset * const inset = pars_[pit].getInset(pos);
265 CursorSlice::idx_type endidx = inset->nargs();
266 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
267 Text * text = cs.text();
269 // last position of the cell
270 CursorSlice cellend = cs;
271 cellend.pit() = cellend.lastpit();
272 cellend.pos() = cellend.lastpos();
273 text->setFont(buffer, cs, cellend, font, toggleall);
279 // return past-the-last paragraph influenced by a layout change on pit
280 pit_type Text::undoSpan(pit_type pit)
282 pit_type end = paragraphs().size();
283 pit_type nextpit = pit + 1;
286 //because of parindents
287 if (!pars_[pit].getDepth())
288 return boost::next(nextpit);
289 //because of depth constrains
290 for (; nextpit != end; ++pit, ++nextpit) {
291 if (!pars_[pit].getDepth())
298 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
299 docstring const & layout)
301 BOOST_ASSERT(start != end);
303 BufferParams const & bufparams = buffer.params();
304 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
306 for (pit_type pit = start; pit != end; ++pit) {
307 Paragraph & par = pars_[pit];
308 par.applyLayout(lyxlayout);
309 if (lyxlayout->margintype == MARGIN_MANUAL)
310 par.setLabelWidthString(par.translateIfPossible(
311 lyxlayout->labelstring(), buffer.params()));
316 // set layout over selection and make a total rebreak of those paragraphs
317 void Text::setLayout(Cursor & cur, docstring const & layout)
319 BOOST_ASSERT(this == cur.text());
320 // special handling of new environment insets
321 BufferView & bv = cur.bv();
322 BufferParams const & params = bv.buffer().params();
323 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
324 if (lyxlayout->is_environment) {
325 // move everything in a new environment inset
326 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
327 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
328 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
329 lyx::dispatch(FuncRequest(LFUN_CUT));
330 Inset * inset = new InsetEnvironment(params, layout);
331 insertInset(cur, inset);
332 //inset->edit(cur, true);
333 //lyx::dispatch(FuncRequest(LFUN_PASTE));
337 pit_type start = cur.selBegin().pit();
338 pit_type end = cur.selEnd().pit() + 1;
339 pit_type undopit = undoSpan(end - 1);
340 recUndo(cur, start, undopit - 1);
341 setLayout(cur.buffer(), start, end, layout);
342 updateLabels(cur.buffer());
346 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
347 Paragraph const & par, int max_depth)
349 if (par.layout()->labeltype == LABEL_BIBLIO)
351 int const depth = par.params().depth();
352 if (type == Text::INC_DEPTH && depth < max_depth)
354 if (type == Text::DEC_DEPTH && depth > 0)
360 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
362 BOOST_ASSERT(this == cur.text());
363 // this happens when selecting several cells in tabular (bug 2630)
364 if (cur.selBegin().idx() != cur.selEnd().idx())
367 pit_type const beg = cur.selBegin().pit();
368 pit_type const end = cur.selEnd().pit() + 1;
369 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
371 for (pit_type pit = beg; pit != end; ++pit) {
372 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
374 max_depth = pars_[pit].getMaxDepthAfter();
380 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
382 BOOST_ASSERT(this == cur.text());
383 pit_type const beg = cur.selBegin().pit();
384 pit_type const end = cur.selEnd().pit() + 1;
385 recordUndoSelection(cur);
386 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
388 for (pit_type pit = beg; pit != end; ++pit) {
389 Paragraph & par = pars_[pit];
390 if (lyx::changeDepthAllowed(type, par, max_depth)) {
391 int const depth = par.params().depth();
392 if (type == INC_DEPTH)
393 par.params().depth(depth + 1);
395 par.params().depth(depth - 1);
397 max_depth = par.getMaxDepthAfter();
399 // this handles the counter labels, and also fixes up
400 // depth values for follow-on (child) paragraphs
401 updateLabels(cur.buffer());
405 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
407 BOOST_ASSERT(this == cur.text());
408 // Set the current_font
409 // Determine basis font
411 pit_type pit = cur.pit();
412 if (cur.pos() < pars_[pit].beginOfBody())
413 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
415 layoutfont = getLayoutFont(cur.buffer(), pit);
417 // Update current font
418 real_current_font.update(font,
419 cur.buffer().params().language,
422 // Reduce to implicit settings
423 current_font = real_current_font;
424 current_font.reduce(layoutfont);
425 // And resolve it completely
426 real_current_font.realize(layoutfont);
428 // if there is no selection that's all we need to do
429 if (!cur.selection())
432 // Ok, we have a selection.
433 recordUndoSelection(cur);
435 setFont(cur.buffer(), cur.selectionBegin().top(),
436 cur.selectionEnd().top(), font, toggleall);
440 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
441 CursorSlice const & end, Font const & font,
444 // Don't use forwardChar here as ditend might have
445 // pos() == lastpos() and forwardChar would miss it.
446 // Can't use forwardPos either as this descends into
448 Language const * language = buffer.params().language;
449 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
450 if (dit.pos() != dit.lastpos()) {
451 pit_type const pit = dit.pit();
452 pos_type const pos = dit.pos();
453 if (pars_[pit].isInset(pos) &&
454 pars_[pit].getInset(pos)->noFontChange())
455 // We need to propagate the font change to all
456 // text cells of the inset (bug 1973).
457 // FIXME: This should change, see documentation
458 // of noFontChange in Inset.h
459 setInsetFont(buffer, pit, pos, font, toggleall);
460 Font f = getFont(buffer, dit.paragraph(), pos);
461 f.update(font, language, toggleall);
462 setCharFont(buffer, pit, pos, f);
468 // the cursor set functions have a special mechanism. When they
469 // realize you left an empty paragraph, they will delete it.
471 bool Text::cursorHome(Cursor & cur)
473 BOOST_ASSERT(this == cur.text());
474 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
475 Row const & row = pm.getRow(cur.pos(),cur.boundary());
476 return setCursor(cur, cur.pit(), row.pos());
480 bool Text::cursorEnd(Cursor & cur)
482 BOOST_ASSERT(this == cur.text());
483 // if not on the last row of the par, put the cursor before
484 // the final space exept if I have a spanning inset or one string
485 // is so long that we force a break.
486 pos_type end = cur.textRow().endpos();
488 // empty text, end-1 is no valid position
490 bool boundary = false;
491 if (end != cur.lastpos()) {
492 if (!cur.paragraph().isLineSeparator(end-1)
493 && !cur.paragraph().isNewline(end-1))
498 return setCursor(cur, cur.pit(), end, true, boundary);
502 bool Text::cursorTop(Cursor & cur)
504 BOOST_ASSERT(this == cur.text());
505 return setCursor(cur, 0, 0);
509 bool Text::cursorBottom(Cursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
516 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
518 BOOST_ASSERT(this == cur.text());
519 // If the mask is completely neutral, tell user
520 if (font == Font(Font::ALL_IGNORE)) {
521 // Could only happen with user style
522 cur.message(_("No font change defined."));
526 // Try implicit word selection
527 // If there is a change in the language the implicit word selection
529 CursorSlice resetCursor = cur.top();
530 bool implicitSelection =
531 font.language() == ignore_language
532 && font.number() == Font::IGNORE
533 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
536 setFont(cur, font, toggleall);
538 // Implicit selections are cleared afterwards
539 // and cursor is set to the original position.
540 if (implicitSelection) {
541 cur.clearSelection();
542 cur.top() = resetCursor;
548 docstring Text::getStringToIndex(Cursor const & cur)
550 BOOST_ASSERT(this == cur.text());
554 idxstring = cur.selectionAsString(false);
556 // Try implicit word selection. If there is a change
557 // in the language the implicit word selection is
560 selectWord(tmpcur, PREVIOUS_WORD);
562 if (!tmpcur.selection())
563 cur.message(_("Nothing to index!"));
564 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
565 cur.message(_("Cannot index more than one paragraph!"));
567 idxstring = tmpcur.selectionAsString(false);
574 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
576 BOOST_ASSERT(cur.text());
577 // make sure that the depth behind the selection are restored, too
578 pit_type undopit = undoSpan(cur.selEnd().pit());
579 recUndo(cur, cur.selBegin().pit(), undopit - 1);
581 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
583 Paragraph & par = pars_[pit];
584 ParagraphParameters params = par.params();
585 params.read(to_utf8(arg), merge);
586 Layout const & layout = *(par.layout());
587 par.params().apply(params, layout);
592 //FIXME This is a little redundant now, but it's probably worth keeping,
593 //especially if we're going to go away from using serialization internally
595 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
597 BOOST_ASSERT(cur.text());
598 // make sure that the depth behind the selection are restored, too
599 pit_type undopit = undoSpan(cur.selEnd().pit());
600 recUndo(cur, cur.selBegin().pit(), undopit - 1);
602 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
604 Paragraph & par = pars_[pit];
605 Layout const & layout = *(par.layout());
606 par.params().apply(p, layout);
611 // this really should just insert the inset and not move the cursor.
612 void Text::insertInset(Cursor & cur, Inset * inset)
614 BOOST_ASSERT(this == cur.text());
616 cur.paragraph().insertInset(cur.pos(), inset, current_font,
617 Change(cur.buffer().params().trackChanges ?
618 Change::INSERTED : Change::UNCHANGED));
622 // needed to insert the selection
623 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
625 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
626 current_font, str, autoBreakRows_);
630 // turn double CR to single CR, others are converted into one
631 // blank. Then insertStringAsLines is called
632 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
634 docstring linestr = str;
635 bool newline_inserted = false;
637 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
638 if (linestr[i] == '\n') {
639 if (newline_inserted) {
640 // we know that \r will be ignored by
641 // insertStringAsLines. Of course, it is a dirty
642 // trick, but it works...
643 linestr[i - 1] = '\r';
647 newline_inserted = true;
649 } else if (isPrintable(linestr[i])) {
650 newline_inserted = false;
653 insertStringAsLines(cur, linestr);
657 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
658 bool setfont, bool boundary)
661 setCursorIntern(cur, par, pos, setfont, boundary);
662 return cur.bv().checkDepm(cur, old);
666 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
668 BOOST_ASSERT(par != int(paragraphs().size()));
672 // now some strict checking
673 Paragraph & para = getPar(par);
675 // None of these should happen, but we're scaredy-cats
677 lyxerr << "dont like -1" << endl;
681 if (pos > para.size()) {
682 lyxerr << "dont like 1, pos: " << pos
683 << " size: " << para.size()
684 << " par: " << par << endl;
690 void Text::setCursorIntern(Cursor & cur,
691 pit_type par, pos_type pos, bool setfont, bool boundary)
693 BOOST_ASSERT(this == cur.text());
694 cur.boundary(boundary);
695 setCursor(cur.top(), par, pos);
701 void Text::setCurrentFont(Cursor & cur)
703 BOOST_ASSERT(this == cur.text());
704 pos_type pos = cur.pos();
705 Paragraph & par = cur.paragraph();
707 // are we behind previous char in fact? -> go to that char
708 if (pos > 0 && cur.boundary())
711 // find position to take the font from
713 // paragraph end? -> font of last char
714 if (pos == cur.lastpos())
716 // on space? -> look at the words in front of space
717 else if (pos > 0 && par.isSeparator(pos)) {
718 // abc| def -> font of c
719 // abc |[WERBEH], i.e. boundary==true -> font of c
720 // abc [WERBEH]| def, font of the space
721 if (!isRTLBoundary(cur.buffer(), par, pos))
727 BufferParams const & bufparams = cur.buffer().params();
728 current_font = par.getFontSettings(bufparams, pos);
729 real_current_font = getFont(cur.buffer(), par, pos);
731 // special case for paragraph end
732 if (cur.pos() == cur.lastpos()
733 && isRTLBoundary(cur.buffer(), par, cur.pos())
734 && !cur.boundary()) {
735 Language const * lang = par.getParLanguage(bufparams);
736 current_font.setLanguage(lang);
737 current_font.setNumber(Font::OFF);
738 real_current_font.setLanguage(lang);
739 real_current_font.setNumber(Font::OFF);
744 bool Text::checkAndActivateInset(Cursor & cur, bool front)
748 if (front && cur.pos() == cur.lastpos())
750 if (!front && cur.pos() == 0)
752 Inset * inset = front ? cur.nextInset() : cur.prevInset();
753 if (!isHighlyEditableInset(inset))
756 * Apparently, when entering an inset we are expected to be positioned
757 * *before* it in the containing paragraph, regardless of the direction
758 * from which we are entering. Otherwise, cursor placement goes awry,
759 * and when we exit from the beginning, we'll be placed *after* the
764 inset->edit(cur, front);
769 bool Text::cursorLeft(Cursor & cur)
771 // Tell BufferView to test for FitCursor in any case!
772 cur.updateFlags(Update::FitCursor);
774 // not at paragraph start?
776 // if on right side of boundary (i.e. not at paragraph end, but line end)
777 // -> skip it, i.e. set boundary to true, i.e. go only logically left
778 // there are some exceptions to ignore this: lineseps, newlines, spaces
780 // some effectless debug code to see the values in the debugger
781 bool bound = cur.boundary();
782 int rowpos = cur.textRow().pos();
784 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
785 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
786 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
788 if (!cur.boundary() &&
789 cur.textRow().pos() == cur.pos() &&
790 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
791 !cur.paragraph().isNewline(cur.pos() - 1) &&
792 !cur.paragraph().isSeparator(cur.pos() - 1)) {
793 return setCursor(cur, cur.pit(), cur.pos(), true, true);
796 // go left and try to enter inset
797 if (checkAndActivateInset(cur, false))
800 // normal character left
801 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
804 // move to the previous paragraph or do nothing
806 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
811 bool Text::cursorRight(Cursor & cur)
813 // Tell BufferView to test for FitCursor in any case!
814 cur.updateFlags(Update::FitCursor);
816 // not at paragraph end?
817 if (cur.pos() != cur.lastpos()) {
818 // in front of editable inset, i.e. jump into it?
819 if (checkAndActivateInset(cur, true))
822 // if left of boundary -> just jump to right side
823 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
824 if (cur.boundary() &&
825 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
826 return setCursor(cur, cur.pit(), cur.pos(), true, false);
828 // next position is left of boundary,
829 // but go to next line for special cases like space, newline, linesep
831 // some effectless debug code to see the values in the debugger
832 int endpos = cur.textRow().endpos();
833 int lastpos = cur.lastpos();
835 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
836 bool newline = cur.paragraph().isNewline(cur.pos());
837 bool sep = cur.paragraph().isSeparator(cur.pos());
838 if (cur.pos() != cur.lastpos()) {
839 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
840 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
841 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
844 if (cur.textRow().endpos() == cur.pos() + 1 &&
845 cur.textRow().endpos() != cur.lastpos() &&
846 !cur.paragraph().isNewline(cur.pos()) &&
847 !cur.paragraph().isLineSeparator(cur.pos()) &&
848 !cur.paragraph().isSeparator(cur.pos())) {
849 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
852 // in front of RTL boundary? Stay on this side of the boundary because:
853 // ab|cDDEEFFghi -> abc|DDEEFFghi
854 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
855 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
858 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
861 // move to next paragraph
862 if (cur.pit() != cur.lastpit())
863 return setCursor(cur, cur.pit() + 1, 0);
868 bool Text::cursorUpParagraph(Cursor & cur)
870 bool updated = false;
872 updated = setCursor(cur, cur.pit(), 0);
873 else if (cur.pit() != 0)
874 updated = setCursor(cur, cur.pit() - 1, 0);
879 bool Text::cursorDownParagraph(Cursor & cur)
881 bool updated = false;
882 if (cur.pit() != cur.lastpit())
883 updated = setCursor(cur, cur.pit() + 1, 0);
885 updated = setCursor(cur, cur.pit(), cur.lastpos());
890 // fix the cursor `cur' after a characters has been deleted at `where'
891 // position. Called by deleteEmptyParagraphMechanism
892 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
894 // Do nothing if cursor is not in the paragraph where the
896 if (cur.pit() != where.pit())
899 // If cursor position is after the deletion place update it
900 if (cur.pos() > where.pos())
903 // Check also if we don't want to set the cursor on a spot behind the
904 // pagragraph because we erased the last character.
905 if (cur.pos() > cur.lastpos())
906 cur.pos() = cur.lastpos();
910 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
911 Cursor & old, bool & need_anchor_change)
913 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
915 Paragraph & oldpar = old.paragraph();
917 // We allow all kinds of "mumbo-jumbo" when freespacing.
918 if (oldpar.isFreeSpacing())
921 /* Ok I'll put some comments here about what is missing.
922 There are still some small problems that can lead to
923 double spaces stored in the document file or space at
924 the beginning of paragraphs(). This happens if you have
925 the cursor between to spaces and then save. Or if you
926 cut and paste and the selection have a space at the
927 beginning and then save right after the paste. (Lgb)
930 // If old.pos() == 0 and old.pos()(1) == LineSeparator
931 // delete the LineSeparator.
934 // If old.pos() == 1 and old.pos()(0) == LineSeparator
935 // delete the LineSeparator.
938 bool const same_inset = &old.inset() == &cur.inset();
939 bool const same_par = same_inset && old.pit() == cur.pit();
940 bool const same_par_pos = same_par && old.pos() == cur.pos();
942 // If the chars around the old cursor were spaces, delete one of them.
944 // Only if the cursor has really moved.
946 && old.pos() < oldpar.size()
947 && oldpar.isLineSeparator(old.pos())
948 && oldpar.isLineSeparator(old.pos() - 1)
949 && !oldpar.isDeleted(old.pos() - 1)
950 && !oldpar.isDeleted(old.pos())) {
951 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
952 // FIXME: This will not work anymore when we have multiple views of the same buffer
953 // In this case, we will have to correct also the cursors held by
954 // other bufferviews. It will probably be easier to do that in a more
955 // automated way in CursorSlice code. (JMarc 26/09/2001)
956 // correct all cursor parts
958 fixCursorAfterDelete(cur.top(), old.top());
959 need_anchor_change = true;
965 // only do our magic if we changed paragraph
969 // don't delete anything if this is the ONLY paragraph!
970 if (old.lastpit() == 0)
973 // Do not delete empty paragraphs with keepempty set.
974 if (oldpar.allowEmpty())
977 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
979 recordUndo(old, Undo::ATOMIC,
980 max(old.pit() - 1, pit_type(0)),
981 min(old.pit() + 1, old.lastpit()));
982 ParagraphList & plist = old.text()->paragraphs();
983 plist.erase(boost::next(plist.begin(), old.pit()));
985 // see #warning (FIXME?) above
986 if (cur.depth() >= old.depth()) {
987 CursorSlice & curslice = cur[old.depth() - 1];
988 if (&curslice.inset() == &old.inset()
989 && curslice.pit() > old.pit()) {
991 // since a paragraph has been deleted, all the
992 // insets after `old' have been copied and
993 // their address has changed. Therefore we
994 // need to `regenerate' cur. (JMarc)
995 cur.updateInsets(&(cur.bottom().inset()));
996 need_anchor_change = true;
1002 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1003 need_anchor_change = true;
1004 // We return true here because the Paragraph contents changed and
1005 // we need a redraw before further action is processed.
1013 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1015 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1017 for (pit_type pit = first; pit <= last; ++pit) {
1018 Paragraph & par = pars_[pit];
1020 // We allow all kinds of "mumbo-jumbo" when freespacing.
1021 if (par.isFreeSpacing())
1024 for (pos_type pos = 1; pos < par.size(); ++pos) {
1025 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1026 && !par.isDeleted(pos - 1)) {
1027 if (par.eraseChar(pos - 1, trackChanges)) {
1033 // don't delete anything if this is the only remaining paragraph within the given range
1034 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1038 // don't delete empty paragraphs with keepempty set
1039 if (par.allowEmpty())
1042 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1043 pars_.erase(boost::next(pars_.begin(), pit));
1049 par.stripLeadingSpaces(trackChanges);
1054 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1056 recordUndo(cur, Undo::ATOMIC, first, last);
1060 void Text::recUndo(Cursor & cur, pit_type par) const
1062 recordUndo(cur, Undo::ATOMIC, par, par);