#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 "debug.h"
+#include "Font.h"
#include "Language.h"
#include "LyXRC.h"
-#include "support/unicode.h"
+#include "insets/Inset.h"
+
+#include "support/lassert.h"
+#include "support/debug.h"
#include <QPixmapCache>
#include <QTextLayout>
// 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 (QT_VERSION < 0x040200) || 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 std::endl;
-using std::string;
-
+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;
}
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;
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);
}
sig.append(QChar(static_cast<short>(f.series())));
sig.append(QChar(static_cast<short>(f.realShape())));
sig.append(QChar(static_cast<short>(f.size())));
- sig.append(QChar(static_cast<short>(f.color())));
+ Color const & color = f.realColor();
+ sig.append(QChar(static_cast<short>(color.baseColor)));
+ sig.append(QChar(static_cast<short>(color.mergeColor)));
if (!monochrome_min_.empty()) {
QColor const & min = monochrome_min_.top();
QColor const & max = monochrome_max_.top();
}
-QColor GuiPainter::computeColor(ColorCode col)
+QColor GuiPainter::computeColor(Color col)
{
return filterColor(guiApp->colorCache().get(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));
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;
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;
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<QPoint> points(32);
if (np > points.size())
points.resize(2 * 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;
}
-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;
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);
+ return text(x, y, docstring(1, c), f);
}
-int GuiPainter::smallCapsText(int x, int y,
- QString const & s, FontInfo const & f)
+void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
{
- 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;
+ 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
}
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;
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);
+ // Note that we have to take in account space stretching (word spacing)
+ int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
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 " << std::string(str.toUtf8())
- // << " at " << x << "," << y << std::endl;
- 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.
+ // FIXME: this is probably broken for RTL now that we draw full strings.
+ // Morover the first/last element is possibly not the right one since the glyph may have changed.
+ 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 = std::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 << endl;
+ if (w > 0 && h > 0) {
+ pm = QPixmap(static_cast<int>(pixelRatio() * w),
+ static_cast<int>(pixelRatio() * h));
+#if QT_VERSION >= 0x050000
+ pm.setDevicePixelRatio(pixelRatio());
+#endif
+ pm.fill(Qt::transparent);
+ GuiPainter p(&pm, pixelRatio());
+ p.do_drawText(-lb, mA, str, rtl, f, ff);
+ 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);
+ //rectangle(x-lb, y-mA, w, h, Color_green);
+ }
+ return textwidth;
}
- // Draw the cached pixmap.
- drawPixmap(x + lb, y - mA, pm);
+
+ // don't use the pixmap cache,
+ do_drawText(x, y, str, rtl, f, ff);
+ //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; }
+
+
+void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
+{
+ if (mouseHover)
+ fillRectangle(x, y, w, h, Color_buttonhoverbg);
+ else
+ fillRectangle(x, y, w, h, Color_buttonbg);
+ buttonFrame(x, y, w, h);
+}
+
+
+void GuiPainter::buttonFrame(int x, int y, int w, int h)
+{
+ line(x, y, x, y + h - 1, Color_buttonframe);
+ line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
+ line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
+ line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
+}
+
+
+void GuiPainter::rectText(int x, int y, docstring const & str,
+ FontInfo const & font, Color back, Color frame)
+{
+ int width;
+ int ascent;
+ int descent;
+
+ FontMetrics const & fm = theFontMetrics(font);
+ fm.rectText(str, width, ascent, descent);
+
+ if (back != Color_none)
+ fillRectangle(x + 1, y - ascent + 1, width - 1,
+ ascent + descent - 1, back);
+
+ if (frame != Color_none)
+ rectangle(x, y - ascent, width, ascent + descent, frame);
+
+ text(x + 3, y, str, font);
+}
+
+
+void GuiPainter::buttonText(int x, int y, docstring const & str,
+ FontInfo const & font, bool mouseHover)
+{
+ int width;
+ int ascent;
+ int descent;
+
+ 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);
+}
+
+
+int GuiPainter::preeditText(int x, int y, char_type c,
+ FontInfo const & font, preedit_style style)
+{
+ FontInfo temp_font = font;
+ FontMetrics const & fm = theFontMetrics(font);
+ int ascent = fm.maxAscent();
+ int descent = fm.maxDescent();
+ int height = ascent + descent;
+ int width = fm.width(c);
+
+ switch (style) {
+ case preedit_default:
+ // default unselecting mode.
+ fillRectangle(x, y - height + 1, width, height, Color_background);
+ dashedUnderline(font, x, y - descent + 1, width);
+ break;
+ case preedit_selecting:
+ // We are in selecting mode: white text on black background.
+ fillRectangle(x, y - height + 1, width, height, Color_black);
+ temp_font.setColor(Color_white);
+ break;
+ case preedit_cursor:
+ // The character comes with a cursor.
+ fillRectangle(x, y - height + 1, width, height, Color_background);
+ underline(font, x, y - descent + 1, width);
+ break;
+ }
+ text(x, y - descent + 1, c, temp_font);
+
+ return 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();
+
+ line(x, y + pos, x + width, y + pos,
+ f.realColor(), ls, fm.lineWidth());
+}
+
+
+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());
+}
+
+
+void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
+{
+ FontMetrics const & fm = theFontMetrics(f);
+
+ int const below = max(fm.maxDescent() / 2, 2);
+ int height = max((fm.maxDescent() / 4) - 1, 1);
+
+ if (height >= 2)
+ height += below;
+
+ for (int n = 0; n != height; ++n)
+ 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
} // namespace lyx