X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Frowpainter.cpp;h=47ab84d3a2b5e7130eb47cebc276b3ebc8dae163;hb=e2982037d9f981a6070c3cf90148bde33edbfdc9;hp=cb48fb7f1a376ef85076ce207878412fda1836a9;hpb=eac118ae98f58b3c379a4679d17510c89dcf0d48;p=lyx.git diff --git a/src/rowpainter.cpp b/src/rowpainter.cpp index cb48fb7f1a..47ab84d3a2 100644 --- a/src/rowpainter.cpp +++ b/src/rowpainter.cpp @@ -38,6 +38,8 @@ #include "insets/InsetText.h" +#include "mathed/InsetMath.h" + #include "support/debug.h" #include "support/gettext.h" #include "support/textutils.h" @@ -60,9 +62,20 @@ RowPainter::RowPainter(PainterInfo & pi, row_(row), pit_(pit), par_(text.paragraphs()[pit]), pm_(text_metrics_.parMetrics(pit)), bidi_(bidi), change_(pi_.change_), - xo_(x), yo_(y), width_(text_metrics_.width()) + xo_(x), yo_(y), width_(text_metrics_.width()), + line_thickness_(1.0), line_offset_(2) { bidi_.computeTables(par_, pi_.base.bv->buffer(), row_); + + if (lyxrc.zoom >= 100) { + // derive the line thickness from zoom factor + // the zoom is given in percent + // (increase thickness at 150%, 250% etc.) + line_thickness_ = (float)(int((lyxrc.zoom + 50) / 100.0)); + // adjust line_offset_ too + line_offset_ = int(2 * line_thickness_) + 1; + } + x_ = row_.x + xo_; //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl; @@ -114,6 +127,8 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos) int const x1 = int(x_); pi_.base.bv->coordCache().insets().add(inset, x1, yo_); // insets are painted completely. Recursive + // FIXME: it is wrong to completely paint the background + // if we want to do single row painting. inset->drawBackground(pi_, x1, yo_); inset->drawSelection(pi_, x1, yo_); inset->draw(pi_, x1, yo_); @@ -209,9 +224,10 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, // This method takes up 70% of time when typing pos_type pos = bidi_.vis2log(vpos); // first character + char_type prev_char = par_.getChar(pos); vector str; str.reserve(100); - str.push_back(par_.getChar(pos)); + str.push_back(prev_char); if (arabic) { char_type c = str[0]; @@ -231,9 +247,23 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, bool const selection = (pos >= row_.sel_beg && pos < row_.sel_end) || pi_.selected; - char_type prev_char = ' '; + // spelling correct? + bool const spell_state = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + // collect as much similar chars as we can for (++vpos ; vpos < end ; ++vpos) { + // Work-around bug #6920 + // The bug can be reproduced with DejaVu font under Linux. + // The issue is that we compute the metrics character by character + // in ParagraphMetrics::singleWidth(); but we paint word by word + // for performance reason. + // Maybe a more general fix would be draw character by character + // for some predefined fonts on some platform. In arabic and + // Hebrew we already do paint this way. + if (prev_char == 'f') + break; + pos = bidi_.vis2log(vpos); if (pos < font_span.first || pos > font_span.last) break; @@ -243,6 +273,12 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, // Selection ends or starts here. break; + bool const new_spell_state = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + if (new_spell_state != spell_state) + // Spell checker state changed here. + break; + Change const & change = par_.lookupChange(pos); if (!change_running.isSimilarTo(change)) // Track change type or author has changed. @@ -250,10 +286,8 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, char_type c = par_.getChar(pos); - if (c == '\t' || prev_char == '\t') { - prev_char = c; + if (c == '\t') break; - } if (!isPrintableNonspace(c)) break; @@ -295,6 +329,7 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, } str.push_back(c); + prev_char = c; } docstring s(&str[0], str.size()); @@ -306,7 +341,7 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, x_ += pi_.pain.text(int(x_), yo_, s, font); return; } - + FontInfo copy = font; if (change_running.changed()) copy.setPaintColor(change_running.color()); @@ -327,19 +362,23 @@ void RowPainter::paintForeignMark(double orig_x, Language const * lang, if (lang == pi_.base.bv->buffer().params().language) return; - int const y = yo_ + 1 + desc; - pi_.pain.line(int(orig_x), y, int(x_), y, Color_language); + int const y = yo_ + 1 + desc + int(line_thickness_/2); + pi_.pain.line(int(orig_x), y, int(x_), y, Color_language, + Painter::line_solid, line_thickness_); } -void RowPainter::paintMisspelledMark(double orig_x, int desc) +void RowPainter::paintMisspelledMark(double orig_x, bool changed) { - int const y = yo_ + desc; - pi_.pain.line(int(orig_x), y, int(x_), y, Color_red, Painter::line_onoffdash, 0.5); + // if changed the misspelled marker gets placed slightly lower than normal + // to avoid drawing at the same vertical offset + int const y = yo_ + (changed ? int(line_thickness_ + 1.0) : 0) + line_offset_; + pi_.pain.line(int(orig_x), y, int(x_), y, Color_error, + Painter::line_onoffdash, line_thickness_); } -void RowPainter::paintFromPos(pos_type & vpos) +void RowPainter::paintFromPos(pos_type & vpos, bool changed) { pos_type const pos = bidi_.vis2log(vpos); Font const orig_font = text_metrics_.displayFont(pit_, pos); @@ -351,9 +390,13 @@ void RowPainter::paintFromPos(pos_type & vpos) // special case languages string const & lang = orig_font.language()->lang(); bool const hebrew = lang == "hebrew"; - bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" || + bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" || lang == "farsi"; + // spelling correct? + bool const misspelled = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + // draw as many chars as we can if ((!hebrew && !arabic) || (hebrew && !Encodings::isHebrewComposeChar(c)) @@ -367,8 +410,24 @@ void RowPainter::paintFromPos(pos_type & vpos) paintForeignMark(orig_x, orig_font.language()); - if (lyxrc.spellcheck_continuously && orig_font.isMisspelled()) - paintMisspelledMark(orig_x, 2); + if (lyxrc.spellcheck_continuously && misspelled) { + // check for cursor position + // don't draw misspelled marker for words at cursor position + // we don't want to disturb the process of text editing + BufferView const * bv = pi_.base.bv; + DocIterator const nw = bv->cursor().newWord(); + bool new_word = false; + if (!nw.empty() && par_.id() == nw.paragraph().id()) { + pos_type cpos = nw.pos(); + if (cpos > 0 && cpos == par_.size() && !par_.isWordSeparator(cpos-1)) + --cpos; + else if (cpos > 0 && par_.isWordSeparator(cpos)) + --cpos; + new_word = par_.isSameSpellRange(pos, cpos) ; + } + if (!new_word) + paintMisspelledMark(orig_x, changed); + } } @@ -710,18 +769,23 @@ void RowPainter::paintOnlyInsets() for (pos_type pos = row_.pos(); pos != end; ++pos) { // If outer row has changed, nested insets are repaint completely. Inset const * inset = par_.getInset(pos); - if (!inset) + bool const nested_inset = inset && + ((inset->asInsetMath() && + !inset->asInsetMath()->asMacroTemplate()) + || inset->asInsetText() + || inset->asInsetTabular()); + if (!nested_inset) continue; - if (x_ > pi_.base.bv->workWidth() + if (x_ > pi_.base.bv->workWidth() || !cache.getInsets().has(inset)) continue; x_ = cache.getInsets().x(inset); bool const pi_selected = pi_.selected; Cursor const & cur = pi_.base.bv->cursor(); - if (cur.selection() && cur.text() == &text_ + if (cur.selection() && cur.text() == &text_ && cur.normalAnchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; + pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; paintInset(inset, pos); pi_.selected = pi_selected; } @@ -731,7 +795,7 @@ void RowPainter::paintOnlyInsets() void RowPainter::paintText() { pos_type const end = row_.endpos(); - // Spaces at logical line breaks in bidi text must be skipped during + // Spaces at logical line breaks in bidi text must be skipped during // painting. However, they may appear visually in the middle // of a row; they must be skipped, wherever they are... // * logically "abc_[HEBREW_\nHEBREW]" @@ -822,9 +886,9 @@ void RowPainter::paintText() FontMetrics const & fm = theFontMetrics(pi_.base.bv->buffer().params().getFont()); int const y_bar = change_running.deleted() ? - yo_ - fm.maxAscent() / 3 : yo_ + fm.maxAscent() / 6; + yo_ - fm.maxAscent() / 3 : yo_ + line_offset_; pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, 0.5); + change_running.color(), Painter::line_solid, line_thickness_); // Change might continue with a different author or type if (change.changed() && !highly_editable_inset) { @@ -840,7 +904,7 @@ void RowPainter::paintText() x_ += row_.label_hfill + lwidth - width_pos; } - + // Is the inline completion in front of character? if (font.isRightToLeft() && vpos == inlineCompletionVPos) paintInlineCompletion(font); @@ -857,19 +921,19 @@ void RowPainter::paintText() } else if (inset) { // If outer row has changed, nested insets are repaint completely. pi_.base.bv->coordCache().insets().add(inset, int(x_), yo_); - + bool const pi_selected = pi_.selected; Cursor const & cur = pi_.base.bv->cursor(); - if (cur.selection() && cur.text() == &text_ + if (cur.selection() && cur.text() == &text_ && cur.normalAnchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; + pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; paintInset(inset, pos); pi_.selected = pi_selected; ++vpos; } else { // paint as many characters as possible. - paintFromPos(vpos); + paintFromPos(vpos, change_running.changed()); } // Is the inline completion after character? @@ -882,20 +946,118 @@ void RowPainter::paintText() FontMetrics const & fm = theFontMetrics(pi_.base.bv->buffer().params().getFont()); int const y_bar = change_running.deleted() ? - yo_ - fm.maxAscent() / 3 : yo_ + fm.maxAscent() / 6; + yo_ - fm.maxAscent() / 3 : yo_ + line_offset_; pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, 0.5); + change_running.color(), Painter::line_solid, line_thickness_); change_running.setUnchanged(); } } +void RowPainter::paintSelection() +{ + if (!row_.selection()) + return; + Cursor const & curs = pi_.base.bv->cursor(); + DocIterator beg = curs.selectionBegin(); + beg.pit() = pit_; + beg.pos() = row_.sel_beg; + + DocIterator end = curs.selectionEnd(); + end.pit() = pit_; + end.pos() = row_.sel_end; + + bool const begin_boundary = beg.pos() >= row_.endpos(); + bool const end_boundary = row_.sel_end == row_.endpos(); + + DocIterator cur = beg; + cur.boundary(begin_boundary); + int x1 = text_metrics_.cursorX(beg.top(), begin_boundary); + int x2 = text_metrics_.cursorX(end.top(), end_boundary); + int const y1 = yo_ - row_.ascent(); + int const y2 = y1 + row_.height(); + + int const rm = text_.isMainText() ? pi_.base.bv->rightMargin() : 0; + int const lm = text_.isMainText() ? pi_.base.bv->leftMargin() : 0; + + // draw the margins + if (row_.begin_margin_sel) { + if (text_.isRTL(beg.paragraph())) { + pi_.pain.fillRectangle(int(xo_ + x1), y1, + text_metrics_.width() - rm - x1, y2 - y1, Color_selection); + } else { + pi_.pain.fillRectangle(int(xo_ + lm), y1, x1 - lm, y2 - y1, + Color_selection); + } + } + + if (row_.end_margin_sel) { + if (text_.isRTL(beg.paragraph())) { + pi_.pain.fillRectangle(int(xo_ + lm), y1, x2 - lm, y2 - y1, + Color_selection); + } else { + pi_.pain.fillRectangle(int(xo_ + x2), y1, text_metrics_.width() - rm - x2, + y2 - y1, Color_selection); + } + } + + // if we are on a boundary from the beginning, it's probably + // a RTL boundary and we jump to the other side directly as this + // segement is 0-size and confuses the logic below + if (cur.boundary()) + cur.boundary(false); + + // go through row and draw from RTL boundary to RTL boundary + while (cur < end) { + bool draw_now = false; + + // simplified cursorForward code below which does not + // descend into insets and which does not go into the + // next line. Compare the logic with the original cursorForward + + // if left of boundary -> just jump to right side, but + // for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi + if (cur.boundary()) { + cur.boundary(false); + } else if (text_metrics_.isRTLBoundary(cur.pit(), cur.pos() + 1)) { + // in front of RTL boundary -> Stay on this side of the boundary + // because: ab|cDDEEFFghi -> abc|DDEEFFghi + ++cur.pos(); + cur.boundary(true); + draw_now = true; + } else { + // move right + ++cur.pos(); + + // line end? + if (cur.pos() == row_.endpos()) + cur.boundary(true); + } + + if (x1 == -1) { + // the previous segment was just drawn, now the next starts + x1 = text_metrics_.cursorX(cur.top(), cur.boundary()); + } + + if (!(cur < end) || draw_now) { + x2 = text_metrics_.cursorX(cur.top(), cur.boundary()); + pi_.pain.fillRectangle(int(xo_ + min(x1, x2)), y1, abs(x2 - x1), + y2 - y1, Color_selection); + + // reset x1, so it is set again next round (which will be on the + // right side of a boundary or at the selection end) + x1 = -1; + } + } +} + + void RowPainter::paintInlineCompletion(Font const & font) { docstring completion = pi_.base.bv->inlineCompletion(); FontInfo f = font.fontInfo(); bool rtl = font.isRightToLeft(); - + // draw the unique and the non-unique completion part // Note: this is not time-critical as it is // only done once per screen. @@ -904,7 +1066,7 @@ void RowPainter::paintInlineCompletion(Font const & font) docstring s2 = completion.substr(uniqueTo); ColorCode c1 = Color_inlinecompletion; ColorCode c2 = Color_nonunique_inlinecompletion; - + // right to left? if (rtl) { swap(s1, s2); @@ -916,7 +1078,7 @@ void RowPainter::paintInlineCompletion(Font const & font) pi_.pain.text(int(x_), yo_, s1, f); x_ += theFontMetrics(font).width(s1); } - + if (s2.size() > 0) { f.setColor(c2); pi_.pain.text(int(x_), yo_, s2, f);