diff --git a/src/BufferView.cpp b/src/BufferView.cpp index e9824097d4..9d42f3d64c 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -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" @@ -230,7 +232,8 @@ struct BufferView::Private 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 +298,16 @@ struct BufferView::Private /// map 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 +469,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; } @@ -2889,6 +2904,105 @@ 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); + + // 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); + + // 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(workWidth()); + 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,11 +3014,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); @@ -2912,6 +3031,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. @@ -2924,6 +3044,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; diff --git a/src/BufferView.h b/src/BufferView.h index a144aee136..6aa2f06777 100644 --- a/src/BufferView.h +++ b/src/BufferView.h @@ -32,6 +32,7 @@ class Buffer; class Change; class CoordCache; class Cursor; +class CursorSlice; class DispatchResult; class DocIterator; class DocumentClass; @@ -39,6 +40,7 @@ class FuncRequest; class FuncStatus; class Intl; class Inset; +class PainterInfo; class ParIterator; class ParagraphMetrics; class Point; @@ -118,6 +120,17 @@ public: /// move the screen to fit the cursor. /// Only to be called with good y coordinates (after a bv::metrics) bool fitCursor(); + + // Returns the amount of horizontal scrolling applied to the + // top-level row where the cursor lies + int horizScrollOffset() const; + + // Points to the top-level row where the cursor lies (during draw). + CursorSlice const & currentRowSlice() const; + + // Points to the top-level row where the cursor lied at last draw event. + CursorSlice const & lastRowSlice() const; + /// reset the scrollbar to reflect current view position. void updateScrollbar(); /// return the Scrollbar Parameters. @@ -331,6 +344,13 @@ private: /// \return true if no further update is needed. bool singleParUpdate(); + // Set the row on which the cursor lives. + void setCurrentRowSlice(CursorSlice const & rowSlice); + + // Check whether the row where the cursor lives needs to be scrolled. + // Update the drawing strategy if needed. + void checkCursorScrollOffset(PainterInfo & pi); + /// The minimal size of the document that is visible. Used /// when it is allowed to scroll below the document. int minVisiblePart(); diff --git a/src/Color.cpp b/src/Color.cpp index 31d5f62697..161ebbad09 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -238,6 +238,7 @@ ColorSet::ColorSet() { Color_urllabel, N_("URL label"), "urllabel", "blue", "urllabel" }, { Color_urltext, N_("URL text"), "urltext", "blue", "urltext" }, { Color_depthbar, N_("depth bar"), "depthbar", "IndianRed", "depthbar" }, + { Color_scroll, N_("scroll indicator"), "scroll", "IndianRed", "scroll" }, { Color_language, N_("language"), "language", "Blue", "language" }, { Color_command, N_("command inset"), "command", "black", "command" }, { Color_commandbg, N_("command inset background"), "commandbg", "azure", "commandbg" }, diff --git a/src/ColorCode.h b/src/ColorCode.h index 2338995f7d..4d391c0081 100644 --- a/src/ColorCode.h +++ b/src/ColorCode.h @@ -91,6 +91,8 @@ enum ColorCode { /// Color for the depth bars in the margin Color_depthbar, + /// Color that indicates when a row can be scrolled + Color_scroll, /// Color for marking foreign language words Color_language, diff --git a/src/CursorSlice.h b/src/CursorSlice.h index d703cb4b34..fa7af6ad17 100644 --- a/src/CursorSlice.h +++ b/src/CursorSlice.h @@ -65,6 +65,8 @@ public: friend bool operator<=(CursorSlice const &, CursorSlice const &); //@} + /// return true if the slice has not been initialized + bool empty() const { return !inset_; } /// the current inset Inset & inset() const { return *inset_; } /// return the cell this cursor is in diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp index ab42e675b4..83ff825796 100644 --- a/src/RowPainter.cpp +++ b/src/RowPainter.cpp @@ -439,6 +439,23 @@ int RowPainter::paintAppendixStart(int y) } +void RowPainter::paintTooLargeMarks(bool const left, bool const right) +{ + if (left) + pi_.pain.line(dotted_line_thickness_, yo_ - row_.ascent(), + dotted_line_thickness_, yo_ + row_.descent(), + Color_scroll, + Painter::line_onoffdash, dotted_line_thickness_); + if (right) { + int const wwidth = pi_.base.bv->workWidth() - dotted_line_thickness_; + pi_.pain.line(wwidth, yo_ - row_.ascent(), + wwidth, yo_ + row_.descent(), + Color_scroll, + Painter::line_onoffdash, dotted_line_thickness_); + } +} + + void RowPainter::paintFirst() { BufferParams const & bparams = pi_.base.bv->buffer().params(); diff --git a/src/RowPainter.h b/src/RowPainter.h index aebd73e20e..0628f3288f 100644 --- a/src/RowPainter.h +++ b/src/RowPainter.h @@ -66,6 +66,7 @@ public: void paintAppendix(); void paintDepthBar(); void paintChangeBar(); + void paintTooLargeMarks(bool const left, bool const right); void paintFirst(); void paintLast(); void paintText(); diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index b8771b23e6..f29202c668 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -1803,7 +1803,7 @@ void TextMetrics::draw(PainterInfo & pi, int x, int y) const } -void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const +void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const x, int y) const { BufferParams const & bparams = bv_->buffer().params(); ParagraphMetrics const & pm = par_metrics_[pit]; @@ -1843,14 +1843,25 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co for (size_t i = 0; i != nrows; ++i) { Row const & row = pm.rows()[i]; + int row_x = x; if (i) y += row.ascent(); + CursorSlice rowSlice(const_cast(text_->inset())); + rowSlice.pit() = pit; + rowSlice.pos() = row.pos(); + bool const inside = (y + row.descent() >= 0 && y - row.ascent() < ww); + + // Adapt to cursor row scroll offset if applicable. + if (bv_->currentRowSlice() == rowSlice) + row_x -= bv_->horizScrollOffset(); + // It is not needed to draw on screen if we are not inside. pi.pain.setDrawingEnabled(inside && original_drawing_state); - RowPainter rp(pi, *text_, pit, row, x, y); + + RowPainter rp(pi, *text_, pit, row, row_x, y); if (selection) row.setSelectionAndMargins(sel_beg_par, sel_end_par); @@ -1868,7 +1879,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co // Row signature; has row changed since last paint? row.setCrc(pm.computeRowSignature(row, bparams)); - bool row_has_changed = row.changed(); + bool row_has_changed = row.changed() + || rowSlice == bv_->lastRowSlice(); // Take this opportunity to spellcheck the row contents. if (row_has_changed && lyxrc.spellcheck_continuously) { @@ -1888,7 +1900,10 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co // Clear background of this row if paragraph background was not // already cleared because of a full repaint. if (!pi.full_repaint && row_has_changed) { - pi.pain.fillRectangle(x, y - row.ascent(), + LYXERR(Debug::PAINTING, "Clear rect@(" + << max(row_x, 0) << ", " << y - row.ascent() << ")=" + << width() << " x " << row.height()); + pi.pain.fillRectangle(max(row_x, 0), y - row.ascent(), width(), row.height(), pi.background_color); } @@ -1923,6 +1938,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co rp.paintLast(); if (i == 0 && is_rtl) rp.paintFirst(); + rp.paintTooLargeMarks(row_x < 0, + row_x + row.width() > bv_->workWidth()); y += row.descent(); // Restore full_repaint status. diff --git a/src/frontends/qt4/GuiWorkArea.cpp b/src/frontends/qt4/GuiWorkArea.cpp index 185eba06ce..1656d4d416 100644 --- a/src/frontends/qt4/GuiWorkArea.cpp +++ b/src/frontends/qt4/GuiWorkArea.cpp @@ -640,6 +640,12 @@ void GuiWorkArea::Private::showCursor() && !completer_->inlineVisible(); cursor_visible_ = true; cursor_->recomputeWidth(); + + //int cur_x = buffer_view_->getPos(cur).x_; + // We may have decided to slide the cursor row so that cursor + // is visible. + p.x_ -= buffer_view_->horizScrollOffset(); + showCursor(p.x_, p.y_, h, l_shape, isrtl, completable); } diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp index 6e69a03a7c..8f86888df5 100644 --- a/src/insets/InsetTabular.cpp +++ b/src/insets/InsetTabular.cpp @@ -3456,14 +3456,14 @@ docstring InsetTableCell::xhtml(XHTMLStream & xs, OutputParams const & rp) const InsetTabular::InsetTabular(Buffer * buf, row_type rows, col_type columns) - : Inset(buf), tabular(buf, max(rows, row_type(1)), max(columns, col_type(1))), scx_(0), - rowselect_(false), colselect_(false) + : Inset(buf), tabular(buf, max(rows, row_type(1)), max(columns, col_type(1))), + rowselect_(false), colselect_(false) { } InsetTabular::InsetTabular(InsetTabular const & tab) - : Inset(tab), tabular(tab.tabular), scx_(0) + : Inset(tab), tabular(tab.tabular) { } @@ -3714,11 +3714,10 @@ bool InsetTabular::isCellSelected(Cursor & cur, row_type row, col_type col) void InsetTabular::draw(PainterInfo & pi, int x, int y) const { - x += scx_ + ADD_TO_TABULAR_WIDTH; + x += ADD_TO_TABULAR_WIDTH; BufferView * bv = pi.base.bv; Cursor & cur = pi.base.bv->cursor(); - resetPos(cur); // FIXME: As the full background is painted in drawBackground(), // we have no choice but to do a full repaint for the Text cells. @@ -3766,7 +3765,7 @@ void InsetTabular::draw(PainterInfo & pi, int x, int y) const void InsetTabular::drawBackground(PainterInfo & pi, int x, int y) const { - x += scx_ + ADD_TO_TABULAR_WIDTH; + x += ADD_TO_TABULAR_WIDTH; y += offset_valign_ - tabular.rowAscent(0); pi.pain.fillRectangle(x, y, tabular.width(), tabular.height(), pi.backgroundColor(this)); @@ -3776,9 +3775,8 @@ void InsetTabular::drawBackground(PainterInfo & pi, int x, int y) const void InsetTabular::drawSelection(PainterInfo & pi, int x, int y) const { Cursor & cur = pi.base.bv->cursor(); - resetPos(cur); - x += scx_ + ADD_TO_TABULAR_WIDTH; + x += ADD_TO_TABULAR_WIDTH; if (!cur.selection()) return; @@ -3893,7 +3891,6 @@ void InsetTabular::edit(Cursor & cur, bool front, EntryDirection) } cur.setCurrentFont(); // FIXME: this accesses the position cache before it is initialized - //resetPos(cur); //cur.bv().fitCursor(); } @@ -5083,7 +5080,6 @@ void InsetTabular::cursorPos(BufferView const & bv, x += cellXPos(sl.idx()); x += tabular.textHOffset(sl.idx()); x += ADD_TO_TABULAR_WIDTH; - x += scx_; } @@ -5124,7 +5120,6 @@ Inset * InsetTabular::editXY(Cursor & cur, int x, int y) cur.setSelection(false); cur.push(*this); cur.idx() = getNearestCell(cur.bv(), x, y); - resetPos(cur); return cur.bv().textMetrics(&cell(cur.idx())->text()).editXY(cur, x, y); } @@ -5174,36 +5169,6 @@ int InsetTabular::cellXPos(idx_type const cell) const } -void InsetTabular::resetPos(Cursor & cur) const -{ - BufferView & bv = cur.bv(); - int const maxwidth = bv.workWidth(); - - int const scx_old = scx_; - int const i = cur.find(this); - if (i == -1) { - scx_ = 0; - } else { - int const X1 = 0; - int const X2 = maxwidth; - int const offset = ADD_TO_TABULAR_WIDTH + 2; - int const x1 = xo(cur.bv()) + cellXPos(cur[i].idx()) + offset; - int const x2 = x1 + tabular.cellWidth(cur[i].idx()); - - if (x1 < X1) - scx_ = X1 + 20 - x1; - else if (x2 > X2) - scx_ = X2 - 20 - x2; - else - scx_ = 0; - } - - // only update if offset changed - if (scx_ != scx_old) - cur.screenUpdateFlags(Update::Force | Update::FitCursor); -} - - void InsetTabular::moveNextCell(Cursor & cur, EntryDirection entry_from) { row_type const row = tabular.cellRow(cur.idx()); @@ -5238,7 +5203,6 @@ void InsetTabular::moveNextCell(Cursor & cur, EntryDirection entry_from) if (cur.selIsMultiCell()) { cur.pit() = cur.lastpit(); cur.pos() = cur.lastpos(); - resetPos(cur); return; } @@ -5261,7 +5225,6 @@ void InsetTabular::moveNextCell(Cursor & cur, EntryDirection entry_from) } cur.setCurrentFont(); - resetPos(cur); } @@ -5296,7 +5259,6 @@ void InsetTabular::movePrevCell(Cursor & cur, EntryDirection entry_from) if (cur.selIsMultiCell()) { cur.pit() = cur.lastpit(); cur.pos() = cur.lastpos(); - resetPos(cur); return; } @@ -5319,7 +5281,6 @@ void InsetTabular::movePrevCell(Cursor & cur, EntryDirection entry_from) } cur.setCurrentFont(); - resetPos(cur); } diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h index 0aac6420ec..7be8bb639a 100644 --- a/src/insets/InsetTabular.h +++ b/src/insets/InsetTabular.h @@ -1003,8 +1003,6 @@ private: /// int cellYPos(idx_type cell) const; /// - void resetPos(Cursor & cur) const; - /// bool copySelection(Cursor & cur); /// bool pasteClipboard(Cursor & cur); @@ -1027,8 +1025,6 @@ private: col_type col_start, col_type col_end) const; /// mutable idx_type first_visible_cell; - /// - mutable int scx_; /// The vertical offset of the table due to the vertical /// alignment with respect to the baseline. mutable int offset_valign_;