]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Remove support for pixmap cache
[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::do_drawText(int x, int y, QString str,
288                              GuiPainter::Direction const dir,
289                              FontInfo const & f, QFont ff)
290 {
291         setQPainterPen(computeColor(f.realColor()));
292         if (font() != ff)
293                 setFont(ff);
294
295          /* In LyX, the character direction is forced by the language.
296           * Therefore, we have to signal that fact to Qt.
297           */
298 #if 1
299         /* Use unicode override characters to enforce drawing direction
300          * Source: http://www.iamcal.com/understanding-bidirectional-text/
301          */
302         if (dir == RtL)
303                 // Right-to-left override: forces to draw text right-to-left
304                 str = QChar(0x202E) + str;
305         else if (dir == LtR)
306                 // Left-to-right override: forces to draw text left-to-right
307                 str =  QChar(0x202D) + str;
308         drawText(x, y, str);
309 #else
310         /* This looks like a cleaner solution, but it has drawbacks
311          * - does not work reliably (Mac OS X, ...)
312          * - it is not really documented
313          * Keep it here for now, in case it can be helpful
314          */
315         //This is much stronger than setLayoutDirection.
316         int flag = 0;
317         if (dir == RtL)
318                 flag = Qt::TextForceRightToLeft;
319         else if (dir == LtR)
320                 flag = Qt::TextForceLeftToRight;
321         drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
322                  flag | Qt::TextDontClip,
323                  str);
324 #endif
325 }
326
327
328 void GuiPainter::text(int x, int y, docstring const & s,
329                       FontInfo const & f, Direction const dir,
330                       double const wordspacing, double const tw)
331 {
332         //LYXERR0("text: x=" << x << ", s=" << s);
333         if (s.empty())
334                 return;
335
336         /* Caution: The following ucs4 to QString conversions work for symbol fonts
337         only because they are no real conversions but simple casts in reality.
338         When we want to draw a symbol or calculate the metrics we pass the position
339         of the symbol in the font (as given in lib/symbols) as a char_type to the
340         frontend. This is just wrong, because the symbol is no UCS4 character at
341         all. You can think of this number as the code point of the symbol in a
342         custom symbol encoding. It works because this char_type is later on again
343         interpreted as a position in the font.
344         The correct solution would be to have extra functions for symbols, but that
345         would require to duplicate a lot of frontend and mathed support code.
346         */
347         QString str = toqstr(s);
348
349 #if 0
350         // HACK: QT3 refuses to show single compose characters
351         //       Still needed with Qt4?
352         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
353                 str = ' ' + str;
354 #endif
355
356         QFont ff = getFont(f);
357         ff.setWordSpacing(wordspacing);
358         GuiFontMetrics const & fm = getFontMetrics(f);
359
360         int textwidth = 0;
361         if (tw == 0.0)
362                 // Take into account space stretching (word spacing)
363                 textwidth = fm.width(s) +
364                         static_cast<int>(fm.countExpanders(s) * wordspacing);
365         else
366                 textwidth = static_cast<int>(tw);
367
368         textDecoration(f, x, y, textwidth);
369
370         setQPainterPen(computeColor(f.realColor()));
371         if (dir != Auto) {
372                 shared_ptr<QTextLayout const> ptl =
373                         fm.getTextLayout(s, dir == RtL, wordspacing);
374                 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
375         }
376         else {
377                 if (font() != ff)
378                         setFont(ff);
379                 drawText(x, y, str);
380         }
381         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
382         //      << " at " << x << "," << y);
383 }
384
385
386 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
387                       double const wordspacing, double const tw)
388 {
389         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
390              wordspacing, tw);
391 }
392
393
394 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
395                       Color other, size_type const from, size_type const to,
396                       double const wordspacing, double const tw)
397 {
398         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
399         FontInfo fi = f.fontInfo();
400         Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
401
402         // dimensions
403         int const ascent = fm.maxAscent();
404         int const height = fm.maxAscent() + fm.maxDescent();
405         int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
406         int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
407         if (xmin > xmax)
408                 swap(xmin, xmax);
409
410         // First the part in other color
411         Color const orig = fi.realColor();
412         fi.setPaintColor(other);
413         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
414         setClipRegion(clip);
415         text(x, y, str, fi, dir, wordspacing, tw);
416
417         // Then the part in normal color
418         // Note that in Qt5, it is not possible to use Qt::UniteClip,
419         // therefore QRegion is used.
420         fi.setPaintColor(orig);
421         QRegion region(viewport());
422         setClipRegion(region - clip);
423         text(x, y, str, fi, dir, wordspacing, tw);
424         setClipping(false);
425 }
426
427
428 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
429 {
430         if (f.underbar() == FONT_ON)
431                 underline(f, x, y, width);
432         if (f.strikeout() == FONT_ON)
433                 strikeoutLine(f, x, y, width);
434         if (f.xout() == FONT_ON)
435                 crossoutLines(f, x, y, width);
436         if (f.uuline() == FONT_ON)
437                 doubleUnderline(f, x, y, width);
438         if (f.uwave() == FONT_ON)
439                 // f.color() doesn't work on some circumstances
440                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
441 }
442
443
444 static int max(int a, int b) { return a > b ? a : b; }
445
446
447 void GuiPainter::rectText(int x, int y, docstring const & str,
448         FontInfo const & font, Color back, Color frame)
449 {
450         int width, ascent, descent;
451
452         FontMetrics const & fm = theFontMetrics(font);
453         fm.rectText(str, width, ascent, descent);
454
455         if (back != Color_none)
456                 fillRectangle(x + 1, y - ascent + 1, width - 1,
457                               ascent + descent - 1, back);
458
459         if (frame != Color_none)
460                 rectangle(x, y - ascent, width, ascent + descent, frame);
461
462         // FIXME: let offset depend on font
463         text(x + 3, y, str, font);
464 }
465
466
467 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
468         FontInfo const & font, Color back, Color frame, int offset)
469 {
470         int width, ascent, descent;
471
472         FontMetrics const & fm = theFontMetrics(font);
473         fm.buttonText(s, offset, width, ascent, descent);
474
475         static int const d = offset / 2;
476
477         fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
478                               ascent + descent - 1, back);
479         rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
480         text(x + offset, baseline, s, font);
481 }
482
483
484 int GuiPainter::preeditText(int x, int y, char_type c,
485         FontInfo const & font, preedit_style style)
486 {
487         FontInfo temp_font = font;
488         FontMetrics const & fm = theFontMetrics(font);
489         int ascent = fm.maxAscent();
490         int descent = fm.maxDescent();
491         int height = ascent + descent;
492         int width = fm.width(c);
493
494         switch (style) {
495                 case preedit_default:
496                         // default unselecting mode.
497                         fillRectangle(x, y - height + 1, width, height, Color_background);
498                         dashedUnderline(font, x, y - descent + 1, width);
499                         break;
500                 case preedit_selecting:
501                         // We are in selecting mode: white text on black background.
502                         fillRectangle(x, y - height + 1, width, height, Color_black);
503                         temp_font.setColor(Color_white);
504                         break;
505                 case preedit_cursor:
506                         // The character comes with a cursor.
507                         fillRectangle(x, y - height + 1, width, height, Color_background);
508                         underline(font, x, y - descent + 1, width);
509                         break;
510         }
511         text(x, y - descent + 1, c, temp_font);
512
513         return width;
514 }
515
516
517 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
518                            line_style ls)
519 {
520         FontMetrics const & fm = theFontMetrics(f);
521         int const pos = fm.underlinePos();
522
523         line(x, y + pos, x + width, y + pos,
524              f.realColor(), ls, fm.lineWidth());
525 }
526
527
528 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
529 {
530         FontMetrics const & fm = theFontMetrics(f);
531         int const pos = fm.strikeoutPos();
532
533         line(x, y - pos, x + width, y - pos,
534              f.realColor(), line_solid, fm.lineWidth());
535 }
536
537
538 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
539 {
540         FontInfo tmpf = f;
541         tmpf.setXout(FONT_OFF);
542
543         // the definition of \xout in ulem.sty is
544     //  \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
545         // Let's mimick it somewhat.
546         double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
547         for (int i = 0 ; i < iround(width / offset) ; ++i)
548                 text(x + iround(i * offset), y, '/', tmpf);
549 }
550
551
552 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
553 {
554         FontMetrics const & fm = theFontMetrics(f);
555         int const pos1 = fm.underlinePos() + fm.lineWidth();
556         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
557
558         line(x, y + pos1, x + width, y + pos1,
559                  f.realColor(), line_solid, fm.lineWidth());
560         line(x, y + pos2, x + width, y + pos2,
561                  f.realColor(), line_solid, fm.lineWidth());
562 }
563
564
565 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
566 {
567         FontMetrics const & fm = theFontMetrics(f);
568
569         int const below = max(fm.maxDescent() / 2, 2);
570         int height = max((fm.maxDescent() / 4) - 1, 1);
571
572         if (height >= 2)
573                 height += below;
574
575         for (int n = 0; n != height; ++n)
576                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
577 }
578
579
580 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
581 {
582         setQPainterPen(computeColor(col));
583         int const step = 2;
584         int const xend = x + width;
585         int height = 1;
586         //FIXME: I am not sure if Antialiasing gives the best effect.
587         //setRenderHint(Antialiasing, true);
588         while (x < xend) {
589                 height = - height;
590                 drawLine(x, y - height, x + step, y + height);
591                 x += step;
592                 drawLine(x, y + height, x + step/2, y + height);
593                 x += step/2;
594         }
595         //setRenderHint(Antialiasing, false);
596 }
597
598 } // namespace frontend
599 } // namespace lyx