]> git.lyx.org Git - lyx.git/blob - src/rowpainter.C
8d56f11cdf5a2ed2e34707261b22c2775d7c8ceb
[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().getFont() :
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_ -
662                                 font_metrics::maxAscent(bv_.buffer()->params().getFont()) / 3;
663                         pain_.line(last_strikeout_x, middle, int(x_), middle,
664                                 LColor::strikeout, Painter::line_solid, Painter::line_thin);
665                         running_strikeout = false;
666                 }
667
668                 if (body_pos > 0 && pos == body_pos - 1) {
669                         int const lwidth = font_metrics::width(layout->labelsep,
670                                 getLabelFont());
671
672                         x_ += label_hfill_ + lwidth - width_pos;
673                 }
674
675                 if (par_.isHfill(pos)) {
676                         x_ += 1;
677
678                         int const y0 = yo_;
679                         int const y1 = y0 - defaultRowHeight() / 2;
680
681                         pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
682
683                         if (hfillExpansion(par_, row_, pos)) {
684                                 int const y2 = (y0 + y1) / 2;
685
686                                 if (pos >= body_pos) {
687                                         pain_.line(int(x_), y2, int(x_ + hfill_), y2,
688                                                   LColor::added_space,
689                                                   Painter::line_onoffdash);
690                                         x_ += hfill_;
691                                 } else {
692                                         pain_.line(int(x_), y2, int(x_ + label_hfill_), y2,
693                                                   LColor::added_space,
694                                                   Painter::line_onoffdash);
695                                         x_ += label_hfill_;
696                                 }
697                                 pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
698                         }
699                         x_ += 2;
700                         ++vpos;
701                 } else if (par_.isSeparator(pos)) {
702                         x_ += width_pos;
703                         if (pos >= body_pos)
704                                 x_ += separator_;
705                         ++vpos;
706                 } else {
707                         paintFromPos(vpos);
708                 }
709         }
710
711         // if we reach the end of a struck out range, paint it
712         if (running_strikeout) {
713                 // calculate 1/3 height of the buffer's default font
714                 int const middle =
715                         yo_ -
716                         font_metrics::maxAscent(bv_.buffer()->params().getFont()) / 3;
717                 pain_.line(last_strikeout_x, middle, int(x_), middle,
718                         LColor::strikeout, Painter::line_solid, Painter::line_thin);
719                 running_strikeout = false;
720         }
721 }
722
723
724 lyx::size_type calculateRowSignature(Row const & row, Paragraph const & par)
725 {
726         boost::crc_32_type crc;
727         for (lyx::pos_type i = row.pos(); i < row.endpos(); ++i) {
728                 const unsigned char b[] = { par.getChar(i) };
729                 crc.process_bytes(b, 1);
730         }
731         return crc.checksum();
732 }
733
734
735 bool isCursorOnRow(PainterInfo & pi, pit_type pit, RowList::const_iterator rit)
736 {
737         LCursor & cur = pi.base.bv->cursor();
738         for (lyx::size_type d = 0; d < cur.depth(); d++)
739                 if (cur[d].pit() == pit
740                     && cur[d].pos() >= rit->pos()
741                     && cur[d].pos() <= rit->endpos())
742                         return true;
743         return false;
744 }
745
746
747 void paintPar
748         (PainterInfo & pi, LyXText const & text, pit_type pit, int x, int y,
749          bool repaintAll)
750 {
751 //      lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
752         static NullPainter nop;
753         static PainterInfo nullpi(pi.base.bv, nop);
754         int const ww = pi.base.bv->workHeight();
755
756         Paragraph const & par = text.paragraphs()[pit];
757
758         RowList::const_iterator const rb = par.rows().begin();
759         RowList::const_iterator const re = par.rows().end();
760         theCoords.parPos()[&text][pit] = Point(x, y);
761
762         y -= rb->ascent();
763         lyx::size_type rowno(0);
764         for (RowList::const_iterator rit = rb; rit != re; ++rit, ++rowno) {
765                 y += rit->ascent();
766
767                 // Row signature; has row changed since last paint?
768                 lyx::size_type const row_sig = calculateRowSignature(*rit, par);
769
770                 bool cursor_on_row = isCursorOnRow(pi, pit, rit);
771                 
772                 // If selection is on, the current row signature differs from
773                 // from cache, or cursor is inside an inset _on this row_, 
774                 // then paint the row
775                 if (repaintAll || par.rowSignature()[rowno] != row_sig
776                             || cursor_on_row) {
777                         // Add to row signature cache
778                         par.rowSignature()[rowno] = row_sig;
779
780                         bool const inside = (y + rit->descent() >= 0
781                                        && y - rit->ascent() < ww);
782                         RowPainter rp(inside ? pi : nullpi, text, pit, *rit, x, y);
783                         // Clear background of this row 
784                         // (if paragraph background was not cleared)
785                         if (!repaintAll) {
786                                 pi.pain.fillRectangle(x, y - rit->ascent(), 
787                                     pi.base.bv->workWidth(), rit->height(),
788                                     text.backgroundColor());
789                         }
790                         
791                         // Instrumentation for testing row cache (see also
792                         // 12 lines lower):
793                         //lyxerr << "#";
794                         rp.paintAppendix();
795                         rp.paintDepthBar();
796                         rp.paintChangeBar();
797                         if (rit == rb)
798                                 rp.paintFirst();
799                         if (rit + 1 == re)
800                                 rp.paintLast();
801                         rp.paintText();
802                 }
803                 y += rit->descent();
804         }
805         //lyxerr << "." << endl;
806 }
807
808 } // namespace anon
809
810
811 void paintText(BufferView const & bv, ViewMetricsInfo const & vi)
812 {
813         Painter & pain = bv.painter();
814         LyXText * const text = bv.text();
815         bool const select = bv.cursor().selection();
816
817         PainterInfo pi(const_cast<BufferView *>(&bv), pain);
818         if (select || !vi.singlepar) {
819                 // Clear background (Delegated to rows if no selection)
820                 pain.fillRectangle(0, vi.y1, bv.workWidth(), vi.y2 - vi.y1,
821                         text->backgroundColor());
822         }
823         if (select) {
824                 text->drawSelection(pi, 0, 0);
825         }
826
827         int yy = vi.y1;
828         // draw contents
829         for (pit_type pit = vi.p1; pit <= vi.p2; ++pit) {
830                 Paragraph const & par = text->getPar(pit);
831                 yy += par.ascent();
832                 paintPar(pi, *bv.text(), pit, 0, yy, select || !vi.singlepar);
833                 yy += par.descent();
834         }
835
836         // Cache one paragraph above and one below
837         // Note MV: this cannot be suppressed even for singlepar.
838         // Try viewing the User Guide Mobius figure
839
840         if (vi.p1 > 0) {
841                 text->redoParagraph(vi.p1 - 1);
842                 theCoords.parPos()[bv.text()][vi.p1 - 1] = 
843                         Point(0, vi.y1 - text->getPar(vi.p1 - 1).descent());
844         }
845
846         if (vi.p2 < lyx::pit_type(text->paragraphs().size()) - 1) {
847                 text->redoParagraph(vi.p2 + 1);
848                 theCoords.parPos()[bv.text()][vi.p2 + 1] = 
849                         Point(0, vi.y2 + text->getPar(vi.p2 + 1).ascent());
850         }
851
852         // and grey out above (should not happen later)
853 //      lyxerr << "par ascent: " << text->getPar(vi.p1).ascent() << endl;
854         if (vi.y1 > 0 && !vi.singlepar)
855                 pain.fillRectangle(0, 0, bv.workWidth(), vi.y1, LColor::bottomarea);
856
857         // and possibly grey out below
858 //      lyxerr << "par descent: " << text->getPar(vi.p1).ascent() << endl;
859         if (vi.y2 < bv.workHeight() && !vi.singlepar)
860                 pain.fillRectangle(0, vi.y2, bv.workWidth(), bv.workHeight() - vi.y2, LColor::bottomarea);
861 }
862
863
864 void paintTextInset(LyXText const & text, PainterInfo & pi, int x, int y)
865 {
866 //      lyxerr << "  paintTextInset: y: " << y << endl;
867
868         y -= text.getPar(0).ascent();
869         for (int pit = 0; pit < int(text.paragraphs().size()); ++pit) {
870                 y += text.getPar(pit).ascent();
871                 paintPar(pi, text, pit, x, y, true);
872                 y += text.getPar(pit).descent();
873         }
874 }