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