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.
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
34 #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 "TextClass.h"
47 #include "TextMetrics.h"
50 #include "frontends/FontMetrics.h"
52 #include "insets/InsetEnvironment.h"
54 #include "mathed/InsetMathHull.h"
56 #include "support/debug.h"
57 #include "support/gettext.h"
58 #include "support/textutils.h"
60 #include <boost/next_prior.hpp>
69 : autoBreakRows_(false)
73 bool Text::isMainText(Buffer const & buffer) const
75 return &buffer.text() == this;
79 FontInfo Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
81 LayoutPtr const & layout = pars_[pit].layout();
83 if (!pars_[pit].getDepth()) {
84 FontInfo lf = layout->resfont;
85 // In case the default family has been customized
86 if (layout->font.family() == INHERIT_FAMILY)
87 lf.setFamily(buffer.params().getFont().fontInfo().family());
91 FontInfo font = layout->font;
92 // Realize with the fonts of lesser depth.
93 //font.realize(outerFont(pit, paragraphs()));
94 font.realize(buffer.params().getFont().fontInfo());
100 FontInfo Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
102 LayoutPtr const & layout = par.layout();
104 if (!par.getDepth()) {
105 FontInfo lf = layout->reslabelfont;
106 // In case the default family has been customized
107 if (layout->labelfont.family() == INHERIT_FAMILY)
108 lf.setFamily(buffer.params().getFont().fontInfo().family());
112 FontInfo font = layout->labelfont;
113 // Realize with the fonts of lesser depth.
114 font.realize(buffer.params().getFont().fontInfo());
120 void Text::setCharFont(Buffer const & buffer, pit_type pit,
121 pos_type pos, Font const & fnt, Font const & display_font)
124 LayoutPtr const & layout = pars_[pit].layout();
126 // Get concrete layout font to reduce against
129 if (pos < pars_[pit].beginOfBody())
130 layoutfont = layout->labelfont;
132 layoutfont = layout->font;
134 // Realize against environment font information
135 if (pars_[pit].getDepth()) {
137 while (!layoutfont.resolved() &&
138 tp != pit_type(paragraphs().size()) &&
139 pars_[tp].getDepth()) {
140 tp = outerHook(tp, paragraphs());
141 if (tp != pit_type(paragraphs().size()))
142 layoutfont.realize(pars_[tp].layout()->font);
146 // Inside inset, apply the inset's font attributes if any
148 if (!isMainText(buffer))
149 layoutfont.realize(display_font.fontInfo());
151 layoutfont.realize(buffer.params().getFont().fontInfo());
153 // Now, reduce font against full layout font
154 font.fontInfo().reduce(layoutfont);
156 pars_[pit].setFont(pos, font);
160 void Text::setInsetFont(BufferView const & bv, pit_type pit,
161 pos_type pos, Font const & font, bool toggleall)
163 Inset * const inset = pars_[pit].getInset(pos);
164 BOOST_ASSERT(inset && inset->noFontChange());
166 CursorSlice::idx_type endidx = inset->nargs();
167 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
168 Text * text = cs.text();
170 // last position of the cell
171 CursorSlice cellend = cs;
172 cellend.pit() = cellend.lastpit();
173 cellend.pos() = cellend.lastpos();
174 text->setFont(bv, cs, cellend, font, toggleall);
180 // return past-the-last paragraph influenced by a layout change on pit
181 pit_type Text::undoSpan(pit_type pit)
183 pit_type end = paragraphs().size();
184 pit_type nextpit = pit + 1;
187 //because of parindents
188 if (!pars_[pit].getDepth())
189 return boost::next(nextpit);
190 //because of depth constrains
191 for (; nextpit != end; ++pit, ++nextpit) {
192 if (!pars_[pit].getDepth())
199 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
200 docstring const & layout)
202 BOOST_ASSERT(start != end);
204 BufferParams const & bufparams = buffer.params();
205 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
207 for (pit_type pit = start; pit != end; ++pit) {
208 Paragraph & par = pars_[pit];
209 par.applyLayout(lyxlayout);
210 if (lyxlayout->margintype == MARGIN_MANUAL)
211 par.setLabelWidthString(par.translateIfPossible(
212 lyxlayout->labelstring(), buffer.params()));
217 // set layout over selection and make a total rebreak of those paragraphs
218 void Text::setLayout(Cursor & cur, docstring const & layout)
220 BOOST_ASSERT(this == cur.text());
221 // special handling of new environment insets
222 BufferView & bv = cur.bv();
223 BufferParams const & params = bv.buffer().params();
224 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
225 if (lyxlayout->is_environment) {
226 // move everything in a new environment inset
227 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
228 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
229 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
230 lyx::dispatch(FuncRequest(LFUN_CUT));
231 Inset * inset = new InsetEnvironment(params, layout);
232 insertInset(cur, inset);
233 //inset->edit(cur, true);
234 //lyx::dispatch(FuncRequest(LFUN_PASTE));
238 pit_type start = cur.selBegin().pit();
239 pit_type end = cur.selEnd().pit() + 1;
240 pit_type undopit = undoSpan(end - 1);
241 recUndo(cur, start, undopit - 1);
242 setLayout(cur.buffer(), start, end, layout);
243 updateLabels(cur.buffer());
247 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
248 Paragraph const & par, int max_depth)
250 if (par.layout()->labeltype == LABEL_BIBLIO)
252 int const depth = par.params().depth();
253 if (type == Text::INC_DEPTH && depth < max_depth)
255 if (type == Text::DEC_DEPTH && depth > 0)
261 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
263 BOOST_ASSERT(this == cur.text());
264 // this happens when selecting several cells in tabular (bug 2630)
265 if (cur.selBegin().idx() != cur.selEnd().idx())
268 pit_type const beg = cur.selBegin().pit();
269 pit_type const end = cur.selEnd().pit() + 1;
270 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
272 for (pit_type pit = beg; pit != end; ++pit) {
273 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
275 max_depth = pars_[pit].getMaxDepthAfter();
281 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
283 BOOST_ASSERT(this == cur.text());
284 pit_type const beg = cur.selBegin().pit();
285 pit_type const end = cur.selEnd().pit() + 1;
286 cur.recordUndoSelection();
287 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
289 for (pit_type pit = beg; pit != end; ++pit) {
290 Paragraph & par = pars_[pit];
291 if (lyx::changeDepthAllowed(type, par, max_depth)) {
292 int const depth = par.params().depth();
293 if (type == INC_DEPTH)
294 par.params().depth(depth + 1);
296 par.params().depth(depth - 1);
298 max_depth = par.getMaxDepthAfter();
300 // this handles the counter labels, and also fixes up
301 // depth values for follow-on (child) paragraphs
302 updateLabels(cur.buffer());
306 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
308 BOOST_ASSERT(this == cur.text());
309 // Set the current_font
310 // Determine basis font
312 pit_type pit = cur.pit();
313 if (cur.pos() < pars_[pit].beginOfBody())
314 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
316 layoutfont = getLayoutFont(cur.buffer(), pit);
318 // Update current font
319 cur.real_current_font.update(font,
320 cur.buffer().params().language,
323 // Reduce to implicit settings
324 cur.current_font = cur.real_current_font;
325 cur.current_font.fontInfo().reduce(layoutfont);
326 // And resolve it completely
327 cur.real_current_font.fontInfo().realize(layoutfont);
329 // if there is no selection that's all we need to do
330 if (!cur.selection())
333 // Ok, we have a selection.
334 cur.recordUndoSelection();
336 setFont(cur.bv(), cur.selectionBegin().top(),
337 cur.selectionEnd().top(), font, toggleall);
341 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
342 CursorSlice const & end, Font const & font,
345 Buffer const & buffer = bv.buffer();
347 // Don't use forwardChar here as ditend might have
348 // pos() == lastpos() and forwardChar would miss it.
349 // Can't use forwardPos either as this descends into
351 Language const * language = buffer.params().language;
352 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
353 if (dit.pos() != dit.lastpos()) {
354 pit_type const pit = dit.pit();
355 pos_type const pos = dit.pos();
356 Inset * inset = pars_[pit].getInset(pos);
357 if (inset && inset->noFontChange()) {
358 // We need to propagate the font change to all
359 // text cells of the inset (bug 1973).
360 // FIXME: This should change, see documentation
361 // of noFontChange in Inset.h
362 setInsetFont(bv, pit, pos, font, toggleall);
364 TextMetrics const & tm = bv.textMetrics(this);
365 Font f = tm.getDisplayFont(pit, pos);
366 f.update(font, language, toggleall);
367 setCharFont(buffer, pit, pos, f, tm.font_);
373 bool Text::cursorTop(Cursor & cur)
375 BOOST_ASSERT(this == cur.text());
376 return setCursor(cur, 0, 0);
380 bool Text::cursorBottom(Cursor & cur)
382 BOOST_ASSERT(this == cur.text());
383 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
387 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
389 BOOST_ASSERT(this == cur.text());
390 // If the mask is completely neutral, tell user
391 if (font.fontInfo() == ignore_font &&
392 (font.language() == 0 || font.language() == ignore_language)) {
393 // Could only happen with user style
394 cur.message(_("No font change defined."));
398 // Try implicit word selection
399 // If there is a change in the language the implicit word selection
401 CursorSlice resetCursor = cur.top();
402 bool implicitSelection =
403 font.language() == ignore_language
404 && font.fontInfo().number() == FONT_IGNORE
405 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
408 setFont(cur, font, toggleall);
410 // Implicit selections are cleared afterwards
411 // and cursor is set to the original position.
412 if (implicitSelection) {
413 cur.clearSelection();
414 cur.top() = resetCursor;
420 docstring Text::getStringToIndex(Cursor const & cur)
422 BOOST_ASSERT(this == cur.text());
426 idxstring = cur.selectionAsString(false);
428 // Try implicit word selection. If there is a change
429 // in the language the implicit word selection is
432 selectWord(tmpcur, PREVIOUS_WORD);
434 if (!tmpcur.selection())
435 cur.message(_("Nothing to index!"));
436 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
437 cur.message(_("Cannot index more than one paragraph!"));
439 idxstring = tmpcur.selectionAsString(false);
446 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
448 BOOST_ASSERT(cur.text());
449 // make sure that the depth behind the selection are restored, too
450 pit_type undopit = undoSpan(cur.selEnd().pit());
451 recUndo(cur, cur.selBegin().pit(), undopit - 1);
453 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
455 Paragraph & par = pars_[pit];
456 ParagraphParameters params = par.params();
457 params.read(to_utf8(arg), merge);
458 Layout const & layout = *(par.layout());
459 par.params().apply(params, layout);
464 //FIXME This is a little redundant now, but it's probably worth keeping,
465 //especially if we're going to go away from using serialization internally
467 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
469 BOOST_ASSERT(cur.text());
470 // make sure that the depth behind the selection are restored, too
471 pit_type undopit = undoSpan(cur.selEnd().pit());
472 recUndo(cur, cur.selBegin().pit(), undopit - 1);
474 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
476 Paragraph & par = pars_[pit];
477 Layout const & layout = *(par.layout());
478 par.params().apply(p, layout);
483 // this really should just insert the inset and not move the cursor.
484 void Text::insertInset(Cursor & cur, Inset * inset)
486 BOOST_ASSERT(this == cur.text());
488 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
489 Change(cur.buffer().params().trackChanges ?
490 Change::INSERTED : Change::UNCHANGED));
494 // needed to insert the selection
495 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
497 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
498 cur.current_font, str, autoBreakRows_);
502 // turn double CR to single CR, others are converted into one
503 // blank. Then insertStringAsLines is called
504 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
506 docstring linestr = str;
507 bool newline_inserted = false;
509 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
510 if (linestr[i] == '\n') {
511 if (newline_inserted) {
512 // we know that \r will be ignored by
513 // insertStringAsLines. Of course, it is a dirty
514 // trick, but it works...
515 linestr[i - 1] = '\r';
519 newline_inserted = true;
521 } else if (isPrintable(linestr[i])) {
522 newline_inserted = false;
525 insertStringAsLines(cur, linestr);
529 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
530 bool setfont, bool boundary)
533 setCursorIntern(cur, par, pos, setfont, boundary);
534 return cur.bv().checkDepm(cur, old);
538 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
540 BOOST_ASSERT(par != int(paragraphs().size()));
544 // now some strict checking
545 Paragraph & para = getPar(par);
547 // None of these should happen, but we're scaredy-cats
549 lyxerr << "dont like -1" << endl;
553 if (pos > para.size()) {
554 lyxerr << "dont like 1, pos: " << pos
555 << " size: " << para.size()
556 << " par: " << par << endl;
562 void Text::setCursorIntern(Cursor & cur,
563 pit_type par, pos_type pos, bool setfont, bool boundary)
565 BOOST_ASSERT(this == cur.text());
566 cur.boundary(boundary);
567 setCursor(cur.top(), par, pos);
569 cur.setCurrentFont();
573 bool Text::checkAndActivateInset(Cursor & cur, bool front)
577 if (front && cur.pos() == cur.lastpos())
579 if (!front && cur.pos() == 0)
581 Inset * inset = front ? cur.nextInset() : cur.prevInset();
582 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
585 * Apparently, when entering an inset we are expected to be positioned
586 * *before* it in the containing paragraph, regardless of the direction
587 * from which we are entering. Otherwise, cursor placement goes awry,
588 * and when we exit from the beginning, we'll be placed *after* the
593 inset->edit(cur, front);
598 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
604 if (cur.pos() == cur.lastpos())
606 Paragraph & par = cur.paragraph();
607 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
608 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
610 inset->edit(cur, movingForward,
611 movingLeft ? Inset::ENTER_FROM_RIGHT : Inset::ENTER_FROM_LEFT);
616 bool Text::cursorBackward(Cursor & cur)
618 // Tell BufferView to test for FitCursor in any case!
619 cur.updateFlags(Update::FitCursor);
621 // not at paragraph start?
623 // if on right side of boundary (i.e. not at paragraph end, but line end)
624 // -> skip it, i.e. set boundary to true, i.e. go only logically left
625 // there are some exceptions to ignore this: lineseps, newlines, spaces
627 // some effectless debug code to see the values in the debugger
628 bool bound = cur.boundary();
629 int rowpos = cur.textRow().pos();
631 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
632 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
633 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
635 if (!cur.boundary() &&
636 cur.textRow().pos() == cur.pos() &&
637 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
638 !cur.paragraph().isNewline(cur.pos() - 1) &&
639 !cur.paragraph().isSeparator(cur.pos() - 1)) {
640 return setCursor(cur, cur.pit(), cur.pos(), true, true);
643 // go left and try to enter inset
644 if (checkAndActivateInset(cur, false))
647 // normal character left
648 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
651 // move to the previous paragraph or do nothing
653 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
658 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
660 pit_type new_pit = cur.pit(); // the paragraph to which we will move
661 pos_type new_pos; // the position we will move to
662 bool new_boundary; // will we move to a boundary position?
663 pos_type left_pos; // position visually left of current cursor
664 pos_type right_pos; // position visually right of current cursor
665 bool new_pos_is_RTL; // is new position we're moving to RTL?
667 cur.getSurroundingPos(left_pos, right_pos);
669 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
671 // Are we at an inset?
672 Cursor temp_cur = cur;
673 temp_cur.pos() = left_pos;
674 temp_cur.boundary(false);
676 checkAndActivateInsetVisual(temp_cur, left_pos >= cur.pos(), true)) {
677 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
678 cur = temp_cur; // set the real cursor to new position inside inset!
682 // Are we already at leftmost pos in row?
683 if (left_pos == -1) {
685 Cursor new_cur = cur;
686 if (!new_cur.posVisToNewRow(true)) {
687 LYXERR(Debug::RTL, "not moving!");
691 // we actually move the cursor at the end of this function, for now
692 // just keep track of the new position...
693 new_pit = new_cur.pit();
694 new_pos = new_cur.pos();
695 new_boundary = new_cur.boundary();
697 LYXERR(Debug::RTL, "left edge, moving: " << int(new_pit) << ","
698 << int(new_pos) << "," << (new_boundary ? 1 : 0));
701 // normal movement to the left
703 // Recall, if the cursor is at position 'x', that means *before*
704 // the character at position 'x'. In RTL, "before" means "to the
705 // right of", in LTR, "to the left of". So currently our situation
706 // is this: the position to our left is 'left_pos' (i.e., we're
707 // currently to the right of 'left_pos'). In order to move to the
708 // left, it depends whether or not the character at 'left_pos' is RTL.
709 new_pos_is_RTL = cur.paragraph().getFontSettings(
710 cur.bv().buffer().params(), left_pos).isVisibleRightToLeft();
711 // If the character at 'left_pos' *is* RTL, then in order to move to
712 // the left of it, we need to be *after* 'left_pos', i.e., move to
713 // position 'left_pos' + 1.
714 if (new_pos_is_RTL) {
715 new_pos = left_pos + 1;
716 // if the position *after* left_pos is not RTL, set boundary to
717 // true (we want to be *after* left_pos, not before left_pos + 1!)
718 new_boundary = !cur.paragraph().getFontSettings(
719 cur.bv().buffer().params(), new_pos).isVisibleRightToLeft();
721 // Otherwise (if the character at position 'left_pos' is LTR), then
722 // moving to the left of it is as easy as setting the new position
726 new_boundary = false;
731 LYXERR(Debug::RTL, "moving to: " << new_pos
732 << (new_boundary ? " (boundary)" : ""));
734 return setCursor(cur, new_pit, new_pos, true, new_boundary);
738 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
740 pit_type new_pit = cur.pit(); // the paragraph to which we will move
741 pos_type new_pos; // the position we will move to
742 bool new_boundary; // will we move to a boundary position?
743 pos_type left_pos; // position visually left of current cursor
744 pos_type right_pos; // position visually right of current cursor
745 bool new_pos_is_RTL; // is new position we're moving to RTL?
747 cur.getSurroundingPos(left_pos, right_pos);
749 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
751 // Are we at an inset?
752 Cursor temp_cur = cur;
753 temp_cur.pos() = right_pos;
754 temp_cur.boundary(false);
756 checkAndActivateInsetVisual(temp_cur, right_pos >= cur.pos(), false)) {
757 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
758 cur = temp_cur; // set the real cursor to new position inside inset!
762 // Are we already at rightmost pos in row?
763 if (right_pos == -1) {
765 Cursor new_cur = cur;
766 if (!new_cur.posVisToNewRow(false)) {
767 LYXERR(Debug::RTL, "not moving!");
771 // we actually move the cursor at the end of this function, for now
772 // just keep track of the new position...
773 new_pit = new_cur.pit();
774 new_pos = new_cur.pos();
775 new_boundary = new_cur.boundary();
777 LYXERR(Debug::RTL, "right edge, moving: " << int(new_pit) << ","
778 << int(new_pos) << "," << (new_boundary ? 1 : 0));
781 // normal movement to the right
783 // Recall, if the cursor is at position 'x', that means *before*
784 // the character at position 'x'. In RTL, "before" means "to the
785 // right of", in LTR, "to the left of". So currently our situation
786 // is this: the position to our right is 'right_pos' (i.e., we're
787 // currently to the left of 'right_pos'). In order to move to the
788 // right, it depends whether or not the character at 'right_pos' is RTL.
789 new_pos_is_RTL = cur.paragraph().getFontSettings(
790 cur.bv().buffer().params(), right_pos).isVisibleRightToLeft();
791 // If the character at 'right_pos' *is* LTR, then in order to move to
792 // the right of it, we need to be *after* 'right_pos', i.e., move to
793 // position 'right_pos' + 1.
794 if (!new_pos_is_RTL) {
795 new_pos = right_pos + 1;
796 // if the position *after* right_pos is RTL, set boundary to
797 // true (we want to be *after* right_pos, not before right_pos + 1!)
798 new_boundary = cur.paragraph().getFontSettings(
799 cur.bv().buffer().params(), new_pos).isVisibleRightToLeft();
801 // Otherwise (if the character at position 'right_pos' is RTL), then
802 // moving to the right of it is as easy as setting the new position
806 new_boundary = false;
811 LYXERR(Debug::RTL, "moving to: " << new_pos
812 << (new_boundary ? " (boundary)" : ""));
814 return setCursor(cur, new_pit, new_pos, true, new_boundary);
818 bool Text::cursorForward(Cursor & cur)
820 // Tell BufferView to test for FitCursor in any case!
821 cur.updateFlags(Update::FitCursor);
823 // not at paragraph end?
824 if (cur.pos() != cur.lastpos()) {
825 // in front of editable inset, i.e. jump into it?
826 if (checkAndActivateInset(cur, true))
829 TextMetrics const & tm = cur.bv().textMetrics(this);
830 // if left of boundary -> just jump to right side
831 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
832 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
833 return setCursor(cur, cur.pit(), cur.pos(), true, false);
835 // next position is left of boundary,
836 // but go to next line for special cases like space, newline, linesep
838 // some effectless debug code to see the values in the debugger
839 int endpos = cur.textRow().endpos();
840 int lastpos = cur.lastpos();
842 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
843 bool newline = cur.paragraph().isNewline(cur.pos());
844 bool sep = cur.paragraph().isSeparator(cur.pos());
845 if (cur.pos() != cur.lastpos()) {
846 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
847 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
848 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
851 if (cur.textRow().endpos() == cur.pos() + 1 &&
852 cur.textRow().endpos() != cur.lastpos() &&
853 !cur.paragraph().isNewline(cur.pos()) &&
854 !cur.paragraph().isLineSeparator(cur.pos()) &&
855 !cur.paragraph().isSeparator(cur.pos())) {
856 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
859 // in front of RTL boundary? Stay on this side of the boundary because:
860 // ab|cDDEEFFghi -> abc|DDEEFFghi
861 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
862 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
865 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
868 // move to next paragraph
869 if (cur.pit() != cur.lastpit())
870 return setCursor(cur, cur.pit() + 1, 0, true, false);
875 bool Text::cursorUpParagraph(Cursor & cur)
877 bool updated = false;
879 updated = setCursor(cur, cur.pit(), 0);
880 else if (cur.pit() != 0)
881 updated = setCursor(cur, cur.pit() - 1, 0);
886 bool Text::cursorDownParagraph(Cursor & cur)
888 bool updated = false;
889 if (cur.pit() != cur.lastpit())
890 updated = setCursor(cur, cur.pit() + 1, 0);
892 updated = setCursor(cur, cur.pit(), cur.lastpos());
897 // fix the cursor `cur' after a characters has been deleted at `where'
898 // position. Called by deleteEmptyParagraphMechanism
899 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
901 // Do nothing if cursor is not in the paragraph where the
903 if (cur.pit() != where.pit())
906 // If cursor position is after the deletion place update it
907 if (cur.pos() > where.pos())
910 // Check also if we don't want to set the cursor on a spot behind the
911 // pagragraph because we erased the last character.
912 if (cur.pos() > cur.lastpos())
913 cur.pos() = cur.lastpos();
917 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
918 Cursor & old, bool & need_anchor_change)
920 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
922 Paragraph & oldpar = old.paragraph();
924 // We allow all kinds of "mumbo-jumbo" when freespacing.
925 if (oldpar.isFreeSpacing())
928 /* Ok I'll put some comments here about what is missing.
929 There are still some small problems that can lead to
930 double spaces stored in the document file or space at
931 the beginning of paragraphs(). This happens if you have
932 the cursor between to spaces and then save. Or if you
933 cut and paste and the selection have a space at the
934 beginning and then save right after the paste. (Lgb)
937 // If old.pos() == 0 and old.pos()(1) == LineSeparator
938 // delete the LineSeparator.
941 // If old.pos() == 1 and old.pos()(0) == LineSeparator
942 // delete the LineSeparator.
945 bool const same_inset = &old.inset() == &cur.inset();
946 bool const same_par = same_inset && old.pit() == cur.pit();
947 bool const same_par_pos = same_par && old.pos() == cur.pos();
949 // If the chars around the old cursor were spaces, delete one of them.
951 // Only if the cursor has really moved.
953 && old.pos() < oldpar.size()
954 && oldpar.isLineSeparator(old.pos())
955 && oldpar.isLineSeparator(old.pos() - 1)
956 && !oldpar.isDeleted(old.pos() - 1)
957 && !oldpar.isDeleted(old.pos())) {
958 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
959 // FIXME: This will not work anymore when we have multiple views of the same buffer
960 // In this case, we will have to correct also the cursors held by
961 // other bufferviews. It will probably be easier to do that in a more
962 // automated way in CursorSlice code. (JMarc 26/09/2001)
963 // correct all cursor parts
965 fixCursorAfterDelete(cur.top(), old.top());
966 need_anchor_change = true;
972 // only do our magic if we changed paragraph
976 // don't delete anything if this is the ONLY paragraph!
977 if (old.lastpit() == 0)
980 // Do not delete empty paragraphs with keepempty set.
981 if (oldpar.allowEmpty())
984 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
986 old.recordUndo(ATOMIC_UNDO,
987 max(old.pit() - 1, pit_type(0)),
988 min(old.pit() + 1, old.lastpit()));
989 ParagraphList & plist = old.text()->paragraphs();
990 bool const soa = oldpar.params().startOfAppendix();
991 plist.erase(boost::next(plist.begin(), old.pit()));
992 // do not lose start of appendix marker (bug 4212)
993 if (soa && old.pit() < pit_type(plist.size()))
994 plist[old.pit()].params().startOfAppendix(true);
996 // see #warning (FIXME?) above
997 if (cur.depth() >= old.depth()) {
998 CursorSlice & curslice = cur[old.depth() - 1];
999 if (&curslice.inset() == &old.inset()
1000 && curslice.pit() > old.pit()) {
1002 // since a paragraph has been deleted, all the
1003 // insets after `old' have been copied and
1004 // their address has changed. Therefore we
1005 // need to `regenerate' cur. (JMarc)
1006 cur.updateInsets(&(cur.bottom().inset()));
1007 need_anchor_change = true;
1013 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1014 need_anchor_change = true;
1015 // We return true here because the Paragraph contents changed and
1016 // we need a redraw before further action is processed.
1024 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1026 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1028 for (pit_type pit = first; pit <= last; ++pit) {
1029 Paragraph & par = pars_[pit];
1031 // We allow all kinds of "mumbo-jumbo" when freespacing.
1032 if (par.isFreeSpacing())
1035 for (pos_type pos = 1; pos < par.size(); ++pos) {
1036 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1037 && !par.isDeleted(pos - 1)) {
1038 if (par.eraseChar(pos - 1, trackChanges)) {
1044 // don't delete anything if this is the only remaining paragraph within the given range
1045 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1049 // don't delete empty paragraphs with keepempty set
1050 if (par.allowEmpty())
1053 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1054 pars_.erase(boost::next(pars_.begin(), pit));
1060 par.stripLeadingSpaces(trackChanges);
1065 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1067 cur.recordUndo(ATOMIC_UNDO, first, last);
1071 void Text::recUndo(Cursor & cur, pit_type par) const
1073 cur.recordUndo(ATOMIC_UNDO, par, par);