]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
FindAdv: Comments
[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                 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
333         } else {
334                 if (font() != ff)
335                         setFont(ff);
336                 drawText(x, y, str);
337         }
338         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
339         //      << " at " << x << "," << y);
340 }
341
342
343 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
344                       double const wordspacing, double const tw)
345 {
346         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
347              wordspacing, tw);
348 }
349
350
351 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
352                       Color other, size_type const from, size_type const to,
353                       double const wordspacing, double const tw)
354 {
355         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
356         FontInfo fi = f.fontInfo();
357         Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
358
359         // dimensions
360         int const ascent = fm.maxAscent();
361         int const height = fm.maxAscent() + fm.maxDescent();
362         int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
363         int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
364         if (xmin > xmax)
365                 swap(xmin, xmax);
366
367         // First the part in other color
368         Color const orig = fi.realColor();
369         fi.setPaintColor(other);
370         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
371         setClipRegion(clip);
372         text(x, y, str, fi, dir, wordspacing, tw);
373
374         // Then the part in normal color
375         // Note that in Qt5, it is not possible to use Qt::UniteClip,
376         // therefore QRegion is used.
377         fi.setPaintColor(orig);
378         QRegion region(viewport());
379         setClipRegion(region - clip);
380         text(x, y, str, fi, dir, wordspacing, tw);
381         setClipping(false);
382 }
383
384
385 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
386 {
387         if (f.underbar() == FONT_ON)
388                 underline(f, x, y, width);
389         if (f.strikeout() == FONT_ON)
390                 strikeoutLine(f, x, y, width);
391         if (f.xout() == FONT_ON)
392                 crossoutLines(f, x, y, width);
393         if (f.uuline() == FONT_ON)
394                 doubleUnderline(f, x, y, width);
395         if (f.uwave() == FONT_ON)
396                 // f.color() doesn't work on some circumstances
397                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
398 }
399
400
401 static int max(int a, int b) { return a > b ? a : b; }
402
403
404 void GuiPainter::rectText(int x, int y, docstring const & str,
405         FontInfo const & font, Color back, Color frame)
406 {
407         int width, ascent, descent;
408
409         FontMetrics const & fm = theFontMetrics(font);
410         fm.rectText(str, width, ascent, descent);
411
412         if (back != Color_none)
413                 fillRectangle(x + 1, y - ascent + 1, width - 1,
414                               ascent + descent - 1, back);
415
416         if (frame != Color_none)
417                 rectangle(x, y - ascent, width, ascent + descent, frame);
418
419         // FIXME: let offset depend on font
420         text(x + 3, y, str, font);
421 }
422
423
424 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
425         FontInfo const & font, Color back, Color frame, int offset)
426 {
427         int width, ascent, descent;
428
429         FontMetrics const & fm = theFontMetrics(font);
430         fm.buttonText(s, offset, width, ascent, descent);
431
432         static int const d = offset / 2;
433
434         fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
435                               ascent + descent - 1, back);
436         rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
437         text(x + offset, baseline, s, font);
438 }
439
440
441 int GuiPainter::preeditText(int x, int y, char_type c,
442         FontInfo const & font, preedit_style style)
443 {
444         FontInfo temp_font = font;
445         FontMetrics const & fm = theFontMetrics(font);
446         int ascent = fm.maxAscent();
447         int descent = fm.maxDescent();
448         int height = ascent + descent;
449         int width = fm.width(c);
450
451         switch (style) {
452                 case preedit_default:
453                         // default unselecting mode.
454                         fillRectangle(x, y - height + 1, width, height, Color_background);
455                         dashedUnderline(font, x, y - descent + 1, width);
456                         break;
457                 case preedit_selecting:
458                         // We are in selecting mode: white text on black background.
459                         fillRectangle(x, y - height + 1, width, height, Color_black);
460                         temp_font.setColor(Color_white);
461                         break;
462                 case preedit_cursor:
463                         // The character comes with a cursor.
464                         fillRectangle(x, y - height + 1, width, height, Color_background);
465                         underline(font, x, y - descent + 1, width);
466                         break;
467         }
468         text(x, y - descent + 1, c, temp_font);
469
470         return width;
471 }
472
473
474 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
475                            line_style ls)
476 {
477         FontMetrics const & fm = theFontMetrics(f);
478         int const pos = fm.underlinePos();
479
480         line(x, y + pos, x + width, y + pos,
481              f.realColor(), ls, fm.lineWidth());
482 }
483
484
485 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
486 {
487         FontMetrics const & fm = theFontMetrics(f);
488         int const pos = fm.strikeoutPos();
489
490         line(x, y - pos, x + width, y - pos,
491              f.realColor(), line_solid, fm.lineWidth());
492 }
493
494
495 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
496 {
497         FontInfo tmpf = f;
498         tmpf.setXout(FONT_OFF);
499
500         // the definition of \xout in ulem.sty is
501     //  \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
502         // Let's mimick it somewhat.
503         double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
504         for (int i = 0 ; i < iround(width / offset) ; ++i)
505                 text(x + iround(i * offset), y, '/', tmpf);
506 }
507
508
509 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
510 {
511         FontMetrics const & fm = theFontMetrics(f);
512         int const pos1 = fm.underlinePos() + fm.lineWidth();
513         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
514
515         line(x, y + pos1, x + width, y + pos1,
516                  f.realColor(), line_solid, fm.lineWidth());
517         line(x, y + pos2, x + width, y + pos2,
518                  f.realColor(), line_solid, fm.lineWidth());
519 }
520
521
522 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
523 {
524         FontMetrics const & fm = theFontMetrics(f);
525
526         int const below = max(fm.maxDescent() / 2, 2);
527         int height = max((fm.maxDescent() / 4) - 1, 1);
528
529         if (height >= 2)
530                 height += below;
531
532         for (int n = 0; n != height; ++n)
533                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
534 }
535
536
537 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
538 {
539         setQPainterPen(computeColor(col));
540         int const step = 2;
541         int const xend = x + width;
542         int height = 1;
543         //FIXME: I am not sure if Antialiasing gives the best effect.
544         //setRenderHint(Antialiasing, true);
545         while (x < xend) {
546                 height = - height;
547                 drawLine(x, y - height, x + step, y + height);
548                 x += step;
549                 drawLine(x, y + height, x + step/2, y + height);
550                 x += step/2;
551         }
552         //setRenderHint(Antialiasing, false);
553 }
554
555 } // namespace frontend
556 } // namespace lyx