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