]> git.lyx.org Git - lyx.git/blob - src/RowPainter.cpp
More requires --> required, for C++2a.
[lyx.git] / src / RowPainter.cpp
1 /**
2  * \file RowPainter.cpp
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 "RowPainter.h"
17
18 #include "Buffer.h"
19 #include "CoordCache.h"
20 #include "Cursor.h"
21 #include "BufferParams.h"
22 #include "BufferView.h"
23 #include "Changes.h"
24 #include "Language.h"
25 #include "Layout.h"
26 #include "LyXRC.h"
27 #include "Row.h"
28 #include "MetricsInfo.h"
29 #include "Paragraph.h"
30 #include "ParagraphList.h"
31 #include "ParagraphParameters.h"
32 #include "Text.h"
33 #include "TextMetrics.h"
34
35 #include "frontends/FontMetrics.h"
36 #include "frontends/Painter.h"
37
38 #include "support/debug.h"
39 #include "support/gettext.h"
40 #include "support/lassert.h"
41
42
43 using namespace std;
44
45 namespace lyx {
46
47 using frontend::Painter;
48 using frontend::FontMetrics;
49
50
51 RowPainter::RowPainter(PainterInfo & pi,
52         Text const & text, Row const & row, int x, int y)
53         : pi_(pi), text_(text),
54           tm_(pi_.base.bv->textMetrics(&text)),
55           row_(row), par_(text.paragraphs()[row.pit()]),
56           xo_(x), yo_(y)
57 {
58         x_ = row_.left_margin + xo_;
59
60         //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl;
61         //row_.dump();
62
63         LBUFERR(row.pit() >= 0);
64         LBUFERR(row.pit() < int(text.paragraphs().size()));
65 }
66
67
68 FontInfo RowPainter::labelFont(bool end) const
69 {
70         FontInfo f = text_.labelFont(par_);
71         // selected text?
72         if ((end ? row_.end_margin_sel : row_.begin_margin_sel)
73             || pi_.selected)
74                 f.setPaintColor(Color_selectiontext);
75         return f;
76 }
77
78
79 // If you want to debug inset metrics uncomment the following line:
80 //#define DEBUG_METRICS
81 // This draws green lines around each inset.
82
83
84 void RowPainter::paintInset(Row::Element const & e) const
85 {
86         // Handle selection
87         bool const pi_selected = pi_.selected;
88         Cursor const & cur = pi_.base.bv->cursor();
89         if (cur.selection() && cur.text() == &text_
90                 && cur.normalAnchor().text() == &text_)
91                 pi_.selected = row_.sel_beg <= e.pos && row_.sel_end > e.pos;
92
93         LASSERT(e.inset, return);
94         // Backup full_repaint status because some insets (InsetTabular)
95         // requires a full repaint
96         bool const pi_full_repaint = pi_.full_repaint;
97         bool const pi_do_spellcheck = pi_.do_spellcheck;
98         Change const pi_change = pi_.change;
99
100         pi_.base.font = e.inset->inheritFont() ? e.font.fontInfo() :
101                 pi_.base.bv->buffer().params().getFont().fontInfo();
102         pi_.ltr_pos = !e.font.isVisibleRightToLeft();
103         pi_.change = pi_.change.changed() ? pi_.change : e.change;
104         pi_.do_spellcheck &= e.inset->allowSpellCheck();
105
106         int const x1 = int(x_);
107         pi_.base.bv->coordCache().insets().add(e.inset, x1, yo_);
108         // insets are painted completely. Recursive
109         // FIXME: it is wrong to completely paint the background
110         // if we want to do single row painting.
111         e.inset->drawBackground(pi_, x1, yo_);
112         e.inset->drawSelection(pi_, x1, yo_);
113         e.inset->draw(pi_, x1, yo_);
114         paintTextDecoration(e);
115
116         // Restore full_repaint status.
117         pi_.full_repaint = pi_full_repaint;
118         pi_.change = pi_change;
119         pi_.do_spellcheck = pi_do_spellcheck;
120         pi_.selected = pi_selected;
121
122 #ifdef DEBUG_METRICS
123         Dimension const & dim = pi_.base.bv->coordCache().insets().dim(e.inset);
124         int const x2 = x1 + dim.wid;
125         int const y1 = yo_ + dim.des;
126         int const y2 = yo_ - dim.asc;
127         pi_.pain.line(x1, y1, x1, y2, Color_green);
128         pi_.pain.line(x1, y1, x2, y1, Color_green);
129         pi_.pain.line(x2, y1, x2, y2, Color_green);
130         pi_.pain.line(x1, y2, x2, y2, Color_green);
131 #endif
132 }
133
134
135 void RowPainter::paintLanguageMarkings(Row::Element const & e) const
136 {
137         paintForeignMark(e);
138         paintNoSpellingMark(e);
139 }
140
141
142 void RowPainter::paintForeignMark(Row::Element const & e) const
143 {
144         Language const * lang = e.font.language();
145         if (!lyxrc.mark_foreign_language)
146                 return;
147         if (lang == latex_language)
148                 return;
149         if (lang == pi_.base.bv->buffer().params().language)
150                 return;
151
152         int const desc = e.inset ? e.dim.descent() : 0;
153         int const y = yo_ + min(3 * pi_.base.solidLineOffset() / 2 + desc,
154                                 row_.descent() - 1);
155         pi_.pain.line(int(x_), y, int(x_ + e.full_width() - 1), y, Color_language,
156                       Painter::line_solid, pi_.base.solidLineThickness());
157 }
158
159
160 void RowPainter::paintNoSpellingMark(Row::Element const & e) const
161 {
162         //if (!lyxrc.mark_no_spelling)
163         //      return;
164         if (e.font.language() == latex_language)
165                 return;
166         if (e.font.fontInfo().nospellcheck() != FONT_ON)
167                 return;
168
169         // We at the same voffset than the misspelled mark, since
170         // these two are mutually exclusive
171         int const desc = e.inset ? e.dim.descent() : 0;
172         int y = yo_ + pi_.base.solidLineOffset() + desc
173                 + pi_.base.solidLineThickness()
174                 + (e.change.changed() ? pi_.base.solidLineThickness() + 1 : 0)
175                 + 1;
176         // Make sure that the mark does not go below the row rectangle
177         y = min(y, yo_ + row_.descent() - 1);
178
179         pi_.pain.line(int(x_), y, int(x_ + e.full_width()), y, Color_language,
180                       Painter::line_onoffdash, pi_.base.solidLineThickness());
181 }
182
183
184 void RowPainter::paintMisspelledMark(Row::Element const & e) const
185 {
186         if (e.font.fontInfo().nospellcheck() == FONT_ON)
187                 return;
188         // if changed the misspelled marker gets placed slightly lower than normal
189         // to avoid drawing at the same vertical offset
190         FontMetrics const & fm = theFontMetrics(e.font);
191         int const thickness = max(fm.lineWidth(), 2);
192         int y = yo_ + pi_.base.solidLineOffset() + pi_.base.solidLineThickness()
193                 + (e.change.changed() ? pi_.base.solidLineThickness() + 1 : 0)
194                 + 1 + thickness / 2;
195         // Make sure that the mark does not go below the row rectangle
196         y = min(y, yo_ + row_.descent() - 1);
197
198         //FIXME: this could be computed only once, it is probably not costly.
199         // check for cursor position
200         // don't draw misspelled marker for words at cursor position
201         // we don't want to disturb the process of text editing
202         DocIterator const nw = pi_.base.bv->cursor().newWord();
203         pos_type cpos = -1;
204         if (!nw.empty() && par_.id() == nw.paragraph().id()) {
205                 cpos = nw.pos();
206                 if (cpos > 0 && cpos == par_.size() && !par_.isWordSeparator(cpos-1))
207                         --cpos;
208                 else if (cpos > 0 && par_.isWordSeparator(cpos))
209                         --cpos;
210         }
211
212         pos_type pos = e.pos;
213         while (pos < e.pos + pos_type(e.str.length())) {
214                 if (!par_.isMisspelled(pos)) {
215                         ++pos;
216                         continue;
217                 }
218
219                 FontSpan const & range = par_.getSpellRange(pos);
220
221                 // Skip element which are being edited
222                 if (range.contains(cpos)) {
223                         // the range includes the last element
224                         pos = range.last + 1;
225                         continue;
226                 }
227
228                 int x1 = fm.pos2x(e.str, range.first - e.pos,
229                                   e.isRTL(), e.extra);
230                 int x2 = fm.pos2x(e.str, min(range.last - e.pos + 1,
231                                                                          pos_type(e.str.length())),
232                                                                          e.isRTL(), e.extra);
233                 if (x1 > x2)
234                         swap(x1, x2);
235
236                 pi_.pain.line(int(x_ + x1), y, int(x_ + x2), y,
237                               Color_error,
238                               Painter::line_onoffdash, thickness);
239                 pos = range.last + 1;
240         }
241 }
242
243
244 void RowPainter::paintStringAndSel(Row::Element const & e) const
245 {
246         // at least part of text selected?
247         bool const some_sel = (e.endpos >= row_.sel_beg && e.pos < row_.sel_end)
248                 || pi_.selected;
249         // all the text selected?
250         bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
251                 || pi_.selected;
252
253         if (all_sel || e.change.changed()) {
254                 Font copy = e.font;
255                 Color const col = e.change.changed() ? e.change.color()
256                                                      : Color_selectiontext;
257                 copy.fontInfo().setPaintColor(col);
258                 pi_.pain.text(int(x_), yo_, e.str, copy, e.extra, e.full_width());
259         } else if (!some_sel) {
260                 pi_.pain.text(int(x_), yo_, e.str, e.font, e.extra, e.full_width());
261         } else {
262                 pi_.pain.text(int(x_), yo_, e.str, e.font, Color_selectiontext,
263                               max(row_.sel_beg, e.pos) - e.pos,
264                               min(row_.sel_end, e.endpos) - e.pos,
265                               e.extra, e.full_width());
266         }
267 }
268
269
270 void RowPainter::paintTextDecoration(Row::Element const & e) const
271 {
272         // element selected?
273         bool const sel = (e.pos >= row_.sel_beg && e.endpos <= row_.sel_end)
274                 || pi_.selected;
275         FontInfo copy = e.font.fontInfo();
276         if (sel || e.change.changed()) {
277                 Color const col = e.change.changed() ? e.change.color()
278                                                      : Color_selectiontext;
279                 copy.setPaintColor(col);
280         }
281         pi_.pain.textDecoration(copy, int(x_), yo_, int(e.full_width()));
282 }
283
284
285 void RowPainter::paintChange(Row::Element const & e) const
286 {
287         e.change.paintCue(pi_, x_, yo_, x_ + e.full_width(), e.font.fontInfo());
288 }
289
290
291 void RowPainter::paintChangeBar() const
292 {
293         pi_.pain.fillRectangle(5, yo_ - row_.ascent(), 3, row_.height(), Color_changebar);
294 }
295
296
297 void RowPainter::paintAppendix() const
298 {
299         // only draw the appendix frame once (for the main text)
300         if (!par_.params().appendix() || !text_.isMainText())
301                 return;
302
303         int y = yo_ - row_.ascent();
304
305         if (par_.params().startOfAppendix())
306                 y += 2 * defaultRowHeight();
307
308         pi_.pain.line(1, y, 1, yo_ + row_.height(), Color_appendix);
309         pi_.pain.line(tm_.width() - 2, y, tm_.width() - 2, yo_ + row_.height(), Color_appendix);
310 }
311
312
313 void RowPainter::paintDepthBar() const
314 {
315         depth_type const depth = par_.getDepth();
316         ParagraphList const & pars = text_.paragraphs();
317
318         if (depth <= 0)
319                 return;
320
321         depth_type prev_depth = 0;
322         if (!tm_.isFirstRow(row_)) {
323                 pit_type pit2 = row_.pit();
324                 if (row_.pos() == 0)
325                         --pit2;
326                 prev_depth = pars[pit2].getDepth();
327         }
328
329         depth_type next_depth = 0;
330         if (!tm_.isLastRow(row_)) {
331                 pit_type pit2 = row_.pit();
332                 if (row_.endpos() >= pars[pit2].size())
333                         ++pit2;
334                 next_depth = pars[pit2].getDepth();
335         }
336
337         for (depth_type i = 1; i <= depth; ++i) {
338                 int const w = nestMargin() / 5;
339                 int x = int(xo_) + w * i;
340                 // consider the bufferview left margin if we're drawing outermost text
341                 if (text_.isMainText())
342                         x += pi_.base.bv->leftMargin();
343
344                 int const starty = yo_ - row_.ascent();
345                 int const h =  row_.height() - 1 - (i - next_depth - 1) * 3;
346
347                 pi_.pain.line(x, starty, x, starty + h, Color_depthbar);
348
349                 if (i > prev_depth)
350                         pi_.pain.fillRectangle(x, starty, w, 2, Color_depthbar);
351                 if (i > next_depth)
352                         pi_.pain.fillRectangle(x, starty + h, w, 2, Color_depthbar);
353         }
354 }
355
356
357 void RowPainter::paintAppendixStart(int y) const
358 {
359         FontInfo pb_font = sane_font;
360         pb_font.setColor(Color_appendix);
361         pb_font.decSize();
362
363         int w = 0;
364         int a = 0;
365         int d = 0;
366
367         docstring const label = _("Appendix");
368         theFontMetrics(pb_font).rectText(label, w, a, d);
369
370         int const text_start = int(xo_ + (tm_.width() - w) / 2);
371         int const text_end = text_start + w;
372
373         pi_.pain.rectText(text_start, y + d, label, pb_font, Color_none, Color_none);
374
375         pi_.pain.line(int(xo_ + 1), y, text_start, y, Color_appendix);
376         pi_.pain.line(text_end, y, int(xo_ + tm_.width() - 2), y, Color_appendix);
377 }
378
379
380 void RowPainter::paintTooLargeMarks(bool const left, bool const right) const
381 {
382         if (left)
383                 pi_.pain.line(pi_.base.dottedLineThickness(), yo_ - row_.ascent(),
384                                           pi_.base.dottedLineThickness(), yo_ + row_.descent(),
385                                           Color_scroll, Painter::line_onoffdash,
386                               pi_.base.dottedLineThickness());
387         if (right) {
388                 int const wwidth =
389                         pi_.base.bv->workWidth() - pi_.base.dottedLineThickness();
390                 pi_.pain.line(wwidth, yo_ - row_.ascent(),
391                                           wwidth, yo_ + row_.descent(),
392                                           Color_scroll, Painter::line_onoffdash,
393                               pi_.base.dottedLineThickness());
394         }
395 }
396
397
398 void RowPainter::paintFirst() const
399 {
400         Layout const & layout = par_.layout();
401
402         // start of appendix?
403         if (par_.params().startOfAppendix())
404             paintAppendixStart(yo_ - row_.ascent() + 2 * defaultRowHeight());
405
406         bool const is_first =
407                 text_.isFirstInSequence(row_.pit()) || !layout.isParagraphGroup();
408         //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << endl;
409
410         if (layout.labelIsInline()
411             && (layout.labeltype != LABEL_STATIC || is_first))
412                 paintLabel();
413         else if (is_first && layout.labelIsAbove())
414                 paintTopLevelLabel();
415 }
416
417
418 void RowPainter::paintLabel() const
419 {
420         docstring const & str = par_.labelString();
421         if (str.empty())
422                 return;
423
424         Layout const & layout = par_.layout();
425         FontInfo const font = labelFont(false);
426         FontMetrics const & fm = theFontMetrics(font);
427         int const x = row_.isRTL() ? row_.width() + fm.width(layout.labelsep)
428                                    : row_.left_margin - fm.width(layout.labelsep) - fm.width(str);
429
430         pi_.pain.text(int(xo_) + x, yo_, str, font);
431 }
432
433
434 void RowPainter::paintTopLevelLabel() const
435 {
436         BufferParams const & bparams = pi_.base.bv->buffer().params();
437         ParagraphParameters const & pparams = par_.params();
438         Layout const & layout = par_.layout();
439         FontInfo const font = labelFont(false);
440         docstring const str = par_.labelString();
441         if (str.empty())
442                 return;
443
444         double spacing_val = 1.0;
445         if (!pparams.spacing().isDefault())
446                 spacing_val = pparams.spacing().getValue();
447         else
448                 spacing_val = bparams.spacing().getValue();
449
450         FontMetrics const & fm = theFontMetrics(font);
451
452         int const labeladdon = int(fm.maxHeight()
453                 * layout.spacing.getValue() * spacing_val);
454
455         int maxdesc =
456                 int(fm.maxDescent() * layout.spacing.getValue() * spacing_val
457                 + (layout.labelbottomsep * defaultRowHeight()));
458
459         double x = x_;
460         if (layout.labeltype == LABEL_CENTERED) {
461                 x += (tm_.width() - row_.left_margin - row_.right_margin) / 2;
462                 x -= fm.width(str) / 2;
463         } else if (row_.isRTL()) {
464                 x = xo_ + tm_.width() - row_.right_margin - fm.width(str);
465         }
466         pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font);
467 }
468
469
470 void RowPainter::paintLast() const
471 {
472         int const endlabel = text_.getEndLabel(row_.pit());
473         switch (endlabel) {
474         case END_LABEL_BOX:
475         case END_LABEL_FILLED_BOX: {
476                 FontInfo font = labelFont(true);
477                 if (font.realColor() != Color_selectiontext)
478                         font.setPaintColor(Color_eolmarker);
479                 FontMetrics const & fm = theFontMetrics(font);
480                 int const size = int(0.75 * fm.maxAscent());
481                 int const y = yo_ - size;
482
483                 // If needed, move the box a bit to avoid overlapping with text.
484                 int x = 0;
485                 if (row_.isRTL()) {
486                         int const normal_x = nestMargin() + changebarMargin();
487                         x = min(normal_x, row_.left_margin - size - Inset::textOffset(pi_.base.bv));
488                 } else {
489                         int const normal_x = tm_.width() - row_.right_margin
490                                 - size - Inset::textOffset(pi_.base.bv);
491                         x = max(normal_x, row_.width());
492                 }
493
494                 if (endlabel == END_LABEL_BOX)
495                         pi_.pain.rectangle(int(xo_) + x, y, size, size, font.realColor());
496                 else
497                         pi_.pain.fillRectangle(int(xo_) + x, y, size, size, font.realColor());
498                 break;
499         }
500
501         case END_LABEL_STATIC: {
502                 FontInfo const font = labelFont(true);
503                 FontMetrics const & fm = theFontMetrics(font);
504                 docstring const & str = par_.layout().endlabelstring();
505                 double const x = row_.isRTL() ? x_ - fm.width(str) : x_;
506                 pi_.pain.text(int(x), yo_, str, font);
507                 break;
508         }
509
510         case END_LABEL_NO_LABEL:
511                 break;
512         }
513 }
514
515
516 void RowPainter::paintOnlyInsets()
517 {
518         for (Row::Element const & e : row_) {
519                 if (e.type == Row::INSET) {
520                         paintInset(e);
521                         // The markings of foreign languages
522                         // and of text ignored for spellchecking
523                         paintLanguageMarkings(e);
524                         // change tracking (not for insets that handle it themselves)
525                         if (!e.inset->canPaintChange(*pi_.base.bv))
526                                 paintChange(e);
527                 }
528
529                 x_ += e.full_width();
530         }
531 }
532
533
534 void RowPainter::paintText()
535 {
536         for (Row::Element const & e : row_) {
537                 switch (e.type) {
538                 case Row::STRING:
539                 case Row::VIRTUAL:
540                         paintStringAndSel(e);
541
542                         // Paint the spelling marks if enabled.
543                         if (lyxrc.spellcheck_continuously && pi_.do_spellcheck && !pi_.pain.isNull())
544                                 paintMisspelledMark(e);
545                         break;
546
547                 case Row::INSET:
548                         paintInset(e);
549                         break;
550
551                 case Row::SPACE:
552                         paintTextDecoration(e);
553                 }
554
555                 // The markings of foreign languages
556                 // and of text ignored for spellchecking
557                 paintLanguageMarkings(e);
558
559                 // change tracking (not for insets that handle it themselves)
560                 if (e.type != Row::INSET || ! e.inset->canPaintChange(*pi_.base.bv))
561                         paintChange(e);
562
563                 x_ += e.full_width();
564         }
565 }
566
567
568 void RowPainter::paintSelection() const
569 {
570         if (!row_.selection())
571                 return;
572
573         int const y1 = yo_ - row_.ascent();
574         int const y2 = y1 + row_.height();
575
576         // draw the margins
577         if (row_.isRTL() ? row_.end_margin_sel : row_.begin_margin_sel)
578                 pi_.pain.fillRectangle(int(xo_), y1, row_.left_margin, y2 - y1,
579                                        Color_selection);
580
581         // go through row and draw from RTL boundary to RTL boundary
582         double x = xo_ + row_.left_margin;
583         for (auto const & e : row_) {
584                 // These are the same tests as in paintStringAndSel, except
585                 // that all_sel has an additional clause that triggers for end
586                 // of paragraph markers. The clause was not used in
587                 // paintStringAndSel to avoid changing the drawing color.
588                 // at least part of text selected?
589                 bool const some_sel = (e.endpos >= row_.sel_beg && e.pos < row_.sel_end)
590                         || pi_.selected;
591                 // all the text selected?
592                 bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
593                     || (e.isVirtual() && e.pos == row_.endpos() && row_.end_margin_sel)
594                     || pi_.selected;
595
596                 if (all_sel) {
597                         // the 3rd argument is written like that to avoid rounding issues
598                         pi_.pain.fillRectangle(int(x), y1,
599                                                int(x + e.full_width()) - int(x), y2 - y1,
600                                                Color_selection);
601                 } else if (some_sel) {
602                         pos_type const from = min(max(row_.sel_beg, e.pos), e.endpos);
603                         pos_type const to = max(min(row_.sel_end, e.endpos), e.pos);
604                         double x1 = e.pos2x(from);
605                         double x2 = e.pos2x(to);
606                         if (x1 > x2)
607                                 swap(x1, x2);
608                         // the 3rd argument is written like that to avoid rounding issues
609                         pi_.pain.fillRectangle(int(x + x1), y1, int(x2 + x) - int(x1 + x),
610                                                y2 - y1, Color_selection);
611                 }
612                 x += e.full_width();
613         }
614
615         if (row_.isRTL() ? row_.begin_margin_sel : row_.end_margin_sel)
616                 pi_.pain.fillRectangle(int(x), y1,
617                                        int(xo_ + tm_.width()) - int(x), y2 - y1,
618                                        Color_selection);
619
620 }
621
622
623 } // namespace lyx