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.0;
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 Color const & color = f.realColor();
100 sig.append(QChar(static_cast<short>(color.baseColor)));
101 sig.append(QChar(static_cast<short>(color.mergeColor)));
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(), return);
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
202 static QVector<QPoint> points(32);
203 if (np > points.size())
204 points.resize(2 * np);
206 bool antialias = false;
207 for (int i = 0; i < np; ++i) {
208 points[i].setX(xp[i]);
209 points[i].setY(yp[i]);
211 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
213 setQPainterPen(computeColor(col), ls, lw);
214 bool const text_is_antialiased = renderHints() & TextAntialiasing;
215 setRenderHint(Antialiasing, antialias && text_is_antialiased);
216 drawPolyline(points.data(), np);
217 setRenderHint(Antialiasing, false);
221 void GuiPainter::rectangle(int x, int y, int w, int h,
226 if (!isDrawingEnabled())
229 setQPainterPen(computeColor(col), ls, lw);
230 drawRect(x, y, w, h);
234 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
236 if (!isDrawingEnabled())
239 fillRect(x, y, w, h, guiApp->colorCache().get(col));
243 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
244 int a1, int a2, Color col)
246 if (!isDrawingEnabled())
249 // LyX usings 1/64ths degree, Qt usings 1/16th
250 setQPainterPen(computeColor(col));
251 bool const do_antialiasing = renderHints() & TextAntialiasing;
252 setRenderHint(Antialiasing, do_antialiasing);
253 drawArc(x, y, w, h, a1 / 4, a2 / 4);
254 setRenderHint(Antialiasing, false);
258 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
260 graphics::GuiImage const & qlimage =
261 static_cast<graphics::GuiImage const &>(i);
263 fillRectangle(x, y, w, h, Color_graphicsbg);
265 if (!isDrawingEnabled())
268 drawImage(x, y, qlimage.image(), 0, 0, w, h);
272 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
275 return text(x, y, s, f);
279 int GuiPainter::text(int x, int y, docstring const & s,
285 /* Caution: The following ucs4 to QString conversions work for symbol fonts
286 only because they are no real conversions but simple casts in reality.
287 When we want to draw a symbol or calculate the metrics we pass the position
288 of the symbol in the font (as given in lib/symbols) as a char_type to the
289 frontend. This is just wrong, because the symbol is no UCS4 character at
290 all. You can think of this number as the code point of the symbol in a
291 custom symbol encoding. It works because this char_type is lateron again
292 interpreted as a position in the font again.
293 The correct solution would be to have extra functions for symbols, but that
294 would require to duplicate a lot of frontend and mathed support code.
296 QString str = toqstr(s);
299 // HACK: QT3 refuses to show single compose characters
300 // Still needed with Qt4?
301 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
305 QFont const & ff = getFont(f);
306 GuiFontMetrics const & fm = getFontMetrics(f);
310 // Here we use the font width cache instead of
311 // textwidth = fontMetrics().width(str);
312 // because the above is awfully expensive on MacOSX
313 textwidth = fm.width(s);
314 textDecoration(f, x, y, textwidth);
316 if (!isDrawingEnabled())
319 // Qt4 does not display a glyph whose codepoint is the
320 // same as that of a soft-hyphen (0x00ad), unless it
321 // occurs at a line-break. As a kludge, we force Qt to
322 // render this glyph using a one-column line.
323 // This is needed for some math glyphs.
324 // FIXME In texted, this behaves differently depending
325 // on lyxrc.force_paint_single_char status.
326 // Should the soft hyphen char be displayed at all?
327 // I don't think so (i.e., Qt is correct as far as
328 // texted is concerned). /spitz
329 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
330 setQPainterPen(computeColor(f.realColor()));
331 QTextLayout adsymbol(str);
332 adsymbol.setFont(ff);
333 adsymbol.beginLayout();
334 QTextLine line = adsymbol.createLine();
335 line.setNumColumns(1);
336 line.setPosition(QPointF(0, -line.ascent()));
337 adsymbol.endLayout();
338 line.draw(this, QPointF(x, y));
342 if (use_pixmap_cache_) {
344 QString key = generateStringSignature(str, f);
346 // Warning: Left bearing is in general negative! Only the case
347 // where left bearing is negative is of interest WRT the
348 // pixmap width and the text x-position.
349 // Only the left bearing of the first character is important
350 // as we always write from left to right, even for
351 // right-to-left languages.
352 int const lb = min(fm.lbearing(s[0]), 0);
353 int const mA = fm.maxAscent();
354 if (QPixmapCache::find(key, pm)) {
355 // Draw the cached pixmap.
356 drawPixmap(x + lb, y - mA, pm);
360 // Only the right bearing of the last character is
361 // important as we always write from left to right,
362 // even for right-to-left languages.
363 int const rb = fm.rbearing(s[s.size()-1]);
364 int const w = textwidth + rb - lb;
365 int const mD = fm.maxDescent();
366 int const h = mA + mD;
367 if (w > 0 && h > 0) {
369 pm.fill(Qt::transparent);
371 p.setQPainterPen(computeColor(f.realColor()));
374 // We need to draw the text as LTR as we use our own bidi code.
375 p.setLayoutDirection(Qt::LeftToRight);
376 p.drawText(-lb, mA, str);
377 QPixmapCache::insert(key, pm);
378 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
379 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
382 // Draw the new cached pixmap.
383 drawPixmap(x + lb, y - mA, pm);
389 // don't use the pixmap cache,
390 // draw directly onto the painting device
391 setQPainterPen(computeColor(f.realColor()));
394 // We need to draw the text as LTR as we use our own bidi code.
395 QPainter::setLayoutDirection(Qt::LeftToRight);
397 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
398 // << " at " << x << "," << y);
403 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
405 if (f.underbar() == FONT_ON)
406 underline(f, x, y, width);
407 if (f.strikeout() == FONT_ON)
408 strikeoutLine(f, x, y, width);
409 if (f.uuline() == FONT_ON)
410 doubleUnderline(f, x, y, width);
411 if (f.uwave() == FONT_ON)
412 // f.color() doesn't work on some circumstances
413 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
417 static int max(int a, int b) { return a > b ? a : b; }
420 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
423 fillRectangle(x, y, w, h, Color_buttonhoverbg);
425 fillRectangle(x, y, w, h, Color_buttonbg);
426 buttonFrame(x, y, w, h);
430 void GuiPainter::buttonFrame(int x, int y, int w, int h)
432 line(x, y, x, y + h - 1, Color_buttonframe);
433 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
434 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
435 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
439 void GuiPainter::rectText(int x, int y, docstring const & str,
440 FontInfo const & font, Color back, Color frame)
446 FontMetrics const & fm = theFontMetrics(font);
447 fm.rectText(str, width, ascent, descent);
449 if (back != Color_none)
450 fillRectangle(x + 1, y - ascent + 1, width - 1,
451 ascent + descent - 1, back);
453 if (frame != Color_none)
454 rectangle(x, y - ascent, width, ascent + descent, frame);
456 text(x + 3, y, str, font);
460 void GuiPainter::buttonText(int x, int y, docstring const & str,
461 FontInfo const & font, bool mouseHover)
467 FontMetrics const & fm = theFontMetrics(font);
468 fm.buttonText(str, width, ascent, descent);
470 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
472 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
473 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
477 int GuiPainter::preeditText(int x, int y, char_type c,
478 FontInfo const & font, preedit_style style)
480 FontInfo temp_font = font;
481 FontMetrics const & fm = theFontMetrics(font);
482 int ascent = fm.maxAscent();
483 int descent = fm.maxDescent();
484 int height = ascent + descent;
485 int width = fm.width(c);
488 case preedit_default:
489 // default unselecting mode.
490 fillRectangle(x, y - height + 1, width, height, Color_background);
491 dashedUnderline(font, x, y - descent + 1, width);
493 case preedit_selecting:
494 // We are in selecting mode: white text on black background.
495 fillRectangle(x, y - height + 1, width, height, Color_black);
496 temp_font.setColor(Color_white);
499 // The character comes with a cursor.
500 fillRectangle(x, y - height + 1, width, height, Color_background);
501 underline(font, x, y - descent + 1, width);
504 text(x, y - descent + 1, c, temp_font);
510 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
512 FontMetrics const & fm = theFontMetrics(f);
514 int const below = max(fm.maxDescent() / 2, 2);
516 line(x, y + below, x + width, y + below, f.realColor());
517 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
521 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
523 FontMetrics const & fm = theFontMetrics(f);
525 int const below = max(fm.maxDescent() / 2, 2);
526 int const height = max((fm.maxDescent() / 4) - 1, 1);
529 line(x, y + below, x + width, y + below, f.realColor());
531 fillRectangle(x, y + below, width, below + height, f.realColor());
535 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
537 FontMetrics const & fm = theFontMetrics(f);
539 int const middle = max((fm.maxHeight() / 4), 1);
540 int const height = middle/3;
543 line(x, y - middle, x + width, y - middle, f.realColor());
545 fillRectangle(x, y - middle, width, height, f.realColor());
549 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
551 FontMetrics const & fm = theFontMetrics(f);
553 int const below = max(fm.maxDescent() / 2, 2);
554 int height = max((fm.maxDescent() / 4) - 1, 1);
559 for (int n = 0; n != height; ++n)
560 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
564 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
566 setQPainterPen(computeColor(col));
568 int const xend = x + width;
570 //FIXME: I am not sure if Antialiasing gives the best effect.
571 //setRenderHint(Antialiasing, true);
574 drawLine(x, y - height, x + step, y + height);
576 drawLine(x, y + height, x + step/2, y + height);
579 //setRenderHint(Antialiasing, false);
582 } // namespace frontend