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