]> git.lyx.org Git - features.git/blob - src/frontends/qt4/GuiPainter.cpp
Make the different Painter::text void methods
[features.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 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
331 {
332         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 void 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() || !isDrawingEnabled())
377                 return;
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         textDecoration(f, x, y, textwidth);
410
411         if (use_pixmap_cache_) {
412                 QPixmap pm;
413                 QString key = generateStringSignature(str, f, wordspacing);
414
415                 // Warning: Left bearing is in general negative! Only the case
416                 // where left bearing is negative is of interest WRT the
417                 // pixmap width and the text x-position.
418                 // Only the left bearing of the first character is important
419                 // as we always write from left to right, even for
420                 // right-to-left languages.
421                 // FIXME: this is probably broken for RTL now that we draw full strings.
422                 // Morover the first/last element is possibly not the right one since the glyph may have changed.
423                 int const lb = min(fm.lbearing(s[0]), 0);
424                 int const mA = fm.maxAscent();
425                 if (QPixmapCache::find(key, pm)) {
426                         // Draw the cached pixmap.
427                         drawPixmap(x + lb, y - mA, pm);
428                         return;
429                 }
430
431                 // Only the right bearing of the last character is
432                 // important as we always write from left to right,
433                 // even for right-to-left languages.
434                 int const rb = fm.rbearing(s[s.size()-1]);
435                 int const w = textwidth + rb - lb;
436                 int const mD = fm.maxDescent();
437                 int const h = mA + mD;
438                 if (w > 0 && h > 0) {
439                         pm = QPixmap(static_cast<int>(pixelRatio() * w),
440                                                  static_cast<int>(pixelRatio() * h));
441 #if QT_VERSION >= 0x050000
442                         pm.setDevicePixelRatio(pixelRatio());
443 #endif
444                         pm.fill(Qt::transparent);
445                         GuiPainter p(&pm, pixelRatio());
446                         p.do_drawText(-lb, mA, str, rtl, f, ff);
447                         QPixmapCache::insert(key, pm);
448                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
449                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
450                         //      << "  rb=" << rb);
451
452                         // Draw the new cached pixmap.
453                         drawPixmap(x + lb, y - mA, pm);
454                         //rectangle(x-lb, y-mA, w, h, Color_green);
455                 }
456                 return;
457         }
458
459         // don't use the pixmap cache,
460         do_drawText(x, y, str, rtl, f, ff);
461         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
462         //      << " at " << x << "," << y);
463 }
464
465
466 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
467                       double const wordspacing)
468 {
469         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
470 }
471
472
473 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
474                       Color other, size_type const from, size_type const to,
475                       double const wordspacing)
476 {
477         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
478         FontInfo fi = f.fontInfo();
479         bool const rtl = f.isVisibleRightToLeft();
480
481         // dimensions
482         int const ascent = fm.maxAscent();
483         int const height = fm.maxAscent() + fm.maxDescent();
484         int xmin = fm.pos2x(str, from, rtl, wordspacing);
485         int xmax = fm.pos2x(str, to, rtl, wordspacing);
486         if (xmin > xmax)
487                 swap(xmin, xmax);
488
489         // First the part in other color
490         Color const orig = fi.realColor();
491         fi.setPaintColor(other);
492         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
493         setClipRegion(clip);
494         text(x, y, str, fi, rtl, wordspacing);
495
496         // Then the part in normal color
497         // Note that in Qt5, it is not possible to use Qt::UniteClip,
498         // therefore QRegion is used.
499         fi.setPaintColor(orig);
500         QRegion region(viewport());
501         setClipRegion(region - clip);
502         text(x, y, str, fi, rtl, wordspacing);
503         setClipping(false);
504 }
505
506
507 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
508 {
509         if (f.underbar() == FONT_ON)
510                 underline(f, x, y, width);
511         if (f.strikeout() == FONT_ON)
512                 strikeoutLine(f, x, y, width);
513         if (f.uuline() == FONT_ON)
514                 doubleUnderline(f, x, y, width);
515         if (f.uwave() == FONT_ON)
516                 // f.color() doesn't work on some circumstances
517                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
518 }
519
520
521 static int max(int a, int b) { return a > b ? a : b; }
522
523
524 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
525 {
526         if (mouseHover)
527                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
528         else
529                 fillRectangle(x, y, w, h, Color_buttonbg);
530         buttonFrame(x, y, w, h);
531 }
532
533
534 void GuiPainter::buttonFrame(int x, int y, int w, int h)
535 {
536         line(x, y, x, y + h - 1, Color_buttonframe);
537         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
538         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
539         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
540 }
541
542
543 void GuiPainter::rectText(int x, int y, docstring const & str,
544         FontInfo const & font, Color back, Color frame)
545 {
546         int width;
547         int ascent;
548         int descent;
549
550         FontMetrics const & fm = theFontMetrics(font);
551         fm.rectText(str, width, ascent, descent);
552
553         if (back != Color_none)
554                 fillRectangle(x + 1, y - ascent + 1, width - 1,
555                               ascent + descent - 1, back);
556
557         if (frame != Color_none)
558                 rectangle(x, y - ascent, width, ascent + descent, frame);
559
560         text(x + 3, y, str, font);
561 }
562
563
564 void GuiPainter::buttonText(int x, int y, docstring const & str,
565         FontInfo const & font, bool mouseHover)
566 {
567         int width;
568         int ascent;
569         int descent;
570
571         FontMetrics const & fm = theFontMetrics(font);
572         fm.buttonText(str, width, ascent, descent);
573
574         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
575
576         button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
577         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
578 }
579
580
581 int GuiPainter::preeditText(int x, int y, char_type c,
582         FontInfo const & font, preedit_style style)
583 {
584         FontInfo temp_font = font;
585         FontMetrics const & fm = theFontMetrics(font);
586         int ascent = fm.maxAscent();
587         int descent = fm.maxDescent();
588         int height = ascent + descent;
589         int width = fm.width(c);
590
591         switch (style) {
592                 case preedit_default:
593                         // default unselecting mode.
594                         fillRectangle(x, y - height + 1, width, height, Color_background);
595                         dashedUnderline(font, x, y - descent + 1, width);
596                         break;
597                 case preedit_selecting:
598                         // We are in selecting mode: white text on black background.
599                         fillRectangle(x, y - height + 1, width, height, Color_black);
600                         temp_font.setColor(Color_white);
601                         break;
602                 case preedit_cursor:
603                         // The character comes with a cursor.
604                         fillRectangle(x, y - height + 1, width, height, Color_background);
605                         underline(font, x, y - descent + 1, width);
606                         break;
607         }
608         text(x, y - descent + 1, c, temp_font);
609
610         return width;
611 }
612
613
614 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
615                            line_style ls)
616 {
617         FontMetrics const & fm = theFontMetrics(f);
618         int const pos = fm.underlinePos();
619
620         line(x, y + pos, x + width, y + pos,
621              f.realColor(), ls, fm.lineWidth());
622 }
623
624
625 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
626 {
627         FontMetrics const & fm = theFontMetrics(f);
628         int const pos = fm.strikeoutPos();
629
630         line(x, y - pos, x + width, y - pos,
631              f.realColor(), line_solid, fm.lineWidth());
632 }
633
634
635 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
636 {
637         FontMetrics const & fm = theFontMetrics(f);
638         int const pos1 = fm.underlinePos() + fm.lineWidth();
639         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
640
641         line(x, y + pos1, x + width, y + pos1,
642                  f.realColor(), line_solid, fm.lineWidth());
643         line(x, y + pos2, x + width, y + pos2,
644                  f.realColor(), line_solid, fm.lineWidth());
645 }
646
647
648 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
649 {
650         FontMetrics const & fm = theFontMetrics(f);
651
652         int const below = max(fm.maxDescent() / 2, 2);
653         int height = max((fm.maxDescent() / 4) - 1, 1);
654
655         if (height >= 2)
656                 height += below;
657
658         for (int n = 0; n != height; ++n)
659                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
660 }
661
662
663 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
664 {
665         setQPainterPen(computeColor(col));
666         int const step = 2;
667         int const xend = x + width;
668         int height = 1;
669         //FIXME: I am not sure if Antialiasing gives the best effect.
670         //setRenderHint(Antialiasing, true);
671         while (x < xend) {
672                 height = - height;
673                 drawLine(x, y - height, x + step, y + height);
674                 x += step;
675                 drawLine(x, y + height, x + step/2, y + height);
676                 x += step/2;
677         }
678         //setRenderHint(Antialiasing, false);
679 }
680
681 } // namespace frontend
682 } // namespace lyx