]> git.lyx.org Git - features.git/blob - src/frontends/qt4/GuiPainter.cpp
Change the shape of the parbreak separator
[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 = 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::path(int const * xp, int const * yp,
233         int const * c1x, int const * c1y,
234         int const * c2x, int const * c2y,
235         int np,
236         Color col,
237         fill_style fs,
238         line_style ls,
239         int lw)
240 {
241         if (!isDrawingEnabled())
242                 return;
243
244         QPainterPath bpath;
245         // This is the starting point, so its control points are meaningless
246         bpath.moveTo(xp[0], yp[0]);
247
248         for (int i = 1; i < np; ++i) {
249                 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
250                             c2x[i] == xp[i] && c2y[i] == yp[i];
251                 if (line)
252                         bpath.lineTo(xp[i], yp[i]);
253                 else
254                         bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
255         }
256         QColor const color = computeColor(col);
257         setQPainterPen(color, ls, lw);
258         bool const text_is_antialiased = renderHints() & TextAntialiasing;
259         setRenderHint(Antialiasing, text_is_antialiased);
260         drawPath(bpath);
261         if (fs != fill_none)
262                 fillPath(bpath, QBrush(color));
263         setRenderHint(Antialiasing, false);
264 }
265
266
267 void GuiPainter::rectangle(int x, int y, int w, int h,
268         Color col,
269         line_style ls,
270         int lw)
271 {
272         if (!isDrawingEnabled())
273                 return;
274
275         setQPainterPen(computeColor(col), ls, lw);
276         drawRect(x, y, w, h);
277 }
278
279
280 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
281 {
282         if (!isDrawingEnabled())
283                 return;
284
285         fillRect(x, y, w, h, guiApp->colorCache().get(col));
286 }
287
288
289 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
290         int a1, int a2, Color col)
291 {
292         if (!isDrawingEnabled())
293                 return;
294
295         // LyX usings 1/64ths degree, Qt usings 1/16th
296         setQPainterPen(computeColor(col));
297         bool const do_antialiasing = renderHints() & TextAntialiasing;
298         setRenderHint(Antialiasing, do_antialiasing);
299         drawArc(x, y, w, h, a1 / 4, a2 / 4);
300         setRenderHint(Antialiasing, false);
301 }
302
303
304 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
305 {
306         graphics::GuiImage const & qlimage =
307                 static_cast<graphics::GuiImage const &>(i);
308
309         fillRectangle(x, y, w, h, Color_graphicsbg);
310
311         if (!isDrawingEnabled())
312                 return;
313
314         QImage const image = qlimage.image();
315         QRectF const drect = QRectF(x, y, w, h);
316         QRectF const srect = QRectF(0, 0, image.width(), image.height());
317         drawImage(drect, image, srect);
318 }
319
320
321 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
322 {
323         return text(x, y, docstring(1, c), f);
324 }
325
326
327 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
328 {
329         setQPainterPen(computeColor(f.realColor()));
330         if (font() != ff)
331                 setFont(ff);
332
333          /* In LyX, the character direction is forced by the language.
334           * Therefore, we have to signal that fact to Qt.
335           */
336 #if 1
337         /* Use unicode override characters to enforce drawing direction
338          * Source: http://www.iamcal.com/understanding-bidirectional-text/
339          */
340         if (rtl)
341                 // Right-to-left override: forces to draw text right-to-left
342                 str = QChar(0x202E) + str;
343         else
344                 // Left-to-right override: forces to draw text left-to-right
345                 str =  QChar(0x202D) + str;
346         drawText(x, y, str);
347 #else
348         /* This looks like a cleaner solution, but it has drawbacks
349          * - does not work reliably (Mac OS X, ...)
350          * - it is not really documented
351          * Keep it here for now, in case it can be helpful
352          */
353         //This is much stronger than setLayoutDirection.
354         int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
355         drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
356                  flag | Qt::TextDontClip,
357                  str);
358 #endif
359 }
360
361
362 int GuiPainter::text(int x, int y, docstring const & s,
363                      FontInfo const & f, bool const rtl,
364                      double const wordspacing)
365 {
366         //LYXERR0("text: x=" << x << ", s=" << s);
367         if (s.empty())
368                 return 0;
369
370         /* Caution: The following ucs4 to QString conversions work for symbol fonts
371         only because they are no real conversions but simple casts in reality.
372         When we want to draw a symbol or calculate the metrics we pass the position
373         of the symbol in the font (as given in lib/symbols) as a char_type to the
374         frontend. This is just wrong, because the symbol is no UCS4 character at
375         all. You can think of this number as the code point of the symbol in a
376         custom symbol encoding. It works because this char_type is lateron again
377         interpreted as a position in the font again.
378         The correct solution would be to have extra functions for symbols, but that
379         would require to duplicate a lot of frontend and mathed support code.
380         */
381         QString str = toqstr(s);
382
383 #if 0
384         // HACK: QT3 refuses to show single compose characters
385         //       Still needed with Qt4?
386         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
387                 str = ' ' + str;
388 #endif
389
390         QFont ff = getFont(f);
391         ff.setWordSpacing(wordspacing);
392         GuiFontMetrics const & fm = getFontMetrics(f);
393
394         // Here we use the font width cache instead of
395         //   textwidth = fontMetrics().width(str);
396         // because the above is awfully expensive on MacOSX
397         // Note that we have to take in account space stretching (word spacing)
398         int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
399
400         if (!isDrawingEnabled())
401                 return textwidth;
402
403         textDecoration(f, x, y, textwidth);
404
405         if (use_pixmap_cache_) {
406                 QPixmap pm;
407                 QString key = generateStringSignature(str, f);
408
409                 // Warning: Left bearing is in general negative! Only the case
410                 // where left bearing is negative is of interest WRT the
411                 // pixmap width and the text x-position.
412                 // Only the left bearing of the first character is important
413                 // as we always write from left to right, even for
414                 // right-to-left languages.
415                 // FIXME: this is probably broken for RTL now that we draw full strings.
416                 // Morover the first/last element is possibly not the right one since the glyph may have changed.
417                 int const lb = min(fm.lbearing(s[0]), 0);
418                 int const mA = fm.maxAscent();
419                 if (QPixmapCache::find(key, pm)) {
420                         // Draw the cached pixmap.
421                         drawPixmap(x + lb, y - mA, pm);
422                         return textwidth;
423                 }
424
425                 // Only the right bearing of the last character is
426                 // important as we always write from left to right,
427                 // even for right-to-left languages.
428                 int const rb = fm.rbearing(s[s.size()-1]);
429                 int const w = textwidth + rb - lb;
430                 int const mD = fm.maxDescent();
431                 int const h = mA + mD;
432                 if (w > 0 && h > 0) {
433                         pm = QPixmap(static_cast<int>(pixelRatio() * w),
434                                                  static_cast<int>(pixelRatio() * h));
435 #if QT_VERSION >= 0x050000
436                         pm.setDevicePixelRatio(pixelRatio());
437 #endif
438                         pm.fill(Qt::transparent);
439                         GuiPainter p(&pm, pixelRatio());
440                         p.do_drawText(-lb, mA, str, rtl, f, ff);
441                         QPixmapCache::insert(key, pm);
442                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
443                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
444                         //      << "  rb=" << rb);
445
446                         // Draw the new cached pixmap.
447                         drawPixmap(x + lb, y - mA, pm);
448                         //rectangle(x-lb, y-mA, w, h, Color_green);
449                 }
450                 return textwidth;
451         }
452
453         // don't use the pixmap cache,
454         do_drawText(x, y, str, rtl, f, ff);
455         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
456         //      << " at " << x << "," << y);
457         return textwidth;
458 }
459
460
461 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
462                      double const wordspacing)
463 {
464         return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
465 }
466
467
468 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
469                      Color other, size_type const from, size_type const to,
470                      double const wordspacing)
471 {
472         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
473         FontInfo fi = f.fontInfo();
474         bool const rtl = f.isVisibleRightToLeft();
475
476         // dimensions
477         int const ascent = fm.maxAscent();
478         int const height = fm.maxAscent() + fm.maxDescent();
479         int xmin = fm.pos2x(str, from, rtl, wordspacing);
480         int xmax = fm.pos2x(str, to, rtl, wordspacing);
481         if (xmin > xmax)
482                 swap(xmin, xmax);
483
484         // First the part in other color
485         Color const orig = fi.realColor();
486         fi.setPaintColor(other);
487         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
488         setClipRegion(clip);
489         int const textwidth = text(x, y, str, fi, rtl, wordspacing);
490
491         // Then the part in normal color
492         // Note that in Qt5, it is not possible to use Qt::UniteClip,
493         // therefore QRegion is used.
494         fi.setPaintColor(orig);
495         QRegion region(viewport());
496         setClipRegion(region - clip);
497         text(x, y, str, fi, rtl, wordspacing);
498         setClipping(false);
499
500         return textwidth;
501 }
502
503
504 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
505 {
506         if (f.underbar() == FONT_ON)
507                 underline(f, x, y, width);
508         if (f.strikeout() == FONT_ON)
509                 strikeoutLine(f, x, y, width);
510         if (f.uuline() == FONT_ON)
511                 doubleUnderline(f, x, y, width);
512         if (f.uwave() == FONT_ON)
513                 // f.color() doesn't work on some circumstances
514                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
515 }
516
517
518 static int max(int a, int b) { return a > b ? a : b; }
519
520
521 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
522 {
523         if (mouseHover)
524                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
525         else
526                 fillRectangle(x, y, w, h, Color_buttonbg);
527         buttonFrame(x, y, w, h);
528 }
529
530
531 void GuiPainter::buttonFrame(int x, int y, int w, int h)
532 {
533         line(x, y, x, y + h - 1, Color_buttonframe);
534         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
535         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
536         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
537 }
538
539
540 void GuiPainter::rectText(int x, int y, docstring const & str,
541         FontInfo const & font, Color back, Color frame)
542 {
543         int width;
544         int ascent;
545         int descent;
546
547         FontMetrics const & fm = theFontMetrics(font);
548         fm.rectText(str, width, ascent, descent);
549
550         if (back != Color_none)
551                 fillRectangle(x + 1, y - ascent + 1, width - 1,
552                               ascent + descent - 1, back);
553
554         if (frame != Color_none)
555                 rectangle(x, y - ascent, width, ascent + descent, frame);
556
557         text(x + 3, y, str, font);
558 }
559
560
561 void GuiPainter::buttonText(int x, int y, docstring const & str,
562         FontInfo const & font, bool mouseHover)
563 {
564         int width;
565         int ascent;
566         int descent;
567
568         FontMetrics const & fm = theFontMetrics(font);
569         fm.buttonText(str, width, ascent, descent);
570
571         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
572
573         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
574         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
575 }
576
577
578 int GuiPainter::preeditText(int x, int y, char_type c,
579         FontInfo const & font, preedit_style style)
580 {
581         FontInfo temp_font = font;
582         FontMetrics const & fm = theFontMetrics(font);
583         int ascent = fm.maxAscent();
584         int descent = fm.maxDescent();
585         int height = ascent + descent;
586         int width = fm.width(c);
587
588         switch (style) {
589                 case preedit_default:
590                         // default unselecting mode.
591                         fillRectangle(x, y - height + 1, width, height, Color_background);
592                         dashedUnderline(font, x, y - descent + 1, width);
593                         break;
594                 case preedit_selecting:
595                         // We are in selecting mode: white text on black background.
596                         fillRectangle(x, y - height + 1, width, height, Color_black);
597                         temp_font.setColor(Color_white);
598                         break;
599                 case preedit_cursor:
600                         // The character comes with a cursor.
601                         fillRectangle(x, y - height + 1, width, height, Color_background);
602                         underline(font, x, y - descent + 1, width);
603                         break;
604         }
605         text(x, y - descent + 1, c, temp_font);
606
607         return width;
608 }
609
610
611 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
612                            line_style ls)
613 {
614         FontMetrics const & fm = theFontMetrics(f);
615         int const pos = fm.underlinePos();
616
617         line(x, y + pos, x + width, y + pos,
618              f.realColor(), ls, fm.lineWidth());
619 }
620
621
622 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
623 {
624         FontMetrics const & fm = theFontMetrics(f);
625         int const pos = fm.strikeoutPos();
626
627         line(x, y - pos, x + width, y - pos,
628              f.realColor(), line_solid, fm.lineWidth());
629 }
630
631
632 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
633 {
634         FontMetrics const & fm = theFontMetrics(f);
635         int const pos1 = fm.underlinePos() + fm.lineWidth();
636         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
637
638         line(x, y + pos1, x + width, y + pos1,
639                  f.realColor(), line_solid, fm.lineWidth());
640         line(x, y + pos2, x + width, y + pos2,
641                  f.realColor(), line_solid, fm.lineWidth());
642 }
643
644
645 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
646 {
647         FontMetrics const & fm = theFontMetrics(f);
648
649         int const below = max(fm.maxDescent() / 2, 2);
650         int height = max((fm.maxDescent() / 4) - 1, 1);
651
652         if (height >= 2)
653                 height += below;
654
655         for (int n = 0; n != height; ++n)
656                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
657 }
658
659
660 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
661 {
662         setQPainterPen(computeColor(col));
663         int const step = 2;
664         int const xend = x + width;
665         int height = 1;
666         //FIXME: I am not sure if Antialiasing gives the best effect.
667         //setRenderHint(Antialiasing, true);
668         while (x < xend) {
669                 height = - height;
670                 drawLine(x, y - height, x + step, y + height);
671                 x += step;
672                 drawLine(x, y + height, x + step/2, y + height);
673                 x += step/2;
674         }
675         //setRenderHint(Antialiasing, false);
676 }
677
678 } // namespace frontend
679 } // namespace lyx