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