]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiPainter.cpp
Do not pass a vector by value to getArgInset
[lyx.git] / src / frontends / qt / GuiPainter.cpp
1 /**
2  * \file GuiPainter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiPainter.h"
15
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
20 #include "GuiImage.h"
21 #include "qt_helpers.h"
22
23 #include "Font.h"
24 #include "LyXRC.h"
25
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/lyxlib.h"
29 #include "support/lstrings.h"
30
31 #include <algorithm>
32
33 #include <QPainterPath>
34 #include <QTextLayout>
35
36 using namespace std;
37 using namespace lyx::support;
38
39 namespace lyx {
40 namespace frontend {
41
42 const int Painter::thin_line = 1;
43
44 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio, bool devel_mode)
45         : QPainter(device), Painter(pixel_ratio, devel_mode)
46 {
47         // set cache correctly
48         current_color_ = pen().color();
49         current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
50         current_lw_ = pen().width();
51 }
52
53
54 GuiPainter::~GuiPainter()
55 {
56         QPainter::end();
57         //lyxerr << "GuiPainter::end()" << endl;
58 }
59
60
61 void GuiPainter::setQPainterPen(QColor const & col,
62         Painter::line_style ls, int lw, Qt::PenJoinStyle js)
63 {
64         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
65                 return;
66
67         current_color_ = col;
68         current_ls_ = ls;
69         current_lw_ = lw;
70
71         QPen pen = QPainter::pen();
72         pen.setColor(col);
73
74         switch (ls) {
75         case line_solid:
76         case line_solid_aliased:
77                 pen.setStyle(Qt::SolidLine); break;
78         case line_onoffdash:
79                 pen.setStyle(Qt::DotLine); break;
80         }
81
82         pen.setWidth(lw);
83
84         pen.setJoinStyle(js);
85
86         setPen(pen);
87 }
88
89
90 QColor GuiPainter::computeColor(Color col)
91 {
92         return filterColor(guiApp->colorCache().get(col));
93 }
94
95
96 QColor GuiPainter::filterColor(QColor const & col)
97 {
98         if (monochrome_blend_.empty())
99                 return col;
100
101         QColor const blend = monochrome_blend_.top();
102         return QColor::fromHsv(blend.hue(), blend.saturation(), qGray(col.rgb()));
103 }
104
105
106 void GuiPainter::enterMonochromeMode(Color const & blend)
107 {
108         QColor qblend = filterColor(guiApp->colorCache().get(blend));
109         monochrome_blend_.push(qblend);
110 }
111
112
113 void GuiPainter::leaveMonochromeMode()
114 {
115         LASSERT(!monochrome_blend_.empty(), return);
116         monochrome_blend_.pop();
117 }
118
119
120 void GuiPainter::point(int x, int y, Color col)
121 {
122         setQPainterPen(computeColor(col));
123         drawPoint(x, y);
124 }
125
126
127 void GuiPainter::line(int x1, int y1, int x2, int y2,
128         Color col,
129         line_style ls,
130         int lw)
131 {
132         setQPainterPen(computeColor(col), ls, lw);
133         bool const do_antialiasing = renderHints() & TextAntialiasing
134                 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
135         setRenderHint(Antialiasing, do_antialiasing);
136         drawLine(x1, y1, x2, y2);
137         setRenderHint(Antialiasing, false);
138 }
139
140
141 void GuiPainter::lines(int const * xp, int const * yp, int np,
142         Color col,
143         fill_style fs,
144         line_style ls,
145         int lw)
146 {
147         // double the size if needed
148         // FIXME THREAD
149         static QVector<QPoint> points(32);
150         if (np > points.size())
151                 points.resize(2 * np);
152
153         // Note: the proper way to not get blurry vertical and horizontal lines is
154         // to add 0.5 to all coordinates.
155         bool antialias = false;
156         for (int i = 0; i < np; ++i) {
157                 points[i].setX(xp[i]);
158                 points[i].setY(yp[i]);
159                 if (i != 0)
160                         antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
161         }
162         QColor const color = computeColor(col);
163         setQPainterPen(color, ls, lw);
164         bool const text_is_antialiased = renderHints() & TextAntialiasing;
165         setRenderHint(Antialiasing,
166                       antialias && text_is_antialiased && ls != line_solid_aliased);
167         if (fs == fill_none) {
168                 drawPolyline(points.data(), np);
169         } else {
170                 QBrush const oldbrush = brush();
171                 setBrush(QBrush(color));
172                 drawPolygon(points.data(), np, fs == fill_oddeven ?
173                             Qt::OddEvenFill : Qt::WindingFill);
174                 setBrush(oldbrush);
175         }
176         setRenderHint(Antialiasing, false);
177 }
178
179
180 void GuiPainter::path(int const * xp, int const * yp,
181         int const * c1x, int const * c1y,
182         int const * c2x, int const * c2y,
183         int np,
184         Color col,
185         fill_style fs,
186         line_style ls,
187         int lw)
188 {
189         QPainterPath bpath;
190         // This is the starting point, so its control points are meaningless
191         bpath.moveTo(xp[0], yp[0]);
192
193         for (int i = 1; i < np; ++i) {
194                 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
195                             c2x[i] == xp[i] && c2y[i] == yp[i];
196                 if (line)
197                         bpath.lineTo(xp[i], yp[i]);
198                 else
199                         bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
200         }
201         QColor const color = computeColor(col);
202         setQPainterPen(color, ls, lw);
203         bool const text_is_antialiased = renderHints() & TextAntialiasing;
204         setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
205         drawPath(bpath);
206         if (fs != fill_none)
207                 fillPath(bpath, QBrush(color));
208         setRenderHint(Antialiasing, false);
209 }
210
211
212 void GuiPainter::rectangle(int x, int y, int w, int h,
213         Color col,
214         line_style ls,
215         int lw)
216 {
217         setQPainterPen(computeColor(col), ls, lw, Qt::MiterJoin);
218         drawRect(x, y, w, h);
219 }
220
221
222 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
223 {
224         fillRect(x, y, w, h, guiApp->colorCache().get(col));
225 }
226
227
228 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
229         int a1, int a2, Color col)
230 {
231         // LyX usings 1/64ths degree, Qt usings 1/16th
232         setQPainterPen(computeColor(col));
233         bool const do_antialiasing = renderHints() & TextAntialiasing;
234         setRenderHint(Antialiasing, do_antialiasing);
235         drawArc(x, y, w, h, a1 / 4, a2 / 4);
236         setRenderHint(Antialiasing, false);
237 }
238
239
240 void GuiPainter::ellipse(double x, double y, double rx, double ry,
241         Color col, fill_style fs, line_style ls, int lw)
242 {
243         QColor const color = computeColor(col);
244         setQPainterPen(color, ls, lw);
245         bool const do_antialiasing = renderHints() & TextAntialiasing;
246         setRenderHint(Antialiasing, do_antialiasing);
247         if (fs == fill_none) {
248                 drawEllipse(QPointF(x, y), rx, ry);
249         } else {
250                 QBrush const oldbrush = brush();
251                 setBrush(QBrush(color));
252                 drawEllipse(QPointF(x, y), rx, ry);
253                 setBrush(oldbrush);
254         }
255         setRenderHint(Antialiasing, false);
256 }
257
258
259 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
260                        bool const revert_in_darkmode)
261 {
262         graphics::GuiImage const & qlimage =
263                 static_cast<graphics::GuiImage const &>(i);
264
265         fillRectangle(x, y, w, h, Color_graphicsbg);
266
267         QImage image = qlimage.image();
268
269         if (revert_in_darkmode && guiApp && guiApp->colorCache().isDarkMode())
270                 // FIXME this is only a cheap approximation
271                 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
272                 image.invertPixels();
273
274         QRectF const drect = QRectF(x, y, w, h);
275         QRectF const srect = QRectF(0, 0, image.width(), image.height());
276         // Bilinear filtering is needed on a rare occasion for instant previews when
277         // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
278         // This filter is optimised by qt on pixel-aligned images, so this does not
279         // affect performances in other cases.
280         setRenderHint(SmoothPixmapTransform);
281         drawImage(drect, image, srect);
282         setRenderHint(SmoothPixmapTransform, false);
283 }
284
285
286 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f, Direction const dir)
287 {
288         text(x, y, docstring(1, c), f, dir);
289 }
290
291
292 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f, Direction const dir)
293 {
294         text(x, y, s, f, dir, 0.0, 0.0);
295 }
296
297
298 void GuiPainter::text(int x, int y, docstring const & s,
299                       FontInfo const & f, Direction const dir,
300                       double const wordspacing, double const tw)
301 {
302         //LYXERR0("text: x=" << x << ", s=" << s);
303         if (s.empty())
304                 return;
305
306         /* Caution: The following ucs4 to QString conversions work for symbol fonts
307         only because they are no real conversions but simple casts in reality.
308         When we want to draw a symbol or calculate the metrics we pass the position
309         of the symbol in the font (as given in lib/symbols) as a char_type to the
310         frontend. This is just wrong, because the symbol is no UCS4 character at
311         all. You can think of this number as the code point of the symbol in a
312         custom symbol encoding. It works because this char_type is later on again
313         interpreted as a position in the font.
314         The correct solution would be to have extra functions for symbols, but that
315         would require to duplicate a lot of frontend and mathed support code.
316         */
317         QString str = toqstr(s);
318
319         QFont ff = getFont(f);
320         ff.setWordSpacing(wordspacing);
321         GuiFontMetrics const & fm = getFontMetrics(f);
322
323         int textwidth = 0;
324         if (tw == 0.0)
325                 // Take into account space stretching (word spacing)
326                 textwidth = fm.width(s) +
327                         static_cast<int>(countExpanders(s) * wordspacing);
328         else
329                 textwidth = static_cast<int>(tw);
330
331         textDecoration(f, x, y, textwidth);
332
333         setQPainterPen(computeColor(f.realColor()));
334         if (dir != Auto) {
335                 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
336                 QTextLine const & tline = ptl->lineForTextPosition(0);
337                 ptl->draw(this, QPointF(x, y - tline.ascent()));
338         } else {
339                 if (font() != ff)
340                         setFont(ff);
341                 drawText(x, y, str);
342         }
343         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
344         //      << " at " << x << "," << y);
345 }
346
347
348 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
349                       double const wordspacing, double const tw)
350 {
351         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
352              wordspacing, tw);
353 }
354
355
356 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
357                       Color other, size_type const from, size_type const to,
358                       double const wordspacing, double const tw)
359 {
360         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
361         FontInfo fi = f.fontInfo();
362         Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
363
364         // dimensions
365         int const ascent = fm.maxAscent();
366         int const height = fm.maxAscent() + fm.maxDescent();
367         int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
368         int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
369         // Avoid this case, since it would make the `other' text spill in some cases
370         if (xmin == xmax) {
371                 text(x, y, str, fi, dir, wordspacing, tw);
372                 return;
373         } else if (xmin > xmax)
374                 swap(xmin, xmax);
375
376         // First the part in other color
377         Color const orig = fi.realColor();
378         fi.setPaintColor(other);
379         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
380         setClipRegion(clip);
381         text(x, y, str, fi, dir, wordspacing, tw);
382
383         // Then the part in normal color
384         // Note that in Qt5, it is not possible to use Qt::UniteClip,
385         // therefore QRegion is used.
386         fi.setPaintColor(orig);
387         QRegion region(viewport());
388         setClipRegion(region - clip);
389         text(x, y, str, fi, dir, wordspacing, tw);
390         setClipping(false);
391 }
392
393
394 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
395 {
396         if (f.underbar() == FONT_ON)
397                 underline(f, x, y, width);
398         if (f.strikeout() == FONT_ON)
399                 strikeoutLine(f, x, y, width);
400         if (f.xout() == FONT_ON)
401                 crossoutLines(f, x, y, width);
402         if (f.uuline() == FONT_ON)
403                 doubleUnderline(f, x, y, width);
404         if (f.uwave() == FONT_ON)
405                 // f.color() doesn't work on some circumstances
406                 wavyHorizontalLine(f, x, y, width,  f.realColor().baseColor);
407 }
408
409
410 static int max(int a, int b) { return a > b ? a : b; }
411
412
413 void GuiPainter::rectText(int x, int y, docstring const & str,
414         FontInfo const & font, Color back, Color frame)
415 {
416         int width, ascent, descent;
417
418         FontMetrics const & fm = theFontMetrics(font);
419         fm.rectText(str, width, ascent, descent);
420
421         if (back != Color_none)
422                 fillRectangle(x + 1, y - ascent + 1, width - 1,
423                               ascent + descent - 1, back);
424
425         if (frame != Color_none)
426                 rectangle(x, y - ascent, width, ascent + descent, frame);
427
428         // FIXME: let offset depend on font
429         text(x + 3, y, str, font);
430 }
431
432
433 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
434         FontInfo const & font, Color back, Color frame, int offset)
435 {
436         int width, ascent, descent;
437
438         FontMetrics const & fm = theFontMetrics(font);
439         fm.buttonText(s, offset, width, ascent, descent);
440
441         int const d = offset / 2;
442
443         fillRectangle(x + d, baseline - ascent, width - offset,
444                               ascent + descent, back);
445         rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
446         text(x + offset, baseline, s, font);
447 }
448
449
450 int GuiPainter::preeditText(int x, int y, char_type c,
451         FontInfo const & font, preedit_style style)
452 {
453         FontInfo temp_font = font;
454         FontMetrics const & fm = theFontMetrics(font);
455         int ascent = fm.maxAscent();
456         int descent = fm.maxDescent();
457         int height = ascent + descent;
458         int width = fm.width(c);
459
460         switch (style) {
461                 case preedit_default:
462                         // default unselecting mode.
463                         fillRectangle(x, y - height + 1, width, height, Color_background);
464                         dashedUnderline(font, x, y - descent + 1, width);
465                         break;
466                 case preedit_selecting:
467                         // We are in selecting mode: white text on black background.
468                         fillRectangle(x, y - height + 1, width, height, Color_darkgray);
469                         temp_font.setColor(Color_white);
470                         break;
471                 case preedit_cursor:
472                         // The character comes with a cursor.
473                         fillRectangle(x, y - height + 1, width, height, Color_background);
474                         underline(font, x, y - descent + 1, width);
475                         break;
476         }
477         text(x, y - descent + 1, c, temp_font);
478
479         return width;
480 }
481
482
483 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
484                            line_style ls)
485 {
486         FontMetrics const & fm = theFontMetrics(f);
487         int const pos = fm.underlinePos();
488
489         line(x, y + pos, x + width, y + pos,
490              f.realColor(), ls, fm.lineWidth());
491 }
492
493
494 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
495 {
496         FontMetrics const & fm = theFontMetrics(f);
497         int const pos = fm.strikeoutPos();
498
499         line(x, y - pos, x + width, y - pos,
500              f.realColor(), line_solid, fm.lineWidth());
501 }
502
503
504 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
505 {
506         FontInfo tmpf = f;
507         tmpf.setXout(FONT_OFF);
508
509         // the definition of \xout in ulem.sty is
510     //  \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
511         // Let's mimic it somewhat.
512         double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
513         for (int i = 0 ; i < iround(width / offset) ; ++i)
514                 text(x + iround(i * offset), y, '/', tmpf);
515 }
516
517
518 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
519 {
520         FontMetrics const & fm = theFontMetrics(f);
521         int const pos1 = fm.underlinePos() + fm.lineWidth();
522         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
523
524         line(x, y + pos1, x + width, y + pos1,
525                  f.realColor(), line_solid, fm.lineWidth());
526         line(x, y + pos2, x + width, y + pos2,
527                  f.realColor(), line_solid, fm.lineWidth());
528 }
529
530
531 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
532 {
533         FontMetrics const & fm = theFontMetrics(f);
534
535         int const below = max(fm.maxDescent() / 2, 2);
536         int height = max((fm.maxDescent() / 4) - 1, 1);
537
538         if (height >= 2)
539                 height += below;
540
541         for (int n = 0; n != height; ++n)
542                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
543 }
544
545
546 void GuiPainter::wavyHorizontalLine(FontInfo const & f, int x, int y, int width, ColorCode col)
547 {
548         FontMetrics const & fm = theFontMetrics(f);
549         int const pos = fm.underlinePos();
550         int const lw = max(1, fm.lineWidth());
551
552         setQPainterPen(computeColor(col), line_solid, lw);
553         int const step = 2 * lw;
554         int const xend = x + width;
555         int height = lw;
556         //FIXME: I am not sure if Antialiasing gives the best effect.
557         //setRenderHint(Antialiasing, true);
558         QVector<QPoint> points;
559         while (x < xend) {
560                 height = - height;
561                 points.append(QPoint(x, y + pos - height));
562                 points.append(QPoint(x + step, y + pos + height));
563                 x += step;
564                 points.append(QPoint(x, (qreal)y + pos + height));
565                 points.append(QPoint(x + lw, y + pos + height));
566                 x += lw;
567         }
568         drawPolyline(points);
569         //setRenderHint(Antialiasing, false);
570 }
571
572 } // namespace frontend
573 } // namespace lyx