]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Audit all the LASSERT calls, and try to do something sensible at
[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         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                 textDecoration(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         textDecoration(f, x, y, textwidth);
347
348         if (!isDrawingEnabled())
349                 return textwidth;
350
351         // Qt4 does not display a glyph whose codepoint is the
352         // same as that of a soft-hyphen (0x00ad), unless it
353         // occurs at a line-break. As a kludge, we force Qt to
354         // render this glyph using a one-column line.
355         if (s.size() == 1 && str[0].unicode() == 0x00ad) {
356                 setQPainterPen(computeColor(f.realColor()));
357                 QTextLayout adsymbol(str);
358                 adsymbol.setFont(ff);
359                 adsymbol.beginLayout();
360                 QTextLine line = adsymbol.createLine();
361                 line.setNumColumns(1);
362                 line.setPosition(QPointF(0, -line.ascent()));
363                 adsymbol.endLayout();
364                 line.draw(this, QPointF(x, y));
365                 return textwidth;
366         }
367
368         if (use_pixmap_cache_) {
369                 QPixmap pm;
370                 QString key = generateStringSignature(str, f);
371
372                 // Warning: Left bearing is in general negative! Only the case
373                 // where left bearing is negative is of interest WRT the
374                 // pixmap width and the text x-position.
375                 // Only the left bearing of the first character is important
376                 // as we always write from left to right, even for
377                 // right-to-left languages.
378                 int const lb = min(fm.lbearing(s[0]), 0);
379                 int const mA = fm.maxAscent();
380                 if (QPixmapCache::find(key, pm)) {
381                         // Draw the cached pixmap.
382                         drawPixmap(x + lb, y - mA, pm);
383                         return textwidth;
384                 }
385
386                 // Only the right bearing of the last character is
387                 // important as we always write from left to right,
388                 // even for right-to-left languages.
389                 int const rb = fm.rbearing(s[s.size()-1]);
390                 int const w = textwidth + rb - lb;
391                 int const mD = fm.maxDescent();
392                 int const h = mA + mD;
393                 if (w > 0 && h > 0) {
394                         pm = QPixmap(w, h);
395                         pm.fill(Qt::transparent);
396                         GuiPainter p(&pm);
397                         p.setQPainterPen(computeColor(f.realColor()));
398                         if (p.font() != ff)
399                                 p.setFont(ff);
400                         // We need to draw the text as LTR as we use our own bidi code.
401                         p.setLayoutDirection(Qt::LeftToRight);
402                         p.drawText(-lb, mA, str);
403                         QPixmapCache::insert(key, pm);
404                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
405                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
406                         //      << "  rb=" << rb);
407
408                         // Draw the new cached pixmap.
409                         drawPixmap(x + lb, y - mA, pm);
410
411                         return textwidth;
412                 }
413         }
414
415         // don't use the pixmap cache,
416         // draw directly onto the painting device
417         setQPainterPen(computeColor(f.realColor()));
418         if (font() != ff)
419                 setFont(ff);
420         // We need to draw the text as LTR as we use our own bidi code.
421         QPainter::setLayoutDirection(Qt::LeftToRight);
422         drawText(x, y, str);
423         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
424         //      << " at " << x << "," << y);
425         return textwidth;
426 }
427
428
429 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
430 {
431         if (f.underbar() == FONT_ON)
432                 underline(f, x, y, width);
433         if (f.strikeout() == FONT_ON)
434                 strikeoutLine(f, x, y, width);
435         if (f.uuline() == FONT_ON)
436                 doubleUnderline(f, x, y, width);
437         if (f.uwave() == FONT_ON)
438                 // f.color() doesn't work on some circumstances
439                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
440 }
441
442
443 static int max(int a, int b) { return a > b ? a : b; }
444
445
446 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
447 {
448         if (mouseHover)
449                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
450         else
451                 fillRectangle(x, y, w, h, Color_buttonbg);
452         buttonFrame(x, y, w, h);
453 }
454
455
456 void GuiPainter::buttonFrame(int x, int y, int w, int h)
457 {
458         line(x, y, x, y + h - 1, Color_buttonframe);
459         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
460         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
461         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
462 }
463
464
465 void GuiPainter::rectText(int x, int y, docstring const & str,
466         FontInfo const & font, Color back, Color frame)
467 {
468         int width;
469         int ascent;
470         int descent;
471
472         FontMetrics const & fm = theFontMetrics(font);
473         fm.rectText(str, width, ascent, descent);
474
475         if (back != Color_none)
476                 fillRectangle(x + 1, y - ascent + 1, width - 1,
477                               ascent + descent - 1, back);
478
479         if (frame != Color_none)
480                 rectangle(x, y - ascent, width, ascent + descent, frame);
481
482         text(x + 3, y, str, font);
483 }
484
485
486 void GuiPainter::buttonText(int x, int y, docstring const & str,
487         FontInfo const & font, bool mouseHover)
488 {
489         int width;
490         int ascent;
491         int descent;
492
493         FontMetrics const & fm = theFontMetrics(font);
494         fm.buttonText(str, width, ascent, descent);
495
496         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
497
498         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
499         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
500 }
501
502
503 int GuiPainter::preeditText(int x, int y, char_type c,
504         FontInfo const & font, preedit_style style)
505 {
506         FontInfo temp_font = font;
507         FontMetrics const & fm = theFontMetrics(font);
508         int ascent = fm.maxAscent();
509         int descent = fm.maxDescent();
510         int height = ascent + descent;
511         int width = fm.width(c);
512
513         switch (style) {
514                 case preedit_default:
515                         // default unselecting mode.
516                         fillRectangle(x, y - height + 1, width, height, Color_background);
517                         dashedUnderline(font, x, y - descent + 1, width);
518                         break;
519                 case preedit_selecting:
520                         // We are in selecting mode: white text on black background.
521                         fillRectangle(x, y - height + 1, width, height, Color_black);
522                         temp_font.setColor(Color_white);
523                         break;
524                 case preedit_cursor:
525                         // The character comes with a cursor.
526                         fillRectangle(x, y - height + 1, width, height, Color_background);
527                         underline(font, x, y - descent + 1, width);
528                         break;
529         }
530         text(x, y - descent + 1, c, temp_font);
531
532         return width;
533 }
534
535
536 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
537 {
538         FontMetrics const & fm = theFontMetrics(f);
539
540         int const below = max(fm.maxDescent() / 2, 2);
541
542         line(x, y + below, x + width, y + below, f.realColor());
543         line(x, y + below - 2, x + width, y + below - 2, f.realColor());
544 }
545
546
547 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
548 {
549         FontMetrics const & fm = theFontMetrics(f);
550
551         int const below = max(fm.maxDescent() / 2, 2);
552         int const height = max((fm.maxDescent() / 4) - 1, 1);
553
554         if (height < 2)
555                 line(x, y + below, x + width, y + below, f.realColor());
556         else
557                 fillRectangle(x, y + below, width, below + height, f.realColor());
558 }
559
560
561 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
562 {
563         FontMetrics const & fm = theFontMetrics(f);
564
565         int const middle = max((fm.maxHeight() / 4), 1);
566         int const height =  middle/3;
567
568         if (height < 2)
569                 line(x, y - middle, x + width, y - middle, f.realColor());
570         else
571                 fillRectangle(x, y - middle, width, height, f.realColor());
572 }
573
574
575 void GuiPainter::dashedUnderline(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         int height = max((fm.maxDescent() / 4) - 1, 1);
581
582         if (height >= 2)
583                 height += below;
584
585         for (int n = 0; n != height; ++n)
586                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
587 }
588
589
590 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
591 {
592         setQPainterPen(computeColor(col));
593         int const step = 2;
594         int const xend = x + width;
595         int height = 1;
596         //FIXME: I am not sure if Antialiasing gives the best effect.
597         //setRenderHint(Antialiasing, true);
598         while (x < xend) {
599                 height = - height;
600                 drawLine(x, y - height, x + step, y + height);
601                 x += step;
602                 drawLine(x, y + height, x + step/2, y + height);
603                 x += step/2;
604         }
605         //setRenderHint(Antialiasing, false);
606 }
607
608 } // namespace frontend
609 } // namespace lyx