]> git.lyx.org Git - lyx.git/blob - src/RowPainter.cpp
Accelerators
[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                 /* Currently, x points at row_.left_margin (which contains the
462                  * indent). First remove that, and then center the title with
463                  * respect to the left and right margins.
464                  */
465                 int const leftm = row_.isRTL() ? tm_.rightMargin(row_.pit())
466                                                : tm_.leftMargin(row_.pit());
467                 int const rightm = row_.isRTL() ? tm_.leftMargin(row_.pit())
468                                                     : tm_.rightMargin(row_.pit());
469                 x += leftm - row_.left_margin + (tm_.width() - leftm -rightm) / 2
470                         - fm.width(str) / 2;
471         } else if (row_.isRTL()) {
472                 x = xo_ + tm_.width() - row_.right_margin - fm.width(str);
473         }
474         pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font);
475 }
476
477
478 void RowPainter::paintLast() const
479 {
480         int const endlabel = text_.getEndLabel(row_.pit());
481         switch (endlabel) {
482         case END_LABEL_BOX:
483         case END_LABEL_FILLED_BOX: {
484                 FontInfo font = labelFont(true);
485                 if (font.realColor() != Color_selectiontext)
486                         font.setPaintColor(Color_eolmarker);
487                 FontMetrics const & fm = theFontMetrics(font);
488                 int const size = int(0.75 * fm.maxAscent());
489                 int const y = yo_ - size;
490
491                 // If needed, move the box a bit to avoid overlapping with text.
492                 int x = 0;
493                 if (row_.isRTL()) {
494                         int const normal_x = nestMargin() + changebarMargin();
495                         x = min(normal_x, row_.left_margin - size - Inset::textOffset(pi_.base.bv));
496                 } else {
497                         int const normal_x = tm_.width() - row_.right_margin
498                                 - size - Inset::textOffset(pi_.base.bv);
499                         x = max(normal_x, row_.width());
500                 }
501
502                 if (endlabel == END_LABEL_BOX)
503                         pi_.pain.rectangle(int(xo_) + x, y, size, size, font.realColor());
504                 else
505                         pi_.pain.fillRectangle(int(xo_) + x, y, size, size, font.realColor());
506                 break;
507         }
508
509         case END_LABEL_STATIC: {
510                 FontInfo const font = labelFont(true);
511                 FontMetrics const & fm = theFontMetrics(font);
512                 docstring const & str = par_.layout().endlabelstring();
513                 double const x = row_.isRTL() ? x_ - fm.width(str) : x_;
514                 pi_.pain.text(int(x), yo_, str, font);
515                 break;
516         }
517
518         case END_LABEL_NO_LABEL:
519                 break;
520         }
521 }
522
523
524 void RowPainter::paintOnlyInsets()
525 {
526         for (Row::Element const & e : row_) {
527                 if (e.type == Row::INSET) {
528                         paintInset(e);
529                         // The markings of foreign languages
530                         // and of text ignored for spellchecking
531                         paintLanguageMarkings(e);
532                         // change tracking (not for insets that handle it themselves)
533                         if (!e.inset->canPaintChange(*pi_.base.bv))
534                                 paintChange(e);
535                 }
536
537                 x_ += e.full_width();
538         }
539 }
540
541
542 void RowPainter::paintText()
543 {
544         for (Row::Element const & e : row_) {
545                 switch (e.type) {
546                 case Row::STRING:
547                 case Row::VIRTUAL:
548                         paintStringAndSel(e);
549
550                         // Paint the spelling marks if enabled.
551                         if (lyxrc.spellcheck_continuously && pi_.do_spellcheck && !pi_.pain.isNull())
552                                 paintMisspelledMark(e);
553                         break;
554
555                 case Row::INSET:
556                         paintInset(e);
557                         break;
558
559                 case Row::SPACE:
560                         paintTextDecoration(e);
561                 }
562
563                 // The markings of foreign languages
564                 // and of text ignored for spellchecking
565                 paintLanguageMarkings(e);
566
567                 // change tracking (not for insets that handle it themselves)
568                 if (e.type != Row::INSET || ! e.inset->canPaintChange(*pi_.base.bv))
569                         paintChange(e);
570
571                 x_ += e.full_width();
572         }
573 }
574
575
576 void RowPainter::paintSelection() const
577 {
578         if (!row_.selection())
579                 return;
580
581         int const y1 = yo_ - row_.ascent();
582         int const y2 = y1 + row_.height();
583
584         // draw the margins
585         if (row_.isRTL() ? row_.end_margin_sel : row_.begin_margin_sel)
586                 pi_.pain.fillRectangle(int(xo_), y1, row_.left_margin, y2 - y1,
587                                        Color_selection);
588
589         // go through row and draw from RTL boundary to RTL boundary
590         double x = xo_ + row_.left_margin;
591         for (auto const & e : row_) {
592                 // These are the same tests as in paintStringAndSel, except
593                 // that all_sel has an additional clause that triggers for end
594                 // of paragraph markers. The clause was not used in
595                 // paintStringAndSel to avoid changing the drawing color.
596                 // at least part of text selected?
597                 bool const some_sel = (e.endpos >= row_.sel_beg && e.pos < row_.sel_end)
598                         || pi_.selected;
599                 // all the text selected?
600                 bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
601                     || (e.isVirtual() && e.pos == row_.endpos() && row_.end_margin_sel)
602                     || pi_.selected;
603
604                 if (all_sel) {
605                         // the 3rd argument is written like that to avoid rounding issues
606                         pi_.pain.fillRectangle(int(x), y1,
607                                                int(x + e.full_width()) - int(x), y2 - y1,
608                                                Color_selection);
609                 } else if (some_sel) {
610                         pos_type const from = min(max(row_.sel_beg, e.pos), e.endpos);
611                         pos_type const to = max(min(row_.sel_end, e.endpos), e.pos);
612                         double x1 = e.pos2x(from);
613                         double x2 = e.pos2x(to);
614                         if (x1 > x2)
615                                 swap(x1, x2);
616                         // the 3rd argument is written like that to avoid rounding issues
617                         pi_.pain.fillRectangle(int(x + x1), y1, int(x2 + x) - int(x1 + x),
618                                                y2 - y1, Color_selection);
619                 }
620                 x += e.full_width();
621         }
622
623         if (row_.isRTL() ? row_.begin_margin_sel : row_.end_margin_sel)
624                 pi_.pain.fillRectangle(int(x), y1,
625                                        int(xo_ + tm_.width()) - int(x), y2 - y1,
626                                        Color_selection);
627
628 }
629
630
631 } // namespace lyx