]> git.lyx.org Git - lyx.git/blobdiff - src/BufferView.cpp
Fix bug #8258 (Zoom of editor doesn't zoom instant preview)
[lyx.git] / src / BufferView.cpp
index faa08f0320310cd5a3eb4781d8f273f3d755b980..8a947c169fb12e30e80f6aff2bd8065a0406cd69 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"
@@ -45,6 +46,7 @@
 #include "Paragraph.h"
 #include "ParagraphParameters.h"
 #include "ParIterator.h"
+#include "RowPainter.h"
 #include "Session.h"
 #include "Text.h"
 #include "TextClass.h"
@@ -225,12 +227,14 @@ 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)
        {}
 
        ///
@@ -295,6 +299,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_;
 };
 
 
@@ -456,8 +470,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 scrolling.
                d->update_strategy_ = NoScreenUpdate;
+               buffer_.changed(false);
                return;
        }
 
@@ -1087,6 +1103,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
        case LFUN_KEYMAP_PRIMARY:
        case LFUN_KEYMAP_SECONDARY:
        case LFUN_KEYMAP_TOGGLE:
+       case LFUN_INSET_SELECT_ALL:
                flag.setEnabled(true);
                break;
 
@@ -1096,13 +1113,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);
@@ -1235,7 +1252,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);
@@ -1256,7 +1273,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);
@@ -1272,7 +1292,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);
@@ -1301,7 +1324,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);
@@ -1389,20 +1415,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
@@ -1721,6 +1748,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                Cursor old = cur;
                bool const in_texted = cur.inTexted();
                cur.setCursor(doc_iterator_begin(cur.buffer()));
+               if (cur != old)
+                       notifyCursorLeavesOrEnters(old, cur);
                cur.selHandle(false);
                buffer_.changed(true);
                updateHoveredInset();
@@ -1800,6 +1829,41 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
        }
 
 
+       case LFUN_INSET_SELECT_ALL:
+               if (cur.depth() > 1
+                   && cur.selBegin().at_begin()
+                   && cur.selEnd().at_end()) {
+                       // All the contents of the inset if selected.
+                       // Select the inset from outside.
+                       cur.pop();
+                       cur.resetAnchor();
+                       cur.setSelection(true);
+                       cur.posForward();
+               } else if (cur.selBegin().idx() != cur.selEnd().idx()
+                          || (cur.depth() > 1
+                                  && cur.selBegin().at_cell_begin()
+                              && cur.selEnd().at_cell_end())) {
+                       // At least one complete cell is selected.
+                       // Select all cells
+                       cur.idx() = 0;
+                       cur.pos() = 0;
+                       cur.resetAnchor();
+                       cur.setSelection(true);
+                       cur.idx() = cur.lastidx();
+                       cur.pos() = cur.lastpos();
+               } else {
+                       // select current cell
+                       cur.pit() = 0;
+                       cur.pos() = 0;
+                       cur.resetAnchor();
+                       cur.setSelection(true);
+                       cur.pit() = cur.lastpit();
+                       cur.pos() = cur.lastpos();
+               }
+               dr.screenUpdate(Update::Force);
+               break;
+
+
        // This would be in Buffer class if only Cursor did not
        // require a bufferview
        case LFUN_INSET_FORALL: {
@@ -1977,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();
@@ -2853,6 +2917,119 @@ 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(PainterInfo & pi)
+{
+       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);
+
+       /** FIXME: the code below adds an extraneous computation of inset
+        * positions, and can therefore be bad for performance (think for
+        * example about a very large tabular inset. Redawing the row
+        * where it is means redrawing the whole screen).
+        *
+        * The bug that this fixes is the following: assume that there is
+        * a very large math inset. Upon entering the inset, when pressing
+        * `End', the row is not scrolled and the cursor is not visible. I
+        * am not sure why the extra row computation fixes the problem,
+        * actually.
+        *
+        * A proper fix should be found and this code should be removed.
+        */
+       // Force the recomputation of inset positions
+       bool const drawing = pi.pain.isDrawingEnabled();
+       pi.pain.setDrawingEnabled(false);
+       // No need to care about vertical position.
+       RowPainter rp(pi, buffer().text(), d->cursor_.bottom().pit(), row,
+                     -d->horiz_scroll_offset_, 0);
+       rp.paintText();
+       pi.pain.setDrawingEnabled(drawing);
+       /** END of bad code */
+
+       // 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 = Length(2, Length::EM).inPixels(pi.base);
+       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)
@@ -2864,11 +3041,16 @@ 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(pi);
+
        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);
@@ -2876,6 +3058,7 @@ void BufferView::draw(frontend::Painter & pain)
 
        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.
@@ -2888,6 +3071,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;