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