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