]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
This is the result of an audit of all static variables, looking
[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::smallCapsText(int x, int y,
280         QString const & s, FontInfo const & f)
281 {
282         FontInfo smallfont(f);
283         smallfont.decSize().decSize().setShape(UP_SHAPE);
284
285         QFont const & qfont = getFont(f);
286         QFont const & qsmallfont = getFont(smallfont);
287
288         setQPainterPen(computeColor(f.realColor()));
289         int textwidth = 0;
290         size_t const ls = s.length();
291         for (unsigned int i = 0; i < ls; ++i) {
292                 QChar const c = s[i].toUpper();
293                 if (c != s.at(i)) {
294                         setFont(qsmallfont);
295                 } else {
296                         setFont(qfont);
297                 }
298                 if (isDrawingEnabled())
299                         drawText(x + textwidth, y, c);
300                 textwidth += fontMetrics().width(c);
301         }
302         return textwidth;
303 }
304
305
306 int GuiPainter::text(int x, int y, docstring const & s,
307                 FontInfo const & f)
308 {
309         if (s.empty())
310                 return 0;
311
312         /* Caution: The following ucs4 to QString conversions work for symbol fonts
313         only because they are no real conversions but simple casts in reality.
314         When we want to draw a symbol or calculate the metrics we pass the position
315         of the symbol in the font (as given in lib/symbols) as a char_type to the
316         frontend. This is just wrong, because the symbol is no UCS4 character at
317         all. You can think of this number as the code point of the symbol in a
318         custom symbol encoding. It works because this char_type is lateron again
319         interpreted as a position in the font again.
320         The correct solution would be to have extra functions for symbols, but that
321         would require to duplicate a lot of frontend and mathed support code.
322         */
323         QString str = toqstr(s);
324
325 #if 0
326         // HACK: QT3 refuses to show single compose characters
327         //       Still needed with Qt4?
328         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
329                 str = ' ' + str;
330 #endif
331
332         QFont const & ff = getFont(f); 
333         GuiFontMetrics const & fm = getFontMetrics(f); 
334
335         int textwidth;
336
337         if (f.realShape() == SMALLCAPS_SHAPE) {
338                 textwidth = smallCapsText(x, y, str, f);
339                 textDecoration(f, x, y, textwidth);
340                 return textwidth;
341         }
342
343         // Here we use the font width cache instead of
344         //   textwidth = fontMetrics().width(str);
345         // because the above is awfully expensive on MacOSX
346         textwidth = fm.width(s);
347         textDecoration(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         // This is needed for some math glyphs.
357         // FIXME In texted, this behaves differently depending
358         // on lyxrc.force_paint_single_char status.
359         // Should the soft hyphen char be displayed at all?
360         // I don't think so (i.e., Qt is correct as far as
361         // texted is concerned). /spitz
362         if (s.size() == 1 && str[0].unicode() == 0x00ad) {
363                 setQPainterPen(computeColor(f.realColor()));
364                 QTextLayout adsymbol(str);
365                 adsymbol.setFont(ff);
366                 adsymbol.beginLayout();
367                 QTextLine line = adsymbol.createLine();
368                 line.setNumColumns(1);
369                 line.setPosition(QPointF(0, -line.ascent()));
370                 adsymbol.endLayout();
371                 line.draw(this, QPointF(x, y));
372                 return textwidth;
373         }
374
375         if (use_pixmap_cache_) {
376                 QPixmap pm;
377                 QString key = generateStringSignature(str, f);
378
379                 // Warning: Left bearing is in general negative! Only the case
380                 // where left bearing is negative is of interest WRT the
381                 // pixmap width and the text x-position.
382                 // Only the left bearing of the first character is important
383                 // as we always write from left to right, even for
384                 // right-to-left languages.
385                 int const lb = min(fm.lbearing(s[0]), 0);
386                 int const mA = fm.maxAscent();
387                 if (QPixmapCache::find(key, pm)) {
388                         // Draw the cached pixmap.
389                         drawPixmap(x + lb, y - mA, pm);
390                         return textwidth;
391                 }
392
393                 // Only the right bearing of the last character is
394                 // important as we always write from left to right,
395                 // even for right-to-left languages.
396                 int const rb = fm.rbearing(s[s.size()-1]);
397                 int const w = textwidth + rb - lb;
398                 int const mD = fm.maxDescent();
399                 int const h = mA + mD;
400                 if (w > 0 && h > 0) {
401                         pm = QPixmap(w, h);
402                         pm.fill(Qt::transparent);
403                         GuiPainter p(&pm);
404                         p.setQPainterPen(computeColor(f.realColor()));
405                         if (p.font() != ff)
406                                 p.setFont(ff);
407                         // We need to draw the text as LTR as we use our own bidi code.
408                         p.setLayoutDirection(Qt::LeftToRight);
409                         p.drawText(-lb, mA, str);
410                         QPixmapCache::insert(key, pm);
411                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
412                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
413                         //      << "  rb=" << rb);
414
415                         // Draw the new cached pixmap.
416                         drawPixmap(x + lb, y - mA, pm);
417
418                         return textwidth;
419                 }
420         }
421
422         // don't use the pixmap cache,
423         // draw directly onto the painting device
424         setQPainterPen(computeColor(f.realColor()));
425         if (font() != ff)
426                 setFont(ff);
427         // We need to draw the text as LTR as we use our own bidi code.
428         QPainter::setLayoutDirection(Qt::LeftToRight);
429         drawText(x, y, str);
430         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
431         //      << " at " << x << "," << y);
432         return textwidth;
433 }
434
435
436 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
437 {
438         if (f.underbar() == FONT_ON)
439                 underline(f, x, y, width);
440         if (f.strikeout() == FONT_ON)
441                 strikeoutLine(f, x, y, width);
442         if (f.uuline() == FONT_ON)
443                 doubleUnderline(f, x, y, width);
444         if (f.uwave() == FONT_ON)
445                 // f.color() doesn't work on some circumstances
446                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
447 }
448
449
450 static int max(int a, int b) { return a > b ? a : b; }
451
452
453 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
454 {
455         if (mouseHover)
456                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
457         else
458                 fillRectangle(x, y, w, h, Color_buttonbg);
459         buttonFrame(x, y, w, h);
460 }
461
462
463 void GuiPainter::buttonFrame(int x, int y, int w, int h)
464 {
465         line(x, y, x, y + h - 1, Color_buttonframe);
466         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
467         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
468         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
469 }
470
471
472 void GuiPainter::rectText(int x, int y, docstring const & str,
473         FontInfo const & font, Color back, Color frame)
474 {
475         int width;
476         int ascent;
477         int descent;
478
479         FontMetrics const & fm = theFontMetrics(font);
480         fm.rectText(str, width, ascent, descent);
481
482         if (back != Color_none)
483                 fillRectangle(x + 1, y - ascent + 1, width - 1,
484                               ascent + descent - 1, back);
485
486         if (frame != Color_none)
487                 rectangle(x, y - ascent, width, ascent + descent, frame);
488
489         text(x + 3, y, str, font);
490 }
491
492
493 void GuiPainter::buttonText(int x, int y, docstring const & str,
494         FontInfo const & font, bool mouseHover)
495 {
496         int width;
497         int ascent;
498         int descent;
499
500         FontMetrics const & fm = theFontMetrics(font);
501         fm.buttonText(str, width, ascent, descent);
502
503         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
504
505         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
506         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
507 }
508
509
510 int GuiPainter::preeditText(int x, int y, char_type c,
511         FontInfo const & font, preedit_style style)
512 {
513         FontInfo temp_font = font;
514         FontMetrics const & fm = theFontMetrics(font);
515         int ascent = fm.maxAscent();
516         int descent = fm.maxDescent();
517         int height = ascent + descent;
518         int width = fm.width(c);
519
520         switch (style) {
521                 case preedit_default:
522                         // default unselecting mode.
523                         fillRectangle(x, y - height + 1, width, height, Color_background);
524                         dashedUnderline(font, x, y - descent + 1, width);
525                         break;
526                 case preedit_selecting:
527                         // We are in selecting mode: white text on black background.
528                         fillRectangle(x, y - height + 1, width, height, Color_black);
529                         temp_font.setColor(Color_white);
530                         break;
531                 case preedit_cursor:
532                         // The character comes with a cursor.
533                         fillRectangle(x, y - height + 1, width, height, Color_background);
534                         underline(font, x, y - descent + 1, width);
535                         break;
536         }
537         text(x, y - descent + 1, c, temp_font);
538
539         return width;
540 }
541
542
543 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
544 {
545         FontMetrics const & fm = theFontMetrics(f);
546
547         int const below = max(fm.maxDescent() / 2, 2);
548
549         line(x, y + below, x + width, y + below, f.realColor());
550         line(x, y + below - 2, x + width, y + below - 2, f.realColor());
551 }
552
553
554 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
555 {
556         FontMetrics const & fm = theFontMetrics(f);
557
558         int const below = max(fm.maxDescent() / 2, 2);
559         int const height = max((fm.maxDescent() / 4) - 1, 1);
560
561         if (height < 2)
562                 line(x, y + below, x + width, y + below, f.realColor());
563         else
564                 fillRectangle(x, y + below, width, below + height, f.realColor());
565 }
566
567
568 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
569 {
570         FontMetrics const & fm = theFontMetrics(f);
571
572         int const middle = max((fm.maxHeight() / 4), 1);
573         int const height =  middle/3;
574
575         if (height < 2)
576                 line(x, y - middle, x + width, y - middle, f.realColor());
577         else
578                 fillRectangle(x, y - middle, width, height, f.realColor());
579 }
580
581
582 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
583 {
584         FontMetrics const & fm = theFontMetrics(f);
585
586         int const below = max(fm.maxDescent() / 2, 2);
587         int height = max((fm.maxDescent() / 4) - 1, 1);
588
589         if (height >= 2)
590                 height += below;
591
592         for (int n = 0; n != height; ++n)
593                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
594 }
595
596
597 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
598 {
599         setQPainterPen(computeColor(col));
600         int const step = 2;
601         int const xend = x + width;
602         int height = 1;
603         //FIXME: I am not sure if Antialiasing gives the best effect.
604         //setRenderHint(Antialiasing, true);
605         while (x < xend) {
606                 height = - height;
607                 drawLine(x, y - height, x + step, y + height);
608                 x += step;
609                 drawLine(x, y + height, x + step/2, y + height);
610                 x += step/2;
611         }
612         //setRenderHint(Antialiasing, false);
613 }
614
615 } // namespace frontend
616 } // namespace lyx