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