]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
fd2f96b39db0ebd17389dde96d03e0d3234ffd16
[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(Color 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(Color const & min, Color 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, Color 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         Color 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         Color 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         Color 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, Color 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, Color 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
409                         // Draw the new cached pixmap.
410                         drawPixmap(x + lb, y - mA, pm);
411
412                         return textwidth;
413                 }
414         }
415
416         // don't use the pixmap cache,
417         // draw directly onto the painting device
418         setQPainterPen(computeColor(f.realColor()));
419         if (font() != ff)
420                 setFont(ff);
421         // We need to draw the text as LTR as we use our own bidi code.
422         QPainter::setLayoutDirection(Qt::LeftToRight);
423         drawText(x, y, str);
424         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
425         //      << " at " << x << "," << y);
426         return textwidth;
427 }
428
429
430 static int max(int a, int b) { return a > b ? a : b; }
431
432
433 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
434 {
435         if (mouseHover)
436                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
437         else
438                 fillRectangle(x, y, w, h, Color_buttonbg);
439         buttonFrame(x, y, w, h);
440 }
441
442
443 void GuiPainter::buttonFrame(int x, int y, int w, int h)
444 {
445         line(x, y, x, y + h - 1, Color_buttonframe);
446         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
447         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
448         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
449 }
450
451
452 void GuiPainter::rectText(int x, int y, docstring const & str,
453         FontInfo const & font, Color back, Color frame)
454 {
455         int width;
456         int ascent;
457         int descent;
458
459         FontMetrics const & fm = theFontMetrics(font);
460         fm.rectText(str, width, ascent, descent);
461
462         if (back != Color_none)
463                 fillRectangle(x + 1, y - ascent + 1, width - 1,
464                               ascent + descent - 1, back);
465
466         if (frame != Color_none)
467                 rectangle(x, y - ascent, width, ascent + descent, frame);
468
469         text(x + 3, y, str, font);
470 }
471
472
473 void GuiPainter::buttonText(int x, int y, docstring const & str,
474         FontInfo const & font, bool mouseHover)
475 {
476         int width;
477         int ascent;
478         int descent;
479
480         FontMetrics const & fm = theFontMetrics(font);
481         fm.buttonText(str, width, ascent, descent);
482
483         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
484
485         button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
486         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
487 }
488
489
490 int GuiPainter::preeditText(int x, int y, char_type c,
491         FontInfo const & font, preedit_style style)
492 {
493         FontInfo temp_font = font;
494         FontMetrics const & fm = theFontMetrics(font);
495         int ascent = fm.maxAscent();
496         int descent = fm.maxDescent();
497         int height = ascent + descent;
498         int width = fm.width(c);
499
500         switch (style) {
501                 case preedit_default:
502                         // default unselecting mode.
503                         fillRectangle(x, y - height + 1, width, height, Color_background);
504                         dashedUnderline(font, x, y - descent + 1, width);
505                         break;
506                 case preedit_selecting:
507                         // We are in selecting mode: white text on black background.
508                         fillRectangle(x, y - height + 1, width, height, Color_black);
509                         temp_font.setColor(Color_white);
510                         break;
511                 case preedit_cursor:
512                         // The character comes with a cursor.
513                         fillRectangle(x, y - height + 1, width, height, Color_background);
514                         underline(font, x, y - descent + 1, width);
515                         break;
516         }
517         text(x, y - descent + 1, c, temp_font);
518
519         return width;
520 }
521
522
523 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
524 {
525         FontMetrics const & fm = theFontMetrics(f);
526
527         int const below = max(fm.maxDescent() / 2, 2);
528         int const height = max((fm.maxDescent() / 4) - 1, 1);
529
530         if (height < 2)
531                 line(x, y + below, x + width, y + below, f.realColor());
532         else
533                 fillRectangle(x, y + below, width, below + height, f.realColor());
534 }
535
536
537 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
538 {
539         FontMetrics const & fm = theFontMetrics(f);
540
541         int const below = max(fm.maxDescent() / 2, 2);
542         int height = max((fm.maxDescent() / 4) - 1, 1);
543
544         if (height >= 2)
545                 height += below;
546
547         for (int n = 0; n != height; ++n)
548                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
549 }
550
551
552 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
553 {
554         setQPainterPen(computeColor(col));
555         int const step = 4;
556         int const xend = x + width;
557         int height = 1;
558         //FIXME: I am not sure if Antialiasing gives the best effect.
559         //setRenderHint(Antialiasing, true);
560         while (x < xend) {
561                 height = - height;
562                 drawLine(x, y - height, x + step, y + height);
563                 x += step;
564                 drawLine(x, y + height, x + 2, y + height);
565                 x += 2;
566         }
567         //setRenderHint(Antialiasing, false);
568 }
569
570 } // namespace frontend
571 } // namespace lyx