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