QMacStyle: Fix drawing of PE_IndicatorMenuCheckMark

... And refactor to use it for drawing CE_MenuItem.

This removes a couple calls to HITheme. Instead, we
use CoreText to render the checkmark character. Notice
that this is what we were doing with HITheme for
CE_MenuItem. Drawing PE_IndicatorMenuCheckMark was
done manually, and in a way that has not been updated.

Notice also that the choice of color from the palette
has also been fixed to look for the State_Selected state
instead of the State_On state, in accordance with
QCommonStyle. In fact, on macOS, the checkmark is never
rendered when the State_On bit is clear. Moreover, we
systematically pick the checkmark color from the style
option palette.

[ChangeLog][QtWidgets][QMacStyle] PE_IndicatorMenuCheckMark
looks for State_On instead of the State_Selected to set its
highlighted state. Its color is picked from the style option
palette.

Change-Id: Ib033df357fd83080cb4320c43949f75bb079c8a5
Task-number: QTBUG-57470
Reviewed-by: Jake Petroules <jake.petroules@qt.io>
This commit is contained in:
Gabriel de Dietrich 2016-12-20 19:12:36 -08:00
parent 64f4108c3a
commit 0e810e27a5

View File

@ -3301,32 +3301,60 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai
}
break;
case PE_IndicatorMenuCheckMark: {
const int checkw = 8;
const int checkh = 8;
const int xoff = qMax(0, (opt->rect.width() - checkw) / 2);
const int yoff = qMax(0, (opt->rect.width() - checkh) / 2);
const int x1 = xoff + opt->rect.x();
const int y1 = yoff + opt->rect.y() + checkw/2;
const int x2 = xoff + opt->rect.x() + checkw/4;
const int y2 = yoff + opt->rect.y() + checkh;
const int x3 = xoff + opt->rect.x() + checkw;
const int y3 = yoff + opt->rect.y();
if (!(opt->state & State_On))
break;
QColor pc;
if (opt->state & State_Selected)
pc = opt->palette.highlightedText().color();
else
pc = opt->palette.text().color();
QCFType<CGColorRef> checkmarkColor = CGColorCreateGenericRGB(static_cast<CGFloat>(pc.redF()),
static_cast<CGFloat>(pc.greenF()),
static_cast<CGFloat>(pc.blueF()),
static_cast<CGFloat>(pc.alphaF()));
// kCTFontUIFontSystem and others give the same result
// as kCTFontUIFontMenuItemMark. However, the latter is
// more reminiscent to HITheme's kThemeMenuItemMarkFont.
// See also the font for small- and mini-sized widgets,
// where we end up using the generic system font type.
const CTFontUIFontType fontType = (opt->state & State_Mini) ? kCTFontUIFontMiniSystem :
(opt->state & State_Small) ? kCTFontUIFontSmallSystem :
kCTFontUIFontMenuItemMark;
// Similarly for the font size, where there is a small difference
// between regular combobox and item view items, and and menu items.
// However, we ignore any difference for small- and mini-sized widgets.
const CGFloat fontSize = fontType == kCTFontUIFontMenuItemMark ? opt->fontMetrics.height() : 0.0;
QCFType<CTFontRef> checkmarkFont = CTFontCreateUIFontForLanguage(fontType, fontSize, NULL);
QVector<QLineF> a(2);
a << QLineF(x1, y1, x2, y2);
a << QLineF(x2, y2, x3, y3);
if (opt->palette.currentColorGroup() == QPalette::Active) {
if (opt->state & State_On)
p->setPen(QPen(opt->palette.highlightedText().color(), 3));
else
p->setPen(QPen(opt->palette.text().color(), 3));
} else {
p->setPen(QPen(QColor(100, 100, 100), 3));
}
p->save();
p->setRenderHint(QPainter::Antialiasing);
p->drawLines(a);
p->restore();
CGContextSaveGState(cg);
CGContextSetShouldSmoothFonts(cg, NO); // Same as HITheme and Cocoa menu checkmarks
// Baseline alignment tweaks for QComboBox and QMenu
const CGFloat vOffset = (opt->state & State_Mini) ? 0.0 :
(opt->state & State_Small) ? 1.0 :
0.75;
CGContextTranslateCTM(cg, 0, opt->rect.bottom());
CGContextScaleCTM(cg, 1, -1);
// Translate back to the original position and add rect origin and offset
CGContextTranslateCTM(cg, opt->rect.x(), vOffset);
// CTFont has severe difficulties finding the checkmark character among its
// glyphs. Fortunately, CTLine knows its ways inside the Cocoa labyrinth.
static const CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName };
static const int numValues = sizeof(keys) / sizeof(keys[0]);
const CFTypeRef values[] = { (CFTypeRef)checkmarkFont, (CFTypeRef)checkmarkColor };
Q_STATIC_ASSERT((sizeof(values) / sizeof(values[0])) == numValues);
QCFType<CFDictionaryRef> attributes = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values,
numValues, NULL, NULL);
// U+2713: CHECK MARK
QCFType<CFAttributedStringRef> checkmarkString = CFAttributedStringCreate(kCFAllocatorDefault, (CFStringRef)@"\u2713", attributes);
QCFType<CTLineRef> line = CTLineCreateWithAttributedString(checkmarkString);
CTLineDraw((CTLineRef)line, cg);
CGContextFlush(cg); // CTLineDraw's documentation says it doesn't flush
CGContextRestoreGState(cg);
break; }
case PE_IndicatorViewItemCheck:
case PE_IndicatorRadioButton:
@ -4412,61 +4440,27 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
p->setPen(mi->palette.buttonText().color());
if (mi->checked) {
// Use the HIThemeTextInfo foo to draw the check mark correctly, if we do it,
// we somehow need to use a special encoding as it doesn't look right with our
// drawText().
p->save();
CGContextSetShouldAntialias(cg, true);
CGContextSetShouldSmoothFonts(cg, true);
QColor textColor = p->pen().color();
CGFloat colorComp[] = { static_cast<CGFloat>(textColor.redF()), static_cast<CGFloat>(textColor.greenF()),
static_cast<CGFloat>(textColor.blueF()), static_cast<CGFloat>(textColor.alphaF()) };
CGContextSetFillColorSpace(cg, qt_mac_genericColorSpace());
CGContextSetFillColor(cg, colorComp);
HIThemeTextInfo tti;
tti.version = qt_mac_hitheme_version;
tti.state = tds;
if (active && enabled)
tti.state = kThemeStatePressed;
switch (widgetSize) {
case QAquaSizeUnknown:
case QAquaSizeLarge:
tti.fontID = kThemeMenuItemMarkFont;
break;
case QAquaSizeSmall:
tti.fontID = kThemeSmallSystemFont;
break;
case QAquaSizeMini:
tti.fontID = kThemeMiniSystemFont;
break;
}
tti.horizontalFlushness = kHIThemeTextHorizontalFlushLeft;
tti.verticalFlushness = kHIThemeTextVerticalFlushCenter;
tti.options = kHIThemeTextBoxOptionNone;
tti.truncationPosition = kHIThemeTextTruncationNone;
tti.truncationMaxLines = 1;
QCFString checkmark;
#if 0
if (mi->checkType == QStyleOptionMenuItem::Exclusive)
checkmark = QString(QChar(kDiamondUnicode));
else
#endif
checkmark = QString(QChar(kCheckUnicode));
int mw = checkcol + macItemFrame;
int mh = contentRect.height() - 2 * macItemFrame;
int xp = contentRect.x();
xp += macItemFrame;
CGFloat outWidth, outHeight, outBaseline;
HIThemeGetTextDimensions(checkmark, 0, &tti, &outWidth, &outHeight,
&outBaseline);
QStyleOption checkmarkOpt;
checkmarkOpt.initFrom(w);
const int mw = checkcol + macItemFrame;
const int mh = contentRect.height() + macItemFrame;
const int xp = contentRect.x() + macItemFrame;
checkmarkOpt.rect = QRect(xp, contentRect.y() - checkmarkOpt.fontMetrics.descent(), mw, mh);
checkmarkOpt.state |= State_On; // Always on. Never rendered when off.
checkmarkOpt.state.setFlag(State_Selected, active);
checkmarkOpt.state.setFlag(State_Enabled, enabled);
if (widgetSize == QAquaSizeMini)
outBaseline += 1;
QRect r(xp, contentRect.y(), mw, mh);
r.translate(0, p->fontMetrics().ascent() - int(outBaseline) + 1);
HIRect bounds = qt_hirectForQRect(r);
HIThemeDrawTextBox(checkmark, &bounds, &tti,
cg, kHIThemeOrientationNormal);
p->restore();
checkmarkOpt.state |= State_Mini;
else if (widgetSize == QAquaSizeSmall)
checkmarkOpt.state |= State_Small;
// We let drawPrimitive(PE_IndicatorMenuCheckMark) pick the right color
checkmarkOpt.palette.setColor(QPalette::HighlightedText, p->pen().color());
checkmarkOpt.palette.setColor(QPalette::Text, p->pen().color());
proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &checkmarkOpt, p, w);
}
if (!mi->icon.isNull()) {
QIcon::Mode mode = (mi->state & State_Enabled) ? QIcon::Normal