]> git.lyx.org Git - lyx.git/blobdiff - src/Cursor.cpp
Fixed some lines that were too long. It compiled afterwards.
[lyx.git] / src / Cursor.cpp
index c49673cf4ca9a57b250d3018cc3170a955cb08c0..9bfbada8a986a651d08f9db269f635a73673feac 100644 (file)
@@ -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 <config.h>
 
+#include "Bidi.h"
 #include "BufferView.h"
 #include "bufferview_funcs.h"
 #include "Buffer.h"
 #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"
 #include <limits>
 #include <map>
 
-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<InsetMathScript*>(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<InsetMathScript*>(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;
 }