]> git.lyx.org Git - lyx.git/blob - src/rowpainter.C
"Inter-word Space"
[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_.selection.start.row();
318         RowList::iterator endrow = text_.selection.end.row();
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         if (boost::next(row_) != text_.rows().end())
456                 next_depth = boost::next(row_)->par()->getDepth();
457
458         for (Paragraph::depth_type i = 1; i <= depth; ++i) {
459                 int x = (PAPER_MARGIN / 5) * i + xo_;
460                 // only consider the changebar space if we're drawing outer left
461                 if (!xo_)
462                         x += CHANGEBAR_MARGIN;
463                 int const h = yo_ + row_->height() - 1 - (i - next_depth - 1) * 3;
464
465                 pain_.line(x, yo_, x, h, LColor::depthbar);
466
467                 int const w = PAPER_MARGIN / 5;
468
469                 if (i > prev_depth) {
470                         pain_.fillRectangle(x, yo_, w, 2, LColor::depthbar);
471                 }
472                 if (i > next_depth) {
473                         pain_.fillRectangle(x, h, w, 2, LColor::depthbar);
474                 }
475         }
476 }
477
478
479 int getLengthMarkerHeight(BufferView const & bv, VSpace const & vsp)
480 {
481         if (vsp.kind() == VSpace::NONE)
482                 return 0;
483
484         int const arrow_size = 4;
485         int const space_size = int(vsp.inPixels(bv));
486
487         LyXFont font;
488         font.decSize();
489         int const min_size = max(3 * arrow_size,
490                 font_metrics::maxAscent(font)
491                 + font_metrics::maxDescent(font));
492
493         if (vsp.length().len().value() < 0.0)
494                 return min_size;
495         else
496                 return max(min_size, space_size);
497 }
498
499
500 int RowPainter::paintLengthMarker(string const & prefix, VSpace const & vsp, int start)
501 {
502         if (vsp.kind() == VSpace::NONE)
503                 return 0;
504
505         int const arrow_size = 4;
506         int const size = getLengthMarkerHeight(bv_, vsp);
507         int const end = start + size;
508
509         // the label to display (if any)
510         string str;
511         // y-values for top arrow
512         int ty1, ty2;
513         // y-values for bottom arrow
514         int by1, by2;
515
516         str = prefix + " (" + vsp.asLyXCommand() + ")";
517
518         if (vsp.kind() == VSpace::VFILL) {
519                 ty1 = ty2 = start;
520                 by1 = by2 = end;
521         } else {
522                 // adding or removing space
523                 bool const added = vsp.kind() != VSpace::LENGTH ||
524                                    vsp.length().len().value() > 0.0;
525                 ty1 = added ? (start + arrow_size) : start;
526                 ty2 = added ? start : (start + arrow_size);
527                 by1 = added ? (end - arrow_size) : end;
528                 by2 = added ? end : (end - arrow_size);
529         }
530
531         int const leftx = xo_ + leftMargin();
532         int const midx = leftx + arrow_size;
533         int const rightx = midx + arrow_size;
534
535         // first the string
536         int w = 0;
537         int a = 0;
538         int d = 0;
539
540         LyXFont font;
541         font.setColor(LColor::added_space).decSize().decSize();
542         font_metrics::rectText(str, font, w, a, d);
543
544         pain_.rectText(leftx + 2 * arrow_size + 5,
545                          start + ((end - start) / 2) + d,
546                          str, font);
547
548         // top arrow
549         pain_.line(leftx, ty1, midx, ty2, LColor::added_space);
550         pain_.line(midx, ty2, rightx, ty1, LColor::added_space);
551
552         // bottom arrow
553         pain_.line(leftx, by1, midx, by2, LColor::added_space);
554         pain_.line(midx, by2, rightx, by1, LColor::added_space);
555
556         // joining line
557         pain_.line(midx, ty2, midx, by2, LColor::added_space);
558
559         return size;
560 }
561
562
563 int RowPainter::paintPageBreak(string const & label, int y)
564 {
565         LyXFont pb_font;
566         pb_font.setColor(LColor::pagebreak).decSize();
567
568         int w = 0;
569         int a = 0;
570         int d = 0;
571         font_metrics::rectText(label, pb_font, w, a, d);
572
573         int const text_start = xo_ + ((width_ - w) / 2);
574         int const text_end = text_start + w;
575
576         pain_.rectText(text_start, y + d, label, pb_font);
577
578         pain_.line(xo_, y, text_start, y,
579                 LColor::pagebreak, Painter::line_onoffdash);
580         pain_.line(text_end, y, xo_ + width_, y,
581                 LColor::pagebreak, Painter::line_onoffdash);
582
583         return 3 * defaultRowHeight();
584 }
585
586
587 int RowPainter::paintAppendixStart(int y)
588 {
589         LyXFont pb_font;
590         pb_font.setColor(LColor::appendix).decSize();
591
592         string const label = _("Appendix");
593         int w = 0;
594         int a = 0;
595         int d = 0;
596         font_metrics::rectText(label, pb_font, w, a, d);
597
598         int const text_start = xo_ + ((width_ - w) / 2);
599         int const text_end = text_start + w;
600
601         pain_.rectText(text_start, y + d, label, pb_font);
602
603         pain_.line(xo_ + 1, y, text_start, y, LColor::appendix);
604         pain_.line(text_end, y, xo_ + width_ - 2, y, LColor::appendix);
605
606         return 3 * defaultRowHeight();
607 }
608
609
610 void RowPainter::paintFirst()
611 {
612         ParagraphParameters const & parparams = pit_->params();
613
614         int y_top = 0;
615
616         // start of appendix?
617         if (parparams.startOfAppendix()) {
618                 y_top += paintAppendixStart(yo_ + y_top + 2 * defaultRowHeight());
619         }
620
621         // the top margin
622         if (row_ == text_.rows().begin() && !text_.isInInset())
623                 y_top += PAPER_MARGIN;
624
625         // draw a top pagebreak
626         if (parparams.pagebreakTop()) {
627                 y_top += paintPageBreak(_("Page Break (top)"),
628                         yo_ + y_top + 2 * defaultRowHeight());
629         }
630
631         // draw the additional space if needed:
632         y_top += paintLengthMarker(_("Space above"), parparams.spaceTop(),
633                         yo_ + y_top);
634
635         Buffer const * buffer = bv_.buffer();
636
637         LyXLayout_ptr const & layout = pit_->layout();
638
639         if (buffer->params.paragraph_separation == BufferParams::PARSEP_SKIP) {
640                 if (pit_ != text_.ownerParagraphs().begin()) {
641                         if (layout->latextype == LATEX_PARAGRAPH
642                                 && !pit_->getDepth()) {
643                                 y_top += buffer->params.getDefSkip().inPixels(bv_);
644                         } else {
645                                 LyXLayout_ptr const & playout =
646                                         boost::prior(pit_)->layout();
647                                 if (playout->latextype == LATEX_PARAGRAPH
648                                         && !boost::prior(pit_)->getDepth()) {
649                                         // is it right to use defskip here, too? (AS)
650                                         y_top += buffer->params.getDefSkip().inPixels(bv_);
651                                 }
652                         }
653                 }
654         }
655
656         int const ww = bv_.workWidth();
657
658         // draw a top line
659         if (parparams.lineTop()) {
660                 LyXFont font(LyXFont::ALL_SANE);
661                 int const asc = font_metrics::ascent('x', getFont(0));
662
663                 y_top += asc;
664
665                 int const w = (text_.isInInset() ? text_.inset_owner->width(perv(bv_), font) : ww);
666                 int const xp = static_cast<int>(text_.isInInset() ? xo_ : 0);
667                 pain_.line(xp, yo_ + y_top, xp + w, yo_ + y_top,
668                         LColor::topline, Painter::line_solid,
669                         Painter::line_thick);
670
671                 y_top += asc;
672         }
673
674         bool const is_rtl = pit_->isRightToLeftPar(bv_.buffer()->params);
675
676         // should we print a label?
677         if (layout->labeltype >= LABEL_STATIC
678             && (layout->labeltype != LABEL_STATIC
679                 || layout->latextype != LATEX_ENVIRONMENT
680                 || isFirstInSequence(pit_, text_.ownerParagraphs()))) {
681
682                 LyXFont font = getLabelFont();
683                 if (!pit_->getLabelstring().empty()) {
684                         float x = x_;
685                         string const str = pit_->getLabelstring();
686
687                         // this is special code for the chapter layout. This is
688                         // printed in an extra row and has a pagebreak at
689                         // the top.
690                         if (layout->labeltype == LABEL_COUNTER_CHAPTER) {
691                                 if (buffer->params.secnumdepth >= 0) {
692                                         float spacing_val = 1.0;
693                                         if (!parparams.spacing().isDefault()) {
694                                                 spacing_val = parparams.spacing().getValue();
695                                         } else {
696                                                 spacing_val = buffer->params.spacing.getValue();
697                                         }
698
699                                         int const maxdesc =
700                                                 int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val)
701                                                 + int(layout->parsep) * defaultRowHeight();
702
703                                         if (is_rtl) {
704                                                 x = ww - leftMargin() -
705                                                         font_metrics::width(str, font);
706                                         }
707
708                                         pain_.text(int(x),
709                                                 yo_ + row_->baseline() -
710                                                 row_->ascent_of_text() - maxdesc,
711                                                 str, font);
712                                 }
713                         } else {
714                                 if (is_rtl) {
715                                         x = ww - leftMargin()
716                                                 + font_metrics::width(layout->labelsep, font);
717                                 } else {
718                                         x = x_ - font_metrics::width(layout->labelsep, font)
719                                                 - font_metrics::width(str, font);
720                                 }
721
722                                 pain_.text(int(x), yo_ + row_->baseline(), str, font);
723                         }
724                 }
725
726         // the labels at the top of an environment.
727         // More or less for bibliography
728         } else if (isFirstInSequence(pit_, text_.ownerParagraphs()) &&
729                 (layout->labeltype == LABEL_TOP_ENVIRONMENT ||
730                 layout->labeltype == LABEL_BIBLIO ||
731                 layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) {
732                 LyXFont font = getLabelFont();
733                 if (!pit_->getLabelstring().empty()) {
734                         string const str = pit_->getLabelstring();
735                         float spacing_val = 1.0;
736                         if (!parparams.spacing().isDefault()) {
737                                 spacing_val = parparams.spacing().getValue();
738                         } else {
739                                 spacing_val = buffer->params.spacing.getValue();
740                         }
741
742                         int maxdesc =
743                                 int(font_metrics::maxDescent(font) * layout->spacing.getValue() * spacing_val
744                                 + (layout->labelbottomsep * defaultRowHeight()));
745
746                         float x = x_;
747                         if (layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) {
748                                 x = ((is_rtl ? leftMargin() : x_)
749                                          + ww - text_.rightMargin(*bv_.buffer(), *row_)) / 2;
750                                 x -= font_metrics::width(str, font) / 2;
751                         } else if (is_rtl) {
752                                 x = ww - leftMargin() -
753                                         font_metrics::width(str, font);
754                         }
755                         pain_.text(int(x), yo_ + row_->baseline()
756                                   - row_->ascent_of_text() - maxdesc,
757                                   str, font);
758                 }
759         }
760 }
761
762
763 void RowPainter::paintLast()
764 {
765         ParagraphParameters const & parparams = pit_->params();
766         int y_bottom = row_->height() - 1;
767
768         // the bottom margin
769         if (boost::next(row_) == text_.rows().end() && !text_.isInInset())
770                 y_bottom -= PAPER_MARGIN;
771
772         int const ww = bv_.workWidth();
773
774         // draw a bottom pagebreak
775         if (parparams.pagebreakBottom()) {
776                 y_bottom -= paintPageBreak(_("Page Break (bottom)"),
777                         yo_ + y_bottom - 2 * defaultRowHeight());
778         }
779
780         // draw the additional space if needed:
781         int const height = getLengthMarkerHeight(bv_, parparams.spaceBottom());
782         y_bottom -= paintLengthMarker(_("Space below"), parparams.spaceBottom(),
783                              yo_ + y_bottom - height);
784
785         // draw a bottom line
786         if (parparams.lineBottom()) {
787                 LyXFont font(LyXFont::ALL_SANE);
788                 int const asc = font_metrics::ascent('x',
789                         getFont(max(pos_type(0), pit_->size() - 1)));
790
791                 y_bottom -= asc;
792
793                 int const w = (text_.isInInset() ? text_.inset_owner->width(perv(bv_), font) : ww);
794                 int const xp = static_cast<int>(text_.isInInset() ? xo_ : 0);
795                 int const y = yo_ + y_bottom;
796                 pain_.line(xp, y, xp + w, y, LColor::topline, Painter::line_solid,
797                           Painter::line_thick);
798
799                 y_bottom -= asc;
800         }
801
802         bool const is_rtl = pit_->isRightToLeftPar(bv_.buffer()->params);
803         int const endlabel = getEndLabel(pit_, text_.ownerParagraphs());
804
805         // draw an endlabel
806         switch (endlabel) {
807         case END_LABEL_BOX:
808         case END_LABEL_FILLED_BOX:
809         {
810                 LyXFont const font = getLabelFont();
811                 int const size = int(0.75 * font_metrics::maxAscent(font));
812                 int const y = (yo_ + row_->baseline()) - size;
813                 int x = is_rtl ? LEFT_MARGIN : ww - PAPER_MARGIN - size;
814
815                 if (row_->fill() <= size)
816                         x += (size - row_->fill() + 1) * (is_rtl ? -1 : 1);
817
818                 if (endlabel == END_LABEL_BOX) {
819                         pain_.rectangle(x, y, size, size, LColor::eolmarker);
820                 } else {
821                         pain_.fillRectangle(x, y, size, size, LColor::eolmarker);
822                 }
823                 break;
824         }
825         case END_LABEL_STATIC:
826         {
827 #if 0
828                 LyXFont font(LyXFont::ALL_SANE);
829                 font = getLabelFont();
830 #else
831                 LyXFont font = getLabelFont();
832 #endif
833                 string const & str = pit_->layout()->endlabelstring();
834                 int const x = is_rtl ?
835                         int(x_) - font_metrics::width(str, font)
836                         : ww - text_.rightMargin(*bv_.buffer(), *row_) - row_->fill();
837                 pain_.text(x, yo_ + row_->baseline(), str, font);
838                 break;
839         }
840         case END_LABEL_NO_LABEL:
841                 break;
842         }
843 }
844
845
846 void RowPainter::paintText()
847 {
848         pos_type const last = lastPrintablePos(text_, row_);
849         pos_type body_pos = pit_->beginningOfBody();
850         if (body_pos > 0 &&
851                 (body_pos - 1 > last ||
852                 !pit_->isLineSeparator(body_pos - 1))) {
853                 body_pos = 0;
854         }
855
856         LyXLayout_ptr const & layout = pit_->layout();
857
858         bool running_strikeout = false;
859         bool is_struckout = false;
860         float last_strikeout_x = 0.0;
861
862         pos_type vpos = row_->pos();
863         while (vpos <= last) {
864                 if (x_ > bv_.workWidth())
865                         break;
866                 pos_type pos = text_.vis2log(vpos);
867
868                 if (pos >= pit_->size()) {
869                         ++vpos;
870                         continue;
871                 }
872
873                 if (x_ + singleWidth(pos) < 0) {
874                         x_ += singleWidth(pos);
875                         ++vpos;
876                         continue;
877                 }
878
879                 is_struckout = isDeletedText(*pit_, pos);
880
881                 if (is_struckout && !running_strikeout) {
882                         running_strikeout = true;
883                         last_strikeout_x = x_;
884                 }
885
886                 bool const highly_editable_inset = pit_->isInset(pos)
887                         && isHighlyEditableInset(pit_->getInset(pos));
888
889                 // if we reach the end of a struck out range, paint it
890                 // we also don't paint across things like tables
891                 if (running_strikeout && (highly_editable_inset || !is_struckout)) {
892                         int const middle = yo_ + row_->top_of_text()
893                                 + ((row_->baseline() - row_->top_of_text()) / 2);
894                         pain_.line(int(last_strikeout_x), middle, int(x_), middle,
895                                 LColor::strikeout, Painter::line_solid, Painter::line_thin);
896                         running_strikeout = false;
897                 }
898
899                 if (body_pos > 0 && pos == body_pos - 1) {
900                         int const lwidth = font_metrics::width(layout->labelsep,
901                                 getLabelFont());
902
903                         x_ += label_hfill_ + lwidth
904                                 - singleWidth(body_pos - 1);
905                 }
906
907                 if (pit_->isHfill(pos)) {
908                         x_ += 1;
909
910                         int const y0 = yo_ + row_->baseline();
911                         int const y1 = y0 - defaultRowHeight() / 2;
912
913                         pain_.line(int(x_), y1, int(x_), y0,
914                                      LColor::added_space);
915
916                         if (hfillExpansion(text_, row_, pos)) {
917                                 int const y2 = (y0 + y1) / 2;
918
919                                 if (pos >= body_pos) {
920                                         pain_.line(int(x_), y2,
921                                                   int(x_ + hfill_), y2,
922                                                   LColor::added_space,
923                                                   Painter::line_onoffdash);
924                                         x_ += hfill_;
925                                 } else {
926                                         pain_.line(int(x_), y2,
927                                                   int(x_ + label_hfill_), y2,
928                                                   LColor::added_space,
929                                                   Painter::line_onoffdash);
930                                         x_ += label_hfill_;
931                                 }
932                                 pain_.line(int(x_), y1,
933                                              int(x_), y0,
934                                              LColor::added_space);
935                         }
936                         x_ += 2;
937                         ++vpos;
938                 } else if (pit_->isSeparator(pos)) {
939                         x_ += singleWidth(pos);
940                         if (pos >= body_pos)
941                                 x_ += separator_;
942                         ++vpos;
943                 } else {
944                         paintFromPos(vpos);
945                 }
946         }
947
948         // if we reach the end of a struck out range, paint it
949         if (running_strikeout) {
950                 int const middle = yo_ + row_->top_of_text()
951                         + ((row_->baseline() - row_->top_of_text()) / 2);
952                 pain_.line(int(last_strikeout_x), middle, int(x_), middle,
953                         LColor::strikeout, Painter::line_solid, Painter::line_thin);
954                 running_strikeout = false;
955         }
956         return;
957 }
958
959
960 void RowPainter::paint(int y_offset, int x_offset, int y)
961 {
962         xo_ = x_offset;
963         yo_ = y_offset;
964         y_ = y;
965         width_ = text_.isInInset()
966                 ? text_.inset_owner->textWidth(perv(bv_), true) : bv_.workWidth();
967
968         // FIXME: must be a cleaner way here. Aren't these calculations
969         // belonging to row metrics ?
970         text_.prepareToPrint(row_, x_, separator_, hfill_, label_hfill_);
971
972         // FIXME: what is this fixing ?
973         if (text_.isInInset() && (x_ < 0))
974                 x_ = 0;
975         x_ += xo_;
976
977         // If we're *not* at the top-level of rows, then the
978         // background has already been cleared.
979         if (&text_ == bv_.text)
980                 paintBackground();
981
982         // paint the selection background
983         if (text_.selection.set()) {
984                 paintSelection();
985         }
986
987         // vertical lines for appendix
988         paintAppendix();
989
990         // environment depth brackets
991         paintDepthBar();
992
993         // changebar
994         paintChangeBar();
995
996         if (row_->isParStart()) {
997                 paintFirst();
998         }
999
1000         if (isParEnd(text_, row_)) {
1001                 paintLast();
1002         }
1003
1004         // paint text
1005         paintText();
1006 }