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