X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FCursor.cpp;h=9bfbada8a986a651d08f9db269f635a73673feac;hb=e54ae72e5fac6f750c3f7972c74bb42b57f3a049;hp=c49673cf4ca9a57b250d3018cc3170a955cb08c0;hpb=32871c1284f15265f652ff01c438e539a7c8181f;p=lyx.git diff --git a/src/Cursor.cpp b/src/Cursor.cpp index c49673cf4c..9bfbada8a9 100644 --- a/src/Cursor.cpp +++ b/src/Cursor.cpp @@ -6,12 +6,14 @@ * \author Alejandro Aguilar Sierra * \author Alfredo Braunstein * \author André Pönitz + * \author Stefan Schimanski * * Full author contact details are available in file CREDITS. */ #include +#include "Bidi.h" #include "BufferView.h" #include "bufferview_funcs.h" #include "Buffer.h" @@ -24,11 +26,11 @@ #include "FuncRequest.h" #include "Language.h" #include "lfuns.h" -#include "LyXFont.h" +#include "Font.h" #include "LyXFunc.h" // only for setMessage() #include "LyXRC.h" #include "Row.h" -#include "LyXText.h" +#include "Text.h" #include "Paragraph.h" #include "paragraph_funcs.h" #include "ParIterator.h" @@ -51,14 +53,15 @@ #include #include -namespace lyx { - using std::string; using std::vector; using std::endl; using std::min; using std::for_each; + +namespace lyx { + namespace { bool @@ -245,13 +248,24 @@ namespace { return true; } + docstring parbreak(Paragraph const & par) + { + odocstringstream ods; + ods << '\n'; + // only add blank line if we're not in an ERT or Listings inset + if (par.ownerCode() != Inset::ERT_CODE + && par.ownerCode() != Inset::LISTINGS_CODE) + ods << '\n'; + return ods.str(); + } + } // namespace anon // be careful: this is called from the bv's constructor, too, so // bv functions are not yet available! Cursor::Cursor(BufferView & bv) - : DocIterator(), bv_(&bv), anchor_(), x_target_(-1), + : DocIterator(), bv_(&bv), anchor_(), x_target_(-1), textTargetOffset_(0), selection_(false), mark_(false), logicalpos_(false) {} @@ -285,7 +299,10 @@ void Cursor::dispatch(FuncRequest const & cmd0) fixIfBroken(); FuncRequest cmd = cmd0; Cursor safe = *this; - + + // store some values to be used inside of the handlers + getPos(beforeDispX_, beforeDispY_); + beforeDispatchCursor_ = *this; for (; depth(); pop()) { LYXERR(Debug::DEBUG) << "Cursor::dispatch: cmd: " << cmd0 << endl << *this << endl; @@ -302,6 +319,7 @@ void Cursor::dispatch(FuncRequest const & cmd0) if (disp_.dispatched()) break; } + // it completely to get a 'bomb early' behaviour in case this // object will be used again. if (!disp_.dispatched()) { @@ -309,6 +327,10 @@ void Cursor::dispatch(FuncRequest const & cmd0) operator=(safe); disp_.update(Update::None); disp_.dispatched(false); + } else { + // restore the previous one because nested Cursor::dispatch calls + // are possible which would change it + beforeDispatchCursor_ = safe.beforeDispatchCursor_; } } @@ -490,9 +512,7 @@ void Cursor::setSelection() { selection() = true; // A selection with no contents is not a selection -#ifdef WITH_WARNINGS -#warning doesnt look ok -#endif + // FIXME: doesnt look ok if (pit() == anchor().pit() && pos() == anchor().pos()) selection() = false; } @@ -515,9 +535,10 @@ void Cursor::clearSelection() } -int & Cursor::x_target() +void Cursor::setTargetX(int x) { - return x_target_; + x_target_ = x; + textTargetOffset_ = 0; } @@ -530,9 +551,18 @@ int Cursor::x_target() const void Cursor::clearTargetX() { x_target_ = -1; + textTargetOffset_ = 0; } +void Cursor::updateTextTargetOffset() +{ + int x; + int y; + getPos(x, y); + textTargetOffset_ = x - x_target_; +} + void Cursor::info(odocstream & os) const { @@ -557,9 +587,11 @@ bool Cursor::selHandle(bool sel) if (sel == selection()) return false; + if (!sel) + cap::saveSelection(*this); + resetAnchor(); selection() = sel; - cap::saveSelection(*this); return true; } @@ -651,7 +683,7 @@ bool Cursor::openable(MathAtom const & t) const void Cursor::setScreenPos(int x, int y) { - x_target() = x; + setTargetX(x); bruteFind(*this, x, y, 0, bv().workWidth(), 0, bv().workHeight()); } @@ -774,7 +806,7 @@ bool Cursor::backspace() // If empty cell, and not part of a big cell if (lastpos() == 0 && inset().nargs() == 1) { popLeft(); - // Directly delete empty cell: [|[]] => [|] + // Directly delete empty cell: [|[]] => [|] if (inMathed()) { plainErase(); resetAnchor(); @@ -833,7 +865,7 @@ bool Cursor::erase() bool one_cell = inset().nargs() == 1; if (one_cell && lastpos() == 0) { popLeft(); - // Directly delete empty cell: [|[]] => [|] + // Directly delete empty cell: [|[]] => [|] if (inMathed()) { plainErase(); resetAnchor(); @@ -865,11 +897,13 @@ bool Cursor::up() { macroModeClose(); DocIterator save = *this; - if (goUpDown(true)) + FuncRequest cmd(selection() ? LFUN_UP_SELECT : LFUN_UP, docstring()); + this->dispatch(cmd); + if (disp_.dispatched()) return true; setCursor(save); autocorrect() = false; - return selection(); + return false; } @@ -877,11 +911,13 @@ bool Cursor::down() { macroModeClose(); DocIterator save = *this; - if (goUpDown(false)) + FuncRequest cmd(selection() ? LFUN_DOWN_SELECT : LFUN_DOWN, docstring()); + this->dispatch(cmd); + if (disp_.dispatched()) return true; setCursor(save); autocorrect() = false; - return selection(); + return false; } @@ -940,17 +976,25 @@ int Cursor::targetX() const } +int Cursor::textTargetOffset() const +{ + return textTargetOffset_; +} + + void Cursor::setTargetX() { int x; int y; getPos(x, y); - x_target_ = x; + setTargetX(x); } bool Cursor::inMacroMode() const { + if (!inMathed()) + return false; if (pos() == 0) return false; InsetMathUnknown const * p = prevAtom()->asUnknownInset(); @@ -966,9 +1010,7 @@ InsetMathUnknown * Cursor::activeMacro() void Cursor::pullArg() { -#ifdef WITH_WARNINGS -#warning Look here -#endif + // FIXME: Look here MathData ar = cell(); if (popLeft() && inMathed()) { plainErase(); @@ -982,9 +1024,7 @@ void Cursor::pullArg() void Cursor::touch() { -#ifdef WITH_WARNINGS -#warning look here -#endif + // FIXME: look here #if 0 DocIterator::const_iterator it = begin(); DocIterator::const_iterator et = end(); @@ -1016,26 +1056,52 @@ void Cursor::normalize() } -bool Cursor::goUpDown(bool up) +bool Cursor::upDownInMath(bool up) { // Be warned: The 'logic' implemented in this function is highly // fragile. A distance of one pixel or a '<' vs '<=' _really // matters. So fiddle around with it only if you think you know // what you are doing! - int xo = 0; int yo = 0; getPos(xo, yo); + xo = beforeDispX_; // check if we had something else in mind, if not, this is the future // target - if (x_target() == -1) - x_target() = xo; - else - xo = x_target(); + if (x_target_ == -1) + setTargetX(xo); + else if (inset().asTextInset() && 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 left; + bool right; + if (inRTL) { + left = pos() == textRow().endpos(); + right = pos() == textRow().pos(); + } else { + left = pos() == textRow().pos(); + right = pos() == textRow().endpos(); + } + if ((!left && !right) || + (left && !right && xo < x_target_) || + (!left && right && x_target_ < xo)) + setTargetX(xo); + else + xo = targetX(); + } else + xo = targetX(); // try neigbouring script insets - if (!selection()) { + Cursor old = *this; + if (inMathed() && !selection()) { // try left if (pos() != 0) { InsetMathScript const * p = prevAtom()->asScriptInset(); @@ -1044,10 +1110,19 @@ bool Cursor::goUpDown(bool up) push(*const_cast(p)); idx() = p->idxOfScript(up); pos() = lastpos(); - return true; + + // we went in the right direction? Otherwise don't jump into the script + int x; + int y; + getPos(x, y); + if ((!up && y <= beforeDispY_) || + (up && y >= beforeDispY_)) + operator=(old); + else + return true; } } - + // try right if (pos() != lastpos()) { InsetMathScript const * p = nextAtom()->asScriptInset(); @@ -1055,60 +1130,157 @@ bool Cursor::goUpDown(bool up) push(*const_cast(p)); idx() = p->idxOfScript(up); pos() = 0; - return true; + + // we went in the right direction? Otherwise don't jump into the script + int x; + int y; + getPos(x, y); + if ((!up && y <= beforeDispY_) || + (up && y >= beforeDispY_)) + operator=(old); + else + return true; } } } + + // try to find an inset that knows better then we, + if (inset().idxUpDown(*this, up)) { + //lyxerr << "idxUpDown triggered" << endl; + // try to find best position within this inset + if (!selection()) + setCursor(bruteFind2(*this, xo, yo)); + return true; + } + + // any improvement going just out of inset? + if (popLeft() && inMathed()) { + //lyxerr << "updown: popLeft succeeded" << endl; + int xnew; + int ynew; + getPos(xnew, ynew); + if (up ? ynew < beforeDispY_ : ynew > beforeDispY_) + return true; + } + + // no success, we are probably at the document top or bottom + operator=(old); + return false; +} -// FIXME: Switch this on for more robust movement -#if 0 - return bruteFind3(*this, xo, yo, up); - -#else - //xarray().boundingBox(xlow, xhigh, ylow, yhigh); - //if (up) - // yhigh = yo - 4; - //else - // ylow = yo + 4; - //if (bruteFind(*this, xo, yo, xlow, xhigh, ylow, yhigh)) { - // lyxerr << "updown: handled by brute find in the same cell" << endl; - // return true; - //} - - // try to find an inset that knows better then we - while (true) { - //lyxerr << "updown: We are in " << &inset() << " idx: " << idx() << endl; - // ask inset first - if (inset().idxUpDown(*this, up)) { - //lyxerr << "idxUpDown triggered" << endl; - // try to find best position within this inset - if (!selection()) - setCursor(bruteFind2(*this, xo, yo)); - return true; +bool Cursor::upDownInText(bool up, bool & updateNeeded) +{ + BOOST_ASSERT(text()); + + // where are we? + int xo = 0; + int yo = 0; + getPos(xo, yo); + xo = beforeDispX_; + + // update the targetX - this is here before the "return false" + // to set a new target which can be used by InsetTexts above + // if we cannot move up/down inside this inset anymore + if (x_target_ == -1) + 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); + bool left; + bool right; + if (inRTL) { + left = pos() == textRow().endpos(); + right = pos() == textRow().pos(); + } else { + left = pos() == textRow().pos(); + right = pos() == textRow().endpos(); } + if ((!left && !right) || + (left && !right && xo < x_target_) || + (!left && right && x_target_ < xo)) + setTargetX(xo); + else + xo = targetX(); + } else + xo = targetX(); + + // first get the current line + 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()); + + // are we not at the start or end? + if (up) { + if (pit() == 0 && row == 0) + return false; + } else { + if (pit() + 1 >= int(text()->paragraphs().size()) && + row + 1 >= int(pm.rows().size())) + return false; + } - // no such inset found, just take something "above" - if (!popLeft()) { - //lyxerr << "updown: popleft failed (strange case)" << endl; - int ylow = up ? 0 : yo + 1; - int yhigh = up ? yo - 1 : bv().workHeight(); - return bruteFind(*this, xo, yo, 0, bv().workWidth(), ylow, yhigh); + // with and without selection are handled differently + if (!selection()) { + int yo = bv_funcs::getPos(bv(), *this, boundary()).y_; + Cursor old = *this; + // To next/previous row + if (up) + text()->editXY(*this, xo, yo - textRow().ascent() - 1); + else + text()->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) + 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) + operator=(dummy); + } + } else { + // if there is a selection, we stay out of any inset, and just jump to the right position: + Cursor old = *this; + if (up) { + if (row > 0) { + top().pos() = std::min(tm.x2pos(pit(), row - 1, xo), top().lastpos()); + } else if (pit() > 0) { + --pit(); + ParagraphMetrics const & pmcur = bv_->parMetrics(text(), pit()); + top().pos() = std::min(tm.x2pos(pit(), pmcur.rows().size() - 1, xo), top().lastpos()); + } + } else { + if (row + 1 < int(pm.rows().size())) { + top().pos() = std::min(tm.x2pos(pit(), row + 1, xo), top().lastpos()); + } else if (pit() + 1 < int(text()->paragraphs().size())) { + ++pit(); + top().pos() = std::min(tm.x2pos(pit(), 0, xo), top().lastpos()); + } } - // any improvement so far? - //lyxerr << "updown: popLeft succeeded" << endl; - int xnew; - int ynew; - getPos(xnew, ynew); - if (up ? ynew < yo : ynew > yo) - return true; + updateNeeded |= bv().checkDepm(*this, old); } - // we should not come here. - BOOST_ASSERT(false); -#endif -} + updateTextTargetOffset(); + return true; +} void Cursor::handleFont(string const & font) @@ -1178,12 +1350,14 @@ docstring Cursor::selectionAsString(bool label) const // First paragraph in selection docstring result = pars[startpit]. - asString(buffer, startpos, pars[startpit].size(), label) + "\n\n"; + asString(buffer, startpos, pars[startpit].size(), label) + + parbreak(pars[startpit]); // The paragraphs in between (if any) for (pit_type pit = startpit + 1; pit != endpit; ++pit) { Paragraph const & par = pars[pit]; - result += par.asString(buffer, 0, par.size(), label) + "\n\n"; + result += par.asString(buffer, 0, par.size(), label) + + parbreak(pars[pit]); } // Last paragraph in selection @@ -1233,8 +1407,8 @@ Encoding const * Cursor::getEncoding() const if (operator[](s).text()) break; CursorSlice const & sl = operator[](s); - LyXText const & text = *sl.text(); - LyXFont font = text.getPar(sl.pit()).getFont( + Text const & text = *sl.text(); + Font font = text.getPar(sl.pit()).getFont( bv().buffer()->params(), sl.pos(), outerFont(sl.pit(), text.paragraphs())); return font.language()->encoding(); } @@ -1264,20 +1438,38 @@ void Cursor::noUpdate() } -LyXFont Cursor::getFont() const +Font Cursor::getFont() const { + // The logic here should more or less match to the Text::setCurrentFont + // logic, i.e. the cursor height should give a hint what will happen + // if a character is entered. + // HACK. far from being perfect... - int s = 0; // go up until first non-0 text is hit // (innermost text is 0 in mathed) + int s = 0; for (s = depth() - 1; s >= 0; --s) if (operator[](s).text()) break; CursorSlice const & sl = operator[](s); - LyXText const & text = *sl.text(); - LyXFont font = text.getPar(sl.pit()).getFont( - bv().buffer()->params(), - sl.pos(), + 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) { + if (pos == sl.lastpos() + || (par.isSeparator(pos) && + !text.isRTLBoundary(buffer(), par, pos))) + --pos; + } + + // get font at the position + Font font = par.getFont(bv().buffer()->params(), pos, outerFont(sl.pit(), text.paragraphs())); return font; @@ -1286,50 +1478,30 @@ LyXFont Cursor::getFont() const void Cursor::fixIfBroken() { - // find out last good level - Cursor copy = *this; - size_t newdepth = depth(); - while (!copy.empty()) { - if (copy.idx() > copy.lastidx()) { - lyxerr << "wrong idx " << copy.idx() - << ", max is " << copy.lastidx() - << " at level " << copy.depth() - << ". Trying to correct this." << endl; - lyxerr << "old: " << *this << endl; - newdepth = copy.depth() - 1; - } - else if (copy.pit() > copy.lastpit()) { - lyxerr << "wrong pit " << copy.pit() - << ", max is " << copy.lastpit() - << " at level " << copy.depth() - << ". Trying to correct this." << endl; - lyxerr << "old: " << *this << endl; - newdepth = copy.depth() - 1; - } - else if (copy.pos() > copy.lastpos()) { - lyxerr << "wrong pos " << copy.pos() - << ", max is " << copy.lastpos() - << " at level " << copy.depth() - << ". Trying to correct this." << endl; - lyxerr << "old: " << *this << endl; - newdepth = copy.depth() - 1; - } - copy.pop(); - } - // shrink cursor to a size where everything is valid, possibly - // leaving insets - while (depth() > newdepth) { - pop(); - lyxerr << "correcting cursor to level " << depth() << endl; - lyxerr << "new: " << *this << endl; - clearSelection(); + if (DocIterator::fixIfBroken()) { + clearSelection(); + resetAnchor(); } } -bool Cursor::isRTL() const +bool notifyCursorLeaves(DocIterator const & old, Cursor & cur) { - return top().paragraph().isRightToLeftPar(bv().buffer()->params()); + // find inset in common + size_type i; + for (i = 0; i < old.depth() && i < cur.depth(); ++i) { + if (&old.inset() != &cur.inset()) + break; + } + + // notify everything on top of the common part in old cursor, + // but stop if the inset claims the cursor to be invalid now + for (; i < old.depth(); ++i) { + if (old[i].inset().notifyCursorLeaves(cur)) + return true; + } + + return false; }