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/lstrings.h"
31 #include "support/debug.h"
33 #include <QPixmapCache>
34 #include <QTextLayout>
36 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
37 // drawing text. This is especially useful for older PPC/Mac systems.
39 #define USE_PIXMAP_CACHE 0
41 #define USE_PIXMAP_CACHE 1
45 using namespace lyx::support;
50 const float Painter::thin_line = 0.0;
52 GuiPainter::GuiPainter(QPaintDevice * device)
53 : QPainter(device), Painter(),
54 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
56 // new QPainter has default QPen:
57 current_color_ = guiApp->colorCache().get(Color_black);
58 current_ls_ = line_solid;
59 current_lw_ = thin_line;
63 GuiPainter::~GuiPainter()
66 //lyxerr << "GuiPainter::end()" << endl;
70 void GuiPainter::setQPainterPen(QColor const & col,
71 Painter::line_style ls, float lw)
73 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
80 QPen pen = QPainter::pen();
84 case line_solid: pen.setStyle(Qt::SolidLine); break;
85 case line_onoffdash: pen.setStyle(Qt::DotLine); 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 Color const & color = f.realColor();
102 sig.append(QChar(static_cast<short>(color.baseColor)));
103 sig.append(QChar(static_cast<short>(color.mergeColor)));
104 if (!monochrome_min_.empty()) {
105 QColor const & min = monochrome_min_.top();
106 QColor const & max = monochrome_max_.top();
107 sig.append(QChar(static_cast<short>(min.red())));
108 sig.append(QChar(static_cast<short>(min.green())));
109 sig.append(QChar(static_cast<short>(min.blue())));
110 sig.append(QChar(static_cast<short>(max.red())));
111 sig.append(QChar(static_cast<short>(max.green())));
112 sig.append(QChar(static_cast<short>(max.blue())));
118 QColor GuiPainter::computeColor(Color col)
120 return filterColor(guiApp->colorCache().get(col));
124 QColor GuiPainter::filterColor(QColor const & col)
126 if (monochrome_min_.empty())
129 // map into [min,max] interval
130 QColor const & min = monochrome_min_.top();
131 QColor const & max = monochrome_max_.top();
133 qreal v = col.valueF();
134 v *= v; // make it a bit steeper (i.e. darker)
136 qreal minr, ming, minb;
137 qreal maxr, maxg, maxb;
138 min.getRgbF(&minr, &ming, &minb);
139 max.getRgbF(&maxr, &maxg, &maxb);
143 v * (minr - maxr) + maxr,
144 v * (ming - maxg) + maxg,
145 v * (minb - maxb) + maxb);
150 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
152 QColor qmin = filterColor(guiApp->colorCache().get(min));
153 QColor qmax = filterColor(guiApp->colorCache().get(max));
154 monochrome_min_.push(qmin);
155 monochrome_max_.push(qmax);
159 void GuiPainter::leaveMonochromeMode()
161 LASSERT(!monochrome_min_.empty(), return);
162 monochrome_min_.pop();
163 monochrome_max_.pop();
167 void GuiPainter::point(int x, int y, Color col)
169 if (!isDrawingEnabled())
172 setQPainterPen(computeColor(col));
177 void GuiPainter::line(int x1, int y1, int x2, int y2,
182 if (!isDrawingEnabled())
185 setQPainterPen(computeColor(col), ls, lw);
186 bool const do_antialiasing = renderHints() & TextAntialiasing
187 && x1 != x2 && y1 != y2;
188 setRenderHint(Antialiasing, do_antialiasing);
189 drawLine(x1, y1, x2, y2);
190 setRenderHint(Antialiasing, false);
194 void GuiPainter::lines(int const * xp, int const * yp, int np,
199 if (!isDrawingEnabled())
202 // double the size if needed
204 static QVector<QPoint> points(32);
205 if (np > points.size())
206 points.resize(2 * np);
208 bool antialias = false;
209 for (int i = 0; i < np; ++i) {
210 points[i].setX(xp[i]);
211 points[i].setY(yp[i]);
213 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
215 setQPainterPen(computeColor(col), ls, lw);
216 bool const text_is_antialiased = renderHints() & TextAntialiasing;
217 setRenderHint(Antialiasing, antialias && text_is_antialiased);
218 drawPolyline(points.data(), np);
219 setRenderHint(Antialiasing, false);
223 void GuiPainter::rectangle(int x, int y, int w, int h,
228 if (!isDrawingEnabled())
231 setQPainterPen(computeColor(col), ls, lw);
232 drawRect(x, y, w, h);
236 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
238 if (!isDrawingEnabled())
241 fillRect(x, y, w, h, guiApp->colorCache().get(col));
245 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
246 int a1, int a2, Color col)
248 if (!isDrawingEnabled())
251 // LyX usings 1/64ths degree, Qt usings 1/16th
252 setQPainterPen(computeColor(col));
253 bool const do_antialiasing = renderHints() & TextAntialiasing;
254 setRenderHint(Antialiasing, do_antialiasing);
255 drawArc(x, y, w, h, a1 / 4, a2 / 4);
256 setRenderHint(Antialiasing, false);
260 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
262 graphics::GuiImage const & qlimage =
263 static_cast<graphics::GuiImage const &>(i);
265 fillRectangle(x, y, w, h, Color_graphicsbg);
267 if (!isDrawingEnabled())
270 drawImage(x, y, qlimage.image(), 0, 0, w, h);
274 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
277 return text(x, y, s, f);
281 int GuiPainter::text(int x, int y, docstring const & s,
287 /* Caution: The following ucs4 to QString conversions work for symbol fonts
288 only because they are no real conversions but simple casts in reality.
289 When we want to draw a symbol or calculate the metrics we pass the position
290 of the symbol in the font (as given in lib/symbols) as a char_type to the
291 frontend. This is just wrong, because the symbol is no UCS4 character at
292 all. You can think of this number as the code point of the symbol in a
293 custom symbol encoding. It works because this char_type is lateron again
294 interpreted as a position in the font again.
295 The correct solution would be to have extra functions for symbols, but that
296 would require to duplicate a lot of frontend and mathed support code.
298 QString str = toqstr(s);
301 // HACK: QT3 refuses to show single compose characters
302 // Still needed with Qt4?
303 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
307 QFont const & ff = getFont(f);
308 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 int const textwidth = fm.width(s);
315 if (!isDrawingEnabled())
318 textDecoration(f, x, y, textwidth);
320 // Qt4 does not display a glyph whose codepoint is the
321 // same as that of a soft-hyphen (0x00ad), unless it
322 // occurs at a line-break. As a kludge, we force Qt to
323 // render this glyph using a one-column line.
324 // This is needed for some math glyphs.
325 // Should the soft hyphen char be displayed at all?
326 // I don't think so (i.e., Qt is correct as far as
327 // texted is concerned). /spitz
328 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
329 setQPainterPen(computeColor(f.realColor()));
330 QTextLayout adsymbol(str);
331 adsymbol.setFont(ff);
332 adsymbol.beginLayout();
333 QTextLine line = adsymbol.createLine();
334 line.setNumColumns(1);
335 line.setPosition(QPointF(0, -line.ascent()));
336 adsymbol.endLayout();
337 line.draw(this, QPointF(x, y));
341 if (use_pixmap_cache_) {
343 QString key = generateStringSignature(str, f);
345 // Warning: Left bearing is in general negative! Only the case
346 // where left bearing is negative is of interest WRT the
347 // pixmap width and the text x-position.
348 // Only the left bearing of the first character is important
349 // as we always write from left to right, even for
350 // right-to-left languages.
351 int const lb = min(fm.lbearing(s[0]), 0);
352 int const mA = fm.maxAscent();
353 if (QPixmapCache::find(key, pm)) {
354 // Draw the cached pixmap.
355 drawPixmap(x + lb, y - mA, pm);
359 // Only the right bearing of the last character is
360 // important as we always write from left to right,
361 // even for right-to-left languages.
362 int const rb = fm.rbearing(s[s.size()-1]);
363 int const w = textwidth + rb - lb;
364 int const mD = fm.maxDescent();
365 int const h = mA + mD;
366 if (w > 0 && h > 0) {
368 pm.fill(Qt::transparent);
370 p.setQPainterPen(computeColor(f.realColor()));
373 // We need to draw the text as LTR as we use our own bidi code.
374 p.setLayoutDirection(Qt::LeftToRight);
375 p.drawText(-lb, mA, str);
376 QPixmapCache::insert(key, pm);
377 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
378 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
381 // Draw the new cached pixmap.
382 drawPixmap(x + lb, y - mA, pm);
388 // don't use the pixmap cache,
389 // draw directly onto the painting device
390 setQPainterPen(computeColor(f.realColor()));
394 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
395 // << " at " << x << "," << y);
400 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
402 docstring const dstr = directedString(str, f.isVisibleRightToLeft());
403 return text(x, y, dstr, f.fontInfo());
407 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
408 Color other, size_type from, size_type to)
410 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
411 FontInfo fi = f.fontInfo();
412 bool const rtl = f.isVisibleRightToLeft();
413 docstring const dstr = directedString(str, rtl);
416 int const ascent = fm.maxAscent();
417 int const height = fm.maxAscent() + fm.maxDescent();
418 int xmin = fm.pos2x(str, from, rtl);
419 int xmax = fm.pos2x(str, to, rtl);
423 // First the part in other color
424 Color const orig = fi.realColor();
425 fi.setPaintColor(other);
426 setClipRect(QRect(x + xmin, y - ascent, xmax - xmin, height));
427 int const textwidth = text(x, y, dstr, fi);
429 // Then the part in normal color
430 // Note that in Qt5, it is not possible to use Qt::UniteClip
431 fi.setPaintColor(orig);
432 setClipRect(QRect(x, y - ascent, xmin, height));
433 text(x, y, dstr, fi);
434 setClipRect(QRect(x + xmax, y - ascent, textwidth - xmax, height));
435 text(x, y, dstr, fi);
442 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
444 if (f.underbar() == FONT_ON)
445 underline(f, x, y, width);
446 if (f.strikeout() == FONT_ON)
447 strikeoutLine(f, x, y, width);
448 if (f.uuline() == FONT_ON)
449 doubleUnderline(f, x, y, width);
450 if (f.uwave() == FONT_ON)
451 // f.color() doesn't work on some circumstances
452 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
456 static int max(int a, int b) { return a > b ? a : b; }
459 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
462 fillRectangle(x, y, w, h, Color_buttonhoverbg);
464 fillRectangle(x, y, w, h, Color_buttonbg);
465 buttonFrame(x, y, w, h);
469 void GuiPainter::buttonFrame(int x, int y, int w, int h)
471 line(x, y, x, y + h - 1, Color_buttonframe);
472 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
473 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
474 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
478 void GuiPainter::rectText(int x, int y, docstring const & str,
479 FontInfo const & font, Color back, Color frame)
485 FontMetrics const & fm = theFontMetrics(font);
486 fm.rectText(str, width, ascent, descent);
488 if (back != Color_none)
489 fillRectangle(x + 1, y - ascent + 1, width - 1,
490 ascent + descent - 1, back);
492 if (frame != Color_none)
493 rectangle(x, y - ascent, width, ascent + descent, frame);
495 text(x + 3, y, str, font);
499 void GuiPainter::buttonText(int x, int y, docstring const & str,
500 FontInfo const & font, bool mouseHover)
506 FontMetrics const & fm = theFontMetrics(font);
507 fm.buttonText(str, width, ascent, descent);
509 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
511 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
512 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
516 int GuiPainter::preeditText(int x, int y, char_type c,
517 FontInfo const & font, preedit_style style)
519 FontInfo temp_font = font;
520 FontMetrics const & fm = theFontMetrics(font);
521 int ascent = fm.maxAscent();
522 int descent = fm.maxDescent();
523 int height = ascent + descent;
524 int width = fm.width(c);
527 case preedit_default:
528 // default unselecting mode.
529 fillRectangle(x, y - height + 1, width, height, Color_background);
530 dashedUnderline(font, x, y - descent + 1, width);
532 case preedit_selecting:
533 // We are in selecting mode: white text on black background.
534 fillRectangle(x, y - height + 1, width, height, Color_black);
535 temp_font.setColor(Color_white);
538 // The character comes with a cursor.
539 fillRectangle(x, y - height + 1, width, height, Color_background);
540 underline(font, x, y - descent + 1, width);
543 text(x, y - descent + 1, c, temp_font);
549 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
551 FontMetrics const & fm = theFontMetrics(f);
553 int const below = max(fm.maxDescent() / 2, 2);
555 line(x, y + below, x + width, y + below, f.realColor());
556 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
560 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
562 FontMetrics const & fm = theFontMetrics(f);
564 int const below = max(fm.maxDescent() / 2, 2);
565 int const height = max((fm.maxDescent() / 4) - 1, 1);
568 line(x, y + below, x + width, y + below, f.realColor());
570 fillRectangle(x, y + below, width, below + height, f.realColor());
574 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
576 FontMetrics const & fm = theFontMetrics(f);
578 int const middle = max((fm.maxHeight() / 4), 1);
579 int const height = middle/3;
582 line(x, y - middle, x + width, y - middle, f.realColor());
584 fillRectangle(x, y - middle, width, height, f.realColor());
588 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
590 FontMetrics const & fm = theFontMetrics(f);
592 int const below = max(fm.maxDescent() / 2, 2);
593 int height = max((fm.maxDescent() / 4) - 1, 1);
598 for (int n = 0; n != height; ++n)
599 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
603 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
605 setQPainterPen(computeColor(col));
607 int const xend = x + width;
609 //FIXME: I am not sure if Antialiasing gives the best effect.
610 //setRenderHint(Antialiasing, true);
613 drawLine(x, y - height, x + step, y + height);
615 drawLine(x, y + height, x + step/2, y + height);
618 //setRenderHint(Antialiasing, false);
621 } // namespace frontend