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 GuiPainter::GuiPainter(QPaintDevice * device)
49 : QPainter(device), Painter(),
50 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
52 // new QPainter has default QPen:
53 current_color_ = guiApp->colorCache().get(Color_black);
54 current_ls_ = line_solid;
59 GuiPainter::~GuiPainter()
62 //lyxerr << "GuiPainter::end()" << endl;
66 void GuiPainter::setQPainterPen(QColor const & col,
67 Painter::line_style ls, float lw)
69 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
76 QPen pen = QPainter::pen();
80 case line_solid: pen.setStyle(Qt::SolidLine); break;
81 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
90 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
93 sig.append(QChar(static_cast<short>(f.family())));
94 sig.append(QChar(static_cast<short>(f.series())));
95 sig.append(QChar(static_cast<short>(f.realShape())));
96 sig.append(QChar(static_cast<short>(f.size())));
97 sig.append(QChar(static_cast<short>(f.color())));
98 if (!monochrome_min_.empty()) {
99 QColor const & min = monochrome_min_.top();
100 QColor const & max = monochrome_max_.top();
101 sig.append(QChar(static_cast<short>(min.red())));
102 sig.append(QChar(static_cast<short>(min.green())));
103 sig.append(QChar(static_cast<short>(min.blue())));
104 sig.append(QChar(static_cast<short>(max.red())));
105 sig.append(QChar(static_cast<short>(max.green())));
106 sig.append(QChar(static_cast<short>(max.blue())));
112 QColor GuiPainter::computeColor(Color col)
114 return filterColor(guiApp->colorCache().get(col));
118 QColor GuiPainter::filterColor(QColor const & col)
120 if (monochrome_min_.empty())
123 // map into [min,max] interval
124 QColor const & min = monochrome_min_.top();
125 QColor const & max = monochrome_max_.top();
127 qreal v = col.valueF();
128 v *= v; // make it a bit steeper (i.e. darker)
130 qreal minr, ming, minb;
131 qreal maxr, maxg, maxb;
132 min.getRgbF(&minr, &ming, &minb);
133 max.getRgbF(&maxr, &maxg, &maxb);
137 v * (minr - maxr) + maxr,
138 v * (ming - maxg) + maxg,
139 v * (minb - maxb) + maxb);
144 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
146 QColor qmin = filterColor(guiApp->colorCache().get(min));
147 QColor qmax = filterColor(guiApp->colorCache().get(max));
148 monochrome_min_.push(qmin);
149 monochrome_max_.push(qmax);
153 void GuiPainter::leaveMonochromeMode()
155 LASSERT(!monochrome_min_.empty(), /**/);
156 monochrome_min_.pop();
157 monochrome_max_.pop();
161 void GuiPainter::point(int x, int y, Color col)
163 if (!isDrawingEnabled())
166 setQPainterPen(computeColor(col));
171 void GuiPainter::line(int x1, int y1, int x2, int y2,
176 if (!isDrawingEnabled())
179 setQPainterPen(computeColor(col), ls, lw);
180 bool const do_antialiasing = renderHints() & TextAntialiasing
181 && x1 != x2 && y1 != y2;
182 setRenderHint(Antialiasing, do_antialiasing);
183 drawLine(x1, y1, x2, y2);
184 setRenderHint(Antialiasing, false);
188 void GuiPainter::lines(int const * xp, int const * yp, int np,
193 if (!isDrawingEnabled())
196 // double the size if needed
197 static QVector<QPoint> points(32);
198 if (np > points.size())
199 points.resize(2 * np);
201 bool antialias = false;
202 for (int i = 0; i < np; ++i) {
203 points[i].setX(xp[i]);
204 points[i].setY(yp[i]);
206 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
208 setQPainterPen(computeColor(col), ls, lw);
209 bool const text_is_antialiased = renderHints() & TextAntialiasing;
210 setRenderHint(Antialiasing, antialias && text_is_antialiased);
211 drawPolyline(points.data(), np);
212 setRenderHint(Antialiasing, false);
216 void GuiPainter::rectangle(int x, int y, int w, int h,
221 if (!isDrawingEnabled())
224 setQPainterPen(computeColor(col), ls, lw);
225 drawRect(x, y, w, h);
229 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
231 if (!isDrawingEnabled())
234 fillRect(x, y, w, h, guiApp->colorCache().get(col));
238 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
239 int a1, int a2, Color col)
241 if (!isDrawingEnabled())
244 // LyX usings 1/64ths degree, Qt usings 1/16th
245 setQPainterPen(computeColor(col));
246 bool const do_antialiasing = renderHints() & TextAntialiasing;
247 setRenderHint(Antialiasing, do_antialiasing);
248 drawArc(x, y, w, h, a1 / 4, a2 / 4);
249 setRenderHint(Antialiasing, false);
253 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
255 graphics::GuiImage const & qlimage =
256 static_cast<graphics::GuiImage const &>(i);
258 fillRectangle(x, y, w, h, Color_graphicsbg);
260 if (!isDrawingEnabled())
263 drawImage(x, y, qlimage.image(), 0, 0, w, h);
267 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
270 return text(x, y, s, f);
274 int GuiPainter::smallCapsText(int x, int y,
275 QString const & s, FontInfo const & f)
277 FontInfo smallfont(f);
278 smallfont.decSize().decSize().setShape(UP_SHAPE);
280 QFont const & qfont = getFont(f);
281 QFont const & qsmallfont = getFont(smallfont);
283 setQPainterPen(computeColor(f.realColor()));
285 size_t const ls = s.length();
286 for (unsigned int i = 0; i < ls; ++i) {
287 QChar const c = s[i].toUpper();
293 if (isDrawingEnabled())
294 drawText(x + textwidth, y, c);
295 textwidth += fontMetrics().width(c);
301 int GuiPainter::text(int x, int y, docstring const & s,
307 /* Caution: The following ucs4 to QString conversions work for symbol fonts
308 only because they are no real conversions but simple casts in reality.
309 When we want to draw a symbol or calculate the metrics we pass the position
310 of the symbol in the font (as given in lib/symbols) as a char_type to the
311 frontend. This is just wrong, because the symbol is no UCS4 character at
312 all. You can think of this number as the code point of the symbol in a
313 custom symbol encoding. It works because this char_type is lateron again
314 interpreted as a position in the font again.
315 The correct solution would be to have extra functions for symbols, but that
316 would require to duplicate a lot of frontend and mathed support code.
318 QString str = toqstr(s);
321 // HACK: QT3 refuses to show single compose characters
322 // Still needed with Qt4?
323 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
327 QFont const & ff = getFont(f);
328 GuiFontMetrics const & fm = getFontMetrics(f);
332 if (f.realShape() == SMALLCAPS_SHAPE) {
333 textwidth = smallCapsText(x, y, str, f);
334 if (f.underbar() == FONT_ON)
335 underline(f, x, y, textwidth);
336 if (f.strikeout() == FONT_ON)
337 strikeoutLine(f, x, y, textwidth);
338 if (f.uuline() == FONT_ON)
339 doubleUnderline(f, x, y, textwidth);
340 if (f.uwave() == FONT_ON)
341 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
345 // Here we use the font width cache instead of
346 // textwidth = fontMetrics().width(str);
347 // because the above is awfully expensive on MacOSX
348 textwidth = fm.width(s);
349 if (f.underbar() == FONT_ON)
350 underline(f, x, y, textwidth);
351 if (f.strikeout() == FONT_ON)
352 strikeoutLine(f, x, y, textwidth);
353 if (f.uuline() == FONT_ON)
354 doubleUnderline(f, x, y, textwidth);
355 if (f.uwave() == FONT_ON)
356 // f.color() doesn't work on some circumstances
357 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
359 if (!isDrawingEnabled())
362 // Qt4 does not display a glyph whose codepoint is the
363 // same as that of a soft-hyphen (0x00ad), unless it
364 // occurs at a line-break. As a kludge, we force Qt to
365 // render this glyph using a one-column line.
366 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
367 setQPainterPen(computeColor(f.realColor()));
368 QTextLayout adsymbol(str);
369 adsymbol.setFont(ff);
370 adsymbol.beginLayout();
371 QTextLine line = adsymbol.createLine();
372 line.setNumColumns(1);
373 line.setPosition(QPointF(0, -line.ascent()));
374 adsymbol.endLayout();
375 line.draw(this, QPointF(x, y));
379 if (use_pixmap_cache_) {
381 QString key = generateStringSignature(str, f);
383 // Warning: Left bearing is in general negative! Only the case
384 // where left bearing is negative is of interest WRT the
385 // pixmap width and the text x-position.
386 // Only the left bearing of the first character is important
387 // as we always write from left to right, even for
388 // right-to-left languages.
389 int const lb = min(fm.lbearing(s[0]), 0);
390 int const mA = fm.maxAscent();
391 if (QPixmapCache::find(key, pm)) {
392 // Draw the cached pixmap.
393 drawPixmap(x + lb, y - mA, pm);
397 // Only the right bearing of the last character is
398 // important as we always write from left to right,
399 // even for right-to-left languages.
400 int const rb = fm.rbearing(s[s.size()-1]);
401 int const w = textwidth + rb - lb;
402 int const mD = fm.maxDescent();
403 int const h = mA + mD;
404 if (w > 0 && h > 0) {
406 pm.fill(Qt::transparent);
408 p.setQPainterPen(computeColor(f.realColor()));
411 // We need to draw the text as LTR as we use our own bidi code.
412 p.setLayoutDirection(Qt::LeftToRight);
413 p.drawText(-lb, mA, str);
414 QPixmapCache::insert(key, pm);
415 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
416 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
419 // Draw the new cached pixmap.
420 drawPixmap(x + lb, y - mA, pm);
426 // don't use the pixmap cache,
427 // draw directly onto the painting device
428 setQPainterPen(computeColor(f.realColor()));
431 // We need to draw the text as LTR as we use our own bidi code.
432 QPainter::setLayoutDirection(Qt::LeftToRight);
434 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
435 // << " at " << x << "," << y);
440 static int max(int a, int b) { return a > b ? a : b; }
443 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
446 fillRectangle(x, y, w, h, Color_buttonhoverbg);
448 fillRectangle(x, y, w, h, Color_buttonbg);
449 buttonFrame(x, y, w, h);
453 void GuiPainter::buttonFrame(int x, int y, int w, int h)
455 line(x, y, x, y + h - 1, Color_buttonframe);
456 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
457 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
458 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
462 void GuiPainter::rectText(int x, int y, docstring const & str,
463 FontInfo const & font, Color back, Color frame)
469 FontMetrics const & fm = theFontMetrics(font);
470 fm.rectText(str, width, ascent, descent);
472 if (back != Color_none)
473 fillRectangle(x + 1, y - ascent + 1, width - 1,
474 ascent + descent - 1, back);
476 if (frame != Color_none)
477 rectangle(x, y - ascent, width, ascent + descent, frame);
479 text(x + 3, y, str, font);
483 void GuiPainter::buttonText(int x, int y, docstring const & str,
484 FontInfo const & font, bool mouseHover)
490 FontMetrics const & fm = theFontMetrics(font);
491 fm.buttonText(str, width, ascent, descent);
493 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
495 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
496 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
500 int GuiPainter::preeditText(int x, int y, char_type c,
501 FontInfo const & font, preedit_style style)
503 FontInfo temp_font = font;
504 FontMetrics const & fm = theFontMetrics(font);
505 int ascent = fm.maxAscent();
506 int descent = fm.maxDescent();
507 int height = ascent + descent;
508 int width = fm.width(c);
511 case preedit_default:
512 // default unselecting mode.
513 fillRectangle(x, y - height + 1, width, height, Color_background);
514 dashedUnderline(font, x, y - descent + 1, width);
516 case preedit_selecting:
517 // We are in selecting mode: white text on black background.
518 fillRectangle(x, y - height + 1, width, height, Color_black);
519 temp_font.setColor(Color_white);
522 // The character comes with a cursor.
523 fillRectangle(x, y - height + 1, width, height, Color_background);
524 underline(font, x, y - descent + 1, width);
527 text(x, y - descent + 1, c, temp_font);
533 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
535 FontMetrics const & fm = theFontMetrics(f);
537 int const below = max(fm.maxDescent() / 2, 2);
539 line(x, y + below, x + width, y + below, f.realColor());
540 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
544 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
546 FontMetrics const & fm = theFontMetrics(f);
548 int const below = max(fm.maxDescent() / 2, 2);
549 int const height = max((fm.maxDescent() / 4) - 1, 1);
552 line(x, y + below, x + width, y + below, f.realColor());
554 fillRectangle(x, y + below, width, below + height, f.realColor());
558 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
560 FontMetrics const & fm = theFontMetrics(f);
562 int const middle = max((fm.maxHeight() / 4), 1);
563 int const height = middle/3;
566 line(x, y - middle, x + width, y - middle, f.realColor());
568 fillRectangle(x, y - middle, width, height, f.realColor());
572 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
574 FontMetrics const & fm = theFontMetrics(f);
576 int const below = max(fm.maxDescent() / 2, 2);
577 int height = max((fm.maxDescent() / 4) - 1, 1);
582 for (int n = 0; n != height; ++n)
583 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
587 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
589 setQPainterPen(computeColor(col));
591 int const xend = x + width;
593 //FIXME: I am not sure if Antialiasing gives the best effect.
594 //setRenderHint(Antialiasing, true);
597 drawLine(x, y - height, x + step, y + height);
599 drawLine(x, y + height, x + step/2, y + height);
602 //setRenderHint(Antialiasing, false);
605 } // namespace frontend