X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCursor.cpp;h=bdfba6114bc776d61b28fc196f6d4736bf54924a;hb=c7d29be153debac82e3d2e8865fcc849f0a5f40d;hp=f4e9ce80190a2715f396313bd78ca8553e327a83;hpb=21c30a09e12a7ad94bde592d3fea3e66f6fb38f5;p=lyx.git diff --git a/src/Cursor.cpp b/src/Cursor.cpp index f4e9ce8019..bdfba6114b 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -14,14 +14,13 @@ #include -#include "Bidi.h" #include "Buffer.h" +#include "BufferParams.h" #include "BufferView.h" #include "CoordCache.h" #include "Cursor.h" #include "CutAndPaste.h" #include "DispatchResult.h" -#include "Encoding.h" #include "Font.h" #include "FuncCode.h" #include "FuncRequest.h" @@ -32,6 +31,7 @@ #include "Paragraph.h" #include "ParIterator.h" #include "Row.h" +#include "texstream.h" #include "Text.h" #include "TextMetrics.h" #include "TocBackend.h" @@ -47,12 +47,12 @@ #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/MathMacro.h" - -#include "support/bind.h" +#include "mathed/MathFactory.h" +#include "mathed/InsetMathMacro.h" #include #include @@ -67,32 +67,32 @@ namespace { // 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_; @@ -114,133 +114,13 @@ 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() = prev(cache.end(), 1)->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 +} // namespace 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) {} @@ -248,7 +128,7 @@ CursorData::CursorData() 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) {} @@ -256,7 +136,7 @@ CursorData::CursorData(Buffer * buffer) 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) {} @@ -314,13 +194,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()) { @@ -633,24 +537,6 @@ 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 @@ -735,10 +621,7 @@ bool Cursor::posVisRight(bool skip_inset) } - 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() @@ -834,9 +717,7 @@ bool Cursor::posVisLeft(bool skip_inset) } - 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() @@ -863,21 +744,20 @@ bool findNonVirtual(Row const & row, Row::const_iterator & cit, bool onleft) return cit != row.end() && !cit->isVirtual(); } -} +} // namespace -void Cursor::getSurroundingPosNew(pos_type & left_pos, pos_type & right_pos) const +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(); - TextMetrics const & tm = bv_->textMetrics(text()); double dummy = 0; - Row::const_iterator cit = tm.findRowElement(row, pos(), boundary(), dummy); + Row::const_iterator cit = row.findElement(pos(), boundary(), dummy); // Handle the case of empty row if (cit == row.end()) { - if (paragraph().isRTL(buffer()->params())) + if (row.isRTL()) right_pos = row.pos(); else left_pos = row.pos() - 1; @@ -942,147 +822,8 @@ void Cursor::getSurroundingPosNew(pos_type & left_pos, pos_type & right_pos) con left_pos = pos() - (cit->isRTL() ? 0 : 1); right_pos = pos() - (cit->isRTL() ? 1 : 0); } -} - - -void Cursor::getSurroundingPosOrig(pos_type & left_pos, pos_type & right_pos) const -{ - // preparing bidi tables - Paragraph const & par = paragraph(); - Buffer const & buf = *buffer(); - 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); - } - 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); - } - } - return; -} - -void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const -{ - // Check result wrt old implementation - // FIXME: remove after correct testing. - pos_type lp, rp; - getSurroundingPosNew(lp, rp); - getSurroundingPosOrig(left_pos, right_pos); - if (lp != left_pos || rp != right_pos) { - Row const & row = textRow(); - TextMetrics const & tm = bv_->textMetrics(text()); - double dummy = 0; - Row::const_iterator cit = tm.findRowElement(row, pos(), boundary(), dummy); - if (cit != row.end()) - LYXERR0("Wrong surroundingpos: old=(" << left_pos << ", " << right_pos - << "), new=(" << lp << ", " << rp - << ") *cit= " << *cit - << "\ncur = " << *this << "\nrow =" << row); - else - LYXERR0("Wrong surroundingpos: old=(" << left_pos << ", " << right_pos - << "), new=(" << lp << ", " << rp - << ") in empty row" - << "\ncur = " << *this << "\nrow =" << row); - } + // Note that debug message does not catch all early returns above LYXERR(Debug::RTL,"getSurroundingPos(" << pos() << (boundary() ? "b" : "") << ") => (" << left_pos << ", " << right_pos <<")"); } @@ -1090,16 +831,13 @@ void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const 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)); } @@ -1179,8 +917,11 @@ CursorSlice Cursor::normalAnchor() const // 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); + 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) { @@ -1191,12 +932,6 @@ CursorSlice Cursor::normalAnchor() const } -DocIterator & Cursor::realAnchor() -{ - return anchor_; -} - - CursorSlice Cursor::selBegin() const { if (!selection()) @@ -1253,18 +988,18 @@ DocIterator Cursor::selectionEnd() const void Cursor::setSelection() { - setSelection(true); + 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; } @@ -1272,13 +1007,63 @@ void Cursor::setSelection(DocIterator const & where, int n) void Cursor::clearSelection() { - setSelection(false); + selection(false); setWordSelection(false); setMark(false); resetAnchor(); } +int Cursor::countInsetsInSelection(InsetCode const & inset_code) +{ + if (!selection_) + return 0; + + DocIterator from, to; + from = selectionBegin(); + to = selectionEnd(); + + int count = 0; + + if (!from.nextInset()) //move to closest inset + from.forwardInset(); + + while (!from.empty() && from < to) { + Inset * inset = from.nextInset(); + if (!inset) + break; + if (inset->lyxCode() == inset_code) + count ++; + from.forwardInset(); + } + return count; +} + + +bool Cursor::insetInSelection(InsetCode const & inset_code) +{ + if (!selection_) + return false; + + DocIterator from, to; + from = selectionBegin(); + to = selectionEnd(); + + if (!from.nextInset()) //move to closest inset + from.forwardInset(); + + while (!from.empty() && from < to) { + Inset * inset = from.nextInset(); + if (!inset) + break; + if (inset->lyxCode() == inset_code) + return true; + from.forwardInset(); + } + return false; +} + + void Cursor::setTargetX(int x) { x_target_ = x; @@ -1308,7 +1093,7 @@ void Cursor::updateTextTargetOffset() } -void Cursor::info(odocstream & os) const +void Cursor::info(odocstream & os, bool devel_mode) const { for (int i = 1, n = depth(); i < n; ++i) { operator[](i).inset().infoize(os); @@ -1320,8 +1105,14 @@ void Cursor::info(odocstream & os) const if (inset) prevInset()->infoize2(os); } - // overwite old message - os << " "; + if (devel_mode) { + InsetMath * math = inset().asInsetMath(); + if (math) + os << _(", Inset: ") << math->id(); + os << _(", Cell: ") << idx(); + os << _(", Position: ") << pos(); + } + } @@ -1337,7 +1128,7 @@ bool Cursor::selHandle(bool sel) cap::saveSelection(*this); resetAnchor(); - setSelection(sel); + selection(sel); return true; } } // namespace lyx @@ -1436,15 +1227,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); } @@ -1537,7 +1327,7 @@ void Cursor::insert(MathData const & ar) } -bool Cursor::backspace() +bool Cursor::backspace(bool const force) { if (selection()) { cap::eraseSelection(*this); @@ -1557,9 +1347,20 @@ bool Cursor::backspace() // [|], can not delete from inside return false; } else { - if (inMathed()) - pullArg(); - else + if (inMathed()) { + switch (inset().asInsetMath()->getType()) { + case hullEqnArray: + case hullAlign: + case hullFlAlign: { + FuncRequest cmd(LFUN_CHAR_BACKWARD); + this->dispatch(cmd); + break; + } + default: + pullArg(); + break; + } + } else popBackward(); return true; } @@ -1573,11 +1374,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(); @@ -1587,7 +1388,7 @@ bool Cursor::backspace() } -bool Cursor::erase() +bool Cursor::erase(bool const force) { if (inMacroMode()) return true; @@ -1622,9 +1423,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(); @@ -1660,7 +1461,7 @@ bool Cursor::down() } -bool Cursor::macroModeClose() +bool Cursor::macroModeClose(bool cancel) { if (!inMacroMode()) return false; @@ -1672,42 +1473,67 @@ bool Cursor::macroModeClose() --pos(); cell().erase(pos()); - // do nothing if the macro name is empty - if (s == "\\") - return false; - // trigger updates of macros, at least, if no full // updates take place anyway screenUpdateFlags(Update::Force); + // do nothing if the macro name is empty + if (s == "\\" || cancel) { + return false; + } + docstring const name = s.substr(1); 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 InsetMathMacro(buffer(), name)) + : createInsetMath(name, buffer()); // try to put argument into macro, if we just inserted a macro bool macroArg = false; - MathMacro * atomAsMacro = atom.nucleus()->asMacro(); + InsetMathMacro * atomAsMacro = atom.nucleus()->asMacro(); if (atomAsMacro) { // macros here are still unfolded (in init mode in fact). So // 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.empty() && data && data->numargs() - data->optionals() > 0) { + if (!selection.empty() && data && data->numargs()) { macroArg = true; - atomAsMacro->setDisplayMode(MathMacro::DISPLAY_INTERACTIVE_INIT, 1); + atomAsMacro->setDisplayMode(InsetMathMacro::DISPLAY_INTERACTIVE_INIT, 1); } else // non-greedy case. Do not touch the arguments behind - atomAsMacro->setDisplayMode(MathMacro::DISPLAY_INTERACTIVE_INIT, 0); + atomAsMacro->setDisplayMode(InsetMathMacro::DISPLAY_INTERACTIVE_INIT, 0); } // 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 = user_macro + || (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) { @@ -1730,8 +1556,11 @@ docstring Cursor::macroName() void Cursor::handleNest(MathAtom const & a, int c) { //lyxerr << "Cursor::handleNest: " << c << endl; + InsetMath const * im = selectionBegin().inset().asInsetMath(); + Parse::flags const f = im && im->currentMode() != InsetMath::MATH_MODE + ? Parse::TEXTMODE : Parse::NORMAL; MathAtom t = a; - asArray(cap::grabAndEraseSelection(*this), t.nucleus()->cell(c)); + asArray(cap::grabAndEraseSelection(*this), t.nucleus()->cell(c), f); insert(t); posBackward(); pushBackward(*nextInset()); @@ -1827,7 +1656,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(); @@ -1930,7 +1760,7 @@ bool Cursor::upDownInMath(bool 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; } @@ -1951,6 +1781,95 @@ bool Cursor::upDownInMath(bool up) } +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()); @@ -2065,10 +1984,13 @@ 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. @@ -2083,6 +2005,8 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) // Make sure that cur gets back whatever happened to dummy (Lgb) 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: @@ -2115,6 +2039,8 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded) bool bound = false; top().pos() = tm.getPosNearX(real_next_row, xo, bound); boundary(bound); + // When selection==false, this is done by TextMetrics::editXY + setCurrentFont(); updateNeeded |= bv().checkDepm(*this, old); } @@ -2190,7 +2116,7 @@ docstring parbreak(Cursor const * cur) return os.str(); } -} +} // namespace docstring Cursor::selectionAsString(bool with_label) const @@ -2242,16 +2168,16 @@ docstring Cursor::selectionAsString(bool with_label) const } -docstring Cursor::currentState() const +docstring Cursor::currentState(bool devel_mode) const { if (inMathed()) { odocstringstream os; - info(os); + info(os, devel_mode); return os.str(); } if (inTexted()) - return text()->currentState(*this); + return text()->currentState(*this, devel_mode); return docstring(); } @@ -2263,18 +2189,6 @@ docstring Cursor::getPossibleLabel() const } -Encoding const * Cursor::getEncoding() const -{ - if (empty()) - return 0; - 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())); - return font.language()->encoding(); -} - - void Cursor::undispatched() const { disp_.dispatched(false); @@ -2322,6 +2236,7 @@ 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... @@ -2369,6 +2284,7 @@ void Cursor::sanitize() { setBuffer(&bv_->buffer()); DocIterator::sanitize(); + new_word_.sanitize(); if (selection()) anchor_.sanitize(); else @@ -2553,6 +2469,47 @@ void Cursor::checkBufferStructure() // 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(); + } }