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;
55 current_lw_ = line_thin;
59 GuiPainter::~GuiPainter()
62 //lyxerr << "GuiPainter::end()" << endl;
66 void GuiPainter::setQPainterPen(QColor const & col,
67 Painter::line_style ls, Painter::line_width 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;
85 case line_thin: pen.setWidth(0); break;
86 case line_medium: pen.setWidth(1); break;
87 case line_thick: pen.setWidth(3); break;
94 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
97 sig.append(QChar(static_cast<short>(f.family())));
98 sig.append(QChar(static_cast<short>(f.series())));
99 sig.append(QChar(static_cast<short>(f.realShape())));
100 sig.append(QChar(static_cast<short>(f.size())));
101 sig.append(QChar(static_cast<short>(f.color())));
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())));
116 QColor GuiPainter::computeColor(Color col)
118 return filterColor(guiApp->colorCache().get(col));
122 QColor GuiPainter::filterColor(QColor const & col)
124 if (monochrome_min_.empty())
127 // map into [min,max] interval
128 QColor const & min = monochrome_min_.top();
129 QColor const & max = monochrome_max_.top();
131 qreal v = col.valueF();
132 v *= v; // make it a bit steeper (i.e. darker)
134 qreal minr, ming, minb;
135 qreal maxr, maxg, maxb;
136 min.getRgbF(&minr, &ming, &minb);
137 max.getRgbF(&maxr, &maxg, &maxb);
141 v * (minr - maxr) + maxr,
142 v * (ming - maxg) + maxg,
143 v * (minb - maxb) + maxb);
148 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
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);
157 void GuiPainter::leaveMonochromeMode()
159 LASSERT(!monochrome_min_.empty(), /**/);
160 monochrome_min_.pop();
161 monochrome_max_.pop();
165 void GuiPainter::point(int x, int y, Color col)
167 if (!isDrawingEnabled())
170 setQPainterPen(computeColor(col));
175 void GuiPainter::line(int x1, int y1, int x2, int y2,
180 if (!isDrawingEnabled())
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);
192 void GuiPainter::lines(int const * xp, int const * yp, int np,
197 if (!isDrawingEnabled())
200 // double the size if needed
201 static QVector<QPoint> points(32);
202 if (np > points.size())
203 points.resize(2 * np);
205 bool antialias = false;
206 for (int i = 0; i < np; ++i) {
207 points[i].setX(xp[i]);
208 points[i].setY(yp[i]);
210 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
212 setQPainterPen(computeColor(col), ls, lw);
213 bool const text_is_antialiased = renderHints() & TextAntialiasing;
214 setRenderHint(Antialiasing, antialias && text_is_antialiased);
215 drawPolyline(points.data(), np);
216 setRenderHint(Antialiasing, false);
220 void GuiPainter::rectangle(int x, int y, int w, int h,
225 if (!isDrawingEnabled())
228 setQPainterPen(computeColor(col), ls, lw);
229 drawRect(x, y, w, h);
233 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
235 if (!isDrawingEnabled())
238 fillRect(x, y, w, h, guiApp->colorCache().get(col));
242 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
243 int a1, int a2, Color col)
245 if (!isDrawingEnabled())
248 // LyX usings 1/64ths degree, Qt usings 1/16th
249 setQPainterPen(computeColor(col));
250 bool const do_antialiasing = renderHints() & TextAntialiasing;
251 setRenderHint(Antialiasing, do_antialiasing);
252 drawArc(x, y, w, h, a1 / 4, a2 / 4);
253 setRenderHint(Antialiasing, false);
257 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
259 graphics::GuiImage const & qlimage =
260 static_cast<graphics::GuiImage const &>(i);
262 fillRectangle(x, y, w, h, Color_graphicsbg);
264 if (!isDrawingEnabled())
267 drawImage(x, y, qlimage.image(), 0, 0, w, h);
271 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
274 return text(x, y, s, f);
278 int GuiPainter::smallCapsText(int x, int y,
279 QString const & s, FontInfo const & f)
281 FontInfo smallfont(f);
282 smallfont.decSize().decSize().setShape(UP_SHAPE);
284 QFont const & qfont = getFont(f);
285 QFont const & qsmallfont = getFont(smallfont);
287 setQPainterPen(computeColor(f.realColor()));
289 size_t const ls = s.length();
290 for (unsigned int i = 0; i < ls; ++i) {
291 QChar const c = s[i].toUpper();
297 if (isDrawingEnabled())
298 drawText(x + textwidth, y, c);
299 textwidth += fontMetrics().width(c);
305 int GuiPainter::text(int x, int y, docstring const & s,
311 /* Caution: The following ucs4 to QString conversions work for symbol fonts
312 only because they are no real conversions but simple casts in reality.
313 When we want to draw a symbol or calculate the metrics we pass the position
314 of the symbol in the font (as given in lib/symbols) as a char_type to the
315 frontend. This is just wrong, because the symbol is no UCS4 character at
316 all. You can think of this number as the code point of the symbol in a
317 custom symbol encoding. It works because this char_type is lateron again
318 interpreted as a position in the font again.
319 The correct solution would be to have extra functions for symbols, but that
320 would require to duplicate a lot of frontend and mathed support code.
322 QString str = toqstr(s);
325 // HACK: QT3 refuses to show single compose characters
326 // Still needed with Qt4?
327 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
331 QFont const & ff = getFont(f);
332 GuiFontMetrics const & fm = getFontMetrics(f);
336 if (f.realShape() == SMALLCAPS_SHAPE) {
337 textwidth = smallCapsText(x, y, str, f);
338 if (f.underbar() == FONT_ON)
339 underline(f, x, y, textwidth);
340 if (f.strikeout() == FONT_ON)
341 strikeoutLine(f, x, y, textwidth);
342 if (f.uuline() == FONT_ON)
343 doubleUnderline(f, x, y, textwidth);
344 if (f.uwave() == FONT_ON)
345 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
349 // Here we use the font width cache instead of
350 // textwidth = fontMetrics().width(str);
351 // because the above is awfully expensive on MacOSX
352 textwidth = fm.width(s);
353 if (f.underbar() == FONT_ON)
354 underline(f, x, y, textwidth);
355 if (f.strikeout() == FONT_ON)
356 strikeoutLine(f, x, y, textwidth);
357 if (f.uuline() == FONT_ON)
358 doubleUnderline(f, x, y, textwidth);
359 if (f.uwave() == FONT_ON)
360 // f.color() doesn't work on some circumstances
361 wavyHorizontalLine(x, y, textwidth, f.realColor().baseColor);
363 if (!isDrawingEnabled())
366 // Qt4 does not display a glyph whose codepoint is the
367 // same as that of a soft-hyphen (0x00ad), unless it
368 // occurs at a line-break. As a kludge, we force Qt to
369 // render this glyph using a one-column line.
370 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
371 setQPainterPen(computeColor(f.realColor()));
372 QTextLayout adsymbol(str);
373 adsymbol.setFont(ff);
374 adsymbol.beginLayout();
375 QTextLine line = adsymbol.createLine();
376 line.setNumColumns(1);
377 line.setPosition(QPointF(0, -line.ascent()));
378 adsymbol.endLayout();
379 line.draw(this, QPointF(x, y));
383 if (use_pixmap_cache_) {
385 QString key = generateStringSignature(str, f);
387 // Warning: Left bearing is in general negative! Only the case
388 // where left bearing is negative is of interest WRT the
389 // pixmap width and the text x-position.
390 // Only the left bearing of the first character is important
391 // as we always write from left to right, even for
392 // right-to-left languages.
393 int const lb = min(fm.lbearing(s[0]), 0);
394 int const mA = fm.maxAscent();
395 if (QPixmapCache::find(key, pm)) {
396 // Draw the cached pixmap.
397 drawPixmap(x + lb, y - mA, pm);
401 // Only the right bearing of the last character is
402 // important as we always write from left to right,
403 // even for right-to-left languages.
404 int const rb = fm.rbearing(s[s.size()-1]);
405 int const w = textwidth + rb - lb;
406 int const mD = fm.maxDescent();
407 int const h = mA + mD;
408 if (w > 0 && h > 0) {
410 pm.fill(Qt::transparent);
412 p.setQPainterPen(computeColor(f.realColor()));
415 // We need to draw the text as LTR as we use our own bidi code.
416 p.setLayoutDirection(Qt::LeftToRight);
417 p.drawText(-lb, mA, str);
418 QPixmapCache::insert(key, pm);
419 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
420 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
423 // Draw the new cached pixmap.
424 drawPixmap(x + lb, y - mA, pm);
430 // don't use the pixmap cache,
431 // draw directly onto the painting device
432 setQPainterPen(computeColor(f.realColor()));
435 // We need to draw the text as LTR as we use our own bidi code.
436 QPainter::setLayoutDirection(Qt::LeftToRight);
438 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
439 // << " at " << x << "," << y);
444 static int max(int a, int b) { return a > b ? a : b; }
447 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
450 fillRectangle(x, y, w, h, Color_buttonhoverbg);
452 fillRectangle(x, y, w, h, Color_buttonbg);
453 buttonFrame(x, y, w, h);
457 void GuiPainter::buttonFrame(int x, int y, int w, int h)
459 line(x, y, x, y + h - 1, Color_buttonframe);
460 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
461 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
462 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
466 void GuiPainter::rectText(int x, int y, docstring const & str,
467 FontInfo const & font, Color back, Color frame)
473 FontMetrics const & fm = theFontMetrics(font);
474 fm.rectText(str, width, ascent, descent);
476 if (back != Color_none)
477 fillRectangle(x + 1, y - ascent + 1, width - 1,
478 ascent + descent - 1, back);
480 if (frame != Color_none)
481 rectangle(x, y - ascent, width, ascent + descent, frame);
483 text(x + 3, y, str, font);
487 void GuiPainter::buttonText(int x, int y, docstring const & str,
488 FontInfo const & font, bool mouseHover)
494 FontMetrics const & fm = theFontMetrics(font);
495 fm.buttonText(str, width, ascent, descent);
497 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
499 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
500 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
504 int GuiPainter::preeditText(int x, int y, char_type c,
505 FontInfo const & font, preedit_style style)
507 FontInfo temp_font = font;
508 FontMetrics const & fm = theFontMetrics(font);
509 int ascent = fm.maxAscent();
510 int descent = fm.maxDescent();
511 int height = ascent + descent;
512 int width = fm.width(c);
515 case preedit_default:
516 // default unselecting mode.
517 fillRectangle(x, y - height + 1, width, height, Color_background);
518 dashedUnderline(font, x, y - descent + 1, width);
520 case preedit_selecting:
521 // We are in selecting mode: white text on black background.
522 fillRectangle(x, y - height + 1, width, height, Color_black);
523 temp_font.setColor(Color_white);
526 // The character comes with a cursor.
527 fillRectangle(x, y - height + 1, width, height, Color_background);
528 underline(font, x, y - descent + 1, width);
531 text(x, y - descent + 1, c, temp_font);
537 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
539 FontMetrics const & fm = theFontMetrics(f);
541 int const below = max(fm.maxDescent() / 2, 2);
543 line(x, y + below, x + width, y + below, f.realColor());
544 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
548 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
550 FontMetrics const & fm = theFontMetrics(f);
552 int const below = max(fm.maxDescent() / 2, 2);
553 int const height = max((fm.maxDescent() / 4) - 1, 1);
556 line(x, y + below, x + width, y + below, f.realColor());
558 fillRectangle(x, y + below, width, below + height, f.realColor());
562 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
564 FontMetrics const & fm = theFontMetrics(f);
566 int const middle = max((fm.maxHeight() / 4), 1);
567 int const height = middle/3;
570 line(x, y - middle, x + width, y - middle, f.realColor());
572 fillRectangle(x, y - middle, width, height, f.realColor());
576 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
578 FontMetrics const & fm = theFontMetrics(f);
580 int const below = max(fm.maxDescent() / 2, 2);
581 int height = max((fm.maxDescent() / 4) - 1, 1);
586 for (int n = 0; n != height; ++n)
587 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
591 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
593 setQPainterPen(computeColor(col));
595 int const xend = x + width;
597 //FIXME: I am not sure if Antialiasing gives the best effect.
598 //setRenderHint(Antialiasing, true);
601 drawLine(x, y - height, x + step, y + height);
603 drawLine(x, y + height, x + step/2, y + height);
606 //setRenderHint(Antialiasing, false);
609 } // namespace frontend