]> git.lyx.org Git - lyx.git/blob - src/rowpainter.C
efb9342e1951767be15862a874a235c71005de63
[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 "debug.h"
18 #include "bufferparams.h"
19 #include "BufferView.h"
20 #include "encoding.h"
21 #include "gettext.h"
22 #include "language.h"
23 #include "LColor.h"
24 #include "lyxrc.h"
25 #include "lyxrow.h"
26 #include "lyxrow_funcs.h"
27 #include "metricsinfo.h"
28 #include "paragraph.h"
29 #include "paragraph_funcs.h"
30 #include "ParagraphParameters.h"
31 #include "vspace.h"
32
33 #include "frontends/font_metrics.h"
34 #include "frontends/Painter.h"
35
36 #include "insets/insettext.h"
37
38 #include "support/textutils.h"
39
40
41 using lyx::pos_type;
42 using std::max;
43 using std::string;
44
45 extern int PAPER_MARGIN;
46 extern int CHANGEBAR_MARGIN;
47 extern int LEFT_MARGIN;
48
49 namespace {
50
51 // "temporary". We'll never get to use more
52 // references until we start adding hacks like
53 // these until other places catch up.
54 BufferView * perv(BufferView const & bv)
55 {
56         return const_cast<BufferView *>(&bv);
57 }
58
59 /**
60  * A class used for painting an individual row of text.
61  */
62 class RowPainter {
63 public:
64         /// initialise painter
65         RowPainter(BufferView const & bv, LyXText const & text,
66                 ParagraphList::iterator pit,
67                 RowList::iterator rit, int y_offset, int x_offset, int y);
68
69         /// do the painting
70         void paint();
71 private:
72         // paint various parts
73         void paintBackground();
74         void paintSelection();
75         void paintAppendix();
76         void paintDepthBar();
77         void paintChangeBar();
78         void paintFirst();
79         void paintLast();
80         void paintForeignMark(double orig_x, LyXFont const & orig_font);
81         void paintHebrewComposeChar(lyx::pos_type & vpos);
82         void paintArabicComposeChar(lyx::pos_type & vpos);
83         void paintChars(lyx::pos_type & vpos, bool hebrew, bool arabic);
84         int paintPageBreak(string const & label, int y);
85         int paintAppendixStart(int y);
86         int paintLengthMarker(string const & prefix, VSpace const & vsp, int start);
87         void paintText();
88         void paintFromPos(lyx::pos_type & vpos);
89         void paintInset(lyx::pos_type const pos);
90
91         /// return left margin
92         int leftMargin() const;
93
94         /// return the font at the given pos
95         LyXFont const getFont(lyx::pos_type pos) const;
96
97         /// return the label font for this row
98         LyXFont const getLabelFont() const;
99
100         char const transformChar(char c, lyx::pos_type pos) const;
101
102         /// return pixel width for the given pos
103         int singleWidth(lyx::pos_type pos) const;
104         int singleWidth(lyx::pos_type pos, char c) const;
105
106         /// bufferview to paint on
107         BufferView const & bv_;
108
109         /// Painter to use
110         Painter & pain_;
111
112         /// LyXText for the row
113         LyXText const & text_;
114
115         /// The row to paint
116         RowList::iterator row_;
117
118         /// Row's paragraph
119         mutable ParagraphList::iterator  pit_;
120
121         // Looks ugly - is
122         double xo_;
123         int yo_;
124         double x_;
125         int y_;
126         int width_;
127         double separator_;
128         double hfill_;
129         double label_hfill_;
130 };
131
132
133 RowPainter::RowPainter(BufferView const & bv, LyXText const & text,
134      ParagraphList::iterator pit, RowList::iterator rit,
135      int y_offset, int x_offset, int y)
136         : bv_(bv), pain_(bv_.painter()), text_(text), row_(rit),
137           pit_(pit), xo_(x_offset), yo_(y_offset), y_(y)
138 {}
139
140
141 /// "temporary"
142 LyXFont const RowPainter::getFont(pos_type pos) const
143 {
144         return text_.getFont(pit_, pos);
145 }
146
147
148 int RowPainter::singleWidth(lyx::pos_type pos) const
149 {
150         return text_.singleWidth(pit_, pos);
151 }
152
153
154 int RowPainter::singleWidth(lyx::pos_type pos, char c) const
155 {
156         LyXFont const & font = text_.getFont(pit_, pos);
157         return text_.singleWidth(pit_, pos, c, font);
158 }
159
160
161 LyXFont const RowPainter::getLabelFont() const
162 {
163         return text_.getLabelFont(pit_);
164 }
165
166
167 char const RowPainter::transformChar(char c, lyx::pos_type pos) const
168 {
169         return text_.transformChar(c, *pit_, pos);
170 }
171
172
173 int RowPainter::leftMargin() const
174 {
175         return text_.leftMargin(pit_, *row_);
176 }
177
178
179 void RowPainter::paintInset(pos_type const pos)
180 {
181         InsetOld * inset = const_cast<InsetOld*>(pit_->getInset(pos));
182
183         BOOST_ASSERT(inset);
184
185         PainterInfo pi(perv(bv_));
186         pi.base.font = getFont(pos);
187         inset->draw(pi, int(x_), yo_ + row_->baseline());
188         x_ += inset->width();
189 }
190
191
192 void RowPainter::paintHebrewComposeChar(pos_type & vpos)
193 {
194         pos_type pos = text_.vis2log(vpos);
195
196         string str;
197
198         // first char
199         char c = pit_->getChar(pos);
200         str += c;
201         ++vpos;
202
203         LyXFont const & font = getFont(pos);
204         int const width = font_metrics::width(c, font);
205         int dx = 0;
206
207         for (pos_type i = pos - 1; i >= 0; --i) {
208                 c = pit_->getChar(i);
209                 if (!Encodings::IsComposeChar_hebrew(c)) {
210                         if (IsPrintableNonspace(c)) {
211                                 int const width2 =
212                                         singleWidth(i, c);
213                                 // dalet / resh
214                                 dx = (c == 'ø' || c == 'ã')
215                                         ? width2 - width
216                                         : (width2 - width) / 2;
217                         }
218                         break;
219                 }
220         }
221
222         // Draw nikud
223         pain_.text(int(x_) + dx, yo_ + row_->baseline(), str, font);
224 }
225
226
227 void RowPainter::paintArabicComposeChar(pos_type & vpos)
228 {
229         pos_type pos = text_.vis2log(vpos);
230         string str;
231
232         // first char
233         char c = pit_->getChar(pos);
234         c = transformChar(c, pos);
235         str +=c;
236         ++vpos;
237
238         LyXFont const & font = getFont(pos);
239         int const width = font_metrics::width(c, font);
240         int dx = 0;
241
242         for (pos_type i = pos - 1; i >= 0; --i) {
243                 c = pit_->getChar(i);
244                 if (!Encodings::IsComposeChar_arabic(c)) {
245                         if (IsPrintableNonspace(c)) {
246                                 int const width2 =
247                                         singleWidth(i, c);
248                                 dx = (width2 - width) / 2;
249                         }
250                         break;
251                 }
252         }
253         // Draw nikud
254         pain_.text(int(x_) + dx, yo_ + row_->baseline(), str, font);
255 }
256
257
258 void RowPainter::paintChars(pos_type & vpos, bool hebrew, bool arabic)
259 {
260         pos_type pos = text_.vis2log(vpos);
261         pos_type const last = lastPos(*pit_, *row_);
262         LyXFont orig_font = getFont(pos);
263
264         // first character
265         string str;
266         str += pit_->getChar(pos);
267         if (arabic) {
268                 unsigned char c = str[0];
269                 str[0] = transformChar(c, pos);
270         }
271
272         bool prev_struckout(isDeletedText(*pit_, pos));
273         bool prev_newtext(isInsertedText(*pit_, pos));
274
275         ++vpos;
276
277         // collect as much similar chars as we can
278         while (vpos <= last && (pos = text_.vis2log(vpos)) >= 0) {
279                 char c = pit_->getChar(pos);
280
281                 if (!IsPrintableNonspace(c))
282                         break;
283
284                 if (prev_struckout != isDeletedText(*pit_, pos))
285                         break;
286
287                 if (prev_newtext != isInsertedText(*pit_, pos))
288                         break;
289
290                 if (arabic && Encodings::IsComposeChar_arabic(c))
291                         break;
292                 if (hebrew && Encodings::IsComposeChar_hebrew(c))
293                         break;
294
295                 if (orig_font != getFont(pos))
296                         break;
297
298                 if (arabic)
299                         c = transformChar(c, pos);
300                 str += c;
301                 ++vpos;
302         }
303
304         if (prev_struckout) {
305                 orig_font.setColor(LColor::strikeout);
306         } else if (prev_newtext) {
307                 orig_font.setColor(LColor::newtext);
308         }
309
310         // Draw text and set the new x position
311         //lyxerr << "paint row: yo_ " << yo_ << " baseline: " << row_->baseline()
312         //      << "\n";
313         pain_.text(int(x_), yo_ + row_->baseline(), str, orig_font);
314         x_ += font_metrics::width(str, orig_font);
315 }
316
317
318 void RowPainter::paintForeignMark(double orig_x, LyXFont const & orig_font)
319 {
320         if (!lyxrc.mark_foreign_language)
321                 return;
322         if (orig_font.language() == latex_language)
323                 return;
324         if (orig_font.language() == bv_.buffer()->params().language)
325                 return;
326
327         int const y = yo_ + row_->baseline() + 1;
328         pain_.line(int(orig_x), y, int(x_), y, LColor::language);
329 }
330
331
332 void RowPainter::paintFromPos(pos_type & vpos)
333 {
334         pos_type const pos = text_.vis2log(vpos);
335
336         LyXFont const & orig_font = getFont(pos);
337
338         double const orig_x = x_;
339
340         char const c = pit_->getChar(pos);
341
342         if (c == Paragraph::META_INSET) {
343                 paintInset(pos);
344                 ++vpos;
345                 paintForeignMark(orig_x, orig_font);
346                 return;
347         }
348
349         // usual characters, no insets
350
351         // special case languages
352         bool const hebrew = (orig_font.language()->lang() == "hebrew");
353         bool const arabic =
354                 orig_font.language()->lang() == "arabic" &&
355                 (lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 ||
356                 lyxrc.font_norm_type == LyXRC::ISO_10646_1);
357
358         // draw as many chars as we can
359         if ((!hebrew && !arabic)
360                 || (hebrew && !Encodings::IsComposeChar_hebrew(c))
361                 || (arabic && !Encodings::IsComposeChar_arabic(c))) {
362                 paintChars(vpos, hebrew, arabic);
363         } else if (hebrew) {
364                 paintHebrewComposeChar(vpos);
365         } else if (arabic) {
366                 paintArabicComposeChar(vpos);
367         }
368
369         paintForeignMark(orig_x, orig_font);
370
371         return;
372 }
373
374
375 void RowPainter::paintBackground()
376 {
377         int const x = int(xo_);
378         int const y = yo_ < 0 ? 0 : yo_;
379         int const h = yo_ < 0 ? row_->height() + yo_ : row_->height();
380         pain_.fillRectangle(x, y, width_, h, text_.backgroundColor());
381 }
382
383
384 void RowPainter::paintSelection()
385 {
386         bool const is_rtl = pit_->isRightToLeftPar(bv_.buffer()->params());
387
388         // the current selection
389         int const startx = text_.selection.start.x();
390         int const endx = text_.selection.end.x();
391         int const starty = text_.selection.start.y();
392         int const endy = text_.selection.end.y();
393         RowList::iterator startrow = text_.getRow(text_.selection.start);
394         RowList::iterator endrow = text_.getRow(text_.selection.end);
395
396         if (text_.bidi_same_direction) {
397                 int x;
398                 int y = yo_;
399                 int w;
400                 int h = row_->height();
401
402                 if (startrow == row_ && endrow == row_) {
403                         if (startx < endx) {
404                                 x = int(xo_) + startx;
405                                 w = endx - startx;
406                                 pain_.fillRectangle(x, y, w, h, LColor::selection);
407                         } else {
408                                 x = int(xo_) + endx;
409                                 w = startx - endx;
410                                 pain_.fillRectangle(x, y, w, h, LColor::selection);
411                         }
412                 } else if (startrow == row_) {
413                         int const x = is_rtl ? int(xo_) : int(xo_ + startx);
414                         int const w = is_rtl ? startx : (width_ - startx);
415                         pain_.fillRectangle(x, y, w, h, LColor::selection);
416                 } else if (endrow == row_) {
417                         int const x = is_rtl ? int(xo_ + endx) : int(xo_);
418                         int const w = is_rtl ? (width_ - endx) : endx;
419                         pain_.fillRectangle(x, y, w, h, LColor::selection);
420                 } else if (y_ > starty && y_ < endy) {
421                         pain_.fillRectangle(int(xo_), y, width_, h, LColor::selection);
422                 }
423                 return;
424         } else if (startrow != row_ && endrow != row_) {
425                 if (y_ > starty && y_ < endy) {
426                         int w = width_;
427                         int h = row_->height();
428                         pain_.fillRectangle(int(xo_), yo_, w, h, LColor::selection);
429                 }
430                 return;
431         }
432
433         if ((startrow != row_ && !is_rtl) || (endrow != row_ && is_rtl))
434                 pain_.fillRectangle(int(xo_), yo_,
435                         int(x_), row_->height(), LColor::selection);
436
437         pos_type const body_pos = pit_->beginningOfBody();
438         pos_type const last = lastPos(*pit_, *row_);
439         double tmpx = x_;
440
441         for (pos_type vpos = row_->pos(); vpos <= last; ++vpos)  {
442                 pos_type pos = text_.vis2log(vpos);
443                 double const old_tmpx = tmpx;
444                 if (body_pos > 0 && pos == body_pos - 1) {
445                         LyXLayout_ptr const & layout = pit_->layout();
446                         LyXFont const lfont = getLabelFont();
447
448                         tmpx += label_hfill_ + font_metrics::width(layout->labelsep, lfont);
449
450                         if (pit_->isLineSeparator(body_pos - 1))
451                                 tmpx -= singleWidth(body_pos - 1);
452                 }
453
454                 if (hfillExpansion(*pit_, *row_, pos)) {
455                         tmpx += singleWidth(pos);
456                         if (pos >= body_pos)
457                                 tmpx += hfill_;
458                         else
459                                 tmpx += label_hfill_;
460                 }
461
462                 else if (pit_->isSeparator(pos)) {
463                         tmpx += singleWidth(pos);
464                         if (pos >= body_pos)
465                                 tmpx += separator_;
466                 } else {
467                         tmpx += singleWidth(pos);
468                 }
469
470                 if ((startrow != row_ || text_.selection.start.pos() <= pos) &&
471                         (endrow != row_ || pos < text_.selection.end.pos())) {
472                         // Here we do not use x_ as xo_ was added to x_.
473                         pain_.fillRectangle(int(old_tmpx), yo_,
474                                 int(tmpx - old_tmpx + 1),
475                                 row_->height(), LColor::selection);
476                 }
477         }
478
479         if ((startrow != row_ && is_rtl) || (endrow != row_ && !is_rtl)) {
480                 pain_.fillRectangle(int(xo_ + tmpx),
481                                       yo_, int(bv_.workWidth() - tmpx),
482                                       row_->height(), LColor::selection);
483         }
484 }
485
486
487 void RowPainter::paintChangeBar()
488 {
489         pos_type const start = row_->pos();
490         pos_type const end = lastPos(*pit_, *row_);
491
492         if (!pit_->isChanged(start, end))
493                 return;
494
495         int const height = (row_ == text_.lastRow())
496                 ? row_->baseline()
497                 : row_->height() + boost::next(row_)->top_of_text();
498
499         pain_.fillRectangle(4, yo_, 5, height, LColor::changebar);
500 }
501
502
503 void RowPainter::paintAppendix()
504 {
505         if (!pit_->params().appendix())
506                 return;
507
508         // FIXME: can be just width_ ?
509         int const ww = bv_.workWidth();
510
511         int y = yo_;
512
513         if (pit_->params().startOfAppendix())
514                 y += 2 * defaultRowHeight();
515
516         pain_.line(1, y, 1, yo_ + row_->height(), LColor::appendix);
517         pain_.line(ww - 2, y, ww - 2, yo_ + row_->height(), LColor::appendix);
518 }
519
520
521 void RowPainter::paintDepthBar()
522 {
523         Paragraph::depth_type const depth = pit_->getDepth();
524
525         if (depth <= 0)
526                 return;
527
528         Paragraph::depth_type prev_depth = 0;
529         if (row_ != text_.firstRow()) {
530                 ParagraphList::iterator pit2 = pit_;
531                 if (row_->pos() == 0)
532                         --pit2;
533                 prev_depth = pit2->getDepth();
534         }
535
536         Paragraph::depth_type next_depth = 0;
537         if (row_ != text_.lastRow()) {
538                 ParagraphList::iterator pit2 = pit_;
539                 if (row_->endpos() >= pit2->size())
540                         ++pit2;
541                 next_depth = pit2->getDepth();
542         }
543
544         for (Paragraph::depth_type i = 1; i <= depth; ++i) {
545                 int const w = PAPER_MARGIN / 5;
546                 int x = int(w * i + xo_);
547                 // only consider the changebar space if we're drawing outer left
548                 if (!xo_)
549                         x += CHANGEBAR_MARGIN;
550                 int const h = yo_ + row_->height() - 1 - (i - next_depth - 1) * 3;
551
552                 pain_.line(x, yo_, x, h, LColor::depthbar);
553
554                 if (i > prev_depth)
555                         pain_.fillRectangle(x, yo_, w, 2, LColor::depthbar);
556                 if (i > next_depth)
557                         pain_.fillRectangle(x, h, w, 2, LColor::depthbar);
558         }
559 }
560
561
562 int RowPainter::paintLengthMarker(string const & prefix, VSpace const & vsp, int start)
563 {
564         if (vsp.kind() == VSpace::NONE)
565                 return 0;
566
567         int const arrow_size = 4;
568         int const size = getLengthMarkerHeight(bv_, vsp);
569         int const end = start + size;
570
571         // the label to display (if any)
572         string str;
573         // y-values for top arrow
574         int ty1, ty2;
575         // y-values for bottom arrow
576         int by1, by2;
577
578         str = prefix + " (" + vsp.asLyXCommand() + ")";
579
580         if (vsp.kind() == VSpace::VFILL) {
581                 ty1 = ty2 = start;
582                 by1 = by2 = end;
583         } else {
584                 // adding or removing space
585                 bool const added = vsp.kind() != VSpace::LENGTH ||
586                                    vsp.length().len().value() > 0.0;
587                 ty1 = added ? (start + arrow_size) : start;
588                 ty2 = added ? start : (start + arrow_size);
589                 by1 = added ? (end - arrow_size) : end;
590                 by2 = added ? end : (end - arrow_size);
591         }
592
593         int const leftx = int(xo_) + leftMargin();
594         int const midx = leftx + arrow_size;
595         int const rightx = midx + arrow_size;
596
597         // first the string
598         int w = 0;
599         int a = 0;
600         int d = 0;
601
602         LyXFont font;
603         font.setColor(LColor::added_space);
604         font.decSize();
605         font.decSize();
606         font_metrics::rectText(str, font, w, a, d);
607
608         pain_.rectText(leftx + 2 * arrow_size + 5,
609                        start + ((end - start) / 2) + d,
610                        str, font,
611                        LColor::none, LColor::none);
612
613         // top arrow
614         pain_.line(leftx, ty1, midx, ty2, LColor::added_space);
615         pain_.line(midx, ty2, rightx, ty1, LColor::added_space);
616
617         // bottom arrow
618         pain_.line(leftx, by1, midx, by2, LColor::added_space);
619         pain_.line(midx, by2, rightx, by1, LColor::added_space);
620
621         // joining line
622         pain_.line(midx, ty2, midx, by2, LColor::added_space);
623
624         return size;
625 }
626
627
628 int RowPainter::paintPageBreak(string const & label, int y)
629 {
630         LyXFont pb_font;
631         pb_font.setColor(LColor::pagebreak);
632         pb_font.decSize();
633
634         int w = 0;
635         int a = 0;
636         int d = 0;
637         font_metrics::rectText(label, pb_font, w, a, d);
638
639         int const text_start = int(xo_ + (width_ - w) / 2);
640         int const text_end = text_start + w;
641
642         pain_.rectText(text_start, y + d, label, pb_font, LColor::none, LColor::none);
643
644         pain_.line(int(xo_), y, text_start, y,
645                    LColor::pagebreak, Painter::line_onoffdash);
646         pain_.line(text_end, y, int(xo_ + width_), y,
647                    LColor::pagebreak, Painter::line_onoffdash);
648
649         return 3 * defaultRowHeight();
650 }
651
652
653 int RowPainter::paintAppendixStart(int y)
654 {
655         LyXFont pb_font;
656         pb_font.setColor(LColor::appendix);
657         pb_font.decSize();
658
659         string const label = _("Appendix");
660         int w = 0;
661         int a = 0;
662         int d = 0;
663         font_metrics::rectText(label, pb_font, w, a, d);
664
665         int const text_start = int(xo_ + (width_ - w) / 2);
666         int const text_end = text_start + w;
667
668         pain_.rectText(text_start, y + d, label, pb_font, LColor::none, LColor::none);
669
670         pain_.line(int(xo_ + 1), y, text_start, y, LColor::appendix);
671         pain_.line(text_end, y, int(xo_ + width_ - 2), y, LColor::appendix);
672
673         return 3 * defaultRowHeight();
674 }
675
676
677 void RowPainter::paintFirst()
678 {
679         ParagraphParameters const & parparams = pit_->params();
680
681         int y_top = 0;
682
683         // start of appendix?
684         if (parparams.startOfAppendix())
685                 y_top += paintAppendixStart(yo_ + y_top + 2 * defaultRowHeight());
686
687         // the top margin
688         if (row_ == text_.firstRow() && !text_.isInInset())
689                 y_top += PAPER_MARGIN;
690
691         // draw a top pagebreak
692         if (parparams.pagebreakTop())
693                 y_top += paintPageBreak(_("Page Break (top)"),
694                         yo_ + y_top + 2 * defaultRowHeight());
695
696         // draw the additional space if needed:
697         y_top += paintLengthMarker(_("Space above"), parparams.spaceTop(),
698                         yo_ + y_top);
699
700         Buffer const & buffer = *bv_.buffer();
701
702         LyXLayout_ptr const & layout = pit_->layout();
703
704         if (buffer.params().paragraph_separation == BufferParams::PARSEP_SKIP) {
705                 if (pit_ != text_.ownerParagraphs().begin()) {
706                         if (layout->latextype == LATEX_PARAGRAPH
707                                 && !pit_->getDepth()) {
708                                 y_top += buffer.params().getDefSkip().inPixels(bv_);
709                         } else {
710                                 LyXLayout_ptr const & playout =
711                                         boost::prior(pit_)->layout();
712                                 if (playout->latextype == LATEX_PARAGRAPH
713                                         && !boost::prior(pit_)->getDepth()) {
714                                         // is it right to use defskip here, too? (AS)
715                                         y_top += buffer.params().getDefSkip().inPixels(bv_);
716                                 }
717                         }
718                 }
719         }
720
721         int const ww = bv_.workWidth();
722
723         // draw a top line
724         if (parparams.lineTop()) {
725                 int const asc = font_metrics::ascent('x', getFont(0));
726
727                 y_top += asc;
728
729                 int const w = (text_.isInInset() ? text_.inset_owner->width() : ww);
730                 int const xp = static_cast<int>(text_.isInInset() ? xo_ : 0);
731                 pain_.line(xp, yo_ + y_top, xp + w, yo_ + y_top,
732                         LColor::topline, Painter::line_solid,
733                         Painter::line_thick);
734
735                 y_top += asc;
736         }
737
738         bool const is_rtl = pit_->isRightToLeftPar(bv_.buffer()->params());
739         bool const is_seq = isFirstInSequence(pit_, text_.ownerParagraphs());
740         //lyxerr << "paintFirst: " << pit_->id() << " is_seq: " << is_seq << std::endl;
741
742         // should we print a label?
743         if (layout->labeltype >= LABEL_STATIC
744             && (layout->labeltype != LABEL_STATIC
745                       || layout->latextype != LATEX_ENVIRONMENT
746                       || is_seq)) {
747
748                 LyXFont font = getLabelFont();
749                 if (!pit_->getLabelstring().empty()) {
750                         double x = x_;
751                         string const str = pit_->getLabelstring();
752
753                         // this is special code for the chapter layout. This is
754                         // printed in an extra row and has a pagebreak at
755                         // the top.
756                         if (layout->counter == "chapter") {
757                                 if (buffer.params().secnumdepth >= 0) {
758                                         float spacing_val = 1.0;
759                                         if (!parparams.spacing().isDefault()) {
760                                                 spacing_val = parparams.spacing().getValue();
761                                         } else {
762                                                 spacing_val = buffer.params().spacing().getValue();
763                                         }
764
765                                         int const maxdesc =
766                                                 int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val)
767                                                 + int(layout->parsep) * defaultRowHeight();
768
769                                         if (is_rtl) {
770                                                 x = ww - leftMargin() -
771                                                         font_metrics::width(str, font);
772                                         }
773
774                                         pain_.text(int(x),
775                                                 yo_ + row_->baseline() -
776                                                 row_->ascent_of_text() - maxdesc,
777                                                 str, font);
778                                 }
779                         } else {
780                                 if (is_rtl) {
781                                         x = ww - leftMargin()
782                                                 + font_metrics::width(layout->labelsep, font);
783                                 } else {
784                                         x = x_ - font_metrics::width(layout->labelsep, font)
785                                                 - font_metrics::width(str, font);
786                                 }
787
788                                 pain_.text(int(x), yo_ + row_->baseline(), str, font);
789                         }
790                 }
791
792         // the labels at the top of an environment.
793         // More or less for bibliography
794         } else if (is_seq &&
795                 (layout->labeltype == LABEL_TOP_ENVIRONMENT ||
796                 layout->labeltype == LABEL_BIBLIO ||
797                 layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) {
798                 LyXFont font = getLabelFont();
799                 if (!pit_->getLabelstring().empty()) {
800                         string const str = pit_->getLabelstring();
801                         float spacing_val = 1.0;
802                         if (!parparams.spacing().isDefault()) {
803                                 spacing_val = parparams.spacing().getValue();
804                         } else {
805                                 spacing_val = buffer.params().spacing().getValue();
806                         }
807
808                         int maxdesc =
809                                 int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val
810                                 + (layout->labelbottomsep * defaultRowHeight()));
811
812                         double x = x_;
813                         if (layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) {
814                                 x = ((is_rtl ? leftMargin() : x_)
815                                          + ww - text_.rightMargin(*pit_, *bv_.buffer(), *row_)) / 2;
816                                 x -= font_metrics::width(str, font) / 2;
817                         } else if (is_rtl) {
818                                 x = ww - leftMargin() -
819                                         font_metrics::width(str, font);
820                         }
821                         pain_.text(int(x),
822                             yo_ + row_->baseline() - row_->ascent_of_text() - maxdesc,
823                                   str, font);
824                 }
825         }
826 }
827
828
829 void RowPainter::paintLast()
830 {
831         ParagraphParameters const & parparams = pit_->params();
832         int y_bottom = row_->height() - 1;
833
834         // the bottom margin
835         if (row_ == text_.lastRow() && !text_.isInInset())
836                 y_bottom -= PAPER_MARGIN;
837
838         int const ww = bv_.workWidth();
839
840         // draw a bottom pagebreak
841         if (parparams.pagebreakBottom()) {
842                 y_bottom -= paintPageBreak(_("Page Break (bottom)"),
843                         yo_ + y_bottom - 2 * defaultRowHeight());
844         }
845
846         // draw the additional space if needed:
847         int const height = getLengthMarkerHeight(bv_, parparams.spaceBottom());
848         y_bottom -= paintLengthMarker(_("Space below"), parparams.spaceBottom(),
849                              yo_ + y_bottom - height);
850
851         // draw a bottom line
852         if (parparams.lineBottom()) {
853                 int const asc = font_metrics::ascent('x',
854                         getFont(max(pos_type(0), pit_->size() - 1)));
855
856                 y_bottom -= asc;
857
858                 int const w = text_.isInInset() ? text_.inset_owner->width() : ww;
859                 int const xp = int(text_.isInInset() ? xo_ : 0);
860                 int const y = yo_ + y_bottom;
861                 pain_.line(xp, y, xp + w, y, LColor::topline, Painter::line_solid,
862                           Painter::line_thick);
863
864                 y_bottom -= asc;
865         }
866
867         bool const is_rtl = pit_->isRightToLeftPar(bv_.buffer()->params());
868         int const endlabel = getEndLabel(pit_, text_.ownerParagraphs());
869
870         // draw an endlabel
871         switch (endlabel) {
872         case END_LABEL_BOX:
873         case END_LABEL_FILLED_BOX:
874         {
875                 LyXFont const font = getLabelFont();
876                 int const size = int(0.75 * font_metrics::maxAscent(font));
877                 int const y = (yo_ + row_->baseline()) - size;
878                 int x = is_rtl ? LEFT_MARGIN : ww - PAPER_MARGIN - size;
879
880                 if (row_->fill() <= size)
881                         x += (size - row_->fill() + 1) * (is_rtl ? -1 : 1);
882
883                 if (endlabel == END_LABEL_BOX)
884                         pain_.rectangle(x, y, size, size, LColor::eolmarker);
885                 else
886                         pain_.fillRectangle(x, y, size, size, LColor::eolmarker);
887                 break;
888         }
889         case END_LABEL_STATIC:
890         {
891                 LyXFont font = getLabelFont();
892                 string const & str = pit_->layout()->endlabelstring();
893                 double const x = is_rtl ?
894                         x_ - font_metrics::width(str, font)
895                         : ww - text_.rightMargin(*pit_, *bv_.buffer(), *row_) - row_->fill();
896                 pain_.text(int(x), yo_ + row_->baseline(), str, font);
897                 break;
898         }
899         case END_LABEL_NO_LABEL:
900                 break;
901         }
902 }
903
904
905 void RowPainter::paintText()
906 {
907         pos_type const last = lastPos(*pit_, *row_);
908         pos_type body_pos = pit_->beginningOfBody();
909         if (body_pos > 0 &&
910                 (body_pos - 1 > last || !pit_->isLineSeparator(body_pos - 1))) {
911                 body_pos = 0;
912         }
913
914         LyXLayout_ptr const & layout = pit_->layout();
915
916         bool running_strikeout = false;
917         bool is_struckout = false;
918         int last_strikeout_x = 0;
919
920         pos_type vpos = row_->pos();
921         while (vpos <= last) {
922                 if (x_ > bv_.workWidth())
923                         break;
924                 pos_type pos = text_.vis2log(vpos);
925
926                 if (pos >= pit_->size()) {
927                         ++vpos;
928                         continue;
929                 }
930
931                 if (x_ + singleWidth(pos) < 0) {
932                         x_ += singleWidth(pos);
933                         ++vpos;
934                         continue;
935                 }
936
937                 is_struckout = isDeletedText(*pit_, pos);
938
939                 if (is_struckout && !running_strikeout) {
940                         running_strikeout = true;
941                         last_strikeout_x = int(x_);
942                 }
943
944                 bool const highly_editable_inset = pit_->isInset(pos)
945                         && isHighlyEditableInset(pit_->getInset(pos));
946
947                 // if we reach the end of a struck out range, paint it
948                 // we also don't paint across things like tables
949                 if (running_strikeout && (highly_editable_inset || !is_struckout)) {
950                         int const middle = yo_ + row_->top_of_text()
951                                 + (row_->baseline() - row_->top_of_text()) / 2;
952                         pain_.line(last_strikeout_x, middle, int(x_), middle,
953                                 LColor::strikeout, Painter::line_solid, Painter::line_thin);
954                         running_strikeout = false;
955                 }
956
957                 if (body_pos > 0 && pos == body_pos - 1) {
958                         int const lwidth = font_metrics::width(layout->labelsep,
959                                 getLabelFont());
960
961                         x_ += label_hfill_ + lwidth - singleWidth(body_pos - 1);
962                 }
963
964                 if (pit_->isHfill(pos)) {
965                         x_ += 1;
966
967                         int const y0 = yo_ + row_->baseline();
968                         int const y1 = y0 - defaultRowHeight() / 2;
969
970                         pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
971
972                         if (hfillExpansion(*pit_, *row_, pos)) {
973                                 int const y2 = (y0 + y1) / 2;
974
975                                 if (pos >= body_pos) {
976                                         pain_.line(int(x_), y2, int(x_ + hfill_), y2,
977                                                   LColor::added_space,
978                                                   Painter::line_onoffdash);
979                                         x_ += hfill_;
980                                 } else {
981                                         pain_.line(int(x_), y2, int(x_ + label_hfill_), y2,
982                                                   LColor::added_space,
983                                                   Painter::line_onoffdash);
984                                         x_ += label_hfill_;
985                                 }
986                                 pain_.line(int(x_), y1, int(x_), y0, LColor::added_space);
987                         }
988                         x_ += 2;
989                         ++vpos;
990                 } else if (pit_->isSeparator(pos)) {
991                         x_ += singleWidth(pos);
992                         if (pos >= body_pos)
993                                 x_ += separator_;
994                         ++vpos;
995                 } else {
996                         paintFromPos(vpos);
997                 }
998         }
999
1000         // if we reach the end of a struck out range, paint it
1001         if (running_strikeout) {
1002                 int const middle = yo_ + row_->top_of_text()
1003                         + ((row_->baseline() - row_->top_of_text()) / 2);
1004                 pain_.line(last_strikeout_x, middle, int(x_), middle,
1005                         LColor::strikeout, Painter::line_solid, Painter::line_thin);
1006                 running_strikeout = false;
1007         }
1008 }
1009
1010
1011 void RowPainter::paint()
1012 {
1013         width_       = text_.workWidth();
1014         x_           = row_->x();
1015         separator_   = row_->fill_separator();
1016         hfill_       = row_->fill_hfill();
1017         label_hfill_ = row_->fill_label_hfill();
1018
1019         // FIXME: what is this fixing ?
1020         if (text_.isInInset() && x_ < 0)
1021                 x_ = 0;
1022         x_ += xo_;
1023
1024         // If we're *not* at the top-level of rows, then the
1025         // background has already been cleared.
1026         if (&text_ == bv_.text)
1027                 paintBackground();
1028
1029         // paint the selection background
1030         if (text_.selection.set())
1031                 paintSelection();
1032
1033         // vertical lines for appendix
1034         paintAppendix();
1035
1036         // environment depth brackets
1037         paintDepthBar();
1038
1039         // changebar
1040         paintChangeBar();
1041
1042         if (row_->pos() == 0)
1043                 paintFirst();
1044
1045         if (row_->endpos() >= pit_->size())
1046                 paintLast();
1047
1048         // paint text
1049         paintText();
1050 }
1051
1052
1053 int paintRows(BufferView const & bv, LyXText const & text,
1054         ParagraphList::iterator pit, RowList::iterator rit,
1055         int xo, int y, int yf, int yo)
1056 {
1057         //lyxerr << "  paintRows: rit: " << &*rit << endl;
1058         //const_cast<LyXText&>(text).updateRowPositions();
1059         int const yy = yf - y;
1060         int const y2 = bv.painter().paperHeight();
1061
1062         ParagraphList::iterator end = text.ownerParagraphs().end();
1063         bool active = false;
1064
1065         for ( ; pit != end; ++pit) {
1066                 RowList::iterator row = pit->rows.begin();
1067                 RowList::iterator rend = pit->rows.end();
1068
1069                 for ( ; row != rend; ++row) {
1070                         if (row == rit)
1071                                 active = true;
1072                         if (active) {
1073                                 RowPainter painter(bv, text, pit, row, y + yo, xo, y + bv.top_y());
1074                                 painter.paint();
1075                                 y += row->height();
1076                                 if (yy + y >= y2)
1077                                         return y;
1078                         } else {
1079                                 //lyxerr << "   paintRows: row: " << &*row << " ignored" << endl;
1080                         }
1081                 }
1082         }
1083
1084         return y;
1085 }
1086
1087 } // namespace anon
1088
1089
1090 int paintText(BufferView & bv)
1091 {
1092         int const topy = bv.top_y();
1093         ParagraphList::iterator pit;
1094         RowList::iterator rit = bv.text->getRowNearY(topy, pit);
1095         int const y = pit->y + rit->y_offset() - topy;
1096         return paintRows(bv, *bv.text, pit, rit, 0, y, y, 0);
1097 }
1098
1099
1100 void paintTextInset(BufferView & bv, LyXText & text, int x, int baseline)
1101 {
1102         RowList::iterator rit = text.firstRow();
1103         RowList::iterator end = text.endRow();
1104         ParagraphList::iterator pit = text.ownerParagraphs().begin();
1105
1106         int y_offset = baseline - rit->ascent_of_text();
1107         int y = y_offset;
1108         while (rit != end && y + rit->height() <= 0) {
1109                 y += rit->height();
1110                 text.nextRow(pit, rit);
1111         }
1112         if (y_offset < 0)
1113                 paintRows(bv, text, pit, rit, x, 0, y, y);
1114         else
1115                 paintRows(bv, text, pit, rit, x, 0, y_offset, y_offset);
1116 }
1117
1118
1119 int getLengthMarkerHeight(BufferView const & bv, VSpace const & vsp)
1120 {
1121         if (vsp.kind() == VSpace::NONE)
1122                 return 0;
1123
1124         int const arrow_size = 4;
1125         int const space_size = vsp.inPixels(bv);
1126
1127         LyXFont font;
1128         font.decSize();
1129         int const min_size = max(3 * arrow_size,
1130                 font_metrics::maxAscent(font)
1131                 + font_metrics::maxDescent(font));
1132
1133         if (vsp.length().len().value() < 0.0)
1134                 return min_size;
1135         else
1136                 return max(min_size, space_size);
1137 }