]> git.lyx.org Git - lyx.git/blob - src/rowpainter.C
405914173ab491ab98dc2a9a5a09bf66525c8212
[lyx.git] / src / rowpainter.C
1 /**
2  * \file rowpainter.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author various
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "rowpainter.h"
15
16 #include "buffer.h"
17 #include "coordcache.h"
18 #include "cursor.h"
19 #include "debug.h"
20 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "encoding.h"
23 #include "gettext.h"
24 #include "language.h"
25 #include "LColor.h"
26 #include "lyxrc.h"
27 #include "lyxrow.h"
28 #include "lyxrow_funcs.h"
29 #include "metricsinfo.h"
30 #include "paragraph.h"
31 #include "paragraph_funcs.h"
32 #include "ParagraphParameters.h"
33 #include "vspace.h"
34
35 #include "frontends/font_metrics.h"
36 #include "frontends/nullpainter.h"
37 #include "frontends/Painter.h"
38
39 #include "insets/insettext.h"
40
41 #include "support/textutils.h"
42
43 #include <boost/crc.hpp>
44
45 using lyx::pos_type;
46 using lyx::pit_type;
47
48 using std::endl;
49 using std::max;
50 using std::min;
51 using std::string;
52
53
54 namespace {
55
56 /**
57  * A class used for painting an individual row of text.
58  */
59 class RowPainter {
60 public:
61         /// initialise and run painter
62         RowPainter(PainterInfo & pi, LyXText const & text,
63                 pit_type pit, Row const & row, int x, int y);
64
65         // paint various parts
66         void paintAppendix();
67         void paintDepthBar();
68         void paintChangeBar();
69         void paintFirst();
70         void paintLast();
71         void paintText();
72
73 private:
74         void paintForeignMark(double orig_x, LyXFont const & font, int desc = 0);
75         void paintHebrewComposeChar(lyx::pos_type & vpos, LyXFont const & font);
76         void paintArabicComposeChar(lyx::pos_type & vpos, LyXFont const & font);
77         void paintChars(lyx::pos_type & vpos, LyXFont font, 
78                         bool hebrew, bool arabic);
79         int paintAppendixStart(int y);
80         void paintFromPos(lyx::pos_type & vpos);
81         void paintInset(lyx::pos_type const pos, LyXFont const & font);
82
83         /// return left margin
84         int leftMargin() const;
85
86         /// return the label font for this row
87         LyXFont const getLabelFont() const;
88
89         /// bufferview to paint on
90         BufferView const & bv_;
91
92         /// Painter to use
93         Painter & pain_;
94
95         /// LyXText for the row
96         LyXText const & text_;
97         ParagraphList const & pars_;
98
99         /// The row to paint
100         Row const & row_;
101
102         /// Row's paragraph
103         pit_type const pit_;
104         Paragraph const & par_;
105
106         /// is row erased? (change tracking)
107         bool erased_;
108
109         // Looks ugly - is
110         double const xo_;
111         int const yo_;    // current baseline
112         double x_;
113         int width_;
114         double separator_;
115         double hfill_;
116         double label_hfill_;
117 };
118
119
120 RowPainter::RowPainter(PainterInfo & pi,
121         LyXText const & text, pit_type pit, Row const & row, int x, int y)
122         : bv_(*pi.base.bv), pain_(pi.pain), text_(text), pars_(text.paragraphs()),
123           row_(row), pit_(pit), par_(text.paragraphs()[pit]),
124           erased_(pi.erased_),
125           xo_(x), yo_(y), width_(text_.width())
126 {
127         RowMetrics m = text_.computeRowMetrics(pit, row_);
128         x_ = m.x + xo_;
129
130         //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl;
131         //row_.dump();
132
133         separator_ = m.separator;
134         hfill_ = m.hfill;
135         label_hfill_ = m.label_hfill;
136
137         BOOST_ASSERT(pit >= 0);
138         BOOST_ASSERT(pit < int(text.paragraphs().size()));
139 }
140
141
142 LyXFont const RowPainter::getLabelFont() const
143 {
144         return text_.getLabelFont(par_);
145 }
146
147
148 int RowPainter::leftMargin() const
149 {
150         return text_.leftMargin(pit_, row_.pos());
151 }
152
153
154 void RowPainter::paintInset(pos_type const pos, LyXFont const & font)
155 {
156         InsetBase const * inset = par_.getInset(pos);
157         BOOST_ASSERT(inset);
158         PainterInfo pi(const_cast<BufferView *>(&bv_), pain_);
159         // FIXME: We should always use font, see documentation of
160         // noFontChange() in insetbase.h.
161         pi.base.font = inset->noFontChange() ?
162                 bv_.buffer()->params().getLyXTextClass().defaultfont() :
163                 font;
164         pi.ltr_pos = (text_.bidi.level(pos) % 2 == 0);
165         pi.erased_ = erased_ || isDeletedText(par_, pos);
166         theCoords.insets().add(inset, int(x_), yo_);
167         inset->drawSelection(pi, int(x_), yo_);
168         inset->draw(pi, int(x_), yo_);
169         x_ += inset->width();
170 }
171
172
173 void RowPainter::paintHebrewComposeChar(pos_type & vpos, LyXFont const & font)
174 {
175         pos_type pos = text_.bidi.vis2log(vpos);
176
177         string str;
178
179         // first char
180         char c = par_.getChar(pos);
181         str += c;
182         ++vpos;
183
184         int const width = font_metrics::width(c, font);
185         int dx = 0;
186
187         for (pos_type i = pos - 1; i >= 0; --i) {
188                 c = par_.getChar(i);
189                 if (!Encodings::IsComposeChar_hebrew(c)) {
190                         if (IsPrintableNonspace(c)) {
191                                 int const width2 =
192                                         text_.singleWidth(par_, i, c, text_.getFont(par_, i));
193                                 // dalet / resh
194                                 dx = (c == 'ø' || c == 'ã')
195                                         ? width2 - width
196                                         : (width2 - width) / 2;
197                         }
198                         break;
199                 }
200         }
201
202         // Draw nikud
203         pain_.text(int(x_) + dx, yo_, str, font);
204 }
205
206
207 void RowPainter::paintArabicComposeChar(pos_type & vpos, LyXFont const & font)
208 {
209         pos_type pos = text_.bidi.vis2log(vpos);
210         string str;
211
212         // first char
213         char c = par_.getChar(pos);
214         c = par_.transformChar(c, pos);
215         str += c;
216         ++vpos;
217
218         int const width = font_metrics::width(c, font);
219         int dx = 0;
220
221         for (pos_type i = pos - 1; i >= 0; --i) {
222                 c = par_.getChar(i);
223                 if (!Encodings::IsComposeChar_arabic(c)) {
224                         if (IsPrintableNonspace(c)) {
225                                 int const width2 = 
226                                         text_.singleWidth(par_, i, c, text_.getFont(par_, i));
227                                 dx = (width2 - width) / 2;
228                         }
229                         break;
230                 }
231         }
232         // Draw nikud
233         pain_.text(int(x_) + dx, yo_, str, font);
234 }
235
236
237 void RowPainter::paintChars(pos_type & vpos, LyXFont font, 
238                             bool hebrew, bool arabic)
239 {
240         pos_type pos = text_.bidi.vis2log(vpos);
241         pos_type const end = row_.endpos();
242         FontSpan const font_span = par_.fontSpan(pos);
243         Change::Type const prev_change = par_.lookupChange(pos);
244
245         // first character
246         string str;
247         str += par_.getChar(pos);
248         if (arabic) {
249                 unsigned char c = str[0];
250                 str[0] = par_.transformChar(c, pos);
251         }
252
253         // collect as much similar chars as we can
254         for (++vpos ; vpos < end ; ++vpos) {
255                 pos = text_.bidi.vis2log(vpos);
256                 if (pos < font_span.first || pos > font_span.last)
257                         break;
258
259                 if (prev_change != par_.lookupChange(pos))
260                         break;
261
262                 char c = par_.getChar(pos);
263
264                 if (!IsPrintableNonspace(c))
265                         break;
266
267                 if (arabic && Encodings::IsComposeChar_arabic(c))
268                         break;
269
270                 if (hebrew && Encodings::IsComposeChar_hebrew(c))
271                         break;
272
273                 if (arabic)
274                         c = par_.transformChar(c, pos);
275
276                 str += c;
277         }
278
279         if (prev_change == Change::DELETED)
280                 font.setColor(LColor::strikeout);
281         else if (prev_change == Change::INSERTED)
282                 font.setColor(LColor::newtext);
283
284         // Draw text and set the new x position
285         //lyxerr << "paint row: yo_ " << yo_ << "\n";
286         pain_.text(int(x_), yo_, str, font);
287         x_ += font_metrics::width(str, font);
288 }
289
290
291 void RowPainter::paintForeignMark(double orig_x, LyXFont const & font, int desc)
292 {
293         if (!lyxrc.mark_foreign_language)
294                 return;
295         if (font.language() == latex_language)
296                 return;
297         if (font.language() == bv_.buffer()->params().language)
298                 return;
299
300         int const y = yo_ + 1 + desc;
301         pain_.line(int(orig_x), y, int(x_), y, LColor::language);
302 }
303
304
305 void RowPainter::paintFromPos(pos_type & vpos)
306 {
307         pos_type const pos = text_.bidi.vis2log(vpos);
308         LyXFont orig_font = text_.getFont(par_, pos);
309         text_.applyOuterFont(orig_font);
310
311         double const orig_x = x_;
312
313         if (par_.isInset(pos)) {
314                 paintInset(pos, orig_font);
315                 ++vpos;
316                 paintForeignMark(orig_x, orig_font,
317                         par_.getInset(pos)->descent());
318                 return;
319         }
320
321         // usual characters, no insets
322         char const c = par_.getChar(pos);
323
324         // special case languages
325         std::string const & lang = orig_font.language()->lang();
326         bool const hebrew = lang == "hebrew";
327         bool const arabic = lang == "arabic" &&
328                 (lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 ||
329                 lyxrc.font_norm_type == LyXRC::ISO_10646_1);
330
331         // draw as many chars as we can
332         if ((!hebrew && !arabic)
333                 || (hebrew && !Encodings::IsComposeChar_hebrew(c))
334                 || (arabic && !Encodings::IsComposeChar_arabic(c))) {
335                 paintChars(vpos, orig_font, hebrew, arabic);
336         } else if (hebrew) {
337                 paintHebrewComposeChar(vpos, orig_font);
338         } else if (arabic) {
339                 paintArabicComposeChar(vpos, orig_font);
340         }
341
342         paintForeignMark(orig_x, orig_font);
343 }
344
345
346 void RowPainter::paintChangeBar()
347 {
348         pos_type const start = row_.pos();
349         pos_type const end = row_.endpos();
350
351         if (start == end || !par_.isChanged(start, end - 1))
352                 return;
353
354         int const height = text_.isLastRow(pit_, row_)
355                 ? row_.ascent()
356                 : row_.height();
357
358         pain_.fillRectangle(4, yo_ - row_.ascent(), 5, height, LColor::changebar);
359 }
360
361
362 void RowPainter::paintAppendix()
363 {
364         if (!par_.params().appendix())
365                 return;
366
367         int y = yo_ - row_.ascent();
368
369         if (par_.params().startOfAppendix())
370                 y += 2 * defaultRowHeight();
371
372         pain_.line(1, y, 1, yo_ + row_.height(), LColor::appendix);
373         pain_.line(width_ - 2, y, width_ - 2, yo_ + row_.height(), LColor::appendix);
374 }
375
376
377 void RowPainter::paintDepthBar()
378 {
379         Paragraph::depth_type const depth = par_.getDepth();
380
381         if (depth <= 0)
382                 return;
383
384         Paragraph::depth_type prev_depth = 0;
385         if (!text_.isFirstRow(pit_, row_)) {
386                 pit_type pit2 = pit_;
387                 if (row_.pos() == 0)
388                         --pit2;
389                 prev_depth = pars_[pit2].getDepth();
390         }
391
392         Paragraph::depth_type next_depth = 0;
393         if (!text_.isLastRow(pit_, row_)) {
394                 pit_type pit2 = pit_;
395                 if (row_.endpos() >= pars_[pit2].size())
396                         ++pit2;
397                 next_depth = pars_[pit2].getDepth();
398         }
399
400         for (Paragraph::depth_type i = 1; i <= depth; ++i) {
401                 int const w = nestMargin() / 5;
402                 int x = int(xo_) + w * i;
403                 // only consider the changebar space if we're drawing outermost text
404                 if (text_.isMainText())
405                         x += changebarMargin();
406
407                 int const starty = yo_ - row_.ascent();
408                 int const h =  row_.height() - 1 - (i - next_depth - 1) * 3;
409
410                 pain_.line(x, starty, x, starty + h, LColor::depthbar);
411
412                 if (i > prev_depth)
413                         pain_.fillRectangle(x, starty, w, 2, LColor::depthbar);
414                 if (i > next_depth)
415                         pain_.fillRectangle(x, starty + h, w, 2, LColor::depthbar);
416         }
417 }
418
419
420 int RowPainter::paintAppendixStart(int y)
421 {
422         LyXFont pb_font;
423         pb_font.setColor(LColor::appendix);
424         pb_font.decSize();
425
426         string const label = _("Appendix");
427         int w = 0;
428         int a = 0;
429         int d = 0;
430         font_metrics::rectText(label, pb_font, w, a, d);
431
432         int const text_start = int(xo_ + (width_ - w) / 2);
433         int const text_end = text_start + w;
434
435         pain_.rectText(text_start, y + d, label, pb_font, LColor::none, LColor::none);
436
437         pain_.line(int(xo_ + 1), y, text_start, y, LColor::appendix);
438         pain_.line(text_end, y, int(xo_ + width_ - 2), y, LColor::appendix);
439
440         return 3 * defaultRowHeight();
441 }
442
443
444 void RowPainter::paintFirst()
445 {
446         ParagraphParameters const & parparams = par_.params();
447
448         int y_top = 0;
449
450         // start of appendix?
451         if (parparams.startOfAppendix())
452                 y_top += paintAppendixStart(yo_ - row_.ascent() + 2 * defaultRowHeight());
453
454         Buffer const & buffer = *bv_.buffer();
455
456         LyXLayout_ptr const & layout = par_.layout();
457
458         if (buffer.params().paragraph_separation == BufferParams::PARSEP_SKIP) {
459                 if (pit_ != 0) {
460                         if (layout->latextype == LATEX_PARAGRAPH
461                                 && !par_.getDepth()) {
462                                 y_top += buffer.params().getDefSkip().inPixels(bv_);
463                         } else {
464                                 LyXLayout_ptr const & playout = pars_[pit_ - 1].layout();
465                                 if (playout->latextype == LATEX_PARAGRAPH
466                                         && !pars_[pit_ - 1].getDepth()) {
467                                         // is it right to use defskip here, too? (AS)
468                                         y_top += buffer.params().getDefSkip().inPixels(bv_);
469                                 }
470                         }
471                 }
472         }
473
474         bool const is_rtl = text_.isRTL(par_);
475         bool const is_seq = isFirstInSequence(pit_, text_.paragraphs());
476         //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << std::endl;
477
478         // should we print a label?
479         if (layout->labeltype >= LABEL_STATIC
480             && (layout->labeltype != LABEL_STATIC
481                       || layout->latextype != LATEX_ENVIRONMENT
482                       || is_seq)) {
483
484                 LyXFont const font = getLabelFont();
485                 string const str = par_.getLabelstring();
486                 if (!str.empty()) {
487                         double x = x_;
488
489                         // this is special code for the chapter layout. This is
490                         // printed in an extra row and has a pagebreak at
491                         // the top.
492                         if (layout->counter == "chapter") {
493                                 double spacing_val = 1.0;
494                                 if (!parparams.spacing().isDefault()) {
495                                         spacing_val = parparams.spacing().getValue();
496                                 } else {
497                                         spacing_val = buffer.params().spacing().getValue();
498                                 }
499
500                                 int const labeladdon = int(font_metrics::maxHeight(font) * layout->spacing.getValue() * spacing_val);
501
502                                 int const maxdesc = int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val)
503                                         + int(layout->parsep) * defaultRowHeight();
504
505                                 if (is_rtl) {
506                                         x = width_ - leftMargin() -
507                                                 font_metrics::width(str, font);
508                                 }
509
510                                 pain_.text(int(x), yo_ - maxdesc - labeladdon, str, font);
511                         } else {
512                                 if (is_rtl) {
513                                         x = width_ - leftMargin()
514                                                 + font_metrics::width(layout->labelsep, font);
515                                 } else {
516                                         x = x_ - font_metrics::width(layout->labelsep, font)
517                                                 - font_metrics::width(str, font);
518                                 }
519
520                                 pain_.text(int(x), yo_, str, font);
521                         }
522                 }
523
524         // the labels at the top of an environment.
525         // More or less for bibliography
526         } else if (is_seq &&
527                 (layout->labeltype == LABEL_TOP_ENVIRONMENT ||
528                 layout->labeltype == LABEL_BIBLIO ||
529                 layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) {
530                 LyXFont font = getLabelFont();
531                 if (!par_.getLabelstring().empty()) {
532                         string const str = par_.getLabelstring();
533                         double spacing_val = 1.0;
534                         if (!parparams.spacing().isDefault())
535                                 spacing_val = parparams.spacing().getValue();
536                         else
537                                 spacing_val = buffer.params().spacing().getValue();
538
539                         int const labeladdon = int(font_metrics::maxHeight(font) * layout->spacing.getValue() * spacing_val);
540
541                         int maxdesc =
542                                 int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val
543                                 + (layout->labelbottomsep * defaultRowHeight()));
544
545                         double x = x_;
546                         if (layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) {
547                                 if (is_rtl)
548                                         x = leftMargin();
549                                 x += (width_ - text_.rightMargin(par_) - leftMargin()) / 2;
550                                 x -= font_metrics::width(str, font) / 2;
551                         } else if (is_rtl) {
552                                 x = width_ - leftMargin() -
553                                         font_metrics::width(str, font);
554                         }
555                         pain_.text(int(x), yo_ - maxdesc - labeladdon, str, font);
556                 }
557         }
558 }
559
560
561 void RowPainter::paintLast()
562 {
563         bool const is_rtl = text_.isRTL(par_);
564         int const endlabel = getEndLabel(pit_, text_.paragraphs());
565
566         // draw an endlabel
567         switch (endlabel) {
568         case END_LABEL_BOX:
569         case END_LABEL_FILLED_BOX: {
570                 LyXFont const font = getLabelFont();
571                 int const size = int(0.75 * font_metrics::maxAscent(font));
572                 int const y = yo_ - size;
573                 int x = is_rtl ? nestMargin() + changebarMargin() : width_ - size;
574
575                 if (width_ - int(row_.width()) <= size)
576                         x += (size - width_ + row_.width() + 1) * (is_rtl ? -1 : 1);
577
578                 if (endlabel == END_LABEL_BOX)
579                         pain_.rectangle(x, y, size, size, LColor::eolmarker);
580                 else
581                         pain_.fillRectangle(x, y, size, size, LColor::eolmarker);
582                 break;
583         }
584
585         case END_LABEL_STATIC: {
586                 LyXFont font = getLabelFont();
587                 string const & str = par_.layout()->endlabelstring();
588                 double const x = is_rtl ?
589                         x_ - font_metrics::width(str, font)
590                         : - text_.rightMargin(par_) - row_.width();
591                 pain_.text(int(x), yo_, str, font);
592                 break;
593         }
594
595         case END_LABEL_NO_LABEL:
596                 break;
597         }
598 }
599
600
601 void RowPainter::paintText()
602 {
603         pos_type const end = row_.endpos();
604         pos_type body_pos = par_.beginOfBody();
605         if (body_pos > 0 &&
606                 (body_pos > end || !par_.isLineSeparator(body_pos - 1))) {
607                 body_pos = 0;
608         }
609
610         LyXLayout_ptr const & layout = par_.layout();
611
612         bool running_strikeout = false;
613         bool is_struckout = false;
614         int last_strikeout_x = 0;
615
616         // Use font span to speed things up, see below 
617         FontSpan font_span;
618         LyXFont font;
619
620         for (pos_type vpos = row_.pos(); vpos < end; ) {
621                 if (x_ > bv_.workWidth())
622                         break;
623
624                 pos_type const pos = text_.bidi.vis2log(vpos);
625
626                 if (pos >= par_.size()) {
627                         ++vpos;
628                         continue;
629                 }
630
631                 // Use font span to speed things up, see above
632                 if (vpos < font_span.first || vpos > font_span.last) {
633                         font_span = par_.fontSpan(vpos);
634                         font = text_.getFont(par_, vpos);
635                 }
636
637                 const int width_pos =
638                         text_.singleWidth(par_, pos, par_.getChar(pos), font);
639
640                 if (x_ + width_pos < 0) {
641                         x_ += width_pos;
642                         ++vpos;
643                         continue;
644                 }
645
646                 is_struckout = isDeletedText(par_, pos);
647
648                 if (is_struckout && !running_strikeout) {
649                         running_strikeout = true;
650                         last_strikeout_x = int(x_);
651                 }
652
653                 bool const highly_editable_inset = par_.isInset(pos)
654                         && isHighlyEditableInset(par_.getInset(pos));
655
656                 // If we reach the end of a struck out range, paint it.
657                 // We also don't paint across things like tables
658                 if (running_strikeout && (highly_editable_inset || !is_struckout)) {
659                         // Calculate 1/3 height of the buffer's default font
660                         int const middle =
661                                 yo_ - font_metrics::maxAscent(text_.defaultfont_) / 3;
662                         pain_.line(last_strikeout_x, middle, int(x_), middle,
663                                 LColor::strikeout, Painter::line_solid, Painter::line_thin);
664                         running_strikeout = false;
665                 }
666
667                 if (body_pos > 0 && pos == body_pos - 1) {
668                         int const lwidth = font_metrics::width(layout->labelsep,
669                                 getLabelFont());
670
671                         x_ += label_hfill_ + lwidth - width_pos;
672                 }
673
674                 if (par_.isHfill(pos)) {
675                         x_ += 1;
676
677                         int const y0 = yo_;
678                         int const y1 = y0 - defaultRowHeight() / 2;
679
680                         pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
681
682                         if (hfillExpansion(par_, row_, pos)) {
683                                 int const y2 = (y0 + y1) / 2;
684
685                                 if (pos >= body_pos) {
686                                         pain_.line(int(x_), y2, int(x_ + hfill_), y2,
687                                                   LColor::added_space,
688                                                   Painter::line_onoffdash);
689                                         x_ += hfill_;
690                                 } else {
691                                         pain_.line(int(x_), y2, int(x_ + label_hfill_), y2,
692                                                   LColor::added_space,
693                                                   Painter::line_onoffdash);
694                                         x_ += label_hfill_;
695                                 }
696                                 pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
697                         }
698                         x_ += 2;
699                         ++vpos;
700                 } else if (par_.isSeparator(pos)) {
701                         x_ += width_pos;
702                         if (pos >= body_pos)
703                                 x_ += separator_;
704                         ++vpos;
705                 } else {
706                         paintFromPos(vpos);
707                 }
708         }
709
710         // if we reach the end of a struck out range, paint it
711         if (running_strikeout) {
712                 // calculate 1/3 height of the buffer's default font
713                 int const middle =
714                         yo_ - font_metrics::maxAscent(text_.defaultfont_) / 3;
715                 pain_.line(last_strikeout_x, middle, int(x_), middle,
716                         LColor::strikeout, Painter::line_solid, Painter::line_thin);
717                 running_strikeout = false;
718         }
719 }
720
721
722 lyx::size_type calculateRowSignature(Row const & row, Paragraph const & par)
723 {
724         boost::crc_32_type crc;
725         for (lyx::pos_type i = row.pos(); i < row.endpos(); ++i) {
726                 const unsigned char b[] = { par.getChar(i) };
727                 crc.process_bytes(b, 1);
728         }
729         return crc.checksum();
730 }
731
732
733 bool isCursorOnRow(PainterInfo & pi, pit_type pit, RowList::const_iterator rit)
734 {
735         LCursor & cur = pi.base.bv->cursor();
736         for (lyx::size_type d = 0; d < cur.depth(); d++)
737                 if (cur[d].pit() == pit
738                     && cur[d].pos() >= rit->pos()
739                     && cur[d].pos() <= rit->endpos())
740                         return true;
741         return false;
742 }
743
744
745 void paintPar
746         (PainterInfo & pi, LyXText const & text, pit_type pit, int x, int y,
747          bool repaintAll)
748 {
749 //      lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
750         static NullPainter nop;
751         static PainterInfo nullpi(pi.base.bv, nop);
752         int const ww = pi.base.bv->workHeight();
753
754         Paragraph const & par = text.paragraphs()[pit];
755
756         RowList::const_iterator const rb = par.rows().begin();
757         RowList::const_iterator const re = par.rows().end();
758         theCoords.parPos()[&text][pit] = Point(x, y);
759
760         y -= rb->ascent();
761         lyx::size_type rowno(0);
762         for (RowList::const_iterator rit = rb; rit != re; ++rit, ++rowno) {
763                 y += rit->ascent();
764
765                 // Row signature; has row changed since last paint?
766                 lyx::size_type const row_sig = calculateRowSignature(*rit, par);
767
768                 bool cursor_on_row = isCursorOnRow(pi, pit, rit);
769                 
770                 // If selection is on, the current row signature differs from
771                 // from cache, or cursor is inside an inset _on this row_, 
772                 // then paint the row
773                 if (repaintAll || par.rowSignature()[rowno] != row_sig
774                             || cursor_on_row) {
775                         // Add to row signature cache
776                         par.rowSignature()[rowno] = row_sig;
777
778                         bool const inside = (y + rit->descent() >= 0
779                                        && y - rit->ascent() < ww);
780                         RowPainter rp(inside ? pi : nullpi, text, pit, *rit, x, y);
781                         // Clear background of this row 
782                         // (if paragraph background was not cleared)
783                         if (!repaintAll) {
784                                 pi.pain.fillRectangle(x, y - rit->ascent(), 
785                                     pi.base.bv->workWidth(), rit->height(),
786                                     text.backgroundColor());
787                         }
788                         
789                         // Instrumentation for testing row cache (see also
790                         // 12 lines lower):
791                         //lyxerr << "#";
792                         rp.paintAppendix();
793                         rp.paintDepthBar();
794                         rp.paintChangeBar();
795                         if (rit == rb)
796                                 rp.paintFirst();
797                         if (rit + 1 == re)
798                                 rp.paintLast();
799                         rp.paintText();
800                 }
801                 y += rit->descent();
802         }
803         //lyxerr << "." << endl;
804 }
805
806 } // namespace anon
807
808
809 void paintText(BufferView const & bv, ViewMetricsInfo const & vi)
810 {
811         Painter & pain = bv.painter();
812         LyXText * const text = bv.text();
813         bool const select = bv.cursor().selection();
814
815         PainterInfo pi(const_cast<BufferView *>(&bv), pain);
816         if (select || !vi.singlepar) {
817                 // Clear background (Delegated to rows if no selection)
818                 pain.fillRectangle(0, vi.y1, bv.workWidth(), vi.y2 - vi.y1,
819                         text->backgroundColor());
820         }
821         if (select) {
822                 text->drawSelection(pi, 0, 0);
823         }
824
825         int yy = vi.y1;
826         // draw contents
827         for (pit_type pit = vi.p1; pit <= vi.p2; ++pit) {
828                 Paragraph const & par = text->getPar(pit);
829                 yy += par.ascent();
830                 paintPar(pi, *bv.text(), pit, 0, yy, select || !vi.singlepar);
831                 yy += par.descent();
832         }
833
834         // Cache one paragraph above and one below
835         // Note MV: this cannot be suppressed even for singlepar.
836         // Try viewing the User Guide Mobius figure
837
838         if (vi.p1 > 0) {
839                 text->redoParagraph(vi.p1 - 1);
840                 theCoords.parPos()[bv.text()][vi.p1 - 1] = 
841                         Point(0, vi.y1 - text->getPar(vi.p1 - 1).descent());
842         }
843
844         if (vi.p2 < lyx::pit_type(text->paragraphs().size()) - 1) {
845                 text->redoParagraph(vi.p2 + 1);
846                 theCoords.parPos()[bv.text()][vi.p2 + 1] = 
847                         Point(0, vi.y2 + text->getPar(vi.p2 + 1).ascent());
848         }
849
850         // and grey out above (should not happen later)
851 //      lyxerr << "par ascent: " << text->getPar(vi.p1).ascent() << endl;
852         if (vi.y1 > 0 && !vi.singlepar)
853                 pain.fillRectangle(0, 0, bv.workWidth(), vi.y1, LColor::bottomarea);
854
855         // and possibly grey out below
856 //      lyxerr << "par descent: " << text->getPar(vi.p1).ascent() << endl;
857         if (vi.y2 < bv.workHeight() && !vi.singlepar)
858                 pain.fillRectangle(0, vi.y2, bv.workWidth(), bv.workHeight() - vi.y2, LColor::bottomarea);
859 }
860
861
862 void paintTextInset(LyXText const & text, PainterInfo & pi, int x, int y)
863 {
864 //      lyxerr << "  paintTextInset: y: " << y << endl;
865
866         y -= text.getPar(0).ascent();
867         for (int pit = 0; pit < int(text.paragraphs().size()); ++pit) {
868                 y += text.getPar(pit).ascent();
869                 paintPar(pi, text, pit, x, y, true);
870                 y += text.getPar(pit).descent();
871         }
872 }