X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Frowpainter.cpp;h=5810fd66e5c4726ceea15c59fa20bf6094936b98;hb=90f7007a2e6c78ffd031e4636ff909ab1bc2ddec;hp=16c3ad7250f7c20bf89693468a68ccfd162a0e98;hpb=29c40527e56424d9c66ffb2e6c9709d8e4a30e1e;p=lyx.git diff --git a/src/rowpainter.cpp b/src/rowpainter.cpp index 16c3ad7250..5810fd66e5 100644 --- a/src/rowpainter.cpp +++ b/src/rowpainter.cpp @@ -22,7 +22,6 @@ #include "BufferView.h" #include "Changes.h" #include "Encoding.h" -#include "support/gettext.h" #include "Language.h" #include "Layout.h" #include "LyXRC.h" @@ -30,7 +29,6 @@ #include "MetricsInfo.h" #include "Paragraph.h" #include "ParagraphMetrics.h" -#include "paragraph_funcs.h" #include "ParagraphParameters.h" #include "TextMetrics.h" #include "VSpace.h" @@ -40,7 +38,10 @@ #include "insets/InsetText.h" +#include "mathed/InsetMath.h" + #include "support/debug.h" +#include "support/gettext.h" #include "support/textutils.h" #include "support/lassert.h" @@ -53,6 +54,7 @@ namespace lyx { using frontend::Painter; using frontend::FontMetrics; + RowPainter::RowPainter(PainterInfo & pi, Text const & text, pit_type pit, Row const & row, Bidi & bidi, int x, int y) : pi_(pi), text_(text), @@ -61,9 +63,29 @@ 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()), + solid_line_thickness_(1.0), solid_line_offset_(1), + dotted_line_thickness_(1.0), dotted_line_offset_(2) { bidi_.computeTables(par_, pi_.base.bv->buffer(), row_); + + if (lyxrc.zoom >= 200) { + // derive the line thickness from zoom factor + // the zoom is given in percent + // (increase thickness at 250%, 450% etc.) + solid_line_thickness_ = (float)(int((lyxrc.zoom + 50) / 200.0)); + // adjust line_offset_ too + solid_line_offset_ = 1 + int(0.5 * solid_line_thickness_); + } + if (lyxrc.zoom >= 100) { + // derive the line thickness from zoom factor + // the zoom is given in percent + // (increase thickness at 150%, 250% etc.) + dotted_line_thickness_ = (float)(int((lyxrc.zoom + 50) / 100.0)); + // adjust line_offset_ too + dotted_line_offset_ = int(0.5 * dotted_line_thickness_) + 1; + } + x_ = row_.x + xo_; //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl; @@ -76,7 +98,11 @@ RowPainter::RowPainter(PainterInfo & pi, FontInfo RowPainter::labelFont() const { - return text_.labelFont(pi_.base.bv->buffer(), par_); + FontInfo f = text_.labelFont(par_); + // selected text? + if (row_.begin_margin_sel || pi_.selected) + f.setPaintColor(Color_selectiontext); + return f; } @@ -100,17 +126,17 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos) // requires a full repaint bool pi_full_repaint = pi_.full_repaint; - // FIXME: We should always use font, see documentation of - // noFontChange() in Inset.h. - pi_.base.font = inset->noFontChange() ? - pi_.base.bv->buffer().params().getFont().fontInfo() : - font.fontInfo(); + pi_.base.font = inset->inheritFont() ? font.fontInfo() : + pi_.base.bv->buffer().params().getFont().fontInfo(); pi_.ltr_pos = (bidi_.level(pos) % 2 == 0); + Change prev_change = change_; pi_.change_ = change_.changed() ? change_ : par_.lookupChange(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_); @@ -123,6 +149,7 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos) // Restore full_repaint status. pi_.full_repaint = pi_full_repaint; + pi_.change_ = prev_change; #ifdef DEBUG_METRICS int const x2 = x1 + dim.wid; @@ -206,10 +233,13 @@ 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); + // FIXME: Why only round brackets and why the difference to + // Hebrew? See also Paragraph::getUChar if (arabic) { char_type c = str[0]; if (c == '(') @@ -225,11 +255,26 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, Change const & change_running = par_.lookupChange(pos); // selected text? - bool const selection = pos >= row_.sel_beg && pos < row_.sel_end; + bool const selection = (pos >= row_.sel_beg && pos < row_.sel_end) + || pi_.selected; + + // spelling correct? + bool const spell_state = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); - char_type prev_char = ' '; // 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' || lyxrc.force_paint_single_char) + break; + pos = bidi_.vis2log(vpos); if (pos < font_span.first || pos > font_span.last) break; @@ -239,6 +284,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. @@ -246,10 +297,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; @@ -280,6 +329,8 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, break; */ + // FIXME: Why only round brackets and why the difference to + // Hebrew? See also Paragraph::getUChar if (arabic) { if (c == '(') c = ')'; @@ -291,6 +342,7 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, } str.push_back(c); + prev_char = c; } docstring s(&str[0], str.size()); @@ -302,7 +354,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()); @@ -313,6 +365,14 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font, } +void RowPainter::paintSeparator(double orig_x, double width, + FontInfo const & font) +{ + pi_.pain.textDecoration(font, int(orig_x), yo_, int(width)); + x_ += width; +} + + void RowPainter::paintForeignMark(double orig_x, Language const * lang, int desc) { @@ -323,19 +383,25 @@ 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_ + solid_line_offset_ + desc + int(solid_line_thickness_/2); + pi_.pain.line(int(orig_x), y, int(x_), y, Color_language, + Painter::line_solid, 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.wavyHorizontalLine(int(orig_x), y, int(x_) - int(orig_x), Color_red); + // if changed the misspelled marker gets placed slightly lower than normal + // to avoid drawing at the same vertical offset + float const y = yo_ + solid_line_offset_ + solid_line_thickness_ + + (changed ? solid_line_thickness_ + 1 : 0) + + dotted_line_offset_; + pi_.pain.line(int(orig_x), int(y), int(x_), int(y), Color_error, + Painter::line_onoffdash, dotted_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); @@ -347,9 +413,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)) @@ -363,8 +433,24 @@ void RowPainter::paintFromPos(pos_type & vpos) paintForeignMark(orig_x, orig_font.language()); - if (orig_font.isMisspelled()) - paintMisspelledMark(orig_x, 3); + 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); + } } @@ -393,7 +479,7 @@ void RowPainter::paintChangeBar() void RowPainter::paintAppendix() { // only draw the appendix frame once (for the main text) - if (!par_.params().appendix() || !text_.isMainText(pi_.base.bv->buffer())) + if (!par_.params().appendix() || !text_.isMainText()) return; int y = yo_ - row_.ascent(); @@ -433,7 +519,7 @@ void RowPainter::paintDepthBar() int const w = nestMargin() / 5; int x = int(xo_) + w * i; // only consider the changebar space if we're drawing outermost text - if (text_.isMainText(pi_.base.bv->buffer())) + if (text_.isMainText()) x += changebarMargin(); int const starty = yo_ - row_.ascent(); @@ -476,128 +562,175 @@ int RowPainter::paintAppendixStart(int y) void RowPainter::paintFirst() { - ParagraphParameters const & parparams = par_.params(); + BufferParams const & bparams = pi_.base.bv->buffer().params(); + Layout const & layout = par_.layout(); int y_top = 0; // start of appendix? - if (parparams.startOfAppendix()) + if (par_.params().startOfAppendix()) y_top += paintAppendixStart(yo_ - row_.ascent() + 2 * defaultRowHeight()); - Buffer const & buffer = pi_.base.bv->buffer(); - Layout const & layout = par_.layout(); - - if (buffer.params().paragraph_separation == BufferParams::ParagraphSkipSeparation) { - if (pit_ != 0) { - if (layout.latextype == LATEX_PARAGRAPH - && !par_.getDepth()) { - y_top += buffer.params().getDefSkip().inPixels(*pi_.base.bv); - } else { - Layout const & playout = pars_[pit_ - 1].layout(); - if (playout.latextype == LATEX_PARAGRAPH - && !pars_[pit_ - 1].getDepth()) { - // is it right to use defskip here, too? (AS) - y_top += buffer.params().getDefSkip().inPixels(*pi_.base.bv); - } + if (bparams.paragraph_separation == BufferParams::ParagraphSkipSeparation + && pit_ != 0) { + if (layout.latextype == LATEX_PARAGRAPH + && !par_.getDepth()) { + y_top += bparams.getDefSkip().inPixels(*pi_.base.bv); + } else { + Layout const & playout = pars_[pit_ - 1].layout(); + if (playout.latextype == LATEX_PARAGRAPH + && !pars_[pit_ - 1].getDepth()) { + // is it right to use defskip here, too? (AS) + y_top += bparams.getDefSkip().inPixels(*pi_.base.bv); } } } - bool const is_rtl = text_.isRTL(buffer, par_); - bool const is_seq = isFirstInSequence(pit_, text_.paragraphs()); + bool const is_seq = text_.isFirstInSequence(pit_); //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << endl; - // should we print a label? if (layout.labeltype >= LABEL_STATIC - && (layout.labeltype != LABEL_STATIC - || layout.latextype != LATEX_ENVIRONMENT - || is_seq)) { + && (layout.labeltype != LABEL_STATIC + || layout.latextype != LATEX_ENVIRONMENT + || is_seq)) { + paintLabel(); + } else if (is_seq + && (layout.labeltype == LABEL_TOP_ENVIRONMENT + || layout.labeltype == LABEL_BIBLIO + || layout.labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) { + // the labels at the top of an environment. + // More or less for bibliography + paintTopLevelLabel(); + } +} - FontInfo const font = labelFont(); - FontMetrics const & fm = theFontMetrics(font); - docstring const str = par_.labelString(); - if (!str.empty()) { - double x = x_; - - // this is special code for the chapter layout. This is - // printed in an extra row and has a pagebreak at - // the top. - if (layout.counter == "chapter") { - double spacing_val = 1.0; - if (!parparams.spacing().isDefault()) { - spacing_val = parparams.spacing().getValue(); - } else { - spacing_val = buffer.params().spacing().getValue(); - } - - int const labeladdon = int(fm.maxHeight() * layout.spacing.getValue() * spacing_val); - - int const maxdesc = int(fm.maxDescent() * layout.spacing.getValue() * spacing_val) - + int(layout.parsep) * defaultRowHeight(); - - if (is_rtl) { - x = width_ - leftMargin() - - fm.width(str); - } - - pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font); - } else { - if (is_rtl) { - x = width_ - leftMargin() - + fm.width(layout.labelsep); - } else { - x = x_ - fm.width(layout.labelsep) - - fm.width(str); - } - - pi_.pain.text(int(x), yo_, str, font); - } +void RowPainter::paintLabel() +{ + docstring const str = par_.labelString(); + if (str.empty()) + return; + + BufferParams const & bparams = pi_.base.bv->buffer().params(); + bool const is_rtl = text_.isRTL(par_); + Layout const & layout = par_.layout(); + ParagraphParameters const & pparams = par_.params(); + FontInfo const font = labelFont(); + FontMetrics const & fm = theFontMetrics(font); + + double x = x_; + + // this is special code for the chapter layout. This is + // printed in an extra row and has a pagebreak at + // the top. + if (layout.counter == "chapter") { + double spacing_val = 1.0; + if (!pparams.spacing().isDefault()) { + spacing_val = pparams.spacing().getValue(); + } else { + spacing_val = bparams.spacing().getValue(); } - // the labels at the top of an environment. - // More or less for bibliography - } else if (is_seq && - (layout.labeltype == LABEL_TOP_ENVIRONMENT || - layout.labeltype == LABEL_BIBLIO || - layout.labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) { - FontInfo const font = labelFont(); - docstring const str = par_.labelString(); - if (!str.empty()) { - double spacing_val = 1.0; - if (!parparams.spacing().isDefault()) - spacing_val = parparams.spacing().getValue(); - else - spacing_val = buffer.params().spacing().getValue(); - - FontMetrics const & fm = theFontMetrics(font); - - int const labeladdon = int(fm.maxHeight() - * layout.spacing.getValue() * spacing_val); - - int maxdesc = - int(fm.maxDescent() * layout.spacing.getValue() * spacing_val - + (layout.labelbottomsep * defaultRowHeight())); - - double x = x_; - if (layout.labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) { - if (is_rtl) - x = leftMargin(); - x += (width_ - text_metrics_.rightMargin(pm_) - leftMargin()) / 2; - x -= fm.width(str) / 2; - } else if (is_rtl) { - x = width_ - leftMargin() - fm.width(str); - } - pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font); + int const labeladdon = int(fm.maxHeight() * layout.spacing.getValue() * spacing_val); + + int const maxdesc = int(fm.maxDescent() * layout.spacing.getValue() * spacing_val) + + int(layout.parsep) * defaultRowHeight(); + + if (is_rtl) { + x = width_ - leftMargin() - + fm.width(str); } + + pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font); + } else { + if (is_rtl) { + x = width_ - leftMargin() + + fm.width(layout.labelsep); + } else { + x = x_ - fm.width(layout.labelsep) + - fm.width(str); + } + + pi_.pain.text(int(x), yo_, str, font); } } +void RowPainter::paintTopLevelLabel() +{ + BufferParams const & bparams = pi_.base.bv->buffer().params(); + bool const is_rtl = text_.isRTL(par_); + ParagraphParameters const & pparams = par_.params(); + Layout const & layout = par_.layout(); + FontInfo const font = labelFont(); + docstring const str = par_.labelString(); + if (str.empty()) + return; + + double spacing_val = 1.0; + if (!pparams.spacing().isDefault()) + spacing_val = pparams.spacing().getValue(); + else + spacing_val = bparams.spacing().getValue(); + + FontMetrics const & fm = theFontMetrics(font); + + int const labeladdon = int(fm.maxHeight() + * layout.spacing.getValue() * spacing_val); + + int maxdesc = + int(fm.maxDescent() * layout.spacing.getValue() * spacing_val + + (layout.labelbottomsep * defaultRowHeight())); + + double x = x_; + if (layout.labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) { + if (is_rtl) + x = leftMargin(); + x += (width_ - text_metrics_.rightMargin(pm_) - leftMargin()) / 2; + x -= fm.width(str) / 2; + } else if (is_rtl) { + x = width_ - leftMargin() - fm.width(str); + } + pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font); +} + + +/** Check if the current paragraph is the last paragraph in a + proof environment */ +static int getEndLabel(pit_type p, Text const & text) +{ + ParagraphList const & pars = text.paragraphs(); + pit_type pit = p; + depth_type par_depth = pars[p].getDepth(); + while (pit != pit_type(pars.size())) { + Layout const & layout = pars[pit].layout(); + int const endlabeltype = layout.endlabeltype; + + if (endlabeltype != END_LABEL_NO_LABEL) { + if (p + 1 == pit_type(pars.size())) + return endlabeltype; + + depth_type const next_depth = + pars[p + 1].getDepth(); + if (par_depth > next_depth || + (par_depth == next_depth && layout != pars[p + 1].layout())) + return endlabeltype; + break; + } + if (par_depth == 0) + break; + pit = text.outerHook(pit); + if (pit != pit_type(pars.size())) + par_depth = pars[pit].getDepth(); + } + return END_LABEL_NO_LABEL; +} + + void RowPainter::paintLast() { - bool const is_rtl = text_.isRTL(pi_.base.bv->buffer(), par_); - int const endlabel = getEndLabel(pit_, text_.paragraphs()); + bool const is_rtl = text_.isRTL(par_); + int const endlabel = getEndLabel(pit_, text_); // paint imaginary end-of-paragraph character @@ -609,16 +742,15 @@ void RowPainter::paintLast() Color col = change.color(); pi_.pain.line(int(x_) + 1, yo_ + 2, int(x_) + 1, yo_ + 2 - length, col, - Painter::line_solid, Painter::line_thick); + Painter::line_solid, 3); if (change.deleted()) { pi_.pain.line(int(x_) + 1 - length, yo_ + 2, int(x_) + 1 + length, - yo_ + 2, col, Painter::line_solid, Painter::line_thick); + yo_ + 2, col, Painter::line_solid, 3); } else { pi_.pain.line(int(x_) + 1 - length, yo_ + 2, int(x_) + 1, - yo_ + 2, col, Painter::line_solid, Painter::line_thick); + yo_ + 2, col, Painter::line_solid, 3); } - } // draw an endlabel @@ -650,14 +782,20 @@ void RowPainter::paintLast() FontInfo const font = labelFont(); FontMetrics const & fm = theFontMetrics(font); docstring const & str = par_.layout().endlabelstring(); - double const x = is_rtl ? - x_ - fm.width(str) - : - text_metrics_.rightMargin(pm_) - row_.width(); + double const x = is_rtl ? x_ - fm.width(str) : x_; pi_.pain.text(int(x), yo_, str, font); break; } case END_LABEL_NO_LABEL: + if (lyxrc.paragraph_markers && size_type(pit_ + 1) < pars_.size()) { + docstring const s = docstring(1, char_type(0x00B6)); + FontInfo f = FontInfo(); + FontMetrics const & fm = theFontMetrics(f); + f.setColor(Color_paragraphmarker); + pi_.pain.text(int(x_), yo_, s, f); + x_ += fm.width(s); + } break; } } @@ -670,18 +808,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_ - && cur.anchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; + if (cur.selection() && cur.text() == &text_ + && cur.normalAnchor().text() == &text_) + pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; paintInset(inset, pos); pi_.selected = pi_selected; } @@ -691,7 +834,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]" @@ -772,7 +915,7 @@ void RowPainter::paintText() Inset const * inset = par_.getInset(pos); bool const highly_editable_inset = inset - && inset->editable() == Inset::HIGHLY_EDITABLE; + && inset->editable(); // If we reach the end of a change or if the author changes, paint it. // We also don't paint across things like tables @@ -781,11 +924,10 @@ void RowPainter::paintText() // Calculate 1/3 height of the buffer's default font 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; - pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, - Painter::line_thin); + float const y_bar = change_running.deleted() ? + yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_; + pi_.pain.line(change_last_x, int(y_bar), int(x_), int(y_bar), + change_running.color(), Painter::line_solid, solid_line_thickness_); // Change might continue with a different author or type if (change.changed() && !highly_editable_inset) { @@ -801,7 +943,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); @@ -809,28 +951,29 @@ void RowPainter::paintText() if (par_.isSeparator(pos)) { Font const orig_font = text_metrics_.displayFont(pit_, pos); double const orig_x = x_; - x_ += width_pos; + double separator_width = width_pos; if (pos >= body_pos) - x_ += row_.separator; + separator_width += row_.separator; + paintSeparator(orig_x, separator_width, orig_font.fontInfo()); paintForeignMark(orig_x, orig_font.language()); ++vpos; } 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_ - && cur.anchor().text() == &text_) - pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos; + if (cur.selection() && cur.text() == &text_ + && cur.normalAnchor().text() == &text_) + 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? @@ -842,21 +985,119 @@ void RowPainter::paintText() if (change_running.changed()) { 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; - pi_.pain.line(change_last_x, y_bar, int(x_), y_bar, - change_running.color(), Painter::line_solid, Painter::line_thin); + float const y_bar = change_running.deleted() ? + yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_; + pi_.pain.line(change_last_x, int(y_bar), int(x_), int(y_bar), + change_running.color(), Painter::line_solid, 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. @@ -865,20 +1106,20 @@ 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); swap(c1, c2); } - if (s1.size() > 0) { + if (!s1.empty()) { f.setColor(c1); pi_.pain.text(int(x_), yo_, s1, f); x_ += theFontMetrics(font).width(s1); } - - if (s2.size() > 0) { + + if (!s2.empty()) { f.setColor(c2); pi_.pain.text(int(x_), yo_, s2, f); x_ += theFontMetrics(font).width(s2);