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