X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCursor.cpp;h=eff32d05769bf650b7f6846c586efdaf19fa5267;hb=28be7d552f62cc02fa86d7f79201d089bfb2d7b5;hp=3ca61633a5bb0267327e9352108ed70e35c668f4;hpb=1b1f8dd235ba8e168348cd23c824063f2595a0c5;p=lyx.git diff --git a/src/Cursor.cpp b/src/Cursor.cpp index 3ca61633a5..eff32d0576 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -14,8 +14,8 @@ #include -#include "Bidi.h" #include "Buffer.h" +#include "BufferParams.h" #include "BufferView.h" #include "CoordCache.h" #include "Cursor.h" @@ -32,6 +32,7 @@ #include "Paragraph.h" #include "ParIterator.h" #include "Row.h" +#include "texstream.h" #include "Text.h" #include "TextMetrics.h" #include "TocBackend.h" @@ -47,13 +48,13 @@ #include "mathed/InsetMath.h" #include "mathed/InsetMathBrace.h" +#include "mathed/InsetMathEnsureMath.h" #include "mathed/InsetMathScript.h" #include "mathed/MacroTable.h" #include "mathed/MathData.h" +#include "mathed/MathFactory.h" #include "mathed/MathMacro.h" -#include "support/bind.h" - #include #include #include @@ -65,50 +66,34 @@ namespace lyx { namespace { -bool positionable(DocIterator const & cursor, DocIterator const & anchor) -{ - // avoid deeper nested insets when selecting - if (cursor.depth() > anchor.depth()) - return false; - - // anchor might be deeper, should have same path then - for (size_t i = 0; i < cursor.depth(); ++i) - if (&cursor[i].inset() != &anchor[i].inset()) - return false; - - // position should be ok. - return true; -} - - // Find position closest to (x, y) in cell given by iter. // Used only in mathed -DocIterator bruteFind2(Cursor const & c, int x, int y) +DocIterator bruteFind(Cursor const & c, int x, int y) { double best_dist = numeric_limits::max(); DocIterator result; DocIterator it = c; - it.top().pos() = 0; + it.pos() = 0; DocIterator et = c; - et.top().pos() = et.top().asInsetMath()->cell(et.top().idx()).size(); + et.pos() = et.lastpos(); for (size_t i = 0;; ++i) { int xo; int yo; Inset const * inset = &it.inset(); - CoordCache const & cache = c.bv().coordCache(); + CoordCache::Insets const & insetCache = c.bv().coordCache().getInsets(); // FIXME: in the case where the inset is not in the cache, this // means that no part of it is visible on screen. In this case // we don't do elaborate search and we just return the forwarded // DocIterator at its beginning. - if (!cache.getInsets().has(inset)) { + if (!insetCache.has(inset)) { it.top().pos() = 0; return it; } - Point const o = cache.getInsets().xy(inset); + Point const o = insetCache.xy(inset); inset->cursorPos(c.bv(), it.top(), c.boundary(), xo, yo); // Convert to absolute xo += o.x_; @@ -130,155 +115,69 @@ DocIterator bruteFind2(Cursor const & c, int x, int y) } -/* -/// moves position closest to (x, y) in given box -bool bruteFind(Cursor & cursor, - int x, int y, int xlow, int xhigh, int ylow, int yhigh) -{ - LASSERT(!cursor.empty(), return false); - Inset & inset = cursor[0].inset(); - BufferView & bv = cursor.bv(); - - CoordCache::InnerParPosCache const & cache = - bv.coordCache().getParPos().find(cursor.bottom().text())->second; - // Get an iterator on the first paragraph in the cache - DocIterator it(inset); - it.push_back(CursorSlice(inset)); - it.pit() = cache.begin()->first; - // Get an iterator after the last paragraph in the cache - DocIterator et(inset); - et.push_back(CursorSlice(inset)); - et.pit() = boost::prior(cache.end())->first; - if (et.pit() >= et.lastpit()) - et = doc_iterator_end(inset); - else - ++et.pit(); - - double best_dist = numeric_limits::max(); - DocIterator best_cursor = et; - - for ( ; it != et; it.forwardPos(true)) { - // avoid invalid nesting when selecting - if (!cursor.selection() || positionable(it, cursor.anchor_)) { - Point p = bv.getPos(it, false); - int xo = p.x_; - int yo = p.y_; - if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) { - double const dx = xo - x; - double const dy = yo - y; - double const d = dx * dx + dy * dy; - // '<=' in order to take the last possible position - // this is important for clicking behind \sum in e.g. '\sum_i a' - if (d <= best_dist) { - // lyxerr << "*" << endl; - best_dist = d; - best_cursor = it; - } - } - } - } - - if (best_cursor != et) { - cursor.setCursor(best_cursor); - return true; - } - - return false; -} -*/ - - -/// moves position closest to (x, y) in given box -bool bruteFind3(Cursor & cur, int x, int y, bool up) -{ - BufferView & bv = cur.bv(); - int ylow = up ? 0 : y + 1; - int yhigh = up ? y - 1 : bv.workHeight(); - int xlow = 0; - int xhigh = bv.workWidth(); - -// FIXME: bit more work needed to get 'from' and 'to' right. - pit_type from = cur.bottom().pit(); - //pit_type to = cur.bottom().pit(); - //lyxerr << "Pit start: " << from << endl; - - //lyxerr << "bruteFind3: x: " << x << " y: " << y - // << " xlow: " << xlow << " xhigh: " << xhigh - // << " ylow: " << ylow << " yhigh: " << yhigh - // << endl; - DocIterator it = doc_iterator_begin(cur.buffer()); - it.pit() = from; - DocIterator et = doc_iterator_end(cur.buffer()); - - double best_dist = numeric_limits::max(); - DocIterator best_cursor = et; - - for ( ; it != et; it.forwardPos()) { - // avoid invalid nesting when selecting - if (bv.cursorStatus(it) == CUR_INSIDE - && (!cur.selection() || positionable(it, cur.realAnchor()))) { - // If this function is ever used again, check - // whether this is the same as "bv.getPos(it, - // false)" with boundary = false. - Point p = bv.getPos(it); - int xo = p.x_; - int yo = p.y_; - if (xlow <= xo && xo <= xhigh && ylow <= yo && yo <= yhigh) { - double const dx = xo - x; - double const dy = yo - y; - double const d = dx * dx + dy * dy; - //lyxerr << "itx: " << xo << " ity: " << yo << " d: " << d - // << " dx: " << dx << " dy: " << dy - // << " idx: " << it.idx() << " pos: " << it.pos() - // << " it:\n" << it - // << endl; - // '<=' in order to take the last possible position - // this is important for clicking behind \sum in e.g. '\sum_i a' - if (d <= best_dist) { - //lyxerr << "*" << endl; - best_dist = d; - best_cursor = it; - } - } - } - } - - //lyxerr << "best_dist: " << best_dist << " cur:\n" << best_cursor << endl; - if (best_cursor == et) - return false; - cur.setCursor(best_cursor); - return true; -} - } // namespace anon CursorData::CursorData() : DocIterator(), anchor_(), selection_(false), mark_(false), word_selection_(false), - logicalpos_(false), current_font(inherit_font) + current_font(inherit_font), + autocorrect_(false), macromode_(false) {} CursorData::CursorData(Buffer * buffer) : DocIterator(buffer), anchor_(), selection_(false), mark_(false), word_selection_(false), - logicalpos_(false), current_font(inherit_font) + current_font(inherit_font), + autocorrect_(false), macromode_(false) {} CursorData::CursorData(DocIterator const & dit) : DocIterator(dit), anchor_(), selection_(false), mark_(false), word_selection_(false), - logicalpos_(false), current_font(inherit_font) + current_font(inherit_font), + autocorrect_(false), macromode_(false) {} + + +ostream & operator<<(ostream & os, CursorData const & cur) +{ + os << "\n cursor: | anchor:\n"; + for (size_t i = 0, n = cur.depth(); i != n; ++i) { + os << " " << cur[i] << " | "; + if (i < cur.anchor_.depth()) + os << cur.anchor_[i]; + else + os << "-------------------------------"; + os << "\n"; + } + for (size_t i = cur.depth(), n = cur.anchor_.depth(); i < n; ++i) { + os << "------------------------------- | " << cur.anchor_[i] << "\n"; + } + os << " selection: " << cur.selection_ +// << " x_target: " << cur.x_target_ + << " boundary: " << cur.boundary() << endl; + return os; +} + + +LyXErr & operator<<(LyXErr & os, CursorData const & cur) +{ + os.stream() << cur; + return os; +} + + // be careful: this is called from the bv's constructor, too, so // bv functions are not yet available! Cursor::Cursor(BufferView & bv) - : CursorData(&bv.buffer()), bv_(&bv), - x_target_(-1), textTargetOffset_(0) + : CursorData(&bv.buffer()), bv_(&bv), + x_target_(-1), textTargetOffset_(0), + beforeDispatchPosX_(0), beforeDispatchPosY_(0) {} @@ -296,13 +195,37 @@ void Cursor::reset() } -// this (intentionally) does neither touch anchor nor selection status void Cursor::setCursor(DocIterator const & cur) { DocIterator::operator=(cur); } +void Cursor::setCursorSelectionTo(DocIterator dit) +{ + size_t i = 0; + // normalise dit + while (i < dit.depth() && i < anchor_.depth() && dit[i] == anchor_[i]) + ++i; + if (i != dit.depth()) { + // otherwise the cursor is already normal + if (i == anchor_.depth()) + // dit is a proper extension of the anchor_ + dit.cutOff(i - 1); + else if (i + 1 < dit.depth()) { + // one has dit[i] != anchor_[i] but either dit[i-1] == anchor_[i-1] + // or i == 0. Remove excess. + dit.cutOff(i); + if (dit[i] > anchor_[i]) + // place dit after the inset it was in + ++dit.pos(); + } + } + setCursor(dit); + setSelection(); +} + + void Cursor::setCursorToAnchor() { if (selection()) { @@ -368,7 +291,7 @@ void Cursor::saveBeforeDispatchPosXY() void Cursor::dispatch(FuncRequest const & cmd0) { - LYXERR(Debug::DEBUG, "cmd: " << cmd0 << '\n' << *this); + LYXERR(Debug::ACTION, "Cursor::dispatch: cmd: " << cmd0 << '\n' << *this); if (empty()) return; @@ -400,10 +323,12 @@ void Cursor::dispatch(FuncRequest const & cmd0) for (; depth(); pop(), boundary(false)) { LYXERR(Debug::DEBUG, "Cursor::dispatch: cmd: " << cmd0 << endl << *this); - // LASSERT: Is it safe to continue here, or should we return? - LASSERT(pos() <= lastpos(), /**/); - LASSERT(idx() <= lastidx(), /**/); - LASSERT(pit() <= lastpit(), /**/); + + // In any of these cases, the cursor is invalid, and we should + // try to save this document rather than crash. + LBUFERR(pos() <= lastpos()); + LBUFERR(idx() <= lastidx()); + LBUFERR(pit() <= lastpit()); // The common case is 'LFUN handled, need update', so make the // LFUN handler's life easier by assuming this as default value. @@ -414,7 +339,7 @@ void Cursor::dispatch(FuncRequest const & cmd0) if (disp_.dispatched()) break; } - + // it completely to get a 'bomb early' behaviour in case this // object will be used again. if (!disp_.dispatched()) { @@ -422,7 +347,7 @@ void Cursor::dispatch(FuncRequest const & cmd0) // We might have invalidated the cursor when removing an empty // paragraph while the cursor could not be moved out the inset // while we initially thought we could. This might happen when - // a multiline inset becomes an inline inset when the second + // a multiline inset becomes an inline inset when the second // paragraph is removed. if (safe.pit() > safe.lastpit()) { safe.pit() = safe.lastpit(); @@ -438,6 +363,9 @@ void Cursor::dispatch(FuncRequest const & cmd0) } endUndoGroup(); + // NOTE: The code below has been copied to BufferView::dispatch. + // If you need to modify this, please update the other one too. + // notify insets we just left if (*this != old) { old.beginUndoGroup(); @@ -460,14 +388,14 @@ DispatchResult const & Cursor::result() const BufferView & Cursor::bv() const { - LBUFERR(bv_, _("Cursor has no BufferView!")); + LBUFERR(bv_); return *bv_; } void Cursor::pop() { - LBUFERR(depth() >= 1, _("Attempt to pop empty cursor!")); + LBUFERR(depth() >= 1); pop_back(); } @@ -526,6 +454,19 @@ int Cursor::currentMode() } +bool Cursor::inCoordCache() const +{ + // the root inset is not in cache, but we do not need it. + if (depth() == 1) + return true; + CoordCache::Insets const & icache = bv_->coordCache().getInsets(); + for (size_t i = 1 ; i < depth() ; ++i) + if (!icache.has(&(*this)[i].inset())) + return false; + return true; +} + + void Cursor::getPos(int & x, int & y) const { Point p = bv().getPos(*this); @@ -610,30 +551,11 @@ void Cursor::checkNewWordPosition() } -bool Cursor::posBackward() -{ - if (pos() == 0) - return false; - --pos(); - return true; -} - - -bool Cursor::posForward() -{ - if (pos() == lastpos()) - return false; - ++pos(); - return true; -} - - bool Cursor::posVisRight(bool skip_inset) { Cursor new_cur = *this; // where we will move to pos_type left_pos; // position visually left of current cursor pos_type right_pos; // position visually right of current cursor - bool new_pos_is_RTL; // is new position we're moving to RTL? getSurroundingPos(left_pos, right_pos); @@ -652,17 +574,17 @@ bool Cursor::posVisRight(bool skip_inset) // Are we already at rightmost pos in row? else if (text()->empty() || right_pos == -1) { - + new_cur = *this; if (!new_cur.posVisToNewRow(false)) { LYXERR(Debug::RTL, "not moving!"); return false; } - + // we actually move the cursor at the end of this // function, for now just keep track of the new // position in new_cur... - LYXERR(Debug::RTL, "right edge, moving: " << int(new_cur.pit()) << "," + LYXERR(Debug::RTL, "right edge, moving: " << int(new_cur.pit()) << "," << int(new_cur.pos()) << "," << (new_cur.boundary() ? 1 : 0)); } @@ -677,7 +599,7 @@ bool Cursor::posVisRight(bool skip_inset) // we're currently to the left of 'right_pos'). In // order to move to the right, it depends whether or // not the character at 'right_pos' is RTL. - new_pos_is_RTL = paragraph().getFontSettings( + bool const new_pos_is_RTL = paragraph().getFontSettings( buffer()->params(), right_pos).isVisibleRightToLeft(); // If the character at 'right_pos' *is* LTR, then in // order to move to the right of it, we need to be @@ -710,16 +632,13 @@ bool Cursor::posVisRight(bool skip_inset) new_cur.pos() = right_pos; new_cur.boundary(false); } - + } - bool moved = (new_cur.pos() != pos() - || new_cur.pit() != pit() - || new_cur.boundary() != boundary() - || &new_cur.inset() != &inset()); - + bool const moved = new_cur != *this || new_cur.boundary() != boundary(); + if (moved) { - LYXERR(Debug::RTL, "moving to: " << new_cur.pos() + LYXERR(Debug::RTL, "moving to: " << new_cur.pos() << (new_cur.boundary() ? " (boundary)" : "")); *this = new_cur; } @@ -733,7 +652,6 @@ bool Cursor::posVisLeft(bool skip_inset) Cursor new_cur = *this; // where we will move to pos_type left_pos; // position visually left of current cursor pos_type right_pos; // position visually right of current cursor - bool new_pos_is_RTL; // is new position we're moving to RTL? getSurroundingPos(left_pos, right_pos); @@ -742,7 +660,7 @@ bool Cursor::posVisLeft(bool skip_inset) // Are we at an inset? new_cur.pos() = left_pos; new_cur.boundary(false); - if (!skip_inset && + if (!skip_inset && text()->checkAndActivateInsetVisual(new_cur, left_pos >= pos(), true)) { // we actually move the cursor at the end of this // function, for now we just keep track of the new @@ -752,17 +670,17 @@ bool Cursor::posVisLeft(bool skip_inset) // Are we already at leftmost pos in row? else if (text()->empty() || left_pos == -1) { - + new_cur = *this; if (!new_cur.posVisToNewRow(true)) { LYXERR(Debug::RTL, "not moving!"); return false; } - + // we actually move the cursor at the end of this // function, for now just keep track of the new // position in new_cur... - LYXERR(Debug::RTL, "left edge, moving: " << int(new_cur.pit()) << "," + LYXERR(Debug::RTL, "left edge, moving: " << int(new_cur.pit()) << "," << int(new_cur.pos()) << "," << (new_cur.boundary() ? 1 : 0)); } @@ -777,7 +695,7 @@ bool Cursor::posVisLeft(bool skip_inset) // currently to the right of 'left_pos'). In order to // move to the left, it depends whether or not the // character at 'left_pos' is RTL. - new_pos_is_RTL = paragraph().getFontSettings( + bool const new_pos_is_RTL = paragraph().getFontSettings( buffer()->params(), left_pos).isVisibleRightToLeft(); // If the character at 'left_pos' *is* RTL, then in // order to move to the left of it, we need to be @@ -810,153 +728,133 @@ bool Cursor::posVisLeft(bool skip_inset) new_cur.pos() = left_pos; new_cur.boundary(false); } - + } - bool moved = (new_cur.pos() != pos() - || new_cur.pit() != pit() - || new_cur.boundary() != boundary()); + bool const moved = new_cur != *this || new_cur.boundary() != boundary(); if (moved) { - LYXERR(Debug::RTL, "moving to: " << new_cur.pos() + LYXERR(Debug::RTL, "moving to: " << new_cur.pos() << (new_cur.boundary() ? " (boundary)" : "")); *this = new_cur; } - + return moved; } -void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) +namespace { + +// Return true on success +bool findNonVirtual(Row const & row, Row::const_iterator & cit, bool onleft) { - // preparing bidi tables - Paragraph const & par = paragraph(); - Buffer const & buf = *buffer(); + if (onleft) { + while (cit != row.begin() && cit->isVirtual()) + --cit; + } else { + while (cit != row.end() && cit->isVirtual()) + ++cit; + } + return cit != row.end() && !cit->isVirtual(); +} + +} + +void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const +{ + // by default, we know nothing. + left_pos = -1; + right_pos = -1; + Row const & row = textRow(); - Bidi bidi; - bidi.computeTables(par, buf, row); - - LYXERR(Debug::RTL, "bidi: " << row.pos() << "--" << row.endpos()); - - // The cursor is painted *before* the character at pos(), or, - // if 'boundary' is true, *after* the character at (pos() - - // 1). So we already have one known position around the - // cursor: - pos_type const known_pos = boundary() && pos() > 0 ? pos() - 1 : pos(); - - // edge case: if we're at the end of the paragraph, things are - // a little different (because lastpos is a position which - // does not really "exist" --- there's no character there - // yet). - if (known_pos == lastpos()) { - if (par.isRTL(buf.params())) { - left_pos = -1; - right_pos = bidi.vis2log(row.pos()); - } else { - // LTR paragraph - right_pos = -1; - left_pos = bidi.vis2log(row.endpos() - 1); - } + double dummy = 0; + Row::const_iterator cit = row.findElement(pos(), boundary(), dummy); + // Handle the case of empty row + if (cit == row.end()) { + if (row.isRTL()) + right_pos = row.pos(); + else + left_pos = row.pos() - 1; return; } - - // Whether 'known_pos' is to the left or to the right of the - // cursor depends on whether it is an RTL or LTR character... - bool const cur_is_RTL = - par.getFontSettings(buf.params(), known_pos).isVisibleRightToLeft(); - // ... in the following manner: - // For an RTL character, "before" - // means "to the right" and "after" means "to the left"; and - // for LTR, it's the reverse. So, 'known_pos' is to the right - // of the cursor if (RTL && boundary) or (!RTL && !boundary): - bool const known_pos_on_right = cur_is_RTL == boundary(); - - // So we now know one of the positions surrounding the cursor. - // Let's determine the other one: - if (known_pos_on_right) { - right_pos = known_pos; - // *visual* position of 'left_pos': - pos_type v_left_pos = bidi.log2vis(right_pos) - 1; - // If the position we just identified as 'left_pos' is - // a "skipped separator" (a separator which is at the - // logical end of a row, except for the last row in a - // paragraph; such separators are not painted, so they - // "are not really there"; note that in bidi text, - // such a separator could appear visually in the - // middle of a row), set 'left_pos' to the *next* - // position to the left. - if (bidi.inRange(v_left_pos) - && bidi.vis2log(v_left_pos) + 1 == row.endpos() - && row.endpos() < lastpos() - && par.isSeparator(bidi.vis2log(v_left_pos))) - --v_left_pos; - - // calculate the logical position of 'left_pos', if in row - if (!bidi.inRange(v_left_pos)) - left_pos = -1; - else - left_pos = bidi.vis2log(v_left_pos); - // If the position we identified as 'right_pos' is a - // "skipped separator", set 'right_pos' to the *next* - // position to the right. - if (right_pos + 1 == row.endpos() && row.endpos() < lastpos() - && par.isSeparator(right_pos)) { - pos_type const v_right_pos = bidi.log2vis(right_pos) + 1; - if (!bidi.inRange(v_right_pos)) - right_pos = -1; - else - right_pos = bidi.vis2log(v_right_pos); - } - } else { - // known_pos is on the left - left_pos = known_pos; - // *visual* position of 'right_pos' - pos_type v_right_pos = bidi.log2vis(left_pos) + 1; - // If the position we just identified as 'right_pos' - // is a "skipped separator", set 'right_pos' to the - // *next* position to the right. - if (bidi.inRange(v_right_pos) - && bidi.vis2log(v_right_pos) + 1 == row.endpos() - && row.endpos() < lastpos() - && par.isSeparator(bidi.vis2log(v_right_pos))) - ++v_right_pos; - - // calculate the logical position of 'right_pos', if in row - if (!bidi.inRange(v_right_pos)) - right_pos = -1; - else - right_pos = bidi.vis2log(v_right_pos); - // If the position we identified as 'left_pos' is a - // "skipped separator", set 'left_pos' to the *next* - // position to the left. - if (left_pos + 1 == row.endpos() && row.endpos() < lastpos() - && par.isSeparator(left_pos)) { - pos_type const v_left_pos = bidi.log2vis(left_pos) - 1; - if (!bidi.inRange(v_left_pos)) - left_pos = -1; - else - left_pos = bidi.vis2log(v_left_pos); + + // skip virtual elements and exit if no non-virtual one exists + if (!findNonVirtual(row, cit, !cit->isRTL())) + return; + + // if the position is at the left side of the element, we have to + // look at the previous element + if (pos() == cit->left_pos()) { + LYXERR(Debug::RTL, "getSurroundingPos(" << pos() << (boundary() ? "b" : "") + << "), AT LEFT of *cit=" << *cit); + // this one is easy (see common case below) + right_pos = pos() - (cit->isRTL() ? 1 : 0); + // at the left of the row + if (cit == row.begin()) + return; + --cit; + if (!findNonVirtual(row, cit, true)) + return; + // [...[ is the row element, | is cursor position (! with boundary) + // [ 1 2 [ is a ltr row element with pos=1 and endpos=3 + // ] 2 1] is an rtl row element with pos=1 and endpos=3 + // [ 1 2 [ [|3 4 [ => (2, 3) + // or [ 1 2 [ ]!4 3 ] => (2, 4) + // or ] 2 1 ] [|3 4 [ => (1, 3) + // or ] 4 3 ] ]!2 1 ] => (3, 2) + left_pos = cit->right_pos() - (cit->isRTL() ? 0 : 1); + // happens with consecutive row of same direction + if (left_pos == right_pos) { + left_pos += cit->isRTL() ? 1 : -1; } } - return; + // same code but with the element at the right + else if (pos() == cit->right_pos()) { + LYXERR(Debug::RTL, "getSurroundingPos(" << pos() << (boundary() ? "b" : "") + << "), AT RIGHT of *cit=" << *cit); + // this one is easy (see common case below) + left_pos = pos() - (cit->isRTL() ? 0 : 1); + // at the right of the row + if (cit + 1 == row.end()) + return; + ++cit; + if (!findNonVirtual(row, cit, false)) + return; + // [ 1 2![ [ 3 4 [ => (2, 3) + // or [ 1 2![ ] 4 3 ] => (2, 4) + // or ] 2 1|] [ 3 4 [ => (1, 3) + // or ] 4 3|] ] 2 1 ] => (3, 2) + right_pos = cit->left_pos() - (cit->isRTL() ? 1 : 0); + // happens with consecutive row of same direction + if (right_pos == left_pos) + right_pos += cit->isRTL() ? -1 : 1; + } + // common case: both positions are inside the row element + else { + // [ 1 2|3 [ => (2, 3) + // or ] 3|2 1 ] => (3, 2) + left_pos = pos() - (cit->isRTL() ? 0 : 1); + right_pos = pos() - (cit->isRTL() ? 1 : 0); + } + + // Note that debug message does not catch all early returns above + LYXERR(Debug::RTL,"getSurroundingPos(" << pos() << (boundary() ? "b" : "") + << ") => (" << left_pos << ", " << right_pos <<")"); } bool Cursor::posVisToNewRow(bool movingLeft) { - Paragraph const & par = paragraph(); - Buffer const & buf = *buffer(); Row const & row = textRow(); - bool par_is_LTR = !par.isRTL(buf.params()); + bool par_is_LTR = !row.isRTL(); // Inside a table, determining whether to move to the next or // previous row should be done based on the table's direction. - int s = depth() - 1; - if (s >= 1 && (*this)[s].inset().asInsetTabular()) { - par_is_LTR = !(*this)[s].inset().asInsetTabular()->isRightToLeft(*this); + if (inset().asInsetTabular()) { + par_is_LTR = !inset().asInsetTabular()->isRightToLeft(*this); LYXERR(Debug::RTL, "Inside table! par_is_LTR=" << (par_is_LTR ? 1 : 0)); } - + // if moving left in an LTR paragraph or moving right in an // RTL one, move to previous row if (par_is_LTR == movingLeft) { @@ -987,7 +885,7 @@ bool Cursor::posVisToNewRow(bool movingLeft) boundary(false); } } - + // make sure we're at left-/right-most pos in new row posVisToRowExtremity(!movingLeft); @@ -995,158 +893,50 @@ bool Cursor::posVisToNewRow(bool movingLeft) } -void Cursor::posVisToRowExtremity(bool left) +void Cursor::posVisToRowExtremity(bool left) { - // prepare bidi tables - Paragraph const & par = paragraph(); - Buffer const & buf = *buffer(); - Row const & row = textRow(); - Bidi bidi; - bidi.computeTables(par, buf, row); - LYXERR(Debug::RTL, "entering extremity: " << pit() << "," << pos() << "," << (boundary() ? 1 : 0)); - if (left) { // move to leftmost position - // if this is an RTL paragraph, and we're at the last row in the - // paragraph, move to lastpos - if (par.isRTL(buf.params()) && row.endpos() == lastpos()) - pos() = lastpos(); - else { - pos() = bidi.vis2log(row.pos()); - - // Moving to the leftmost position in the row, - // the cursor should normally be placed to the - // *left* of the leftmost position. A very - // common exception, though, is if the - // leftmost character also happens to be the - // separator at the (logical) end of the row - // --- in this case, the separator is - // positioned beyond the left margin, and we - // don't want to move the cursor there (moving - // to the left of the separator is equivalent - // to moving to the next line). So, in this - // case we actually want to place the cursor - // to the *right* of the leftmost position - // (the separator). Another exception is if - // we're moving to the logically last position - // in the row, which is *not* a separator: - // this means that the entire row has no - // separators (if there were any, the row - // would have been broken there); and - // therefore in this case we also move to the - // *right* of the last position (this - // indicates to the user that there is no - // space after this position, and is - // consistent with the behavior in the middle - // of a row --- moving right or left moves to - // the next/previous character; if we were to - // move to the *left* of this position, that - // would simulate a separator which is not - // really there!). Finally, there is an - // exception to the previous exception: if - // this non-separator-but-last-position-in-row - // is an inset, then we *do* want to stay to - // the left of it anyway: this is the - // "boundary" which we simulate at insets. - - // Another exception is when row.endpos() is - // 0. - - // do we want to be to the right of pos? - // as explained above, if at last pos in row, stay to the right - bool const right_of_pos = row.endpos() > 0 - && pos() == row.endpos() - 1 && !par.isInset(pos()); - - // Now we know if we want to be to the left or to the right of pos, - // let's make sure we are where we want to be. - bool const new_pos_is_RTL = - par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft(); - - if (new_pos_is_RTL != right_of_pos) { - ++pos(); - boundary(true); - } - } - } else { - // move to rightmost position - // if this is an LTR paragraph, and we're at the last row in the - // paragraph, move to lastpos - if (!par.isRTL(buf.params()) && row.endpos() == lastpos()) - pos() = lastpos(); - else { - pos() = row.endpos() > 0 ? bidi.vis2log(row.endpos() - 1) : 0; - - // Moving to the rightmost position in the - // row, the cursor should normally be placed - // to the *right* of the rightmost position. A - // very common exception, though, is if the - // rightmost character also happens to be the - // separator at the (logical) end of the row - // --- in this case, the separator is - // positioned beyond the right margin, and we - // don't want to move the cursor there (moving - // to the right of the separator is equivalent - // to moving to the next line). So, in this - // case we actually want to place the cursor - // to the *left* of the rightmost position - // (the separator). Another exception is if - // we're moving to the logically last position - // in the row, which is *not* a separator: - // this means that the entire row has no - // separators (if there were any, the row - // would have been broken there); and - // therefore in this case we also move to the - // *left* of the last position (this indicates - // to the user that there is no space after - // this position, and is consistent with the - // behavior in the middle of a row --- moving - // right or left moves to the next/previous - // character; if we were to move to the - // *right* of this position, that would - // simulate a separator which is not really - // there!). Finally, there is an exception to - // the previous exception: if this - // non-separator-but-last-position-in-row is - // an inset, then we *do* want to stay to the - // right of it anyway: this is the "boundary" - // which we simulate at insets. Another - // exception is when row.endpos() is 0. - - // do we want to be to the left of pos? - // as explained above, if at last pos in row, stay to the left, - // unless the last position is the same as the first. - bool const left_of_pos = row.endpos() > 0 - && pos() == row.endpos() - 1 && !par.isInset(pos()); - - // Now we know if we want to be to the left or to the right of pos, - // let's make sure we are where we want to be. - bool const new_pos_is_RTL = - par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft(); - - if (new_pos_is_RTL == left_of_pos) { - ++pos(); - boundary(true); - } - } - } + TextMetrics const & tm = bv_->textMetrics(text()); + // Looking for extremities is like clicking on the left or the + // right of the row. + int x = tm.origin().x_ + (left ? 0 : textRow().width()); + bool b = false; + pos() = tm.getPosNearX(textRow(), x, b); + boundary(b); + LYXERR(Debug::RTL, "leaving extremity: " << pit() << "," << pos() << "," << (boundary() ? 1 : 0)); } +bool Cursor::reverseDirectionNeeded() const +{ + /* + * We determine the directions based on the direction of the + * bottom() --- i.e., outermost --- paragraph, because that is + * the only way to achieve consistency of the arrow's movements + * within a paragraph, and thus avoid situations in which the + * cursor gets stuck. + */ + return bottom().paragraph().isRTL(bv().buffer().params()); +} + + CursorSlice Cursor::normalAnchor() const { if (!selection()) return top(); - if (anchor_.depth() >= depth()) { - // LASSERT: There have been several bugs around this code, that seem - // to involve failures to reset the anchor. We can at least not crash - // in release mode by resetting it ourselves. - LASSERT(false, /* */); - DocIterator & di = const_cast(anchor_); - di = *this; + // LASSERT: There have been several bugs around this code, that seem + // to involve failures to reset the anchor. We can at least not crash + // in release mode by resetting it ourselves. + if (anchor_.depth() < depth()) { + LYXERR0("Cursor is deeper than anchor. PLEASE REPORT.\nCursor is" + << *this); + const_cast(anchor_) = *this; } + CursorSlice normal = anchor_[depth() - 1]; if (depth() < anchor_.depth() && top() <= normal) { // anchor is behind cursor -> move anchor behind the inset @@ -1156,12 +946,6 @@ CursorSlice Cursor::normalAnchor() const } -DocIterator & Cursor::realAnchor() -{ - return anchor_; -} - - CursorSlice Cursor::selBegin() const { if (!selection()) @@ -1218,20 +1002,18 @@ DocIterator Cursor::selectionEnd() const void Cursor::setSelection() { - setSelection(true); - // A selection with no contents is not a selection - // FIXME: doesnt look ok - if (idx() == normalAnchor().idx() && - pit() == normalAnchor().pit() && + selection(true); + if (idx() == normalAnchor().idx() && + pit() == normalAnchor().pit() && pos() == normalAnchor().pos()) - setSelection(false); + selection(false); } void Cursor::setSelection(DocIterator const & where, int n) { setCursor(where); - setSelection(true); + selection(true); anchor_ = where; pos() += n; } @@ -1239,7 +1021,7 @@ void Cursor::setSelection(DocIterator const & where, int n) void Cursor::clearSelection() { - setSelection(false); + selection(false); setWordSelection(false); setMark(false); resetAnchor(); @@ -1287,8 +1069,6 @@ void Cursor::info(odocstream & os) const if (inset) prevInset()->infoize2(os); } - // overwite old message - os << " "; } @@ -1304,38 +1084,9 @@ bool Cursor::selHandle(bool sel) cap::saveSelection(*this); resetAnchor(); - setSelection(sel); + selection(sel); return true; } - - -ostream & operator<<(ostream & os, Cursor const & cur) -{ - os << "\n cursor: | anchor:\n"; - for (size_t i = 0, n = cur.depth(); i != n; ++i) { - os << " " << cur[i] << " | "; - if (i < cur.anchor_.depth()) - os << cur.anchor_[i]; - else - os << "-------------------------------"; - os << "\n"; - } - for (size_t i = cur.depth(), n = cur.anchor_.depth(); i < n; ++i) { - os << "------------------------------- | " << cur.anchor_[i] << "\n"; - } - os << " selection: " << cur.selection_ - << " x_target: " << cur.x_target_ << endl; - return os; -} - - -LyXErr & operator<<(LyXErr & os, Cursor const & cur) -{ - os.stream() << cur; - return os; -} - - } // namespace lyx @@ -1432,15 +1183,14 @@ void Cursor::plainInsert(MathAtom const & t) ++pos(); inset().setBuffer(bv_->buffer()); inset().initView(); - forceBufferUpdate(); + checkBufferStructure(); } void Cursor::insert(docstring const & str) { - for_each(str.begin(), str.end(), - bind(static_cast - (&Cursor::insert), this, _1)); + for (char_type c : str) + insert(c); } @@ -1533,7 +1283,7 @@ void Cursor::insert(MathData const & ar) } -bool Cursor::backspace() +bool Cursor::backspace(bool const force) { if (selection()) { cap::eraseSelection(*this); @@ -1569,11 +1319,11 @@ bool Cursor::backspace() } } - if (pos() != 0 && prevAtom()->nargs() > 0) { + if (pos() != 0 && !force && prevAtom()->confirmDeletion()) { // let's require two backspaces for 'big stuff' and // highlight on the first resetAnchor(); - setSelection(true); + selection(true); --pos(); } else { --pos(); @@ -1583,7 +1333,7 @@ bool Cursor::backspace() } -bool Cursor::erase() +bool Cursor::erase(bool const force) { if (inMacroMode()) return true; @@ -1618,9 +1368,9 @@ bool Cursor::erase() } // 'clever' UI hack: only erase large items if previously slected - if (pos() != lastpos() && nextAtom()->nargs() > 0) { + if (pos() != lastpos() && !force && nextAtom()->confirmDeletion()) { resetAnchor(); - setSelection(true); + selection(true); ++pos(); } else { plainErase(); @@ -1680,8 +1430,9 @@ bool Cursor::macroModeClose() InsetMathNest * const in = inset().asInsetMath()->asNestInset(); if (in && in->interpretString(*this, s)) return true; - MathAtom atom = buffer()->getMacro(name, *this, false) ? - MathAtom(new MathMacro(buffer(), name)) : createInsetMath(name, buffer()); + bool const user_macro = buffer()->getMacro(name, *this, false); + MathAtom atom = user_macro ? MathAtom(new MathMacro(buffer(), name)) + : createInsetMath(name, buffer()); // try to put argument into macro, if we just inserted a macro bool macroArg = false; @@ -1702,8 +1453,30 @@ bool Cursor::macroModeClose() // insert remembered selection into first argument of a non-macro else if (atom.nucleus()->nargs() > 0) atom.nucleus()->cell(0).append(selection); - - plainInsert(atom); + + MathWordList const & words = mathedWordList(); + MathWordList::const_iterator it = words.find(name); + bool keep_mathmode = it != words.end() && (it->second.inset == "font" + || it->second.inset == "oldfont" + || it->second.inset == "mbox"); + bool ert_macro = !user_macro && it == words.end() && atomAsMacro; + + if (in && in->currentMode() == Inset::TEXT_MODE + && atom.nucleus()->currentMode() == Inset::MATH_MODE + && name != from_ascii("ensuremath") && !ert_macro) { + MathAtom at(new InsetMathEnsureMath(buffer())); + at.nucleus()->cell(0).push_back(atom); + niceInsert(at); + posForward(); + } else if (in && in->currentMode() == Inset::MATH_MODE + && atom.nucleus()->currentMode() == Inset::TEXT_MODE + && !keep_mathmode) { + MathAtom at = createInsetMath("text", buffer()); + at.nucleus()->cell(0).push_back(atom); + niceInsert(at); + posForward(); + } else + plainInsert(atom); // finally put the macro argument behind, if needed if (macroArg) { @@ -1712,7 +1485,7 @@ bool Cursor::macroModeClose() else insert(selection); } - + return true; } @@ -1823,7 +1596,8 @@ void Cursor::normalize() << pos() << ' ' << lastpos() << " in idx: " << idx() << " in atom: '"; odocstringstream os; - WriteStream wi(os, false, true, WriteStream::wsDefault); + otexrowstream ots(os); + WriteStream wi(ots, false, true, WriteStream::wsDefault); inset().asInsetMath()->write(wi); lyxerr << to_utf8(os.str()) << endl; pos() = lastpos(); @@ -1849,13 +1623,13 @@ bool Cursor::upDownInMath(bool up) else if (inset().asInsetText() && xo - textTargetOffset() != x_target()) { // In text mode inside the line (not left or right) possibly set a new target_x, // but only if we are somewhere else than the previous target-offset. - + // We want to keep the x-target on subsequent up/down movements // that cross beyond the end of short lines. Thus a special // handling when the cursor is at the end of line: Use the new // x-target only if the old one was before the end of line // or the old one was after the beginning of the line - bool inRTL = isWithinRtlParagraph(*this); + bool inRTL = innerParagraph().isRTL(bv().buffer().params()); bool left; bool right; if (inRTL) { @@ -1885,7 +1659,7 @@ bool Cursor::upDownInMath(bool up) push(*const_cast(p)); idx() = p->idxOfScript(up); pos() = lastpos(); - + // we went in the right direction? Otherwise don't jump into the script int x; int y; @@ -1898,7 +1672,7 @@ bool Cursor::upDownInMath(bool up) return true; } } - + // try right if (pos() != lastpos()) { InsetMathScript const * p = nextAtom()->asScriptInset(); @@ -1906,7 +1680,7 @@ bool Cursor::upDownInMath(bool up) push(*const_cast(p)); idx() = p->idxOfScript(up); pos() = 0; - + // we went in the right direction? Otherwise don't jump into the script int x; int y; @@ -1920,16 +1694,16 @@ bool Cursor::upDownInMath(bool up) } } } - + // try to find an inset that knows better then we, if (inset().idxUpDown(*this, up)) { //lyxerr << "idxUpDown triggered" << endl; // try to find best position within this inset if (!selection()) - setCursor(bruteFind2(*this, xo, yo)); + setCursor(bruteFind(*this, xo, yo)); return true; } - + // any improvement going just out of inset? if (popBackward() && inMathed()) { //lyxerr << "updown: popBackward succeeded" << endl; @@ -1940,24 +1714,113 @@ bool Cursor::upDownInMath(bool up) if (up ? ynew < yold : ynew > yold) return true; } - + // no success, we are probably at the document top or bottom operator=(old); return false; } +InsetMath & Cursor::nextMath() +{ + return *nextAtom().nucleus(); +} + + +InsetMath & Cursor::prevMath() +{ + return *prevAtom().nucleus(); +} + + +bool Cursor::mathForward(bool word) +{ + LASSERT(inMathed(), return false); + if (pos() < lastpos()) { + if (word) { + // word: skip a group of insets of the form X*(B*|R*|P*) (greedy + // match) where X is any math class, B is mathbin, R is mathrel, and + // P is mathpunct. Make sure that the following remains true: + // mathForward(true); mathBackward(true); mathForward(true) + // is the same as mathForward(true) and + // mathBackward(true); mathForward(true); mathBackward(true) + // is the same as mathBackward(true). + MathClass mc = nextMath().mathClass(); + do + posForward(); + while (pos() < lastpos() && mc == nextMath().mathClass()); + if (pos() < lastpos() && + ((mc = nextMath().mathClass()) == MC_BIN || + mc == MC_REL || mc == MC_PUNCT)) + do + posForward(); + while (pos() < lastpos() && mc == nextMath().mathClass()); + } else if (openable(nextAtom())) { + // single step: try to enter the next inset + pushBackward(nextMath()); + inset().idxFirst(*this); + } else + posForward(); + return true; + } + if (inset().idxForward(*this)) + return true; + // try to pop forwards --- but don't pop out of math! leave that to + // the FINISH lfuns + int s = depth() - 2; + if (s >= 0 && operator[](s).inset().asInsetMath()) + return popForward(); + return false; +} + + +bool Cursor::mathBackward(bool word) +{ + LASSERT(inMathed(), return false); + if (pos() > 0) { + if (word) { + // word: skip a group of insets. See the comment in mathForward. + MathClass mc = prevMath().mathClass(); + do + posBackward(); + while (pos() > 0 && mc == prevMath().mathClass()); + if (pos() > 0 && (mc == MC_BIN || mc == MC_REL || mc == MC_PUNCT)) { + mc = prevMath().mathClass(); + do + posBackward(); + while (pos() > 0 && mc == prevMath().mathClass()); + } + } else if (openable(prevAtom())) { + // single step: try to enter the preceding inset + posBackward(); + push(nextMath()); + inset().idxLast(*this); + } else + posBackward(); + return true; + } + if (inset().idxBackward(*this)) + return true; + // try to pop backwards --- but don't pop out of math! leave that to + // the FINISH lfuns + int s = depth() - 2; + if (s >= 0 && operator[](s).inset().asInsetMath()) + return popBackward(); + return false; +} + + bool Cursor::atFirstOrLastRow(bool up) { TextMetrics const & tm = bv_->textMetrics(text()); ParagraphMetrics const & pm = tm.parMetrics(pit()); - + int row; if (pos() && boundary()) row = pm.pos2row(pos() - 1); else row = pm.pos2row(pos()); - + if (up) { if (pit() == 0 && row == 0) return true; @@ -1990,14 +1853,14 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) // In text mode inside the line (not left or right) // possibly set a new target_x, but only if we are // somewhere else than the previous target-offset. - + // We want to keep the x-target on subsequent up/down // movements that cross beyond the end of short lines. // Thus a special handling when the cursor is at the // end of line: Use the new x-target only if the old // one was before the end of line or the old one was // after the beginning of the line - bool inRTL = isWithinRtlParagraph(*this); + bool inRTL = innerParagraph().isRTL(bv().buffer().params()); bool left; bool right; if (inRTL) { @@ -2015,7 +1878,7 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) xo = targetX(); } else xo = targetX(); - + // first get the current line TextMetrics & tm = bv_->textMetrics(text()); ParagraphMetrics const & pm = tm.parMetrics(pit()); @@ -2024,7 +1887,7 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) row = pm.pos2row(pos() - 1); else row = pm.pos2row(pos()); - + if (atFirstOrLastRow(up)) { // Is there a place for the cursor to go ? If yes, we // can execute the DEPM, otherwise we should keep the @@ -2037,11 +1900,11 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) break; } - // will a next dispatch follow and if there is a new + // will a next dispatch follow and if there is a new // dispatch will it move the cursor out ? if (depth() > 1 && valid_destination) { // The cursor hasn't changed yet. This happens when - // you e.g. move out of an inset. And to give the + // you e.g. move out of an inset. And to give the // DEPM the possibility of doing something we must // provide it with two different cursors. (Lgb, vfr) dummy = *this; @@ -2050,12 +1913,8 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) updateNeeded |= bv().checkDepm(dummy, *this); updateTextTargetOffset(); - if (updateNeeded) { + if (updateNeeded) forceBufferUpdate(); - // DEPM may have requested a screen update - this->screenUpdateFlags( - this->screenUpdate() | dummy.screenUpdate()); - } } return false; } @@ -2065,25 +1924,29 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) int yo = bv().getPos(*this).y_; Cursor old = *this; // To next/previous row + // FIXME: the y position is often guessed wrongly across styles and + // insets, which leads to weird behaviour. if (up) tm.editXY(*this, xo, yo - textRow().ascent() - 1); else tm.editXY(*this, xo, yo + textRow().descent() + 1); + x_target_ = old.x_target_; clearSelection(); - - // This happens when you move out of an inset. - // And to give the DEPM the possibility of doing - // something we must provide it with two different - // cursors. (Lgb) + + // This happens when you move out of an inset. + // And to give the DEPM the possibility of doing + // something we must provide it with two different + // cursors. (Lgb) Cursor dummy = *this; if (dummy == old) ++dummy.pos(); if (bv().checkDepm(dummy, old)) { updateNeeded = true; // Make sure that cur gets back whatever happened to dummy (Lgb) - // This will include any screen update requested by DEPM operator=(dummy); } + if (inTexted() && pos() && paragraph().isEnvSeparator(pos() - 1)) + posBackward(); } else { // if there is a selection, we stay out of any inset, // and just jump to the right position: @@ -2111,17 +1974,11 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) next_row = 0; } } - top().pos() = min(tm.x2pos(pit(), next_row, xo), top().lastpos()); - - int const xpos = tm.x2pos(pit(), next_row, xo); - bool const at_end_row = xpos == tm.x2pos(pit(), next_row, tm.width()); - bool const at_beg_row = xpos == tm.x2pos(pit(), next_row, 0); - if (at_end_row && at_beg_row) - // make sure the cursor ends up on this row - boundary(false); - else - boundary(at_end_row); + Row const & real_next_row = tm.parMetrics(pit()).rows()[next_row]; + bool bound = false; + top().pos() = tm.getPosNearX(real_next_row, xo, bound); + boundary(bound); updateNeeded |= bv().checkDepm(*this, old); } @@ -2130,7 +1987,7 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) forceBufferUpdate(); updateTextTargetOffset(); return true; -} +} void Cursor::handleFont(string const & font) @@ -2191,7 +2048,7 @@ docstring parbreak(Cursor const * cur) odocstringstream os; os << '\n'; // only add blank line if we're not in a ParbreakIsNewline situation - if (!cur->inset().getLayout().parbreakIsNewline() + if (!cur->inset().getLayout().parbreakIsNewline() && !cur->paragraph().layout().parbreak_is_newline) os << '\n'; return os.str(); @@ -2254,6 +2111,13 @@ docstring Cursor::currentState() const if (inMathed()) { odocstringstream os; info(os); +#ifdef DEVEL_VERSION + InsetMath * math = inset().asInsetMath(); + if (math) + os << _(", Inset: ") << math->id(); + os << _(", Cell: ") << idx(); + os << _(", Position: ") << pos(); +#endif return os.str(); } @@ -2274,10 +2138,14 @@ Encoding const * Cursor::getEncoding() const { if (empty()) return 0; + BufferParams const & bp = bv().buffer().params(); + if (bp.useNonTeXFonts) + return encodings.fromLyXName("utf8-plain"); + CursorSlice const & sl = innerTextSlice(); Text const & text = *sl.text(); - Font font = text.getPar(sl.pit()).getFont( - bv().buffer().params(), sl.pos(), text.outerFont(sl.pit())); + Font font = text.getPar(sl.pit()).getFont(bp, sl.pos(), + text.outerFont(sl.pit())); return font.language()->encoding(); } @@ -2329,27 +2197,28 @@ Font Cursor::getFont() const // The logic here should more or less match to the // Cursor::setCurrentFont logic, i.e. the cursor height should // give a hint what will happen if a character is entered. - + // FIXME: this is not the case, what about removing this method ? (see #10478). + // HACK. far from being perfect... CursorSlice const & sl = innerTextSlice(); Text const & text = *sl.text(); Paragraph const & par = text.getPar(sl.pit()); - + // on boundary, so we are really at the character before pos_type pos = sl.pos(); if (pos > 0 && boundary()) --pos; - + // on space? Take the font before (only for RTL boundary stay) if (pos > 0) { TextMetrics const & tm = bv().textMetrics(&text); if (pos == sl.lastpos() - || (par.isSeparator(pos) + || (par.isSeparator(pos) && !tm.isRTLBoundary(sl.pit(), pos))) --pos; } - + // get font at the position Font font = par.getFont(buffer()->params(), pos, text.outerFont(sl.pit())); @@ -2362,7 +2231,7 @@ bool Cursor::fixIfBroken() { bool const broken_cursor = DocIterator::fixIfBroken(); bool const broken_anchor = anchor_.fixIfBroken(); - + if (broken_cursor || broken_anchor) { clearNewWordPosition(); clearSelection(); @@ -2376,7 +2245,11 @@ void Cursor::sanitize() { setBuffer(&bv_->buffer()); DocIterator::sanitize(); - anchor_.sanitize(); + new_word_.sanitize(); + if (selection()) + anchor_.sanitize(); + else + resetAnchor(); } @@ -2412,7 +2285,7 @@ bool notifyCursorLeavesOrEnters(Cursor const & old, Cursor & cur) if (cur[i].inset().notifyCursorEnters(cur)) return true; } - + return false; } @@ -2489,7 +2362,7 @@ void Cursor::finishUndo() const void Cursor::beginUndoGroup() const { - buffer()->undo().beginUndoGroup(); + buffer()->undo().beginUndoGroup(*this); } @@ -2499,15 +2372,15 @@ void Cursor::endUndoGroup() const } -void Cursor::recordUndo(UndoKind kind, pit_type from, pit_type to) const +void Cursor::recordUndo(pit_type from, pit_type to) const { - buffer()->undo().recordUndo(*this, kind, from, to); + buffer()->undo().recordUndo(*this, from, to); } -void Cursor::recordUndo(UndoKind kind, pit_type from) const +void Cursor::recordUndo(pit_type from) const { - buffer()->undo().recordUndo(*this, kind, from); + buffer()->undo().recordUndo(*this, from, pit()); } @@ -2517,15 +2390,21 @@ void Cursor::recordUndo(UndoKind kind) const } -void Cursor::recordUndoInset(UndoKind kind, Inset const * inset) const +void Cursor::recordUndoInset(Inset const * in) const +{ + buffer()->undo().recordUndoInset(*this, in); +} + + +void Cursor::recordUndoFullBuffer() const { - buffer()->undo().recordUndoInset(*this, kind, inset); + buffer()->undo().recordUndoFullBuffer(*this); } -void Cursor::recordUndoFullDocument() const +void Cursor::recordUndoBufferParams() const { - buffer()->undo().recordUndoFullDocument(*this); + buffer()->undo().recordUndoBufferParams(*this); } @@ -2537,7 +2416,7 @@ void Cursor::recordUndoSelection() const else recordUndo(); } else { - buffer()->undo().recordUndo(*this, ATOMIC_UNDO, + buffer()->undo().recordUndo(*this, selBegin().pit(), selEnd().pit()); } } @@ -2548,9 +2427,50 @@ void Cursor::checkBufferStructure() Buffer const * master = buffer()->masterBuffer(); master->tocBackend().updateItem(*this); if (master != buffer() && !master->hasGuiDelegate()) - // In case the master has no gui associated with it, + // In case the master has no gui associated with it, // the TocItem is not updated (part of bug 5699). buffer()->tocBackend().updateItem(*this); + + // If the last tracked change of the paragraph has just been + // deleted, then we need to recompute the buffer flag + // tracked_changes_present_. + if (inTexted() && paragraph().isChangeUpdateRequired()) + disp_.forceChangesUpdate(); +} + + +bool Cursor::confirmDeletion(bool const before) const +{ + if (!selection()) { + if (Inset const * inset = before ? prevInset() : nextInset()) + return inset->confirmDeletion(); + } else { + DocIterator dit = selectionBegin(); + CursorSlice const end = selectionEnd().top(); + for (; dit.top() < end; dit.top().forwardPos()) + if (Inset const * inset = dit.nextInset()) + if (inset->confirmDeletion()) + return true; + } + return false; +} + + +void Cursor::moveToClosestEdge(int const x, bool const edit) +{ + if (Inset const * inset = nextInset()) { + // stay in front of insets for which we want to open the dialog + // (e.g. InsetMathSpace). + if (edit && (inset->hasSettings() || !inset->contextMenuName().empty())) + return; + CoordCache::Insets const & insetCache = bv().coordCache().getInsets(); + if (!insetCache.has(inset)) + return; + int const wid = insetCache.dim(inset).wid; + Point p = insetCache.xy(inset); + if (x > p.x_ + (wid + 1) / 2) + posForward(); + } }