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