]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
bda52f189049d209e71c050e24665e8db94da29a
[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                 QPixmap pm;
371                 QString key = generateStringSignature(str, f);
372
373                 // Warning: Left bearing is in general negative! Only the case
374                 // where left bearing is negative is of interest WRT the
375                 // pixmap width and the text x-position.
376                 // Only the left bearing of the first character is important
377                 // as we always write from left to right, even for
378                 // right-to-left languages.
379                 int const lb = min(fm.lbearing(s[0]), 0);
380                 int const mA = fm.maxAscent();
381                 if (QPixmapCache::find(key, pm)) {
382                         // Draw the cached pixmap.
383                         drawPixmap(x + lb, y - mA, pm);
384                         return textwidth;
385                 }
386
387                 // Only the right bearing of the last character is
388                 // important as we always write from left to right,
389                 // even for right-to-left languages.
390                 int const rb = fm.rbearing(s[s.size()-1]);
391                 int const w = textwidth + rb - lb;
392                 int const mD = fm.maxDescent();
393                 int const h = mA + mD;
394                 if (w > 0 && h > 0) {
395                         pm = QPixmap(w, h);
396                         pm.fill(Qt::transparent);
397                         GuiPainter p(&pm);
398                         p.setQPainterPen(computeColor(f.realColor()));
399                         if (p.font() != ff)
400                                 p.setFont(ff);
401                         // We need to draw the text as LTR as we use our own bidi code.
402                         p.setLayoutDirection(Qt::LeftToRight);
403                         p.drawText(-lb, mA, str);
404                         QPixmapCache::insert(key, pm);
405                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
406                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
407                         //      << "  rb=" << rb);
408                         return textwidth;
409                 }
410         }
411
412         // don't use the pixmap cache,
413         // draw directly onto the painting device
414         setQPainterPen(computeColor(f.realColor()));
415         if (font() != ff)
416                 setFont(ff);
417         // We need to draw the text as LTR as we use our own bidi code.
418         QPainter::setLayoutDirection(Qt::LeftToRight);
419         drawText(x, y, str);
420         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
421         //      << " at " << x << "," << y);
422         return textwidth;
423 }
424
425
426 static int max(int a, int b) { return a > b ? a : b; }
427
428
429 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
430 {
431         if (mouseHover)
432                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
433         else
434                 fillRectangle(x, y, w, h, Color_buttonbg);
435         buttonFrame(x, y, w, h);
436 }
437
438
439 void GuiPainter::buttonFrame(int x, int y, int w, int h)
440 {
441         line(x, y, x, y + h - 1, Color_buttonframe);
442         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
443         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
444         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
445 }
446
447
448 void GuiPainter::rectText(int x, int y, docstring const & str,
449         FontInfo const & font, ColorCode back, ColorCode frame)
450 {
451         int width;
452         int ascent;
453         int descent;
454
455         FontMetrics const & fm = theFontMetrics(font);
456         fm.rectText(str, width, ascent, descent);
457
458         if (back != Color_none)
459                 fillRectangle(x + 1, y - ascent + 1, width - 1,
460                               ascent + descent - 1, back);
461
462         if (frame != Color_none)
463                 rectangle(x, y - ascent, width, ascent + descent, frame);
464
465         text(x + 3, y, str, font);
466 }
467
468
469 void GuiPainter::buttonText(int x, int y, docstring const & str,
470         FontInfo const & font, bool mouseHover)
471 {
472         int width;
473         int ascent;
474         int descent;
475
476         FontMetrics const & fm = theFontMetrics(font);
477         fm.buttonText(str, width, ascent, descent);
478
479         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
480
481         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
482         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
483 }
484
485
486 int GuiPainter::preeditText(int x, int y, char_type c,
487         FontInfo const & font, preedit_style style)
488 {
489         FontInfo temp_font = font;
490         FontMetrics const & fm = theFontMetrics(font);
491         int ascent = fm.maxAscent();
492         int descent = fm.maxDescent();
493         int height = ascent + descent;
494         int width = fm.width(c);
495
496         switch (style) {
497                 case preedit_default:
498                         // default unselecting mode.
499                         fillRectangle(x, y - height + 1, width, height, Color_background);
500                         dashedUnderline(font, x, y - descent + 1, width);
501                         break;
502                 case preedit_selecting:
503                         // We are in selecting mode: white text on black background.
504                         fillRectangle(x, y - height + 1, width, height, Color_black);
505                         temp_font.setColor(Color_white);
506                         break;
507                 case preedit_cursor:
508                         // The character comes with a cursor.
509                         fillRectangle(x, y - height + 1, width, height, Color_background);
510                         underline(font, x, y - descent + 1, width);
511                         break;
512         }
513         text(x, y - descent + 1, c, temp_font);
514
515         return width;
516 }
517
518
519 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
520 {
521         FontMetrics const & fm = theFontMetrics(f);
522
523         int const below = max(fm.maxDescent() / 2, 2);
524         int const height = max((fm.maxDescent() / 4) - 1, 1);
525
526         if (height < 2)
527                 line(x, y + below, x + width, y + below, f.realColor());
528         else
529                 fillRectangle(x, y + below, width, below + height, f.realColor());
530 }
531
532
533 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
534 {
535         FontMetrics const & fm = theFontMetrics(f);
536
537         int const below = max(fm.maxDescent() / 2, 2);
538         int height = max((fm.maxDescent() / 4) - 1, 1);
539
540         if (height >= 2)
541                 height += below;
542
543         for (int n = 0; n != height; ++n)
544                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
545 }
546
547 } // namespace frontend
548 } // namespace lyx