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::getLayoutFont(Buffer const & buffer, pit_type const pit) const
78 LayoutPtr 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::getLabelFont(Buffer const & buffer, Paragraph const & par) const
99 LayoutPtr 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 LayoutPtr 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 LayoutPtr const & lyxlayout = bufparams.textClass()[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 LayoutPtr const & lyxlayout = params.textClass()[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(params, 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 = getLabelFont(cur.buffer(), pars_[pit]);
313 layoutfont = getLayoutFont(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.getDisplayFont(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 Layout const & layout = *(par.layout());
474 par.params().apply(p, layout);
479 // this really should just insert the inset and not move the cursor.
480 void Text::insertInset(Cursor & cur, Inset * inset)
482 BOOST_ASSERT(this == cur.text());
484 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
485 Change(cur.buffer().params().trackChanges
486 ? Change::INSERTED : Change::UNCHANGED));
490 // needed to insert the selection
491 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
493 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
494 cur.current_font, str, autoBreakRows_);
498 // turn double CR to single CR, others are converted into one
499 // blank. Then insertStringAsLines is called
500 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
502 docstring linestr = str;
503 bool newline_inserted = false;
505 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
506 if (linestr[i] == '\n') {
507 if (newline_inserted) {
508 // we know that \r will be ignored by
509 // insertStringAsLines. Of course, it is a dirty
510 // trick, but it works...
511 linestr[i - 1] = '\r';
515 newline_inserted = true;
517 } else if (isPrintable(linestr[i])) {
518 newline_inserted = false;
521 insertStringAsLines(cur, linestr);
525 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
526 bool setfont, bool boundary)
528 TextMetrics const & tm = cur.bv().textMetrics(this);
529 bool const update_needed = !tm.has(par);
531 setCursorIntern(cur, par, pos, setfont, boundary);
532 return cur.bv().checkDepm(cur, old) || update_needed;
536 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
538 BOOST_ASSERT(par != int(paragraphs().size()));
542 // now some strict checking
543 Paragraph & para = getPar(par);
545 // None of these should happen, but we're scaredy-cats
547 lyxerr << "dont like -1" << endl;
551 if (pos > para.size()) {
552 lyxerr << "dont like 1, pos: " << pos
553 << " size: " << para.size()
554 << " par: " << par << endl;
560 void Text::setCursorIntern(Cursor & cur,
561 pit_type par, pos_type pos, bool setfont, bool boundary)
563 BOOST_ASSERT(this == cur.text());
564 cur.boundary(boundary);
565 setCursor(cur.top(), par, pos);
567 cur.setCurrentFont();
571 bool Text::checkAndActivateInset(Cursor & cur, bool front)
575 if (front && cur.pos() == cur.lastpos())
577 if (!front && cur.pos() == 0)
579 Inset * inset = front ? cur.nextInset() : cur.prevInset();
580 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
583 * Apparently, when entering an inset we are expected to be positioned
584 * *before* it in the containing paragraph, regardless of the direction
585 * from which we are entering. Otherwise, cursor placement goes awry,
586 * and when we exit from the beginning, we'll be placed *after* the
591 inset->edit(cur, front);
596 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
602 if (cur.pos() == cur.lastpos())
604 Paragraph & par = cur.paragraph();
605 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
606 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
608 inset->edit(cur, movingForward,
609 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
614 bool Text::cursorBackward(Cursor & cur)
616 // Tell BufferView to test for FitCursor in any case!
617 cur.updateFlags(Update::FitCursor);
619 // not at paragraph start?
621 // if on right side of boundary (i.e. not at paragraph end, but line end)
622 // -> skip it, i.e. set boundary to true, i.e. go only logically left
623 // there are some exceptions to ignore this: lineseps, newlines, spaces
625 // some effectless debug code to see the values in the debugger
626 bool bound = cur.boundary();
627 int rowpos = cur.textRow().pos();
629 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
630 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
631 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
633 if (!cur.boundary() &&
634 cur.textRow().pos() == cur.pos() &&
635 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
636 !cur.paragraph().isNewline(cur.pos() - 1) &&
637 !cur.paragraph().isSeparator(cur.pos() - 1)) {
638 return setCursor(cur, cur.pit(), cur.pos(), true, true);
641 // go left and try to enter inset
642 if (checkAndActivateInset(cur, false))
645 // normal character left
646 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
649 // move to the previous paragraph or do nothing
651 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
656 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
658 pit_type new_pit = cur.pit(); // the paragraph to which we will move
659 pos_type new_pos; // the position we will move to
660 bool new_boundary; // will we move to a boundary position?
661 pos_type left_pos; // position visually left of current cursor
662 pos_type right_pos; // position visually right of current cursor
663 bool new_pos_is_RTL; // is new position we're moving to RTL?
665 cur.getSurroundingPos(left_pos, right_pos);
667 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
669 // Are we at an inset?
670 Cursor temp_cur = cur;
671 temp_cur.pos() = left_pos;
672 temp_cur.boundary(false);
674 checkAndActivateInsetVisual(temp_cur, left_pos >= cur.pos(), true)) {
675 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
676 cur = temp_cur; // set the real cursor to new position inside inset!
680 // Are we already at leftmost pos in row?
681 if (left_pos == -1) {
683 Cursor new_cur = cur;
684 if (!new_cur.posVisToNewRow(true)) {
685 LYXERR(Debug::RTL, "not moving!");
689 // we actually move the cursor at the end of this function, for now
690 // just keep track of the new position...
691 new_pit = new_cur.pit();
692 new_pos = new_cur.pos();
693 new_boundary = new_cur.boundary();
695 LYXERR(Debug::RTL, "left edge, moving: " << int(new_pit) << ","
696 << int(new_pos) << "," << (new_boundary ? 1 : 0));
699 // normal movement to the left
701 // Recall, if the cursor is at position 'x', that means *before*
702 // the character at position 'x'. In RTL, "before" means "to the
703 // right of", in LTR, "to the left of". So currently our situation
704 // is this: the position to our left is 'left_pos' (i.e., we're
705 // currently to the right of 'left_pos'). In order to move to the
706 // left, it depends whether or not the character at 'left_pos' is RTL.
707 new_pos_is_RTL = cur.paragraph().getFontSettings(
708 cur.bv().buffer().params(), left_pos).isVisibleRightToLeft();
709 // If the character at 'left_pos' *is* RTL, then in order to move to
710 // the left of it, we need to be *after* 'left_pos', i.e., move to
711 // position 'left_pos' + 1.
712 if (new_pos_is_RTL) {
713 new_pos = left_pos + 1;
714 // set the boundary to true in two situations:
716 // 1. if new_pos is now lastpos (which means that we're moving left
717 // to the end of an RTL chunk which is at the end of an LTR
719 new_pos == cur.lastpos()
720 // 2. if the position *after* left_pos is not RTL (we want to be
721 // *after* left_pos, not before left_pos + 1!)
722 || !cur.paragraph().getFontSettings(cur.bv().buffer().params(),
723 new_pos).isVisibleRightToLeft()
726 else // set the boundary to false
727 new_boundary = false;
729 // Otherwise (if the character at position 'left_pos' is LTR), then
730 // moving to the left of it is as easy as setting the new position
734 new_boundary = false;
739 LYXERR(Debug::RTL, "moving to: " << new_pos
740 << (new_boundary ? " (boundary)" : ""));
742 return setCursor(cur, new_pit, new_pos, true, new_boundary);
746 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
748 pit_type new_pit = cur.pit(); // the paragraph to which we will move
749 pos_type new_pos; // the position we will move to
750 bool new_boundary; // will we move to a boundary position?
751 pos_type left_pos; // position visually left of current cursor
752 pos_type right_pos; // position visually right of current cursor
753 bool new_pos_is_RTL; // is new position we're moving to RTL?
755 cur.getSurroundingPos(left_pos, right_pos);
757 LYXERR(Debug::RTL, left_pos <<"|"<< right_pos << " (pos: "<<cur.pos()<<")");
759 // Are we at an inset?
760 Cursor temp_cur = cur;
761 temp_cur.pos() = right_pos;
762 temp_cur.boundary(false);
764 checkAndActivateInsetVisual(temp_cur, right_pos >= cur.pos(), false)) {
765 LYXERR(Debug::RTL, "entering inset at: " << temp_cur.pos());
766 cur = temp_cur; // set the real cursor to new position inside inset!
770 // Are we already at rightmost pos in row?
771 if (right_pos == -1) {
773 Cursor new_cur = cur;
774 if (!new_cur.posVisToNewRow(false)) {
775 LYXERR(Debug::RTL, "not moving!");
779 // we actually move the cursor at the end of this function, for now
780 // just keep track of the new position...
781 new_pit = new_cur.pit();
782 new_pos = new_cur.pos();
783 new_boundary = new_cur.boundary();
785 LYXERR(Debug::RTL, "right edge, moving: " << int(new_pit) << ","
786 << int(new_pos) << "," << (new_boundary ? 1 : 0));
789 // normal movement to the right
791 // Recall, if the cursor is at position 'x', that means *before*
792 // the character at position 'x'. In RTL, "before" means "to the
793 // right of", in LTR, "to the left of". So currently our situation
794 // is this: the position to our right is 'right_pos' (i.e., we're
795 // currently to the left of 'right_pos'). In order to move to the
796 // right, it depends whether or not the character at 'right_pos' is RTL.
797 new_pos_is_RTL = cur.paragraph().getFontSettings(
798 cur.bv().buffer().params(), right_pos).isVisibleRightToLeft();
799 // If the character at 'right_pos' *is* LTR, then in order to move to
800 // the right of it, we need to be *after* 'right_pos', i.e., move to
801 // position 'right_pos' + 1.
802 if (!new_pos_is_RTL) {
803 new_pos = right_pos + 1;
804 // set the boundary to true in two situations:
806 // 1. if new_pos is now lastpos (which means that we're moving
807 // right to the end of an LTR chunk which is at the end of an
809 new_pos == cur.lastpos()
810 // 2. if the position *after* right_pos is RTL (we want to be
811 // *after* right_pos, not before right_pos + 1!)
812 || cur.paragraph().getFontSettings(cur.bv().buffer().params(),
813 new_pos).isVisibleRightToLeft()
816 else // set the boundary to false
817 new_boundary = false;
819 // Otherwise (if the character at position 'right_pos' is RTL), then
820 // moving to the right of it is as easy as setting the new position
824 new_boundary = false;
829 LYXERR(Debug::RTL, "moving to: " << new_pos
830 << (new_boundary ? " (boundary)" : ""));
832 return setCursor(cur, new_pit, new_pos, true, new_boundary);
836 bool Text::cursorForward(Cursor & cur)
838 // Tell BufferView to test for FitCursor in any case!
839 cur.updateFlags(Update::FitCursor);
841 // not at paragraph end?
842 if (cur.pos() != cur.lastpos()) {
843 // in front of editable inset, i.e. jump into it?
844 if (checkAndActivateInset(cur, true))
847 TextMetrics const & tm = cur.bv().textMetrics(this);
848 // if left of boundary -> just jump to right side
849 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
850 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
851 return setCursor(cur, cur.pit(), cur.pos(), true, false);
853 // next position is left of boundary,
854 // but go to next line for special cases like space, newline, linesep
856 // some effectless debug code to see the values in the debugger
857 int endpos = cur.textRow().endpos();
858 int lastpos = cur.lastpos();
860 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
861 bool newline = cur.paragraph().isNewline(cur.pos());
862 bool sep = cur.paragraph().isSeparator(cur.pos());
863 if (cur.pos() != cur.lastpos()) {
864 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
865 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
866 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
869 if (cur.textRow().endpos() == cur.pos() + 1 &&
870 cur.textRow().endpos() != cur.lastpos() &&
871 !cur.paragraph().isNewline(cur.pos()) &&
872 !cur.paragraph().isLineSeparator(cur.pos()) &&
873 !cur.paragraph().isSeparator(cur.pos())) {
874 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
877 // in front of RTL boundary? Stay on this side of the boundary because:
878 // ab|cDDEEFFghi -> abc|DDEEFFghi
879 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
880 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
883 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
886 // move to next paragraph
887 if (cur.pit() != cur.lastpit())
888 return setCursor(cur, cur.pit() + 1, 0, true, false);
893 bool Text::cursorUpParagraph(Cursor & cur)
895 bool updated = false;
897 updated = setCursor(cur, cur.pit(), 0);
898 else if (cur.pit() != 0)
899 updated = setCursor(cur, cur.pit() - 1, 0);
904 bool Text::cursorDownParagraph(Cursor & cur)
906 bool updated = false;
907 if (cur.pit() != cur.lastpit())
908 updated = setCursor(cur, cur.pit() + 1, 0);
910 updated = setCursor(cur, cur.pit(), cur.lastpos());
915 // fix the cursor `cur' after a characters has been deleted at `where'
916 // position. Called by deleteEmptyParagraphMechanism
917 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
919 // Do nothing if cursor is not in the paragraph where the
921 if (cur.pit() != where.pit())
924 // If cursor position is after the deletion place update it
925 if (cur.pos() > where.pos())
928 // Check also if we don't want to set the cursor on a spot behind the
929 // pagragraph because we erased the last character.
930 if (cur.pos() > cur.lastpos())
931 cur.pos() = cur.lastpos();
935 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
936 Cursor & old, bool & need_anchor_change)
938 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
940 Paragraph & oldpar = old.paragraph();
942 // We allow all kinds of "mumbo-jumbo" when freespacing.
943 if (oldpar.isFreeSpacing())
946 /* Ok I'll put some comments here about what is missing.
947 There are still some small problems that can lead to
948 double spaces stored in the document file or space at
949 the beginning of paragraphs(). This happens if you have
950 the cursor between to spaces and then save. Or if you
951 cut and paste and the selection have a space at the
952 beginning and then save right after the paste. (Lgb)
955 // If old.pos() == 0 and old.pos()(1) == LineSeparator
956 // delete the LineSeparator.
959 // If old.pos() == 1 and old.pos()(0) == LineSeparator
960 // delete the LineSeparator.
963 bool const same_inset = &old.inset() == &cur.inset();
964 bool const same_par = same_inset && old.pit() == cur.pit();
965 bool const same_par_pos = same_par && old.pos() == cur.pos();
967 // If the chars around the old cursor were spaces, delete one of them.
969 // Only if the cursor has really moved.
971 && old.pos() < oldpar.size()
972 && oldpar.isLineSeparator(old.pos())
973 && oldpar.isLineSeparator(old.pos() - 1)
974 && !oldpar.isDeleted(old.pos() - 1)
975 && !oldpar.isDeleted(old.pos())) {
976 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
977 // FIXME: This will not work anymore when we have multiple views of the same buffer
978 // In this case, we will have to correct also the cursors held by
979 // other bufferviews. It will probably be easier to do that in a more
980 // automated way in CursorSlice code. (JMarc 26/09/2001)
981 // correct all cursor parts
983 fixCursorAfterDelete(cur.top(), old.top());
984 need_anchor_change = true;
990 // only do our magic if we changed paragraph
994 // don't delete anything if this is the ONLY paragraph!
995 if (old.lastpit() == 0)
998 // Do not delete empty paragraphs with keepempty set.
999 if (oldpar.allowEmpty())
1002 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1004 old.recordUndo(ATOMIC_UNDO,
1005 max(old.pit() - 1, pit_type(0)),
1006 min(old.pit() + 1, old.lastpit()));
1007 ParagraphList & plist = old.text()->paragraphs();
1008 bool const soa = oldpar.params().startOfAppendix();
1009 plist.erase(boost::next(plist.begin(), old.pit()));
1010 // do not lose start of appendix marker (bug 4212)
1011 if (soa && old.pit() < pit_type(plist.size()))
1012 plist[old.pit()].params().startOfAppendix(true);
1014 // see #warning (FIXME?) above
1015 if (cur.depth() >= old.depth()) {
1016 CursorSlice & curslice = cur[old.depth() - 1];
1017 if (&curslice.inset() == &old.inset()
1018 && curslice.pit() > old.pit()) {
1020 // since a paragraph has been deleted, all the
1021 // insets after `old' have been copied and
1022 // their address has changed. Therefore we
1023 // need to `regenerate' cur. (JMarc)
1024 cur.updateInsets(&(cur.bottom().inset()));
1025 need_anchor_change = true;
1031 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1032 need_anchor_change = true;
1033 // We return true here because the Paragraph contents changed and
1034 // we need a redraw before further action is processed.
1042 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1044 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1046 for (pit_type pit = first; pit <= last; ++pit) {
1047 Paragraph & par = pars_[pit];
1049 // We allow all kinds of "mumbo-jumbo" when freespacing.
1050 if (par.isFreeSpacing())
1053 for (pos_type pos = 1; pos < par.size(); ++pos) {
1054 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1055 && !par.isDeleted(pos - 1)) {
1056 if (par.eraseChar(pos - 1, trackChanges)) {
1062 // don't delete anything if this is the only remaining paragraph within the given range
1063 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1067 // don't delete empty paragraphs with keepempty set
1068 if (par.allowEmpty())
1071 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1072 pars_.erase(boost::next(pars_.begin(), pit));
1078 par.stripLeadingSpaces(trackChanges);
1083 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1085 cur.recordUndo(ATOMIC_UNDO, first, last);
1089 void Text::recUndo(Cursor & cur, pit_type par) const
1091 cur.recordUndo(ATOMIC_UNDO, par, par);