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"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "FuncRequest.h"
42 #include "Paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "TextClass.h"
46 #include "TextMetrics.h"
49 #include "insets/InsetEnvironment.h"
51 #include "mathed/InsetMathHull.h"
53 #include "support/debug.h"
54 #include "support/gettext.h"
55 #include "support/textutils.h"
57 #include <boost/next_prior.hpp>
66 : autoBreakRows_(false)
70 bool Text::isMainText(Buffer const & buffer) const
72 return &buffer.text() == this;
76 FontInfo Text::layoutFont(Buffer const & buffer, pit_type const pit) const
78 Layout const & layout = pars_[pit].layout();
80 if (!pars_[pit].getDepth()) {
81 FontInfo lf = layout.resfont;
82 // In case the default family has been customized
83 if (layout.font.family() == INHERIT_FAMILY)
84 lf.setFamily(buffer.params().getFont().fontInfo().family());
88 FontInfo font = layout.font;
89 // Realize with the fonts of lesser depth.
90 //font.realize(outerFont(pit, paragraphs()));
91 font.realize(buffer.params().getFont().fontInfo());
97 FontInfo Text::labelFont(Buffer const & buffer, Paragraph const & par) const
99 Layout const & layout = par.layout();
101 if (!par.getDepth()) {
102 FontInfo lf = layout.reslabelfont;
103 // In case the default family has been customized
104 if (layout.labelfont.family() == INHERIT_FAMILY)
105 lf.setFamily(buffer.params().getFont().fontInfo().family());
109 FontInfo font = layout.labelfont;
110 // Realize with the fonts of lesser depth.
111 font.realize(buffer.params().getFont().fontInfo());
117 void Text::setCharFont(Buffer const & buffer, pit_type pit,
118 pos_type pos, Font const & fnt, Font const & display_font)
121 Layout const & layout = pars_[pit].layout();
123 // Get concrete layout font to reduce against
126 if (pos < pars_[pit].beginOfBody())
127 layoutfont = layout.labelfont;
129 layoutfont = layout.font;
131 // Realize against environment font information
132 if (pars_[pit].getDepth()) {
134 while (!layoutfont.resolved() &&
135 tp != pit_type(paragraphs().size()) &&
136 pars_[tp].getDepth()) {
137 tp = outerHook(tp, paragraphs());
138 if (tp != pit_type(paragraphs().size()))
139 layoutfont.realize(pars_[tp].layout().font);
143 // Inside inset, apply the inset's font attributes if any
145 if (!isMainText(buffer))
146 layoutfont.realize(display_font.fontInfo());
148 layoutfont.realize(buffer.params().getFont().fontInfo());
150 // Now, reduce font against full layout font
151 font.fontInfo().reduce(layoutfont);
153 pars_[pit].setFont(pos, font);
157 void Text::setInsetFont(BufferView const & bv, pit_type pit,
158 pos_type pos, Font const & font, bool toggleall)
160 Inset * const inset = pars_[pit].getInset(pos);
161 BOOST_ASSERT(inset && inset->noFontChange());
163 CursorSlice::idx_type endidx = inset->nargs();
164 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
165 Text * text = cs.text();
167 // last position of the cell
168 CursorSlice cellend = cs;
169 cellend.pit() = cellend.lastpit();
170 cellend.pos() = cellend.lastpos();
171 text->setFont(bv, cs, cellend, font, toggleall);
177 // return past-the-last paragraph influenced by a layout change on pit
178 pit_type Text::undoSpan(pit_type pit)
180 pit_type end = paragraphs().size();
181 pit_type nextpit = pit + 1;
184 //because of parindents
185 if (!pars_[pit].getDepth())
186 return boost::next(nextpit);
187 //because of depth constrains
188 for (; nextpit != end; ++pit, ++nextpit) {
189 if (!pars_[pit].getDepth())
196 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
197 docstring const & layout)
199 BOOST_ASSERT(start != end);
201 BufferParams const & bufparams = buffer.params();
202 Layout const & lyxlayout = bufparams.documentClass()[layout];
204 for (pit_type pit = start; pit != end; ++pit) {
205 Paragraph & par = pars_[pit];
206 par.applyLayout(lyxlayout);
207 if (lyxlayout.margintype == MARGIN_MANUAL)
208 par.setLabelWidthString(par.translateIfPossible(
209 lyxlayout.labelstring(), buffer.params()));
214 // set layout over selection and make a total rebreak of those paragraphs
215 void Text::setLayout(Cursor & cur, docstring const & layout)
217 BOOST_ASSERT(this == cur.text());
218 // special handling of new environment insets
219 BufferView & bv = cur.bv();
220 BufferParams const & params = bv.buffer().params();
221 Layout const & lyxlayout = params.documentClass()[layout];
222 if (lyxlayout.is_environment) {
223 // move everything in a new environment inset
224 LYXERR(Debug::DEBUG, "setting layout " << to_utf8(layout));
225 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
226 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
227 lyx::dispatch(FuncRequest(LFUN_CUT));
228 Inset * inset = new InsetEnvironment(bv.buffer(), layout);
229 insertInset(cur, inset);
230 //inset->edit(cur, true);
231 //lyx::dispatch(FuncRequest(LFUN_PASTE));
235 pit_type start = cur.selBegin().pit();
236 pit_type end = cur.selEnd().pit() + 1;
237 pit_type undopit = undoSpan(end - 1);
238 recUndo(cur, start, undopit - 1);
239 setLayout(cur.buffer(), start, end, layout);
240 updateLabels(cur.buffer());
244 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
245 Paragraph const & par, int max_depth)
247 if (par.layout().labeltype == LABEL_BIBLIO)
249 int const depth = par.params().depth();
250 if (type == Text::INC_DEPTH && depth < max_depth)
252 if (type == Text::DEC_DEPTH && depth > 0)
258 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
260 BOOST_ASSERT(this == cur.text());
261 // this happens when selecting several cells in tabular (bug 2630)
262 if (cur.selBegin().idx() != cur.selEnd().idx())
265 pit_type const beg = cur.selBegin().pit();
266 pit_type const end = cur.selEnd().pit() + 1;
267 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
269 for (pit_type pit = beg; pit != end; ++pit) {
270 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
272 max_depth = pars_[pit].getMaxDepthAfter();
278 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
280 BOOST_ASSERT(this == cur.text());
281 pit_type const beg = cur.selBegin().pit();
282 pit_type const end = cur.selEnd().pit() + 1;
283 cur.recordUndoSelection();
284 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
286 for (pit_type pit = beg; pit != end; ++pit) {
287 Paragraph & par = pars_[pit];
288 if (lyx::changeDepthAllowed(type, par, max_depth)) {
289 int const depth = par.params().depth();
290 if (type == INC_DEPTH)
291 par.params().depth(depth + 1);
293 par.params().depth(depth - 1);
295 max_depth = par.getMaxDepthAfter();
297 // this handles the counter labels, and also fixes up
298 // depth values for follow-on (child) paragraphs
299 updateLabels(cur.buffer());
303 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
305 BOOST_ASSERT(this == cur.text());
306 // Set the current_font
307 // Determine basis font
309 pit_type pit = cur.pit();
310 if (cur.pos() < pars_[pit].beginOfBody())
311 layoutfont = labelFont(cur.buffer(), pars_[pit]);
313 layoutfont = layoutFont(cur.buffer(), pit);
315 // Update current font
316 cur.real_current_font.update(font,
317 cur.buffer().params().language,
320 // Reduce to implicit settings
321 cur.current_font = cur.real_current_font;
322 cur.current_font.fontInfo().reduce(layoutfont);
323 // And resolve it completely
324 cur.real_current_font.fontInfo().realize(layoutfont);
326 // if there is no selection that's all we need to do
327 if (!cur.selection())
330 // Ok, we have a selection.
331 cur.recordUndoSelection();
333 setFont(cur.bv(), cur.selectionBegin().top(),
334 cur.selectionEnd().top(), font, toggleall);
338 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
339 CursorSlice const & end, Font const & font,
342 Buffer const & buffer = bv.buffer();
344 // Don't use forwardChar here as ditend might have
345 // pos() == lastpos() and forwardChar would miss it.
346 // Can't use forwardPos either as this descends into
348 Language const * language = buffer.params().language;
349 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
350 if (dit.pos() == dit.lastpos())
352 pit_type const pit = dit.pit();
353 pos_type const pos = dit.pos();
354 Inset * inset = pars_[pit].getInset(pos);
355 if (inset && inset->noFontChange()) {
356 // We need to propagate the font change to all
357 // text cells of the inset (bug 1973).
358 // FIXME: This should change, see documentation
359 // of noFontChange in Inset.h
360 setInsetFont(bv, pit, pos, font, toggleall);
362 TextMetrics const & tm = bv.textMetrics(this);
363 Font f = tm.displayFont(pit, pos);
364 f.update(font, language, toggleall);
365 setCharFont(buffer, pit, pos, f, tm.font_);
370 bool Text::cursorTop(Cursor & cur)
372 BOOST_ASSERT(this == cur.text());
373 return setCursor(cur, 0, 0);
377 bool Text::cursorBottom(Cursor & cur)
379 BOOST_ASSERT(this == cur.text());
380 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
384 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
386 BOOST_ASSERT(this == cur.text());
387 // If the mask is completely neutral, tell user
388 if (font.fontInfo() == ignore_font &&
389 (font.language() == 0 || font.language() == ignore_language)) {
390 // Could only happen with user style
391 cur.message(_("No font change defined."));
395 // Try implicit word selection
396 // If there is a change in the language the implicit word selection
398 CursorSlice resetCursor = cur.top();
399 bool implicitSelection =
400 font.language() == ignore_language
401 && font.fontInfo().number() == FONT_IGNORE
402 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
405 setFont(cur, font, toggleall);
407 // Implicit selections are cleared afterwards
408 // and cursor is set to the original position.
409 if (implicitSelection) {
410 cur.clearSelection();
411 cur.top() = resetCursor;
417 docstring Text::getStringToIndex(Cursor const & cur)
419 BOOST_ASSERT(this == cur.text());
422 return cur.selectionAsString(false);
424 // Try implicit word selection. If there is a change
425 // in the language the implicit word selection is
428 selectWord(tmpcur, PREVIOUS_WORD);
430 if (!tmpcur.selection())
431 cur.message(_("Nothing to index!"));
432 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
433 cur.message(_("Cannot index more than one paragraph!"));
435 return tmpcur.selectionAsString(false);
441 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
443 BOOST_ASSERT(cur.text());
444 // make sure that the depth behind the selection are restored, too
445 pit_type undopit = undoSpan(cur.selEnd().pit());
446 recUndo(cur, cur.selBegin().pit(), undopit - 1);
449 string const argument = to_utf8(arg);
450 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
452 Paragraph & par = pars_[pit];
453 ParagraphParameters params = par.params();
454 params.read(argument, merge);
455 par.params().apply(params, par.layout());
460 //FIXME This is a little redundant now, but it's probably worth keeping,
461 //especially if we're going to go away from using serialization internally
463 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
465 BOOST_ASSERT(cur.text());
466 // make sure that the depth behind the selection are restored, too
467 pit_type undopit = undoSpan(cur.selEnd().pit());
468 recUndo(cur, cur.selBegin().pit(), undopit - 1);
470 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
472 Paragraph & par = pars_[pit];
473 par.params().apply(p, par.layout());
478 // this really should just insert the inset and not move the cursor.
479 void Text::insertInset(Cursor & cur, Inset * inset)
481 BOOST_ASSERT(this == cur.text());
483 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
484 Change(cur.buffer().params().trackChanges
485 ? Change::INSERTED : Change::UNCHANGED));
489 // needed to insert the selection
490 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
492 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
493 cur.current_font, str, autoBreakRows_);
497 // turn double CR to single CR, others are converted into one
498 // blank. Then insertStringAsLines is called
499 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
501 docstring linestr = str;
502 bool newline_inserted = false;
504 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
505 if (linestr[i] == '\n') {
506 if (newline_inserted) {
507 // we know that \r will be ignored by
508 // insertStringAsLines. Of course, it is a dirty
509 // trick, but it works...
510 linestr[i - 1] = '\r';
514 newline_inserted = true;
516 } else if (isPrintable(linestr[i])) {
517 newline_inserted = false;
520 insertStringAsLines(cur, linestr);
524 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
525 bool setfont, bool boundary)
527 TextMetrics const & tm = cur.bv().textMetrics(this);
528 bool const update_needed = !tm.contains(par);
530 setCursorIntern(cur, par, pos, setfont, boundary);
531 return cur.bv().checkDepm(cur, old) || update_needed;
535 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
537 BOOST_ASSERT(par != int(paragraphs().size()));
541 // now some strict checking
542 Paragraph & para = getPar(par);
544 // None of these should happen, but we're scaredy-cats
546 lyxerr << "dont like -1" << endl;
550 if (pos > para.size()) {
551 lyxerr << "dont like 1, pos: " << pos
552 << " size: " << para.size()
553 << " par: " << par << endl;
559 void Text::setCursorIntern(Cursor & cur,
560 pit_type par, pos_type pos, bool setfont, bool boundary)
562 BOOST_ASSERT(this == cur.text());
563 cur.boundary(boundary);
564 setCursor(cur.top(), par, pos);
566 cur.setCurrentFont();
570 bool Text::checkAndActivateInset(Cursor & cur, bool front)
574 if (front && cur.pos() == cur.lastpos())
576 if (!front && cur.pos() == 0)
578 Inset * inset = front ? cur.nextInset() : cur.prevInset();
579 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
582 * Apparently, when entering an inset we are expected to be positioned
583 * *before* it in the containing paragraph, regardless of the direction
584 * from which we are entering. Otherwise, cursor placement goes awry,
585 * and when we exit from the beginning, we'll be placed *after* the
590 inset->edit(cur, front);
595 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
601 if (cur.pos() == cur.lastpos())
603 Paragraph & par = cur.paragraph();
604 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
605 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
607 inset->edit(cur, movingForward,
608 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
613 bool Text::cursorBackward(Cursor & cur)
615 // Tell BufferView to test for FitCursor in any case!
616 cur.updateFlags(Update::FitCursor);
618 // not at paragraph start?
620 // if on right side of boundary (i.e. not at paragraph end, but line end)
621 // -> skip it, i.e. set boundary to true, i.e. go only logically left
622 // there are some exceptions to ignore this: lineseps, newlines, spaces
624 // some effectless debug code to see the values in the debugger
625 bool bound = cur.boundary();
626 int rowpos = cur.textRow().pos();
628 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
629 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
630 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
632 if (!cur.boundary() &&
633 cur.textRow().pos() == cur.pos() &&
634 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
635 !cur.paragraph().isNewline(cur.pos() - 1) &&
636 !cur.paragraph().isSeparator(cur.pos() - 1)) {
637 return setCursor(cur, cur.pit(), cur.pos(), true, true);
640 // go left and try to enter inset
641 if (checkAndActivateInset(cur, false))
644 // normal character left
645 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
648 // move to the previous paragraph or do nothing
650 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
655 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
657 pit_type new_pit = cur.pit(); // the paragraph to which we will move
658 pos_type new_pos; // the position we will move to
659 bool new_boundary; // will we move to a boundary position?
660 pos_type left_pos; // position visually left of current cursor
661 pos_type right_pos; // position visually right of current cursor
662 bool new_pos_is_RTL; // is new position we're moving to RTL?
664 cur.getSurroundingPos(left_pos, right_pos);
666 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
668 // Are we at an inset?
669 Cursor temp_cur = cur;
670 temp_cur.pos() = left_pos;
671 temp_cur.boundary(false);
673 checkAndActivateInsetVisual(temp_cur, left_pos >= cur.pos(), true)) {
674 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
675 cur = temp_cur; // set the real cursor to new position inside inset!
679 // Are we already at leftmost pos in row?
680 if (left_pos == -1) {
682 Cursor new_cur = cur;
683 if (!new_cur.posVisToNewRow(true)) {
684 LYXERR(Debug::RTL, "not moving!");
688 // we actually move the cursor at the end of this function, for now
689 // just keep track of the new position...
690 new_pit = new_cur.pit();
691 new_pos = new_cur.pos();
692 new_boundary = new_cur.boundary();
694 LYXERR(Debug::RTL, "left edge, moving: " << int(new_pit) << ","
695 << int(new_pos) << "," << (new_boundary ? 1 : 0));
698 // normal movement to the left
700 // Recall, if the cursor is at position 'x', that means *before*
701 // the character at position 'x'. In RTL, "before" means "to the
702 // right of", in LTR, "to the left of". So currently our situation
703 // is this: the position to our left is 'left_pos' (i.e., we're
704 // currently to the right of 'left_pos'). In order to move to the
705 // left, it depends whether or not the character at 'left_pos' is RTL.
706 new_pos_is_RTL = cur.paragraph().getFontSettings(
707 cur.bv().buffer().params(), left_pos).isVisibleRightToLeft();
708 // If the character at 'left_pos' *is* RTL, then in order to move to
709 // the left of it, we need to be *after* 'left_pos', i.e., move to
710 // position 'left_pos' + 1.
711 if (new_pos_is_RTL) {
712 new_pos = left_pos + 1;
713 // set the boundary to true in two situations:
715 // 1. if new_pos is now lastpos (which means that we're moving left
716 // to the end of an RTL chunk which is at the end of an LTR
718 new_pos == cur.lastpos()
719 // 2. if the position *after* left_pos is not RTL (we want to be
720 // *after* left_pos, not before left_pos + 1!)
721 || !cur.paragraph().getFontSettings(cur.bv().buffer().params(),
722 new_pos).isVisibleRightToLeft()
725 else // set the boundary to false
726 new_boundary = false;
728 // Otherwise (if the character at position 'left_pos' is LTR), then
729 // moving to the left of it is as easy as setting the new position
733 new_boundary = false;
738 LYXERR(Debug::RTL, "moving to: " << new_pos
739 << (new_boundary ? " (boundary)" : ""));
741 return setCursor(cur, new_pit, new_pos, true, new_boundary);
745 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
747 pit_type new_pit = cur.pit(); // the paragraph to which we will move
748 pos_type new_pos; // the position we will move to
749 bool new_boundary; // will we move to a boundary position?
750 pos_type left_pos; // position visually left of current cursor
751 pos_type right_pos; // position visually right of current cursor
752 bool new_pos_is_RTL; // is new position we're moving to RTL?
754 cur.getSurroundingPos(left_pos, right_pos);
756 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
758 // Are we at an inset?
759 Cursor temp_cur = cur;
760 temp_cur.pos() = right_pos;
761 temp_cur.boundary(false);
763 checkAndActivateInsetVisual(temp_cur, right_pos >= cur.pos(), false)) {
764 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
765 cur = temp_cur; // set the real cursor to new position inside inset!
769 // Are we already at rightmost pos in row?
770 if (right_pos == -1) {
772 Cursor new_cur = cur;
773 if (!new_cur.posVisToNewRow(false)) {
774 LYXERR(Debug::RTL, "not moving!");
778 // we actually move the cursor at the end of this function, for now
779 // just keep track of the new position...
780 new_pit = new_cur.pit();
781 new_pos = new_cur.pos();
782 new_boundary = new_cur.boundary();
784 LYXERR(Debug::RTL, "right edge, moving: " << int(new_pit) << ","
785 << int(new_pos) << "," << (new_boundary ? 1 : 0));
788 // normal movement to the right
790 // Recall, if the cursor is at position 'x', that means *before*
791 // the character at position 'x'. In RTL, "before" means "to the
792 // right of", in LTR, "to the left of". So currently our situation
793 // is this: the position to our right is 'right_pos' (i.e., we're
794 // currently to the left of 'right_pos'). In order to move to the
795 // right, it depends whether or not the character at 'right_pos' is RTL.
796 new_pos_is_RTL = cur.paragraph().getFontSettings(
797 cur.bv().buffer().params(), right_pos).isVisibleRightToLeft();
798 // If the character at 'right_pos' *is* LTR, then in order to move to
799 // the right of it, we need to be *after* 'right_pos', i.e., move to
800 // position 'right_pos' + 1.
801 if (!new_pos_is_RTL) {
802 new_pos = right_pos + 1;
803 // set the boundary to true in two situations:
805 // 1. if new_pos is now lastpos (which means that we're moving
806 // right to the end of an LTR chunk which is at the end of an
808 new_pos == cur.lastpos()
809 // 2. if the position *after* right_pos is RTL (we want to be
810 // *after* right_pos, not before right_pos + 1!)
811 || cur.paragraph().getFontSettings(cur.bv().buffer().params(),
812 new_pos).isVisibleRightToLeft()
815 else // set the boundary to false
816 new_boundary = false;
818 // Otherwise (if the character at position 'right_pos' is RTL), then
819 // moving to the right of it is as easy as setting the new position
823 new_boundary = false;
828 LYXERR(Debug::RTL, "moving to: " << new_pos
829 << (new_boundary ? " (boundary)" : ""));
831 return setCursor(cur, new_pit, new_pos, true, new_boundary);
835 bool Text::cursorForward(Cursor & cur)
837 // Tell BufferView to test for FitCursor in any case!
838 cur.updateFlags(Update::FitCursor);
840 // not at paragraph end?
841 if (cur.pos() != cur.lastpos()) {
842 // in front of editable inset, i.e. jump into it?
843 if (checkAndActivateInset(cur, true))
846 TextMetrics const & tm = cur.bv().textMetrics(this);
847 // if left of boundary -> just jump to right side
848 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
849 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
850 return setCursor(cur, cur.pit(), cur.pos(), true, false);
852 // next position is left of boundary,
853 // but go to next line for special cases like space, newline, linesep
855 // some effectless debug code to see the values in the debugger
856 int endpos = cur.textRow().endpos();
857 int lastpos = cur.lastpos();
859 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
860 bool newline = cur.paragraph().isNewline(cur.pos());
861 bool sep = cur.paragraph().isSeparator(cur.pos());
862 if (cur.pos() != cur.lastpos()) {
863 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
864 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
865 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
868 if (cur.textRow().endpos() == cur.pos() + 1 &&
869 cur.textRow().endpos() != cur.lastpos() &&
870 !cur.paragraph().isNewline(cur.pos()) &&
871 !cur.paragraph().isLineSeparator(cur.pos()) &&
872 !cur.paragraph().isSeparator(cur.pos())) {
873 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
876 // in front of RTL boundary? Stay on this side of the boundary because:
877 // ab|cDDEEFFghi -> abc|DDEEFFghi
878 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
879 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
882 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
885 // move to next paragraph
886 if (cur.pit() != cur.lastpit())
887 return setCursor(cur, cur.pit() + 1, 0, true, false);
892 bool Text::cursorUpParagraph(Cursor & cur)
894 bool updated = false;
896 updated = setCursor(cur, cur.pit(), 0);
897 else if (cur.pit() != 0)
898 updated = setCursor(cur, cur.pit() - 1, 0);
903 bool Text::cursorDownParagraph(Cursor & cur)
905 bool updated = false;
906 if (cur.pit() != cur.lastpit())
907 updated = setCursor(cur, cur.pit() + 1, 0);
909 updated = setCursor(cur, cur.pit(), cur.lastpos());
914 // fix the cursor `cur' after a characters has been deleted at `where'
915 // position. Called by deleteEmptyParagraphMechanism
916 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
918 // Do nothing if cursor is not in the paragraph where the
920 if (cur.pit() != where.pit())
923 // If cursor position is after the deletion place update it
924 if (cur.pos() > where.pos())
927 // Check also if we don't want to set the cursor on a spot behind the
928 // pagragraph because we erased the last character.
929 if (cur.pos() > cur.lastpos())
930 cur.pos() = cur.lastpos();
934 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
935 Cursor & old, bool & need_anchor_change)
937 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
939 Paragraph & oldpar = old.paragraph();
941 // We allow all kinds of "mumbo-jumbo" when freespacing.
942 if (oldpar.isFreeSpacing())
945 /* Ok I'll put some comments here about what is missing.
946 There are still some small problems that can lead to
947 double spaces stored in the document file or space at
948 the beginning of paragraphs(). This happens if you have
949 the cursor between to spaces and then save. Or if you
950 cut and paste and the selection have a space at the
951 beginning and then save right after the paste. (Lgb)
954 // If old.pos() == 0 and old.pos()(1) == LineSeparator
955 // delete the LineSeparator.
958 // If old.pos() == 1 and old.pos()(0) == LineSeparator
959 // delete the LineSeparator.
962 bool const same_inset = &old.inset() == &cur.inset();
963 bool const same_par = same_inset && old.pit() == cur.pit();
964 bool const same_par_pos = same_par && old.pos() == cur.pos();
966 // If the chars around the old cursor were spaces, delete one of them.
968 // Only if the cursor has really moved.
970 && old.pos() < oldpar.size()
971 && oldpar.isLineSeparator(old.pos())
972 && oldpar.isLineSeparator(old.pos() - 1)
973 && !oldpar.isDeleted(old.pos() - 1)
974 && !oldpar.isDeleted(old.pos())) {
975 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
976 // FIXME: This will not work anymore when we have multiple views of the same buffer
977 // In this case, we will have to correct also the cursors held by
978 // other bufferviews. It will probably be easier to do that in a more
979 // automated way in CursorSlice code. (JMarc 26/09/2001)
980 // correct all cursor parts
982 fixCursorAfterDelete(cur.top(), old.top());
983 need_anchor_change = true;
989 // only do our magic if we changed paragraph
993 // don't delete anything if this is the ONLY paragraph!
994 if (old.lastpit() == 0)
997 // Do not delete empty paragraphs with keepempty set.
998 if (oldpar.allowEmpty())
1001 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1003 old.recordUndo(ATOMIC_UNDO,
1004 max(old.pit() - 1, pit_type(0)),
1005 min(old.pit() + 1, old.lastpit()));
1006 ParagraphList & plist = old.text()->paragraphs();
1007 bool const soa = oldpar.params().startOfAppendix();
1008 plist.erase(boost::next(plist.begin(), old.pit()));
1009 // do not lose start of appendix marker (bug 4212)
1010 if (soa && old.pit() < pit_type(plist.size()))
1011 plist[old.pit()].params().startOfAppendix(true);
1013 // see #warning (FIXME?) above
1014 if (cur.depth() >= old.depth()) {
1015 CursorSlice & curslice = cur[old.depth() - 1];
1016 if (&curslice.inset() == &old.inset()
1017 && curslice.pit() > old.pit()) {
1019 // since a paragraph has been deleted, all the
1020 // insets after `old' have been copied and
1021 // their address has changed. Therefore we
1022 // need to `regenerate' cur. (JMarc)
1023 cur.updateInsets(&(cur.bottom().inset()));
1024 need_anchor_change = true;
1030 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1031 need_anchor_change = true;
1032 // We return true here because the Paragraph contents changed and
1033 // we need a redraw before further action is processed.
1041 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1043 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1045 for (pit_type pit = first; pit <= last; ++pit) {
1046 Paragraph & par = pars_[pit];
1048 // We allow all kinds of "mumbo-jumbo" when freespacing.
1049 if (par.isFreeSpacing())
1052 for (pos_type pos = 1; pos < par.size(); ++pos) {
1053 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1054 && !par.isDeleted(pos - 1)) {
1055 if (par.eraseChar(pos - 1, trackChanges)) {
1061 // don't delete anything if this is the only remaining paragraph within the given range
1062 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1066 // don't delete empty paragraphs with keepempty set
1067 if (par.allowEmpty())
1070 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1071 pars_.erase(boost::next(pars_.begin(), pit));
1077 par.stripLeadingSpaces(trackChanges);
1082 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1084 cur.recordUndo(ATOMIC_UNDO, first, last);
1088 void Text::recUndo(Cursor & cur, pit_type par) const
1090 cur.recordUndo(ATOMIC_UNDO, par, par);