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