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