X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Ffrontends%2Fqt4%2FGuiPainter.cpp;h=b2ccbfc41c60664f683635159eec21f8e7519d3b;hb=b6b263b9fc4cd70849c60de9f8849c346b3c12ab;hp=9b06147d2c9068df9acbad6fbd98d655f810ce71;hpb=9e4780ffd82c888442b6b6a8863a335154bc9d38;p=lyx.git diff --git a/src/frontends/qt4/GuiPainter.cpp b/src/frontends/qt4/GuiPainter.cpp index 9b06147d2c..b2ccbfc41c 100644 --- a/src/frontends/qt4/GuiPainter.cpp +++ b/src/frontends/qt4/GuiPainter.cpp @@ -13,41 +13,49 @@ #include "GuiPainter.h" +#include "ColorCache.h" #include "GuiApplication.h" +#include "GuiFontLoader.h" #include "GuiFontMetrics.h" #include "GuiImage.h" - -#include "GuiApplication.h" #include "qt_helpers.h" -#include "support/debug.h" +#include "Font.h" #include "Language.h" #include "LyXRC.h" +#include "insets/Inset.h" + +#include "support/lassert.h" +#include "support/debug.h" + #include #include // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when // drawing text. This is especially useful for older PPC/Mac systems. -#if defined(Q_WS_X11) +#if defined(Q_WS_X11) || defined(QPA_XCB) #define USE_PIXMAP_CACHE 0 #else #define USE_PIXMAP_CACHE 1 #endif using namespace std; +using namespace lyx::support; namespace lyx { namespace frontend { -GuiPainter::GuiPainter(QPaintDevice * device) - : QPainter(device), Painter(), +const int Painter::thin_line = 0; + +GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio) + : QPainter(device), Painter(pixel_ratio), use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE) { // new QPainter has default QPen: current_color_ = guiApp->colorCache().get(Color_black); current_ls_ = line_solid; - current_lw_ = line_thin; + current_lw_ = thin_line; } @@ -59,7 +67,7 @@ GuiPainter::~GuiPainter() void GuiPainter::setQPainterPen(QColor const & col, - Painter::line_style ls, Painter::line_width lw) + Painter::line_style ls, int lw) { if (col == current_color_ && ls == current_ls_ && lw == current_lw_) return; @@ -76,10 +84,7 @@ void GuiPainter::setQPainterPen(QColor const & col, case line_onoffdash: pen.setStyle(Qt::DotLine); break; } - switch (lw) { - case line_thin: pen.setWidth(0); break; - case line_thick: pen.setWidth(3); break; - } + pen.setWidth(lw); setPen(pen); } @@ -92,7 +97,9 @@ QString GuiPainter::generateStringSignature(QString const & str, FontInfo const sig.append(QChar(static_cast(f.series()))); sig.append(QChar(static_cast(f.realShape()))); sig.append(QChar(static_cast(f.size()))); - sig.append(QChar(static_cast(f.color()))); + Color const & color = f.realColor(); + sig.append(QChar(static_cast(color.baseColor))); + sig.append(QChar(static_cast(color.mergeColor))); if (!monochrome_min_.empty()) { QColor const & min = monochrome_min_.top(); QColor const & max = monochrome_max_.top(); @@ -107,7 +114,7 @@ QString GuiPainter::generateStringSignature(QString const & str, FontInfo const } -QColor GuiPainter::computeColor(ColorCode col) +QColor GuiPainter::computeColor(Color col) { return filterColor(guiApp->colorCache().get(col)); } @@ -139,7 +146,7 @@ QColor GuiPainter::filterColor(QColor const & col) } -void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & max) +void GuiPainter::enterMonochromeMode(Color const & min, Color const & max) { QColor qmin = filterColor(guiApp->colorCache().get(min)); QColor qmax = filterColor(guiApp->colorCache().get(max)); @@ -150,13 +157,13 @@ void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & ma void GuiPainter::leaveMonochromeMode() { - BOOST_ASSERT(!monochrome_min_.empty()); + LASSERT(!monochrome_min_.empty(), return); monochrome_min_.pop(); monochrome_max_.pop(); } -void GuiPainter::point(int x, int y, ColorCode col) +void GuiPainter::point(int x, int y, Color col) { if (!isDrawingEnabled()) return; @@ -167,9 +174,9 @@ void GuiPainter::point(int x, int y, ColorCode col) void GuiPainter::line(int x1, int y1, int x2, int y2, - ColorCode col, + Color col, line_style ls, - line_width lw) + int lw) { if (!isDrawingEnabled()) return; @@ -184,14 +191,16 @@ void GuiPainter::line(int x1, int y1, int x2, int y2, void GuiPainter::lines(int const * xp, int const * yp, int np, - ColorCode col, + Color col, + fill_style fs, line_style ls, - line_width lw) + int lw) { if (!isDrawingEnabled()) return; // double the size if needed + // FIXME THREAD static QVector points(32); if (np > points.size()) points.resize(2 * np); @@ -203,18 +212,27 @@ void GuiPainter::lines(int const * xp, int const * yp, int np, if (i != 0) antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i]; } - setQPainterPen(computeColor(col), ls, lw); + QColor const color = computeColor(col); + setQPainterPen(color, ls, lw); bool const text_is_antialiased = renderHints() & TextAntialiasing; setRenderHint(Antialiasing, antialias && text_is_antialiased); - drawPolyline(points.data(), np); + if (fs == fill_none) { + drawPolyline(points.data(), np); + } else { + QBrush const oldbrush = brush(); + setBrush(QBrush(color)); + drawPolygon(points.data(), np, fs == fill_oddeven ? + Qt::OddEvenFill : Qt::WindingFill); + setBrush(oldbrush); + } setRenderHint(Antialiasing, false); } void GuiPainter::rectangle(int x, int y, int w, int h, - ColorCode col, + Color col, line_style ls, - line_width lw) + int lw) { if (!isDrawingEnabled()) return; @@ -224,14 +242,17 @@ void GuiPainter::rectangle(int x, int y, int w, int h, } -void GuiPainter::fillRectangle(int x, int y, int w, int h, ColorCode col) +void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col) { + if (!isDrawingEnabled()) + return; + fillRect(x, y, w, h, guiApp->colorCache().get(col)); } void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h, - int a1, int a2, ColorCode col) + int a1, int a2, Color col) { if (!isDrawingEnabled()) return; @@ -255,47 +276,24 @@ void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i) if (!isDrawingEnabled()) return; - drawImage(x, y, qlimage.qimage(), 0, 0, w, h); + QImage const image = qlimage.image(); + QRectF const drect = QRectF(x, y, w, h); + QRectF const srect = QRectF(0, 0, image.width(), image.height()); + drawImage(drect, image, srect); } int GuiPainter::text(int x, int y, char_type c, FontInfo const & f) { - docstring s(1, c); - return text(x, y, s, f); -} - - -int GuiPainter::smallCapsText(int x, int y, - QString const & s, FontInfo const & f) -{ - FontInfo smallfont(f); - smallfont.decSize().decSize().setShape(UP_SHAPE); - - QFont const & qfont = guiApp->guiFontLoader().get(f); - QFont const & qsmallfont = guiApp->guiFontLoader().get(smallfont); - - setQPainterPen(computeColor(f.realColor())); - int textwidth = 0; - size_t const ls = s.length(); - for (unsigned int i = 0; i < ls; ++i) { - QChar const c = s[i].toUpper(); - if (c != s.at(i)) { - setFont(qsmallfont); - } else { - setFont(qfont); - } - if (isDrawingEnabled()) - drawText(x + textwidth, y, c); - textwidth += fontMetrics().width(c); - } - return textwidth; + return text(x, y, docstring(1, c), f); } int GuiPainter::text(int x, int y, docstring const & s, - FontInfo const & f) + FontInfo const & f, bool const rtl, + double const wordspacing) { + //LYXERR0("text: x=" << x << ", s=" << s); if (s.empty()) return 0; @@ -319,99 +317,165 @@ int GuiPainter::text(int x, int y, docstring const & s, str = ' ' + str; #endif - GuiFontInfo & fi = guiApp->guiFontLoader().fontinfo(f); - - int textwidth; - - if (f.realShape() == SMALLCAPS_SHAPE) { - textwidth = smallCapsText(x, y, str, f); - if (f.underbar() == FONT_ON) - underline(f, x, y, textwidth); - return textwidth; - } + QFont ff = getFont(f); + ff.setWordSpacing(wordspacing); + GuiFontMetrics const & fm = getFontMetrics(f); // Here we use the font width cache instead of // textwidth = fontMetrics().width(str); // because the above is awfully expensive on MacOSX - textwidth = fi.metrics.width(s); - if (f.underbar() == FONT_ON) - underline(f, x, y, textwidth); + int const textwidth = fm.width(s); if (!isDrawingEnabled()) return textwidth; - // Qt4 does not display a glyph whose codepoint is the - // same as that of a soft-hyphen (0x00ad), unless it - // occurs at a line-break. As a kludge, we force Qt to - // render this glyph using a one-column line. - if (s.size() == 1 && str[0].unicode() == 0x00ad) { - setQPainterPen(computeColor(f.realColor())); - QTextLayout adsymbol(str); - adsymbol.setFont(fi.font); - adsymbol.beginLayout(); - QTextLine line = adsymbol.createLine(); - line.setNumColumns(1); - line.setPosition(QPointF(0, -line.ascent())); - adsymbol.endLayout(); - line.draw(this, QPointF(x, y)); - return textwidth; - } - - if (!use_pixmap_cache_) { - // don't use the pixmap cache, - // draw directly onto the painting device - setQPainterPen(computeColor(f.realColor())); - if (font() != fi.font) - setFont(fi.font); - // We need to draw the text as LTR as we use our own bidi code. - setLayoutDirection(Qt::LeftToRight); - // We need to draw the text as LTR as we use our own bidi code. - setLayoutDirection(Qt::LeftToRight); - drawText(x, y, str); - //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8()) - // << " at " << x << "," << y); - return textwidth; - } + textDecoration(f, x, y, textwidth); + + if (use_pixmap_cache_) { + QPixmap pm; + QString key = generateStringSignature(str, f); + + // Warning: Left bearing is in general negative! Only the case + // where left bearing is negative is of interest WRT the + // pixmap width and the text x-position. + // Only the left bearing of the first character is important + // as we always write from left to right, even for + // right-to-left languages. + int const lb = min(fm.lbearing(s[0]), 0); + int const mA = fm.maxAscent(); + if (QPixmapCache::find(key, pm)) { + // Draw the cached pixmap. + drawPixmap(x + lb, y - mA, pm); + return textwidth; + } - QPixmap pm; - QString key = generateStringSignature(str, f); - // Warning: Left bearing is in general negative! Only the case - // where left bearing is negative is of interest WRT the the - // pixmap width and the text x-position. - // Only the left bearing of the first character is important - // as we always write from left to right, even for - // right-to-left languages. - int const lb = min(fi.metrics.lbearing(s[0]), 0); - int const mA = fi.metrics.maxAscent(); - if (!QPixmapCache::find(key, pm)) { // Only the right bearing of the last character is // important as we always write from left to right, // even for right-to-left languages. - int const rb = fi.metrics.rbearing(s[s.size()-1]); + int const rb = fm.rbearing(s[s.size()-1]); int const w = textwidth + rb - lb; - int const mD = fi.metrics.maxDescent(); + int const mD = fm.maxDescent(); int const h = mA + mD; - pm = QPixmap(w, h); - pm.fill(Qt::transparent); - GuiPainter p(&pm); - p.setQPainterPen(computeColor(f.realColor())); - if (p.font() != fi.font) - p.setFont(fi.font); - // We need to draw the text as LTR as we use our own bidi code. - p.setLayoutDirection(Qt::LeftToRight); - p.drawText(-lb, mA, str); - QPixmapCache::insert(key, pm); - //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD - // << " w=" << w << " lb=" << lb << " tw=" << textwidth - // << " rb=" << rb); + if (w > 0 && h > 0) { + pm = QPixmap(static_cast(pixelRatio() * w), + static_cast(pixelRatio() * h)); +#if QT_VERSION >= 0x050000 + pm.setDevicePixelRatio(pixelRatio()); +#endif + pm.fill(Qt::transparent); + GuiPainter p(&pm, pixelRatio()); + p.setQPainterPen(computeColor(f.realColor())); + if (p.font() != ff) + p.setFont(ff); + // We need to draw the text as LTR as we use our own bidi code. + p.setLayoutDirection(Qt::LeftToRight); + p.drawText(-lb, mA, str); + QPixmapCache::insert(key, pm); + //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD + // << " w=" << w << " lb=" << lb << " tw=" << textwidth + // << " rb=" << rb); + + // Draw the new cached pixmap. + drawPixmap(x + lb, y - mA, pm); + } + return textwidth; } - // Draw the cached pixmap. - drawPixmap(x + lb, y - mA, pm); + + // don't use the pixmap cache, + // draw directly onto the painting device + setQPainterPen(computeColor(f.realColor())); + if (font() != ff) + setFont(ff); + + /* In LyX, the character direction is forced by the language. + * Therefore, we have to signal that fact to Qt. + */ +#if 1 + /* Use unicode override characters to enforce drawing direction + * Source: http://www.iamcal.com/understanding-bidirectional-text/ + */ + if (rtl) + // Right-to-left override: forces to draw text right-to-left + str = QChar(0x202E) + str; + else + // Left-to-right override: forces to draw text left-to-right + str = QChar(0x202D) + str; + drawText(x, y, str); +#else + /* This looks like a cleaner solution, but it has drawbacks + * - does not work reliably (Mac OS X, ...) + * - it is not really documented + * Keep it here for now, in case it can be helpful + */ + //This is much stronger than setLayoutDirection. + int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight; + drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0, + flag | Qt::TextDontClip, + str); +#endif + //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8()) + // << " at " << x << "," << y); + return textwidth; +} + + +int GuiPainter::text(int x, int y, docstring const & str, Font const & f, + double const wordspacing) +{ + return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing); +} + + +int GuiPainter::text(int x, int y, docstring const & str, Font const & f, + Color other, size_type const from, size_type const to, + double const wordspacing) +{ + GuiFontMetrics const & fm = getFontMetrics(f.fontInfo()); + FontInfo fi = f.fontInfo(); + bool const rtl = f.isVisibleRightToLeft(); + + // dimensions + int const ascent = fm.maxAscent(); + int const height = fm.maxAscent() + fm.maxDescent(); + int xmin = fm.pos2x(str, from, rtl, wordspacing); + int xmax = fm.pos2x(str, to, rtl, wordspacing); + if (xmin > xmax) + swap(xmin, xmax); + + // First the part in other color + Color const orig = fi.realColor(); + fi.setPaintColor(other); + QRegion const clip(x + xmin, y - ascent, xmax - xmin, height); + setClipRegion(clip); + int const textwidth = text(x, y, str, fi, rtl, wordspacing); + + // Then the part in normal color + // Note that in Qt5, it is not possible to use Qt::UniteClip, + // therefore QRegion is used. + fi.setPaintColor(orig); + QRegion region(viewport()); + setClipRegion(region - clip); + text(x, y, str, fi, rtl, wordspacing); + setClipping(false); return textwidth; } +void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width) +{ + if (f.underbar() == FONT_ON) + underline(f, x, y, width); + if (f.strikeout() == FONT_ON) + strikeoutLine(f, x, y, width); + if (f.uuline() == FONT_ON) + doubleUnderline(f, x, y, width); + if (f.uwave() == FONT_ON) + // f.color() doesn't work on some circumstances + wavyHorizontalLine(x, y, width, f.realColor().baseColor); +} + + static int max(int a, int b) { return a > b ? a : b; } @@ -433,10 +497,9 @@ void GuiPainter::buttonFrame(int x, int y, int w, int h) line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe); } -static int const d = Inset::TEXT_TO_INSET_OFFSET / 2; void GuiPainter::rectText(int x, int y, docstring const & str, - FontInfo const & font, ColorCode back, ColorCode frame) + FontInfo const & font, Color back, Color frame) { int width; int ascent; @@ -466,6 +529,8 @@ void GuiPainter::buttonText(int x, int y, docstring const & str, FontMetrics const & fm = theFontMetrics(font); fm.buttonText(str, width, ascent, descent); + static int const d = Inset::TEXT_TO_INSET_OFFSET / 2; + button(x + d, y - ascent, width - d, descent + ascent, mouseHover); text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font); } @@ -504,17 +569,37 @@ int GuiPainter::preeditText(int x, int y, char_type c, } -void GuiPainter::underline(FontInfo const & f, int x, int y, int width) +void GuiPainter::underline(FontInfo const & f, int x, int y, int width, + line_style ls) { FontMetrics const & fm = theFontMetrics(f); + int const pos = fm.underlinePos(); - int const below = max(fm.maxDescent() / 2, 2); - int const height = max((fm.maxDescent() / 4) - 1, 1); + line(x, y + pos, x + width, y + pos, + f.realColor(), ls, fm.lineWidth()); +} - if (height < 2) - line(x, y + below, x + width, y + below, f.color()); - else - fillRectangle(x, y + below, width, below + height, f.color()); + +void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width) +{ + FontMetrics const & fm = theFontMetrics(f); + int const pos = fm.strikeoutPos(); + + line(x, y - pos, x + width, y - pos, + f.realColor(), line_solid, fm.lineWidth()); +} + + +void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width) +{ + FontMetrics const & fm = theFontMetrics(f); + int const pos1 = fm.underlinePos() + fm.lineWidth(); + int const pos2 = fm.underlinePos() - fm.lineWidth() + 1; + + line(x, y + pos1, x + width, y + pos1, + f.realColor(), line_solid, fm.lineWidth()); + line(x, y + pos2, x + width, y + pos2, + f.realColor(), line_solid, fm.lineWidth()); } @@ -529,7 +614,26 @@ void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width) height += below; for (int n = 0; n != height; ++n) - line(x, y + below + n, x + width, y + below + n, f.color(), line_onoffdash); + line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash); +} + + +void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col) +{ + setQPainterPen(computeColor(col)); + int const step = 2; + int const xend = x + width; + int height = 1; + //FIXME: I am not sure if Antialiasing gives the best effect. + //setRenderHint(Antialiasing, true); + while (x < xend) { + height = - height; + drawLine(x, y - height, x + step, y + height); + x += step; + drawLine(x, y + height, x + step/2, y + height); + x += step/2; + } + //setRenderHint(Antialiasing, false); } } // namespace frontend