]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Fix using system theme icons with Qt 5 (#10052)
[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 "Language.h"
25 #include "LyXRC.h"
26
27 #include "insets/Inset.h"
28
29 #include "support/lassert.h"
30 #include "support/debug.h"
31
32 #include <QPixmapCache>
33 #include <QTextLayout>
34
35 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
36 // drawing text. This is especially useful for older PPC/Mac systems.
37 #if defined(Q_WS_X11) || defined(QPA_XCB)
38 #define USE_PIXMAP_CACHE 0
39 #else
40 #define USE_PIXMAP_CACHE 1
41 #endif
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47 namespace frontend {
48
49 const int Painter::thin_line = 1;
50
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52         : QPainter(device), Painter(pixel_ratio),
53           use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
54 {
55         // new QPainter has default QPen:
56         current_color_ = guiApp->colorCache().get(Color_black);
57         current_ls_ = line_solid;
58         current_lw_ = thin_line;
59 }
60
61
62 GuiPainter::~GuiPainter()
63 {
64         QPainter::end();
65         //lyxerr << "GuiPainter::end()" << endl;
66 }
67
68
69 void GuiPainter::setQPainterPen(QColor const & col,
70         Painter::line_style ls, int lw)
71 {
72         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
73                 return;
74
75         current_color_ = col;
76         current_ls_ = ls;
77         current_lw_ = lw;
78
79         QPen pen = QPainter::pen();
80         pen.setColor(col);
81
82         switch (ls) {
83                 case line_solid: pen.setStyle(Qt::SolidLine); break;
84                 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
85         }
86
87         pen.setWidth(lw);
88
89         setPen(pen);
90 }
91
92
93 QString GuiPainter::generateStringSignature(QString const & str,
94                                             FontInfo const & f,
95                                             double wordspacing)
96 {
97         QString sig = str;
98         sig.append(QChar(static_cast<short>(f.family())));
99         sig.append(QChar(static_cast<short>(f.series())));
100         sig.append(QChar(static_cast<short>(f.realShape())));
101         sig.append(QChar(static_cast<short>(f.size())));
102         Color const & color = f.realColor();
103         sig.append(QChar(static_cast<short>(color.baseColor)));
104         sig.append(QChar(static_cast<short>(color.mergeColor)));
105         sig.append(QString::number(wordspacing));
106         if (!monochrome_min_.empty()) {
107                 QColor const & min = monochrome_min_.top();
108                 QColor const & max = monochrome_max_.top();
109                 sig.append(QChar(static_cast<short>(min.red())));
110                 sig.append(QChar(static_cast<short>(min.green())));
111                 sig.append(QChar(static_cast<short>(min.blue())));
112                 sig.append(QChar(static_cast<short>(max.red())));
113                 sig.append(QChar(static_cast<short>(max.green())));
114                 sig.append(QChar(static_cast<short>(max.blue())));
115         }
116         return sig;
117 }
118
119
120 QColor GuiPainter::computeColor(Color col)
121 {
122         return filterColor(guiApp->colorCache().get(col));
123 }
124
125
126 QColor GuiPainter::filterColor(QColor const & col)
127 {
128         if (monochrome_min_.empty())
129                 return col;
130
131         // map into [min,max] interval
132         QColor const & min = monochrome_min_.top();
133         QColor const & max = monochrome_max_.top();
134                         
135         qreal v = col.valueF();
136         v *= v; // make it a bit steeper (i.e. darker)
137                 
138         qreal minr, ming, minb;
139         qreal maxr, maxg, maxb;
140         min.getRgbF(&minr, &ming, &minb);
141         max.getRgbF(&maxr, &maxg, &maxb);
142                         
143         QColor c;
144         c.setRgbF(
145                 v * (minr - maxr) + maxr,
146                 v * (ming - maxg) + maxg,
147                 v * (minb - maxb) + maxb);
148         return c;
149 }
150
151
152 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
153 {
154         QColor qmin = filterColor(guiApp->colorCache().get(min));
155         QColor qmax = filterColor(guiApp->colorCache().get(max));
156         monochrome_min_.push(qmin);
157         monochrome_max_.push(qmax);
158 }
159
160
161 void GuiPainter::leaveMonochromeMode()
162 {
163         LASSERT(!monochrome_min_.empty(), return);
164         monochrome_min_.pop();
165         monochrome_max_.pop();
166 }
167
168
169 void GuiPainter::point(int x, int y, Color col)
170 {
171         if (!isDrawingEnabled())
172                 return;
173
174         setQPainterPen(computeColor(col));
175         drawPoint(x, y);
176 }
177
178
179 void GuiPainter::line(int x1, int y1, int x2, int y2,
180         Color col,
181         line_style ls,
182         int lw)
183 {
184         if (!isDrawingEnabled())
185                 return;
186
187         setQPainterPen(computeColor(col), ls, lw);
188         bool const do_antialiasing = renderHints() & TextAntialiasing
189                 && x1 != x2 && y1 != y2;
190         setRenderHint(Antialiasing, do_antialiasing);
191         drawLine(x1, y1, x2, y2);
192         setRenderHint(Antialiasing, false);
193 }
194
195
196 void GuiPainter::lines(int const * xp, int const * yp, int np,
197         Color col,
198         fill_style fs,
199         line_style ls,
200         int lw)
201 {
202         if (!isDrawingEnabled())
203                 return;
204
205         // double the size if needed
206         // FIXME THREAD
207         static QVector<QPoint> points(32);
208         if (np > points.size())
209                 points.resize(2 * np);
210
211         bool antialias = false;
212         for (int i = 0; i < np; ++i) {
213                 points[i].setX(xp[i]);
214                 points[i].setY(yp[i]);
215                 if (i != 0)
216                         antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
217         }
218         QColor const color = computeColor(col);
219         setQPainterPen(color, ls, lw);
220         bool const text_is_antialiased = renderHints() & TextAntialiasing;
221         setRenderHint(Antialiasing, antialias && text_is_antialiased);
222         if (fs == fill_none) {
223                 drawPolyline(points.data(), np);
224         } else {
225                 QBrush const oldbrush = brush();
226                 setBrush(QBrush(color));
227                 drawPolygon(points.data(), np, fs == fill_oddeven ?
228                             Qt::OddEvenFill : Qt::WindingFill);
229                 setBrush(oldbrush);
230         }
231         setRenderHint(Antialiasing, false);
232 }
233
234
235 void GuiPainter::path(int const * xp, int const * yp,
236         int const * c1x, int const * c1y,
237         int const * c2x, int const * c2y,
238         int np,
239         Color col,
240         fill_style fs,
241         line_style ls,
242         int lw)
243 {
244         if (!isDrawingEnabled())
245                 return;
246
247         QPainterPath bpath;
248         // This is the starting point, so its control points are meaningless
249         bpath.moveTo(xp[0], yp[0]);
250
251         for (int i = 1; i < np; ++i) {
252                 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
253                             c2x[i] == xp[i] && c2y[i] == yp[i];
254                 if (line)
255                         bpath.lineTo(xp[i], yp[i]);
256                 else
257                         bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
258         }
259         QColor const color = computeColor(col);
260         setQPainterPen(color, ls, lw);
261         bool const text_is_antialiased = renderHints() & TextAntialiasing;
262         setRenderHint(Antialiasing, text_is_antialiased);
263         drawPath(bpath);
264         if (fs != fill_none)
265                 fillPath(bpath, QBrush(color));
266         setRenderHint(Antialiasing, false);
267 }
268
269
270 void GuiPainter::rectangle(int x, int y, int w, int h,
271         Color col,
272         line_style ls,
273         int lw)
274 {
275         if (!isDrawingEnabled())
276                 return;
277
278         setQPainterPen(computeColor(col), ls, lw);
279         drawRect(x, y, w, h);
280 }
281
282
283 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
284 {
285         if (!isDrawingEnabled())
286                 return;
287
288         fillRect(x, y, w, h, guiApp->colorCache().get(col));
289 }
290
291
292 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
293         int a1, int a2, Color col)
294 {
295         if (!isDrawingEnabled())
296                 return;
297
298         // LyX usings 1/64ths degree, Qt usings 1/16th
299         setQPainterPen(computeColor(col));
300         bool const do_antialiasing = renderHints() & TextAntialiasing;
301         setRenderHint(Antialiasing, do_antialiasing);
302         drawArc(x, y, w, h, a1 / 4, a2 / 4);
303         setRenderHint(Antialiasing, false);
304 }
305
306
307 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
308 {
309         graphics::GuiImage const & qlimage =
310                 static_cast<graphics::GuiImage const &>(i);
311
312         fillRectangle(x, y, w, h, Color_graphicsbg);
313
314         if (!isDrawingEnabled())
315                 return;
316
317         QImage const image = qlimage.image();
318         QRectF const drect = QRectF(x, y, w, h);
319         QRectF const srect = QRectF(0, 0, image.width(), image.height());
320         // Bilinear filtering is needed on a rare occasion for instant previews when
321         // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
322         // This filter is optimised by qt on pixel-aligned images, so this does not
323         // affect performances in other cases.
324         setRenderHint(SmoothPixmapTransform);
325         drawImage(drect, image, srect);
326         setRenderHint(SmoothPixmapTransform, false);
327 }
328
329
330 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
331 {
332         return text(x, y, docstring(1, c), f);
333 }
334
335
336 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
337 {
338         setQPainterPen(computeColor(f.realColor()));
339         if (font() != ff)
340                 setFont(ff);
341
342          /* In LyX, the character direction is forced by the language.
343           * Therefore, we have to signal that fact to Qt.
344           */
345 #if 1
346         /* Use unicode override characters to enforce drawing direction
347          * Source: http://www.iamcal.com/understanding-bidirectional-text/
348          */
349         if (rtl)
350                 // Right-to-left override: forces to draw text right-to-left
351                 str = QChar(0x202E) + str;
352         else
353                 // Left-to-right override: forces to draw text left-to-right
354                 str =  QChar(0x202D) + str;
355         drawText(x, y, str);
356 #else
357         /* This looks like a cleaner solution, but it has drawbacks
358          * - does not work reliably (Mac OS X, ...)
359          * - it is not really documented
360          * Keep it here for now, in case it can be helpful
361          */
362         //This is much stronger than setLayoutDirection.
363         int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
364         drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
365                  flag | Qt::TextDontClip,
366                  str);
367 #endif
368 }
369
370
371 int GuiPainter::text(int x, int y, docstring const & s,
372                      FontInfo const & f, bool const rtl,
373                      double const wordspacing)
374 {
375         //LYXERR0("text: x=" << x << ", s=" << s);
376         if (s.empty())
377                 return 0;
378
379         /* Caution: The following ucs4 to QString conversions work for symbol fonts
380         only because they are no real conversions but simple casts in reality.
381         When we want to draw a symbol or calculate the metrics we pass the position
382         of the symbol in the font (as given in lib/symbols) as a char_type to the
383         frontend. This is just wrong, because the symbol is no UCS4 character at
384         all. You can think of this number as the code point of the symbol in a
385         custom symbol encoding. It works because this char_type is lateron again
386         interpreted as a position in the font again.
387         The correct solution would be to have extra functions for symbols, but that
388         would require to duplicate a lot of frontend and mathed support code.
389         */
390         QString str = toqstr(s);
391
392 #if 0
393         // HACK: QT3 refuses to show single compose characters
394         //       Still needed with Qt4?
395         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
396                 str = ' ' + str;
397 #endif
398
399         QFont ff = getFont(f);
400         ff.setWordSpacing(wordspacing);
401         GuiFontMetrics const & fm = getFontMetrics(f);
402
403         // Here we use the font width cache instead of
404         //   textwidth = fontMetrics().width(str);
405         // because the above is awfully expensive on MacOSX
406         // Note that we have to take in account space stretching (word spacing)
407         int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
408
409         if (!isDrawingEnabled())
410                 return textwidth;
411
412         textDecoration(f, x, y, textwidth);
413
414         if (use_pixmap_cache_) {
415                 QPixmap pm;
416                 QString key = generateStringSignature(str, f, wordspacing);
417
418                 // Warning: Left bearing is in general negative! Only the case
419                 // where left bearing is negative is of interest WRT the
420                 // pixmap width and the text x-position.
421                 // Only the left bearing of the first character is important
422                 // as we always write from left to right, even for
423                 // right-to-left languages.
424                 // FIXME: this is probably broken for RTL now that we draw full strings.
425                 // Morover the first/last element is possibly not the right one since the glyph may have changed.
426                 int const lb = min(fm.lbearing(s[0]), 0);
427                 int const mA = fm.maxAscent();
428                 if (QPixmapCache::find(key, pm)) {
429                         // Draw the cached pixmap.
430                         drawPixmap(x + lb, y - mA, pm);
431                         return textwidth;
432                 }
433
434                 // Only the right bearing of the last character is
435                 // important as we always write from left to right,
436                 // even for right-to-left languages.
437                 int const rb = fm.rbearing(s[s.size()-1]);
438                 int const w = textwidth + rb - lb;
439                 int const mD = fm.maxDescent();
440                 int const h = mA + mD;
441                 if (w > 0 && h > 0) {
442                         pm = QPixmap(static_cast<int>(pixelRatio() * w),
443                                                  static_cast<int>(pixelRatio() * h));
444 #if QT_VERSION >= 0x050000
445                         pm.setDevicePixelRatio(pixelRatio());
446 #endif
447                         pm.fill(Qt::transparent);
448                         GuiPainter p(&pm, pixelRatio());
449                         p.do_drawText(-lb, mA, str, rtl, f, ff);
450                         QPixmapCache::insert(key, pm);
451                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
452                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
453                         //      << "  rb=" << rb);
454
455                         // Draw the new cached pixmap.
456                         drawPixmap(x + lb, y - mA, pm);
457                         //rectangle(x-lb, y-mA, w, h, Color_green);
458                 }
459                 return textwidth;
460         }
461
462         // don't use the pixmap cache,
463         do_drawText(x, y, str, rtl, f, ff);
464         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
465         //      << " at " << x << "," << y);
466         return textwidth;
467 }
468
469
470 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
471                      double const wordspacing)
472 {
473         return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
474 }
475
476
477 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
478                      Color other, size_type const from, size_type const to,
479                      double const wordspacing)
480 {
481         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
482         FontInfo fi = f.fontInfo();
483         bool const rtl = f.isVisibleRightToLeft();
484
485         // dimensions
486         int const ascent = fm.maxAscent();
487         int const height = fm.maxAscent() + fm.maxDescent();
488         int xmin = fm.pos2x(str, from, rtl, wordspacing);
489         int xmax = fm.pos2x(str, to, rtl, wordspacing);
490         if (xmin > xmax)
491                 swap(xmin, xmax);
492
493         // First the part in other color
494         Color const orig = fi.realColor();
495         fi.setPaintColor(other);
496         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
497         setClipRegion(clip);
498         int const textwidth = text(x, y, str, fi, rtl, wordspacing);
499
500         // Then the part in normal color
501         // Note that in Qt5, it is not possible to use Qt::UniteClip,
502         // therefore QRegion is used.
503         fi.setPaintColor(orig);
504         QRegion region(viewport());
505         setClipRegion(region - clip);
506         text(x, y, str, fi, rtl, wordspacing);
507         setClipping(false);
508
509         return textwidth;
510 }
511
512
513 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
514 {
515         if (f.underbar() == FONT_ON)
516                 underline(f, x, y, width);
517         if (f.strikeout() == FONT_ON)
518                 strikeoutLine(f, x, y, width);
519         if (f.uuline() == FONT_ON)
520                 doubleUnderline(f, x, y, width);
521         if (f.uwave() == FONT_ON)
522                 // f.color() doesn't work on some circumstances
523                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
524 }
525
526
527 static int max(int a, int b) { return a > b ? a : b; }
528
529
530 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
531 {
532         if (mouseHover)
533                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
534         else
535                 fillRectangle(x, y, w, h, Color_buttonbg);
536         buttonFrame(x, y, w, h);
537 }
538
539
540 void GuiPainter::buttonFrame(int x, int y, int w, int h)
541 {
542         line(x, y, x, y + h - 1, Color_buttonframe);
543         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
544         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
545         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
546 }
547
548
549 void GuiPainter::rectText(int x, int y, docstring const & str,
550         FontInfo const & font, Color back, Color frame)
551 {
552         int width;
553         int ascent;
554         int descent;
555
556         FontMetrics const & fm = theFontMetrics(font);
557         fm.rectText(str, width, ascent, descent);
558
559         if (back != Color_none)
560                 fillRectangle(x + 1, y - ascent + 1, width - 1,
561                               ascent + descent - 1, back);
562
563         if (frame != Color_none)
564                 rectangle(x, y - ascent, width, ascent + descent, frame);
565
566         text(x + 3, y, str, font);
567 }
568
569
570 void GuiPainter::buttonText(int x, int y, docstring const & str,
571         FontInfo const & font, bool mouseHover)
572 {
573         int width;
574         int ascent;
575         int descent;
576
577         FontMetrics const & fm = theFontMetrics(font);
578         fm.buttonText(str, width, ascent, descent);
579
580         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
581
582         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
583         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
584 }
585
586
587 int GuiPainter::preeditText(int x, int y, char_type c,
588         FontInfo const & font, preedit_style style)
589 {
590         FontInfo temp_font = font;
591         FontMetrics const & fm = theFontMetrics(font);
592         int ascent = fm.maxAscent();
593         int descent = fm.maxDescent();
594         int height = ascent + descent;
595         int width = fm.width(c);
596
597         switch (style) {
598                 case preedit_default:
599                         // default unselecting mode.
600                         fillRectangle(x, y - height + 1, width, height, Color_background);
601                         dashedUnderline(font, x, y - descent + 1, width);
602                         break;
603                 case preedit_selecting:
604                         // We are in selecting mode: white text on black background.
605                         fillRectangle(x, y - height + 1, width, height, Color_black);
606                         temp_font.setColor(Color_white);
607                         break;
608                 case preedit_cursor:
609                         // The character comes with a cursor.
610                         fillRectangle(x, y - height + 1, width, height, Color_background);
611                         underline(font, x, y - descent + 1, width);
612                         break;
613         }
614         text(x, y - descent + 1, c, temp_font);
615
616         return width;
617 }
618
619
620 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
621                            line_style ls)
622 {
623         FontMetrics const & fm = theFontMetrics(f);
624         int const pos = fm.underlinePos();
625
626         line(x, y + pos, x + width, y + pos,
627              f.realColor(), ls, fm.lineWidth());
628 }
629
630
631 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
632 {
633         FontMetrics const & fm = theFontMetrics(f);
634         int const pos = fm.strikeoutPos();
635
636         line(x, y - pos, x + width, y - pos,
637              f.realColor(), line_solid, fm.lineWidth());
638 }
639
640
641 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
642 {
643         FontMetrics const & fm = theFontMetrics(f);
644         int const pos1 = fm.underlinePos() + fm.lineWidth();
645         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
646
647         line(x, y + pos1, x + width, y + pos1,
648                  f.realColor(), line_solid, fm.lineWidth());
649         line(x, y + pos2, x + width, y + pos2,
650                  f.realColor(), line_solid, fm.lineWidth());
651 }
652
653
654 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
655 {
656         FontMetrics const & fm = theFontMetrics(f);
657
658         int const below = max(fm.maxDescent() / 2, 2);
659         int height = max((fm.maxDescent() / 4) - 1, 1);
660
661         if (height >= 2)
662                 height += below;
663
664         for (int n = 0; n != height; ++n)
665                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
666 }
667
668
669 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
670 {
671         setQPainterPen(computeColor(col));
672         int const step = 2;
673         int const xend = x + width;
674         int height = 1;
675         //FIXME: I am not sure if Antialiasing gives the best effect.
676         //setRenderHint(Antialiasing, true);
677         while (x < xend) {
678                 height = - height;
679                 drawLine(x, y - height, x + step, y + height);
680                 x += step;
681                 drawLine(x, y + height, x + step/2, y + height);
682                 x += step/2;
683         }
684         //setRenderHint(Antialiasing, false);
685 }
686
687 } // namespace frontend
688 } // namespace lyx