3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
15 #include "rowpainter.h"
19 #include "CoordCache.h"
21 #include "BufferParams.h"
22 #include "BufferView.h"
29 #include "MetricsInfo.h"
30 #include "Paragraph.h"
31 #include "ParagraphMetrics.h"
32 #include "ParagraphParameters.h"
33 #include "TextMetrics.h"
36 #include "frontends/FontMetrics.h"
37 #include "frontends/Painter.h"
39 #include "insets/InsetText.h"
41 #include "support/debug.h"
42 #include "support/gettext.h"
43 #include "support/textutils.h"
45 #include "support/lassert.h"
46 #include <boost/crc.hpp>
52 using frontend::Painter
;
53 using frontend::FontMetrics
;
55 RowPainter::RowPainter(PainterInfo
& pi
,
56 Text
const & text
, pit_type pit
, Row
const & row
, Bidi
& bidi
, int x
, int y
)
57 : pi_(pi
), text_(text
),
58 text_metrics_(pi_
.base
.bv
->textMetrics(&text
)),
59 pars_(text
.paragraphs()),
60 row_(row
), pit_(pit
), par_(text
.paragraphs()[pit
]),
61 pm_(text_metrics_
.parMetrics(pit
)),
62 bidi_(bidi
), change_(pi_
.change_
),
63 xo_(x
), yo_(y
), width_(text_metrics_
.width())
65 bidi_
.computeTables(par_
, pi_
.base
.bv
->buffer(), row_
);
68 //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl;
71 LASSERT(pit
>= 0, /**/);
72 LASSERT(pit
< int(text
.paragraphs().size()), /**/);
76 FontInfo
RowPainter::labelFont() const
78 return text_
.labelFont(par_
);
82 int RowPainter::leftMargin() const
84 return text_metrics_
.leftMargin(text_metrics_
.width(), pit_
,
88 // If you want to debug inset metrics uncomment the following line:
89 //#define DEBUG_METRICS
90 // This draws green lines around each inset.
93 void RowPainter::paintInset(Inset
const * inset
, pos_type
const pos
)
95 Font
const font
= text_metrics_
.displayFont(pit_
, pos
);
97 LASSERT(inset
, return);
98 // Backup full_repaint status because some insets (InsetTabular)
99 // requires a full repaint
100 bool pi_full_repaint
= pi_
.full_repaint
;
102 // FIXME: We should always use font, see documentation of
103 // noFontChange() in Inset.h.
104 pi_
.base
.font
= inset
->noFontChange() ?
105 pi_
.base
.bv
->buffer().params().getFont().fontInfo() :
107 pi_
.ltr_pos
= (bidi_
.level(pos
) % 2 == 0);
108 pi_
.change_
= change_
.changed() ? change_
: par_
.lookupChange(pos
);
110 int const x1
= int(x_
);
111 pi_
.base
.bv
->coordCache().insets().add(inset
, x1
, yo_
);
112 // insets are painted completely. Recursive
113 inset
->drawBackground(pi_
, x1
, yo_
);
114 inset
->drawSelection(pi_
, x1
, yo_
);
115 inset
->draw(pi_
, x1
, yo_
);
117 Dimension
const & dim
= pm_
.insetDimension(inset
);
119 paintForeignMark(x_
, font
.language(), dim
.descent());
123 // Restore full_repaint status.
124 pi_
.full_repaint
= pi_full_repaint
;
127 int const x2
= x1
+ dim
.wid
;
128 int const y1
= yo_
+ dim
.des
;
129 int const y2
= yo_
- dim
.asc
;
130 pi_
.pain
.line(x1
, y1
, x1
, y2
, Color_green
);
131 pi_
.pain
.line(x1
, y1
, x2
, y1
, Color_green
);
132 pi_
.pain
.line(x2
, y1
, x2
, y2
, Color_green
);
133 pi_
.pain
.line(x1
, y2
, x2
, y2
, Color_green
);
138 void RowPainter::paintHebrewComposeChar(pos_type
& vpos
, FontInfo
const & font
)
140 pos_type pos
= bidi_
.vis2log(vpos
);
145 char_type c
= par_
.getChar(pos
);
149 int const width
= theFontMetrics(font
).width(c
);
152 for (pos_type i
= pos
- 1; i
>= 0; --i
) {
154 if (!Encodings::isHebrewComposeChar(c
)) {
155 if (isPrintableNonspace(c
)) {
156 int const width2
= pm_
.singleWidth(i
,
157 text_metrics_
.displayFont(pit_
, i
));
158 dx
= (c
== 0x05e8 || // resh
159 c
== 0x05d3) // dalet
161 : (width2
- width
) / 2;
168 pi_
.pain
.text(int(x_
) + dx
, yo_
, str
, font
);
172 void RowPainter::paintArabicComposeChar(pos_type
& vpos
, FontInfo
const & font
)
174 pos_type pos
= bidi_
.vis2log(vpos
);
178 char_type c
= par_
.getChar(pos
);
179 c
= par_
.transformChar(c
, pos
);
183 int const width
= theFontMetrics(font
).width(c
);
186 for (pos_type i
= pos
- 1; i
>= 0; --i
) {
188 if (!Encodings::isArabicComposeChar(c
)) {
189 if (isPrintableNonspace(c
)) {
190 int const width2
= pm_
.singleWidth(i
,
191 text_metrics_
.displayFont(pit_
, i
));
192 dx
= (width2
- width
) / 2;
198 pi_
.pain
.text(int(x_
) + dx
, yo_
, str
, font
);
202 void RowPainter::paintChars(pos_type
& vpos
, FontInfo
const & font
,
203 bool hebrew
, bool arabic
)
205 // This method takes up 70% of time when typing
206 pos_type pos
= bidi_
.vis2log(vpos
);
208 vector
<char_type
> str
;
210 str
.push_back(par_
.getChar(pos
));
213 char_type c
= str
[0];
218 str
[0] = par_
.transformChar(c
, pos
);
221 pos_type
const end
= row_
.endpos();
222 FontSpan
const font_span
= par_
.fontSpan(pos
);
223 // Track-change status.
224 Change
const & change_running
= par_
.lookupChange(pos
);
227 bool const selection
= pos
>= row_
.sel_beg
&& pos
< row_
.sel_end
;
229 char_type prev_char
= ' ';
230 // collect as much similar chars as we can
231 for (++vpos
; vpos
< end
; ++vpos
) {
232 pos
= bidi_
.vis2log(vpos
);
233 if (pos
< font_span
.first
|| pos
> font_span
.last
)
236 bool const new_selection
= pos
>= row_
.sel_beg
&& pos
< row_
.sel_end
;
237 if (new_selection
!= selection
)
238 // Selection ends or starts here.
241 Change
const & change
= par_
.lookupChange(pos
);
242 if (!change_running
.isSimilarTo(change
))
243 // Track change type or author has changed.
246 char_type c
= par_
.getChar(pos
);
248 if (c
== '\t' || prev_char
== '\t') {
253 if (!isPrintableNonspace(c
))
256 /* Because we do our own bidi, at this point the strings are
257 * already in visual order. However, Qt also applies its own
258 * bidi algorithm to strings that it paints to the screen.
259 * Therefore, if we were to paint Hebrew/Arabic words as a
260 * single string, the letters in the words would get reversed
261 * again. In order to avoid that, we don't collect Hebrew/
262 * Arabic characters, but rather paint them one at a time.
263 * See also http://thread.gmane.org/gmane.editors.lyx.devel/79740
268 /* FIXME: these checks are irrelevant, since 'arabic' and
269 * 'hebrew' alone are already going to trigger a break.
270 * However, this should not be removed completely, because
271 * if an alternative solution is found which allows grouping
272 * of arabic and hebrew characters, then these breaks may have
275 if (arabic && Encodings::isArabicComposeChar(c))
278 if (hebrew && Encodings::isHebrewComposeChar(c))
287 c
= par_
.transformChar(c
, pos
);
288 /* see comment in hebrew, explaining why we break */
295 docstring
s(&str
[0], str
.size());
298 s
.replace(0,1,from_ascii(" "));
300 if (!selection
&& !change_running
.changed()) {
301 x_
+= pi_
.pain
.text(int(x_
), yo_
, s
, font
);
305 FontInfo copy
= font
;
306 if (change_running
.changed())
307 copy
.setPaintColor(change_running
.color());
309 copy
.setPaintColor(Color_selectiontext
);
311 x_
+= pi_
.pain
.text(int(x_
), yo_
, s
, copy
);
315 void RowPainter::paintForeignMark(double orig_x
, Language
const * lang
,
318 if (!lyxrc
.mark_foreign_language
)
320 if (lang
== latex_language
)
322 if (lang
== pi_
.base
.bv
->buffer().params().language
)
325 int const y
= yo_
+ 1 + desc
;
326 pi_
.pain
.line(int(orig_x
), y
, int(x_
), y
, Color_language
);
330 void RowPainter::paintMisspelledMark(double orig_x
, int desc
)
332 int const y
= yo_
+ desc
;
333 pi_
.pain
.wavyHorizontalLine(int(orig_x
), y
, int(x_
) - int(orig_x
), Color_red
);
337 void RowPainter::paintFromPos(pos_type
& vpos
)
339 pos_type
const pos
= bidi_
.vis2log(vpos
);
340 Font
const orig_font
= text_metrics_
.displayFont(pit_
, pos
);
341 double const orig_x
= x_
;
343 // usual characters, no insets
344 char_type
const c
= par_
.getChar(pos
);
346 // special case languages
347 string
const & lang
= orig_font
.language()->lang();
348 bool const hebrew
= lang
== "hebrew";
349 bool const arabic
= lang
== "arabic_arabtex" || lang
== "arabic_arabi" ||
352 // draw as many chars as we can
353 if ((!hebrew
&& !arabic
)
354 || (hebrew
&& !Encodings::isHebrewComposeChar(c
))
355 || (arabic
&& !Encodings::isArabicComposeChar(c
))) {
356 paintChars(vpos
, orig_font
.fontInfo(), hebrew
, arabic
);
358 paintHebrewComposeChar(vpos
, orig_font
.fontInfo());
360 paintArabicComposeChar(vpos
, orig_font
.fontInfo());
363 paintForeignMark(orig_x
, orig_font
.language());
365 if (lyxrc
.spellcheck_continuously
&& orig_font
.isMisspelled())
366 paintMisspelledMark(orig_x
, 3);
370 void RowPainter::paintChangeBar()
372 pos_type
const start
= row_
.pos();
373 pos_type end
= row_
.endpos();
375 if (par_
.size() == end
) {
376 // this is the last row of the paragraph;
377 // thus, we must also consider the imaginary end-of-par character
381 if (start
== end
|| !par_
.isChanged(start
, end
))
384 int const height
= text_metrics_
.isLastRow(pit_
, row_
)
388 pi_
.pain
.fillRectangle(5, yo_
- row_
.ascent(), 3, height
, Color_changebar
);
392 void RowPainter::paintAppendix()
394 // only draw the appendix frame once (for the main text)
395 if (!par_
.params().appendix() || !text_
.isMainText())
398 int y
= yo_
- row_
.ascent();
400 if (par_
.params().startOfAppendix())
401 y
+= 2 * defaultRowHeight();
403 pi_
.pain
.line(1, y
, 1, yo_
+ row_
.height(), Color_appendix
);
404 pi_
.pain
.line(width_
- 2, y
, width_
- 2, yo_
+ row_
.height(), Color_appendix
);
408 void RowPainter::paintDepthBar()
410 depth_type
const depth
= par_
.getDepth();
415 depth_type prev_depth
= 0;
416 if (!text_metrics_
.isFirstRow(pit_
, row_
)) {
417 pit_type pit2
= pit_
;
420 prev_depth
= pars_
[pit2
].getDepth();
423 depth_type next_depth
= 0;
424 if (!text_metrics_
.isLastRow(pit_
, row_
)) {
425 pit_type pit2
= pit_
;
426 if (row_
.endpos() >= pars_
[pit2
].size())
428 next_depth
= pars_
[pit2
].getDepth();
431 for (depth_type i
= 1; i
<= depth
; ++i
) {
432 int const w
= nestMargin() / 5;
433 int x
= int(xo_
) + w
* i
;
434 // only consider the changebar space if we're drawing outermost text
435 if (text_
.isMainText())
436 x
+= changebarMargin();
438 int const starty
= yo_
- row_
.ascent();
439 int const h
= row_
.height() - 1 - (i
- next_depth
- 1) * 3;
441 pi_
.pain
.line(x
, starty
, x
, starty
+ h
, Color_depthbar
);
444 pi_
.pain
.fillRectangle(x
, starty
, w
, 2, Color_depthbar
);
446 pi_
.pain
.fillRectangle(x
, starty
+ h
, w
, 2, Color_depthbar
);
451 int RowPainter::paintAppendixStart(int y
)
453 FontInfo pb_font
= sane_font
;
454 pb_font
.setColor(Color_appendix
);
461 docstring
const label
= _("Appendix");
462 theFontMetrics(pb_font
).rectText(label
, w
, a
, d
);
464 int const text_start
= int(xo_
+ (width_
- w
) / 2);
465 int const text_end
= text_start
+ w
;
467 pi_
.pain
.rectText(text_start
, y
+ d
, label
, pb_font
, Color_none
, Color_none
);
469 pi_
.pain
.line(int(xo_
+ 1), y
, text_start
, y
, Color_appendix
);
470 pi_
.pain
.line(text_end
, y
, int(xo_
+ width_
- 2), y
, Color_appendix
);
472 return 3 * defaultRowHeight();
476 void RowPainter::paintFirst()
478 ParagraphParameters
const & parparams
= par_
.params();
482 // start of appendix?
483 if (parparams
.startOfAppendix())
484 y_top
+= paintAppendixStart(yo_
- row_
.ascent() + 2 * defaultRowHeight());
486 Buffer
const & buffer
= pi_
.base
.bv
->buffer();
487 Layout
const & layout
= par_
.layout();
489 if (buffer
.params().paragraph_separation
== BufferParams::ParagraphSkipSeparation
) {
491 if (layout
.latextype
== LATEX_PARAGRAPH
492 && !par_
.getDepth()) {
493 y_top
+= buffer
.params().getDefSkip().inPixels(*pi_
.base
.bv
);
495 Layout
const & playout
= pars_
[pit_
- 1].layout();
496 if (playout
.latextype
== LATEX_PARAGRAPH
497 && !pars_
[pit_
- 1].getDepth()) {
498 // is it right to use defskip here, too? (AS)
499 y_top
+= buffer
.params().getDefSkip().inPixels(*pi_
.base
.bv
);
505 bool const is_rtl
= text_
.isRTL(par_
);
506 bool const is_seq
= text_
.isFirstInSequence(pit_
);
507 //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << endl;
509 // should we print a label?
510 if (layout
.labeltype
>= LABEL_STATIC
511 && (layout
.labeltype
!= LABEL_STATIC
512 || layout
.latextype
!= LATEX_ENVIRONMENT
515 FontInfo
const font
= labelFont();
516 FontMetrics
const & fm
= theFontMetrics(font
);
518 docstring
const str
= par_
.labelString();
522 // this is special code for the chapter layout. This is
523 // printed in an extra row and has a pagebreak at
525 if (layout
.counter
== "chapter") {
526 double spacing_val
= 1.0;
527 if (!parparams
.spacing().isDefault()) {
528 spacing_val
= parparams
.spacing().getValue();
530 spacing_val
= buffer
.params().spacing().getValue();
533 int const labeladdon
= int(fm
.maxHeight() * layout
.spacing
.getValue() * spacing_val
);
535 int const maxdesc
= int(fm
.maxDescent() * layout
.spacing
.getValue() * spacing_val
)
536 + int(layout
.parsep
) * defaultRowHeight();
539 x
= width_
- leftMargin() -
543 pi_
.pain
.text(int(x
), yo_
- maxdesc
- labeladdon
, str
, font
);
546 x
= width_
- leftMargin()
547 + fm
.width(layout
.labelsep
);
549 x
= x_
- fm
.width(layout
.labelsep
)
553 pi_
.pain
.text(int(x
), yo_
, str
, font
);
557 // the labels at the top of an environment.
558 // More or less for bibliography
560 (layout
.labeltype
== LABEL_TOP_ENVIRONMENT
||
561 layout
.labeltype
== LABEL_BIBLIO
||
562 layout
.labeltype
== LABEL_CENTERED_TOP_ENVIRONMENT
)) {
563 FontInfo
const font
= labelFont();
564 docstring
const str
= par_
.labelString();
566 double spacing_val
= 1.0;
567 if (!parparams
.spacing().isDefault())
568 spacing_val
= parparams
.spacing().getValue();
570 spacing_val
= buffer
.params().spacing().getValue();
572 FontMetrics
const & fm
= theFontMetrics(font
);
574 int const labeladdon
= int(fm
.maxHeight()
575 * layout
.spacing
.getValue() * spacing_val
);
578 int(fm
.maxDescent() * layout
.spacing
.getValue() * spacing_val
579 + (layout
.labelbottomsep
* defaultRowHeight()));
582 if (layout
.labeltype
== LABEL_CENTERED_TOP_ENVIRONMENT
) {
585 x
+= (width_
- text_metrics_
.rightMargin(pm_
) - leftMargin()) / 2;
586 x
-= fm
.width(str
) / 2;
588 x
= width_
- leftMargin() - fm
.width(str
);
590 pi_
.pain
.text(int(x
), yo_
- maxdesc
- labeladdon
, str
, font
);
596 /** Check if the current paragraph is the last paragraph in a
598 static int getEndLabel(pit_type p
, Text
const & text
)
600 ParagraphList
const & pars
= text
.paragraphs();
602 depth_type par_depth
= pars
[p
].getDepth();
603 while (pit
!= pit_type(pars
.size())) {
604 Layout
const & layout
= pars
[pit
].layout();
605 int const endlabeltype
= layout
.endlabeltype
;
607 if (endlabeltype
!= END_LABEL_NO_LABEL
) {
608 if (p
+ 1 == pit_type(pars
.size()))
611 depth_type
const next_depth
=
612 pars
[p
+ 1].getDepth();
613 if (par_depth
> next_depth
||
614 (par_depth
== next_depth
&& layout
!= pars
[p
+ 1].layout()))
620 pit
= text
.outerHook(pit
);
621 if (pit
!= pit_type(pars
.size()))
622 par_depth
= pars
[pit
].getDepth();
624 return END_LABEL_NO_LABEL
;
628 void RowPainter::paintLast()
630 bool const is_rtl
= text_
.isRTL(par_
);
631 int const endlabel
= getEndLabel(pit_
, text_
);
633 // paint imaginary end-of-paragraph character
635 Change
const & change
= par_
.lookupChange(par_
.size());
636 if (change
.changed()) {
637 FontMetrics
const & fm
=
638 theFontMetrics(pi_
.base
.bv
->buffer().params().getFont());
639 int const length
= fm
.maxAscent() / 2;
640 Color col
= change
.color();
642 pi_
.pain
.line(int(x_
) + 1, yo_
+ 2, int(x_
) + 1, yo_
+ 2 - length
, col
,
643 Painter::line_solid
, Painter::line_thick
);
645 if (change
.deleted()) {
646 pi_
.pain
.line(int(x_
) + 1 - length
, yo_
+ 2, int(x_
) + 1 + length
,
647 yo_
+ 2, col
, Painter::line_solid
, Painter::line_thick
);
649 pi_
.pain
.line(int(x_
) + 1 - length
, yo_
+ 2, int(x_
) + 1,
650 yo_
+ 2, col
, Painter::line_solid
, Painter::line_thick
);
658 case END_LABEL_FILLED_BOX
: {
659 FontInfo
const font
= labelFont();
660 FontMetrics
const & fm
= theFontMetrics(font
);
661 int const size
= int(0.75 * fm
.maxAscent());
662 int const y
= yo_
- size
;
663 int const max_row_width
= width_
- size
- Inset::TEXT_TO_INSET_OFFSET
;
664 int x
= is_rtl
? nestMargin() + changebarMargin()
665 : max_row_width
- text_metrics_
.rightMargin(pm_
);
667 // If needed, move the box a bit to avoid overlapping with text.
668 int const rem
= max_row_width
- row_
.width();
670 x
+= is_rtl
? rem
: - rem
;
672 if (endlabel
== END_LABEL_BOX
)
673 pi_
.pain
.rectangle(x
, y
, size
, size
, Color_eolmarker
);
675 pi_
.pain
.fillRectangle(x
, y
, size
, size
, Color_eolmarker
);
679 case END_LABEL_STATIC
: {
680 FontInfo
const font
= labelFont();
681 FontMetrics
const & fm
= theFontMetrics(font
);
682 docstring
const & str
= par_
.layout().endlabelstring();
683 double const x
= is_rtl
? x_
- fm
.width(str
) : x_
;
684 pi_
.pain
.text(int(x
), yo_
, str
, font
);
688 case END_LABEL_NO_LABEL
:
689 if (lyxrc
.paragraph_markers
&& size_type(pit_
+ 1) < pars_
.size()) {
690 docstring
const s
= docstring(1, char_type(0x00B6));
691 FontInfo f
= FontInfo();
692 FontMetrics
const & fm
= theFontMetrics(f
);
693 f
.setColor(Color_paragraphmarker
);
694 pi_
.pain
.text(int(x_
), yo_
, s
, f
);
702 void RowPainter::paintOnlyInsets()
704 CoordCache
const & cache
= pi_
.base
.bv
->coordCache();
705 pos_type
const end
= row_
.endpos();
706 for (pos_type pos
= row_
.pos(); pos
!= end
; ++pos
) {
707 // If outer row has changed, nested insets are repaint completely.
708 Inset
const * inset
= par_
.getInset(pos
);
711 if (x_
> pi_
.base
.bv
->workWidth()
712 || !cache
.getInsets().has(inset
))
714 x_
= cache
.getInsets().x(inset
);
716 bool const pi_selected
= pi_
.selected
;
717 Cursor
const & cur
= pi_
.base
.bv
->cursor();
718 if (cur
.selection() && cur
.text() == &text_
719 && cur
.anchor().text() == &text_
)
720 pi_
.selected
= row_
.sel_beg
<= pos
&& row_
.sel_end
> pos
;
721 paintInset(inset
, pos
);
722 pi_
.selected
= pi_selected
;
727 void RowPainter::paintText()
729 pos_type
const end
= row_
.endpos();
730 // Spaces at logical line breaks in bidi text must be skipped during
731 // painting. However, they may appear visually in the middle
732 // of a row; they must be skipped, wherever they are...
733 // * logically "abc_[HEBREW_\nHEBREW]"
734 // * visually "abc_[_WERBEH\nWERBEH]"
735 pos_type skipped_sep_vpos
= -1;
736 pos_type body_pos
= par_
.beginOfBody();
738 (body_pos
> end
|| !par_
.isLineSeparator(body_pos
- 1))) {
742 Layout
const & layout
= par_
.layout();
744 Change change_running
;
745 int change_last_x
= 0;
747 // check for possible inline completion
748 DocIterator
const & inlineCompletionPos
= pi_
.base
.bv
->inlineCompletionPos();
749 pos_type inlineCompletionVPos
= -1;
750 if (inlineCompletionPos
.inTexted()
751 && inlineCompletionPos
.text() == &text_
752 && inlineCompletionPos
.pit() == pit_
753 && inlineCompletionPos
.pos() - 1 >= row_
.pos()
754 && inlineCompletionPos
.pos() - 1 < row_
.endpos()) {
755 // draw logically behind the previous character
756 inlineCompletionVPos
= bidi_
.log2vis(inlineCompletionPos
.pos() - 1);
759 // Use font span to speed things up, see below
763 // If the last logical character is a separator, don't paint it, unless
764 // it's in the last row of a paragraph; see skipped_sep_vpos declaration
765 if (end
> 0 && end
< par_
.size() && par_
.isSeparator(end
- 1))
766 skipped_sep_vpos
= bidi_
.log2vis(end
- 1);
768 for (pos_type vpos
= row_
.pos(); vpos
< end
; ) {
769 if (x_
> pi_
.base
.bv
->workWidth())
772 // Skip the separator at the logical end of the row
773 if (vpos
== skipped_sep_vpos
) {
778 pos_type
const pos
= bidi_
.vis2log(vpos
);
780 if (pos
>= par_
.size()) {
785 // Use font span to speed things up, see above
786 if (vpos
< font_span
.first
|| vpos
> font_span
.last
) {
787 font_span
= par_
.fontSpan(vpos
);
788 font
= text_metrics_
.displayFont(pit_
, vpos
);
790 // split font span if inline completion is inside
791 if (font_span
.first
<= inlineCompletionVPos
792 && font_span
.last
> inlineCompletionVPos
)
793 font_span
.last
= inlineCompletionVPos
;
796 const int width_pos
= pm_
.singleWidth(pos
, font
);
798 if (x_
+ width_pos
< 0) {
803 Change
const & change
= par_
.lookupChange(pos
);
804 if (change
.changed() && !change_running
.changed()) {
805 change_running
= change
;
806 change_last_x
= int(x_
);
809 Inset
const * inset
= par_
.getInset(pos
);
810 bool const highly_editable_inset
= inset
811 && inset
->editable();
813 // If we reach the end of a change or if the author changes, paint it.
814 // We also don't paint across things like tables
815 if (change_running
.changed() && (highly_editable_inset
816 || !change
.changed() || !change_running
.isSimilarTo(change
))) {
817 // Calculate 1/3 height of the buffer's default font
818 FontMetrics
const & fm
819 = theFontMetrics(pi_
.base
.bv
->buffer().params().getFont());
820 int const y_bar
= change_running
.deleted() ?
821 yo_
- fm
.maxAscent() / 3 : yo_
+ fm
.maxAscent() / 6;
822 pi_
.pain
.line(change_last_x
, y_bar
, int(x_
), y_bar
,
823 change_running
.color(), Painter::line_solid
,
826 // Change might continue with a different author or type
827 if (change
.changed() && !highly_editable_inset
) {
828 change_running
= change
;
829 change_last_x
= int(x_
);
831 change_running
.setUnchanged();
834 if (body_pos
> 0 && pos
== body_pos
- 1) {
835 int const lwidth
= theFontMetrics(labelFont())
836 .width(layout
.labelsep
);
838 x_
+= row_
.label_hfill
+ lwidth
- width_pos
;
841 // Is the inline completion in front of character?
842 if (font
.isRightToLeft() && vpos
== inlineCompletionVPos
)
843 paintInlineCompletion(font
);
845 if (par_
.isSeparator(pos
)) {
846 Font
const orig_font
= text_metrics_
.displayFont(pit_
, pos
);
847 double const orig_x
= x_
;
850 x_
+= row_
.separator
;
851 paintForeignMark(orig_x
, orig_font
.language());
855 // If outer row has changed, nested insets are repaint completely.
856 pi_
.base
.bv
->coordCache().insets().add(inset
, int(x_
), yo_
);
858 bool const pi_selected
= pi_
.selected
;
859 Cursor
const & cur
= pi_
.base
.bv
->cursor();
860 if (cur
.selection() && cur
.text() == &text_
861 && cur
.anchor().text() == &text_
)
862 pi_
.selected
= row_
.sel_beg
<= pos
&& row_
.sel_end
> pos
;
863 paintInset(inset
, pos
);
864 pi_
.selected
= pi_selected
;
868 // paint as many characters as possible.
872 // Is the inline completion after character?
873 if (!font
.isRightToLeft() && vpos
- 1 == inlineCompletionVPos
)
874 paintInlineCompletion(font
);
877 // if we reach the end of a struck out range, paint it
878 if (change_running
.changed()) {
879 FontMetrics
const & fm
880 = theFontMetrics(pi_
.base
.bv
->buffer().params().getFont());
881 int const y_bar
= change_running
.deleted() ?
882 yo_
- fm
.maxAscent() / 3 : yo_
+ fm
.maxAscent() / 6;
883 pi_
.pain
.line(change_last_x
, y_bar
, int(x_
), y_bar
,
884 change_running
.color(), Painter::line_solid
, Painter::line_thin
);
885 change_running
.setUnchanged();
890 void RowPainter::paintInlineCompletion(Font
const & font
)
892 docstring completion
= pi_
.base
.bv
->inlineCompletion();
893 FontInfo f
= font
.fontInfo();
894 bool rtl
= font
.isRightToLeft();
896 // draw the unique and the non-unique completion part
897 // Note: this is not time-critical as it is
898 // only done once per screen.
899 size_t uniqueTo
= pi_
.base
.bv
->inlineCompletionUniqueChars();
900 docstring s1
= completion
.substr(0, uniqueTo
);
901 docstring s2
= completion
.substr(uniqueTo
);
902 ColorCode c1
= Color_inlinecompletion
;
903 ColorCode c2
= Color_nonunique_inlinecompletion
;
913 pi_
.pain
.text(int(x_
), yo_
, s1
, f
);
914 x_
+= theFontMetrics(font
).width(s1
);
919 pi_
.pain
.text(int(x_
), yo_
, s2
, f
);
920 x_
+= theFontMetrics(font
).width(s2
);