X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCursor.cpp;h=eff32d05769bf650b7f6846c586efdaf19fa5267;hb=28be7d552f62cc02fa86d7f79201d089bfb2d7b5;hp=c1eb32f684b34876766c5016ee10752821c0b910;hpb=d3c00446179927360380c1511294e5a7ef19e269;p=lyx.git diff --git a/src/Cursor.cpp b/src/Cursor.cpp index c1eb32f684..eff32d0576 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -15,6 +15,7 @@ #include #include "Buffer.h" +#include "BufferParams.h" #include "BufferView.h" #include "CoordCache.h" #include "Cursor.h" @@ -31,6 +32,7 @@ #include "Paragraph.h" #include "ParIterator.h" #include "Row.h" +#include "texstream.h" #include "Text.h" #include "TextMetrics.h" #include "TocBackend.h" @@ -46,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 @@ -66,32 +68,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_; @@ -113,133 +115,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 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) {} @@ -247,7 +129,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) {} @@ -255,7 +137,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) {} @@ -313,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()) { @@ -550,8 +456,11 @@ 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 = 0 ; i < depth() ; ++i) + for (size_t i = 1 ; i < depth() ; ++i) if (!icache.has(&(*this)[i].inset())) return false; return true; @@ -642,24 +551,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 @@ -876,12 +767,11 @@ void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const 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; @@ -955,10 +845,8 @@ 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. @@ -1043,8 +931,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) { @@ -1055,12 +946,6 @@ CursorSlice Cursor::normalAnchor() const } -DocIterator & Cursor::realAnchor() -{ - return anchor_; -} - - CursorSlice Cursor::selBegin() const { if (!selection()) @@ -1117,18 +1002,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; } @@ -1136,7 +1021,7 @@ void Cursor::setSelection(DocIterator const & where, int n) void Cursor::clearSelection() { - setSelection(false); + selection(false); setWordSelection(false); setMark(false); resetAnchor(); @@ -1199,7 +1084,7 @@ bool Cursor::selHandle(bool sel) cap::saveSelection(*this); resetAnchor(); - setSelection(sel); + selection(sel); return true; } } // namespace lyx @@ -1304,9 +1189,8 @@ void Cursor::plainInsert(MathAtom const & t) 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); } @@ -1399,7 +1283,7 @@ void Cursor::insert(MathData const & ar) } -bool Cursor::backspace() +bool Cursor::backspace(bool const force) { if (selection()) { cap::eraseSelection(*this); @@ -1435,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(); @@ -1449,7 +1333,7 @@ bool Cursor::backspace() } -bool Cursor::erase() +bool Cursor::erase(bool const force) { if (inMacroMode()) return true; @@ -1484,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(); @@ -1546,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; @@ -1569,7 +1454,29 @@ bool Cursor::macroModeClose() 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) { @@ -1689,8 +1596,7 @@ void Cursor::normalize() << pos() << ' ' << lastpos() << " in idx: " << idx() << " in atom: '"; odocstringstream os; - TexRow texrow(false); - otexrowstream ots(os,texrow); + otexrowstream ots(os); WriteStream wi(ots, false, true, WriteStream::wsDefault); inset().asInsetMath()->write(wi); lyxerr << to_utf8(os.str()) << endl; @@ -1794,7 +1700,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; } @@ -1815,6 +1721,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()); @@ -1929,10 +1924,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. @@ -1947,6 +1945,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: @@ -2138,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(); } @@ -2193,6 +2197,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... @@ -2240,6 +2245,7 @@ void Cursor::sanitize() { setBuffer(&bv_->buffer()); DocIterator::sanitize(); + new_word_.sanitize(); if (selection()) anchor_.sanitize(); else @@ -2424,6 +2430,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(); + } }