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