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