X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCursor.cpp;h=bc1219733f99a71d851d34ef5c6736336ca749f0;hb=bbb5a1a28f047e76611641efe999739d537b27f4;hp=c12d054ee79019c4bc73042cc20fc21d9cc40ee5;hpb=9e423f23fa7b3eb3efc8267acac1edbc96f6c5fe;p=lyx.git diff --git a/src/Cursor.cpp b/src/Cursor.cpp index c12d054ee7..bc1219733f 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -14,7 +14,6 @@ #include -#include "Bidi.h" #include "Buffer.h" #include "BufferView.h" #include "CoordCache.h" @@ -26,8 +25,8 @@ #include "FuncCode.h" #include "FuncRequest.h" #include "Language.h" +#include "Layout.h" #include "LyXAction.h" -#include "LyXFunc.h" // only for setMessage() #include "LyXRC.h" #include "Paragraph.h" #include "ParIterator.h" @@ -36,9 +35,11 @@ #include "TextMetrics.h" #include "TocBackend.h" -#include "support/lassert.h" #include "support/debug.h" #include "support/docstream.h" +#include "support/ExceptionMessage.h" +#include "support/gettext.h" +#include "support/lassert.h" #include "insets/InsetTabular.h" #include "insets/InsetText.h" @@ -50,11 +51,12 @@ #include "mathed/MathData.h" #include "mathed/MathMacro.h" -#include +#include "support/bind.h" #include #include #include +#include using namespace std; @@ -62,22 +64,6 @@ 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) @@ -94,20 +80,18 @@ DocIterator bruteFind2(Cursor const & c, int x, int y) int xo; int yo; Inset const * inset = &it.inset(); - map const & data = - c.bv().coordCache().getInsets().getData(); - map::const_iterator I = data.find(inset); + CoordCache const & cache = c.bv().coordCache(); // 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 (I == data.end()) { + if (!cache.getInsets().has(inset)) { it.top().pos() = 0; return it; } - Point o = I->second.pos; + Point const o = cache.getInsets().xy(inset); inset->cursorPos(c.bv(), it.top(), c.boundary(), xo, yo); // Convert to absolute xo += o.x_; @@ -147,13 +131,13 @@ bool bruteFind(Cursor & cursor, // 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; + et.pit() = prev(cache.end(), 1)->first; if (et.pit() >= et.lastpit()) et = doc_iterator_end(inset); else ++et.pit(); - double best_dist = numeric_limits::max();; + double best_dist = numeric_limits::max(); DocIterator best_cursor = et; for ( ; it != et; it.forwardPos(true)) { @@ -186,7 +170,7 @@ bool bruteFind(Cursor & cursor, } */ - +/* /// moves position closest to (x, y) in given box bool bruteFind3(Cursor & cur, int x, int y, bool up) { @@ -215,8 +199,11 @@ bool bruteFind3(Cursor & cur, int x, int y, bool up) for ( ; it != et; it.forwardPos()) { // avoid invalid nesting when selecting if (bv.cursorStatus(it) == CUR_INSIDE - && (!cur.selection() || positionable(it, cur.anchor_))) { - Point p = bv.getPos(it, false); + && (!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) { @@ -245,17 +232,70 @@ bool bruteFind3(Cursor & cur, int x, int y, bool up) 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), + autocorrect_(false), macromode_(false) +{} + + +CursorData::CursorData(Buffer * buffer) + : DocIterator(buffer), anchor_(), + selection_(false), mark_(false), word_selection_(false), + logicalpos_(false), 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), + 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) - : DocIterator(&bv.buffer()), bv_(&bv), anchor_(), + : CursorData(&bv.buffer()), bv_(&bv), x_target_(-1), textTargetOffset_(0), - selection_(false), mark_(false), logicalpos_(false), - current_font(inherit_font) + beforeDispatchPosX_(0), beforeDispatchPosY_(0) {} @@ -265,6 +305,8 @@ void Cursor::reset() push_back(CursorSlice(buffer()->inset())); anchor_ = doc_iterator_begin(buffer()); anchor_.clear(); + new_word_ = doc_iterator_begin(buffer()); + new_word_.clear(); clearTargetX(); selection_ = false; mark_ = false; @@ -278,6 +320,25 @@ void Cursor::setCursor(DocIterator const & cur) } +void Cursor::setCursorToAnchor() +{ + if (selection()) { + DocIterator normal = anchor_; + while (depth() < normal.depth()) + normal.pop_back(); + if (depth() < anchor_.depth() && top() <= anchor_[depth() - 1]) + ++normal.pos(); + setCursor(normal); + } +} + + +void Cursor::setCursorData(CursorData const & data) +{ + CursorData::operator=(data); +} + + bool Cursor::getStatus(FuncRequest const & cmd, FuncStatus & status) const { Cursor cur = *this; @@ -287,7 +348,7 @@ bool Cursor::getStatus(FuncRequest const & cmd, FuncStatus & status) const // Is this a function that acts on inset at point? Inset * inset = cur.nextInset(); - if (lyxaction.funcHasFlag(cmd.action, LyXAction::AtPoint) + if (lyxaction.funcHasFlag(cmd.action(), LyXAction::AtPoint) && inset && inset->getStatus(cur, cmd, status)) return true; @@ -298,6 +359,7 @@ bool Cursor::getStatus(FuncRequest const & cmd, FuncStatus & status) const bool res = false; for ( ; cur.depth(); cur.pop()) { //lyxerr << "\nCursor::getStatus: cmd: " << cmd << endl << *this << endl; + // LASSERT: Is it safe to continue here, or should we return? LASSERT(cur.idx() <= cur.lastidx(), /**/); LASSERT(cur.pit() <= cur.lastpit(), /**/); LASSERT(cur.pos() <= cur.lastpos(), /**/); @@ -315,29 +377,39 @@ bool Cursor::getStatus(FuncRequest const & cmd, FuncStatus & status) const } +void Cursor::saveBeforeDispatchPosXY() +{ + getPos(beforeDispatchPosX_, beforeDispatchPosY_); +} + + 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; fixIfBroken(); FuncRequest cmd = cmd0; Cursor safe = *this; + Cursor old = *this; + disp_ = DispatchResult(); + + beginUndoGroup(); - buffer()->undo().beginUndoGroup(); - // Is this a function that acts on inset at point? - if (lyxaction.funcHasFlag(cmd.action, LyXAction::AtPoint) + if (lyxaction.funcHasFlag(cmd.action(), LyXAction::AtPoint) && nextInset()) { - result().dispatched(true); - result().update(Update::FitCursor | Update::Force); + disp_.dispatched(true); + disp_.screenUpdate(Update::FitCursor | Update::Force); FuncRequest tmpcmd = cmd; LYXERR(Debug::DEBUG, "Cursor::dispatch: (AtPoint) cmd: " << cmd0 << endl << *this); nextInset()->dispatch(*this, tmpcmd); - if (result().dispatched()) + if (disp_.dispatched()) { + endUndoGroup(); return; + } } // store some values to be used inside of the handlers @@ -345,20 +417,23 @@ void Cursor::dispatch(FuncRequest const & cmd0) for (; depth(); pop(), boundary(false)) { LYXERR(Debug::DEBUG, "Cursor::dispatch: cmd: " << cmd0 << endl << *this); - 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. // The handler can reset the update and val flags if necessary. - disp_.update(Update::FitCursor | Update::Force); + disp_.screenUpdate(Update::FitCursor | Update::Force); disp_.dispatched(true); inset().dispatch(*this, cmd); if (disp_.dispatched()) break; } - + // it completely to get a 'bomb early' behaviour in case this // object will be used again. if (!disp_.dispatched()) { @@ -366,25 +441,40 @@ 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(); safe.pos() = safe.lastpos(); } operator=(safe); - disp_.update(Update::None); + disp_.screenUpdate(Update::None); disp_.dispatched(false); } else { // restore the previous one because nested Cursor::dispatch calls // are possible which would change it beforeDispatchCursor_ = safe.beforeDispatchCursor_; } - buffer()->undo().endUndoGroup(); + 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(); + old.fixIfBroken(); + bool badcursor = notifyCursorLeavesOrEnters(old, *this); + if (badcursor) { + fixIfBroken(); + bv().resetInlineCompletionPos(); + } + old.endUndoGroup(); + } } -DispatchResult Cursor::result() const +DispatchResult const & Cursor::result() const { return disp_; } @@ -392,14 +482,14 @@ DispatchResult Cursor::result() const BufferView & Cursor::bv() const { - LASSERT(bv_, /**/); + LBUFERR(bv_); return *bv_; } void Cursor::pop() { - LASSERT(depth() >= 1, /**/); + LBUFERR(depth() >= 1); pop_back(); } @@ -445,7 +535,7 @@ bool Cursor::popForward() int Cursor::currentMode() { - LASSERT(!empty(), /**/); + LASSERT(!empty(), return Inset::UNDECIDED_MODE); for (int i = depth() - 1; i >= 0; --i) { int res = operator[](i).inset().currentMode(); bool locked_mode = operator[](i).inset().lockedMode(); @@ -458,9 +548,19 @@ int Cursor::currentMode() } +bool Cursor::inCoordCache() const +{ + CoordCache::Insets const & icache = bv_->coordCache().getInsets(); + for (size_t i = 0 ; 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, boundary()); + Point p = bv().getPos(*this); x = p.x_; y = p.y_; } @@ -470,7 +570,6 @@ Row const & Cursor::textRow() const { CursorSlice const & cs = innerTextSlice(); ParagraphMetrics const & pm = bv().parMetrics(cs.text(), cs.pit()); - LASSERT(!pm.rows().empty(), /**/); return pm.getRow(pos(), boundary()); } @@ -478,13 +577,68 @@ Row const & Cursor::textRow() const void Cursor::resetAnchor() { anchor_ = *this; + checkNewWordPosition(); } -void Cursor::setCursorToAnchor() +void Cursor::markNewWordPosition() { - if (selection()) - setCursor(anchor_); + if (lyxrc.spellcheck_continuously && inTexted() && new_word_.empty()) { + FontSpan nw = locateWord(WHOLE_WORD); + if (nw.size() == 1) { + LYXERR(Debug::DEBUG, "start new word: " + << " par: " << pit() + << " pos: " << nw.first); + new_word_ = *this; + } + } +} + + +void Cursor::clearNewWordPosition() +{ + if (!new_word_.empty()) { + LYXERR(Debug::DEBUG, "clear new word: " + << " par: " << pit() + << " pos: " << pos()); + new_word_.resize(0); + } +} + + +void Cursor::checkNewWordPosition() +{ + if (!lyxrc.spellcheck_continuously || new_word_.empty()) + return ; + if (!inTexted()) + clearNewWordPosition(); + else { + // forget the position of the current new word if + // 1) the paragraph changes or + // 2) the count of nested insets changes or + // 3) the cursor pos is out of paragraph bound + if (pit() != new_word_.pit() || + depth() != new_word_.depth() || + new_word_.pos() > new_word_.lastpos()) { + clearNewWordPosition(); + } else if (new_word_.fixIfBroken()) + // 4) or the remembered position was "broken" + clearNewWordPosition(); + else { + FontSpan nw = locateWord(WHOLE_WORD); + if (!nw.empty()) { + FontSpan ow = new_word_.locateWord(WHOLE_WORD); + if (nw.intersect(ow).empty()) + clearNewWordPosition(); + else + LYXERR(Debug::DEBUG, "new word: " + << " par: " << pit() + << " pos: " << nw.first << ".." << nw.last); + } else { + clearNewWordPosition(); + } + } + } } @@ -511,7 +665,6 @@ 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); @@ -522,51 +675,58 @@ bool Cursor::posVisRight(bool skip_inset) new_cur.boundary(false); if (!skip_inset && text()->checkAndActivateInsetVisual(new_cur, right_pos >= pos(), false)) { - // we actually move the cursor at the end of this function, for now - // we just keep track of the new position in new_cur... + // we actually move the cursor at the end of this + // function, for now we just keep track of the new + // position in new_cur... LYXERR(Debug::RTL, "entering inset at: " << new_cur.pos()); } // 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()) << "," + + // 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()) << "," << int(new_cur.pos()) << "," << (new_cur.boundary() ? 1 : 0)); } // normal movement to the right else { new_cur = *this; - // Recall, if the cursor is at position 'x', that means *before* - // the character at position 'x'. In RTL, "before" means "to the - // right of", in LTR, "to the left of". So currently our situation - // is this: the position to our right is 'right_pos' (i.e., 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( + // Recall, if the cursor is at position 'x', that + // means *before* the character at position 'x'. In + // RTL, "before" means "to the right of", in LTR, "to + // the left of". So currently our situation is this: + // the position to our right is 'right_pos' (i.e., + // 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. + 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 *after* 'right_pos', i.e., move to - // position 'right_pos' + 1. + // If the character at 'right_pos' *is* LTR, then in + // order to move to the right of it, we need to be + // *after* 'right_pos', i.e., move to position + // 'right_pos' + 1. if (!new_pos_is_RTL) { new_cur.pos() = right_pos + 1; // set the boundary to true in two situations: if ( - // 1. if new_pos is now lastpos, and we're in an RTL paragraph - // (this means that we're moving right to the end of an LTR chunk + // 1. if new_pos is now lastpos, and we're in + // an RTL paragraph (this means that we're + // moving right to the end of an LTR chunk // which is at the end of an RTL paragraph); (new_cur.pos() == lastpos() && paragraph().isRTL(buffer()->params())) - // 2. if the position *after* right_pos is RTL (we want to be - // *after* right_pos, not before right_pos + 1!) + // 2. if the position *after* right_pos is RTL + // (we want to be *after* right_pos, not + // before right_pos + 1!) || paragraph().getFontSettings(buffer()->params(), new_cur.pos()).isVisibleRightToLeft() ) @@ -574,23 +734,20 @@ bool Cursor::posVisRight(bool skip_inset) else // set the boundary to false new_cur.boundary(false); } - // Otherwise (if the character at position 'right_pos' is RTL), then - // moving to the right of it is as easy as setting the new position - // to 'right_pos'. + // Otherwise (if the character at position 'right_pos' + // is RTL), then moving to the right of it is as easy + // as setting the new position to 'right_pos'. else { 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; } @@ -604,7 +761,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); @@ -613,53 +769,60 @@ 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 position in new_cur... + // we actually move the cursor at the end of this + // function, for now we just keep track of the new + // position in new_cur... LYXERR(Debug::RTL, "entering inset at: " << new_cur.pos()); } // 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()) << "," + + // 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()) << "," << int(new_cur.pos()) << "," << (new_cur.boundary() ? 1 : 0)); } // normal movement to the left else { new_cur = *this; - // Recall, if the cursor is at position 'x', that means *before* - // the character at position 'x'. In RTL, "before" means "to the - // right of", in LTR, "to the left of". So currently our situation - // is this: the position to our left is 'left_pos' (i.e., we're - // 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( + // Recall, if the cursor is at position 'x', that + // means *before* the character at position 'x'. In + // RTL, "before" means "to the right of", in LTR, "to + // the left of". So currently our situation is this: + // the position to our left is 'left_pos' (i.e., we're + // 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. + 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 *after* 'left_pos', i.e., move to - // position 'left_pos' + 1. + // If the character at 'left_pos' *is* RTL, then in + // order to move to the left of it, we need to be + // *after* 'left_pos', i.e., move to position + // 'left_pos' + 1. if (new_pos_is_RTL) { new_cur.pos() = left_pos + 1; // set the boundary to true in two situations: if ( - // 1. if new_pos is now lastpos and we're in an LTR paragraph - // (this means that we're moving left to the end of an RTL chunk + // 1. if new_pos is now lastpos and we're in + // an LTR paragraph (this means that we're + // moving left to the end of an RTL chunk // which is at the end of an LTR paragraph); (new_cur.pos() == lastpos() && !paragraph().isRTL(buffer()->params())) - // 2. if the position *after* left_pos is not RTL (we want to be - // *after* left_pos, not before left_pos + 1!) + // 2. if the position *after* left_pos is not + // RTL (we want to be *after* left_pos, not + // before left_pos + 1!) || !paragraph().getFontSettings(buffer()->params(), new_cur.pos()).isVisibleRightToLeft() ) @@ -667,134 +830,126 @@ bool Cursor::posVisLeft(bool skip_inset) else // set the boundary to false new_cur.boundary(false); } - // Otherwise (if the character at position 'left_pos' is LTR), then - // moving to the left of it is as easy as setting the new position - // to 'left_pos'. + // Otherwise (if the character at position 'left_pos' + // is LTR), then moving to the left of it is as easy + // as setting the new position to 'left_pos'. else { 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); - } + TextMetrics const & tm = bv_->textMetrics(text()); + double dummy = 0; + Row::const_iterator cit = tm.findRowElement(row, pos(), boundary(), dummy); + // Handle the case of empty row + if (cit == row.end()) { + if (paragraph().isRTL(buffer()->params())) + 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 <<")"); } @@ -805,16 +960,15 @@ bool Cursor::posVisToNewRow(bool movingLeft) Row const & row = textRow(); bool par_is_LTR = !par.isRTL(buf.params()); - // 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); + // Inside a table, determining whether to move to the next or + // previous row should be done based on the table's direction. + 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 moving left in an LTR paragraph or moving right in an + // RTL one, move to previous row if (par_is_LTR == movingLeft) { if (row.pos() == 0) { // we're at first row in paragraph if (pit() == 0) // no previous paragraph! don't move @@ -828,8 +982,8 @@ bool Cursor::posVisToNewRow(bool movingLeft) boundary(false); } } - // if moving left in an RTL paragraph or moving right in an LTR one, - // move to next row + // if moving left in an RTL paragraph or moving right in an + // LTR one, move to next row else { if (row.endpos() == lastpos()) { // we're at last row in paragraph if (pit() == lastpit()) // last paragraph! don't move @@ -843,7 +997,7 @@ bool Cursor::posVisToNewRow(bool movingLeft) boundary(false); } } - + // make sure we're at left-/right-most pos in new row posVisToRowExtremity(!movingLeft); @@ -851,127 +1005,47 @@ 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)); } -CursorSlice Cursor::anchor() const +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(); - LASSERT(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(anchor_.depth() >= depth(), + const_cast(anchor_) = *this); + CursorSlice normal = anchor_[depth() - 1]; if (depth() < anchor_.depth() && top() <= normal) { // anchor is behind cursor -> move anchor behind the inset @@ -981,11 +1055,17 @@ CursorSlice Cursor::anchor() const } +DocIterator & Cursor::realAnchor() +{ + return anchor_; +} + + CursorSlice Cursor::selBegin() const { if (!selection()) return top(); - return anchor() < top() ? anchor() : top(); + return normalAnchor() < top() ? normalAnchor() : top(); } @@ -993,7 +1073,7 @@ CursorSlice Cursor::selEnd() const { if (!selection()) return top(); - return anchor() > top() ? anchor() : top(); + return normalAnchor() > top() ? normalAnchor() : top(); } @@ -1005,10 +1085,10 @@ DocIterator Cursor::selectionBegin() const DocIterator di; // FIXME: This is a work-around for the problem that // CursorSlice doesn't keep track of the boundary. - if (anchor() == top()) + if (normalAnchor() == top()) di = anchor_.boundary() > boundary() ? anchor_ : *this; else - di = anchor() < top() ? anchor_ : *this; + di = normalAnchor() < top() ? anchor_ : *this; di.resize(depth()); return di; } @@ -1022,10 +1102,10 @@ DocIterator Cursor::selectionEnd() const DocIterator di; // FIXME: This is a work-around for the problem that // CursorSlice doesn't keep track of the boundary. - if (anchor() == top()) + if (normalAnchor() == top()) di = anchor_.boundary() < boundary() ? anchor_ : *this; else - di = anchor() > top() ? anchor_ : *this; + di = normalAnchor() > top() ? anchor_ : *this; if (di.depth() > depth()) { di.resize(depth()); @@ -1038,11 +1118,9 @@ DocIterator Cursor::selectionEnd() const void Cursor::setSelection() { setSelection(true); - // A selection with no contents is not a selection - // FIXME: doesnt look ok - if (idx() == anchor().idx() && - pit() == anchor().pit() && - pos() == anchor().pos()) + if (idx() == normalAnchor().idx() && + pit() == normalAnchor().pit() && + pos() == normalAnchor().pos()) setSelection(false); } @@ -1059,6 +1137,7 @@ void Cursor::setSelection(DocIterator const & where, int n) void Cursor::clearSelection() { setSelection(false); + setWordSelection(false); setMark(false); resetAnchor(); } @@ -1105,8 +1184,6 @@ void Cursor::info(odocstream & os) const if (inset) prevInset()->infoize2(os); } - // overwite old message - os << " "; } @@ -1125,35 +1202,6 @@ bool Cursor::selHandle(bool sel) setSelection(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 @@ -1177,9 +1225,6 @@ LyXErr & operator<<(LyXErr & os, Cursor const & cur) namespace lyx { -//#define FILEDEBUG 1 - - bool Cursor::isInside(Inset const * p) const { for (size_t i = 0; i != depth(); ++i) @@ -1253,13 +1298,14 @@ void Cursor::plainInsert(MathAtom const & t) ++pos(); inset().setBuffer(bv_->buffer()); inset().initView(); + checkBufferStructure(); } void Cursor::insert(docstring const & str) { for_each(str.begin(), str.end(), - boost::bind(static_cast + bind(static_cast (&Cursor::insert), this, _1)); } @@ -1267,7 +1313,7 @@ void Cursor::insert(docstring const & str) void Cursor::insert(char_type c) { //lyxerr << "Cursor::insert char '" << c << "'" << endl; - LASSERT(!empty(), /**/); + LASSERT(!empty(), return); if (inMathed()) { cap::selClearOrDel(*this); insert(new InsetMathChar(c)); @@ -1288,18 +1334,20 @@ void Cursor::insert(MathAtom const & t) void Cursor::insert(Inset * inset0) { - LASSERT(inset0, /**/); + LASSERT(inset0, return); if (inMathed()) - insert(MathAtom(inset0)); + insert(MathAtom(inset0->asInsetMath())); else { text()->insertInset(*this, inset0); inset0->setBuffer(bv_->buffer()); inset0->initView(); + if (inset0->isLabeled()) + forceBufferUpdate(); } } -void Cursor::niceInsert(docstring const & t, Parse::flags f, bool enter) +int Cursor::niceInsert(docstring const & t, Parse::flags f, bool enter) { MathData ar(buffer()); asArray(t, ar, f); @@ -1307,6 +1355,7 @@ void Cursor::niceInsert(docstring const & t, Parse::flags f, bool enter) niceInsert(ar[0]); else insert(ar); + return ar.size(); } @@ -1325,6 +1374,15 @@ void Cursor::niceInsert(MathAtom const & t) MathData ar(buffer()); asArray(safe, ar); insert(ar); + } else if (t->asMacro() && !safe.empty()) { + MathData ar(buffer()); + asArray(safe, ar); + docstring const name = t->asMacro()->name(); + MacroData const * data = buffer()->getMacro(name); + if (data && data->numargs() - data->optionals() > 0) { + plainInsert(MathAtom(new InsetMathBrace(ar))); + posBackward(); + } } } @@ -1336,13 +1394,13 @@ void Cursor::insert(MathData const & ar) cap::eraseSelection(*this); cell().insert(pos(), ar); pos() += ar.size(); + // FIXME audit setBuffer calls + inset().setBuffer(bv_->buffer()); } bool Cursor::backspace() { - autocorrect() = false; - if (selection()) { cap::eraseSelection(*this); return true; @@ -1393,7 +1451,6 @@ bool Cursor::backspace() bool Cursor::erase() { - autocorrect() = false; if (inMacroMode()) return true; @@ -1448,7 +1505,6 @@ bool Cursor::up() if (disp_.dispatched()) return true; setCursor(save); - autocorrect() = false; return false; } @@ -1462,7 +1518,6 @@ bool Cursor::down() if (disp_.dispatched()) return true; setCursor(save); - autocorrect() = false; return false; } @@ -1485,7 +1540,7 @@ bool Cursor::macroModeClose() // trigger updates of macros, at least, if no full // updates take place anyway - updateFlags(Update::Force); + screenUpdateFlags(Update::Force); docstring const name = s.substr(1); InsetMathNest * const in = inset().asInsetMath()->asNestInset(); @@ -1502,7 +1557,7 @@ bool Cursor::macroModeClose() // we have to resolve the macro here manually and check its arity // to put the selection behind it if arity > 0. MacroData const * data = buffer()->getMacro(atomAsMacro->name()); - if (selection.size() > 0 && data && data->numargs() - data->optionals() > 0) { + if (!selection.empty() && data && data->numargs() - data->optionals() > 0) { macroArg = true; atomAsMacro->setDisplayMode(MathMacro::DISPLAY_INTERACTIVE_INIT, 1); } else @@ -1513,7 +1568,7 @@ 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); // finally put the macro argument behind, if needed @@ -1523,7 +1578,7 @@ bool Cursor::macroModeClose() else insert(selection); } - + return true; } @@ -1575,7 +1630,7 @@ bool Cursor::inMacroMode() const { if (!inMathed()) return false; - if (pos() == 0) + if (pos() == 0 || cell().empty()) return false; InsetMathUnknown const * p = prevAtom()->asUnknownInset(); return p && !p->final(); @@ -1634,7 +1689,9 @@ void Cursor::normalize() << pos() << ' ' << lastpos() << " in idx: " << idx() << " in atom: '"; odocstringstream os; - WriteStream wi(os, false, true, WriteStream::wsDefault); + TexRow texrow(false); + otexrowstream ots(os,texrow); + WriteStream wi(ots, false, true, WriteStream::wsDefault); inset().asInsetMath()->write(wi); lyxerr << to_utf8(os.str()) << endl; pos() = lastpos(); @@ -1651,8 +1708,8 @@ bool Cursor::upDownInMath(bool up) int xo = 0; int yo = 0; getPos(xo, yo); - xo = theLyXFunc().cursorBeforeDispatchX(); - + xo = beforeDispatchPosX_; + // check if we had something else in mind, if not, this is the future // target if (x_target_ == -1) @@ -1660,13 +1717,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) { @@ -1696,12 +1753,12 @@ 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; getPos(x, y); - int oy = theLyXFunc().cursorBeforeDispatchY(); + int oy = beforeDispatchPosY_; if ((!up && y <= oy) || (up && y >= oy)) operator=(old); @@ -1709,7 +1766,7 @@ bool Cursor::upDownInMath(bool up) return true; } } - + // try right if (pos() != lastpos()) { InsetMathScript const * p = nextAtom()->asScriptInset(); @@ -1717,12 +1774,12 @@ 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; getPos(x, y); - int oy = theLyXFunc().cursorBeforeDispatchY(); + int oy = beforeDispatchPosY_; if ((!up && y <= oy) || (up && y >= oy)) operator=(old); @@ -1731,7 +1788,7 @@ bool Cursor::upDownInMath(bool up) } } } - + // try to find an inset that knows better then we, if (inset().idxUpDown(*this, up)) { //lyxerr << "idxUpDown triggered" << endl; @@ -1740,18 +1797,18 @@ bool Cursor::upDownInMath(bool up) setCursor(bruteFind2(*this, xo, yo)); return true; } - + // any improvement going just out of inset? if (popBackward() && inMathed()) { //lyxerr << "updown: popBackward succeeded" << endl; int xnew; int ynew; - int yold = theLyXFunc().cursorBeforeDispatchY(); + int yold = beforeDispatchPosY_; getPos(xnew, ynew); if (up ? ynew < yold : ynew > yold) return true; } - + // no success, we are probably at the document top or bottom operator=(old); return false; @@ -1762,13 +1819,13 @@ 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; @@ -1780,15 +1837,16 @@ bool Cursor::atFirstOrLastRow(bool up) return false; } + bool Cursor::upDownInText(bool up, bool & updateNeeded) { - LASSERT(text(), /**/); + LASSERT(text(), return false); // where are we? int xo = 0; int yo = 0; getPos(xo, yo); - xo = theLyXFunc().cursorBeforeDispatchX(); + xo = beforeDispatchPosX_; // update the targetX - this is here before the "return false" // to set a new target which can be used by InsetTexts above @@ -1797,15 +1855,17 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) setTargetX(xo); else if (xo - textTargetOffset() != x_target() && depth() == beforeDispatchCursor_.depth()) { - // 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); + // 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 = innerParagraph().isRTL(bv().buffer().params()); bool left; bool right; if (inRTL) { @@ -1823,7 +1883,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()); @@ -1832,7 +1892,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 @@ -1845,11 +1905,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; @@ -1858,13 +1918,15 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) updateNeeded |= bv().checkDepm(dummy, *this); updateTextTargetOffset(); + if (updateNeeded) + forceBufferUpdate(); } return false; } // with and without selection are handled differently if (!selection()) { - int yo = bv().getPos(*this, boundary()).y_; + int yo = bv().getPos(*this).y_; Cursor old = *this; // To next/previous row if (up) @@ -1872,19 +1934,21 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) else tm.editXY(*this, xo, yo + textRow().descent() + 1); 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) + // Make sure that cur gets back whatever happened to dummy (Lgb) operator=(dummy); } + if (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: @@ -1912,24 +1976,20 @@ 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); } + if (updateNeeded) + forceBufferUpdate(); updateTextTargetOffset(); return true; -} +} void Cursor::handleFont(string const & font) @@ -1972,26 +2032,32 @@ void Cursor::handleFont(string const & font) void Cursor::message(docstring const & msg) const { - theLyXFunc().setMessage(msg); + disp_.setMessage(msg); } void Cursor::errorMessage(docstring const & msg) const { - theLyXFunc().setErrorMessage(msg); + disp_.setMessage(msg); + disp_.setError(true); } -static docstring parbreak(InsetCode code) +namespace { + +docstring parbreak(Cursor const * cur) { odocstringstream os; os << '\n'; - // only add blank line if we're not in an ERT or Listings inset - if (code != ERT_CODE && code != LISTINGS_CODE) + // only add blank line if we're not in a ParbreakIsNewline situation + if (!cur->inset().getLayout().parbreakIsNewline() + && !cur->paragraph().layout().parbreak_is_newline) os << '\n'; return os.str(); } +} + docstring Cursor::selectionAsString(bool with_label) const { @@ -2026,13 +2092,13 @@ docstring Cursor::selectionAsString(bool with_label) const // First paragraph in selection docstring result = pars[startpit]. asString(startpos, pars[startpit].size(), label) - + parbreak(inset().lyxCode()); + + parbreak(this); // The paragraphs in between (if any) for (pit_type pit = startpit + 1; pit != endpit; ++pit) { Paragraph const & par = pars[pit]; result += par.asString(0, par.size(), label) - + parbreak(inset().lyxCode()); + + parbreak(this); } // Last paragraph in selection @@ -2047,6 +2113,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(); } @@ -2075,56 +2148,74 @@ Encoding const * Cursor::getEncoding() const } -void Cursor::undispatched() +void Cursor::undispatched() const { disp_.dispatched(false); } -void Cursor::dispatched() +void Cursor::dispatched() const { disp_.dispatched(true); } -void Cursor::updateFlags(Update::flags f) +void Cursor::screenUpdateFlags(Update::flags f) const { - disp_.update(f); + disp_.screenUpdate(f); } -void Cursor::noUpdate() +void Cursor::forceBufferUpdate() const { - disp_.update(Update::None); + disp_.forceBufferUpdate(); +} + + +void Cursor::clearBufferUpdate() const +{ + disp_.clearBufferUpdate(); +} + + +bool Cursor::needBufferUpdate() const +{ + return disp_.needBufferUpdate(); +} + + +void Cursor::noScreenUpdate() const +{ + disp_.screenUpdate(Update::None); } 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. - + // 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. + // 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())); @@ -2135,14 +2226,29 @@ Font Cursor::getFont() const bool Cursor::fixIfBroken() { - if (DocIterator::fixIfBroken()) { - clearSelection(); - return true; + bool const broken_cursor = DocIterator::fixIfBroken(); + bool const broken_anchor = anchor_.fixIfBroken(); + + if (broken_cursor || broken_anchor) { + clearNewWordPosition(); + clearSelection(); + return true; } return false; } +void Cursor::sanitize() +{ + setBuffer(&bv_->buffer()); + DocIterator::sanitize(); + if (selection()) + anchor_.sanitize(); + else + resetAnchor(); +} + + bool notifyCursorLeavesOrEnters(Cursor const & old, Cursor & cur) { // find inset in common @@ -2175,7 +2281,7 @@ bool notifyCursorLeavesOrEnters(Cursor const & old, Cursor & cur) if (cur[i].inset().notifyCursorEnters(cur)) return true; } - + return false; } @@ -2228,28 +2334,18 @@ void Cursor::setCurrentFont() bool Cursor::textUndo() { - DocIterator dit = *this; - // Undo::textUndo() will modify dit. - if (!buffer()->undo().textUndo(dit)) + if (!buffer()->undo().textUndo(*this)) return false; - // Set cursor - setCursor(dit); - clearSelection(); - fixIfBroken(); + sanitize(); return true; } bool Cursor::textRedo() { - DocIterator dit = *this; - // Undo::textRedo() will modify dit. - if (!buffer()->undo().textRedo(dit)) + if (!buffer()->undo().textRedo(*this)) return false; - // Set cursor - setCursor(dit); - clearSelection(); - fixIfBroken(); + sanitize(); return true; } @@ -2262,25 +2358,25 @@ void Cursor::finishUndo() const void Cursor::beginUndoGroup() const { - buffer()->undo().beginUndoGroup(); + buffer()->undo().beginUndoGroup(*this); } void Cursor::endUndoGroup() const { - buffer()->undo().endUndoGroup(); + buffer()->undo().endUndoGroup(*this); } -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()); } @@ -2290,15 +2386,21 @@ void Cursor::recordUndo(UndoKind kind) const } -void Cursor::recordUndoInset(UndoKind kind) const +void Cursor::recordUndoInset(Inset const * in) const +{ + buffer()->undo().recordUndoInset(*this, in); +} + + +void Cursor::recordUndoFullBuffer() const { - buffer()->undo().recordUndoInset(*this, kind); + buffer()->undo().recordUndoFullBuffer(*this); } -void Cursor::recordUndoFullDocument() const +void Cursor::recordUndoBufferParams() const { - buffer()->undo().recordUndoFullDocument(*this); + buffer()->undo().recordUndoBufferParams(*this); } @@ -2310,7 +2412,7 @@ void Cursor::recordUndoSelection() const else recordUndo(); } else { - buffer()->undo().recordUndo(*this, ATOMIC_UNDO, + buffer()->undo().recordUndo(*this, selBegin().pit(), selEnd().pit()); } } @@ -2321,7 +2423,7 @@ 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); }