]> git.lyx.org Git - lyx.git/blobdiff - src/BufferView.cpp
Fix bug #9813
[lyx.git] / src / BufferView.cpp
index e9824097d429d6b6c746b5df24fb3cfdd659c56f..663618af36f185d7d08439c72294763bf0b3abf8 100644 (file)
@@ -35,6 +35,7 @@
 #include "Language.h"
 #include "LaTeXFeatures.h"
 #include "LayoutFile.h"
+#include "Length.h"
 #include "Lexer.h"
 #include "LyX.h"
 #include "LyXAction.h"
@@ -225,13 +226,17 @@ enum ScreenUpdateStrategy {
 
 struct BufferView::Private
 {
-       Private(BufferView & bv): wh_(0), cursor_(bv),
+       Private(BufferView & bv) : update_strategy_(NoScreenUpdate),
+               wh_(0), cursor_(bv),
                anchor_pit_(0), anchor_ypos_(0),
                inlineCompletionUniqueChars_(0),
                last_inset_(0), clickable_inset_(false),
                mouse_position_cache_(),
-               bookmark_edit_position_(-1), gui_(0)
-       {}
+               bookmark_edit_position_(-1), gui_(0),
+               horiz_scroll_offset_(0)
+       {
+               xsel_cache_.set = false;
+       }
 
        ///
        ScrollbarParameters scrollbarParameters_;
@@ -295,6 +300,16 @@ struct BufferView::Private
 
        ///
        map<string, Inset *> edited_insets_;
+
+       /// When the row where the cursor lies is scrolled, this
+       /// contains the scroll offset
+       int horiz_scroll_offset_;
+       /// a slice pointing to the start of the row where the cursor
+       /// is (at last draw time)
+       CursorSlice current_row_slice_;
+       /// a slice pointing to the start of the row where cursor was
+       /// at previous draw event
+       CursorSlice last_row_slice_;
 };
 
 
@@ -399,7 +414,7 @@ Buffer const & BufferView::buffer() const
 }
 
 
-bool BufferView::fitCursor()
+bool BufferView::needsFitCursor() const
 {
        if (cursorStatus(d->cursor_) == CUR_INSIDE) {
                frontend::FontMetrics const & fm =
@@ -430,6 +445,7 @@ void BufferView::processUpdateFlags(Update::flags flags)
        // Now do the first drawing step if needed. This consists on updating
        // the CoordCache in updateMetrics().
        // The second drawing step is done in WorkArea::redraw() if needed.
+       // FIXME: is this still true now that Buffer::changed() is used all over?
 
        // Case when no explicit update is requested.
        if (!flags) {
@@ -447,7 +463,7 @@ void BufferView::processUpdateFlags(Update::flags flags)
        if (flags == Update::FitCursor
                || flags == (Update::Decoration | Update::FitCursor)) {
                // tell the frontend to update the screen if needed.
-               if (fitCursor()) {
+               if (needsFitCursor()) {
                        showCursor();
                        return;
                }
@@ -456,8 +472,10 @@ void BufferView::processUpdateFlags(Update::flags flags)
                        buffer_.changed(false);
                        return;
                }
-               // no screen update is needed.
+               // no screen update is needed in principle, but this
+               // could change if cursor row needs horizontal scrolling.
                d->update_strategy_ = NoScreenUpdate;
+               buffer_.changed(false);
                return;
        }
 
@@ -477,7 +495,7 @@ void BufferView::processUpdateFlags(Update::flags flags)
        // This is done at draw() time. So we need a redraw!
        buffer_.changed(false);
 
-       if (fitCursor()) {
+       if (needsFitCursor()) {
                // The cursor is off screen so ensure it is visible.
                // refresh it:
                showCursor();
@@ -793,7 +811,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
                // paragraph position which is computed at draw() time.
                // So we need a redraw!
                buffer_.changed(false);
-               if (fitCursor())
+               if (needsFitCursor())
                        showCursor();
        }
 
@@ -971,7 +989,7 @@ void BufferView::updateDocumentClass(DocumentClassConstPtr olddc)
 }
 
 
-/** Return the change status at cursor position, taking in account the
+/** Return the change status at cursor position, taking into account the
  * status at each level of the document iterator (a table in a deleted
  * footnote is deleted).
  * When \param outer is true, the top slice is not looked at.
@@ -1097,13 +1115,13 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
                iss >> opt;
                flag.setEnabled(opt.repl_buf_name.empty()
                                || !buffer_.isReadonly());
+               break;
        }
 
-       case LFUN_LABEL_GOTO: {
+       case LFUN_LABEL_GOTO:
                flag.setEnabled(!cmd.argument().empty()
                    || getInsetByCode<InsetRef>(cur, REF_CODE));
                break;
-       }
 
        case LFUN_CHANGES_TRACK:
                flag.setEnabled(true);
@@ -1221,6 +1239,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
        string const argument = to_utf8(cmd.argument());
        Cursor & cur = d->cursor_;
+       Cursor old = cur;
 
        // Don't dispatch function that does not apply to internal buffers.
        if (buffer_.isInternal()
@@ -1236,7 +1255,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
        case LFUN_BUFFER_PARAMS_APPLY: {
                DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
-               cur.recordUndoFullDocument();
+               cur.recordUndoBufferParams();
                istringstream ss(to_utf8(cmd.argument()));
                Lexer lex;
                lex.setStream(ss);
@@ -1257,7 +1276,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
        }
 
        case LFUN_LAYOUT_MODULES_CLEAR: {
-               cur.recordUndoFullDocument();
+               // FIXME: this modifies the document in cap::switchBetweenClasses
+               //  without calling recordUndo. Fix this before using
+               //  recordUndoBufferParams().
+               cur.recordUndoFullBuffer();
                buffer_.params().clearLayoutModules();
                makeDocumentClass();
                dr.screenUpdate(Update::Force);
@@ -1273,7 +1295,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                                "conflicts with installed modules.");
                        break;
                }
-               cur.recordUndoFullDocument();
+               // FIXME: this modifies the document in cap::switchBetweenClasses
+               //  without calling recordUndo. Fix this before using
+               //  recordUndoBufferParams().
+               cur.recordUndoFullBuffer();
                buffer_.params().addLayoutModule(argument);
                makeDocumentClass();
                dr.screenUpdate(Update::Force);
@@ -1302,7 +1327,10 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        break;
 
                // Save the old, possibly modular, layout for use in conversion.
-               cur.recordUndoFullDocument();
+               // FIXME: this modifies the document in cap::switchBetweenClasses
+               //  without calling recordUndo. Fix this before using
+               //  recordUndoBufferParams().
+               cur.recordUndoFullBuffer();
                buffer_.params().setBaseClass(argument);
                makeDocumentClass();
                dr.screenUpdate(Update::Force);
@@ -1390,20 +1418,21 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                for (Buffer * b = &buffer_; i == 0 || b != &buffer_;
                        b = theBufferList().next(b)) {
 
-                       DocIterator dit = b->getParFromID(id);
-                       if (dit.atEnd()) {
+                       Cursor cur(*this);
+                       cur.setCursor(b->getParFromID(id));
+                       if (cur.atEnd()) {
                                LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "].");
                                ++i;
                                continue;
                        }
-                       LYXERR(Debug::INFO, "Paragraph " << dit.paragraph().id()
+                       LYXERR(Debug::INFO, "Paragraph " << cur.paragraph().id()
                                << " found in buffer `"
                                << b->absFileName() << "'.");
 
                        if (b == &buffer_) {
                                // Set the cursor
-                               dit.pos() = pos;
-                               setCursor(dit);
+                               cur.pos() = pos;
+                               mouseSetCursor(cur);
                                dr.screenUpdate(Update::Force | Update::FitCursor);
                        } else {
                                // Switch to other buffer view and resend cmd
@@ -1719,7 +1748,6 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        p = Point(0, 0);
                if (act == LFUN_SCREEN_DOWN && scrolled < height_)
                        p = Point(width_, height_);
-               Cursor old = cur;
                bool const in_texted = cur.inTexted();
                cur.setCursor(doc_iterator_begin(cur.buffer()));
                cur.selHandle(false);
@@ -2013,7 +2041,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                if (!newL || oldL == newL)
                        break;
                if (oldL->rightToLeft() == newL->rightToLeft()) {
-                       cur.recordUndoFullDocument();
+                       cur.recordUndoFullBuffer();
                        buffer_.changeLanguage(oldL, newL);
                        cur.setCurrentFont();
                        dr.forceBufferUpdate();
@@ -2041,6 +2069,21 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
        buffer_.undo().endUndoGroup();
        dr.dispatched(dispatched);
+
+       // NOTE: The code below is copied from Cursor::dispatch. If you
+       // need to modify this, please update the other one too.
+
+       // notify insets we just entered/left
+       if (cursor() != old) {
+               old.beginUndoGroup();
+               old.fixIfBroken();
+               bool badcursor = notifyCursorLeavesOrEnters(old, cursor());
+               if (badcursor) {
+                       cursor().fixIfBroken();
+                       resetInlineCompletionPos();
+               }
+               old.endUndoGroup();
+       }
 }
 
 
@@ -2206,6 +2249,15 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
 
        // Build temporary cursor.
        Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
+       if (inset) {
+               // If inset is not editable, cur.pos() might point behind the
+               // inset (depending on cmd.x(), cmd.y()). This is needed for
+               // editing to fix bug 9628, but e.g. the context menu needs a
+               // cursor in front of the inset.
+               if (inset->hasSettings() &&
+                   cur.nextInset() != inset && cur.prevInset() == inset)
+                       cur.backwardPos();
+       }
 
        // Put anchor at the same position.
        cur.resetAnchor();
@@ -2313,13 +2365,19 @@ int BufferView::scrollUp(int offset)
 
 
 void BufferView::setCursorFromRow(int row)
+{
+       setCursorFromRow(row, buffer_.texrow());
+}
+
+
+void BufferView::setCursorFromRow(int row, TexRow const & texrow)
 {
        int tmpid;
        int tmppos;
        pit_type newpit = 0;
        pos_type newpos = 0;
 
-       buffer_.texrow().getIdFromRow(row, tmpid, tmppos);
+       texrow.getIdFromRow(row, tmpid, tmppos);
 
        bool posvalid = (tmpid != -1);
        if (posvalid) {
@@ -2389,9 +2447,9 @@ void BufferView::gotoLabel(docstring const & label)
                Buffer const * buf = *it;
 
                // find label
-               Toc & toc = buf->tocBackend().toc("label");
-               TocIterator toc_it = toc.begin();
-               TocIterator end = toc.end();
+               shared_ptr<Toc> toc = buf->tocBackend().toc("label");
+               TocIterator toc_it = toc->begin();
+               TocIterator end = toc->end();
                for (; toc_it != end; ++toc_it) {
                        if (label == toc_it->str()) {
                                lyx::dispatch(toc_it->action());
@@ -2442,6 +2500,7 @@ void BufferView::setCursor(DocIterator const & dit)
 
        d->cursor_.setCursor(dit);
        d->cursor_.setSelection(false);
+       d->cursor_.setCurrentFont();
        // FIXME
        // It seems on general grounds as if this is probably needed, but
        // it is not yet clear.
@@ -2469,7 +2528,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old)
        d->cursor_ = cur;
 
        // we would rather not do this here, but it needs to be done before
-       // the changed() signal is sent.
+       // the changed() signal is sent.
        buffer_.updateBuffer();
 
        buffer_.changed(true);
@@ -2758,7 +2817,7 @@ Point BufferView::coordOffset(DocIterator const & dit) const
        int lastw = 0;
 
        // Addup contribution of nested insets, from inside to outside,
-       // keeping the outer paragraph for a special handling below
+       // keeping the outer paragraph for a special handling below
        for (size_t i = dit.depth() - 1; i >= 1; --i) {
                CursorSlice const & sl = dit[i];
                int xx = 0;
@@ -2788,8 +2847,7 @@ Point BufferView::coordOffset(DocIterator const & dit) const
                        // FIXME (Abdel 23/09/2007): this is a bit messy because of the
                        // elimination of Inset::dim_ cache. This coordOffset() method needs
                        // to be rewritten in light of the new design.
-                       Dimension const & dim = parMetrics(dit[i - 1].text(),
-                               dit[i - 1].pit()).insetDimension(&sl.inset());
+                       Dimension const & dim = coordCache().getInsets().dim(&sl.inset());
                        lastw = dim.wid;
                } else {
                        Dimension const dim = sl.inset().dimension(*this);
@@ -2889,6 +2947,96 @@ bool BufferView::cursorInView(Point const & p, int h) const
 }
 
 
+int BufferView::horizScrollOffset() const
+{
+       return d->horiz_scroll_offset_;
+}
+
+
+CursorSlice const & BufferView::currentRowSlice() const
+{
+       return d->current_row_slice_;
+}
+
+
+CursorSlice const & BufferView::lastRowSlice() const
+{
+       return d->last_row_slice_;
+}
+
+
+void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
+{
+       // nothing to do if the cursor was already on this row
+       if (d->current_row_slice_ == rowSlice) {
+               d->last_row_slice_ = CursorSlice();
+               return;
+       }
+
+       // if the (previous) current row was scrolled, we have to
+       // remember it in order to repaint it next time.
+       if (d->horiz_scroll_offset_ != 0)
+               d->last_row_slice_ = d->current_row_slice_;
+       else
+               d->last_row_slice_ = CursorSlice();
+
+       // Since we changed row, the scroll offset is not valid anymore
+       d->horiz_scroll_offset_ = 0;
+       d->current_row_slice_ = rowSlice;
+}
+
+
+void BufferView::checkCursorScrollOffset()
+{
+       CursorSlice rowSlice = d->cursor_.bottom();
+       TextMetrics const & tm = textMetrics(rowSlice.text());
+
+       // Stop if metrics have not been computed yet, since it means
+       // that there is nothing to do.
+       if (!tm.contains(rowSlice.pit()))
+               return;
+       ParagraphMetrics const & pm = tm.parMetrics(rowSlice.pit());
+       Row const & row = pm.getRow(rowSlice.pos(),
+                                   d->cursor_.boundary() && rowSlice == d->cursor_.top());
+       rowSlice.pos() = row.pos();
+
+       // Set the row on which the cursor lives.
+       setCurrentRowSlice(rowSlice);
+
+       // Current x position of the cursor in pixels
+       int const cur_x = getPos(d->cursor_).x_;
+
+       // Horizontal scroll offset of the cursor row in pixels
+       int offset = d->horiz_scroll_offset_;
+       int const MARGIN = 2 * theFontMetrics(d->cursor_.real_current_font).em();
+       //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", margin=" << MARGIN << endl;
+       if (cur_x < offset + MARGIN) {
+               // scroll right
+               offset = cur_x - MARGIN;
+       } else if (cur_x > offset + workWidth() - MARGIN) {
+               // scroll left
+               offset = cur_x - workWidth() + MARGIN;
+       }
+
+       if (offset < row.left_margin || row.width() <= workWidth())
+               offset = 0;
+
+       if (offset != d->horiz_scroll_offset_)
+               LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
+                      << d->horiz_scroll_offset_ << " to " << offset);
+
+       if (d->update_strategy_ == NoScreenUpdate
+           && (offset != d->horiz_scroll_offset_
+               || !d->last_row_slice_.empty())) {
+               // FIXME: if one uses SingleParUpdate, then home/end
+               // will not work on long rows. Why?
+               d->update_strategy_ = FullScreenUpdate;
+       }
+
+       d->horiz_scroll_offset_ = offset;
+}
+
+
 void BufferView::draw(frontend::Painter & pain)
 {
        if (height_ == 0 || width_ == 0)
@@ -2900,22 +3048,28 @@ void BufferView::draw(frontend::Painter & pain)
        int const y = tm.first().second->position();
        PainterInfo pi(this, pain);
 
+       // Check whether the row where the cursor lives needs to be scrolled.
+       // Update the drawing strategy if needed.
+       checkCursorScrollOffset();
+
        switch (d->update_strategy_) {
 
        case NoScreenUpdate:
                // If no screen painting is actually needed, only some the different
                // coordinates of insets and paragraphs needs to be updated.
+               LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
                pi.full_repaint = true;
                pi.pain.setDrawingEnabled(false);
-               tm.draw(pi, 0, y);
+               tm.draw(pi, 0, y);
                break;
 
        case SingleParUpdate:
                pi.full_repaint = false;
+               LYXERR(Debug::PAINTING, "Strategy: SingleParUpdate");
                // In general, only the current row of the outermost paragraph
                // will be redrawn. Particular cases where selection spans
                // multiple paragraph are correctly detected in TextMetrics.
-               tm.draw(pi, 0, y);
+               tm.draw(pi, 0, y);
                break;
 
        case DecorationUpdate:
@@ -2924,6 +3078,12 @@ void BufferView::draw(frontend::Painter & pain)
                // because of the single backing pixmap.
 
        case FullScreenUpdate:
+
+               LYXERR(Debug::PAINTING,
+                      ((d->update_strategy_ == FullScreenUpdate)
+                       ? "Strategy: FullScreenUpdate"
+                       : "Strategy: DecorationUpdate"));
+
                // The whole screen, including insets, will be refreshed.
                pi.full_repaint = true;