3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Abdelrazak Younes
9 * Full author contact details are available in file CREDITS.
14 #include "GuiPainter.h"
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
21 #include "qt_helpers.h"
27 #include "insets/Inset.h"
29 #include "support/lassert.h"
30 #include "support/debug.h"
32 #include <QPixmapCache>
33 #include <QTextLayout>
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.
38 #define USE_PIXMAP_CACHE 0
40 #define USE_PIXMAP_CACHE 1
48 const float Painter::thin_line = 0.5;
50 GuiPainter::GuiPainter(QPaintDevice * device)
51 : QPainter(device), Painter(),
52 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
54 // new QPainter has default QPen:
55 current_color_ = guiApp->colorCache().get(Color_black);
56 current_ls_ = line_solid;
57 current_lw_ = thin_line;
61 GuiPainter::~GuiPainter()
64 //lyxerr << "GuiPainter::end()" << endl;
68 void GuiPainter::setQPainterPen(QColor const & col,
69 Painter::line_style ls, float lw)
71 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
78 QPen pen = QPainter::pen();
82 case line_solid: pen.setStyle(Qt::SolidLine); break;
83 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
92 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
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 sig.append(QChar(static_cast<short>(f.color())));
100 if (!monochrome_min_.empty()) {
101 QColor const & min = monochrome_min_.top();
102 QColor const & max = monochrome_max_.top();
103 sig.append(QChar(static_cast<short>(min.red())));
104 sig.append(QChar(static_cast<short>(min.green())));
105 sig.append(QChar(static_cast<short>(min.blue())));
106 sig.append(QChar(static_cast<short>(max.red())));
107 sig.append(QChar(static_cast<short>(max.green())));
108 sig.append(QChar(static_cast<short>(max.blue())));
114 QColor GuiPainter::computeColor(Color col)
116 return filterColor(guiApp->colorCache().get(col));
120 QColor GuiPainter::filterColor(QColor const & col)
122 if (monochrome_min_.empty())
125 // map into [min,max] interval
126 QColor const & min = monochrome_min_.top();
127 QColor const & max = monochrome_max_.top();
129 qreal v = col.valueF();
130 v *= v; // make it a bit steeper (i.e. darker)
132 qreal minr, ming, minb;
133 qreal maxr, maxg, maxb;
134 min.getRgbF(&minr, &ming, &minb);
135 max.getRgbF(&maxr, &maxg, &maxb);
139 v * (minr - maxr) + maxr,
140 v * (ming - maxg) + maxg,
141 v * (minb - maxb) + maxb);
146 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
148 QColor qmin = filterColor(guiApp->colorCache().get(min));
149 QColor qmax = filterColor(guiApp->colorCache().get(max));
150 monochrome_min_.push(qmin);
151 monochrome_max_.push(qmax);
155 void GuiPainter::leaveMonochromeMode()
157 LASSERT(!monochrome_min_.empty(), /**/);
158 monochrome_min_.pop();
159 monochrome_max_.pop();
163 void GuiPainter::point(int x, int y, Color col)
165 if (!isDrawingEnabled())
168 setQPainterPen(computeColor(col));
173 void GuiPainter::line(int x1, int y1, int x2, int y2,
178 if (!isDrawingEnabled())
181 setQPainterPen(computeColor(col), ls, lw);
182 bool const do_antialiasing = renderHints() & TextAntialiasing
183 && x1 != x2 && y1 != y2;
184 setRenderHint(Antialiasing, do_antialiasing);
185 drawLine(x1, y1, x2, y2);
186 setRenderHint(Antialiasing, false);
190 void GuiPainter::lines(int const * xp, int const * yp, int np,
195 if (!isDrawingEnabled())
198 // double the size if needed
199 static QVector<QPoint> points(32);
200 if (np > points.size())
201 points.resize(2 * np);
203 bool antialias = false;
204 for (int i = 0; i < np; ++i) {
205 points[i].setX(xp[i]);
206 points[i].setY(yp[i]);
208 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
210 setQPainterPen(computeColor(col), ls, lw);
211 bool const text_is_antialiased = renderHints() & TextAntialiasing;
212 setRenderHint(Antialiasing, antialias && text_is_antialiased);
213 drawPolyline(points.data(), np);
214 setRenderHint(Antialiasing, false);
218 void GuiPainter::rectangle(int x, int y, int w, int h,
223 if (!isDrawingEnabled())
226 setQPainterPen(computeColor(col), ls, lw);
227 drawRect(x, y, w, h);
231 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
233 if (!isDrawingEnabled())
236 fillRect(x, y, w, h, guiApp->colorCache().get(col));
240 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
241 int a1, int a2, Color col)
243 if (!isDrawingEnabled())
246 // LyX usings 1/64ths degree, Qt usings 1/16th
247 setQPainterPen(computeColor(col));
248 bool const do_antialiasing = renderHints() & TextAntialiasing;
249 setRenderHint(Antialiasing, do_antialiasing);
250 drawArc(x, y, w, h, a1 / 4, a2 / 4);
251 setRenderHint(Antialiasing, false);
255 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
257 graphics::GuiImage const & qlimage =
258 static_cast<graphics::GuiImage const &>(i);
260 fillRectangle(x, y, w, h, Color_graphicsbg);
262 if (!isDrawingEnabled())
265 drawImage(x, y, qlimage.image(), 0, 0, w, h);
269 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
272 return text(x, y, s, f);
276 int GuiPainter::smallCapsText(int x, int y,
277 QString const & s, FontInfo const & f)
279 FontInfo smallfont(f);
280 smallfont.decSize().decSize().setShape(UP_SHAPE);
282 QFont const & qfont = getFont(f);
283 QFont const & qsmallfont = getFont(smallfont);
285 setQPainterPen(computeColor(f.realColor()));
287 size_t const ls = s.length();
288 for (unsigned int i = 0; i < ls; ++i) {
289 QChar const c = s[i].toUpper();
295 if (isDrawingEnabled())
296 drawText(x + textwidth, y, c);
297 textwidth += fontMetrics().width(c);
303 int GuiPainter::text(int x, int y, docstring const & s,
309 /* Caution: The following ucs4 to QString conversions work for symbol fonts
310 only because they are no real conversions but simple casts in reality.
311 When we want to draw a symbol or calculate the metrics we pass the position
312 of the symbol in the font (as given in lib/symbols) as a char_type to the
313 frontend. This is just wrong, because the symbol is no UCS4 character at
314 all. You can think of this number as the code point of the symbol in a
315 custom symbol encoding. It works because this char_type is lateron again
316 interpreted as a position in the font again.
317 The correct solution would be to have extra functions for symbols, but that
318 would require to duplicate a lot of frontend and mathed support code.
320 QString str = toqstr(s);
323 // HACK: QT3 refuses to show single compose characters
324 // Still needed with Qt4?
325 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
329 QFont const & ff = getFont(f);
330 GuiFontMetrics const & fm = getFontMetrics(f);
334 if (f.realShape() == SMALLCAPS_SHAPE) {
335 textwidth = smallCapsText(x, y, str, f);
336 if (f.underbar() == FONT_ON)
337 underline(f, x, y, textwidth);
338 if (f.strikeout() == FONT_ON)
339 strikeoutLine(f, x, y, textwidth);
340 if (f.uuline() == FONT_ON)
341 doubleUnderline(f, x, y, textwidth);
342 if (f.uwave() == FONT_ON)
343 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
347 // Here we use the font width cache instead of
348 // textwidth = fontMetrics().width(str);
349 // because the above is awfully expensive on MacOSX
350 textwidth = fm.width(s);
351 if (f.underbar() == FONT_ON)
352 underline(f, x, y, textwidth);
353 if (f.strikeout() == FONT_ON)
354 strikeoutLine(f, x, y, textwidth);
355 if (f.uuline() == FONT_ON)
356 doubleUnderline(f, x, y, textwidth);
357 if (f.uwave() == FONT_ON)
358 // f.color() doesn't work on some circumstances
359 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
361 if (!isDrawingEnabled())
364 // Qt4 does not display a glyph whose codepoint is the
365 // same as that of a soft-hyphen (0x00ad), unless it
366 // occurs at a line-break. As a kludge, we force Qt to
367 // render this glyph using a one-column line.
368 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
369 setQPainterPen(computeColor(f.realColor()));
370 QTextLayout adsymbol(str);
371 adsymbol.setFont(ff);
372 adsymbol.beginLayout();
373 QTextLine line = adsymbol.createLine();
374 line.setNumColumns(1);
375 line.setPosition(QPointF(0, -line.ascent()));
376 adsymbol.endLayout();
377 line.draw(this, QPointF(x, y));
381 if (use_pixmap_cache_) {
383 QString key = generateStringSignature(str, f);
385 // Warning: Left bearing is in general negative! Only the case
386 // where left bearing is negative is of interest WRT the
387 // pixmap width and the text x-position.
388 // Only the left bearing of the first character is important
389 // as we always write from left to right, even for
390 // right-to-left languages.
391 int const lb = min(fm.lbearing(s[0]), 0);
392 int const mA = fm.maxAscent();
393 if (QPixmapCache::find(key, pm)) {
394 // Draw the cached pixmap.
395 drawPixmap(x + lb, y - mA, pm);
399 // Only the right bearing of the last character is
400 // important as we always write from left to right,
401 // even for right-to-left languages.
402 int const rb = fm.rbearing(s[s.size()-1]);
403 int const w = textwidth + rb - lb;
404 int const mD = fm.maxDescent();
405 int const h = mA + mD;
406 if (w > 0 && h > 0) {
408 pm.fill(Qt::transparent);
410 p.setQPainterPen(computeColor(f.realColor()));
413 // We need to draw the text as LTR as we use our own bidi code.
414 p.setLayoutDirection(Qt::LeftToRight);
415 p.drawText(-lb, mA, str);
416 QPixmapCache::insert(key, pm);
417 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
418 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
421 // Draw the new cached pixmap.
422 drawPixmap(x + lb, y - mA, pm);
428 // don't use the pixmap cache,
429 // draw directly onto the painting device
430 setQPainterPen(computeColor(f.realColor()));
433 // We need to draw the text as LTR as we use our own bidi code.
434 QPainter::setLayoutDirection(Qt::LeftToRight);
436 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
437 // << " at " << x << "," << y);
442 static int max(int a, int b) { return a > b ? a : b; }
445 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
448 fillRectangle(x, y, w, h, Color_buttonhoverbg);
450 fillRectangle(x, y, w, h, Color_buttonbg);
451 buttonFrame(x, y, w, h);
455 void GuiPainter::buttonFrame(int x, int y, int w, int h)
457 line(x, y, x, y + h - 1, Color_buttonframe);
458 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
459 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
460 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
464 void GuiPainter::rectText(int x, int y, docstring const & str,
465 FontInfo const & font, Color back, Color frame)
471 FontMetrics const & fm = theFontMetrics(font);
472 fm.rectText(str, width, ascent, descent);
474 if (back != Color_none)
475 fillRectangle(x + 1, y - ascent + 1, width - 1,
476 ascent + descent - 1, back);
478 if (frame != Color_none)
479 rectangle(x, y - ascent, width, ascent + descent, frame);
481 text(x + 3, y, str, font);
485 void GuiPainter::buttonText(int x, int y, docstring const & str,
486 FontInfo const & font, bool mouseHover)
492 FontMetrics const & fm = theFontMetrics(font);
493 fm.buttonText(str, width, ascent, descent);
495 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
497 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
498 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
502 int GuiPainter::preeditText(int x, int y, char_type c,
503 FontInfo const & font, preedit_style style)
505 FontInfo temp_font = font;
506 FontMetrics const & fm = theFontMetrics(font);
507 int ascent = fm.maxAscent();
508 int descent = fm.maxDescent();
509 int height = ascent + descent;
510 int width = fm.width(c);
513 case preedit_default:
514 // default unselecting mode.
515 fillRectangle(x, y - height + 1, width, height, Color_background);
516 dashedUnderline(font, x, y - descent + 1, width);
518 case preedit_selecting:
519 // We are in selecting mode: white text on black background.
520 fillRectangle(x, y - height + 1, width, height, Color_black);
521 temp_font.setColor(Color_white);
524 // The character comes with a cursor.
525 fillRectangle(x, y - height + 1, width, height, Color_background);
526 underline(font, x, y - descent + 1, width);
529 text(x, y - descent + 1, c, temp_font);
535 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
537 FontMetrics const & fm = theFontMetrics(f);
539 int const below = max(fm.maxDescent() / 2, 2);
541 line(x, y + below, x + width, y + below, f.realColor());
542 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
546 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
548 FontMetrics const & fm = theFontMetrics(f);
550 int const below = max(fm.maxDescent() / 2, 2);
551 int const height = max((fm.maxDescent() / 4) - 1, 1);
554 line(x, y + below, x + width, y + below, f.realColor());
556 fillRectangle(x, y + below, width, below + height, f.realColor());
560 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
562 FontMetrics const & fm = theFontMetrics(f);
564 int const middle = max((fm.maxHeight() / 4), 1);
565 int const height = middle/3;
568 line(x, y - middle, x + width, y - middle, f.realColor());
570 fillRectangle(x, y - middle, width, height, f.realColor());
574 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
576 FontMetrics const & fm = theFontMetrics(f);
578 int const below = max(fm.maxDescent() / 2, 2);
579 int height = max((fm.maxDescent() / 4) - 1, 1);
584 for (int n = 0; n != height; ++n)
585 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
589 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
591 setQPainterPen(computeColor(col));
593 int const xend = x + width;
595 //FIXME: I am not sure if Antialiasing gives the best effect.
596 //setRenderHint(Antialiasing, true);
599 drawLine(x, y - height, x + step, y + height);
601 drawLine(x, y + height, x + step/2, y + height);
604 //setRenderHint(Antialiasing, false);
607 } // namespace frontend