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 "GuiApplication.h"
17 #include "GuiFontLoader.h"
18 #include "GuiFontMetrics.h"
20 #include "qt_helpers.h"
25 #include "support/lassert.h"
26 #include "support/debug.h"
28 #include <QPixmapCache>
29 #include <QTextLayout>
31 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
32 // drawing text. This is especially useful for older PPC/Mac systems.
34 #define USE_PIXMAP_CACHE 0
36 #define USE_PIXMAP_CACHE 1
44 GuiPainter::GuiPainter(QPaintDevice * device)
45 : QPainter(device), Painter(),
46 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
48 // new QPainter has default QPen:
49 current_color_ = guiApp->colorCache().get(Color_black);
50 current_ls_ = line_solid;
51 current_lw_ = line_thin;
55 GuiPainter::~GuiPainter()
58 //lyxerr << "GuiPainter::end()" << endl;
62 void GuiPainter::setQPainterPen(QColor const & col,
63 Painter::line_style ls, Painter::line_width lw)
65 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
72 QPen pen = QPainter::pen();
76 case line_solid: pen.setStyle(Qt::SolidLine); break;
77 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
81 case line_thin: pen.setWidth(0); break;
82 case line_thick: pen.setWidth(3); break;
89 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
92 sig.append(QChar(static_cast<short>(f.family())));
93 sig.append(QChar(static_cast<short>(f.series())));
94 sig.append(QChar(static_cast<short>(f.realShape())));
95 sig.append(QChar(static_cast<short>(f.size())));
96 sig.append(QChar(static_cast<short>(f.color())));
97 if (!monochrome_min_.empty()) {
98 QColor const & min = monochrome_min_.top();
99 QColor const & max = monochrome_max_.top();
100 sig.append(QChar(static_cast<short>(min.red())));
101 sig.append(QChar(static_cast<short>(min.green())));
102 sig.append(QChar(static_cast<short>(min.blue())));
103 sig.append(QChar(static_cast<short>(max.red())));
104 sig.append(QChar(static_cast<short>(max.green())));
105 sig.append(QChar(static_cast<short>(max.blue())));
111 QColor GuiPainter::computeColor(ColorCode col)
113 return filterColor(guiApp->colorCache().get(col));
117 QColor GuiPainter::filterColor(QColor const & col)
119 if (monochrome_min_.empty())
122 // map into [min,max] interval
123 QColor const & min = monochrome_min_.top();
124 QColor const & max = monochrome_max_.top();
126 qreal v = col.valueF();
127 v *= v; // make it a bit steeper (i.e. darker)
129 qreal minr, ming, minb;
130 qreal maxr, maxg, maxb;
131 min.getRgbF(&minr, &ming, &minb);
132 max.getRgbF(&maxr, &maxg, &maxb);
136 v * (minr - maxr) + maxr,
137 v * (ming - maxg) + maxg,
138 v * (minb - maxb) + maxb);
143 void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & max)
145 QColor qmin = filterColor(guiApp->colorCache().get(min));
146 QColor qmax = filterColor(guiApp->colorCache().get(max));
147 monochrome_min_.push(qmin);
148 monochrome_max_.push(qmax);
152 void GuiPainter::leaveMonochromeMode()
154 LASSERT(!monochrome_min_.empty(), /**/);
155 monochrome_min_.pop();
156 monochrome_max_.pop();
160 void GuiPainter::point(int x, int y, ColorCode col)
162 if (!isDrawingEnabled())
165 setQPainterPen(computeColor(col));
170 void GuiPainter::line(int x1, int y1, int x2, int y2,
175 if (!isDrawingEnabled())
178 setQPainterPen(computeColor(col), ls, lw);
179 bool const do_antialiasing = renderHints() & TextAntialiasing
180 && x1 != x2 && y1 != y2;
181 setRenderHint(Antialiasing, do_antialiasing);
182 drawLine(x1, y1, x2, y2);
183 setRenderHint(Antialiasing, false);
187 void GuiPainter::lines(int const * xp, int const * yp, int np,
192 if (!isDrawingEnabled())
195 // double the size if needed
196 static QVector<QPoint> points(32);
197 if (np > points.size())
198 points.resize(2 * np);
200 bool antialias = false;
201 for (int i = 0; i < np; ++i) {
202 points[i].setX(xp[i]);
203 points[i].setY(yp[i]);
205 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
207 setQPainterPen(computeColor(col), ls, lw);
208 bool const text_is_antialiased = renderHints() & TextAntialiasing;
209 setRenderHint(Antialiasing, antialias && text_is_antialiased);
210 drawPolyline(points.data(), np);
211 setRenderHint(Antialiasing, false);
215 void GuiPainter::rectangle(int x, int y, int w, int h,
220 if (!isDrawingEnabled())
223 setQPainterPen(computeColor(col), ls, lw);
224 drawRect(x, y, w, h);
228 void GuiPainter::fillRectangle(int x, int y, int w, int h, ColorCode col)
230 fillRect(x, y, w, h, guiApp->colorCache().get(col));
234 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
235 int a1, int a2, ColorCode col)
237 if (!isDrawingEnabled())
240 // LyX usings 1/64ths degree, Qt usings 1/16th
241 setQPainterPen(computeColor(col));
242 bool const do_antialiasing = renderHints() & TextAntialiasing;
243 setRenderHint(Antialiasing, do_antialiasing);
244 drawArc(x, y, w, h, a1 / 4, a2 / 4);
245 setRenderHint(Antialiasing, false);
249 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
251 graphics::GuiImage const & qlimage =
252 static_cast<graphics::GuiImage const &>(i);
254 fillRectangle(x, y, w, h, Color_graphicsbg);
256 if (!isDrawingEnabled())
259 drawImage(x, y, qlimage.qimage(), 0, 0, w, h);
263 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
266 return text(x, y, s, f);
270 int GuiPainter::smallCapsText(int x, int y,
271 QString const & s, FontInfo const & f)
273 FontInfo smallfont(f);
274 smallfont.decSize().decSize().setShape(UP_SHAPE);
276 QFont const & qfont = getFont(f);
277 QFont const & qsmallfont = getFont(smallfont);
279 setQPainterPen(computeColor(f.realColor()));
281 size_t const ls = s.length();
282 for (unsigned int i = 0; i < ls; ++i) {
283 QChar const c = s[i].toUpper();
289 if (isDrawingEnabled())
290 drawText(x + textwidth, y, c);
291 textwidth += fontMetrics().width(c);
297 int GuiPainter::text(int x, int y, docstring const & s,
303 /* Caution: The following ucs4 to QString conversions work for symbol fonts
304 only because they are no real conversions but simple casts in reality.
305 When we want to draw a symbol or calculate the metrics we pass the position
306 of the symbol in the font (as given in lib/symbols) as a char_type to the
307 frontend. This is just wrong, because the symbol is no UCS4 character at
308 all. You can think of this number as the code point of the symbol in a
309 custom symbol encoding. It works because this char_type is lateron again
310 interpreted as a position in the font again.
311 The correct solution would be to have extra functions for symbols, but that
312 would require to duplicate a lot of frontend and mathed support code.
314 QString str = toqstr(s);
317 // HACK: QT3 refuses to show single compose characters
318 // Still needed with Qt4?
319 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
323 QFont const & ff = getFont(f);
324 GuiFontMetrics const & fm = getFontMetrics(f);
328 if (f.realShape() == SMALLCAPS_SHAPE) {
329 textwidth = smallCapsText(x, y, str, f);
330 if (f.underbar() == FONT_ON)
331 underline(f, x, y, textwidth);
335 // Here we use the font width cache instead of
336 // textwidth = fontMetrics().width(str);
337 // because the above is awfully expensive on MacOSX
338 textwidth = fm.width(s);
339 if (f.underbar() == FONT_ON)
340 underline(f, x, y, textwidth);
342 if (!isDrawingEnabled())
345 // Qt4 does not display a glyph whose codepoint is the
346 // same as that of a soft-hyphen (0x00ad), unless it
347 // occurs at a line-break. As a kludge, we force Qt to
348 // render this glyph using a one-column line.
349 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
350 setQPainterPen(computeColor(f.realColor()));
351 QTextLayout adsymbol(str);
352 adsymbol.setFont(ff);
353 adsymbol.beginLayout();
354 QTextLine line = adsymbol.createLine();
355 line.setNumColumns(1);
356 line.setPosition(QPointF(0, -line.ascent()));
357 adsymbol.endLayout();
358 line.draw(this, QPointF(x, y));
362 if (!use_pixmap_cache_) {
363 // don't use the pixmap cache,
364 // draw directly onto the painting device
365 setQPainterPen(computeColor(f.realColor()));
368 // We need to draw the text as LTR as we use our own bidi code.
369 setLayoutDirection(Qt::LeftToRight);
371 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
372 // << " at " << x << "," << y);
377 QString key = generateStringSignature(str, f);
378 // Warning: Left bearing is in general negative! Only the case
379 // where left bearing is negative is of interest WRT the the
380 // pixmap width and the text x-position.
381 // Only the left bearing of the first character is important
382 // as we always write from left to right, even for
383 // right-to-left languages.
384 int const lb = min(fm.lbearing(s[0]), 0);
385 int const mA = fm.maxAscent();
386 if (!QPixmapCache::find(key, pm)) {
387 // Only the right bearing of the last character is
388 // important as we always write from left to right,
389 // even for right-to-left languages.
390 int const rb = fm.rbearing(s[s.size()-1]);
391 int const w = textwidth + rb - lb;
392 int const mD = fm.maxDescent();
393 int const h = mA + mD;
395 pm.fill(Qt::transparent);
397 p.setQPainterPen(computeColor(f.realColor()));
400 // We need to draw the text as LTR as we use our own bidi code.
401 p.setLayoutDirection(Qt::LeftToRight);
402 p.drawText(-lb, mA, str);
403 QPixmapCache::insert(key, pm);
404 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
405 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
408 // Draw the cached pixmap.
409 drawPixmap(x + lb, y - mA, pm);
415 static int max(int a, int b) { return a > b ? a : b; }
418 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
421 fillRectangle(x, y, w, h, Color_buttonhoverbg);
423 fillRectangle(x, y, w, h, Color_buttonbg);
424 buttonFrame(x, y, w, h);
428 void GuiPainter::buttonFrame(int x, int y, int w, int h)
430 line(x, y, x, y + h - 1, Color_buttonframe);
431 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
432 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
433 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
437 void GuiPainter::rectText(int x, int y, docstring const & str,
438 FontInfo const & font, ColorCode back, ColorCode frame)
444 FontMetrics const & fm = theFontMetrics(font);
445 fm.rectText(str, width, ascent, descent);
447 if (back != Color_none)
448 fillRectangle(x + 1, y - ascent + 1, width - 1,
449 ascent + descent - 1, back);
451 if (frame != Color_none)
452 rectangle(x, y - ascent, width, ascent + descent, frame);
454 text(x + 3, y, str, font);
458 void GuiPainter::buttonText(int x, int y, docstring const & str,
459 FontInfo const & font, bool mouseHover)
465 FontMetrics const & fm = theFontMetrics(font);
466 fm.buttonText(str, width, ascent, descent);
468 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
470 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
471 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
475 int GuiPainter::preeditText(int x, int y, char_type c,
476 FontInfo const & font, preedit_style style)
478 FontInfo temp_font = font;
479 FontMetrics const & fm = theFontMetrics(font);
480 int ascent = fm.maxAscent();
481 int descent = fm.maxDescent();
482 int height = ascent + descent;
483 int width = fm.width(c);
486 case preedit_default:
487 // default unselecting mode.
488 fillRectangle(x, y - height + 1, width, height, Color_background);
489 dashedUnderline(font, x, y - descent + 1, width);
491 case preedit_selecting:
492 // We are in selecting mode: white text on black background.
493 fillRectangle(x, y - height + 1, width, height, Color_black);
494 temp_font.setColor(Color_white);
497 // The character comes with a cursor.
498 fillRectangle(x, y - height + 1, width, height, Color_background);
499 underline(font, x, y - descent + 1, width);
502 text(x, y - descent + 1, c, temp_font);
508 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
510 FontMetrics const & fm = theFontMetrics(f);
512 int const below = max(fm.maxDescent() / 2, 2);
513 int const height = max((fm.maxDescent() / 4) - 1, 1);
516 line(x, y + below, x + width, y + below, f.color());
518 fillRectangle(x, y + below, width, below + height, f.color());
522 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
524 FontMetrics const & fm = theFontMetrics(f);
526 int const below = max(fm.maxDescent() / 2, 2);
527 int height = max((fm.maxDescent() / 4) - 1, 1);
532 for (int n = 0; n != height; ++n)
533 line(x, y + below + n, x + width, y + below + n, f.color(), line_onoffdash);
536 } // namespace frontend