diff options
author | Jake Petroules <jake.petroules@qt.io> | 2017-02-27 18:39:44 -0800 |
---|---|---|
committer | Jake Petroules <jake.petroules@qt.io> | 2017-04-14 17:07:54 +0000 |
commit | 4f3249f32dbe5c20aabbfd9b4f9c558aaf449e48 (patch) | |
tree | 42fabff161ec455e2e5eeb0e4182c86f0abc82bb /src/plugins | |
parent | 571eb37ac95a5cf9d566e8c6c965838ee274f950 (diff) |
Pluginize the platform styles
This enforces decoupling and in the case of QMacStyle, isolates
QtWidgets and therefore end user applications, from Carbon/HITheme.
Windows and Fusion are platform independent, so they remain built-in
(but mostly because the Windows style is tightly coupled to other styles
like QStylesheetStyle).
Task-number: QTBUG-59428
Change-Id: Id6519fe0c5269c1bce5b5921f9db06257032a1c9
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@qt.io>
Diffstat (limited to 'src/plugins')
23 files changed, 17538 insertions, 0 deletions
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 620d9cb8c9..710a6c94c1 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -8,6 +8,7 @@ qtHaveModule(gui) { qtConfig(imageformatplugin): SUBDIRS *= imageformats !android:qtConfig(library): SUBDIRS *= generic } +qtHaveModule(widgets): SUBDIRS += styles !winrt:!wince:qtHaveModule(printsupport): \ SUBDIRS += printsupport diff --git a/src/plugins/styles/android/android.pro b/src/plugins/styles/android/android.pro new file mode 100644 index 0000000000..4ca35d8046 --- /dev/null +++ b/src/plugins/styles/android/android.pro @@ -0,0 +1,16 @@ +TARGET = qandroidstyle + +QT += widgets-private + +SOURCES += \ + main.cpp \ + qandroidstyle.cpp + +HEADERS += \ + qandroidstyle_p.h + +DISTFILES += androidstyle.json + +PLUGIN_TYPE = styles +PLUGIN_CLASS_NAME = QAndroidStylePlugin +load(qt_plugin) diff --git a/src/plugins/styles/android/androidstyle.json b/src/plugins/styles/android/androidstyle.json new file mode 100644 index 0000000000..6843bd3301 --- /dev/null +++ b/src/plugins/styles/android/androidstyle.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "android" ] +} diff --git a/src/plugins/styles/android/main.cpp b/src/plugins/styles/android/main.cpp new file mode 100644 index 0000000000..2121538b0a --- /dev/null +++ b/src/plugins/styles/android/main.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets/qstyleplugin.h> +#include "qandroidstyle_p.h" + +QT_BEGIN_NAMESPACE + +class QAndroidStylePlugin : public QStylePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "androidstyle.json") +public: + QStyle *create(const QString &key); +}; + +QStyle *QAndroidStylePlugin::create(const QString &key) +{ + if (key.compare(QLatin1String("android"), Qt::CaseInsensitive) == 0) + return new QAndroidStyle(); + + return 0; +} + +QT_END_NAMESPACE + +#include "main.moc" + diff --git a/src/plugins/styles/android/qandroidstyle.cpp b/src/plugins/styles/android/qandroidstyle.cpp new file mode 100644 index 0000000000..ed8c342668 --- /dev/null +++ b/src/plugins/styles/android/qandroidstyle.cpp @@ -0,0 +1,1805 @@ +/**************************************************************************** +** +** Copyright (C) 2013 BogDan Vatra <bogdan@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidstyle_p.h" + +#include <QFile> +#include <QFont> +#include <QApplication> +#include <qdrawutil.h> +#include <QPixmapCache> +#include <QFileInfo> +#include <QStyleOption> +#include <QPainter> +#include <QJsonDocument> +#include <QJsonObject> +#include <QDebug> + +#include <QGuiApplication> +#include <qpa/qplatformnativeinterface.h> +#include <qpa/qplatformtheme.h> + +QT_BEGIN_NAMESPACE + +namespace { + const quint32 NO_COLOR = 1; + const quint32 TRANSPARENT_COLOR = 0; +} + +QAndroidStyle::QAndroidStyle() + : QFusionStyle() +{ + QPixmapCache::clear(); + checkBoxControl = NULL; + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + QPalette *standardPalette = reinterpret_cast<QPalette *>(nativeInterface->nativeResourceForIntegration("AndroidStandardPalette")); + if (standardPalette) + m_standardPalette = *standardPalette; + + QHash<QByteArray, QFont> *qwidgetsFonts = reinterpret_cast<QHash<QByteArray, QFont> *>(nativeInterface->nativeResourceForIntegration("AndroidQWidgetFonts")); + if (qwidgetsFonts) { + for (auto it = qwidgetsFonts->constBegin(); it != qwidgetsFonts->constEnd(); ++it) + QApplication::setFont(it.value(), it.key()); + qwidgetsFonts->clear(); // free the memory + } + + QJsonObject *object = reinterpret_cast<QJsonObject *>(nativeInterface->nativeResourceForIntegration("AndroidStyleData")); + if (!object) + return; + + for (QJsonObject::const_iterator objectIterator = object->constBegin(); + objectIterator != object->constEnd(); + ++objectIterator) { + QString key = objectIterator.key(); + QJsonValue value = objectIterator.value(); + if (Q_UNLIKELY(!value.isObject())) { + qWarning("Style.json structure is unrecognized."); + continue; + } + + QJsonObject item = value.toObject(); + QAndroidStyle::ItemType itemType = qtControl(key); + if (QC_UnknownType == itemType) + continue; + + switch (itemType) { + case QC_Checkbox: + checkBoxControl = new AndroidCompoundButtonControl(item.toVariantMap(), itemType); + m_androidControlsHash[int(itemType)] = checkBoxControl; + break; + case QC_RadioButton: + m_androidControlsHash[int(itemType)] = new AndroidCompoundButtonControl(item.toVariantMap(), + itemType); + break; + + case QC_ProgressBar: + m_androidControlsHash[int(itemType)] = new AndroidProgressBarControl(item.toVariantMap(), + itemType); + break; + + case QC_Slider: + m_androidControlsHash[int(itemType)] = new AndroidSeekBarControl(item.toVariantMap(), + itemType); + break; + + case QC_Combobox: + m_androidControlsHash[int(itemType)] = new AndroidSpinnerControl(item.toVariantMap(), + itemType); + break; + + default: + m_androidControlsHash[int(itemType)] = new AndroidControl(item.toVariantMap(), + itemType); + break; + } + } + *object = QJsonObject(); // free memory +} + +QAndroidStyle::~QAndroidStyle() +{ + qDeleteAll(m_androidControlsHash); +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(const QString &android) +{ + if (android == QLatin1String("buttonStyle")) + return QC_Button; + if (android == QLatin1String("editTextStyle")) + return QC_EditText; + if (android == QLatin1String("radioButtonStyle")) + return QC_RadioButton; + if (android == QLatin1String("checkboxStyle")) + return QC_Checkbox; + if (android == QLatin1String("textViewStyle")) + return QC_View; + if (android == QLatin1String("buttonStyleToggle")) + return QC_Switch; + if (android == QLatin1String("spinnerStyle")) + return QC_Combobox; + if (android == QLatin1String("progressBarStyleHorizontal")) + return QC_ProgressBar; + if (android == QLatin1String("seekBarStyle")) + return QC_Slider; + + return QC_UnknownType; +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ComplexControl control) +{ + switch (control) { + case CC_ComboBox: + return QC_Combobox; + case CC_Slider: + return QC_Slider; + default: + return QC_UnknownType; + } +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ContentsType contentsType) +{ + switch (contentsType) { + case CT_PushButton: + return QC_Button; + case CT_CheckBox: + return QC_Checkbox; + case CT_RadioButton: + return QC_RadioButton; + case CT_ComboBox: + return QC_Combobox; + case CT_ProgressBar: + return QC_ProgressBar; + case CT_Slider: + return QC_Slider; + case CT_ScrollBar: + return QC_Slider; + case CT_TabWidget: + return QC_Tab; + case CT_TabBarTab: + return QC_TabButton; + case CT_LineEdit: + return QC_EditText; + case CT_GroupBox: + return QC_GroupBox; + default: + return QC_UnknownType; + } +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ControlElement controlElement) +{ + switch (controlElement) { + case CE_PushButton: + case CE_PushButtonBevel: + case CE_PushButtonLabel: + return QC_Button; + + case CE_CheckBox: + case CE_CheckBoxLabel: + return QC_Checkbox; + + case CE_RadioButton: + case CE_RadioButtonLabel: + return QC_RadioButton; + + case CE_TabBarTab: + case CE_TabBarTabShape: + case CE_TabBarTabLabel: + return QC_Tab; + + case CE_ProgressBar: + case CE_ProgressBarGroove: + case CE_ProgressBarContents: + case CE_ProgressBarLabel: + return QC_ProgressBar; + + case CE_ComboBoxLabel: + return QC_Combobox; + + case CE_ShapedFrame: + return QC_View; + + default: + return QC_UnknownType; + } +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::PrimitiveElement primitiveElement) +{ + switch (primitiveElement) { + case QStyle::PE_PanelLineEdit: + case QStyle::PE_FrameLineEdit: + return QC_EditText; + + case QStyle::PE_IndicatorViewItemCheck: + case QStyle::PE_IndicatorCheckBox: + return QC_Checkbox; + + case QStyle::PE_FrameWindow: + case QStyle::PE_Widget: + case QStyle::PE_Frame: + case QStyle::PE_FrameFocusRect: + return QC_View; + default: + return QC_UnknownType; + } +} + +QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::SubElement subElement) +{ + switch (subElement) { + case QStyle::SE_LineEditContents: + return QC_EditText; + + case QStyle::SE_PushButtonContents: + case QStyle::SE_PushButtonFocusRect: + return QC_Button; + + case SE_RadioButtonContents: + return QC_RadioButton; + + case SE_CheckBoxContents: + return QC_Checkbox; + + default: + return QC_UnknownType; + } +} + +void QAndroidStyle::drawPrimitive(PrimitiveElement pe, + const QStyleOption *opt, + QPainter *p, + const QWidget *w) const +{ + const ItemType itemType = qtControl(pe); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) { + if (itemType != QC_EditText) { + it.value()->drawControl(opt, p, w); + } else { + QStyleOption copy(*opt); + copy.state &= ~QStyle::State_Sunken; + it.value()->drawControl(©, p, w); + } + } else if (pe == PE_FrameGroupBox) { + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (frame->features & QStyleOptionFrame::Flat) { + QRect fr = frame->rect; + QPoint p1(fr.x(), fr.y() + 1); + QPoint p2(fr.x() + fr.width(), p1.y()); + qDrawShadeLine(p, p1, p2, frame->palette, true, + frame->lineWidth, frame->midLineWidth); + } else { + qDrawShadeRect(p, frame->rect.x(), frame->rect.y(), frame->rect.width(), + frame->rect.height(), frame->palette, true, + frame->lineWidth, frame->midLineWidth); + } + } + } else { + QFusionStyle::drawPrimitive(pe, opt, p, w); + } +} + + +void QAndroidStyle::drawControl(QStyle::ControlElement element, + const QStyleOption *opt, + QPainter *p, + const QWidget *w) const +{ + const ItemType itemType = qtControl(element); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) { + AndroidControl *androidControl = it.value(); + androidControl->drawControl(opt, p, w); + + switch (itemType) { + case QC_Button: + if (const QStyleOptionButton *buttonOption = + qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QMargins padding = androidControl->padding(); + QStyleOptionButton copy(*buttonOption); + copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom()); + QFusionStyle::drawControl(CE_PushButtonLabel, ©, p, w); + } + break; + case QC_Checkbox: + case QC_RadioButton: + if (const QStyleOptionButton *btn = + qstyleoption_cast<const QStyleOptionButton *>(opt)) { + const bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt(*btn); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, w); + QFusionStyle::drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, w); + } + break; + case QC_Combobox: + if (const QStyleOptionComboBox *comboboxOption = + qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QMargins padding = androidControl->padding(); + QStyleOptionComboBox copy (*comboboxOption); + copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom()); + QFusionStyle::drawControl(CE_ComboBoxLabel, comboboxOption, p, w); + } + break; + default: + QFusionStyle::drawControl(element, opt, p, w); + break; + } + } else { + QFusionStyle::drawControl(element, opt, p, w); + } +} + +QRect QAndroidStyle::subElementRect(SubElement subElement, + const QStyleOption *option, + const QWidget *widget) const +{ + const ItemType itemType = qtControl(subElement); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) + return it.value()->subElementRect(subElement, option, widget); + return QFusionStyle::subElementRect(subElement, option, widget); +} + +void QAndroidStyle::drawComplexControl(ComplexControl cc, + const QStyleOptionComplex *opt, + QPainter *p, + const QWidget *widget) const +{ + const ItemType itemType = qtControl(cc); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) { + it.value()->drawControl(opt, p, widget); + return; + } + if (cc == CC_GroupBox) { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + // Draw frame + QRect textRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, widget); + QRect checkBoxRect; + if (groupBox->subControls & SC_GroupBoxCheckBox) + checkBoxRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxCheckBox, widget); + if (groupBox->subControls & QStyle::SC_GroupBoxFrame) { + QStyleOptionFrame frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, widget); + p->save(); + QRegion region(groupBox->rect); + if (!groupBox->text.isEmpty()) { + bool ltr = groupBox->direction == Qt::LeftToRight; + QRect finalRect; + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) { + finalRect = checkBoxRect.united(textRect); + finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0); + } else { + finalRect = textRect; + } + region -= finalRect; + } + p->setClipRegion(region); + drawPrimitive(PE_FrameGroupBox, &frame, p, widget); + p->restore(); + } + + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) + p->setPen(textColor); + int alignment = int(groupBox->textAlignment); + if (!styleHint(QStyle::SH_UnderlineShortcut, opt, widget)) + alignment |= Qt::TextHideMnemonic; + + drawItemText(p, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment, + groupBox->palette, groupBox->state & State_Enabled, groupBox->text, + textColor.isValid() ? QPalette::NoRole : QPalette::WindowText); + + if (groupBox->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*groupBox); + fropt.rect = textRect; + drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + + // Draw checkbox + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + checkBoxControl->drawControl(&box, p, widget); + } + } + return; + } + QFusionStyle::drawComplexControl(cc, opt, p, widget); +} + +QStyle::SubControl QAndroidStyle::hitTestComplexControl(ComplexControl cc, + const QStyleOptionComplex *opt, + const QPoint &pt, + const QWidget *widget) const +{ + const ItemType itemType = qtControl(cc); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) { + switch (cc) { + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QRect r = it.value()->subControlRect(slider, SC_SliderHandle, widget); + if (r.isValid() && r.contains(pt)) { + return SC_SliderHandle; + } else { + r = it.value()->subControlRect(slider, SC_SliderGroove, widget); + if (r.isValid() && r.contains(pt)) + return SC_SliderGroove; + } + } + break; + default: + break; + } + } + return QFusionStyle::hitTestComplexControl(cc, opt, pt, widget); +} + +QRect QAndroidStyle::subControlRect(ComplexControl cc, + const QStyleOptionComplex *opt, + SubControl sc, + const QWidget *widget) const +{ + const ItemType itemType = qtControl(cc); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) + return it.value()->subControlRect(opt, sc, widget); + QRect rect = opt->rect; + switch (cc) { + case CC_GroupBox: { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + QSize textSize = opt->fontMetrics.boundingRect(groupBox->text).size() + QSize(2, 2); + QSize checkBoxSize = checkBoxControl->size(opt); + int indicatorWidth = checkBoxSize.width(); + int indicatorHeight = checkBoxSize.height(); + QRect checkBoxRect; + if (opt->subControls & QStyle::SC_GroupBoxCheckBox) { + checkBoxRect.setWidth(indicatorWidth); + checkBoxRect.setHeight(indicatorHeight); + } + checkBoxRect.moveLeft(1); + QRect textRect = checkBoxRect; + textRect.setSize(textSize); + if (opt->subControls & QStyle::SC_GroupBoxCheckBox) + textRect.translate(indicatorWidth + 5, (indicatorHeight - textSize.height()) / 2); + if (sc == SC_GroupBoxFrame) { + rect = opt->rect.adjusted(0, 0, 0, 0); + rect.translate(0, textRect.height() / 2); + rect.setHeight(rect.height() - textRect.height() / 2); + } else if (sc == SC_GroupBoxContents) { + QRect frameRect = opt->rect.adjusted(0, 0, 0, -groupBox->lineWidth); + int margin = 3; + int leftMarginExtension = 0; + int topMargin = qMax(pixelMetric(PM_ExclusiveIndicatorHeight), opt->fontMetrics.height()) + groupBox->lineWidth; + frameRect.adjust(leftMarginExtension + margin, margin + topMargin, -margin, -margin - groupBox->lineWidth); + frameRect.translate(0, textRect.height() / 2); + rect = frameRect; + rect.setHeight(rect.height() - textRect.height() / 2); + } else if (sc == SC_GroupBoxCheckBox) { + rect = checkBoxRect; + } else if (sc == SC_GroupBoxLabel) { + rect = textRect; + } + return visualRect(opt->direction, opt->rect, rect); + } + + return rect; + } + + default: + break; + } + + + return QFusionStyle::subControlRect(cc, opt, sc, widget); +} + +int QAndroidStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, + const QWidget *widget) const +{ + switch (metric) { + case PM_ButtonMargin: + case PM_FocusFrameVMargin: + case PM_FocusFrameHMargin: + case PM_ComboBoxFrameWidth: + case PM_SpinBoxFrameWidth: + case PM_ScrollBarExtent: + return 0; + case PM_IndicatorWidth: + return checkBoxControl->size(option).width(); + case PM_IndicatorHeight: + return checkBoxControl->size(option).height(); + default: + return QFusionStyle::pixelMetric(metric, option, widget); + } + +} + +QSize QAndroidStyle::sizeFromContents(ContentsType ct, + const QStyleOption *opt, + const QSize &contentsSize, + const QWidget *w) const +{ + QSize sz = QFusionStyle::sizeFromContents(ct, opt, contentsSize, w); + if (ct == CT_HeaderSection) { + if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + bool nullIcon = hdr->icon.isNull(); + int margin = pixelMetric(QStyle::PM_HeaderMargin, hdr, w); + int iconSize = nullIcon ? 0 : checkBoxControl->size(opt).width(); + QSize txt; +/* + * These next 4 lines are a bad hack to fix a bug in case a QStyleSheet is applied at QApplication level. + * In that case, even if the stylesheet does not refer to headers, the header font is changed to application + * font, which is wrong. Even worst, hdr->fontMetrics(...) does not report the proper size. + */ + if (qApp->styleSheet().isEmpty()) + txt = hdr->fontMetrics.size(0, hdr->text); + else + txt = qApp->fontMetrics().size(0, hdr->text); + + sz.setHeight(margin + qMax(iconSize, txt.height()) + margin); + sz.setWidth((nullIcon ? 0 : margin) + iconSize + + (hdr->text.isNull() ? 0 : margin) + txt.width() + margin); + if (hdr->sortIndicator != QStyleOptionHeader::None) { + int margin = pixelMetric(QStyle::PM_HeaderMargin, hdr, w); + if (hdr->orientation == Qt::Horizontal) + sz.rwidth() += sz.height() + margin; + else + sz.rheight() += sz.width() + margin; + } + return sz; + } + } + const ItemType itemType = qtControl(ct); + AndroidControlsHash::const_iterator it = itemType != QC_UnknownType + ? m_androidControlsHash.find(itemType) + : m_androidControlsHash.end(); + if (it != m_androidControlsHash.end()) + return it.value()->sizeFromContents(opt, sz, w); + if (ct == CT_GroupBox) { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + QSize textSize = opt->fontMetrics.boundingRect(groupBox->text).size() + QSize(2, 2); + QSize checkBoxSize = checkBoxControl->size(opt); + int indicatorWidth = checkBoxSize.width(); + int indicatorHeight = checkBoxSize.height(); + QRect checkBoxRect; + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) { + checkBoxRect.setWidth(indicatorWidth); + checkBoxRect.setHeight(indicatorHeight); + } + checkBoxRect.moveLeft(1); + QRect textRect = checkBoxRect; + textRect.setSize(textSize); + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) + textRect.translate(indicatorWidth + 5, (indicatorHeight - textSize.height()) / 2); + QRect u = textRect.united(checkBoxRect); + sz = QSize(sz.width(), sz.height() + u.height()); + } + } + return sz; +} + +QPixmap QAndroidStyle::standardPixmap(StandardPixmap standardPixmap, + const QStyleOption *opt, + const QWidget *widget) const +{ + return QFusionStyle::standardPixmap(standardPixmap, opt, widget); +} + +QPixmap QAndroidStyle::generatedIconPixmap(QIcon::Mode iconMode, + const QPixmap &pixmap, + const QStyleOption *opt) const +{ + return QFusionStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + +int QAndroidStyle::styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const +{ + switch (hint) { + case SH_Slider_AbsoluteSetButtons: + return Qt::LeftButton; + + case SH_Slider_PageSetButtons: + return 0; + + case SH_RequestSoftwareInputPanel: + return RSIP_OnMouseClick; + + default: + return QFusionStyle::styleHint(hint, option, widget, returnData); + } +} + +QPalette QAndroidStyle::standardPalette() const +{ + return m_standardPalette; +} + +void QAndroidStyle::polish(QWidget *widget) +{ + widget->setAttribute(Qt::WA_StyledBackground, true); +} + +void QAndroidStyle::unpolish(QWidget *widget) +{ + widget->setAttribute(Qt::WA_StyledBackground, false); +} + +QAndroidStyle::AndroidDrawable::AndroidDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) +{ + initPadding(drawable); + m_itemType = itemType; +} + +QAndroidStyle::AndroidDrawable::~AndroidDrawable() +{ +} + +void QAndroidStyle::AndroidDrawable::initPadding(const QVariantMap &drawable) +{ + QVariantMap::const_iterator it = drawable.find(QLatin1String("padding")); + if (it != drawable.end()) + m_padding = extractMargins(it.value().toMap()); +} + +const QMargins &QAndroidStyle::AndroidDrawable::padding() const +{ + return m_padding; +} + +QSize QAndroidStyle::AndroidDrawable::size() const +{ + if (type() == Image || type() == NinePatch) + return static_cast<const QAndroidStyle::AndroidImageDrawable *>(this)->size(); + + return QSize(); +} + +QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidDrawable::fromMap(const QVariantMap &drawable, + ItemType itemType) +{ + const QString type = drawable.value(QLatin1String("type")).toString(); + if (type == QLatin1String("image")) + return new QAndroidStyle::AndroidImageDrawable(drawable, itemType); + if (type == QLatin1String("9patch")) + return new QAndroidStyle::Android9PatchDrawable(drawable, itemType); + if (type == QLatin1String("stateslist")) + return new QAndroidStyle::AndroidStateDrawable(drawable, itemType); + if (type == QLatin1String("layer")) + return new QAndroidStyle::AndroidLayerDrawable(drawable, itemType); + if (type == QLatin1String("gradient")) + return new QAndroidStyle::AndroidGradientDrawable(drawable, itemType); + if (type == QLatin1String("clipDrawable")) + return new QAndroidStyle::AndroidClipDrawable(drawable, itemType); + if (type == QLatin1String("color")) + return new QAndroidStyle::AndroidColorDrawable(drawable, itemType); + return 0; +} + +QMargins QAndroidStyle::AndroidDrawable::extractMargins(const QVariantMap &value) +{ + QMargins m; + m.setLeft(value.value(QLatin1String("left")).toInt()); + m.setRight(value.value(QLatin1String("right")).toInt()); + m.setTop(value.value(QLatin1String("top")).toInt()); + m.setBottom(value.value(QLatin1String("bottom")).toInt()); + return m; +} + +void QAndroidStyle::AndroidDrawable::setPaddingLeftToSizeWidth() +{ + QSize sz = size(); + if (m_padding.isNull() && !sz.isNull()) + m_padding.setLeft(sz.width()); +} + + +QAndroidStyle::AndroidImageDrawable::AndroidImageDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + m_filePath = drawable.value(QLatin1String("path")).toString(); + m_size.setHeight(drawable.value(QLatin1String("height")).toInt()); + m_size.setWidth(drawable.value(QLatin1String("width")).toInt()); +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidImageDrawable::type() const +{ + return QAndroidStyle::Image; +} + +void QAndroidStyle::AndroidImageDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + if (m_hashKey.isEmpty()) + m_hashKey = QFileInfo(m_filePath).fileName(); + + QPixmap pm; + if (!QPixmapCache::find(m_hashKey, &pm)) { + pm.load(m_filePath); + QPixmapCache::insert(m_hashKey, pm); + } + + painter->drawPixmap(opt->rect.x(), opt->rect.y() + (opt->rect.height() - pm.height()) / 2, pm); +} + +QSize QAndroidStyle::AndroidImageDrawable::size() const +{ + return m_size; +} + +QAndroidStyle::AndroidColorDrawable::AndroidColorDrawable(const QVariantMap &drawable, + ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + m_color.setRgba(QRgb(drawable.value(QLatin1String("color")).toInt())); +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidColorDrawable::type() const +{ + return QAndroidStyle::Color; +} + +void QAndroidStyle::AndroidColorDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + painter->fillRect(opt->rect, m_color); +} + +QAndroidStyle::Android9PatchDrawable::Android9PatchDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidImageDrawable(drawable.value(QLatin1String("drawable")).toMap(), itemType) +{ + initPadding(drawable); + QVariantMap chunk = drawable.value(QLatin1String("chunkInfo")).toMap(); + extractIntArray(chunk.value(QLatin1String("xdivs")).toList(), m_chunkData.xDivs); + extractIntArray(chunk.value(QLatin1String("ydivs")).toList(), m_chunkData.yDivs); + extractIntArray(chunk.value(QLatin1String("colors")).toList(), m_chunkData.colors); +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::Android9PatchDrawable::type() const +{ + return QAndroidStyle::NinePatch; +} + +int QAndroidStyle::Android9PatchDrawable::calculateStretch(int boundsLimit, + int startingPoint, + int srcSpace, + int numStrechyPixelsRemaining, + int numFixedPixelsRemaining) +{ + int spaceRemaining = boundsLimit - startingPoint; + int stretchySpaceRemaining = spaceRemaining - numFixedPixelsRemaining; + return (float(srcSpace) * stretchySpaceRemaining / numStrechyPixelsRemaining + .5); +} + +void QAndroidStyle::Android9PatchDrawable::extractIntArray(const QVariantList &values, + QVector<int> & array) +{ + for (const QVariant &value : values) + array << value.toInt(); +} + + +void QAndroidStyle::Android9PatchDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + if (m_hashKey.isEmpty()) + m_hashKey = QFileInfo(m_filePath).fileName(); + + QPixmap pixmap; + if (!QPixmapCache::find(m_hashKey, &pixmap)) { + pixmap.load(m_filePath); + QPixmapCache::insert(m_hashKey, pixmap); + } + + const QRect &bounds = opt->rect; + + // shamelessly stolen from Android's sources (NinepatchImpl.cpp) and adapted for Qt + const int pixmapWidth = pixmap.width(); + const int pixmapHeight = pixmap.height(); + + if (bounds.isNull() || !pixmapWidth || !pixmapHeight) + return; + + QPainter::RenderHints savedHints = painter->renderHints(); + + // The patchs doesn't need smooth transform ! + painter->setRenderHints(QPainter::SmoothPixmapTransform, false); + + QRectF dst; + QRectF src; + + const qint32 x0 = m_chunkData.xDivs[0]; + const qint32 y0 = m_chunkData.yDivs[0]; + const quint8 numXDivs = m_chunkData.xDivs.size(); + const quint8 numYDivs = m_chunkData.yDivs.size(); + int i; + int j; + int colorIndex = 0; + quint32 color; + bool xIsStretchable; + const bool initialXIsStretchable = (x0 == 0); + bool yIsStretchable = (y0 == 0); + const int bitmapWidth = pixmap.width(); + const int bitmapHeight = pixmap.height(); + + int *dstRights = static_cast<int *>(alloca((numXDivs + 1) * sizeof(int))); + bool dstRightsHaveBeenCached = false; + + int numStretchyXPixelsRemaining = 0; + for (i = 0; i < numXDivs; i += 2) + numStretchyXPixelsRemaining += m_chunkData.xDivs[i + 1] - m_chunkData.xDivs[i]; + + int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining; + int numStretchyYPixelsRemaining = 0; + for (i = 0; i < numYDivs; i += 2) + numStretchyYPixelsRemaining += m_chunkData.yDivs[i + 1] - m_chunkData.yDivs[i]; + + int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining; + src.setTop(0); + dst.setTop(bounds.top()); + // The first row always starts with the top being at y=0 and the bottom + // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case + // the first row is stretchable along the Y axis, otherwise it is fixed. + // The last row always ends with the bottom being bitmap.height and the top + // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or + // yDivs[numYDivs-1]. In the former case the last row is stretchable along + // the Y axis, otherwise it is fixed. + // + // The first and last columns are similarly treated with respect to the X + // axis. + // + // The above is to help explain some of the special casing that goes on the + // code below. + + // The initial yDiv and whether the first row is considered stretchable or + // not depends on whether yDiv[0] was zero or not. + for (j = yIsStretchable ? 1 : 0; + j <= numYDivs && src.top() < bitmapHeight; + j++, yIsStretchable = !yIsStretchable) { + src.setLeft(0); + dst.setLeft(bounds.left()); + if (j == numYDivs) { + src.setBottom(bitmapHeight); + dst.setBottom(bounds.bottom()); + } else { + src.setBottom(m_chunkData.yDivs[j]); + const int srcYSize = src.bottom() - src.top(); + if (yIsStretchable) { + dst.setBottom(dst.top() + calculateStretch(bounds.bottom(), dst.top(), + srcYSize, + numStretchyYPixelsRemaining, + numFixedYPixelsRemaining)); + numStretchyYPixelsRemaining -= srcYSize; + } else { + dst.setBottom(dst.top() + srcYSize); + numFixedYPixelsRemaining -= srcYSize; + } + } + + xIsStretchable = initialXIsStretchable; + // The initial xDiv and whether the first column is considered + // stretchable or not depends on whether xDiv[0] was zero or not. + for (i = xIsStretchable ? 1 : 0; + i <= numXDivs && src.left() < bitmapWidth; + i++, xIsStretchable = !xIsStretchable) { + color = m_chunkData.colors[colorIndex++]; + if (color != TRANSPARENT_COLOR) + color = NO_COLOR; + if (i == numXDivs) { + src.setRight(bitmapWidth); + dst.setRight(bounds.right()); + } else { + src.setRight(m_chunkData.xDivs[i]); + if (dstRightsHaveBeenCached) { + dst.setRight(dstRights[i]); + } else { + const int srcXSize = src.right() - src.left(); + if (xIsStretchable) { + dst.setRight(dst.left() + calculateStretch(bounds.right(), dst.left(), + srcXSize, + numStretchyXPixelsRemaining, + numFixedXPixelsRemaining)); + numStretchyXPixelsRemaining -= srcXSize; + } else { + dst.setRight(dst.left() + srcXSize); + numFixedXPixelsRemaining -= srcXSize; + } + dstRights[i] = dst.right(); + } + } + // If this horizontal patch is too small to be displayed, leave + // the destination left edge where it is and go on to the next patch + // in the source. + if (src.left() >= src.right()) { + src.setLeft(src.right()); + continue; + } + // Make sure that we actually have room to draw any bits + if (dst.right() <= dst.left() || dst.bottom() <= dst.top()) { + goto nextDiv; + } + // If this patch is transparent, skip and don't draw. + if (color == TRANSPARENT_COLOR) + goto nextDiv; + if (color != NO_COLOR) + painter->fillRect(dst, QRgb(color)); + else + painter->drawPixmap(dst, pixmap, src); +nextDiv: + src.setLeft(src.right()); + dst.setLeft(dst.right()); + } + src.setTop(src.bottom()); + dst.setTop(dst.bottom()); + dstRightsHaveBeenCached = true; + } + painter->setRenderHints(savedHints); +} + +QAndroidStyle::AndroidGradientDrawable::AndroidGradientDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType), m_orientation(TOP_BOTTOM) +{ + m_radius = drawable.value(QLatin1String("radius")).toInt(); + if (m_radius < 0) + m_radius = 0; + + QVariantList colors = drawable.value(QLatin1String("colors")).toList(); + QVariantList positions = drawable.value(QLatin1String("positions")).toList(); + int min = colors.size() < positions.size() ? colors.size() : positions.size(); + for (int i = 0; i < min; i++) + m_gradient.setColorAt(positions.at(i).toDouble(), QRgb(colors.at(i).toInt())); + + QByteArray orientation = drawable.value(QLatin1String("orientation")).toByteArray(); + if (orientation == "TOP_BOTTOM") // draw the gradient from the top to the bottom + m_orientation = TOP_BOTTOM; + else if (orientation == "TR_BL") // draw the gradient from the top-right to the bottom-left + m_orientation = TR_BL; + else if (orientation == "RIGHT_LEFT") // draw the gradient from the right to the left + m_orientation = RIGHT_LEFT; + else if (orientation == "BR_TL") // draw the gradient from the bottom-right to the top-left + m_orientation = BR_TL; + else if (orientation == "BOTTOM_TOP") // draw the gradient from the bottom to the top + m_orientation = BOTTOM_TOP; + else if (orientation == "BL_TR") // draw the gradient from the bottom-left to the top-right + m_orientation = BL_TR; + else if (orientation == "LEFT_RIGHT") // draw the gradient from the left to the right + m_orientation = LEFT_RIGHT; + else if (orientation == "TL_BR") // draw the gradient from the top-left to the bottom-right + m_orientation = TL_BR; + else + qWarning("AndroidGradientDrawable: unknown orientation"); +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidGradientDrawable::type() const +{ + return QAndroidStyle::Gradient; +} + +void QAndroidStyle::AndroidGradientDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + const int width = opt->rect.width(); + const int height = opt->rect.height(); + switch (m_orientation) { + case TOP_BOTTOM: + // draw the gradient from the top to the bottom + m_gradient.setStart(width / 2, 0); + m_gradient.setFinalStop(width / 2, height); + break; + case TR_BL: + // draw the gradient from the top-right to the bottom-left + m_gradient.setStart(width, 0); + m_gradient.setFinalStop(0, height); + break; + case RIGHT_LEFT: + // draw the gradient from the right to the left + m_gradient.setStart(width, height / 2); + m_gradient.setFinalStop(0, height / 2); + break; + case BR_TL: + // draw the gradient from the bottom-right to the top-left + m_gradient.setStart(width, height); + m_gradient.setFinalStop(0, 0); + break; + case BOTTOM_TOP: + // draw the gradient from the bottom to the top + m_gradient.setStart(width / 2, height); + m_gradient.setFinalStop(width / 2, 0); + break; + case BL_TR: + // draw the gradient from the bottom-left to the top-right + m_gradient.setStart(0, height); + m_gradient.setFinalStop(width, 0); + break; + case LEFT_RIGHT: + // draw the gradient from the left to the right + m_gradient.setStart(0, height / 2); + m_gradient.setFinalStop(width, height / 2); + break; + case TL_BR: + // draw the gradient from the top-left to the bottom-right + m_gradient.setStart(0, 0); + m_gradient.setFinalStop(width, height); + break; + } + + const QBrush &oldBrush = painter->brush(); + const QPen oldPen = painter->pen(); + painter->setPen(Qt::NoPen); + painter->setBrush(m_gradient); + painter->drawRoundedRect(opt->rect, m_radius, m_radius); + painter->setBrush(oldBrush); + painter->setPen(oldPen); +} + +QSize QAndroidStyle::AndroidGradientDrawable::size() const +{ + return QSize(m_radius * 2, m_radius * 2); +} + +QAndroidStyle::AndroidClipDrawable::AndroidClipDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + m_drawable = fromMap(drawable.value(QLatin1String("drawable")).toMap(), itemType); + m_factor = 0; + m_orientation = Qt::Horizontal; +} + +QAndroidStyle::AndroidClipDrawable::~AndroidClipDrawable() +{ + delete m_drawable; +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidClipDrawable::type() const +{ + return QAndroidStyle::Clip; +} + +void QAndroidStyle::AndroidClipDrawable::setFactor(double factor, Qt::Orientation orientation) +{ + m_factor = factor; + m_orientation = orientation; +} + +void QAndroidStyle::AndroidClipDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + QStyleOption copy(*opt); + if (m_orientation == Qt::Horizontal) + copy.rect.setWidth(copy.rect.width() * m_factor); + else + copy.rect.setHeight(copy.rect.height() * m_factor); + + m_drawable->draw(painter, ©); +} + +QAndroidStyle::AndroidStateDrawable::AndroidStateDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + const QVariantList states = drawable.value(QLatin1String("stateslist")).toList(); + for (const QVariant &stateVariant : states) { + QVariantMap state = stateVariant.toMap(); + const int s = extractState(state.value(QLatin1String("states")).toMap()); + if (-1 == s) + continue; + const AndroidDrawable *ad = fromMap(state.value(QLatin1String("drawable")).toMap(), itemType); + if (!ad) + continue; + StateType item; + item.first = s; + item.second = ad; + m_states<<item; + } +} + +QAndroidStyle::AndroidStateDrawable::~AndroidStateDrawable() +{ + for (const StateType &type : qAsConst(m_states)) + delete type.second; +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidStateDrawable::type() const +{ + return QAndroidStyle::State; +} + +void QAndroidStyle::AndroidStateDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + const AndroidDrawable *drawable = bestAndroidStateMatch(opt); + if (drawable) + drawable->draw(painter, opt); +} +QSize QAndroidStyle::AndroidStateDrawable::sizeImage(const QStyleOption *opt) const +{ + QSize s; + const AndroidDrawable *drawable = bestAndroidStateMatch(opt); + if (drawable) + s = drawable->size(); + return s; +} + +const QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidStateDrawable::bestAndroidStateMatch(const QStyleOption *opt) const +{ + const AndroidDrawable *bestMatch = 0; + if (!opt) { + if (m_states.size()) + return m_states[0].second; + return bestMatch; + } + + uint bestCost = 0xffff; + for (const StateType & state : m_states) { + if (int(opt->state) == state.first) + return state.second; + uint cost = 1; + + int difference = int(opt->state^state.first); + + if (difference & QStyle::State_Active) + cost <<= 1; + + if (difference & QStyle::State_Enabled) + cost <<= 1; + + if (difference & QStyle::State_Raised) + cost <<= 1; + + if (difference & QStyle::State_Sunken) + cost <<= 1; + + if (difference & QStyle::State_Off) + cost <<= 1; + + if (difference & QStyle::State_On) + cost <<= 1; + + if (difference & QStyle::State_HasFocus) + cost <<= 1; + + if (difference & QStyle::State_Selected) + cost <<= 1; + + if (cost < bestCost) { + bestCost = cost; + bestMatch = state.second; + } + } + return bestMatch; +} + +int QAndroidStyle::AndroidStateDrawable::extractState(const QVariantMap &value) +{ + QStyle::State state = QStyle::State_Enabled | QStyle::State_Active;; + for (auto it = value.cbegin(), end = value.cend(); it != end; ++it) { + const QString &key = it.key(); + bool val = it.value().toString() == QLatin1String("true"); + if (key == QLatin1String("enabled")) { + state.setFlag(QStyle::State_Enabled, val); + continue; + } + + if (key == QLatin1String("window_focused")) { + state.setFlag(QStyle::State_Active, val); + continue; + } + + if (key == QLatin1String("focused")) { + state.setFlag(QStyle::State_HasFocus, val); + continue; + } + + if (key == QLatin1String("checked")) { + state |= val ? QStyle::State_On : QStyle::State_Off; + continue; + } + + if (key == QLatin1String("pressed")) { + state |= val ? QStyle::State_Sunken : QStyle::State_Raised; + continue; + } + + if (key == QLatin1String("selected")) { + state.setFlag(QStyle::State_Selected, val); + continue; + } + + if (key == QLatin1String("active")) { + state.setFlag(QStyle::State_Active, val); + continue; + } + + if (key == QLatin1String("multiline")) + return 0; + + if (key == QLatin1String("background") && val) + return -1; + } + return static_cast<int>(state); +} + +void QAndroidStyle::AndroidStateDrawable::setPaddingLeftToSizeWidth() +{ + for (const StateType &type : qAsConst(m_states)) + const_cast<AndroidDrawable *>(type.second)->setPaddingLeftToSizeWidth(); +} + +QAndroidStyle::AndroidLayerDrawable::AndroidLayerDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + m_id = 0; + m_factor = 1; + m_orientation = Qt::Horizontal; + const QVariantList layers = drawable.value(QLatin1String("layers")).toList(); + for (const QVariant &layer : layers) { + QVariantMap layerMap = layer.toMap(); + AndroidDrawable *ad = fromMap(layerMap, itemType); + if (ad) { + LayerType l; + l.second = ad; + l.first = layerMap.value(QLatin1String("id")).toInt(); + m_layers << l; + } + } +} + +QAndroidStyle::AndroidLayerDrawable::~AndroidLayerDrawable() +{ + for (const LayerType &layer : qAsConst(m_layers)) + delete layer.second; +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidLayerDrawable::type() const +{ + return QAndroidStyle::Layer; +} + +void QAndroidStyle::AndroidLayerDrawable::setFactor(int id, double factor, Qt::Orientation orientation) +{ + m_id = id; + m_factor = factor; + m_orientation = orientation; +} + +void QAndroidStyle::AndroidLayerDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + for (const LayerType &layer : m_layers) { + if (layer.first == m_id) { + QStyleOption copy(*opt); + if (m_orientation == Qt::Horizontal) + copy.rect.setWidth(copy.rect.width() * m_factor); + else + copy.rect.setHeight(copy.rect.height() * m_factor); + layer.second->draw(painter, ©); + } else { + layer.second->draw(painter, opt); + } + } +} + +QAndroidStyle::AndroidDrawable *QAndroidStyle::AndroidLayerDrawable::layer(int id) const +{ + for (const LayerType &layer : m_layers) + if (layer.first == id) + return layer.second; + return 0; +} + +QSize QAndroidStyle::AndroidLayerDrawable::size() const +{ + QSize sz; + for (const LayerType &layer : m_layers) + sz = sz.expandedTo(layer.second->size()); + return sz; +} + +QAndroidStyle::AndroidControl::AndroidControl(const QVariantMap &control, + QAndroidStyle::ItemType itemType) +{ + QVariantMap::const_iterator it = control.find(QLatin1String("View_background")); + if (it != control.end()) + m_background = AndroidDrawable::fromMap(it.value().toMap(), itemType); + else + m_background = 0; + + it = control.find(QLatin1String("View_minWidth")); + if (it != control.end()) + m_minSize.setWidth(it.value().toInt()); + + it = control.find(QLatin1String("View_minHeight")); + if (it != control.end()) + m_minSize.setHeight(it.value().toInt()); + + it = control.find(QLatin1String("View_maxWidth")); + if (it != control.end()) + m_maxSize.setWidth(it.value().toInt()); + + it = control.find(QLatin1String("View_maxHeight")); + if (it != control.end()) + m_maxSize.setHeight(it.value().toInt()); +} + +QAndroidStyle::AndroidControl::~AndroidControl() +{ + delete m_background; +} + +void QAndroidStyle::AndroidControl::drawControl(const QStyleOption *opt, QPainter *p, const QWidget * /* w */) +{ + if (m_background) { + m_background->draw(p, opt); + } else { + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if ((frame->state & State_Sunken) || (frame->state & State_Raised)) { + qDrawShadePanel(p, frame->rect, frame->palette, frame->state & State_Sunken, + frame->lineWidth); + } else { + qDrawPlainRect(p, frame->rect, frame->palette.foreground().color(), frame->lineWidth); + } + } else { + if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(opt)) { + QColor bg = fropt->backgroundColor; + QPen oldPen = p->pen(); + if (bg.isValid()) { + int h, s, v; + bg.getHsv(&h, &s, &v); + if (v >= 128) + p->setPen(Qt::black); + else + p->setPen(Qt::white); + } else { + p->setPen(opt->palette.foreground().color()); + } + QRect focusRect = opt->rect.adjusted(1, 1, -1, -1); + p->drawRect(focusRect.adjusted(0, 0, -1, -1)); //draw pen inclusive + p->setPen(oldPen); + } else { + p->fillRect(opt->rect, opt->palette.brush(QPalette::Background)); + } + } + } +} + +QRect QAndroidStyle::AndroidControl::subElementRect(QStyle::SubElement /* subElement */, + const QStyleOption *option, + const QWidget * /* widget */) const +{ + if (const AndroidDrawable *drawable = backgroundDrawable()) { + if (drawable->type() == State) + drawable = static_cast<const AndroidStateDrawable *>(backgroundDrawable())->bestAndroidStateMatch(option); + + const QMargins &padding = drawable->padding(); + + QRect r = option->rect.adjusted(padding.left(), padding.top(), + -padding.right(), -padding.bottom()); + + if (r.width() < m_minSize.width()) + r.setWidth(m_minSize.width()); + + if (r.height() < m_minSize.height()) + r.setHeight(m_minSize.height()); + + return visualRect(option->direction, option->rect, r); + } + return option->rect; +} + +QRect QAndroidStyle::AndroidControl::subControlRect(const QStyleOptionComplex *option, + QStyle::SubControl /*sc*/, + const QWidget *widget) const +{ + return subElementRect(QStyle::SE_CustomBase, option, widget); +} + +QSize QAndroidStyle::AndroidControl::sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, + const QWidget * /* w */) const +{ + QSize sz; + if (const AndroidDrawable *drawable = backgroundDrawable()) { + + if (drawable->type() == State) + drawable = static_cast<const AndroidStateDrawable*>(backgroundDrawable())->bestAndroidStateMatch(opt); + const QMargins &padding = drawable->padding(); + sz.setWidth(padding.left() + padding.right()); + sz.setHeight(padding.top() + padding.bottom()); + if (sz.isEmpty()) + sz = drawable->size(); + } + sz += contentsSize; + if (contentsSize.height() < opt->fontMetrics.height()) + sz.setHeight(sz.height() + (opt->fontMetrics.height() - contentsSize.height())); + if (sz.height() < m_minSize.height()) + sz.setHeight(m_minSize.height()); + if (sz.width() < m_minSize.width()) + sz.setWidth(m_minSize.width()); + return sz; +} + +QMargins QAndroidStyle::AndroidControl::padding() +{ + if (const AndroidDrawable *drawable = m_background) { + if (drawable->type() == State) + drawable = static_cast<const AndroidStateDrawable *>(m_background)->bestAndroidStateMatch(0); + return drawable->padding(); + } + return QMargins(); +} + +QSize QAndroidStyle::AndroidControl::size(const QStyleOption *option) +{ + if (const AndroidDrawable *drawable = backgroundDrawable()) { + if (drawable->type() == State) + drawable = static_cast<const AndroidStateDrawable *>(backgroundDrawable())->bestAndroidStateMatch(option); + return drawable->size(); + } + return QSize(); +} + +const QAndroidStyle::AndroidDrawable *QAndroidStyle::AndroidControl::backgroundDrawable() const +{ + return m_background; +} + +QAndroidStyle::AndroidCompoundButtonControl::AndroidCompoundButtonControl(const QVariantMap &control, + ItemType itemType) + : AndroidControl(control, itemType) +{ + QVariantMap::const_iterator it = control.find(QLatin1String("CompoundButton_button")); + if (it != control.end()) { + m_button = AndroidDrawable::fromMap(it.value().toMap(), itemType); + const_cast<AndroidDrawable *>(m_button)->setPaddingLeftToSizeWidth(); + } else { + m_button = 0; + } +} + +QAndroidStyle::AndroidCompoundButtonControl::~AndroidCompoundButtonControl() +{ + delete m_button; +} + +void QAndroidStyle::AndroidCompoundButtonControl::drawControl(const QStyleOption *opt, + QPainter *p, + const QWidget *w) +{ + AndroidControl::drawControl(opt, p, w); + if (m_button) + m_button->draw(p, opt); +} + +QMargins QAndroidStyle::AndroidCompoundButtonControl::padding() +{ + if (m_button) + return m_button->padding(); + return AndroidControl::padding(); +} + +QSize QAndroidStyle::AndroidCompoundButtonControl::size(const QStyleOption *option) +{ + if (m_button) { + if (m_button->type() == State) + return static_cast<const AndroidStateDrawable *>(m_button)->bestAndroidStateMatch(option)->size(); + return m_button->size(); + } + return AndroidControl::size(option); +} + +const QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidCompoundButtonControl::backgroundDrawable() const +{ + return m_background ? m_background : m_button; +} + +QAndroidStyle::AndroidProgressBarControl::AndroidProgressBarControl(const QVariantMap &control, + ItemType itemType) + : AndroidControl(control, itemType) +{ + QVariantMap::const_iterator it = control.find(QLatin1String("ProgressBar_indeterminateDrawable")); + if (it != control.end()) + m_indeterminateDrawable = AndroidDrawable::fromMap(it.value().toMap(), itemType); + else + m_indeterminateDrawable = 0; + + it = control.find(QLatin1String("ProgressBar_progressDrawable")); + if (it != control.end()) + m_progressDrawable = AndroidDrawable::fromMap(it.value().toMap(), itemType); + else + m_progressDrawable = 0; + + it = control.find(QLatin1String("ProgressBar_progress_id")); + if (it != control.end()) + m_progressId = it.value().toInt(); + + it = control.find(QLatin1String("ProgressBar_secondaryProgress_id")); + if (it != control.end()) + m_secondaryProgress_id = it.value().toInt(); + + it = control.find(QLatin1String("ProgressBar_minWidth")); + if (it != control.end()) + m_minSize.setWidth(it.value().toInt()); + + it = control.find(QLatin1String("ProgressBar_minHeight")); + if (it != control.end()) + m_minSize.setHeight(it.value().toInt()); + + it = control.find(QLatin1String("ProgressBar_maxWidth")); + if (it != control.end()) + m_maxSize.setWidth(it.value().toInt()); + + it = control.find(QLatin1String("ProgressBar_maxHeight")); + if (it != control.end()) + m_maxSize.setHeight(it.value().toInt()); +} + +QAndroidStyle::AndroidProgressBarControl::~AndroidProgressBarControl() +{ + delete m_progressDrawable; + delete m_indeterminateDrawable; +} + +void QAndroidStyle::AndroidProgressBarControl::drawControl(const QStyleOption *option, QPainter *p, const QWidget * /* w */) +{ + if (!m_progressDrawable) + return; + + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + if (m_progressDrawable->type() == QAndroidStyle::Layer) { + const double fraction = double(qint64(pb->progress) - pb->minimum) / (qint64(pb->maximum) - pb->minimum); + QAndroidStyle::AndroidDrawable *clipDrawable = static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->layer(m_progressId); + if (clipDrawable->type() == QAndroidStyle::Clip) + static_cast<AndroidClipDrawable *>(clipDrawable)->setFactor(fraction, pb->orientation); + else + static_cast<AndroidLayerDrawable *>(m_progressDrawable)->setFactor(m_progressId, fraction, pb->orientation); + } + m_progressDrawable->draw(p, option); + } +} + +QRect QAndroidStyle::AndroidProgressBarControl::subElementRect(QStyle::SubElement subElement, + const QStyleOption *option, + const QWidget *widget) const +{ + if (const QStyleOptionProgressBar *progressBarOption = + qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + const bool horizontal = progressBarOption->orientation == Qt::Vertical; + if (!m_background) + return option->rect; + + QMargins padding = m_background->padding(); + QRect p(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top()); + padding = m_indeterminateDrawable->padding(); + p |= QRect(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top()); + padding = m_progressDrawable->padding(); + p |= QRect(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top()); + QRect r = option->rect.adjusted(p.left(), p.top(), -p.right(), -p.bottom()); + + if (horizontal) { + if (r.height()<m_minSize.height()) + r.setHeight(m_minSize.height()); + + if (r.height()>m_maxSize.height()) + r.setHeight(m_maxSize.height()); + } else { + if (r.width()<m_minSize.width()) + r.setWidth(m_minSize.width()); + + if (r.width()>m_maxSize.width()) + r.setWidth(m_maxSize.width()); + } + return visualRect(option->direction, option->rect, r); + } + return AndroidControl::subElementRect(subElement, option, widget); +} + +QSize QAndroidStyle::AndroidProgressBarControl::sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, + const QWidget * /* w */) const +{ + QSize sz(contentsSize); + if (sz.height() < m_minSize.height()) + sz.setHeight(m_minSize.height()); + if (sz.width() < m_minSize.width()) + sz.setWidth(m_minSize.width()); + + if (const QStyleOptionProgressBar *progressBarOption = + qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + if (progressBarOption->orientation == Qt::Vertical) { + if (sz.height() > m_maxSize.height()) + sz.setHeight(m_maxSize.height()); + } else { + if (sz.width() > m_maxSize.width()) + sz.setWidth(m_maxSize.width()); + } + } + return contentsSize; +} + +QAndroidStyle::AndroidSeekBarControl::AndroidSeekBarControl(const QVariantMap &control, + ItemType itemType) + : AndroidProgressBarControl(control, itemType) +{ + QVariantMap::const_iterator it = control.find(QLatin1String("SeekBar_thumb")); + if (it != control.end()) + m_seekBarThumb = AndroidDrawable::fromMap(it.value().toMap(), itemType); + else + m_seekBarThumb = 0; +} + +QAndroidStyle::AndroidSeekBarControl::~AndroidSeekBarControl() +{ + delete m_seekBarThumb; +} + +void QAndroidStyle::AndroidSeekBarControl::drawControl(const QStyleOption *option, + QPainter *p, + const QWidget * /* w */) +{ + if (!m_seekBarThumb || !m_progressDrawable) + return; + + if (const QStyleOptionSlider *styleOption = + qstyleoption_cast<const QStyleOptionSlider *>(option)) { + double factor = double(styleOption->sliderPosition - styleOption->minimum) + / double(styleOption->maximum - styleOption->minimum); + + // Android does not have a vertical slider. To support the vertical orientation, we rotate + // the painter and pretend that we are horizontal. + if (styleOption->orientation == Qt::Vertical) + factor = 1 - factor; + + if (m_progressDrawable->type() == QAndroidStyle::Layer) { + QAndroidStyle::AndroidDrawable *clipDrawable = static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->layer(m_progressId); + if (clipDrawable->type() == QAndroidStyle::Clip) + static_cast<QAndroidStyle::AndroidClipDrawable *>(clipDrawable)->setFactor(factor, Qt::Horizontal); + else + static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->setFactor(m_progressId, factor, Qt::Horizontal); + } + const AndroidDrawable *drawable = m_seekBarThumb; + if (drawable->type() == State) + drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(option); + QStyleOption copy(*option); + + p->save(); + + if (styleOption->orientation == Qt::Vertical) { + // rotate the painter, and transform the rectangle to match + p->rotate(90); + copy.rect = QRect(copy.rect.y(), copy.rect.x() - copy.rect.width(), copy.rect.height(), copy.rect.width()); + } + + copy.rect.setHeight(m_progressDrawable->size().height()); + copy.rect.setWidth(copy.rect.width() - drawable->size().width()); + const int yTranslate = abs(drawable->size().height() - copy.rect.height()) / 2; + copy.rect.translate(drawable->size().width() / 2, yTranslate); + m_progressDrawable->draw(p, ©); + int pos = copy.rect.width() * factor - drawable->size().width() / 2; + copy.rect.translate(pos, -yTranslate); + copy.rect.setSize(drawable->size()); + m_seekBarThumb->draw(p, ©); + + p->restore(); + } +} + +QSize QAndroidStyle::AndroidSeekBarControl::sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, + const QWidget *w) const +{ + QSize sz = AndroidProgressBarControl::sizeFromContents(opt, contentsSize, w); + if (!m_seekBarThumb) + return sz; + const AndroidDrawable *drawable = m_seekBarThumb; + if (drawable->type() == State) + drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(opt); + return sz.expandedTo(drawable->size()); +} + +QRect QAndroidStyle::AndroidSeekBarControl::subControlRect(const QStyleOptionComplex *option, + SubControl sc, + const QWidget * /* widget */) const +{ + const QStyleOptionSlider *styleOption = + qstyleoption_cast<const QStyleOptionSlider *>(option); + + if (m_seekBarThumb && sc == SC_SliderHandle && styleOption) { + const AndroidDrawable *drawable = m_seekBarThumb; + if (drawable->type() == State) + drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(option); + + QRect r(option->rect); + double factor = double(styleOption->sliderPosition - styleOption->minimum) + / (styleOption->maximum - styleOption->minimum); + if (styleOption->orientation == Qt::Vertical) { + int pos = option->rect.height() * (1 - factor) - double(drawable->size().height() / 2); + r.setY(r.y() + pos); + } else { + int pos = option->rect.width() * factor - double(drawable->size().width() / 2); + r.setX(r.x() + pos); + } + r.setSize(drawable->size()); + return r; + } + return option->rect; +} + +QAndroidStyle::AndroidSpinnerControl::AndroidSpinnerControl(const QVariantMap &control, + QAndroidStyle::ItemType itemType) + : AndroidControl(control, itemType) +{} + +QRect QAndroidStyle::AndroidSpinnerControl::subControlRect(const QStyleOptionComplex *option, + SubControl sc, + const QWidget *widget) const +{ + if (sc == QStyle::SC_ComboBoxListBoxPopup) + return option->rect; + if (sc == QStyle::SC_ComboBoxArrow) { + const QRect editField = subControlRect(option, QStyle::SC_ComboBoxEditField, widget); + return QRect(editField.topRight(), QSize(option->rect.width() - editField.width(), option->rect.height())); + } + return AndroidControl::subControlRect(option, sc, widget); +} + +QT_END_NAMESPACE diff --git a/src/plugins/styles/android/qandroidstyle_p.h b/src/plugins/styles/android/qandroidstyle_p.h new file mode 100644 index 0000000000..3faa08afb9 --- /dev/null +++ b/src/plugins/styles/android/qandroidstyle_p.h @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** Copyright (C) 2013 BogDan Vatra <bogdan@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANDROIDSTYLE_P_H +#define QANDROIDSTYLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qstylefactory.cpp. This header may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include <QtWidgets/private/qfusionstyle_p.h> +#include <QtCore/QList> +#include <QtCore/QMargins> +#include <QtCore/QHash> +#include <QtCore/QVariantMap> + +QT_BEGIN_NAMESPACE + +class Q_WIDGETS_EXPORT QAndroidStyle : public QFusionStyle +{ + Q_OBJECT + +public: + enum ItemType + { + QC_UnknownType = -1, + QC_View, + QC_GroupBox, + QC_Button, + QC_Checkbox, + QC_RadioButton, + QC_Slider, + QC_Switch, + QC_EditText, + QC_Combobox, + QC_BusyIndicator, + QC_ProgressBar, + QC_Tab, + QC_TabButton, + QC_RatingIndicator, + QC_SearchBox, + QC_CustomControl=0xf00, + QC_ControlMask=0xfff + }; + + struct Android9PatchChunk + { + QVector<int> xDivs; + QVector<int> yDivs; + QVector<int> colors; + }; + + struct AndroidItemStateInfo + { + AndroidItemStateInfo():state(0){} + int state; + QByteArray filePath; + QByteArray hashKey; + Android9PatchChunk chunkData; + QSize size; + QMargins padding; + }; + + enum AndroidDrawableType + { + Color, + Image, + Clip, + NinePatch, + Gradient, + State, + Layer + }; + + class AndroidDrawable + { + public: + AndroidDrawable(const QVariantMap &drawable, ItemType itemType); + virtual ~AndroidDrawable(); + virtual void initPadding(const QVariantMap &drawable); + virtual AndroidDrawableType type() const = 0; + virtual void draw(QPainter *painter,const QStyleOption *opt) const = 0; + const QMargins &padding() const; + virtual QSize size() const; + static AndroidDrawable *fromMap(const QVariantMap &drawable, ItemType itemType); + static QMargins extractMargins(const QVariantMap &value); + virtual void setPaddingLeftToSizeWidth(); + protected: + ItemType m_itemType; + QMargins m_padding; + }; + + class AndroidColorDrawable: public AndroidDrawable + { + public: + AndroidColorDrawable(const QVariantMap &drawable, ItemType itemType); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter,const QStyleOption *opt) const; + + protected: + QColor m_color; + }; + + class AndroidImageDrawable: public AndroidDrawable + { + public: + AndroidImageDrawable(const QVariantMap &drawable, ItemType itemType); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter,const QStyleOption *opt) const; + virtual QSize size() const; + + protected: + QString m_filePath; + mutable QString m_hashKey; + QSize m_size; + }; + + class Android9PatchDrawable: public AndroidImageDrawable + { + public: + Android9PatchDrawable(const QVariantMap &drawable, ItemType itemType); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + private: + static int calculateStretch(int boundsLimit, int startingPoint, + int srcSpace, int numStrechyPixelsRemaining, + int numFixedPixelsRemaining); + void extractIntArray(const QVariantList &values, QVector<int> &array); + private: + Android9PatchChunk m_chunkData; + }; + + class AndroidGradientDrawable: public AndroidDrawable + { + public: + enum GradientOrientation + { + TOP_BOTTOM, + TR_BL, + RIGHT_LEFT, + BR_TL, + BOTTOM_TOP, + BL_TR, + LEFT_RIGHT, + TL_BR + }; + + public: + AndroidGradientDrawable(const QVariantMap &drawable, ItemType itemType); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + QSize size() const; + private: + mutable QLinearGradient m_gradient; + GradientOrientation m_orientation; + int m_radius; + }; + + class AndroidClipDrawable: public AndroidDrawable + { + public: + AndroidClipDrawable(const QVariantMap &drawable, ItemType itemType); + ~AndroidClipDrawable(); + virtual AndroidDrawableType type() const; + virtual void setFactor(double factor, Qt::Orientation orientation); + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + + private: + double m_factor; + Qt::Orientation m_orientation; + const AndroidDrawable *m_drawable; + }; + + class AndroidStateDrawable: public AndroidDrawable + { + public: + AndroidStateDrawable(const QVariantMap &drawable, ItemType itemType); + ~AndroidStateDrawable(); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + inline const AndroidDrawable *bestAndroidStateMatch(const QStyleOption *opt) const; + static int extractState(const QVariantMap &value); + virtual void setPaddingLeftToSizeWidth(); + QSize sizeImage(const QStyleOption *opt) const; + private: + typedef QPair<int, const AndroidDrawable *> StateType; + QList<StateType> m_states; + }; + + class AndroidLayerDrawable: public AndroidDrawable + { + public: + AndroidLayerDrawable(const QVariantMap &drawable, QAndroidStyle::ItemType itemType); + ~AndroidLayerDrawable(); + virtual AndroidDrawableType type() const; + virtual void setFactor(int id, double factor, Qt::Orientation orientation); + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + AndroidDrawable *layer(int id) const; + QSize size() const; + private: + typedef QPair<int, AndroidDrawable *> LayerType; + QList<LayerType> m_layers; + int m_id; + double m_factor; + Qt::Orientation m_orientation; + }; + + class AndroidControl + { + public: + AndroidControl(const QVariantMap &control, ItemType itemType); + virtual ~AndroidControl(); + virtual void drawControl(const QStyleOption *opt, QPainter *p, const QWidget *w); + virtual QRect subElementRect(SubElement subElement, + const QStyleOption *option, + const QWidget *widget = 0) const; + virtual QRect subControlRect(const QStyleOptionComplex *option, + SubControl sc, + const QWidget *widget = 0) const; + virtual QSize sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, + const QWidget *w) const; + virtual QMargins padding(); + virtual QSize size(const QStyleOption *option); + protected: + virtual const AndroidDrawable * backgroundDrawable() const; + const AndroidDrawable *m_background; + QSize m_minSize; + QSize m_maxSize; + }; + + class AndroidCompoundButtonControl : public AndroidControl + { + public: + AndroidCompoundButtonControl(const QVariantMap &control, ItemType itemType); + virtual ~AndroidCompoundButtonControl(); + virtual void drawControl(const QStyleOption *opt, QPainter *p, const QWidget *w); + virtual QMargins padding(); + virtual QSize size(const QStyleOption *option); + protected: + virtual const AndroidDrawable * backgroundDrawable() const; + const AndroidDrawable *m_button; + }; + + class AndroidProgressBarControl : public AndroidControl + { + public: + AndroidProgressBarControl(const QVariantMap &control, ItemType itemType); + virtual ~AndroidProgressBarControl(); + virtual void drawControl(const QStyleOption *option, QPainter *p, const QWidget *w); + virtual QRect subElementRect(SubElement subElement, + const QStyleOption *option, + const QWidget *widget = 0) const; + + QSize sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, + const QWidget *w) const; + protected: + AndroidDrawable *m_progressDrawable; + AndroidDrawable *m_indeterminateDrawable; + int m_secondaryProgress_id; + int m_progressId; + }; + + class AndroidSeekBarControl : public AndroidProgressBarControl + { + public: + AndroidSeekBarControl(const QVariantMap &control, ItemType itemType); + virtual ~AndroidSeekBarControl(); + virtual void drawControl(const QStyleOption *option, QPainter *p, const QWidget *w); + QSize sizeFromContents(const QStyleOption *opt, + const QSize &contentsSize, const QWidget *w) const; + QRect subControlRect(const QStyleOptionComplex *option, SubControl sc, + const QWidget *widget = 0) const; + private: + AndroidDrawable *m_seekBarThumb; + }; + + class AndroidSpinnerControl : public AndroidControl + { + public: + AndroidSpinnerControl(const QVariantMap &control, ItemType itemType); + virtual ~AndroidSpinnerControl(){} + virtual QRect subControlRect(const QStyleOptionComplex *option, + SubControl sc, + const QWidget *widget = 0) const; + }; + + typedef QList<AndroidItemStateInfo *> AndroidItemStateInfoList; + +public: + QAndroidStyle(); + ~QAndroidStyle(); + + virtual void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + + virtual void drawControl(QStyle::ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + + virtual QRect subElementRect(SubElement subElement, const QStyleOption *option, + const QWidget *widget = 0) const; + virtual void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *widget = 0) const; + virtual SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *widget = 0) const; + virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget = 0) const; + + virtual int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + + virtual QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *w = 0) const; + + virtual QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + + virtual QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + virtual QPalette standardPalette() const; + void polish(QWidget *widget); + void unpolish(QWidget *widget); + +private: + Q_DISABLE_COPY(QAndroidStyle) + static ItemType qtControl(QStyle::ComplexControl control); + static ItemType qtControl(QStyle::ContentsType contentsType); + static ItemType qtControl(QStyle::ControlElement controlElement); + static ItemType qtControl(QStyle::PrimitiveElement primitiveElement); + static ItemType qtControl(QStyle::SubElement subElement); + static ItemType qtControl(const QString &android); + +private: + typedef QHash<int, AndroidControl *> AndroidControlsHash; + AndroidControlsHash m_androidControlsHash; + QPalette m_standardPalette; + AndroidCompoundButtonControl *checkBoxControl; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDSTYLE_P_H diff --git a/src/plugins/styles/mac/mac.pro b/src/plugins/styles/mac/mac.pro new file mode 100644 index 0000000000..bf9660eed3 --- /dev/null +++ b/src/plugins/styles/mac/mac.pro @@ -0,0 +1,19 @@ +TARGET = qmacstyle + +QT += widgets-private + +SOURCES += \ + main.mm \ + qmacstyle_mac.mm + +HEADERS += \ + qmacstyle_mac_p.h \ + qmacstyle_mac_p_p.h + +LIBS_PRIVATE += -framework AppKit -framework Carbon + +DISTFILES += macstyle.json + +PLUGIN_TYPE = styles +PLUGIN_CLASS_NAME = QMacStylePlugin +load(qt_plugin) diff --git a/src/plugins/styles/mac/macstyle.json b/src/plugins/styles/mac/macstyle.json new file mode 100644 index 0000000000..5897815eec --- /dev/null +++ b/src/plugins/styles/mac/macstyle.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "macintosh" ] +} diff --git a/src/plugins/styles/mac/main.mm b/src/plugins/styles/mac/main.mm new file mode 100644 index 0000000000..ae31bb95fb --- /dev/null +++ b/src/plugins/styles/mac/main.mm @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets/qstyleplugin.h> +#include "qmacstyle_mac_p.h" + +QT_BEGIN_NAMESPACE + +class QMacStylePlugin : public QStylePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "macstyle.json") +public: + QStyle *create(const QString &key); +}; + +QStyle *QMacStylePlugin::create(const QString &key) +{ + QMacAutoReleasePool pool; + if (key.compare(QLatin1String("macintosh"), Qt::CaseInsensitive) == 0) + return new QMacStyle(); + + return 0; +} + +QT_END_NAMESPACE + +#include "main.moc" + diff --git a/src/plugins/styles/mac/qmacstyle.qdoc b/src/plugins/styles/mac/qmacstyle.qdoc new file mode 100644 index 0000000000..fcbc813844 --- /dev/null +++ b/src/plugins/styles/mac/qmacstyle.qdoc @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/*! + \class QMacStyle + \brief The QMacStyle class provides a \macos style using the Apple Appearance Manager. + + \ingroup appearance + \inmodule QtWidgets + \internal + + This class is implemented as a wrapper to the HITheme + APIs, allowing applications to be styled according to the current + theme in use on \macos. This is done by having primitives + in QStyle implemented in terms of what \macos would normally theme. + + \warning This style is only available on \macos because it relies on the + HITheme APIs. + + There are additional issues that should be taken + into consideration to make an application compatible with the + \l{Apple Human Interface Guidelines}{Apple Human Interface Guidelines}. Some of these issues are outlined + below. + + \list + + \li Layout - The restrictions on window layout are such that some + aspects of layout that are style-dependent cannot be achieved + using QLayout. Changes are being considered (and feedback would be + appreciated) to make layouts QStyle-able. Some of the restrictions + involve horizontal and vertical widget alignment and widget size + (covered below). + + \li Widget size - \macos allows widgets to have specific fixed sizes. Qt + does not fully implement this behavior so as to maintain cross-platform + compatibility. As a result some widgets sizes may be inappropriate (and + subsequently not rendered correctly by the HITheme APIs).The + QWidget::sizeHint() will return the appropriate size for many + managed widgets (widgets enumerated in \l QStyle::ContentsType). + + \li Effects - QMacStyle uses HITheme for performing most of the drawing, but + also uses emulation in a few cases where HITheme does not provide the + required functionality (for example, tab bars on Panther, the toolbar + separator, etc). We tried to make the emulation as close to the original as + possible. Please report any issues you see in effects or non-standard + widgets. + + \endlist + + There are other issues that need to be considered in the feel of + your application (including the general color scheme to match the + Aqua colors). The Guidelines mentioned above will remain current + with new advances and design suggestions for \macos. + + Note that the functions provided by QMacStyle are + reimplementations of QStyle functions; see QStyle for their + documentation. + + \image qmacstyle.png + \sa QWindowsVistaStyle, QWindowsStyle, QFusionStyle +*/ + + +/*! + \enum QMacStyle::WidgetSizePolicy + + \value SizeSmall + \value SizeLarge + \value SizeMini + \value SizeDefault +*/ + +/*! \fn QMacStyle::QMacStyle() + Constructs a QMacStyle object. +*/ + +/*! \fn QMacStyle::~QMacStyle() + Destructs a QMacStyle object. +*/ + +/*! \fn void QMacStyle::polish(QPalette &pal) + \reimp +*/ + +/*! \fn void QMacStyle::polish(QApplication *) + \reimp +*/ + +/*! \fn void QMacStyle::unpolish(QApplication *) + \reimp +*/ + +/*! \fn void QMacStyle::polish(QWidget* w) + \reimp +*/ + +/*! \fn void QMacStyle::unpolish(QWidget* w) + \reimp +*/ + +/*! \fn int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const + \reimp +*/ + +/*! \fn QPalette QMacStyle::standardPalette() const + \reimp +*/ + +/*! \fn int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, QStyleHintReturn *hret) const + \reimp +*/ + +/*! \fn QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, const QStyleOption *opt) const + \reimp +*/ + +/*! \fn QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, const QWidget *widget) const + \reimp +*/ + +/*! \fn void QMacStyle::setWidgetSizePolicy(const QWidget *widget, WidgetSizePolicy policy) + + \obsolete + + Call QWidget::setAttribute() with Qt::WA_MacMiniSize, Qt::WA_MacSmallSize, + or Qt::WA_MacNormalSize instead. +*/ + +/*! \fn QMacStyle::WidgetSizePolicy QMacStyle::widgetSizePolicy(const QWidget *widget, const QStyleOption *opt = 0) + \obsolete + + Call QWidget::testAttribute() with Qt::WA_MacMiniSize, Qt::WA_MacSmallSize, + or Qt::WA_MacNormalSize instead. +*/ + +/*! \fn void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const + + \reimp +*/ + +/*! \fn void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, const QWidget *w) const + + \reimp +*/ + +/*! \fn QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *widget) const + + \reimp +*/ + +/*! \fn void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const + \reimp +*/ + +/*! \fn QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, const QPoint &pt, const QWidget *widget) const + \reimp +*/ + +/*! \fn QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const + \reimp +*/ + +/*! \fn QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &csz, const QWidget *widget) const + \reimp +*/ + +/*! \fn void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const + \reimp +*/ + +/*! \fn bool QMacStyle::event(QEvent *e) + \reimp +*/ + +/*! \fn QIcon QMacStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *opt, const QWidget *widget) const + \reimp +*/ + +/*! \fn int QMacStyle::layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption *option, const QWidget *widget) const + \reimp +*/ + diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm new file mode 100644 index 0000000000..47193f2a84 --- /dev/null +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -0,0 +1,7041 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + Note: The qdoc comments for QMacStyle are contained in + .../doc/src/qstyles.qdoc. +*/ + +#include <AppKit/AppKit.h> + +#include "qmacstyle_mac_p.h" +#include "qmacstyle_mac_p_p.h" + +#define QMAC_QAQUASTYLE_SIZE_CONSTRAIN +//#define DEBUG_SIZE_CONSTRAINT + +#include <private/qcore_mac_p.h> +#include <private/qcombobox_p.h> +#include <private/qtabbar_p.h> +#include <private/qpainter_p.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qcombobox.h> +#if QT_CONFIG(dialogbuttonbox) +#include <qdialogbuttonbox.h> +#endif +#include <qdockwidget.h> +#include <qevent.h> +#include <qfocusframe.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qhash.h> +#include <qheaderview.h> +#include <qlineedit.h> +#include <qmainwindow.h> +#include <qmdisubwindow.h> +#include <qmenubar.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <qpixmapcache.h> +#include <qpointer.h> +#include <qprogressbar.h> +#if QT_CONFIG(pushbutton) +#include <qpushbutton.h> +#endif +#include <qradiobutton.h> +#include <qrubberband.h> +#include <qscrollbar.h> +#include <qsizegrip.h> +#include <qstyleoption.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qtreeview.h> +#include <qtableview.h> +#include <qoperatingsystemversion.h> +#include <qwizard.h> +#include <qdebug.h> +#include <qlibrary.h> +#include <qdatetimeedit.h> +#include <qmath.h> +#include <QtWidgets/qgraphicsproxywidget.h> +#include <QtWidgets/qgraphicsview.h> +#include <QtCore/qvariant.h> +#include <private/qstylehelper_p.h> +#include <private/qstyleanimation_p.h> +#include <qpa/qplatformfontdatabase.h> +#include <qpa/qplatformtheme.h> +#include <QtGui/private/qcoregraphics_p.h> + +QT_USE_NAMESPACE + +static QWindow *qt_getWindow(const QWidget *widget) +{ + return widget ? widget->window()->windowHandle() : 0; +} + +@interface QT_MANGLE_NAMESPACE(NotificationReceiver) : NSObject { +QMacStylePrivate *mPrivate; +} +- (id)initWithPrivate:(QMacStylePrivate *)priv; +- (void)scrollBarStyleDidChange:(NSNotification *)notification; +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(NotificationReceiver); + +@implementation NotificationReceiver +- (id)initWithPrivate:(QMacStylePrivate *)priv +{ + self = [super init]; + mPrivate = priv; + return self; +} + +- (void)scrollBarStyleDidChange:(NSNotification *)notification +{ + Q_UNUSED(notification); + + // purge destroyed scroll bars: + QMacStylePrivate::scrollBars.removeAll(QPointer<QObject>()); + + QEvent event(QEvent::StyleChange); + for (const auto &o : QMacStylePrivate::scrollBars) + QCoreApplication::sendEvent(o, &event); +} +@end + +QT_BEGIN_NAMESPACE + +// The following constants are used for adjusting the size +// of push buttons so that they are drawn inside their bounds. +const int QMacStylePrivate::PushButtonLeftOffset = 6; +const int QMacStylePrivate::PushButtonTopOffset = 4; +const int QMacStylePrivate::PushButtonRightOffset = 12; +const int QMacStylePrivate::PushButtonBottomOffset = 12; +const int QMacStylePrivate::MiniButtonH = 26; +const int QMacStylePrivate::SmallButtonH = 30; +const int QMacStylePrivate::BevelButtonW = 50; +const int QMacStylePrivate::BevelButtonH = 22; +const int QMacStylePrivate::PushButtonContentPadding = 6; + +QVector<QPointer<QObject> > QMacStylePrivate::scrollBars; + +// Title bar gradient colors for Lion were determined by inspecting PSDs exported +// using CoreUI's CoreThemeDocument; there is no public API to retrieve them + +static QLinearGradient titlebarGradientActive() +{ + static QLinearGradient gradient; + if (gradient == QLinearGradient()) { + gradient.setColorAt(0, QColor(235, 235, 235)); + gradient.setColorAt(0.5, QColor(210, 210, 210)); + gradient.setColorAt(0.75, QColor(195, 195, 195)); + gradient.setColorAt(1, QColor(180, 180, 180)); + } + return gradient; +} + +static QLinearGradient titlebarGradientInactive() +{ + static QLinearGradient gradient; + if (gradient == QLinearGradient()) { + gradient.setColorAt(0, QColor(250, 250, 250)); + gradient.setColorAt(1, QColor(225, 225, 225)); + } + return gradient; +} + +static const QColor titlebarSeparatorLineActive(111, 111, 111); +static const QColor titlebarSeparatorLineInactive(131, 131, 131); + +// Gradient colors used for the dock widget title bar and +// non-unifed tool bar bacground. +static const QColor mainWindowGradientBegin(240, 240, 240); +static const QColor mainWindowGradientEnd(200, 200, 200); + +static const int DisclosureOffset = 4; + +// Tab bar colors +// active: window is active +// selected: tab is selected +// hovered: tab is hovered +static const QColor tabBarTabBackgroundActive(190, 190, 190); +static const QColor tabBarTabBackgroundActiveHovered(178, 178, 178); +static const QColor tabBarTabBackgroundActiveSelected(211, 211, 211); +static const QColor tabBarTabBackground(227, 227, 227); +static const QColor tabBarTabBackgroundSelected(246, 246, 246); +static const QColor tabBarTabLineActive(160, 160, 160); +static const QColor tabBarTabLineActiveHovered(150, 150, 150); +static const QColor tabBarTabLine(210, 210, 210); +static const QColor tabBarTabLineSelected(189, 189, 189); +static const QColor tabBarCloseButtonBackgroundHovered(162, 162, 162); +static const QColor tabBarCloseButtonBackgroundPressed(153, 153, 153); +static const QColor tabBarCloseButtonBackgroundSelectedHovered(192, 192, 192); +static const QColor tabBarCloseButtonBackgroundSelectedPressed(181, 181, 181); +static const QColor tabBarCloseButtonCross(100, 100, 100); +static const QColor tabBarCloseButtonCrossSelected(115, 115, 115); + +static const int closeButtonSize = 14; +static const qreal closeButtonCornerRadius = 2.0; + +// Resolve these at run-time, since the functions was moved in Leopard. +typedef HIRect * (*PtrHIShapeGetBounds)(HIShapeRef, HIRect *); +static PtrHIShapeGetBounds ptrHIShapeGetBounds = 0; + +#ifndef QT_NO_TABBAR +static bool isVerticalTabs(const QTabBar::Shape shape) { + return (shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularEast + || shape == QTabBar::RoundedWest + || shape == QTabBar::TriangularWest); +} +#endif + +static bool isInMacUnifiedToolbarArea(QWindow *window, int windowY) +{ + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + QPlatformNativeInterface::NativeResourceForIntegrationFunction function = + nativeInterface->nativeResourceFunctionForIntegration("testContentBorderPosition"); + if (!function) + return false; // Not Cocoa platform plugin. + + typedef bool (*TestContentBorderPositionFunction)(QWindow *, int); + return (reinterpret_cast<TestContentBorderPositionFunction>(function))(window, windowY); +} + + +void drawTabCloseButton(QPainter *p, bool hover, bool selected, bool pressed) +{ + p->setRenderHints(QPainter::Antialiasing); + QRect rect(0, 0, closeButtonSize, closeButtonSize); + const int width = rect.width(); + const int height = rect.height(); + + if (hover) { + // draw background circle + QColor background; + if (selected) { + background = pressed ? tabBarCloseButtonBackgroundSelectedPressed : tabBarCloseButtonBackgroundSelectedHovered; + } else { + background = pressed ? tabBarCloseButtonBackgroundPressed : tabBarCloseButtonBackgroundHovered; + } + p->setPen(Qt::transparent); + p->setBrush(background); + p->drawRoundedRect(rect, closeButtonCornerRadius, closeButtonCornerRadius); + } + + // draw cross + const int margin = 3; + QPen crossPen; + crossPen.setColor(selected ? tabBarCloseButtonCrossSelected : tabBarCloseButtonCross); + crossPen.setWidthF(1.1); + crossPen.setCapStyle(Qt::FlatCap); + p->setPen(crossPen); + p->drawLine(margin, margin, width - margin, height - margin); + p->drawLine(margin, height - margin, width - margin, margin); +} + +#ifndef QT_NO_TABBAR +QRect rotateTabPainter(QPainter *p, QTabBar::Shape shape, QRect tabRect) +{ + if (isVerticalTabs(shape)) { + int newX, newY, newRot; + if (shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularEast) { + newX = tabRect.width(); + newY = tabRect.y(); + newRot = 90; + } else { + newX = 0; + newY = tabRect.y() + tabRect.height(); + newRot = -90; + } + tabRect.setRect(0, 0, tabRect.height(), tabRect.width()); + QMatrix m; + m.translate(newX, newY); + m.rotate(newRot); + p->setMatrix(m, true); + } + return tabRect; +} + +void drawTabShape(QPainter *p, const QStyleOptionTab *tabOpt, bool isUnified, int tabOverlap) +{ + QRect rect = tabOpt->rect; + + switch (tabOpt->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect.adjust(-tabOverlap, 0, 0, 0); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rect.adjust(0, -tabOverlap, 0, 0); + break; + default: + break; + } + + p->translate(rect.x(), rect.y()); + rect.moveLeft(0); + rect.moveTop(0); + const QRect tabRect = rotateTabPainter(p, tabOpt->shape, rect); + + const int width = tabRect.width(); + const int height = tabRect.height(); + const bool active = (tabOpt->state & QStyle::State_Active); + const bool selected = (tabOpt->state & QStyle::State_Selected); + + const QRect bodyRect(1, 1, width - 2, height - 2); + const QRect topLineRect(1, 0, width - 2, 1); + const QRect bottomLineRect(1, height - 1, width - 2, 1); + if (selected) { + // fill body + if (tabOpt->documentMode && isUnified) { + p->save(); + p->setCompositionMode(QPainter::CompositionMode_Source); + p->fillRect(tabRect, QColor(Qt::transparent)); + p->restore(); + } else if (active) { + p->fillRect(bodyRect, tabBarTabBackgroundActiveSelected); + // top line + p->fillRect(topLineRect, tabBarTabLineSelected); + } else { + p->fillRect(bodyRect, tabBarTabBackgroundSelected); + } + } else { + // when the mouse is over non selected tabs they get a new color + const bool hover = (tabOpt->state & QStyle::State_MouseOver); + if (hover) { + // fill body + p->fillRect(bodyRect, tabBarTabBackgroundActiveHovered); + // bottom line + p->fillRect(bottomLineRect, tabBarTabLineActiveHovered); + } + } + + // separator lines between tabs + const QRect leftLineRect(0, 1, 1, height - 2); + const QRect rightLineRect(width - 1, 1, 1, height - 2); + const QColor separatorLineColor = active ? tabBarTabLineActive : tabBarTabLine; + p->fillRect(leftLineRect, separatorLineColor); + p->fillRect(rightLineRect, separatorLineColor); +} + +void drawTabBase(QPainter *p, const QStyleOptionTabBarBase *tbb, const QWidget *w) +{ + QRect r = tbb->rect; + if (isVerticalTabs(tbb->shape)) { + r.setWidth(w->width()); + } else { + r.setHeight(w->height()); + } + const QRect tabRect = rotateTabPainter(p, tbb->shape, r); + const int width = tabRect.width(); + const int height = tabRect.height(); + const bool active = (tbb->state & QStyle::State_Active); + + // fill body + const QRect bodyRect(0, 1, width, height - 1); + const QColor bodyColor = active ? tabBarTabBackgroundActive : tabBarTabBackground; + p->fillRect(bodyRect, bodyColor); + + // top line + const QRect topLineRect(0, 0, width, 1); + const QColor topLineColor = active ? tabBarTabLineActive : tabBarTabLine; + p->fillRect(topLineRect, topLineColor); + + // bottom line + const QRect bottomLineRect(0, height - 1, width, 1); + const QColor bottomLineColor = active ? tabBarTabLineActive : tabBarTabLine; + p->fillRect(bottomLineRect, bottomLineColor); +} +#endif + +static int getControlSize(const QStyleOption *option, const QWidget *widget) +{ + switch (QStyleHelper::widgetSizePolicy(widget, option)) { + case QStyleHelper::SizeSmall: + return QAquaSizeSmall; + case QStyleHelper::SizeMini: + return QAquaSizeMini; + default: + break; + } + return QAquaSizeLarge; +} + + +#ifndef QT_NO_TREEVIEW +static inline bool isTreeView(const QWidget *widget) +{ + return (widget && widget->parentWidget() && + (qobject_cast<const QTreeView *>(widget->parentWidget()) + )); +} +#endif + +#ifndef QT_NO_TABBAR +static inline ThemeTabDirection getTabDirection(QTabBar::Shape shape) +{ + ThemeTabDirection ttd; + switch (shape) { + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + ttd = kThemeTabSouth; + break; + default: // Added to remove the warning, since all values are taken care of, really! + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + ttd = kThemeTabNorth; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + ttd = kThemeTabWest; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + ttd = kThemeTabEast; + break; + } + return ttd; +} +#endif + +static QString qt_mac_removeMnemonics(const QString &original) +{ + QString returnText(original.size(), 0); + int finalDest = 0; + int currPos = 0; + int l = original.length(); + while (l) { + if (original.at(currPos) == QLatin1Char('&') + && (l == 1 || original.at(currPos + 1) != QLatin1Char('&'))) { + ++currPos; + --l; + if (l == 0) + break; + } else if (original.at(currPos) == QLatin1Char('(') && l >= 4 && + original.at(currPos + 1) == QLatin1Char('&') && + original.at(currPos + 2) != QLatin1Char('&') && + original.at(currPos + 3) == QLatin1Char(')')) { + /* remove mnemonics its format is "\s*(&X)" */ + int n = 0; + while (finalDest > n && returnText.at(finalDest - n - 1).isSpace()) + ++n; + finalDest -= n; + currPos += 4; + l -= 4; + continue; + } + returnText[finalDest] = original.at(currPos); + ++currPos; + ++finalDest; + --l; + } + returnText.truncate(finalDest); + return returnText; +} + +OSStatus qt_mac_shape2QRegionHelper(int inMessage, HIShapeRef, const CGRect *inRect, void *inRefcon) +{ + QRegion *region = static_cast<QRegion *>(inRefcon); + if (!region) + return paramErr; + + switch (inMessage) { + case kHIShapeEnumerateRect: + *region += QRect(inRect->origin.x, inRect->origin.y, + inRect->size.width, inRect->size.height); + break; + case kHIShapeEnumerateInit: + // Assume the region is already setup correctly + case kHIShapeEnumerateTerminate: + default: + break; + } + return noErr; +} + +/*! + \internal + Create's a mutable shape, it's the caller's responsibility to release. + WARNING: this function clamps the coordinates to SHRT_MIN/MAX on 10.4 and below. +*/ +HIMutableShapeRef qt_mac_toHIMutableShape(const QRegion ®ion) +{ + HIMutableShapeRef shape = HIShapeCreateMutable(); + if (region.rectCount() < 2 ) { + QRect qtRect = region.boundingRect(); + CGRect cgRect = CGRectMake(qtRect.x(), qtRect.y(), qtRect.width(), qtRect.height()); + HIShapeUnionWithRect(shape, &cgRect); + } else { + for (const QRect &qtRect : region) { + CGRect cgRect = CGRectMake(qtRect.x(), qtRect.y(), qtRect.width(), qtRect.height()); + HIShapeUnionWithRect(shape, &cgRect); + } + } + return shape; +} + +QRegion qt_mac_fromHIShapeRef(HIShapeRef shape) +{ + QRegion returnRegion; + //returnRegion.detach(); + HIShapeEnumerate(shape, kHIShapeParseFromTopLeft, qt_mac_shape2QRegionHelper, &returnRegion); + return returnRegion; +} + +bool qt_macWindowIsTextured(const QWidget *window) +{ + if (QWindow *w = window->windowHandle()) + if (w->handle()) + if (NSWindow *nswindow = static_cast<NSWindow*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow(QByteArrayLiteral("NSWindow"), w))) + return ([nswindow styleMask] & NSTexturedBackgroundWindowMask) ? true : false; + return false; +} + +static bool qt_macWindowMainWindow(const QWidget *window) +{ + if (QWindow *w = window->windowHandle()) { + if (w->handle()) { + if (NSWindow *nswindow = static_cast<NSWindow*>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow(QByteArrayLiteral("nswindow"), w))) { + return [nswindow isMainWindow]; + } + } + } + return false; +} + +/***************************************************************************** + QMacCGStyle globals + *****************************************************************************/ +const int qt_mac_hitheme_version = 0; //the HITheme version we speak +const int macItemFrame = 2; // menu item frame width +const int macItemHMargin = 3; // menu item hor text margin +const int macRightBorder = 12; // right border on mac +const ThemeWindowType QtWinType = kThemeDocumentWindow; // Window type we use for QTitleBar. +QPixmap *qt_mac_backgroundPattern = 0; // stores the standard widget background. + +/***************************************************************************** + QMacCGStyle utility functions + *****************************************************************************/ +static inline int qt_mac_hitheme_tab_version() +{ + return 1; +} + +static inline HIRect qt_hirectForQRect(const QRect &convertRect, const QRect &rect = QRect()) +{ + return CGRectMake(convertRect.x() + rect.x(), convertRect.y() + rect.y(), + convertRect.width() - rect.width(), convertRect.height() - rect.height()); +} + +static inline const QRect qt_qrectForHIRect(const HIRect &hirect) +{ + return QRect(QPoint(int(hirect.origin.x), int(hirect.origin.y)), + QSize(int(hirect.size.width), int(hirect.size.height))); +} + +inline bool qt_mac_is_metal(const QWidget *w) +{ + for (; w; w = w->parentWidget()) { + if (w->testAttribute(Qt::WA_MacBrushedMetal)) + return true; + if (w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) { // If not created will fall through to the opaque check and be fine anyway. + return qt_macWindowIsTextured(w); + } +#ifndef QT_NO_ACCESSIBILITY + if (w->d_func()->isOpaque) + break; +#endif + } + return false; +} + +static int qt_mac_aqua_get_metric(ThemeMetric met) +{ + SInt32 ret; + GetThemeMetric(met, &ret); + return ret; +} + +static QSize qt_aqua_get_known_size(QStyle::ContentsType ct, const QWidget *widg, QSize szHint, + QAquaWidgetSize sz) +{ + QSize ret(-1, -1); + if (sz != QAquaSizeSmall && sz != QAquaSizeLarge && sz != QAquaSizeMini) { + qDebug("Not sure how to return this..."); + return ret; + } + if ((widg && widg->testAttribute(Qt::WA_SetFont)) || !QApplication::desktopSettingsAware()) { + // If you're using a custom font and it's bigger than the default font, + // then no constraints for you. If you are smaller, we can try to help you out + QFont font = qt_app_fonts_hash()->value(widg->metaObject()->className(), QFont()); + if (widg->font().pointSize() > font.pointSize()) + return ret; + } + + if (ct == QStyle::CT_CustomBase && widg) { +#if QT_CONFIG(pushbutton) + if (qobject_cast<const QPushButton *>(widg)) + ct = QStyle::CT_PushButton; +#endif + else if (qobject_cast<const QRadioButton *>(widg)) + ct = QStyle::CT_RadioButton; +#if QT_CONFIG(checkbox) + else if (qobject_cast<const QCheckBox *>(widg)) + ct = QStyle::CT_CheckBox; +#endif +#ifndef QT_NO_COMBOBOX + else if (qobject_cast<const QComboBox *>(widg)) + ct = QStyle::CT_ComboBox; +#endif +#ifndef QT_NO_TOOLBUTTON + else if (qobject_cast<const QToolButton *>(widg)) + ct = QStyle::CT_ToolButton; +#endif + else if (qobject_cast<const QSlider *>(widg)) + ct = QStyle::CT_Slider; +#ifndef QT_NO_PROGRESSBAR + else if (qobject_cast<const QProgressBar *>(widg)) + ct = QStyle::CT_ProgressBar; +#endif +#ifndef QT_NO_LINEEDIT + else if (qobject_cast<const QLineEdit *>(widg)) + ct = QStyle::CT_LineEdit; +#endif + else if (qobject_cast<const QHeaderView *>(widg)) + ct = QStyle::CT_HeaderSection; +#ifndef QT_NO_MENUBAR + else if (qobject_cast<const QMenuBar *>(widg)) + ct = QStyle::CT_MenuBar; +#endif +#ifndef QT_NO_SIZEGRIP + else if (qobject_cast<const QSizeGrip *>(widg)) + ct = QStyle::CT_SizeGrip; +#endif + else + return ret; + } + + switch (ct) { +#if QT_CONFIG(pushbutton) + case QStyle::CT_PushButton: { + const QPushButton *psh = qobject_cast<const QPushButton *>(widg); + // If this comparison is false, then the widget was not a push button. + // This is bad and there's very little we can do since we were requested to find a + // sensible size for a widget that pretends to be a QPushButton but is not. + if(psh) { + QString buttonText = qt_mac_removeMnemonics(psh->text()); + if (buttonText.contains(QLatin1Char('\n'))) + ret = QSize(-1, -1); + else if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPushButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPushButtonHeight)); + + if (!psh->icon().isNull()){ + // If the button got an icon, and the icon is larger than the + // button, we can't decide on a default size + ret.setWidth(-1); + if (ret.height() < psh->iconSize().height()) + ret.setHeight(-1); + } + else if (buttonText == QLatin1String("OK") || buttonText == QLatin1String("Cancel")){ + // Aqua Style guidelines restrict the size of OK and Cancel buttons to 68 pixels. + // However, this doesn't work for German, therefore only do it for English, + // I suppose it would be better to do some sort of lookups for languages + // that like to have really long words. + ret.setWidth(77 - 8); + } + } else { + // The only sensible thing to do is to return whatever the style suggests... + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPushButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPushButtonHeight)); + else + // Since there's no default size we return the large size... + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + } +#endif +#if 0 //Not sure we are applying the rules correctly for RadioButtons/CheckBoxes --Sam + } else if (ct == QStyle::CT_RadioButton) { + QRadioButton *rdo = static_cast<QRadioButton *>(widg); + // Exception for case where multiline radio button text requires no size constrainment + if (rdo->text().find('\n') != -1) + return ret; + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricRadioButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallRadioButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniRadioButtonHeight)); + } else if (ct == QStyle::CT_CheckBox) { + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricCheckBoxHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallCheckBoxHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniCheckBoxHeight)); +#endif + break; + } + case QStyle::CT_SizeGrip: + if (sz == QAquaSizeLarge || sz == QAquaSizeSmall) { + HIRect r; + HIPoint p = { 0, 0 }; + HIThemeGrowBoxDrawInfo gbi; + gbi.version = 0; + gbi.state = kThemeStateActive; + gbi.kind = kHIThemeGrowBoxKindNormal; + gbi.direction = QApplication::isRightToLeft() ? kThemeGrowLeft | kThemeGrowDown + : kThemeGrowRight | kThemeGrowDown; + gbi.size = sz == QAquaSizeSmall ? kHIThemeGrowBoxSizeSmall : kHIThemeGrowBoxSizeNormal; + if (HIThemeGetGrowBoxBounds(&p, &gbi, &r) == noErr) { + int width = 0; +#ifndef QT_NO_MDIAREA + if (widg && qobject_cast<QMdiSubWindow *>(widg->parentWidget())) + width = r.size.width; +#endif + ret = QSize(width, r.size.height); + } + } + break; + case QStyle::CT_ComboBox: + switch (sz) { + case QAquaSizeLarge: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPopupButtonHeight)); + break; + case QAquaSizeSmall: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPopupButtonHeight)); + break; + case QAquaSizeMini: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPopupButtonHeight)); + break; + default: + break; + } + break; + case QStyle::CT_ToolButton: + if (sz == QAquaSizeSmall) { + int width = 0, height = 0; + if (szHint == QSize(-1, -1)) { //just 'guess'.. +#ifndef QT_NO_TOOLBUTTON + const QToolButton *bt = qobject_cast<const QToolButton *>(widg); + // If this conversion fails then the widget was not what it claimed to be. + if(bt) { + if (!bt->icon().isNull()) { + QSize iconSize = bt->iconSize(); + QSize pmSize = bt->icon().actualSize(QSize(32, 32), QIcon::Normal); + width = qMax(width, qMax(iconSize.width(), pmSize.width())); + height = qMax(height, qMax(iconSize.height(), pmSize.height())); + } + if (!bt->text().isNull() && bt->toolButtonStyle() != Qt::ToolButtonIconOnly) { + int text_width = bt->fontMetrics().width(bt->text()), + text_height = bt->fontMetrics().height(); + if (bt->toolButtonStyle() == Qt::ToolButtonTextUnderIcon) { + width = qMax(width, text_width); + height += text_height; + } else { + width += text_width; + width = qMax(height, text_height); + } + } + } else +#endif + { + // Let's return the size hint... + width = szHint.width(); + height = szHint.height(); + } + } else { + width = szHint.width(); + height = szHint.height(); + } + width = qMax(20, width + 5); //border + height = qMax(20, height + 5); //border + ret = QSize(width, height); + } + break; + case QStyle::CT_Slider: { + int w = -1; + const QSlider *sld = qobject_cast<const QSlider *>(widg); + // If this conversion fails then the widget was not what it claimed to be. + if(sld) { + if (sz == QAquaSizeLarge) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricVSliderTickWidth); + } + } else if (sz == QAquaSizeSmall) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricSmallHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricSmallHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricSmallVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricSmallVSliderTickWidth); + } + } else if (sz == QAquaSizeMini) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricMiniHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricMiniHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricMiniVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricMiniVSliderTickWidth); + } + } + } else { + // This is tricky, we were requested to find a size for a slider which is not + // a slider. We don't know if this is vertical or horizontal or if we need to + // have tick marks or not. + // For this case we will return an horizontal slider without tick marks. + w = qt_mac_aqua_get_metric(kThemeMetricHSliderHeight); + w += qt_mac_aqua_get_metric(kThemeMetricHSliderTickHeight); + } + if (sld->orientation() == Qt::Horizontal) + ret.setHeight(w); + else + ret.setWidth(w); + break; + } +#ifndef QT_NO_PROGRESSBAR + case QStyle::CT_ProgressBar: { + int finalValue = -1; + Qt::Orientation orient = Qt::Horizontal; + if (const QProgressBar *pb = qobject_cast<const QProgressBar *>(widg)) + orient = pb->orientation(); + + if (sz == QAquaSizeLarge) + finalValue = qt_mac_aqua_get_metric(kThemeMetricLargeProgressBarThickness) + + qt_mac_aqua_get_metric(kThemeMetricProgressBarShadowOutset); + else + finalValue = qt_mac_aqua_get_metric(kThemeMetricNormalProgressBarThickness) + + qt_mac_aqua_get_metric(kThemeMetricSmallProgressBarShadowOutset); + if (orient == Qt::Horizontal) + ret.setHeight(finalValue); + else + ret.setWidth(finalValue); + break; + } +#endif +#ifndef QT_NO_COMBOBOX + case QStyle::CT_LineEdit: + if (!widg || !qobject_cast<QComboBox *>(widg->parentWidget())) { + //should I take into account the font dimentions of the lineedit? -Sam + if (sz == QAquaSizeLarge) + ret = QSize(-1, 21); + else + ret = QSize(-1, 19); + } + break; +#endif + case QStyle::CT_HeaderSection: +#ifndef QT_NO_TREEVIEW + if (isTreeView(widg)) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricListHeaderHeight)); +#endif + break; + case QStyle::CT_MenuBar: + if (sz == QAquaSizeLarge) { + ret = QSize(-1, [[NSApp mainMenu] menuBarHeight]); + // In the qt_mac_set_native_menubar(false) case, + // we come it here with a zero-height main menu, + // preventing the in-window menu from displaying. + // Use 22 pixels for the height, by observation. + if (ret.height() <= 0) + ret.setHeight(22); + } + break; + default: + break; + } + return ret; +} + + +#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) +static QAquaWidgetSize qt_aqua_guess_size(const QWidget *widg, QSize large, QSize small, QSize mini) +{ + Q_UNUSED(widg); + + if (large == QSize(-1, -1)) { + if (small != QSize(-1, -1)) + return QAquaSizeSmall; + if (mini != QSize(-1, -1)) + return QAquaSizeMini; + return QAquaSizeUnknown; + } else if (small == QSize(-1, -1)) { + if (mini != QSize(-1, -1)) + return QAquaSizeMini; + return QAquaSizeLarge; + } else if (mini == QSize(-1, -1)) { + return QAquaSizeLarge; + } + +#ifndef QT_NO_MAINWINDOW + if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL")) { + //if (small.width() != -1 || small.height() != -1) + return QAquaSizeSmall; + } else if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI")) { + return QAquaSizeMini; + } +#endif + +#if 0 + /* Figure out which size we're closer to, I just hacked this in, I haven't + tested it as it would probably look pretty strange to have some widgets + big and some widgets small in the same window?? -Sam */ + int large_delta=0; + if (large.width() != -1) { + int delta = large.width() - widg->width(); + large_delta += delta * delta; + } + if (large.height() != -1) { + int delta = large.height() - widg->height(); + large_delta += delta * delta; + } + int small_delta=0; + if (small.width() != -1) { + int delta = small.width() - widg->width(); + small_delta += delta * delta; + } + if (small.height() != -1) { + int delta = small.height() - widg->height(); + small_delta += delta * delta; + } + int mini_delta=0; + if (mini.width() != -1) { + int delta = mini.width() - widg->width(); + mini_delta += delta * delta; + } + if (mini.height() != -1) { + int delta = mini.height() - widg->height(); + mini_delta += delta * delta; + } + if (mini_delta < small_delta && mini_delta < large_delta) + return QAquaSizeMini; + else if (small_delta < large_delta) + return QAquaSizeSmall; +#endif + return QAquaSizeLarge; +} +#endif + +void QMacStylePrivate::drawFocusRing(QPainter *p, const QRect &targetRect, int hMargin, int vMargin, qreal radius) const +{ + const qreal pixelRatio = p->device()->devicePixelRatioF(); + static const QString keyFormat = QLatin1String("$qt_focusring%1-%2-%3-%4"); + const QString &key = keyFormat.arg(hMargin).arg(vMargin).arg(radius).arg(pixelRatio); + QPixmap focusRingPixmap; + const qreal size = radius * 2 + 5; + + if (!QPixmapCache::find(key, focusRingPixmap)) { + focusRingPixmap = QPixmap((QSize(size, size) + 2 * QSize(hMargin, vMargin)) * pixelRatio); + focusRingPixmap.fill(Qt::transparent); + focusRingPixmap.setDevicePixelRatio(pixelRatio); + { + const CGFloat focusRingWidth = radius > 0 ? 3.5 : 6; + QMacAutoReleasePool pool; + QMacCGContext ctx(&focusRingPixmap); + CGContextBeginTransparencyLayer(ctx, NULL); + CGContextSetAlpha(ctx, 0.5); // As applied to the stroke color below + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx + flipped:NO]]; + CGRect focusRingRect = CGRectMake(hMargin, vMargin, size, size); + NSBezierPath *focusRingPath; + if (radius > 0) { + const CGFloat roundedRectInset = -1.5; + focusRingPath = [NSBezierPath bezierPathWithRoundedRect:CGRectInset(focusRingRect, roundedRectInset, roundedRectInset) + xRadius:radius + yRadius:radius]; + } else { + const CGFloat outerClipInset = -focusRingWidth / 2; + NSBezierPath *focusRingClipPath = [NSBezierPath bezierPathWithRect:CGRectInset(focusRingRect, outerClipInset, outerClipInset)]; + const CGFloat innerClipInset = 1; + NSBezierPath *focusRingInnerClipPath = [NSBezierPath bezierPathWithRect:CGRectInset(focusRingRect, innerClipInset, innerClipInset)]; + [focusRingClipPath appendBezierPath:focusRingInnerClipPath.bezierPathByReversingPath]; + [focusRingClipPath setClip]; + focusRingPath = [NSBezierPath bezierPathWithRect:focusRingRect]; + focusRingPath.lineJoinStyle = NSRoundLineJoinStyle; + } + + focusRingPath.lineWidth = focusRingWidth; + [[NSColor keyboardFocusIndicatorColor] setStroke]; + [focusRingPath stroke]; + + CGContextEndTransparencyLayer(ctx); + [NSGraphicsContext restoreGraphicsState]; + } + QPixmapCache::insert(key, focusRingPixmap); + } + + // Add 2 for the actual ring tickness going inwards + const qreal hCornerSize = 2 + hMargin + radius; + const qreal vCornerSize = 2 + vMargin + radius; + const qreal shCornerSize = hCornerSize * pixelRatio; + const qreal svCornerSize = vCornerSize * pixelRatio; + // top-left corner + p->drawPixmap(QPointF(targetRect.left(), targetRect.top()), focusRingPixmap, + QRectF(0, 0, shCornerSize, svCornerSize)); + // top-right corner + p->drawPixmap(QPointF(targetRect.right() - hCornerSize + 1, targetRect.top()), focusRingPixmap, + QRectF(focusRingPixmap.width() - shCornerSize, 0, shCornerSize, svCornerSize)); + // bottom-left corner + p->drawPixmap(QPointF(targetRect.left(), targetRect.bottom() - vCornerSize + 1), focusRingPixmap, + QRectF(0, focusRingPixmap.height() - svCornerSize, shCornerSize, svCornerSize)); + // bottom-right corner + p->drawPixmap(QPointF(targetRect.right() - hCornerSize + 1, targetRect.bottom() - vCornerSize + 1), focusRingPixmap, + QRect(focusRingPixmap.width() - shCornerSize, focusRingPixmap.height() - svCornerSize, shCornerSize, svCornerSize)); + // top edge + p->drawPixmap(QRectF(targetRect.left() + hCornerSize, targetRect.top(), targetRect.width() - 2 * hCornerSize, vCornerSize), focusRingPixmap, + QRect(shCornerSize, 0, focusRingPixmap.width() - 2 * shCornerSize, svCornerSize)); + // bottom edge + p->drawPixmap(QRectF(targetRect.left() + hCornerSize, targetRect.bottom() - vCornerSize + 1, targetRect.width() - 2 * hCornerSize, vCornerSize), focusRingPixmap, + QRect(shCornerSize, focusRingPixmap.height() - svCornerSize, focusRingPixmap.width() - 2 * shCornerSize, svCornerSize)); + // left edge + p->drawPixmap(QRectF(targetRect.left(), targetRect.top() + vCornerSize, hCornerSize, targetRect.height() - 2 * vCornerSize), focusRingPixmap, + QRect(0, svCornerSize, shCornerSize, focusRingPixmap.width() - 2 * svCornerSize)); + // right edge + p->drawPixmap(QRectF(targetRect.right() - hCornerSize + 1, targetRect.top() + vCornerSize, hCornerSize, targetRect.height() - 2 * vCornerSize), focusRingPixmap, + QRect(focusRingPixmap.width() - shCornerSize, svCornerSize, shCornerSize, focusRingPixmap.width() - 2 * svCornerSize)); +} + +#ifndef QT_NO_TABBAR +void QMacStylePrivate::tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect) const +{ + Q_ASSERT(textRect); + QRect tr = opt->rect; + const bool verticalTabs = opt->shape == QTabBar::RoundedEast + || opt->shape == QTabBar::RoundedWest + || opt->shape == QTabBar::TriangularEast + || opt->shape == QTabBar::TriangularWest; + if (verticalTabs) + tr.setRect(0, 0, tr.height(), tr.width()); // 0, 0 as we will have a translate transform + + int verticalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget); + int horizontalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget); + const int hpadding = 4; + const int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2; + if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth) + verticalShift = -verticalShift; + tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding); + + // left widget + if (!opt->leftButtonSize.isEmpty()) { + const int buttonSize = verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width(); + tr.setLeft(tr.left() + 4 + buttonSize); + // make text aligned to center + if (opt->rightButtonSize.isEmpty()) + tr.setRight(tr.right() - 4 - buttonSize); + } + // right widget + if (!opt->rightButtonSize.isEmpty()) { + const int buttonSize = verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width(); + tr.setRight(tr.right() - 4 - buttonSize); + // make text aligned to center + if (opt->leftButtonSize.isEmpty()) + tr.setLeft(tr.left() + 4 + buttonSize); + } + + if (!verticalTabs) + tr = proxyStyle->visualRect(opt->direction, opt->rect, tr); + + *textRect = tr; +} +#endif //QT_NO_TABBAR + +QAquaWidgetSize QMacStylePrivate::effectiveAquaSizeConstrain(const QStyleOption *option, + const QWidget *widg, + QStyle::ContentsType ct, + QSize szHint, QSize *insz) const +{ + QAquaWidgetSize sz = aquaSizeConstrain(option, widg, ct, szHint, insz); + if (sz == QAquaSizeUnknown) + return QAquaSizeLarge; + return sz; +} + +QAquaWidgetSize QMacStylePrivate::aquaSizeConstrain(const QStyleOption *option, const QWidget *widg, + QStyle::ContentsType ct, QSize szHint, QSize *insz) const +{ +#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) + if (option) { + if (option->state & QStyle::State_Small) + return QAquaSizeSmall; + if (option->state & QStyle::State_Mini) + return QAquaSizeMini; + } + + if (!widg) { + if (insz) + *insz = QSize(); + if (qEnvironmentVariableIsSet("QWIDGET_ALL_SMALL")) + return QAquaSizeSmall; + if (qEnvironmentVariableIsSet("QWIDGET_ALL_MINI")) + return QAquaSizeMini; + return QAquaSizeUnknown; + } + + QSize large = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeLarge), + small = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeSmall), + mini = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeMini); + bool guess_size = false; + QAquaWidgetSize ret = QAquaSizeUnknown; + QStyleHelper::WidgetSizePolicy wsp = QStyleHelper::widgetSizePolicy(widg); + if (wsp == QStyleHelper::SizeDefault) + guess_size = true; + else if (wsp == QStyleHelper::SizeMini) + ret = QAquaSizeMini; + else if (wsp == QStyleHelper::SizeSmall) + ret = QAquaSizeSmall; + else if (wsp == QStyleHelper::SizeLarge) + ret = QAquaSizeLarge; + if (guess_size) + ret = qt_aqua_guess_size(widg, large, small, mini); + + QSize *sz = 0; + if (ret == QAquaSizeSmall) + sz = &small; + else if (ret == QAquaSizeLarge) + sz = &large; + else if (ret == QAquaSizeMini) + sz = &mini; + if (insz) + *insz = sz ? *sz : QSize(-1, -1); +#ifdef DEBUG_SIZE_CONSTRAINT + if (sz) { + const char *size_desc = "Unknown"; + if (sz == &small) + size_desc = "Small"; + else if (sz == &large) + size_desc = "Large"; + else if (sz == &mini) + size_desc = "Mini"; + qDebug("%s - %s: %s taken (%d, %d) [%d, %d]", + widg ? widg->objectName().toLatin1().constData() : "*Unknown*", + widg ? widg->metaObject()->className() : "*Unknown*", size_desc, widg->width(), widg->height(), + sz->width(), sz->height()); + } +#endif + return ret; +#else + if (insz) + *insz = QSize(); + Q_UNUSED(widg); + Q_UNUSED(ct); + Q_UNUSED(szHint); + return QAquaSizeUnknown; +#endif +} + +/** + Returns the free space awailable for contents inside the + button (and not the size of the contents itself) +*/ +HIRect QMacStylePrivate::pushButtonContentBounds(const QStyleOptionButton *btn, + const HIThemeButtonDrawInfo *bdi) const +{ + HIRect outerBounds = qt_hirectForQRect(btn->rect); + // Adjust the bounds to correct for + // carbon not calculating the content bounds fully correct + if (bdi->kind == kThemePushButton || bdi->kind == kThemePushButtonSmall){ + outerBounds.origin.y += QMacStylePrivate::PushButtonTopOffset; + outerBounds.size.height -= QMacStylePrivate::PushButtonBottomOffset; + } else if (bdi->kind == kThemePushButtonMini) { + outerBounds.origin.y += QMacStylePrivate::PushButtonTopOffset; + } + + HIRect contentBounds; + HIThemeGetButtonContentBounds(&outerBounds, bdi, &contentBounds); + return contentBounds; +} + +/** + Calculates the size of the button contents. + This includes both the text and the icon. +*/ +QSize QMacStylePrivate::pushButtonSizeFromContents(const QStyleOptionButton *btn) const +{ + Q_Q(const QMacStyle); + QSize csz(0, 0); + QSize iconSize = btn->icon.isNull() ? QSize(0, 0) + : (btn->iconSize + QSize(QMacStylePrivate::PushButtonContentPadding, 0)); + QRect textRect = btn->text.isEmpty() ? QRect(0, 0, 1, 1) + : btn->fontMetrics.boundingRect(QRect(), Qt::AlignCenter, btn->text); + csz.setWidth(iconSize.width() + textRect.width() + + ((btn->features & QStyleOptionButton::HasMenu) + ? q->proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, 0) : 0)); + csz.setHeight(qMax(iconSize.height(), textRect.height())); + return csz; +} + +/** + Checks if the actual contents of btn fits inside the free content bounds of + 'buttonKindToCheck'. Meant as a helper function for 'initHIThemePushButton' + for determining which button kind to use for drawing. +*/ +bool QMacStylePrivate::contentFitsInPushButton(const QStyleOptionButton *btn, + HIThemeButtonDrawInfo *bdi, + ThemeButtonKind buttonKindToCheck) const +{ + ThemeButtonKind tmp = bdi->kind; + bdi->kind = buttonKindToCheck; + QSize contentSize = pushButtonSizeFromContents(btn); + QRect freeContentRect = qt_qrectForHIRect(pushButtonContentBounds(btn, bdi)); + bdi->kind = tmp; + return freeContentRect.contains(QRect(freeContentRect.x(), freeContentRect.y(), + contentSize.width(), contentSize.height())); +} + +/** + Creates a HIThemeButtonDrawInfo structure that specifies the correct button + kind and other details to use for drawing the given push button. Which + button kind depends on the size of the button, the size of the contents, + explicit user style settings, etc. +*/ +void QMacStylePrivate::initHIThemePushButton(const QStyleOptionButton *btn, + const QWidget *widget, + const ThemeDrawState tds, + HIThemeButtonDrawInfo *bdi) const +{ + ThemeDrawState tdsModified = tds; + if (btn->state & QStyle::State_On) + tdsModified = kThemeStatePressed; + bdi->version = qt_mac_hitheme_version; + bdi->state = tdsModified; + bdi->value = kThemeButtonOff; + + if (tds == kThemeStateInactive) + bdi->state = kThemeStateActive; + if (btn->state & QStyle::State_HasFocus) + bdi->adornment = kThemeAdornmentFocus; + else + bdi->adornment = kThemeAdornmentNone; + + + if (btn->features & (QStyleOptionButton::Flat)) { + bdi->kind = kThemeBevelButton; + } else { + switch (aquaSizeConstrain(btn, widget)) { + case QAquaSizeSmall: + bdi->kind = kThemePushButtonSmall; + break; + case QAquaSizeMini: + bdi->kind = kThemePushButtonMini; + break; + case QAquaSizeLarge: + // ... We should honor if the user is explicit about using the + // large button. But right now Qt will specify the large button + // as default rather than QAquaSizeUnknown. + // So we treat it like QAquaSizeUnknown + // to get the dynamic choosing of button kind. + case QAquaSizeUnknown: + // Choose the button kind that closest match the button rect, but at the + // same time displays the button contents without clipping. + bdi->kind = kThemeBevelButton; + if (btn->rect.width() >= QMacStylePrivate::BevelButtonW && btn->rect.height() >= QMacStylePrivate::BevelButtonH){ + if (widget && widget->testAttribute(Qt::WA_MacVariableSize)) { + if (btn->rect.height() <= QMacStylePrivate::MiniButtonH){ + if (contentFitsInPushButton(btn, bdi, kThemePushButtonMini)) + bdi->kind = kThemePushButtonMini; + } else if (btn->rect.height() <= QMacStylePrivate::SmallButtonH){ + if (contentFitsInPushButton(btn, bdi, kThemePushButtonSmall)) + bdi->kind = kThemePushButtonSmall; + } else if (contentFitsInPushButton(btn, bdi, kThemePushButton)) { + bdi->kind = kThemePushButton; + } + } else { + bdi->kind = kThemePushButton; + } + } + } + } +} + +#if QT_CONFIG(pushbutton) +bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option) +{ + QMacStyle *macStyle = qobject_cast<QMacStyle *>(pushButton->style()); + if (!macStyle) + return false; + HIThemeButtonDrawInfo bdi; + macStyle->d_func()->initHIThemePushButton(option, pushButton, kThemeStateActive, &bdi); + return bdi.kind == kThemeBevelButton; +} +#endif + +/** + Creates a HIThemeButtonDrawInfo structure that specifies the correct button + kind and other details to use for drawing the given combobox. Which button + kind depends on the size of the combo, wether or not it is editable, + explicit user style settings, etc. +*/ +void QMacStylePrivate::initComboboxBdi(const QStyleOptionComboBox *combo, HIThemeButtonDrawInfo *bdi, + const QWidget *widget, const ThemeDrawState &tds) const +{ + bdi->version = qt_mac_hitheme_version; + bdi->adornment = kThemeAdornmentArrowLeftArrow; + bdi->value = kThemeButtonOff; + if (combo->state & QStyle::State_HasFocus) + bdi->adornment = kThemeAdornmentFocus; + if (combo->activeSubControls & QStyle::SC_ComboBoxArrow) + bdi->state = kThemeStatePressed; + else + bdi->state = tds; + + QAquaWidgetSize aSize = aquaSizeConstrain(combo, widget); + switch (aSize) { + case QAquaSizeMini: + bdi->kind = combo->editable ? ThemeButtonKind(kThemeComboBoxMini) + : ThemeButtonKind(kThemePopupButtonMini); + break; + case QAquaSizeSmall: + bdi->kind = combo->editable ? ThemeButtonKind(kThemeComboBoxSmall) + : ThemeButtonKind(kThemePopupButtonSmall); + break; + case QAquaSizeUnknown: + case QAquaSizeLarge: + // Unless the user explicitly specified large buttons, determine the + // kind by looking at the combox size. + // ... specifying small and mini-buttons it not a current feature of + // Qt (e.g. QWidget::getAttribute(WA_ButtonSize)). But when it is, add + // an extra check here before using the mini and small buttons. + int h = combo->rect.size().height(); + if (combo->editable){ +#ifndef QT_NO_DATETIMEEDIT + if (qobject_cast<const QDateTimeEdit *>(widget)) { + // Except when, you know, we get a QDateTimeEdit with calendarPopup + // enabled. And then things get weird, basically because it's a + // transvestite spinbox with editable combobox tendencies. Meaning + // that it wants to look a combobox, except that it isn't one, so it + // doesn't get all those extra free margins around. (Don't know whose + // idea those margins were, but now it looks like we're stuck with + // them forever). So anyway, the height threshold should be smaller + // in this case, or the style gets confused when it needs to render + // or return any subcontrol size of the poor thing. + if (h < 9) + bdi->kind = kThemeComboBoxMini; + else if (h < 22) + bdi->kind = kThemeComboBoxSmall; + else + bdi->kind = kThemeComboBox; + } else +#endif + { + if (h < 21) + bdi->kind = kThemeComboBoxMini; + else if (h < 26) + bdi->kind = kThemeComboBoxSmall; + else + bdi->kind = kThemeComboBox; + } + } else { + // Even if we specify that we want the kThemePopupButton, Carbon + // will use the kThemePopupButtonSmall if the size matches. So we + // do the same size check explicit to have the size of the inner + // text field be correct. Therefore, do this even if the user specifies + // the use of LargeButtons explicit. + if (h < 21) + bdi->kind = kThemePopupButtonMini; + else if (h < 26) + bdi->kind = kThemePopupButtonSmall; + else + bdi->kind = kThemePopupButton; + } + break; + } +} + +/** + Carbon draws comboboxes (and other views) outside the rect given as argument. Use this function to obtain + the corresponding inner rect for drawing the same combobox so that it stays inside the given outerBounds. +*/ +HIRect QMacStylePrivate::comboboxInnerBounds(const HIRect &outerBounds, int buttonKind) +{ + HIRect innerBounds = outerBounds; + // Carbon draw parts of the view outside the rect. + // So make the rect a bit smaller to compensate + // (I wish HIThemeGetButtonBackgroundBounds worked) + switch (buttonKind){ + case kThemePopupButton: + innerBounds.origin.x += 2; + innerBounds.origin.y += 2; + innerBounds.size.width -= 5; + innerBounds.size.height -= 6; + break; + case kThemePopupButtonSmall: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 6; + innerBounds.size.height -= 7; + break; + case kThemePopupButtonMini: + innerBounds.origin.x += 2; + innerBounds.origin.y += 2; + innerBounds.size.width -= 5; + innerBounds.size.height -= 6; + break; + case kThemeComboBox: + innerBounds.origin.x += 3; + innerBounds.origin.y += 2; + innerBounds.size.width -= 6; + innerBounds.size.height -= 8; + break; + case kThemeComboBoxSmall: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 7; + innerBounds.size.height -= 8; + break; + case kThemeComboBoxMini: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 4; + innerBounds.size.height -= 8; + break; + default: + break; + } + return innerBounds; +} + +/** + Inside a combobox Qt places a line edit widget. The size of this widget should depend on the kind + of combobox we choose to draw. This function calculates and returns this size. +*/ +QRect QMacStylePrivate::comboboxEditBounds(const QRect &outerBounds, const HIThemeButtonDrawInfo &bdi) +{ + QRect ret = outerBounds; + switch (bdi.kind){ + case kThemeComboBox: + ret.adjust(5, 5, -22, -5); + break; + case kThemeComboBoxSmall: + ret.adjust(4, 5, -18, 0); + ret.setHeight(16); + break; + case kThemeComboBoxMini: + ret.adjust(4, 5, -16, 0); + ret.setHeight(13); + break; + case kThemePopupButton: + ret.adjust(10, 2, -23, -4); + break; + case kThemePopupButtonSmall: + ret.adjust(9, 3, -20, -3); + break; + case kThemePopupButtonMini: + ret.adjust(8, 3, -19, 0); + ret.setHeight(13); + break; + } + return ret; +} + +/** + Carbon comboboxes don't scale (sight). If the size of the combo suggest a scaled version, + create it manually by drawing a small Carbon combo onto a pixmap (use pixmap cache), chop + it up, and copy it back onto the widget. Othervise, draw the combobox supplied by Carbon directly. +*/ +void QMacStylePrivate::drawCombobox(const HIRect &outerBounds, const HIThemeButtonDrawInfo &bdi, QPainter *p) +{ + if (!(bdi.kind == kThemeComboBox && outerBounds.size.height > 28)){ + // We have an unscaled combobox, or popup-button; use Carbon directly. + HIRect innerBounds = QMacStylePrivate::comboboxInnerBounds(outerBounds, bdi.kind); + HIThemeDrawButton(&innerBounds, &bdi, QMacCGContext(p), kHIThemeOrientationNormal, 0); + } else { + QPixmap buffer; + QString key = QString(QLatin1String("$qt_cbox%1-%2")).arg(int(bdi.state)).arg(int(bdi.adornment)); + if (!QPixmapCache::find(key, buffer)) { + HIRect innerBoundsSmallCombo = {{3, 3}, {29, 25}}; + buffer = QPixmap(35, 28); + buffer.fill(Qt::transparent); + QPainter buffPainter(&buffer); + HIThemeDrawButton(&innerBoundsSmallCombo, &bdi, QMacCGContext(&buffPainter), kHIThemeOrientationNormal, 0); + buffPainter.end(); + QPixmapCache::insert(key, buffer); + } + + const int bwidth = 20; + const int fwidth = 10; + const int fheight = 10; + int w = qRound(outerBounds.size.width); + int h = qRound(outerBounds.size.height); + int bstart = w - bwidth; + int blower = fheight + 1; + int flower = h - fheight; + int sheight = flower - fheight; + int center = qRound(outerBounds.size.height + outerBounds.origin.y) / 2; + + // Draw upper and lower gap + p->drawPixmap(fwidth, 0, bstart - fwidth, fheight, buffer, fwidth, 0, 1, fheight); + p->drawPixmap(fwidth, flower, bstart - fwidth, fheight, buffer, fwidth, buffer.height() - fheight, 1, fheight); + // Draw left and right gap. Right gap is drawn top and bottom separatly + p->drawPixmap(0, fheight, fwidth, sheight, buffer, 0, fheight, fwidth, 1); + p->drawPixmap(bstart, fheight, bwidth, center - fheight, buffer, buffer.width() - bwidth, fheight - 1, bwidth, 1); + p->drawPixmap(bstart, center, bwidth, sheight / 2, buffer, buffer.width() - bwidth, fheight + 6, bwidth, 1); + // Draw arrow + p->drawPixmap(bstart, center - 4, bwidth - 3, 6, buffer, buffer.width() - bwidth, fheight, bwidth - 3, 6); + // Draw corners + p->drawPixmap(0, 0, fwidth, fheight, buffer, 0, 0, fwidth, fheight); + p->drawPixmap(bstart, 0, bwidth, fheight, buffer, buffer.width() - bwidth, 0, bwidth, fheight); + p->drawPixmap(0, flower, fwidth, fheight, buffer, 0, buffer.height() - fheight, fwidth, fheight); + p->drawPixmap(bstart, h - blower, bwidth, blower, buffer, buffer.width() - bwidth, buffer.height() - blower, bwidth, blower); + } +} + +/** + Carbon tableheaders don't scale (sight). So create it manually by drawing a small Carbon header + onto a pixmap (use pixmap cache), chop it up, and copy it back onto the widget. +*/ +void QMacStylePrivate::drawTableHeader(const HIRect &outerBounds, + bool drawTopBorder, bool drawLeftBorder, const HIThemeButtonDrawInfo &bdi, QPainter *p) +{ + static SInt32 headerHeight = 0; + static OSStatus err = GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight); + Q_UNUSED(err); + + QPixmap buffer; + QString key = QString(QLatin1String("$qt_tableh%1-%2-%3")).arg(int(bdi.state)).arg(int(bdi.adornment)).arg(int(bdi.value)); + if (!QPixmapCache::find(key, buffer)) { + HIRect headerNormalRect = {{0., 0.}, {16., CGFloat(headerHeight)}}; + buffer = QPixmap(headerNormalRect.size.width, headerNormalRect.size.height); + buffer.fill(Qt::transparent); + QPainter buffPainter(&buffer); + HIThemeDrawButton(&headerNormalRect, &bdi, QMacCGContext(&buffPainter), kHIThemeOrientationNormal, 0); + buffPainter.end(); + QPixmapCache::insert(key, buffer); + } + const int buttonw = qRound(outerBounds.size.width); + const int buttonh = qRound(outerBounds.size.height); + const int framew = 1; + const int frameh_n = 4; + const int frameh_s = 3; + const int transh = buffer.height() - frameh_n - frameh_s; + int center = buttonh - frameh_s - int(transh / 2.0f) + 1; // Align bottom; + + int skipTopBorder = 0; + if (!drawTopBorder) + skipTopBorder = 1; + + p->translate(outerBounds.origin.x, outerBounds.origin.y); + + p->drawPixmap(QRect(QRect(0, -skipTopBorder, buttonw - framew , frameh_n)), buffer, QRect(framew, 0, 1, frameh_n)); + p->drawPixmap(QRect(0, buttonh - frameh_s, buttonw - framew, frameh_s), buffer, QRect(framew, buffer.height() - frameh_s, 1, frameh_s)); + // Draw upper and lower center blocks + p->drawPixmap(QRect(0, frameh_n - skipTopBorder, buttonw - framew, center - frameh_n + skipTopBorder), buffer, QRect(framew, frameh_n, 1, 1)); + p->drawPixmap(QRect(0, center, buttonw - framew, buttonh - center - frameh_s), buffer, QRect(framew, buffer.height() - frameh_s, 1, 1)); + // Draw right center block borders + p->drawPixmap(QRect(buttonw - framew, frameh_n - skipTopBorder, framew, center - frameh_n), buffer, QRect(buffer.width() - framew, frameh_n, framew, 1)); + p->drawPixmap(QRect(buttonw - framew, center, framew, buttonh - center - 1), buffer, QRect(buffer.width() - framew, buffer.height() - frameh_s, framew, 1)); + // Draw right corners + p->drawPixmap(QRect(buttonw - framew, -skipTopBorder, framew, frameh_n), buffer, QRect(buffer.width() - framew, 0, framew, frameh_n)); + p->drawPixmap(QRect(buttonw - framew, buttonh - frameh_s, framew, frameh_s), buffer, QRect(buffer.width() - framew, buffer.height() - frameh_s, framew, frameh_s)); + // Draw center transition block + p->drawPixmap(QRect(0, center - qRound(transh / 2.0f), buttonw - framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(framew, frameh_n + 1, 1, transh)); + // Draw right center transition block border + p->drawPixmap(QRect(buttonw - framew, center - qRound(transh / 2.0f), framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(buffer.width() - framew, frameh_n + 1, framew, transh)); + if (drawLeftBorder){ + // Draw left center block borders + p->drawPixmap(QRect(0, frameh_n - skipTopBorder, framew, center - frameh_n + skipTopBorder), buffer, QRect(0, frameh_n, framew, 1)); + p->drawPixmap(QRect(0, center, framew, buttonh - center - 1), buffer, QRect(0, buffer.height() - frameh_s, framew, 1)); + // Draw left corners + p->drawPixmap(QRect(0, -skipTopBorder, framew, frameh_n), buffer, QRect(0, 0, framew, frameh_n)); + p->drawPixmap(QRect(0, buttonh - frameh_s, framew, frameh_s), buffer, QRect(0, buffer.height() - frameh_s, framew, frameh_s)); + // Draw left center transition block border + p->drawPixmap(QRect(0, center - qRound(transh / 2.0f), framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(0, frameh_n + 1, framew, transh)); + } + + p->translate(-outerBounds.origin.x, -outerBounds.origin.y); +} + +/* + Returns cutoff sizes for scroll bars. + thumbIndicatorCutoff is the smallest size where the thumb indicator is drawn. + scrollButtonsCutoff is the smallest size where the up/down buttons is drawn. +*/ +enum ScrollBarCutoffType { thumbIndicatorCutoff = 0, scrollButtonsCutoff = 1 }; +static int scrollButtonsCutoffSize(ScrollBarCutoffType cutoffType, QStyleHelper::WidgetSizePolicy widgetSize) +{ + // Mini scroll bars do not exist as of version 10.4. + if (widgetSize == QStyleHelper::SizeMini) + return 0; + + const int sizeIndex = (widgetSize == QStyleHelper::SizeSmall) ? 1 : 0; + static const int sizeTable[2][2] = { { 61, 56 }, { 49, 44 } }; + return sizeTable[sizeIndex][cutoffType]; +} + +void QMacStylePrivate::getSliderInfo(QStyle::ComplexControl cc, const QStyleOptionSlider *slider, + HIThemeTrackDrawInfo *tdi, const QWidget *needToRemoveMe) const +{ + memset(tdi, 0, sizeof(HIThemeTrackDrawInfo)); // We don't get it all for some reason or another... + tdi->version = qt_mac_hitheme_version; + tdi->reserved = 0; + tdi->filler1 = 0; + bool isScrollbar = (cc == QStyle::CC_ScrollBar); + switch (aquaSizeConstrain(slider, needToRemoveMe)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (isScrollbar) + tdi->kind = kThemeMediumScrollBar; + else + tdi->kind = kThemeMediumSlider; + break; + case QAquaSizeMini: + if (isScrollbar) + tdi->kind = kThemeSmallScrollBar; // should be kThemeMiniScrollBar, but not implemented + else + tdi->kind = kThemeMiniSlider; + break; + case QAquaSizeSmall: + if (isScrollbar) + tdi->kind = kThemeSmallScrollBar; + else + tdi->kind = kThemeSmallSlider; + break; + } + + bool usePlainKnob = slider->tickPosition == QSlider::NoTicks + || slider->tickPosition == QSlider::TicksBothSides; + + tdi->bounds = qt_hirectForQRect(slider->rect); + if (isScrollbar) { + tdi->min = slider->minimum; + tdi->max = slider->maximum; + tdi->value = slider->sliderPosition; + } else { + // Fix min and max positions. HITheme seems confused when it comes to rendering + // a slider at those positions. We give it a hand by extending and offsetting + // the slider range accordingly. See also comment for CC_Slider in drawComplexControl() + tdi->min = 0; + if (slider->orientation == Qt::Horizontal) + tdi->max = 10 * slider->rect.width(); + else + tdi->max = 10 * slider->rect.height(); + + int range = slider->maximum - slider->minimum; + if (range == 0) { + tdi->value = 0; + } else if (usePlainKnob || slider->orientation == Qt::Horizontal) { + int endsCorrection = usePlainKnob ? 25 : 10; + tdi->value = (tdi->max + 2 * endsCorrection) * (slider->sliderPosition - slider->minimum) / range - endsCorrection; + } else { + tdi->value = (tdi->max + 30) * (slider->sliderPosition - slider->minimum) / range - 20; + } + } + tdi->attributes = kThemeTrackShowThumb; + if (slider->upsideDown) + tdi->attributes |= kThemeTrackRightToLeft; + if (slider->orientation == Qt::Horizontal) { + tdi->attributes |= kThemeTrackHorizontal; + if (isScrollbar && slider->direction == Qt::RightToLeft) { + if (!slider->upsideDown) + tdi->attributes |= kThemeTrackRightToLeft; + else + tdi->attributes &= ~kThemeTrackRightToLeft; + } + } + + // Tiger broke reverse scroll bars so put them back and "fake it" + if (isScrollbar && (tdi->attributes & kThemeTrackRightToLeft)) { + tdi->attributes &= ~kThemeTrackRightToLeft; + tdi->value = tdi->max - tdi->value; + } + + tdi->enableState = (slider->state & QStyle::State_Enabled) ? kThemeTrackActive + : kThemeTrackDisabled; + if (!isScrollbar) { + if (slider->state & QStyle::QStyle::State_HasFocus) + tdi->attributes |= kThemeTrackHasFocus; + if (usePlainKnob) + tdi->trackInfo.slider.thumbDir = kThemeThumbPlain; + else if (slider->tickPosition == QSlider::TicksAbove) + tdi->trackInfo.slider.thumbDir = kThemeThumbUpward; + else + tdi->trackInfo.slider.thumbDir = kThemeThumbDownward; + } else { + tdi->trackInfo.scrollbar.viewsize = slider->pageStep; + } +} + +void QMacStylePrivate::setAutoDefaultButton(QObject *button) const +{ + if (autoDefaultButton != button) { + if (QStyleAnimation *anim = animation(autoDefaultButton)) { + anim->updateTarget(); + stopAnimation(autoDefaultButton); + } + autoDefaultButton = button; + } + if (autoDefaultButton && !animation(autoDefaultButton)) + startAnimation(new QStyleAnimation(autoDefaultButton)); +} + +QMacStylePrivate::QMacStylePrivate() + : mouseDown(false), backingStoreNSView(nil) +{ + defaultButtonStart = CFAbsoluteTimeGetCurrent(); + memset(&buttonState, 0, sizeof(ButtonState)); + + if (ptrHIShapeGetBounds == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/Carbon.framework/Carbon")); + library.setLoadHints(QLibrary::ExportExternalSymbolsHint); + ptrHIShapeGetBounds = reinterpret_cast<PtrHIShapeGetBounds>(library.resolve("HIShapeGetBounds")); + } + +} + +QMacStylePrivate::~QMacStylePrivate() +{ + QMacAutoReleasePool pool; + Q_FOREACH (NSView *b, cocoaControls) + [b release]; +} + +ThemeDrawState QMacStylePrivate::getDrawState(QStyle::State flags) +{ + ThemeDrawState tds = kThemeStateActive; + if (flags & QStyle::State_Sunken) { + tds = kThemeStatePressed; + } else if (flags & QStyle::State_Active) { + if (!(flags & QStyle::State_Enabled)) + tds = kThemeStateUnavailable; + } else { + if (flags & QStyle::State_Enabled) + tds = kThemeStateInactive; + else + tds = kThemeStateUnavailableInactive; + } + return tds; +} + +static QCocoaWidget cocoaWidgetFromHIThemeButtonKind(ThemeButtonKind kind) +{ + QCocoaWidget w; + + switch (kind) { + case kThemePopupButton: + case kThemePopupButtonSmall: + case kThemePopupButtonMini: + w.first = QCocoaPopupButton; + break; + case kThemeComboBox: + w.first = QCocoaComboBox; + break; + case kThemeArrowButton: + w.first = QCocoaArrowButton; + break; + case kThemeCheckBox: + case kThemeCheckBoxSmall: + case kThemeCheckBoxMini: + w.first = QCocoaCheckBox; + break; + case kThemeRadioButton: + case kThemeRadioButtonSmall: + case kThemeRadioButtonMini: + w.first = QCocoaRadioButton; + break; + case kThemePushButton: + case kThemePushButtonSmall: + case kThemePushButtonMini: + w.first = QCocoaPushButton; + break; + default: + break; + } + + switch (kind) { + case kThemePushButtonSmall: + case kThemePopupButtonSmall: + case kThemeCheckBoxSmall: + case kThemeRadioButtonSmall: + w.second = QAquaSizeSmall; + break; + case kThemePushButtonMini: + case kThemePopupButtonMini: + case kThemeCheckBoxMini: + case kThemeRadioButtonMini: + w.second = QAquaSizeMini; + break; + default: + w.second = QAquaSizeLarge; + break; + } + + return w; +} + +NSView *QMacStylePrivate::cocoaControl(QCocoaWidget widget) const +{ + NSView *bv = cocoaControls[widget]; + if (!bv) { + + if (widget.first == QCocoaPopupButton + || widget.first == QCocoaPullDownButton) + bv = [[NSPopUpButton alloc] init]; + else if (widget.first == QCocoaComboBox) + bv = [[NSComboBox alloc] init]; + else if (widget.first == QCocoaHorizontalSlider) + bv = [[NSSlider alloc] init]; + else if (widget.first == QCocoaVerticalSlider) + // Cocoa sets the orientation from the view's frame + // at construction time, and it cannot be changed later. + bv = [[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 10, 100)]; + else + bv = [[NSButton alloc] init]; + + switch (widget.first) { + case QCocoaArrowButton: { + NSButton *bc = (NSButton *)bv; + bc.buttonType = NSOnOffButton; + bc.bezelStyle = NSDisclosureBezelStyle; + break; + } + case QCocoaCheckBox: { + NSButton *bc = (NSButton *)bv; + bc.buttonType = NSSwitchButton; + break; + } + case QCocoaRadioButton: { + NSButton *bc = (NSButton *)bv; + bc.buttonType = NSRadioButton; + break; + } + case QCocoaPushButton: { + NSButton *bc = (NSButton *)bv; + bc.buttonType = NSMomentaryLightButton; + bc.bezelStyle = NSRoundedBezelStyle; + break; + } + case QCocoaPullDownButton: { + NSPopUpButton *bc = (NSPopUpButton *)bv; + bc.pullsDown = YES; + break; + } + default: + break; + } + + if ([bv isKindOfClass:[NSButton class]]) { + NSButton *bc = (NSButton *)bv; + bc.title = @""; + } + + if ([bv isKindOfClass:[NSControl class]]) { + NSCell *bcell = [(NSControl *)bv cell]; + switch (widget.second) { + case QAquaSizeSmall: + bcell.controlSize = NSSmallControlSize; + break; + case QAquaSizeMini: + bcell.controlSize = NSMiniControlSize; + break; + default: + break; + } + } + + const_cast<QMacStylePrivate *>(this)->cocoaControls.insert(widget, bv); + } + + return bv; +} + +void QMacStylePrivate::drawNSViewInRect(QCocoaWidget widget, NSView *view, const QRect &qtRect, QPainter *p, bool isQWidget, QCocoaDrawRectBlock drawRectBlock) const +{ + QPoint offset; + if (widget == QCocoaWidget(QCocoaRadioButton, QAquaSizeLarge)) + offset.setY(2); + else if (widget == QCocoaWidget(QCocoaRadioButton, QAquaSizeSmall)) + offset = QPoint(-1, 2); + else if (widget == QCocoaWidget(QCocoaRadioButton, QAquaSizeMini)) + offset.setY(2); + else if (widget == QCocoaWidget(QCocoaPopupButton, QAquaSizeSmall) + || widget == QCocoaWidget(QCocoaCheckBox, QAquaSizeLarge)) + offset.setY(1); + else if (widget == QCocoaWidget(QCocoaCheckBox, QAquaSizeSmall)) + offset.setX(-1); + else if (widget == QCocoaWidget(QCocoaCheckBox, QAquaSizeMini)) + offset = QPoint(7, 5); + else if (widget == QCocoaWidget(QCocoaPopupButton, QAquaSizeMini)) + offset = QPoint(2, -1); + else if (widget == QCocoaWidget(QCocoaPullDownButton, QAquaSizeLarge)) + offset = isQWidget ? QPoint(3, -1) : QPoint(-1, -3); + else if (widget == QCocoaWidget(QCocoaPullDownButton, QAquaSizeSmall)) + offset = QPoint(2, 1); + else if (widget == QCocoaWidget(QCocoaPullDownButton, QAquaSizeMini)) + offset = QPoint(5, 0); + else if (widget == QCocoaWidget(QCocoaComboBox, QAquaSizeLarge)) + offset = QPoint(3, 0); + + QMacCGContext ctx(p); + CGContextSaveGState(ctx); + CGContextTranslateCTM(ctx, offset.x(), offset.y()); + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext + graphicsContextWithGraphicsPort:ctx flipped:YES]]; + + NSRect rect = NSMakeRect(qtRect.x() + 1, qtRect.y(), qtRect.width(), qtRect.height()); + + [backingStoreNSView addSubview:view]; + view.frame = rect; + if (drawRectBlock) + drawRectBlock(rect, (CGContextRef)ctx); + else + [view drawRect:rect]; + [view removeFromSuperviewWithoutNeedingDisplay]; + + [NSGraphicsContext restoreGraphicsState]; + CGContextRestoreGState(ctx); +} + +void QMacStylePrivate::resolveCurrentNSView(QWindow *window) +{ + backingStoreNSView = window ? (NSView *)window->winId() : nil; +} + +void QMacStylePrivate::drawColorlessButton(const HIRect &macRect, HIThemeButtonDrawInfo *bdi, + QPainter *p, const QStyleOption *opt) const +{ + int xoff = 0, + yoff = 0, + extraWidth = 0, + extraHeight = 0, + finalyoff = 0; + + const bool combo = opt->type == QStyleOption::SO_ComboBox; + const bool editableCombo = bdi->kind == kThemeComboBox + || bdi->kind == kThemeComboBoxSmall + || bdi->kind == kThemeComboBoxMini; + const bool button = opt->type == QStyleOption::SO_Button; + const bool viewItem = opt->type == QStyleOption::SO_ViewItem; + const bool pressed = bdi->state == kThemeStatePressed; + + if (button && pressed) { + if (bdi->kind == kThemePushButton) { + extraHeight = 2; + } else if (bdi->kind == kThemePushButtonSmall) { + xoff = 1; + extraWidth = 2; + extraHeight = 5; + } + } + + int devicePixelRatio = p->device()->devicePixelRatioF(); + int width = devicePixelRatio * (int(macRect.size.width) + extraWidth); + int height = devicePixelRatio * (int(macRect.size.height) + extraHeight); + + if (width <= 0 || height <= 0) + return; // nothing to draw + + QString key = QLatin1String("$qt_mac_style_ctb_") + QString::number(bdi->kind) + QLatin1Char('_') + + QString::number(bdi->value) + QLatin1Char('_') + + (button ? QString::number(bdi->state) + QLatin1Char('_') : QString()) + + QLatin1Char('_') + QString::number(width) + QLatin1Char('_') + QString::number(height); + QPixmap pm; + if (!QPixmapCache::find(key, pm)) { + QPixmap activePixmap(width, height); + activePixmap.setDevicePixelRatio(devicePixelRatio); + activePixmap.fill(Qt::transparent); + { + if (combo){ + // Carbon combos don't scale. Therefore we draw it + // ourselves, if a scaled version is needed. + QPainter tmpPainter(&activePixmap); + QMacStylePrivate::drawCombobox(macRect, *bdi, &tmpPainter); + } else { + QMacCGContext cg(&activePixmap); + HIRect newRect = CGRectMake(xoff, yoff, macRect.size.width, macRect.size.height); + if (button && pressed) + bdi->state = kThemeStateActive; + else if (viewItem) + bdi->state = kThemeStateInactive; + HIThemeDrawButton(&newRect, bdi, cg, kHIThemeOrientationNormal, 0); + } + } + + if (!combo && !button && bdi->value == kThemeButtonOff) { + pm = activePixmap; + } else if ((combo && !editableCombo) || button) { + QCocoaWidget cw = cocoaWidgetFromHIThemeButtonKind(bdi->kind); + NSButton *bc = (NSButton *)cocoaControl(cw); + [bc highlight:pressed]; + bc.enabled = bdi->state != kThemeStateUnavailable && bdi->state != kThemeStateUnavailableInactive; + bc.allowsMixedState = YES; + bc.state = bdi->value == kThemeButtonOn ? NSOnState : + bdi->value == kThemeButtonMixed ? NSMixedState : NSOffState; + // The view frame may differ from what we pass to HITheme + QRect rect = opt->rect; + if (bdi->kind == kThemePopupButtonMini) + rect.adjust(0, 0, -5, 0); + drawNSViewInRect(cw, bc, rect, p); + return; + } else if (editableCombo || viewItem) { + QImage image = activePixmap.toImage(); + + for (int y = 0; y < height; ++y) { + QRgb *scanLine = reinterpret_cast<QRgb *>(image.scanLine(y)); + + for (int x = 0; x < width; ++x) { + QRgb &pixel = scanLine[x]; + int gray = qRed(pixel); // We know the image is grayscale + int alpha = qAlpha(pixel); + + if (gray == 128 && alpha == 128) { + pixel = qRgba(255, 255, 255, 255); + } else if (alpha == 0) { + pixel = 0; + } else { + bool belowThreshold = (alpha * gray) / 255 + 255 - alpha < 128; + gray = belowThreshold ? 0 : 2 * gray - 255; + alpha = belowThreshold ? 0 : 2 * alpha - 255; + pixel = qRgba(gray, gray, gray, alpha); + } + } + } + pm = QPixmap::fromImage(image); + } else { + QImage activeImage = activePixmap.toImage(); + QImage colorlessImage; + { + QPixmap colorlessPixmap(width, height); + colorlessPixmap.setDevicePixelRatio(devicePixelRatio); + colorlessPixmap.fill(Qt::transparent); + + QMacCGContext cg(&colorlessPixmap); + HIRect newRect = CGRectMake(xoff, yoff, macRect.size.width, macRect.size.height); + int oldValue = bdi->value; + bdi->value = kThemeButtonOff; + HIThemeDrawButton(&newRect, bdi, cg, kHIThemeOrientationNormal, 0); + bdi->value = oldValue; + colorlessImage = colorlessPixmap.toImage(); + } + + for (int y = 0; y < height; ++y) { + QRgb *colorlessScanLine = reinterpret_cast<QRgb *>(colorlessImage.scanLine(y)); + const QRgb *activeScanLine = reinterpret_cast<const QRgb *>(activeImage.scanLine(y)); + + for (int x = 0; x < width; ++x) { + QRgb &colorlessPixel = colorlessScanLine[x]; + QRgb activePixel = activeScanLine[x]; + + if (activePixel != colorlessPixel) { + int max = qMax(qMax(qRed(activePixel), qGreen(activePixel)), + qBlue(activePixel)); + QRgb newPixel = qRgba(max, max, max, qAlpha(activePixel)); + if (qGray(newPixel) < qGray(colorlessPixel) + || qAlpha(newPixel) > qAlpha(colorlessPixel)) + colorlessPixel = newPixel; + } + } + } + pm = QPixmap::fromImage(colorlessImage); + } + QPixmapCache::insert(key, pm); + } + p->drawPixmap(int(macRect.origin.x) - xoff, int(macRect.origin.y) + finalyoff, width / devicePixelRatio, height / devicePixelRatio , pm); +} + +QMacStyle::QMacStyle() + : QCommonStyle(*new QMacStylePrivate) +{ + Q_D(QMacStyle); + QMacAutoReleasePool pool; + + d->receiver = [[NotificationReceiver alloc] initWithPrivate:d]; + NotificationReceiver *receiver = static_cast<NotificationReceiver *>(d->receiver); + + [[NSNotificationCenter defaultCenter] addObserver:receiver + selector:@selector(scrollBarStyleDidChange:) + name:NSPreferredScrollerStyleDidChangeNotification + object:nil]; + + d->nsscroller = [[NSScroller alloc] init]; + d->indicatorBranchButtonCell = nil; +} + +QMacStyle::~QMacStyle() +{ + Q_D(QMacStyle); + QMacAutoReleasePool pool; + + [reinterpret_cast<NSScroller*>(d->nsscroller) release]; + + NotificationReceiver *receiver = static_cast<NotificationReceiver *>(d->receiver); + [[NSNotificationCenter defaultCenter] removeObserver:receiver]; + [receiver release]; + + delete qt_mac_backgroundPattern; + qt_mac_backgroundPattern = 0; +} + +/*! \internal + Generates the standard widget background pattern. +*/ +QPixmap QMacStylePrivate::generateBackgroundPattern() const +{ + QMacAutoReleasePool pool; + QPixmap px(4, 4); + QMacCGContext cg(&px); + HIThemeSetFill(kThemeBrushDialogBackgroundActive, 0, cg, kHIThemeOrientationNormal); + const CGRect cgRect = CGRectMake(0, 0, px.width(), px.height()); + CGContextFillRect(cg, cgRect); + return px; +} + +/*! \internal + Fills the given \a rect with the pattern stored in \a brush. As an optimization, + HIThemeSetFill us used directly if we are filling with the standard background. +*/ +void qt_mac_fill_background(QPainter *painter, const QRegion &rgn, const QBrush &brush) +{ +#if 0 + QPoint dummy; + const QPaintDevice *target = painter->device(); + const QPaintDevice *redirected = QPainter::redirected(target, &dummy); + //const bool usePainter = redirected && redirected != target; + + if (!usePainter && qt_mac_backgroundPattern + && qt_mac_backgroundPattern->cacheKey() == brush.texture().cacheKey()) { + + painter->setClipRegion(rgn); + + QMacCGContext cg(target); + CGContextSaveGState(cg); + HIThemeSetFill(kThemeBrushDialogBackgroundActive, 0, cg, kHIThemeOrientationInverted); + + for (const QRect &rect : rgn) { + // Anchor the pattern to the top so it stays put when the window is resized. + CGContextSetPatternPhase(cg, CGSizeMake(rect.width(), rect.height())); + CGRect mac_rect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + CGContextFillRect(cg, mac_rect); + } + + CGContextRestoreGState(cg); + } else { +#endif + const QRect rect(rgn.boundingRect()); + painter->setClipRegion(rgn); + painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft()); +// } +} + +void QMacStyle::polish(QPalette &pal) +{ + Q_D(QMacStyle); + if (!qt_mac_backgroundPattern) { + if (!qApp) + return; + qt_mac_backgroundPattern = new QPixmap(d->generateBackgroundPattern()); + } + + + QCFString theme; + const OSErr err = CopyThemeIdentifier(&theme); + if (err == noErr && CFStringCompare(theme, kThemeAppearanceAquaGraphite, 0) == kCFCompareEqualTo) { + pal.setBrush(QPalette::All, QPalette::AlternateBase, QColor(240, 240, 240)); + } else { + pal.setBrush(QPalette::All, QPalette::AlternateBase, QColor(237, 243, 254)); + } +} + +void QMacStyle::polish(QApplication *) +{ +} + +void QMacStyle::unpolish(QApplication *) +{ +} + +void QMacStyle::polish(QWidget* w) +{ + if (qt_mac_is_metal(w) && !w->testAttribute(Qt::WA_SetPalette)) { + // Set a clear brush so that the metal shines through. + QPalette pal = w->palette(); + QBrush background(Qt::transparent); + pal.setBrush(QPalette::All, QPalette::Window, background); + pal.setBrush(QPalette::All, QPalette::Button, background); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + } + +#ifndef QT_NO_MENU + if (qobject_cast<QMenu*>(w) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBoxPrivateContainer *>(w) +#endif + ) { + w->setWindowOpacity(0.985); + if (!w->testAttribute(Qt::WA_SetPalette)) { + QPixmap px(64, 64); + px.fill(Qt::white); + HIThemeMenuDrawInfo mtinfo; + mtinfo.version = qt_mac_hitheme_version; + mtinfo.menuType = kThemeMenuTypePopUp; + // HIRect rect = CGRectMake(0, 0, px.width(), px.height()); + // ### + //HIThemeDrawMenuBackground(&rect, &mtinfo, QMacCGContext(&px)), + // kHIThemeOrientationNormal); + QPalette pal = w->palette(); + QBrush background(px); + pal.setBrush(QPalette::All, QPalette::Window, background); + pal.setBrush(QPalette::All, QPalette::Button, background); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + } + } +#endif + +#ifndef QT_NO_TABBAR + if (QTabBar *tb = qobject_cast<QTabBar*>(w)) { + if (tb->documentMode()) { + w->setAttribute(Qt::WA_Hover); + w->setFont(qt_app_fonts_hash()->value("QSmallFont", QFont())); + QPalette p = w->palette(); + p.setColor(QPalette::WindowText, QColor(17, 17, 17)); + w->setPalette(p); + } + } +#endif + + QCommonStyle::polish(w); + + if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) { + rubber->setWindowOpacity(0.25); + rubber->setAttribute(Qt::WA_PaintOnScreen, false); + rubber->setAttribute(Qt::WA_NoSystemBackground, false); + } + + if (qobject_cast<QScrollBar*>(w)) { + w->setAttribute(Qt::WA_OpaquePaintEvent, false); + w->setAttribute(Qt::WA_Hover, true); + w->setMouseTracking(true); + } +} + +void QMacStyle::unpolish(QWidget* w) +{ + if (( +#ifndef QT_NO_MENU + qobject_cast<QMenu*>(w) || +#endif + qt_mac_is_metal(w) + ) && !w->testAttribute(Qt::WA_SetPalette)) { + QPalette pal = qApp->palette(w); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + w->setWindowOpacity(1.0); + } + +#ifndef QT_NO_COMBOBOX + if (QComboBox *combo = qobject_cast<QComboBox *>(w)) { + if (!combo->isEditable()) { + if (QWidget *widget = combo->findChild<QComboBoxPrivateContainer *>()) + widget->setWindowOpacity(1.0); + } + } +#endif + + if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) { + rubber->setWindowOpacity(1.0); + rubber->setAttribute(Qt::WA_PaintOnScreen, true); + rubber->setAttribute(Qt::WA_NoSystemBackground, true); + } + + if (QFocusFrame *frame = qobject_cast<QFocusFrame *>(w)) + frame->setAttribute(Qt::WA_NoSystemBackground, true); + + QCommonStyle::unpolish(w); + + if (qobject_cast<QScrollBar*>(w)) { + w->setAttribute(Qt::WA_OpaquePaintEvent, true); + w->setAttribute(Qt::WA_Hover, false); + w->setMouseTracking(false); + } +} + +int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const +{ + Q_D(const QMacStyle); + int controlSize = getControlSize(opt, widget); + SInt32 ret = 0; + + switch (metric) { + case PM_TabCloseIndicatorWidth: + case PM_TabCloseIndicatorHeight: + ret = closeButtonSize; + break; + case PM_ToolBarIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize); + break; + case PM_FocusFrameVMargin: + case PM_FocusFrameHMargin: + GetThemeMetric(kThemeMetricFocusRectOutset, &ret); + break; + case PM_DialogButtonsSeparator: + ret = -5; + break; + case PM_DialogButtonsButtonHeight: { + QSize sz; + ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); + if (sz == QSize(-1, -1)) + ret = 32; + else + ret = sz.height(); + break; } + case PM_DialogButtonsButtonWidth: { + QSize sz; + ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); + if (sz == QSize(-1, -1)) + ret = 70; + else + ret = sz.width(); + break; } + + case PM_MenuBarHMargin: + ret = 8; + break; + + case PM_MenuBarVMargin: + ret = 0; + break; + + case PM_MenuBarPanelWidth: + ret = 0; + break; + + case QStyle::PM_MenuDesktopFrameWidth: + ret = 5; + break; + + case PM_CheckBoxLabelSpacing: + case PM_RadioButtonLabelSpacing: + ret = 2; + break; + case PM_MenuScrollerHeight: +#if 0 + SInt16 ash, asw; + GetThemeMenuItemExtra(kThemeMenuItemScrollUpArrow, &ash, &asw); + ret = ash; +#else + ret = 15; // I hate having magic numbers in here... +#endif + break; + case PM_DefaultFrameWidth: +#ifndef QT_NO_MAINWINDOW + if (widget && (widget->isWindow() || !widget->parentWidget() + || (qobject_cast<const QMainWindow*>(widget->parentWidget()) + && static_cast<QMainWindow *>(widget->parentWidget())->centralWidget() == widget)) + && qobject_cast<const QAbstractScrollArea *>(widget)) + ret = 0; + else +#endif + // The combo box popup has no frame. + if (qstyleoption_cast<const QStyleOptionComboBox *>(opt) != 0) + ret = 0; + else + ret = 1; + break; + case PM_MaximumDragDistance: + ret = -1; + break; + case PM_ScrollBarSliderMin: + ret = 24; + break; + case PM_SpinBoxFrameWidth: + GetThemeMetric(kThemeMetricEditTextFrameOutset, &ret); + switch (d->aquaSizeConstrain(opt, widget)) { + default: + ret += 2; + break; + case QAquaSizeMini: + ret += 1; + break; + } + break; + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 0; + break; + case PM_SliderLength: + ret = 17; + break; + // Returns the number of pixels to use for the business part of the + // slider (i.e., the non-tickmark portion). The remaining space is shared + // equally between the tickmark regions. + case PM_SliderControlThickness: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); + int ticks = sl->tickPosition; + int n = 0; + if (ticks & QSlider::TicksAbove) + ++n; + if (ticks & QSlider::TicksBelow) + ++n; + if (!n) { + ret = space; + break; + } + + int thick = 6; // Magic constant to get 5 + 16 + 5 + if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks) + thick += proxy()->pixelMetric(PM_SliderLength, sl, widget) / 4; + + space -= thick; + if (space > 0) + thick += (space * 2) / (n + 2); + ret = thick; + } else { + ret = 0; + } + break; + case PM_SmallIconSize: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + + case PM_LargeIconSize: + ret = int(QStyleHelper::dpiScaled(32.)); + break; + + case PM_IconViewIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget); + break; + + case PM_ButtonDefaultIndicator: + ret = 0; + break; + case PM_TitleBarHeight: + // Always use NSTitledWindowMask since we never need any other type of window here + ret = int([NSWindow frameRectForContentRect:NSZeroRect + styleMask:NSTitledWindowMask].size.height); + break; + case QStyle::PM_TabBarTabHSpace: + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeLarge: + ret = QCommonStyle::pixelMetric(metric, opt, widget); + break; + case QAquaSizeSmall: + ret = 20; + break; + case QAquaSizeMini: + ret = 16; + break; + case QAquaSizeUnknown: + const QStyleOptionTab *tb = qstyleoption_cast<const QStyleOptionTab *>(opt); + if (tb && tb->documentMode) + ret = 30; + else + ret = QCommonStyle::pixelMetric(metric, opt, widget); + break; + } + break; + case PM_TabBarTabVSpace: + ret = 4; + break; + case PM_TabBarTabShiftHorizontal: + case PM_TabBarTabShiftVertical: + ret = 0; + break; + case PM_TabBarBaseHeight: + ret = 21; + break; + case PM_TabBarTabOverlap: + ret = 1; + break; + case PM_TabBarBaseOverlap: + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + ret = 11; + break; + case QAquaSizeSmall: + ret = 8; + break; + case QAquaSizeMini: + ret = 7; + break; + } + break; + case PM_ScrollBarExtent: { + const QAquaWidgetSize size = d->effectiveAquaSizeConstrain(opt, widget); + ret = static_cast<SInt32>([NSScroller + scrollerWidthForControlSize:static_cast<NSControlSize>(size) + scrollerStyle:[NSScroller preferredScrollerStyle]]); + break; } + case PM_IndicatorHeight: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricCheckBoxHeight, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniCheckBoxHeight, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallCheckBoxHeight, &ret); + break; + } + break; } + case PM_IndicatorWidth: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricCheckBoxWidth, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniCheckBoxWidth, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallCheckBoxWidth, &ret); + break; + } + ++ret; + break; } + case PM_ExclusiveIndicatorHeight: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricRadioButtonHeight, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniRadioButtonHeight, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallRadioButtonHeight, &ret); + break; + } + break; } + case PM_ExclusiveIndicatorWidth: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricRadioButtonWidth, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniRadioButtonWidth, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallRadioButtonWidth, &ret); + break; + } + ++ret; + break; } + case PM_MenuVMargin: + ret = 4; + break; + case PM_MenuPanelWidth: + ret = 0; + break; + case PM_ToolTipLabelFrameWidth: + ret = 0; + break; + case PM_SizeGripSize: { + QAquaWidgetSize aSize; + if (widget && widget->window()->windowType() == Qt::Tool) + aSize = QAquaSizeSmall; + else + aSize = QAquaSizeLarge; + const QSize size = qt_aqua_get_known_size(CT_SizeGrip, widget, QSize(), aSize); + ret = size.width(); + break; } + case PM_MdiSubWindowFrameWidth: + ret = 1; + break; + case PM_DockWidgetFrameWidth: + ret = 0; + break; + case PM_DockWidgetTitleMargin: + ret = 0; + break; + case PM_DockWidgetSeparatorExtent: + ret = 1; + break; + case PM_ToolBarHandleExtent: + ret = 11; + break; + case PM_ToolBarItemMargin: + ret = 0; + break; + case PM_ToolBarItemSpacing: + ret = 4; + break; + case PM_SplitterWidth: + ret = qMax(7, QApplication::globalStrut().width()); + break; + case PM_LayoutLeftMargin: + case PM_LayoutTopMargin: + case PM_LayoutRightMargin: + case PM_LayoutBottomMargin: + { + bool isWindow = false; + if (opt) { + isWindow = (opt->state & State_Window); + } else if (widget) { + isWindow = widget->isWindow(); + } + + if (isWindow) { + bool isMetal = widget && widget->testAttribute(Qt::WA_MacBrushedMetal); + if (isMetal) { + if (metric == PM_LayoutTopMargin) { + return_SIZE(9 /* AHIG */, 6 /* guess */, 6 /* guess */); + } else if (metric == PM_LayoutBottomMargin) { + return_SIZE(18 /* AHIG */, 15 /* guess */, 13 /* guess */); + } else { + return_SIZE(14 /* AHIG */, 11 /* guess */, 9 /* guess */); + } + } else { + /* + AHIG would have (20, 8, 10) here but that makes + no sense. It would also have 14 for the top margin + but this contradicts both Builder and most + applications. + */ + return_SIZE(20, 10, 10); // AHIG + } + } else { + // hack to detect QTabWidget + if (widget && widget->parentWidget() + && widget->parentWidget()->sizePolicy().controlType() == QSizePolicy::TabWidget) { + if (metric == PM_LayoutTopMargin) { + /* + Builder would have 14 (= 20 - 6) instead of 12, + but that makes the tab look disproportionate. + */ + return_SIZE(12, 6, 6); // guess + } else { + return_SIZE(20 /* Builder */, 8 /* guess */, 8 /* guess */); + } + } else { + /* + Child margins are highly inconsistent in AHIG and Builder. + */ + return_SIZE(12, 8, 6); // guess + } + } + } + case PM_LayoutHorizontalSpacing: + case PM_LayoutVerticalSpacing: + return -1; + case PM_MenuHMargin: + ret = 0; + break; + case PM_ToolBarExtensionExtent: + ret = 21; + break; + case PM_ToolBarFrameWidth: + ret = 1; + break; + case PM_ScrollView_ScrollBarOverlap: + ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay ? + pixelMetric(PM_ScrollBarExtent, opt, widget) : 0; + break; + default: + ret = QCommonStyle::pixelMetric(metric, opt, widget); + break; + } + return ret; +} + +QPalette QMacStyle::standardPalette() const +{ + QPalette pal = QCommonStyle::standardPalette(); + pal.setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); + pal.setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); + pal.setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); + return pal; +} + +int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, + QStyleHintReturn *hret) const +{ + QMacAutoReleasePool pool; + + SInt32 ret = 0; + switch (sh) { + case SH_Slider_SnapToValue: + case SH_PrintDialog_RightAlignButtons: + case SH_FontDialog_SelectAssociatedText: + case SH_MenuBar_MouseTracking: + case SH_Menu_MouseTracking: + case SH_ComboBox_ListMouseTracking: + case SH_MainWindow_SpaceBelowMenuBar: + case SH_ItemView_ChangeHighlightOnFocus: + ret = 1; + break; + case SH_ToolBox_SelectedPageTitleBold: + ret = 0; + break; + case SH_DialogButtonBox_ButtonsHaveIcons: + ret = 0; + break; + case SH_Menu_SelectionWrap: + ret = false; + break; + case SH_Menu_KeyboardSearch: + ret = true; + break; + case SH_Menu_SpaceActivatesItem: + ret = true; + break; + case SH_Slider_AbsoluteSetButtons: + ret = Qt::LeftButton|Qt::MidButton; + break; + case SH_Slider_PageSetButtons: + ret = 0; + break; + case SH_ScrollBar_ContextMenu: + ret = false; + break; + case SH_TitleBar_AutoRaise: + ret = true; + break; + case SH_Menu_AllowActiveAndDisabled: + ret = false; + break; + case SH_Menu_SubMenuPopupDelay: + ret = 100; + break; + case SH_Menu_SubMenuUniDirection: + ret = true; + break; + case SH_Menu_SubMenuSloppySelectOtherActions: + ret = false; + break; + case SH_Menu_SubMenuResetWhenReenteringParent: + ret = true; + break; + case SH_Menu_SubMenuDontStartSloppyOnLeave: + ret = true; + break; + + case SH_ScrollBar_LeftClickAbsolutePosition: { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + bool result = [defaults boolForKey:@"AppleScrollerPagingBehavior"]; + if(QApplication::keyboardModifiers() & Qt::AltModifier) + ret = !result; + else + ret = result; + break; } + case SH_TabBar_PreferNoArrows: + ret = true; + break; + /* + case SH_DialogButtons_DefaultButton: + ret = QDialogButtons::Reject; + break; + */ + case SH_GroupBox_TextLabelVerticalAlignment: + ret = Qt::AlignTop; + break; + case SH_ScrollView_FrameOnlyAroundContents: + ret = QCommonStyle::styleHint(sh, opt, w, hret); + break; + case SH_Menu_FillScreenWithScroll: + ret = false; + break; + case SH_Menu_Scrollable: + ret = true; + break; + case SH_RichText_FullWidthSelection: + ret = true; + break; + case SH_BlinkCursorWhenTextSelected: + ret = false; + break; + case SH_ScrollBar_StopMouseOverSlider: + ret = true; + break; + case SH_ListViewExpand_SelectMouseType: + ret = QEvent::MouseButtonRelease; + break; + case SH_TabBar_SelectMouseType: +#ifndef QT_NO_TABBAR + if (const QStyleOptionTabBarBase *opt2 = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) { + ret = opt2->documentMode ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; + } else +#endif + { + ret = QEvent::MouseButtonRelease; + } + break; + case SH_ComboBox_Popup: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) + ret = !cmb->editable; + else + ret = 0; + break; + case SH_Workspace_FillSpaceOnMaximize: + ret = true; + break; + case SH_Widget_ShareActivation: + ret = true; + break; + case SH_Header_ArrowAlignment: + ret = Qt::AlignRight; + break; + case SH_TabBar_Alignment: { +#ifndef QT_NO_TABWIDGET + if (const QTabWidget *tab = qobject_cast<const QTabWidget*>(w)) { + if (tab->documentMode()) { + ret = Qt::AlignLeft; + break; + } + } +#endif +#ifndef QT_NO_TABBAR + if (const QTabBar *tab = qobject_cast<const QTabBar*>(w)) { + if (tab->documentMode()) { + ret = Qt::AlignLeft; + break; + } + } +#endif + ret = Qt::AlignCenter; + } break; + case SH_UnderlineShortcut: + ret = false; + break; + case SH_ToolTipLabel_Opacity: + ret = 242; // About 95% + break; + case SH_Button_FocusPolicy: + ret = Qt::TabFocus; + break; + case SH_EtchDisabledText: + ret = false; + break; + case SH_FocusFrame_Mask: { + ret = true; + if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + const uchar fillR = 192, fillG = 191, fillB = 190; + QImage img; + + QSize pixmapSize = opt->rect.size(); + if (!pixmapSize.isEmpty()) { + QPixmap pix(pixmapSize); + pix.fill(QColor(fillR, fillG, fillB)); + QPainter pix_paint(&pix); + proxy()->drawControl(CE_FocusFrame, opt, &pix_paint, w); + pix_paint.end(); + img = pix.toImage(); + } + + const QRgb *sptr = (QRgb*)img.bits(), *srow; + const int sbpl = img.bytesPerLine(); + const int w = sbpl/4, h = img.height(); + + QImage img_mask(img.width(), img.height(), QImage::Format_ARGB32); + QRgb *dptr = (QRgb*)img_mask.bits(), *drow; + const int dbpl = img_mask.bytesPerLine(); + + for (int y = 0; y < h; ++y) { + srow = sptr+((y*sbpl)/4); + drow = dptr+((y*dbpl)/4); + for (int x = 0; x < w; ++x) { + const int redDiff = qRed(*srow) - fillR; + const int greenDiff = qGreen(*srow) - fillG; + const int blueDiff = qBlue(*srow) - fillB; + const int diff = (redDiff * redDiff) + (greenDiff * greenDiff) + (blueDiff * blueDiff); + (*drow++) = (diff < 10) ? 0xffffffff : 0xff000000; + ++srow; + } + } + QBitmap qmask = QBitmap::fromImage(img_mask); + mask->region = QRegion(qmask); + } + break; } + case SH_TitleBar_NoBorder: + ret = 1; + break; + case SH_RubberBand_Mask: + ret = 0; + break; + case SH_ComboBox_LayoutDirection: + ret = Qt::LeftToRight; + break; + case SH_ItemView_EllipsisLocation: + ret = Qt::AlignHCenter; + break; + case SH_ItemView_ShowDecorationSelected: + ret = true; + break; + case SH_TitleBar_ModifyNotification: + ret = false; + break; + case SH_ScrollBar_RollBetweenButtons: + ret = true; + break; + case SH_WindowFrame_Mask: + ret = 1; + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(hret)) { + mask->region = opt->rect; + mask->region -= QRect(opt->rect.left(), opt->rect.top(), 5, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 1, 3, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 2, 2, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 3, 1, 2); + + mask->region -= QRect(opt->rect.right() - 4, opt->rect.top(), 5, 1); + mask->region -= QRect(opt->rect.right() - 2, opt->rect.top() + 1, 3, 1); + mask->region -= QRect(opt->rect.right() - 1, opt->rect.top() + 2, 2, 1); + mask->region -= QRect(opt->rect.right() , opt->rect.top() + 3, 1, 2); + } + break; + case SH_TabBar_ElideMode: + ret = Qt::ElideRight; + break; +#if QT_CONFIG(dialogbuttonbox) + case SH_DialogButtonLayout: + ret = QDialogButtonBox::MacLayout; + break; +#endif + case SH_FormLayoutWrapPolicy: + ret = QFormLayout::DontWrapRows; + break; + case SH_FormLayoutFieldGrowthPolicy: + ret = QFormLayout::FieldsStayAtSizeHint; + break; + case SH_FormLayoutFormAlignment: + ret = Qt::AlignHCenter | Qt::AlignTop; + break; + case SH_FormLayoutLabelAlignment: + ret = Qt::AlignRight; + break; + case SH_ComboBox_PopupFrameStyle: + ret = QFrame::NoFrame | QFrame::Plain; + break; + case SH_MessageBox_TextInteractionFlags: + ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard; + break; + case SH_SpellCheckUnderlineStyle: + ret = QTextCharFormat::DashUnderline; + break; + case SH_MessageBox_CenterButtons: + ret = false; + break; + case SH_MenuBar_AltKeyNavigation: + ret = false; + break; + case SH_ItemView_MovementWithoutUpdatingSelection: + ret = false; + break; + case SH_FocusFrame_AboveWidget: + ret = true; + break; +#ifndef QT_NO_WIZARD + case SH_WizardStyle: + ret = QWizard::MacStyle; + break; +#endif + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = false; + break; + case SH_Menu_FlashTriggeredItem: + ret = true; + break; + case SH_Menu_FadeOutOnHide: + ret = true; + break; + case SH_Menu_Mask: + if (opt) { + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + ret = true; + HIRect menuRect = CGRectMake(opt->rect.x(), opt->rect.y() + 4, + opt->rect.width(), opt->rect.height() - 8); + HIThemeMenuDrawInfo mdi; + mdi.version = 0; +#ifndef QT_NO_MENU + if (w && qobject_cast<QMenu *>(w->parentWidget())) + mdi.menuType = kThemeMenuTypeHierarchical; + else +#endif + mdi.menuType = kThemeMenuTypePopUp; + QCFType<HIShapeRef> shape; + HIThemeGetMenuBackgroundShape(&menuRect, &mdi, &shape); + + mask->region = qt_mac_fromHIShapeRef(shape); + } + } + break; + case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: + ret = true; + break; +#ifndef QT_NO_TABBAR + case SH_TabBar_CloseButtonPosition: + ret = QTabBar::LeftSide; + break; +#endif + case SH_DockWidget_ButtonsHaveFrame: + ret = false; + break; + case SH_ScrollBar_Transient: + if ((qobject_cast<const QScrollBar *>(w) && w->parent() && + qobject_cast<QAbstractScrollArea*>(w->parent()->parent())) +#ifndef QT_NO_ACCESSIBILITY + || (opt && QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ScrollBar)) +#endif + ) { + ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay; + } + break; + case SH_ItemView_ScrollMode: + ret = QAbstractItemView::ScrollPerPixel; + break; + case SH_TitleBar_ShowToolTipsOnButtons: + // min/max/close buttons on windows don't show tool tips + ret = false; + break; + default: + ret = QCommonStyle::styleHint(sh, opt, w, hret); + break; + } + return ret; +} + +QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + switch (iconMode) { + case QIcon::Disabled: { + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int imgh = img.height(); + int imgw = img.width(); + QRgb pixel; + for (int y = 0; y < imgh; ++y) { + for (int x = 0; x < imgw; ++x) { + pixel = img.pixel(x, y); + img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), + qAlpha(pixel) / 2)); + } + } + return QPixmap::fromImage(img); + } + default: + ; + } + return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + + +QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ + // The default implementation of QStyle::standardIconImplementation() is to call standardPixmap() + // I don't want infinite recursion so if we do get in that situation, just return the Window's + // standard pixmap instead (since there is no mac-specific icon then). This should be fine until + // someone changes how Windows standard + // pixmap works. + static bool recursionGuard = false; + + if (recursionGuard) + return QCommonStyle::standardPixmap(standardPixmap, opt, widget); + + recursionGuard = true; + QIcon icon = proxy()->standardIcon(standardPixmap, opt, widget); + recursionGuard = false; + int size; + switch (standardPixmap) { + default: + size = 32; + break; + case SP_MessageBoxCritical: + case SP_MessageBoxQuestion: + case SP_MessageBoxInformation: + case SP_MessageBoxWarning: + size = 64; + break; + } + return icon.pixmap(qt_getWindow(widget), QSize(size, size)); +} + +void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + Q_D(const QMacStyle); + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + QWindow *window = w && w->window() ? w->window()->windowHandle() : + QStyleHelper::styleObjectWindow(opt->styleObject); + const_cast<QMacStylePrivate *>(d)->resolveCurrentNSView(window); + switch (pe) { + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: { + p->save(); + p->setRenderHint(QPainter::Antialiasing); + int xOffset = opt->direction == Qt::LeftToRight ? 2 : -1; + QMatrix matrix; + matrix.translate(opt->rect.center().x() + xOffset, opt->rect.center().y() + 2); + QPainterPath path; + switch(pe) { + default: + case PE_IndicatorArrowDown: + break; + case PE_IndicatorArrowUp: + matrix.rotate(180); + break; + case PE_IndicatorArrowLeft: + matrix.rotate(90); + break; + case PE_IndicatorArrowRight: + matrix.rotate(-90); + break; + } + path.moveTo(0, 5); + path.lineTo(-4, -3); + path.lineTo(4, -3); + p->setMatrix(matrix); + p->setPen(Qt::NoPen); + p->setBrush(QColor(0, 0, 0, 135)); + p->drawPath(path); + p->restore(); + break; } +#ifndef QT_NO_TABBAR + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb + = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) { + if (tbb->documentMode) { + p->save(); + drawTabBase(p, tbb, w); + p->restore(); + return; + } + + QRegion region(tbb->rect); + region -= tbb->tabBarRect; + p->save(); + p->setClipRegion(region); + QStyleOptionTabWidgetFrame twf; + twf.QStyleOption::operator=(*tbb); + twf.shape = tbb->shape; + switch (getTabDirection(twf.shape)) { + case kThemeTabNorth: + twf.rect = twf.rect.adjusted(0, 0, 0, 10); + break; + case kThemeTabSouth: + twf.rect = twf.rect.adjusted(0, -10, 0, 0); + break; + case kThemeTabWest: + twf.rect = twf.rect.adjusted(0, 0, 10, 0); + break; + case kThemeTabEast: + twf.rect = twf.rect.adjusted(0, -10, 0, 0); + break; + } + proxy()->drawPrimitive(PE_FrameTabWidget, &twf, p, w); + p->restore(); + } + break; +#endif + case PE_PanelTipLabel: + p->fillRect(opt->rect, opt->palette.brush(QPalette::ToolTipBase)); + break; + case PE_FrameGroupBox: + if (const QStyleOptionFrame *groupBox = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (groupBox->features & QStyleOptionFrame::Flat) { + QCommonStyle::drawPrimitive(pe, groupBox, p, w); + } else { + HIThemeGroupBoxDrawInfo gdi; + gdi.version = qt_mac_hitheme_version; + gdi.state = tds; +#ifndef QT_NO_GROUPBOX + if (w && qobject_cast<QGroupBox *>(w->parentWidget())) + gdi.kind = kHIThemeGroupBoxKindSecondary; + else +#endif + gdi.kind = kHIThemeGroupBoxKindPrimary; + HIRect hirect = qt_hirectForQRect(opt->rect); + HIThemeDrawGroupBox(&hirect, &gdi, cg, kHIThemeOrientationNormal); + } + } + break; + case PE_IndicatorToolBarSeparator: { + QPainterPath path; + if (opt->state & State_Horizontal) { + int xpoint = opt->rect.center().x(); + path.moveTo(xpoint + 0.5, opt->rect.top() + 1); + path.lineTo(xpoint + 0.5, opt->rect.bottom()); + } else { + int ypoint = opt->rect.center().y(); + path.moveTo(opt->rect.left() + 2 , ypoint + 0.5); + path.lineTo(opt->rect.right() + 1, ypoint + 0.5); + } + QPainterPathStroker theStroker; + theStroker.setCapStyle(Qt::FlatCap); + theStroker.setDashPattern(QVector<qreal>() << 1 << 2); + path = theStroker.createStroke(path); + p->fillPath(path, QColor(0, 0, 0, 119)); + } + break; + case PE_FrameWindow: + break; + case PE_IndicatorDockWidgetResizeHandle: { + // The docwidget resize handle is drawn as a one-pixel wide line. + p->save(); + if (opt->state & State_Horizontal) { + p->setPen(QColor(160, 160, 160)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + } else { + p->setPen(QColor(145, 145, 145)); + p->drawLine(opt->rect.topRight(), opt->rect.bottomRight()); + } + p->restore(); + } break; + case PE_IndicatorToolBarHandle: { + p->save(); + QPainterPath path; + int x = opt->rect.x() + 6; + int y = opt->rect.y() + 7; + static const int RectHeight = 2; + if (opt->state & State_Horizontal) { + while (y < opt->rect.height() - RectHeight - 5) { + path.moveTo(x, y); + path.addEllipse(x, y, RectHeight, RectHeight); + y += 6; + } + } else { + while (x < opt->rect.width() - RectHeight - 5) { + path.moveTo(x, y); + path.addEllipse(x, y, RectHeight, RectHeight); + x += 6; + } + } + p->setPen(Qt::NoPen); + QColor dark = opt->palette.dark().color().darker(); + dark.setAlphaF(0.50); + p->fillPath(path, dark); + p->restore(); + + break; + } + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // In HITheme, up is down, down is up and hamburgers eat people. + if (header->sortIndicator != QStyleOptionHeader::None) + proxy()->drawPrimitive( + (header->sortIndicator == QStyleOptionHeader::SortDown) ? + PE_IndicatorArrowUp : PE_IndicatorArrowDown, header, p, w); + } + break; + case PE_IndicatorMenuCheckMark: { + 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); + + 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: + case PE_IndicatorCheckBox: { + bool drawColorless = tds == kThemeStateInactive; + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + if (drawColorless) + bdi.state = kThemeStateActive; + bdi.adornment = kThemeDrawIndicatorOnly; + if (opt->state & State_HasFocus) + bdi.adornment |= kThemeAdornmentFocus; + bool isRadioButton = (pe == PE_IndicatorRadioButton); + switch (d->aquaSizeConstrain(opt, w)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (isRadioButton) + bdi.kind = kThemeRadioButton; + else + bdi.kind = kThemeCheckBox; + break; + case QAquaSizeMini: + if (isRadioButton) + bdi.kind = kThemeMiniRadioButton; + else + bdi.kind = kThemeMiniCheckBox; + break; + case QAquaSizeSmall: + if (isRadioButton) + bdi.kind = kThemeSmallRadioButton; + else + bdi.kind = kThemeSmallCheckBox; + break; + } + if (opt->state & State_NoChange) + bdi.value = kThemeButtonMixed; + else if (opt->state & State_On) + bdi.value = kThemeButtonOn; + else + bdi.value = kThemeButtonOff; + HIRect macRect = qt_hirectForQRect(opt->rect); + if (!drawColorless) + HIThemeDrawButton(&macRect, &bdi, cg, kHIThemeOrientationNormal, 0); + else + d->drawColorlessButton(macRect, &bdi, p, opt); + break; } + case PE_FrameFocusRect: + // Use the our own focus widget stuff. + break; + case PE_IndicatorBranch: { + if (!(opt->state & State_Children)) + break; + if (!d->indicatorBranchButtonCell) + const_cast<QMacStylePrivate *>(d)->indicatorBranchButtonCell = (void *)[[NSButtonCell alloc] init]; + NSButtonCell *triangleCell = (NSButtonCell *)d->indicatorBranchButtonCell; + [triangleCell setButtonType:NSOnOffButton]; + [triangleCell setState:(opt->state & State_Open) ? NSOnState : NSOffState]; + [triangleCell setBezelStyle:NSDisclosureBezelStyle]; + bool viewHasFocus = (w && w->hasFocus()) || (opt->state & State_HasFocus); + [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleDark : NSBackgroundStyleLight]; + + CGContextSaveGState(cg); + [NSGraphicsContext saveGraphicsState]; + + [NSGraphicsContext setCurrentContext:[NSGraphicsContext + graphicsContextWithGraphicsPort:(CGContextRef)cg flipped:NO]]; + + QRect qtRect = opt->rect.adjusted(DisclosureOffset, 0, -DisclosureOffset, 0); + CGRect rect = CGRectMake(qtRect.x() + 1, qtRect.y(), qtRect.width(), qtRect.height()); + CGContextTranslateCTM(cg, rect.origin.x, rect.origin.y + rect.size.height); + CGContextScaleCTM(cg, 1, -1); + CGContextTranslateCTM(cg, -rect.origin.x, -rect.origin.y); + + [triangleCell drawBezelWithFrame:NSRectFromCGRect(rect) inView:[triangleCell controlView]]; + + [NSGraphicsContext restoreGraphicsState]; + CGContextRestoreGState(cg); + break; } + + case PE_Frame: { + QPen oldPen = p->pen(); + p->setPen(opt->palette.base().color().darker(140)); + p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); + p->setPen(opt->palette.base().color().darker(180)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(oldPen); + break; } + + case PE_FrameLineEdit: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (frame->state & State_Sunken) { + QColor baseColor(frame->palette.background().color()); + HIThemeFrameDrawInfo fdi; + fdi.version = qt_mac_hitheme_version; + fdi.state = tds; + SInt32 frame_size; + fdi.kind = frame->features & QStyleOptionFrame::Rounded ? kHIThemeFrameTextFieldRound : + kHIThemeFrameTextFieldSquare; + GetThemeMetric(kThemeMetricEditTextFrameOutset, &frame_size); + if ((frame->state & State_ReadOnly) || !(frame->state & State_Enabled)) + fdi.state = kThemeStateInactive; + else if (fdi.state == kThemeStatePressed) + // This pressed state doesn't make sense for a line edit frame. + // And Yosemite agrees with us. Otherwise it starts showing yellow pixels. + fdi.state = kThemeStateActive; + fdi.isFocused = (frame->state & State_HasFocus); + int lw = frame->lineWidth; + if (lw <= 0) + lw = proxy()->pixelMetric(PM_DefaultFrameWidth, frame, w); + { //clear to base color + p->save(); + p->setPen(QPen(baseColor, lw)); + p->setBrush(Qt::NoBrush); + p->drawRect(frame->rect); + p->restore(); + } + HIRect hirect = qt_hirectForQRect(frame->rect, + QRect(frame_size, frame_size, + frame_size * 2, frame_size * 2)); + + HIThemeDrawFrame(&hirect, &fdi, cg, kHIThemeOrientationNormal); + } else { + QCommonStyle::drawPrimitive(pe, opt, p, w); + } + } + break; + case PE_PanelLineEdit: + QCommonStyle::drawPrimitive(pe, opt, p, w); + // Draw the focus frame for widgets other than QLineEdit (e.g. for line edits in Webkit). + // Focus frame is drawn outside the rectangle passed in the option-rect. + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { +#ifndef QT_NO_LINEEDIT + if ((opt->state & State_HasFocus) && !qobject_cast<const QLineEdit*>(w)) { + int vmargin = pixelMetric(QStyle::PM_FocusFrameVMargin); + int hmargin = pixelMetric(QStyle::PM_FocusFrameHMargin); + QStyleOptionFrame focusFrame = *panel; + focusFrame.rect = panel->rect.adjusted(-hmargin, -vmargin, hmargin, vmargin); + drawControl(CE_FocusFrame, &focusFrame, p, w); + } +#endif + } + + break; +#ifndef QT_NO_TABWIDGET + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + HIRect hirect = qt_hirectForQRect(twf->rect); + HIThemeTabPaneDrawInfo tpdi; + tpdi.version = qt_mac_hitheme_tab_version(); + tpdi.state = tds; + tpdi.direction = getTabDirection(twf->shape); + tpdi.size = kHIThemeTabSizeNormal; + tpdi.kind = kHIThemeTabKindNormal; + tpdi.adornment = kHIThemeTabPaneAdornmentNormal; + HIThemeDrawTabPane(&hirect, &tpdi, cg, kHIThemeOrientationNormal); + } + break; +#endif + case PE_PanelScrollAreaCorner: { + const QBrush brush(opt->palette.brush(QPalette::Base)); + p->fillRect(opt->rect, brush); + p->setPen(QPen(QColor(217, 217, 217))); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); + } break; + case PE_FrameStatusBarItem: + break; + case PE_IndicatorTabClose: { + // Make close button visible only on the hovered tab. + if (QTabBar *tabBar = qobject_cast<QTabBar*>(w->parentWidget())) { + const QTabBarPrivate *tabBarPrivate = static_cast<QTabBarPrivate *>(QObjectPrivate::get(tabBar)); + const int hoveredTabIndex = tabBarPrivate->hoveredTabIndex(); + if (hoveredTabIndex != -1 && ((w == tabBar->tabButton(hoveredTabIndex, QTabBar::LeftSide)) || + (w == tabBar->tabButton(hoveredTabIndex, QTabBar::RightSide)))) { + const bool hover = (opt->state & State_MouseOver); + const bool selected = (opt->state & State_Selected); + const bool pressed = (opt->state & State_Sunken); + drawTabCloseButton(p, hover, selected, pressed); + } + } + } break; + case PE_PanelStatusBar: { + // Fill the status bar with the titlebar gradient. + QLinearGradient linearGrad; + if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active)) { + linearGrad = titlebarGradientActive(); + } else { + linearGrad = titlebarGradientInactive(); + } + + linearGrad.setStart(0, opt->rect.top()); + linearGrad.setFinalStop(0, opt->rect.bottom()); + p->fillRect(opt->rect, linearGrad); + + // Draw the black separator line at the top of the status bar. + if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active)) + p->setPen(titlebarSeparatorLineActive); + else + p->setPen(titlebarSeparatorLineInactive); + p->drawLine(opt->rect.left(), opt->rect.top(), opt->rect.right(), opt->rect.top()); + + break; + } + + default: + QCommonStyle::drawPrimitive(pe, opt, p, w); + break; + } +} + +static inline QPixmap darkenPixmap(const QPixmap &pixmap) +{ + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int imgh = img.height(); + int imgw = img.width(); + int h, s, v, a; + QRgb pixel; + for (int y = 0; y < imgh; ++y) { + for (int x = 0; x < imgw; ++x) { + pixel = img.pixel(x, y); + a = qAlpha(pixel); + QColor hsvColor(pixel); + hsvColor.getHsv(&h, &s, &v); + s = qMin(100, s * 2); + v = v / 2; + hsvColor.setHsv(h, s, v); + pixel = hsvColor.rgb(); + img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), a)); + } + } + return QPixmap::fromImage(img); +} + + + +void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + Q_D(const QMacStyle); + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + QWindow *window = w && w->window() ? w->window()->windowHandle() : + QStyleHelper::styleObjectWindow(opt->styleObject); + const_cast<QMacStylePrivate *>(d)->resolveCurrentNSView(window); + switch (ce) { + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + State flags = header->state; + QRect ir = header->rect; + bdi.kind = kThemeListHeaderButton; + bdi.adornment = kThemeAdornmentNone; + bdi.state = kThemeStateActive; + + if (flags & State_On) + bdi.value = kThemeButtonOn; + else + bdi.value = kThemeButtonOff; + + if (header->orientation == Qt::Horizontal){ + switch (header->position) { + case QStyleOptionHeader::Beginning: + ir.adjust(-1, -1, 0, 0); + break; + case QStyleOptionHeader::Middle: + ir.adjust(-1, -1, 0, 0); + break; + case QStyleOptionHeader::OnlyOneSection: + case QStyleOptionHeader::End: + ir.adjust(-1, -1, 1, 0); + break; + default: + break; + } + + if (header->position != QStyleOptionHeader::Beginning + && header->position != QStyleOptionHeader::OnlyOneSection) { + bdi.adornment = header->direction == Qt::LeftToRight + ? kThemeAdornmentHeaderButtonLeftNeighborSelected + : kThemeAdornmentHeaderButtonRightNeighborSelected; + } + } + + if (flags & State_Active) { + if (!(flags & State_Enabled)) + bdi.state = kThemeStateUnavailable; + else if (flags & State_Sunken) + bdi.state = kThemeStatePressed; + } else { + if (flags & State_Enabled) + bdi.state = kThemeStateInactive; + else + bdi.state = kThemeStateUnavailableInactive; + } + + if (header->sortIndicator != QStyleOptionHeader::None) { + bdi.value = kThemeButtonOn; + if (header->sortIndicator == QStyleOptionHeader::SortDown) + bdi.adornment = kThemeAdornmentHeaderButtonSortUp; + } + if (flags & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + + ir = visualRect(header->direction, header->rect, ir); + HIRect bounds = qt_hirectForQRect(ir); + + bool noVerticalHeader = true; +#ifndef QT_NO_TABLEVIEW + if (w) + if (const QTableView *table = qobject_cast<const QTableView *>(w->parentWidget())) + noVerticalHeader = !table->verticalHeader()->isVisible(); +#endif + + bool drawTopBorder = header->orientation == Qt::Horizontal; + bool drawLeftBorder = header->orientation == Qt::Vertical + || header->position == QStyleOptionHeader::OnlyOneSection + || (header->position == QStyleOptionHeader::Beginning && noVerticalHeader); + d->drawTableHeader(bounds, drawTopBorder, drawLeftBorder, bdi, p); + } + break; + case CE_HeaderLabel: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + p->save(); + QRect textr = header->rect; + if (!header->icon.isNull()) { + QIcon::Mode mode = QIcon::Disabled; + if (opt->state & State_Enabled) + mode = QIcon::Normal; + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + QPixmap pixmap = header->icon.pixmap(window, QSize(iconExtent, iconExtent), mode); + + QRect pixr = header->rect; + pixr.setY(header->rect.center().y() - (pixmap.height() / pixmap.devicePixelRatio() - 1) / 2); + proxy()->drawItemPixmap(p, pixr, Qt::AlignVCenter, pixmap); + textr.translate(pixmap.width() / pixmap.devicePixelRatio() + 2, 0); + } + + proxy()->drawItemText(p, textr, header->textAlignment | Qt::AlignVCenter, header->palette, + header->state & State_Enabled, header->text, QPalette::ButtonText); + p->restore(); + } + break; + case CE_ToolButtonLabel: + if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + QStyleOptionToolButton myTb = *tb; + myTb.state &= ~State_AutoRaise; +#ifndef QT_NO_ACCESSIBILITY + if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) { + QRect cr = tb->rect; + int shiftX = 0; + int shiftY = 0; + bool needText = false; + int alignment = 0; + bool down = tb->state & (State_Sunken | State_On); + if (down) { + shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, w); + shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, tb, w); + } + // The down state is special for QToolButtons in a toolbar on the Mac + // The text is a bit bolder and gets a drop shadow and the icons are also darkened. + // This doesn't really fit into any particular case in QIcon, so we + // do the majority of the work ourselves. + if (!(tb->features & QStyleOptionToolButton::Arrow)) { + Qt::ToolButtonStyle tbstyle = tb->toolButtonStyle; + if (tb->icon.isNull() && !tb->text.isEmpty()) + tbstyle = Qt::ToolButtonTextOnly; + + switch (tbstyle) { + case Qt::ToolButtonTextOnly: { + needText = true; + alignment = Qt::AlignCenter; + break; } + case Qt::ToolButtonIconOnly: + case Qt::ToolButtonTextBesideIcon: + case Qt::ToolButtonTextUnderIcon: { + QRect pr = cr; + QIcon::Mode iconMode = (tb->state & State_Enabled) ? QIcon::Normal + : QIcon::Disabled; + QIcon::State iconState = (tb->state & State_On) ? QIcon::On + : QIcon::Off; + QPixmap pixmap = tb->icon.pixmap(window, + tb->rect.size().boundedTo(tb->iconSize), + iconMode, iconState); + + // Draw the text if it's needed. + if (tb->toolButtonStyle != Qt::ToolButtonIconOnly) { + needText = true; + if (tb->toolButtonStyle == Qt::ToolButtonTextUnderIcon) { + pr.setHeight(pixmap.size().height() / pixmap.devicePixelRatio() + 6); + cr.adjust(0, pr.bottom(), 0, -3); + alignment |= Qt::AlignCenter; + } else { + pr.setWidth(pixmap.width() / pixmap.devicePixelRatio() + 8); + cr.adjust(pr.right(), 0, 0, 0); + alignment |= Qt::AlignLeft | Qt::AlignVCenter; + } + } + if (opt->state & State_Sunken) { + pr.translate(shiftX, shiftY); + pixmap = darkenPixmap(pixmap); + } + proxy()->drawItemPixmap(p, pr, Qt::AlignCenter, pixmap); + break; } + default: + Q_ASSERT(false); + break; + } + + if (needText) { + QPalette pal = tb->palette; + QPalette::ColorRole role = QPalette::NoRole; + if (!proxy()->styleHint(SH_UnderlineShortcut, tb, w)) + alignment |= Qt::TextHideMnemonic; + if (down) + cr.translate(shiftX, shiftY); + if (tbstyle == Qt::ToolButtonTextOnly + || (tbstyle != Qt::ToolButtonTextOnly && !down)) { + QPen pen = p->pen(); + QColor light = down ? Qt::black : Qt::white; + light.setAlphaF(0.375f); + p->setPen(light); + p->drawText(cr.adjusted(0, 1, 0, 1), alignment, tb->text); + p->setPen(pen); + if (down && tbstyle == Qt::ToolButtonTextOnly) { + pal = QApplication::palette("QMenu"); + pal.setCurrentColorGroup(tb->palette.currentColorGroup()); + role = QPalette::HighlightedText; + } + } + proxy()->drawItemText(p, cr, alignment, pal, + tb->state & State_Enabled, tb->text, role); + } + } else { + QCommonStyle::drawControl(ce, &myTb, p, w); + } + } else { + QCommonStyle::drawControl(ce, &myTb, p, w); + } +#else + Q_UNUSED(tb) +#endif + } + break; + case CE_ToolBoxTabShape: + QCommonStyle::drawControl(ce, opt, p, w); + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (!(btn->state & (State_Raised | State_Sunken | State_On))) + break; + + if (btn->features & QStyleOptionButton::CommandLinkButton) { + QCommonStyle::drawControl(ce, opt, p, w); + break; + } + + // No default button pulsating animation on Yosemite, + // so we have to do few things differently. + + // a focused auto-default button within an active window + // takes precedence over a normal default button + if (btn->features & QStyleOptionButton::AutoDefaultButton + && opt->state & State_Active && opt->state & State_HasFocus) { + d->autoDefaultButton = opt->styleObject; + } else if (d->autoDefaultButton == opt->styleObject) { + d->setAutoDefaultButton(0); + } + + if (!d->autoDefaultButton) { + if (btn->features & QStyleOptionButton::DefaultButton && opt->state & State_Active) { + d->defaultButton = opt->styleObject; + } else if (d->defaultButton == opt->styleObject) { + if (QStyleAnimation *animation = d->animation(opt->styleObject)) { + animation->updateTarget(); + d->stopAnimation(opt->styleObject); + } + d->defaultButton = 0; + } + } + + // TODO: find out the pressed button in a qwidget independent way + extern QWidget *qt_button_down; // qwidgetwindow.cpp + if (opt->styleObject == qt_button_down) + d->pressedButton = opt->styleObject; + else if (d->pressedButton == opt->styleObject) + d->pressedButton = 0; + + bool hasMenu = btn->features & QStyleOptionButton::HasMenu; + HIThemeButtonDrawInfo bdi; + d->initHIThemePushButton(btn, w, tds, &bdi); + + if (!hasMenu) { + // HITheme is not drawing a nice focus frame around buttons. + // We'll do it ourselves further down. + bdi.adornment &= ~kThemeAdornmentFocus; + + // We can't rely on an animation existing to test for the default look. That means a bit + // more logic (notice that the logic is slightly different for the bevel and the label). + if (tds == kThemeStateActive + && (btn->features & QStyleOptionButton::DefaultButton + || (btn->features & QStyleOptionButton::AutoDefaultButton + && d->autoDefaultButton == btn->styleObject))) + bdi.adornment |= kThemeAdornmentDefault; + } + + // Unlike Carbon, we want the button to always be drawn inside its bounds. + // Therefore, make the button a bit smaller, so that even if it got focus, + // the focus 'shadow' will be inside. + HIRect newRect = qt_hirectForQRect(btn->rect); + if (bdi.kind == kThemePushButton || bdi.kind == kThemePushButtonSmall) { + newRect.origin.x += QMacStylePrivate::PushButtonLeftOffset; + newRect.origin.y += QMacStylePrivate::PushButtonTopOffset; + newRect.size.width -= QMacStylePrivate::PushButtonRightOffset; + newRect.size.height -= QMacStylePrivate::PushButtonBottomOffset; + } else if (bdi.kind == kThemePushButtonMini) { + newRect.origin.x += QMacStylePrivate::PushButtonLeftOffset - 2; + newRect.origin.y += QMacStylePrivate::PushButtonTopOffset; + newRect.size.width -= QMacStylePrivate::PushButtonRightOffset - 4; + } + + if (hasMenu && bdi.kind != kThemeBevelButton) { + QCocoaWidget cw = cocoaWidgetFromHIThemeButtonKind(bdi.kind); + cw.first = QCocoaPullDownButton; + NSPopUpButton *pdb = (NSPopUpButton *)d->cocoaControl(cw); + [pdb highlight:(bdi.state == kThemeStatePressed)]; + pdb.enabled = bdi.state != kThemeStateUnavailable && bdi.state != kThemeStateUnavailableInactive; + QRect rect = opt->rect; + rect.adjust(0, 0, cw.second == QAquaSizeSmall ? -4 : cw.second == QAquaSizeMini ? -9 : -6, 0); + d->drawNSViewInRect(cw, pdb, rect, p, w != 0); + } else if (hasMenu && bdi.state == kThemeStatePressed) + d->drawColorlessButton(newRect, &bdi, p, opt); + else + HIThemeDrawButton(&newRect, &bdi, cg, kHIThemeOrientationNormal, 0); + + if (btn->state & State_HasFocus) { + CGRect focusRect = newRect; + if (bdi.kind == kThemePushButton) + focusRect.size.height += 1; // Another thing HITheme and Cocoa seem to disagree about. + else if (bdi.kind == kThemePushButtonMini) + focusRect.size.height = 15; // Our QPushButton sizes are really weird + + if (bdi.adornment & kThemeAdornmentDefault || bdi.state == kThemeStatePressed) { + if (bdi.kind == kThemePushButtonSmall) { + focusRect = CGRectInset(focusRect, -1, 0); + } else if (bdi.kind == kThemePushButtonMini) { + focusRect = CGRectInset(focusRect, 1, 0); + } + } else { + if (bdi.kind == kThemePushButton) { + focusRect = CGRectInset(focusRect, 1, 1); + } else if (bdi.kind == kThemePushButtonSmall) { + focusRect = CGRectInset(focusRect, 0, 2); + } else if (bdi.kind == kThemePushButtonMini) { + focusRect = CGRectInset(focusRect, 2, 1); + } + } + + const qreal radius = bdi.kind == kThemeBevelButton ? 0 : 4; + const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, btn, w); + const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, btn, w); + const QRect focusTargetRect(focusRect.origin.x, focusRect.origin.y, focusRect.size.width, focusRect.size.height); + d->drawFocusRing(p, focusTargetRect.adjusted(-hMargin, -vMargin, hMargin, vMargin), hMargin, vMargin, radius); + } + + if (hasMenu && bdi.kind == kThemeBevelButton) { + int mbi = proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, w); + QRect ir = btn->rect; + int arrowXOffset = bdi.kind == kThemePushButton ? 6 : + bdi.kind == kThemePushButtonSmall ? 7 : 8; + int arrowYOffset = bdi.kind == kThemePushButton ? 3 : + bdi.kind == kThemePushButtonSmall ? 1 : 2; + if (!w) { + // adjustment for Qt Quick Controls + arrowYOffset -= ir.top(); + if (bdi.kind == kThemePushButtonSmall) + arrowYOffset += 1; + } + QRect ar = QRect(ir.right() - mbi - QMacStylePrivate::PushButtonRightOffset, + ir.height() / 2 - arrowYOffset, mbi, ir.height() / 2); + ar = visualRect(btn->direction, ir, ar); + HIRect arrowRect = CGRectMake(ar.x() + arrowXOffset, ar.y(), ar.width(), ar.height()); + + HIThemePopupArrowDrawInfo pdi; + pdi.version = qt_mac_hitheme_version; + pdi.state = tds == kThemeStateInactive ? kThemeStateActive : tds; + pdi.orientation = kThemeArrowDown; + if (bdi.kind == kThemePushButtonMini) + pdi.size = kThemeArrow5pt; + else if (bdi.kind == kThemePushButton || bdi.kind == kThemePushButtonSmall) + pdi.size = kThemeArrow7pt; + HIThemeDrawPopupArrow(&arrowRect, &pdi, cg, kHIThemeOrientationNormal); + } + } + break; + case CE_PushButtonLabel: + if (const QStyleOptionButton *b = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton btn(*b); + // We really don't want the label to be drawn the same as on + // windows style if it has an icon and text, then it should be more like a + // tab. So, cheat a little here. However, if it *is* only an icon + // the windows style works great, so just use that implementation. + bool hasMenu = btn.features & QStyleOptionButton::HasMenu; + bool hasIcon = !btn.icon.isNull(); + bool hasText = !btn.text.isEmpty(); + + if (!hasMenu) { + if (tds == kThemeStatePressed + || (tds == kThemeStateActive + && ((btn.features & QStyleOptionButton::DefaultButton && !d->autoDefaultButton) + || d->autoDefaultButton == btn.styleObject))) + btn.palette.setColor(QPalette::ButtonText, Qt::white); + } + + if (!hasIcon && !hasMenu) { + // ### this is really overly difficult, simplify. + // It basically tries to get the right font for "small" and "mini" icons. + QFont oldFont = p->font(); + QFont newFont = qt_app_fonts_hash()->value("QPushButton", QFont()); + ThemeFontID themeId = kThemePushButtonFont; + if (oldFont == newFont) { // Yes, use HITheme to draw the text for small sizes. + switch (d->aquaSizeConstrain(opt, w)) { + default: + break; + case QAquaSizeSmall: + themeId = kThemeSmallSystemFont; + break; + case QAquaSizeMini: + themeId = kThemeMiniSystemFont; + break; + } + } + + if (themeId == kThemePushButtonFont) { + QCommonStyle::drawControl(ce, &btn, p, w); + } else { + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor; + textColor = btn.palette.buttonText().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); + tti.fontID = themeId; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + btn.text.count(QLatin1Char('\n')); + QCFString buttonText = qt_mac_removeMnemonics(btn.text); + QRect r = btn.rect; + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(buttonText, &bounds, &tti, + cg, kHIThemeOrientationNormal); + p->restore(); + } + } else { + if (hasIcon && !hasText) { + QCommonStyle::drawControl(ce, &btn, p, w); + } else { + QRect freeContentRect = btn.rect; + QRect textRect = itemTextRect( + btn.fontMetrics, freeContentRect, Qt::AlignCenter, btn.state & State_Enabled, btn.text); + if (hasMenu) { + textRect.moveTo(w ? 15 : 11, textRect.top()); // Supports Qt Quick Controls + } + // Draw the icon: + if (hasIcon) { + int contentW = textRect.width(); + if (hasMenu) + contentW += proxy()->pixelMetric(PM_MenuButtonIndicator) + 4; + QIcon::Mode mode = btn.state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + if (mode == QIcon::Normal && btn.state & State_HasFocus) + mode = QIcon::Active; + // Decide if the icon is should be on or off: + QIcon::State state = QIcon::Off; + if (btn.state & State_On) + state = QIcon::On; + QPixmap pixmap = btn.icon.pixmap(window, btn.iconSize, mode, state); + int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio(); + int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio(); + contentW += pixmapWidth + QMacStylePrivate::PushButtonContentPadding; + int iconLeftOffset = freeContentRect.x() + (freeContentRect.width() - contentW) / 2; + int iconTopOffset = freeContentRect.y() + (freeContentRect.height() - pixmapHeight) / 2; + QRect iconDestRect(iconLeftOffset, iconTopOffset, pixmapWidth, pixmapHeight); + QRect visualIconDestRect = visualRect(btn.direction, freeContentRect, iconDestRect); + proxy()->drawItemPixmap(p, visualIconDestRect, Qt::AlignLeft | Qt::AlignVCenter, pixmap); + int newOffset = iconDestRect.x() + iconDestRect.width() + + QMacStylePrivate::PushButtonContentPadding - textRect.x(); + textRect.adjust(newOffset, 0, newOffset, 0); + } + // Draw the text: + if (hasText) { + textRect = visualRect(btn.direction, freeContentRect, textRect); + proxy()->drawItemText(p, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, btn.palette, + (btn.state & State_Enabled), btn.text, QPalette::ButtonText); + } + } + } + } + break; + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QStyleOptionComboBox comboCopy = *cb; + comboCopy.direction = Qt::LeftToRight; + QCommonStyle::drawControl(CE_ComboBoxLabel, &comboCopy, p, w); + } + break; +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + if (const QStyleOptionTab *tabOpt = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + if (tabOpt->documentMode) { + p->save(); + bool isUnified = false; + if (w) { + QRect tabRect = tabOpt->rect; + QPoint windowTabStart = w->mapTo(w->window(), tabRect.topLeft()); + isUnified = isInMacUnifiedToolbarArea(w->window()->windowHandle(), windowTabStart.y()); + } + + const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, opt, w); + drawTabShape(p, tabOpt, isUnified, tabOverlap); + + p->restore(); + return; + } + + HIThemeTabDrawInfo tdi; + tdi.version = 1; + tdi.style = kThemeTabNonFront; + tdi.direction = getTabDirection(tabOpt->shape); + switch (d->aquaSizeConstrain(opt, w)) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + tdi.size = kHIThemeTabSizeNormal; + break; + case QAquaSizeSmall: + tdi.size = kHIThemeTabSizeSmall; + break; + case QAquaSizeMini: + tdi.size = kHIThemeTabSizeMini; + break; + } + bool verticalTabs = tdi.direction == kThemeTabWest || tdi.direction == kThemeTabEast; + QRect tabRect = tabOpt->rect; + + bool selected = tabOpt->state & State_Selected; + if (selected) { + if (!(tabOpt->state & State_Active)) + tdi.style = kThemeTabFrontUnavailable; + else if (!(tabOpt->state & State_Enabled)) + tdi.style = kThemeTabFrontInactive; + else + tdi.style = kThemeTabFront; + } else if (!(tabOpt->state & State_Active)) { + tdi.style = kThemeTabNonFrontUnavailable; + } else if (!(tabOpt->state & State_Enabled)) { + tdi.style = kThemeTabNonFrontInactive; + } else if (tabOpt->state & State_Sunken) { + tdi.style = kThemeTabNonFrontPressed; + } + if (tabOpt->state & State_HasFocus) + tdi.adornment = kHIThemeTabAdornmentFocus; + else + tdi.adornment = kHIThemeTabAdornmentNone; + tdi.kind = kHIThemeTabKindNormal; + + QStyleOptionTab::TabPosition tp = tabOpt->position; + QStyleOptionTab::SelectedPosition sp = tabOpt->selectedPosition; + if (tabOpt->direction == Qt::RightToLeft && !verticalTabs) { + if (sp == QStyleOptionTab::NextIsSelected) + sp = QStyleOptionTab::PreviousIsSelected; + else if (sp == QStyleOptionTab::PreviousIsSelected) + sp = QStyleOptionTab::NextIsSelected; + switch (tp) { + case QStyleOptionTab::Beginning: + tp = QStyleOptionTab::End; + break; + case QStyleOptionTab::End: + tp = QStyleOptionTab::Beginning; + break; + default: + break; + } + } + bool stretchTabs = (!verticalTabs && tabRect.height() > 22) || (verticalTabs && tabRect.width() > 22); + + switch (tp) { + case QStyleOptionTab::Beginning: + tdi.position = kHIThemeTabPositionFirst; + if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) + tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + break; + case QStyleOptionTab::Middle: + tdi.position = kHIThemeTabPositionMiddle; + if (selected) + tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; + if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) // Also when we're selected. + tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + break; + case QStyleOptionTab::End: + tdi.position = kHIThemeTabPositionLast; + if (selected) + tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; + break; + case QStyleOptionTab::OnlyOneTab: + tdi.position = kHIThemeTabPositionOnly; + break; + } + // HITheme doesn't stretch its tabs. Therefore we have to cheat and do the job ourselves. + if (stretchTabs) { + HIRect hirect = CGRectMake(0, 0, 23, 23); + QPixmap pm(23, 23); + pm.fill(Qt::transparent); + { + QMacCGContext pmcg(&pm); + HIThemeDrawTab(&hirect, &tdi, pmcg, kHIThemeOrientationNormal, 0); + } + QStyleHelper::drawBorderPixmap(pm, p, tabRect, 7, 7, 7, 7); + } else { + HIRect hirect = qt_hirectForQRect(tabRect); + HIThemeDrawTab(&hirect, &tdi, cg, kHIThemeOrientationNormal, 0); + } + } + break; + case CE_TabBarTabLabel: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + QStyleOptionTab myTab = *tab; + ThemeTabDirection ttd = getTabDirection(myTab.shape); + bool verticalTabs = ttd == kThemeTabWest || ttd == kThemeTabEast; + + // Check to see if we use have the same as the system font + // (QComboMenuItem is internal and should never be seen by the + // outside world, unless they read the source, in which case, it's + // their own fault). + bool nonDefaultFont = p->font() != qt_app_fonts_hash()->value("QComboMenuItem"); + if (verticalTabs || nonDefaultFont || !tab->icon.isNull() + || !myTab.leftButtonSize.isEmpty() || !myTab.rightButtonSize.isEmpty()) { + int heightOffset = 0; + if (verticalTabs) { + heightOffset = -1; + } else if (nonDefaultFont) { + if (p->fontMetrics().height() == myTab.rect.height()) + heightOffset = 2; + } + myTab.rect.setHeight(myTab.rect.height() + heightOffset); + + QCommonStyle::drawControl(ce, &myTab, p, w); + } else { + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor = myTab.palette.windowText().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); + switch (d->aquaSizeConstrain(opt, w)) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + tti.fontID = kThemeSystemFont; + break; + case QAquaSizeSmall: + tti.fontID = kThemeSmallSystemFont; + break; + case QAquaSizeMini: + tti.fontID = kThemeMiniSystemFont; + break; + } + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = verticalTabs ? kHIThemeTextBoxOptionStronglyVertical : kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + myTab.text.count(QLatin1Char('\n')); + QCFString tabText = qt_mac_removeMnemonics(myTab.text); + QRect r = myTab.rect.adjusted(0, 0, 0, -1); + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(tabText, &bounds, &tti, cg, kHIThemeOrientationNormal); + p->restore(); + } + } + break; +#endif +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(w)) { + bool floating = dockWidget->isFloating(); + if (floating) { + ThemeDrawState tds = d->getDrawState(opt->state); + HIThemeWindowDrawInfo wdi; + wdi.version = qt_mac_hitheme_version; + wdi.state = tds; + wdi.windowType = kThemeMovableDialogWindow; + wdi.titleHeight = opt->rect.height(); + wdi.titleWidth = opt->rect.width(); + wdi.attributes = 0; + + HIRect titleBarRect; + HIRect tmpRect = qt_hirectForQRect(opt->rect); + { + QCFType<HIShapeRef> titleRegion; + QRect newr = opt->rect.adjusted(0, 0, 2, 0); + HIThemeGetWindowShape(&tmpRect, &wdi, kWindowTitleBarRgn, &titleRegion); + ptrHIShapeGetBounds(titleRegion, &tmpRect); + newr.translate(newr.x() - int(tmpRect.origin.x), newr.y() - int(tmpRect.origin.y)); + titleBarRect = qt_hirectForQRect(newr); + } + QMacCGContext cg(p); + HIThemeDrawWindowFrame(&titleBarRect, &wdi, cg, kHIThemeOrientationNormal, 0); + } else { + // fill title bar background + QLinearGradient linearGrad(0, opt->rect.top(), 0, opt->rect.bottom()); + linearGrad.setColorAt(0, mainWindowGradientBegin); + linearGrad.setColorAt(1, mainWindowGradientEnd); + p->fillRect(opt->rect, linearGrad); + + // draw horizontal lines at top and bottom + p->save(); + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); + p->restore(); + } + } + + // Draw the text... + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) { + if (!dwOpt->title.isEmpty()) { + + const bool verticalTitleBar = dwOpt->verticalTitleBar; + + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, opt, w); + if (verticalTitleBar) { + QRect rect = dwOpt->rect; + QRect r = rect.transposed(); + + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + + p->translate(r.left(), r.top() + r.width()); + p->rotate(-90); + p->translate(-r.left(), -r.top()); + } + + QFont oldFont = p->font(); + p->setFont(qt_app_fonts_hash()->value("QToolButton", p->font())); + QString text = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, + titleRect.width()); + drawItemText(p, titleRect, + Qt::AlignCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, text, + QPalette::WindowText); + p->setFont(oldFont); + } + } + break; +#endif + case CE_FocusFrame: { + const int hMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, w); + const int vMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, opt, w); + d->drawFocusRing(p, opt->rect, hMargin, vMargin); + break; } + case CE_MenuItem: + case CE_MenuEmptyArea: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + p->fillRect(mi->rect, opt->palette.background()); + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, w); + int tabwidth = mi->tabWidth; + int maxpmw = mi->maxIconWidth; + bool active = mi->state & State_Selected; + bool enabled = mi->state & State_Enabled; + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + mdi.itemType = kThemeMenuItemPlain; + if (!mi->icon.isNull()) + mdi.itemType |= kThemeMenuItemHasIcon; + if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + mdi.itemType |= kThemeMenuItemHierarchical | kThemeMenuItemHierBackground; + else + mdi.itemType |= kThemeMenuItemPopUpBackground; + if (enabled) + mdi.state = kThemeMenuActive; + else + mdi.state = kThemeMenuDisabled; + if (active) + mdi.state |= kThemeMenuSelected; + QRect contentRect; + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + // First arg should be &menurect, but wacky stuff happens then. + HIThemeDrawMenuSeparator(&itemRect, &itemRect, &mdi, + cg, kHIThemeOrientationNormal); + break; + } else { + HIRect cr; + bool needAlpha = mi->palette.color(QPalette::Button) == Qt::transparent; + if (needAlpha) { + CGContextSaveGState(cg); + CGContextSetAlpha(cg, 0.0); + } + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, + cg, kHIThemeOrientationNormal, &cr); + if (needAlpha) + CGContextRestoreGState(cg); + if (ce == CE_MenuEmptyArea) + break; + contentRect = qt_qrectForHIRect(cr); + } + int xpos = contentRect.x() + 18; + int checkcol = maxpmw; + if (!enabled) + p->setPen(mi->palette.text().color()); + else if (active) + p->setPen(mi->palette.highlightedText().color()); + else + p->setPen(mi->palette.buttonText().color()); + + if (mi->checked) { + 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) + 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 + : QIcon::Disabled; + // Always be normal or disabled to follow the Mac style. + int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize); + QSize iconSize(smallIconSize, smallIconSize); +#ifndef QT_NO_COMBOBOX + if (const QComboBox *comboBox = qobject_cast<const QComboBox *>(w)) { + iconSize = comboBox->iconSize(); + } +#endif + QPixmap pixmap = mi->icon.pixmap(window, iconSize, mode); + int pixw = pixmap.width() / pixmap.devicePixelRatio(); + int pixh = pixmap.height() / pixmap.devicePixelRatio(); + QRect cr(xpos, contentRect.y(), checkcol, contentRect.height()); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(cr.center()); + p->drawPixmap(pmr.topLeft(), pixmap); + xpos += pixw + 6; + } + + QString s = mi->text; + if (!s.isEmpty()) { + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignRight | Qt::AlignVCenter | Qt::TextHideMnemonic + | Qt::TextSingleLine | Qt::AlignAbsolute; + int yPos = contentRect.y(); + if (widgetSize == QAquaSizeMini) + yPos += 1; + p->save(); + if (t >= 0) { + p->setFont(qt_app_fonts_hash()->value("QMenuItem", p->font())); + int xp = contentRect.right() - tabwidth - macRightBorder + - macItemHMargin - macItemFrame + 1; + p->drawText(xp, yPos, tabwidth, contentRect.height(), text_flags, + s.mid(t + 1)); + s = s.left(t); + } + + const int xm = macItemFrame + maxpmw + macItemHMargin; + QFont myFont = mi->font; + // myFont may not have any "hard" flags set. We override + // the point size so that when it is resolved against the device, this font will win. + // This is mainly to handle cases where someone sets the font on the window + // and then the combo inherits it and passes it onward. At that point the resolve mask + // is very, very weak. This makes it stonger. + myFont.setPointSizeF(QFontInfo(mi->font).pointSizeF()); + p->setFont(myFont); + p->drawText(xpos, yPos, contentRect.width() - xm - tabwidth + 1, + contentRect.height(), text_flags ^ Qt::AlignRight, s); + p->restore(); + } + } + break; + case CE_MenuHMargin: + case CE_MenuVMargin: + case CE_MenuTearoff: + case CE_MenuScroller: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + p->fillRect(mi->rect, opt->palette.background()); + + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + if (!(opt->state & State_Enabled)) + mdi.state = kThemeMenuDisabled; + else if (opt->state & State_Selected) + mdi.state = kThemeMenuSelected; + else + mdi.state = kThemeMenuActive; + if (ce == CE_MenuScroller) { + if (opt->state & State_DownArrow) + mdi.itemType = kThemeMenuItemScrollDownArrow; + else + mdi.itemType = kThemeMenuItemScrollUpArrow; + } else { + mdi.itemType = kThemeMenuItemPlain; + } + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, + cg, + kHIThemeOrientationNormal, 0); + if (ce == CE_MenuTearoff) { + p->setPen(QPen(mi->palette.dark().color(), 1, Qt::DashLine)); + p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2 - 1, + mi->rect.x() + mi->rect.width() - 4, + mi->rect.y() + mi->rect.height() / 2 - 1); + p->setPen(QPen(mi->palette.light().color(), 1, Qt::DashLine)); + p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2, + mi->rect.x() + mi->rect.width() - 4, + mi->rect.y() + mi->rect.height() / 2); + } + } + break; + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + + const bool selected = (opt->state & State_Selected) && (opt->state & State_Enabled) && (opt->state & State_Sunken); + if (selected) { + // Draw a selected menu item background: + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + mdi.state = kThemeMenuSelected; + mdi.itemType = kThemeMenuItemPlain; + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, cg, kHIThemeOrientationNormal, 0); + } else { + // Draw the toolbar background: + HIThemeMenuBarDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeMenuBarNormal; + bdi.attributes = 0; + HIThemeDrawMenuBarBackground(&menuRect, &bdi, cg, kHIThemeOrientationNormal); + } + + if (!mi->icon.isNull()) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + drawItemPixmap(p, mi->rect, + Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip + | Qt::TextSingleLine, + mi->icon.pixmap(window, QSize(iconExtent, iconExtent), + (mi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled)); + } else { + drawItemText(p, mi->rect, + Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip + | Qt::TextSingleLine, + mi->palette, mi->state & State_Enabled, + mi->text, selected ? QPalette::HighlightedText : QPalette::ButtonText); + } + } + break; + case CE_MenuBarEmptyArea: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + HIThemeMenuBarDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeMenuBarNormal; + bdi.attributes = 0; + HIRect hirect = qt_hirectForQRect(mi->rect); + HIThemeDrawMenuBarBackground(&hirect, &bdi, cg, + kHIThemeOrientationNormal); + break; + } + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + HIThemeTrackDrawInfo tdi; + tdi.version = qt_mac_hitheme_version; + tdi.reserved = 0; + bool isIndeterminate = (pb->minimum == 0 && pb->maximum == 0); + const bool vertical = pb->orientation == Qt::Vertical; + const bool inverted = pb->invertedAppearance; + bool reverse = (!vertical && (pb->direction == Qt::RightToLeft)); + if (inverted) + reverse = !reverse; + switch (d->aquaSizeConstrain(opt, w)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + tdi.kind = !isIndeterminate ? kThemeLargeProgressBar + : kThemeLargeIndeterminateBar; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + tdi.kind = !isIndeterminate ? kThemeProgressBar : kThemeIndeterminateBar; + break; + } + tdi.bounds = qt_hirectForQRect(pb->rect); + tdi.max = pb->maximum; + tdi.min = pb->minimum; + tdi.value = pb->progress; + tdi.attributes = vertical ? 0 : kThemeTrackHorizontal; + + if (isIndeterminate) { + if (QProgressStyleAnimation *animation = qobject_cast<QProgressStyleAnimation*>(d->animation(opt->styleObject))) + tdi.trackInfo.progress.phase = animation->animationStep(); + else if (opt->styleObject) + d->startAnimation(new QProgressStyleAnimation(d->animateSpeed(QMacStylePrivate::AquaProgressBar), opt->styleObject)); + } else { + d->stopAnimation(opt->styleObject); + } + if (!(pb->state & State_Active)) + tdi.enableState = kThemeTrackInactive; + else if (!(pb->state & State_Enabled)) + tdi.enableState = kThemeTrackDisabled; + else + tdi.enableState = kThemeTrackActive; + HIThemeOrientation drawOrientation = kHIThemeOrientationNormal; + if (reverse) { + if (vertical) { + drawOrientation = kHIThemeOrientationInverted; + } else { + CGContextSaveGState(cg); + CGContextTranslateCTM(cg, pb->rect.width(), 0); + CGContextScaleCTM(cg, -1, 1); + } + } + HIThemeDrawTrack(&tdi, 0, cg, drawOrientation); + if (reverse && !vertical) + CGContextRestoreGState(cg); + } + break; + case CE_ProgressBarLabel: + case CE_ProgressBarGroove: + break; + case CE_SizeGrip: { + if (w && w->testAttribute(Qt::WA_MacOpaqueSizeGrip)) { + HIThemeGrowBoxDrawInfo gdi; + gdi.version = qt_mac_hitheme_version; + gdi.state = tds; + gdi.kind = kHIThemeGrowBoxKindNormal; + gdi.direction = kThemeGrowRight | kThemeGrowDown; + gdi.size = kHIThemeGrowBoxSizeNormal; + HIPoint pt = CGPointMake(opt->rect.x(), opt->rect.y()); + HIThemeDrawGrowBox(&pt, &gdi, cg, kHIThemeOrientationNormal); + } else { + // It isn't possible to draw a transparent size grip with the + // native API, so we do it ourselves here. + const bool metal = qt_mac_is_metal(w); + QPen lineColor = metal ? QColor(236, 236, 236) : QColor(82, 82, 82, 192); + QPen metalHighlight = QColor(5, 5, 5, 192); + lineColor.setWidth(1); + p->save(); + p->setRenderHint(QPainter::Antialiasing); + p->setPen(lineColor); + const Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : qApp->layoutDirection(); + const int NumLines = metal ? 4 : 3; + for (int l = 0; l < NumLines; ++l) { + const int offset = (l * 4 + (metal ? 2 : 3)); + QPoint start, end; + if (layoutDirection == Qt::LeftToRight) { + start = QPoint(opt->rect.width() - offset, opt->rect.height() - 1); + end = QPoint(opt->rect.width() - 1, opt->rect.height() - offset); + } else { + start = QPoint(offset, opt->rect.height() - 1); + end = QPoint(1, opt->rect.height() - offset); + } + p->drawLine(start, end); + if (metal) { + p->setPen(metalHighlight); + p->setRenderHint(QPainter::Antialiasing, false); + p->drawLine(start + QPoint(0, -1), end + QPoint(0, -1)); + p->setRenderHint(QPainter::Antialiasing, true); + p->setPen(lineColor); + } + } + p->restore(); + } + break; + } + case CE_Splitter: + if (opt->rect.width() > 1 && opt->rect.height() > 1){ + HIThemeSplitterDrawInfo sdi; + sdi.version = qt_mac_hitheme_version; + sdi.state = tds; + sdi.adornment = kHIThemeSplitterAdornmentMetal; + HIRect hirect = qt_hirectForQRect(opt->rect); + HIThemeDrawPaneSplitter(&hirect, &sdi, cg, kHIThemeOrientationNormal); + } else { + QPen oldPen = p->pen(); + p->setPen(opt->palette.dark().color()); + if (opt->state & QStyle::State_Horizontal) + p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); + else + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(oldPen); + } + break; + case CE_RubberBand: + if (const QStyleOptionRubberBand *rubber = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + QColor fillColor(opt->palette.color(QPalette::Disabled, QPalette::Highlight)); + if (!rubber->opaque) { + QColor strokeColor; + // I retrieved these colors from the Carbon-Dev mailing list + strokeColor.setHsvF(0, 0, 0.86, 1.0); + fillColor.setHsvF(0, 0, 0.53, 0.25); + if (opt->rect.width() * opt->rect.height() <= 3) { + p->fillRect(opt->rect, strokeColor); + } else { + QPen oldPen = p->pen(); + QBrush oldBrush = p->brush(); + QPen pen(strokeColor); + p->setPen(pen); + p->setBrush(fillColor); + QRect adjusted = opt->rect.adjusted(1, 1, -1, -1); + if (adjusted.isValid()) + p->drawRect(adjusted); + p->setPen(oldPen); + p->setBrush(oldBrush); + } + } else { + p->fillRect(opt->rect, fillColor); + } + } + break; +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: { + const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(opt); + + // Unified title and toolbar drawing. In this mode the cocoa platform plugin will + // fill the top toolbar area part with a background gradient that "unifies" with + // the title bar. The following code fills the toolBar area with transparent pixels + // to make that gradient visible. + if (w) { +#ifndef QT_NO_MAINWINDOW + if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(w->window())) { + if (toolBar && toolBar->toolBarArea == Qt::TopToolBarArea && mainWindow->unifiedTitleAndToolBarOnMac()) { + + // fill with transparent pixels. + p->save(); + p->setCompositionMode(QPainter::CompositionMode_Source); + p->fillRect(opt->rect, Qt::transparent); + p->restore(); + + // Drow a horizontal sepearator line at the toolBar bottom if the "unified" area ends here. + // There might be additional toolbars or other widgets such as tab bars in document + // mode below. Determine this by making a unified toolbar area test for the row below + // this toolbar. + QPoint windowToolbarEnd = w->mapTo(w->window(), opt->rect.bottomLeft()); + bool isEndOfUnifiedArea = !isInMacUnifiedToolbarArea(w->window()->windowHandle(), windowToolbarEnd.y() + 1); + if (isEndOfUnifiedArea) { + SInt32 margin; + GetThemeMetric(kThemeMetricSeparatorSize, &margin); + CGRect separatorRect = CGRectMake(opt->rect.left(), opt->rect.bottom(), opt->rect.width(), margin); + HIThemeSeparatorDrawInfo separatorDrawInfo; + separatorDrawInfo.version = 0; + separatorDrawInfo.state = qt_macWindowMainWindow(mainWindow) ? kThemeStateActive : kThemeStateInactive; + QMacCGContext cg(p); + HIThemeDrawSeparator(&separatorRect, &separatorDrawInfo, cg, kHIThemeOrientationNormal); + } + break; + } + } +#endif + } + + // draw background gradient + QLinearGradient linearGrad; + if (opt->state & State_Horizontal) + linearGrad = QLinearGradient(0, opt->rect.top(), 0, opt->rect.bottom()); + else + linearGrad = QLinearGradient(opt->rect.left(), 0, opt->rect.right(), 0); + + linearGrad.setColorAt(0, mainWindowGradientBegin); + linearGrad.setColorAt(1, mainWindowGradientEnd); + p->fillRect(opt->rect, linearGrad); + + p->save(); + if (opt->state & State_Horizontal) { + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); + + } else { + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.topRight(), opt->rect.bottomRight()); + } + p->restore(); + + + } break; +#endif + default: + QCommonStyle::drawControl(ce, opt, p, w); + break; + } +} + +static void setLayoutItemMargins(int left, int top, int right, int bottom, QRect *rect, Qt::LayoutDirection dir) +{ + if (dir == Qt::RightToLeft) { + rect->adjust(-right, top, -left, bottom); + } else { + rect->adjust(left, top, right, bottom); + } +} + +QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt, + const QWidget *widget) const +{ + Q_D(const QMacStyle); + QRect rect; + int controlSize = getControlSize(opt, widget); + + switch (sr) { + case SE_ItemViewItemText: + if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { + int fw = proxy()->pixelMetric(PM_FocusFrameHMargin, opt, widget); + // We add the focusframeargin between icon and text in commonstyle + rect = QCommonStyle::subElementRect(sr, opt, widget); + if (vopt->features & QStyleOptionViewItem::HasDecoration) + rect.adjust(-fw, 0, 0, 0); + } + break; + case SE_ToolBoxTabContents: + rect = QCommonStyle::subElementRect(sr, opt, widget); + break; + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + // Unlike Carbon, we want the button to always be drawn inside its bounds. + // Therefore, the button is a bit smaller, so that even if it got focus, + // the focus 'shadow' will be inside. Adjust the content rect likewise. + HIThemeButtonDrawInfo bdi; + d->initHIThemePushButton(btn, widget, d->getDrawState(opt->state), &bdi); + HIRect contentRect = d->pushButtonContentBounds(btn, &bdi); + rect = qt_qrectForHIRect(contentRect); + } + break; + case SE_HeaderLabel: { + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); + rect.setRect(opt->rect.x() + margin, opt->rect.y(), + opt->rect.width() - margin * 2, opt->rect.height() - 2); + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // Subtract width needed for arrow, if there is one + if (header->sortIndicator != QStyleOptionHeader::None) { + if (opt->state & State_Horizontal) + rect.setWidth(rect.width() - (opt->rect.height() / 2) - (margin * 2)); + else + rect.setHeight(rect.height() - (opt->rect.width() / 2) - (margin * 2)); + } + } + rect = visualRect(opt->direction, opt->rect, rect); + break; + } + case SE_ProgressBarGroove: + // Wrong in the secondary dimension, but accurate enough in the main dimension. + rect = opt->rect; + break; + case SE_ProgressBarLabel: + break; + case SE_ProgressBarContents: + rect = opt->rect; + break; + case SE_TreeViewDisclosureItem: { + HIRect inRect = CGRectMake(opt->rect.x(), opt->rect.y(), + opt->rect.width(), opt->rect.height()); + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeStateActive; + bdi.kind = kThemeDisclosureButton; + bdi.value = kThemeDisclosureRight; + bdi.adornment = kThemeAdornmentNone; + HIRect contentRect; + HIThemeGetButtonContentBounds(&inRect, &bdi, &contentRect); + QCFType<HIShapeRef> shape; + HIRect outRect; + HIThemeGetButtonShape(&inRect, &bdi, &shape); + ptrHIShapeGetBounds(shape, &outRect); + rect = QRect(int(outRect.origin.x + DisclosureOffset), int(outRect.origin.y), + int(contentRect.origin.x - outRect.origin.x + DisclosureOffset), + int(outRect.size.height)); + break; + } +#ifndef QT_NO_TABWIDGET + case SE_TabWidgetLeftCorner: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect = QRect(QPoint(0, 0), twf->leftCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect = QRect(QPoint(0, twf->rect.height() - twf->leftCornerWidgetSize.height()), + twf->leftCornerWidgetSize); + break; + default: + break; + } + rect = visualRect(twf->direction, twf->rect, rect); + } + break; + case SE_TabWidgetRightCorner: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), 0), + twf->rightCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), + twf->rect.height() - twf->rightCornerWidgetSize.height()), + twf->rightCornerWidgetSize); + break; + default: + break; + } + rect = visualRect(twf->direction, twf->rect, rect); + } + break; + case SE_TabWidgetTabContents: + rect = QCommonStyle::subElementRect(sr, opt, widget); + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + if (twf->lineWidth != 0) { + switch (getTabDirection(twf->shape)) { + case kThemeTabNorth: + rect.adjust(+1, +14, -1, -1); + break; + case kThemeTabSouth: + rect.adjust(+1, +1, -1, -14); + break; + case kThemeTabWest: + rect.adjust(+14, +1, -1, -1); + break; + case kThemeTabEast: + rect.adjust(+1, +1, -14, -1); + } + } + } + break; + case SE_TabBarTabText: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + d->tabLayout(tab, widget, &rect); + } + break; + case SE_TabBarTabLeftButton: + case SE_TabBarTabRightButton: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + bool selected = tab->state & State_Selected; + int verticalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, tab, widget); + int horizontalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, tab, widget); + int hpadding = 5; + + bool verticalTabs = tab->shape == QTabBar::RoundedEast + || tab->shape == QTabBar::RoundedWest + || tab->shape == QTabBar::TriangularEast + || tab->shape == QTabBar::TriangularWest; + + QRect tr = tab->rect; + if (tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::TriangularSouth) + verticalShift = -verticalShift; + if (verticalTabs) { + qSwap(horizontalShift, verticalShift); + horizontalShift *= -1; + verticalShift *= -1; + } + if (tab->shape == QTabBar::RoundedWest || tab->shape == QTabBar::TriangularWest) + horizontalShift = -horizontalShift; + + tr.adjust(0, 0, horizontalShift, verticalShift); + if (selected) + { + tr.setBottom(tr.bottom() - verticalShift); + tr.setRight(tr.right() - horizontalShift); + } + + QSize size = (sr == SE_TabBarTabLeftButton) ? tab->leftButtonSize : tab->rightButtonSize; + int w = size.width(); + int h = size.height(); + int midHeight = static_cast<int>(qCeil(float(tr.height() - h) / 2)); + int midWidth = ((tr.width() - w) / 2); + + bool atTheTop = true; + switch (tab->shape) { + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + atTheTop = (sr == SE_TabBarTabLeftButton); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + atTheTop = (sr == SE_TabBarTabRightButton); + break; + default: + if (sr == SE_TabBarTabLeftButton) + rect = QRect(tab->rect.x() + hpadding, midHeight, w, h); + else + rect = QRect(tab->rect.right() - w - hpadding, midHeight, w, h); + rect = visualRect(tab->direction, tab->rect, rect); + } + if (verticalTabs) { + if (atTheTop) + rect = QRect(midWidth, tr.y() + tab->rect.height() - hpadding - h, w, h); + else + rect = QRect(midWidth, tr.y() + hpadding, w, h); + } + } + break; +#endif + case SE_LineEditContents: + rect = QCommonStyle::subElementRect(sr, opt, widget); +#ifndef QT_NO_COMBOBOX + if (widget && qobject_cast<const QComboBox*>(widget->parentWidget())) + rect.adjust(-1, -2, 0, 0); + else +#endif + rect.adjust(-1, -1, 0, +1); + break; + case SE_CheckBoxLayoutItem: + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + setLayoutItemMargins(+2, +3, -9, -4, &rect, opt->direction); + } else if (controlSize == QAquaSizeSmall) { + setLayoutItemMargins(+1, +5, 0 /* fix */, -6, &rect, opt->direction); + } else { + setLayoutItemMargins(0, +7, 0 /* fix */, -6, &rect, opt->direction); + } + break; + case SE_ComboBoxLayoutItem: +#ifndef QT_NO_TOOLBAR + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) { + // Do nothing, because QToolbar needs the entire widget rect. + // Otherwise it will be clipped. Equivalent to + // widget->setAttribute(Qt::WA_LayoutUsesWidgetRect), but without + // all the hassle. + } else +#endif + { + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + rect.adjust(+3, +2, -3, -4); + } else if (controlSize == QAquaSizeSmall) { + setLayoutItemMargins(+2, +1, -3, -4, &rect, opt->direction); + } else { + setLayoutItemMargins(+1, 0, -2, 0, &rect, opt->direction); + } + } + break; + case SE_LabelLayoutItem: + rect = opt->rect; + setLayoutItemMargins(+1, 0 /* SHOULD be -1, done for alignment */, 0, 0 /* SHOULD be -1, done for alignment */, &rect, opt->direction); + break; + case SE_ProgressBarLayoutItem: { + rect = opt->rect; + int bottom = SIZE(3, 8, 8); + if (opt->state & State_Horizontal) { + rect.adjust(0, +1, 0, -bottom); + } else { + setLayoutItemMargins(+1, 0, -bottom, 0, &rect, opt->direction); + } + break; + } + case SE_PushButtonLayoutItem: + if (const QStyleOptionButton *buttonOpt + = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if ((buttonOpt->features & QStyleOptionButton::Flat)) + break; // leave rect alone + } + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + rect.adjust(+6, +4, -6, -8); + } else if (controlSize == QAquaSizeSmall) { + rect.adjust(+5, +4, -5, -6); + } else { + rect.adjust(+1, 0, -1, -2); + } + break; + case SE_RadioButtonLayoutItem: + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + setLayoutItemMargins(+2, +2 /* SHOULD BE +3, done for alignment */, + 0, -4 /* SHOULD BE -3, done for alignment */, &rect, opt->direction); + } else if (controlSize == QAquaSizeSmall) { + rect.adjust(0, +6, 0 /* fix */, -5); + } else { + rect.adjust(0, +6, 0 /* fix */, -7); + } + break; + case SE_SliderLayoutItem: + if (const QStyleOptionSlider *sliderOpt + = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + rect = opt->rect; + if (sliderOpt->tickPosition == QSlider::NoTicks) { + int above = SIZE(3, 0, 2); + int below = SIZE(4, 3, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.adjust(0, +above, 0, -below); + } else { + rect.adjust(+above, 0, -below, 0); //### Seems that QSlider flip the position of the ticks in reverse mode. + } + } else if (sliderOpt->tickPosition == QSlider::TicksAbove) { + int below = SIZE(3, 2, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.setHeight(rect.height() - below); + } else { + rect.setWidth(rect.width() - below); + } + } else if (sliderOpt->tickPosition == QSlider::TicksBelow) { + int above = SIZE(3, 2, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.setTop(rect.top() + above); + } else { + rect.setLeft(rect.left() + above); + } + } + } + break; + case SE_FrameLayoutItem: + // hack because QStyleOptionFrame doesn't have a frameStyle member + if (const QFrame *frame = qobject_cast<const QFrame *>(widget)) { + rect = opt->rect; + switch (frame->frameStyle() & QFrame::Shape_Mask) { + case QFrame::HLine: + rect.adjust(0, +1, 0, -1); + break; + case QFrame::VLine: + rect.adjust(+1, 0, -1, 0); + break; + default: + ; + } + } + break; + case SE_GroupBoxLayoutItem: + rect = opt->rect; + if (const QStyleOptionGroupBox *groupBoxOpt = + qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + /* + AHIG is very inconsistent when it comes to group boxes. + Basically, we make sure that (non-checkable) group boxes + and tab widgets look good when laid out side by side. + */ + if (groupBoxOpt->subControls & (QStyle::SC_GroupBoxCheckBox + | QStyle::SC_GroupBoxLabel)) { + int delta; + if (groupBoxOpt->subControls & QStyle::SC_GroupBoxCheckBox) { + delta = SIZE(8, 4, 4); // guess + } else { + delta = SIZE(15, 12, 12); // guess + } + rect.setTop(rect.top() + delta); + } + } + rect.setBottom(rect.bottom() - 1); + break; +#ifndef QT_NO_TABWIDGET + case SE_TabWidgetLayoutItem: + if (const QStyleOptionTabWidgetFrame *tabWidgetOpt = + qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + /* + AHIG specifies "12 or 14" as the distance from the window + edge. We choose 14 and since the default top margin is 20, + the overlap is 6. + */ + rect = tabWidgetOpt->rect; + if (tabWidgetOpt->shape == QTabBar::RoundedNorth) + rect.setTop(rect.top() + SIZE(6 /* AHIG */, 3 /* guess */, 2 /* AHIG */)); + } + break; +#endif +#ifndef QT_NO_DOCKWIDGET + case SE_DockWidgetCloseButton: + case SE_DockWidgetFloatButton: + case SE_DockWidgetTitleBarText: + case SE_DockWidgetIcon: { + int iconSize = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + int buttonMargin = proxy()->pixelMetric(PM_DockWidgetTitleBarButtonMargin, opt, widget); + QRect srect = opt->rect; + + const QStyleOptionDockWidget *dwOpt + = qstyleoption_cast<const QStyleOptionDockWidget*>(opt); + bool canClose = dwOpt == 0 ? true : dwOpt->closable; + bool canFloat = dwOpt == 0 ? false : dwOpt->floatable; + + const bool verticalTitleBar = dwOpt->verticalTitleBar; + + // If this is a vertical titlebar, we transpose and work as if it was + // horizontal, then transpose again. + if (verticalTitleBar) + srect = srect.transposed(); + + do { + int right = srect.right(); + int left = srect.left(); + + QRect closeRect; + if (canClose) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, + opt, widget).actualSize(QSize(iconSize, iconSize)); + sz += QSize(buttonMargin, buttonMargin); + if (verticalTitleBar) + sz = sz.transposed(); + closeRect = QRect(left, + srect.center().y() - sz.height()/2, + sz.width(), sz.height()); + left = closeRect.right() + 1; + } + if (sr == SE_DockWidgetCloseButton) { + rect = closeRect; + break; + } + + QRect floatRect; + if (canFloat) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarNormalButton, + opt, widget).actualSize(QSize(iconSize, iconSize)); + sz += QSize(buttonMargin, buttonMargin); + if (verticalTitleBar) + sz = sz.transposed(); + floatRect = QRect(left, + srect.center().y() - sz.height()/2, + sz.width(), sz.height()); + left = floatRect.right() + 1; + } + if (sr == SE_DockWidgetFloatButton) { + rect = floatRect; + break; + } + + QRect iconRect; + if (const QDockWidget *dw = qobject_cast<const QDockWidget*>(widget)) { + QIcon icon; + if (dw->isFloating()) + icon = dw->windowIcon(); + if (!icon.isNull() + && icon.cacheKey() != QApplication::windowIcon().cacheKey()) { + QSize sz = icon.actualSize(QSize(rect.height(), rect.height())); + if (verticalTitleBar) + sz = sz.transposed(); + iconRect = QRect(right - sz.width(), srect.center().y() - sz.height()/2, + sz.width(), sz.height()); + right = iconRect.left() - 1; + } + } + if (sr == SE_DockWidgetIcon) { + rect = iconRect; + break; + } + + QRect textRect = QRect(left, srect.top(), + right - left, srect.height()); + if (sr == SE_DockWidgetTitleBarText) { + rect = textRect; + break; + } + } while (false); + + if (verticalTitleBar) { + rect = QRect(srect.left() + rect.top() - srect.top(), + srect.top() + srect.right() - rect.right(), + rect.height(), rect.width()); + } else { + rect = visualRect(opt->direction, srect, rect); + } + break; + } +#endif + default: + rect = QCommonStyle::subElementRect(sr, opt, widget); + break; + } + return rect; +} + +static inline void drawToolbarButtonArrow(const QRect &toolButtonRect, ThemeDrawState tds, CGContextRef cg) +{ + QRect arrowRect = QRect(toolButtonRect.right() - 9, toolButtonRect.bottom() - 9, 7, 5); + HIThemePopupArrowDrawInfo padi; + padi.version = qt_mac_hitheme_version; + padi.state = tds; + padi.orientation = kThemeArrowDown; + padi.size = kThemeArrow7pt; + HIRect hirect = qt_hirectForQRect(arrowRect); + HIThemeDrawPopupArrow(&hirect, &padi, cg, kHIThemeOrientationNormal); +} + +void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *widget) const +{ + Q_D(const QMacStyle); + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + QWindow *window = widget && widget->window() ? widget->window()->windowHandle() : + QStyleHelper::styleObjectWindow(opt->styleObject); + const_cast<QMacStylePrivate *>(d)->resolveCurrentNSView(window); + switch (cc) { + case CC_Slider: + case CC_ScrollBar: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + if (slider->state & State_Sunken) { + if (cc == CC_Slider) { + if (slider->activeSubControls == SC_SliderHandle) + tdi.trackInfo.slider.pressState = kThemeThumbPressed; + else if (slider->activeSubControls == SC_SliderGroove) + tdi.trackInfo.slider.pressState = kThemeLeftTrackPressed; + } else { + if (slider->activeSubControls == SC_ScrollBarSubLine + || slider->activeSubControls == SC_ScrollBarAddLine) { + // This test looks complex but it basically boils down + // to the following: The "RTL look" on the mac also + // changed the directions of the controls, that's not + // what people expect (an arrow is an arrow), so we + // kind of fake and say the opposite button is hit. + // This works great, up until 10.4 which broke the + // scroll bars, so I also have actually do something + // similar when I have an upside down scroll bar + // because on Tiger I only "fake" the reverse stuff. + bool reverseHorizontal = (slider->direction == Qt::RightToLeft + && slider->orientation == Qt::Horizontal); + + if ((reverseHorizontal + && slider->activeSubControls == SC_ScrollBarAddLine) + || (!reverseHorizontal + && slider->activeSubControls == SC_ScrollBarSubLine)) { + tdi.trackInfo.scrollbar.pressState = kThemeRightInsideArrowPressed + | kThemeLeftOutsideArrowPressed; + } else { + tdi.trackInfo.scrollbar.pressState = kThemeLeftInsideArrowPressed + | kThemeRightOutsideArrowPressed; + } + } else if (slider->activeSubControls == SC_ScrollBarAddPage) { + tdi.trackInfo.scrollbar.pressState = kThemeRightTrackPressed; + } else if (slider->activeSubControls == SC_ScrollBarSubPage) { + tdi.trackInfo.scrollbar.pressState = kThemeLeftTrackPressed; + } else if (slider->activeSubControls == SC_ScrollBarSlider) { + tdi.trackInfo.scrollbar.pressState = kThemeThumbPressed; + } + } + } + HIRect macRect; + bool tracking = slider->sliderPosition == slider->sliderValue; + if (!tracking) { + // Small optimization, the same as q->subControlRect + QCFType<HIShapeRef> shape; + HIThemeGetTrackThumbShape(&tdi, &shape); + ptrHIShapeGetBounds(shape, &macRect); + tdi.value = slider->sliderValue; + } + + // Remove controls from the scroll bar if it is to short to draw them correctly. + // This is done in two stages: first the thumb indicator is removed when it is + // no longer possible to move it, second the up/down buttons are removed when + // there is not enough space for them. + if (cc == CC_ScrollBar) { + if (opt && opt->styleObject && !QMacStylePrivate::scrollBars.contains(opt->styleObject)) + QMacStylePrivate::scrollBars.append(QPointer<QObject>(opt->styleObject)); + const int scrollBarLength = (slider->orientation == Qt::Horizontal) + ? slider->rect.width() : slider->rect.height(); + const QStyleHelper::WidgetSizePolicy sizePolicy = QStyleHelper::widgetSizePolicy(widget, opt); + if (scrollBarLength < scrollButtonsCutoffSize(thumbIndicatorCutoff, sizePolicy)) + tdi.attributes &= ~kThemeTrackShowThumb; + if (scrollBarLength < scrollButtonsCutoffSize(scrollButtonsCutoff, sizePolicy)) + tdi.enableState = kThemeTrackNothingToScroll; + } else { + if (!(slider->subControls & SC_SliderHandle)) + tdi.attributes &= ~kThemeTrackShowThumb; + if (!(slider->subControls & SC_SliderGroove)) + tdi.attributes |= kThemeTrackHideTrack; + } + + const bool isHorizontal = slider->orientation == Qt::Horizontal; + + if (cc == CC_ScrollBar && proxy()->styleHint(SH_ScrollBar_Transient, opt, widget)) { + bool wasActive = false; + CGFloat opacity = 0.0; + CGFloat expandScale = 1.0; + CGFloat expandOffset = -1.0; + bool shouldExpand = false; + const CGFloat maxExpandScale = tdi.kind == kThemeSmallScrollBar ? 11.0 / 7.0 : 13.0 / 9.0; + + if (QObject *styleObject = opt->styleObject) { + int oldPos = styleObject->property("_q_stylepos").toInt(); + int oldMin = styleObject->property("_q_stylemin").toInt(); + int oldMax = styleObject->property("_q_stylemax").toInt(); + QRect oldRect = styleObject->property("_q_stylerect").toRect(); + QStyle::State oldState = static_cast<QStyle::State>(styleObject->property("_q_stylestate").value<QStyle::State::Int>()); + uint oldActiveControls = styleObject->property("_q_stylecontrols").toUInt(); + + // a scrollbar is transient when the scrollbar itself and + // its sibling are both inactive (ie. not pressed/hovered/moved) + bool transient = !opt->activeSubControls && !(slider->state & State_On); + + if (!transient || + oldPos != slider->sliderPosition || + oldMin != slider->minimum || + oldMax != slider->maximum || + oldRect != slider->rect || + oldState != slider->state || + oldActiveControls != slider->activeSubControls) { + + // if the scrollbar is transient or its attributes, geometry or + // state has changed, the opacity is reset back to 100% opaque + opacity = 1.0; + + styleObject->setProperty("_q_stylepos", slider->sliderPosition); + styleObject->setProperty("_q_stylemin", slider->minimum); + styleObject->setProperty("_q_stylemax", slider->maximum); + styleObject->setProperty("_q_stylerect", slider->rect); + styleObject->setProperty("_q_stylestate", static_cast<QStyle::State::Int>(slider->state)); + styleObject->setProperty("_q_stylecontrols", static_cast<uint>(slider->activeSubControls)); + + QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject)); + if (transient) { + if (!anim) { + anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Deactivating, styleObject); + d->startAnimation(anim); + } else if (anim->mode() == QScrollbarStyleAnimation::Deactivating) { + // the scrollbar was already fading out while the + // state changed -> restart the fade out animation + anim->setCurrentTime(0); + } + } else if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { + d->stopAnimation(styleObject); + } + } + + QScrollbarStyleAnimation *anim = qobject_cast<QScrollbarStyleAnimation *>(d->animation(styleObject)); + if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { + // once a scrollbar was active (hovered/pressed), it retains + // the active look even if it's no longer active while fading out + if (oldActiveControls) + anim->setActive(true); + + wasActive = anim->wasActive(); + opacity = anim->currentValue(); + } + + shouldExpand = (opt->activeSubControls || wasActive); + if (shouldExpand) { + if (!anim && !oldActiveControls) { + // Start expand animation only once and when entering + anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Activating, styleObject); + d->startAnimation(anim); + } + if (anim && anim->mode() == QScrollbarStyleAnimation::Activating) { + expandScale = 1.0 + (maxExpandScale - 1.0) * anim->currentValue(); + expandOffset = 5.5 * anim->currentValue() - 1; + } else { + // Keep expanded state after the animation ends, and when fading out + expandScale = maxExpandScale; + expandOffset = 4.5; + } + } + } + + CGContextSaveGState(cg); + [NSGraphicsContext saveGraphicsState]; + + [NSGraphicsContext setCurrentContext:[NSGraphicsContext + graphicsContextWithGraphicsPort:(CGContextRef)cg flipped:NO]]; + NSScroller *scroller = reinterpret_cast<NSScroller*>(d->nsscroller); + [scroller initWithFrame:NSMakeRect(0, 0, slider->rect.width(), slider->rect.height())]; + // mac os behaviour: as soon as one color channel is >= 128, + // the bg is considered bright, scroller is dark + const QColor bgColor = QStyleHelper::backgroundColor(opt->palette, widget); + const bool isDarkBg = bgColor.red() < 128 && bgColor.green() < 128 && + bgColor.blue() < 128; + if (isDarkBg) + [scroller setKnobStyle:NSScrollerKnobStyleLight]; + else + [scroller setKnobStyle:NSScrollerKnobStyleDefault]; + + [scroller setControlSize:(tdi.kind == kThemeSmallScrollBar ? NSMiniControlSize + : NSRegularControlSize)]; + [scroller setBounds:NSMakeRect(0, 0, slider->rect.width(), slider->rect.height())]; + [scroller setScrollerStyle:NSScrollerStyleOverlay]; + + CGContextBeginTransparencyLayer(cg, NULL); + CGContextSetAlpha(cg, opacity); + + // Draw the track when hovering + if (opt->activeSubControls || wasActive) { + NSRect rect = [scroller bounds]; + if (shouldExpand) { + if (isHorizontal) + rect.origin.y += 4.5 - expandOffset; + else + rect.origin.x += 4.5 - expandOffset; + } + [scroller drawKnobSlotInRect:rect highlight:YES]; + } + + const qreal length = slider->maximum - slider->minimum + slider->pageStep; + const qreal proportion = slider->pageStep / length; + qreal value = (slider->sliderValue - slider->minimum) / length; + if (isHorizontal && slider->direction == Qt::RightToLeft) + value = 1.0 - value - proportion; + + [scroller setKnobProportion:1.0]; + + const int minKnobWidth = 26; + + if (isHorizontal) { + const qreal plannedWidth = proportion * slider->rect.width(); + const qreal width = qMax<qreal>(minKnobWidth, plannedWidth); + const qreal totalWidth = slider->rect.width() + plannedWidth - width; + [scroller setFrame:NSMakeRect(0, 0, width, slider->rect.height())]; + if (shouldExpand) { + CGContextScaleCTM(cg, 1, expandScale); + CGContextTranslateCTM(cg, value * totalWidth, -expandOffset); + } else { + CGContextTranslateCTM(cg, value * totalWidth, 1); + } + } else { + const qreal plannedHeight = proportion * slider->rect.height(); + const qreal height = qMax<qreal>(minKnobWidth, plannedHeight); + const qreal totalHeight = slider->rect.height() + plannedHeight - height; + [scroller setFrame:NSMakeRect(0, 0, slider->rect.width(), height)]; + if (shouldExpand) { + CGContextScaleCTM(cg, expandScale, 1); + CGContextTranslateCTM(cg, -expandOffset, value * totalHeight); + } else { + CGContextTranslateCTM(cg, 1, value * totalHeight); + } + } + if (length > 0.0) { + [scroller layout]; + [scroller drawKnob]; + } + + CGContextEndTransparencyLayer(cg); + + [NSGraphicsContext restoreGraphicsState]; + CGContextRestoreGState(cg); + } else { + d->stopAnimation(opt->styleObject); + + if (cc == CC_Slider) { + // Fix min and max positions. (See also getSliderInfo() + // for the slider values adjustments.) + // HITheme seems to have forgotten how to render + // a slide at those positions, leaving a gap between + // the knob and the ends of the track. + // We fix this by rendering the track first, and then + // the knob on top. However, in order to not clip the + // knob, we reduce the the drawing rect for the track. + HIRect bounds = tdi.bounds; + if (isHorizontal) { + tdi.bounds.size.width -= 2; + tdi.bounds.origin.x += 1; + if (tdi.trackInfo.slider.thumbDir == kThemeThumbDownward) + tdi.bounds.origin.y -= 2; + else if (tdi.trackInfo.slider.thumbDir == kThemeThumbUpward) + tdi.bounds.origin.y += 3; + } else { + tdi.bounds.size.height -= 2; + tdi.bounds.origin.y += 1; + if (tdi.trackInfo.slider.thumbDir == kThemeThumbDownward) // pointing right + tdi.bounds.origin.x -= 4; + else if (tdi.trackInfo.slider.thumbDir == kThemeThumbUpward) // pointing left + tdi.bounds.origin.x += 2; + } + + // Yosemite demands its blue progress track when no tickmarks are present + if (!(slider->subControls & SC_SliderTickmarks)) { + QCocoaWidgetKind sliderKind = slider->orientation == Qt::Horizontal ? QCocoaHorizontalSlider : QCocoaVerticalSlider; + QCocoaWidget cw = QCocoaWidget(sliderKind, QAquaSizeLarge); + NSSlider *sl = (NSSlider *)d->cocoaControl(cw); + sl.minValue = slider->minimum; + sl.maxValue = slider->maximum; + sl.intValue = slider->sliderValue; + sl.enabled = slider->state & QStyle::State_Enabled; + d->drawNSViewInRect(cw, sl, opt->rect, p, widget != 0, ^(NSRect rect, CGContextRef ctx) { + if (slider->upsideDown) { + if (isHorizontal) { + CGContextTranslateCTM(ctx, rect.size.width, 0); + CGContextScaleCTM(ctx, -1, 1); + } + } else if (!isHorizontal) { + CGContextTranslateCTM(ctx, 0, rect.size.height); + CGContextScaleCTM(ctx, 1, -1); + } + [sl.cell drawBarInside:NSRectFromCGRect(tdi.bounds) flipped:NO]; + // No need to restore the CTM later, the context has been saved + // and will be restored at the end of drawNSViewInRect() + }); + tdi.attributes |= kThemeTrackHideTrack; + } else { + tdi.attributes &= ~(kThemeTrackShowThumb | kThemeTrackHasFocus); + HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg, + kHIThemeOrientationNormal); + tdi.attributes |= kThemeTrackHideTrack | kThemeTrackShowThumb; + } + + tdi.bounds = bounds; + } + + if (cc == CC_Slider && slider->subControls & SC_SliderTickmarks) { + + HIRect bounds; + // As part of fixing the min and max positions, + // we need to adjust the tickmarks as well + bounds = tdi.bounds; + if (slider->orientation == Qt::Horizontal) { + tdi.bounds.size.width += 2; + tdi.bounds.origin.x -= 1; + if (tdi.trackInfo.slider.thumbDir == kThemeThumbUpward) + tdi.bounds.origin.y -= 2; + } else { + tdi.bounds.size.height += 3; + tdi.bounds.origin.y -= 3; + tdi.bounds.origin.y += 1; + if (tdi.trackInfo.slider.thumbDir == kThemeThumbUpward) // pointing left + tdi.bounds.origin.x -= 2; + } + + if (qt_mac_is_metal(widget)) { + if (tdi.enableState == kThemeTrackInactive) + tdi.enableState = kThemeTrackActive; // Looks more Cocoa-like + } + int interval = slider->tickInterval; + if (interval == 0) { + interval = slider->pageStep; + if (interval == 0) + interval = slider->singleStep; + if (interval == 0) + interval = 1; + } + int numMarks = 1 + ((slider->maximum - slider->minimum) / interval); + + if (tdi.trackInfo.slider.thumbDir == kThemeThumbPlain) { + // They asked for both, so we'll give it to them. + tdi.trackInfo.slider.thumbDir = kThemeThumbDownward; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + tdi.trackInfo.slider.thumbDir = kThemeThumbUpward; + // 10.10 and above need a slight shift + if (slider->orientation == Qt::Vertical) + tdi.bounds.origin.x -= 2; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + // Reset to plain thumb to be drawn further down + tdi.trackInfo.slider.thumbDir = kThemeThumbPlain; + } else { + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + } + + tdi.bounds = bounds; + } + + if (cc == CC_Slider) { + // Still as part of fixing the min and max positions, + // we also adjust the knob position. We can do this + // because it's rendered separately from the track. + if (slider->orientation == Qt::Vertical) { + if (tdi.trackInfo.slider.thumbDir == kThemeThumbDownward) // pointing right + tdi.bounds.origin.x -= 2; + } + } + + HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg, + kHIThemeOrientationNormal); + } + } + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox newSB = *sb; + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + SInt32 frame_size; + GetThemeMetric(kThemeMetricEditTextFrameOutset, &frame_size); + + QRect lineeditRect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxEditField, widget); + lineeditRect.adjust(-frame_size, -frame_size, +frame_size, +frame_size); + + HIThemeFrameDrawInfo fdi; + fdi.version = qt_mac_hitheme_version; + fdi.state = tds == kThemeStateInactive ? kThemeStateActive : tds; + fdi.kind = kHIThemeFrameTextFieldSquare; + fdi.isFocused = false; + HIRect hirect = qt_hirectForQRect(lineeditRect); + HIThemeDrawFrame(&hirect, &fdi, cg, kHIThemeOrientationNormal); + } + if (sb->subControls & (SC_SpinBoxUp | SC_SpinBoxDown)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + QAquaWidgetSize aquaSize = d->aquaSizeConstrain(opt, widget); + switch (aquaSize) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + bdi.kind = kThemeIncDecButton; + break; + case QAquaSizeMini: + bdi.kind = kThemeIncDecButtonMini; + break; + case QAquaSizeSmall: + bdi.kind = kThemeIncDecButtonSmall; + break; + } + if (!(sb->stepEnabled & (QAbstractSpinBox::StepUpEnabled + | QAbstractSpinBox::StepDownEnabled))) + tds = kThemeStateUnavailable; + if (sb->activeSubControls == SC_SpinBoxDown + && (sb->state & State_Sunken)) + tds = kThemeStatePressedDown; + else if (sb->activeSubControls == SC_SpinBoxUp + && (sb->state & State_Sunken)) + tds = kThemeStatePressedUp; + if (tds == kThemeStateInactive) + bdi.state = kThemeStateActive; + else + bdi.state = tds; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + + QRect updown = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget); + + updown |= proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); + HIRect newRect = qt_hirectForQRect(updown); + QRect off_rct; + HIRect outRect; + HIThemeGetButtonBackgroundBounds(&newRect, &bdi, &outRect); + off_rct.setRect(int(newRect.origin.x - outRect.origin.x), + int(newRect.origin.y - outRect.origin.y), + int(outRect.size.width - newRect.size.width), + int(outRect.size.height - newRect.size.height)); + + newRect = qt_hirectForQRect(updown, off_rct); + if (tds == kThemeStateInactive) + d->drawColorlessButton(newRect, &bdi, p, sb); + else + HIThemeDrawButton(&newRect, &bdi, cg, kHIThemeOrientationNormal, 0); + } + } + break; +#endif + case CC_ComboBox: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){ + HIThemeButtonDrawInfo bdi; + d->initComboboxBdi(combo, &bdi, widget, tds); + HIRect rect = qt_hirectForQRect(combo->rect); + if (combo->editable) + rect.origin.y += tds == kThemeStateInactive ? 1 : 2; + if (tds != kThemeStateInactive) + QMacStylePrivate::drawCombobox(rect, bdi, p); + else if (!widget && combo->editable) { + QCocoaWidget cw = cocoaWidgetFromHIThemeButtonKind(bdi.kind); + NSView *cb = d->cocoaControl(cw); + QRect r = combo->rect.adjusted(3, 0, 0, 0); + d->drawNSViewInRect(cw, cb, r, p, widget != 0); + } else + d->drawColorlessButton(rect, &bdi, p, opt); + } + break; + case CC_TitleBar: + if (const QStyleOptionTitleBar *titlebar + = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + if (titlebar->state & State_Active) { + if (titlebar->titleBarState & State_Active) + tds = kThemeStateActive; + else + tds = kThemeStateInactive; + } else { + tds = kThemeStateInactive; + } + + HIThemeWindowDrawInfo wdi; + wdi.version = qt_mac_hitheme_version; + wdi.state = tds; + wdi.windowType = QtWinType; + wdi.titleHeight = titlebar->rect.height(); + wdi.titleWidth = titlebar->rect.width(); + wdi.attributes = kThemeWindowHasTitleText; + // It seems HIThemeDrawTitleBarWidget is not able to draw a dirty + // close button, so use HIThemeDrawWindowFrame instead. + if (widget && widget->isWindowModified() && titlebar->subControls & SC_TitleBarCloseButton) + wdi.attributes |= kThemeWindowHasCloseBox | kThemeWindowHasDirty; + + HIRect titleBarRect; + HIRect tmpRect = qt_hirectForQRect(titlebar->rect); + { + QCFType<HIShapeRef> titleRegion; + QRect newr = titlebar->rect.adjusted(0, 0, 2, 0); + HIThemeGetWindowShape(&tmpRect, &wdi, kWindowTitleBarRgn, &titleRegion); + ptrHIShapeGetBounds(titleRegion, &tmpRect); + newr.translate(newr.x() - int(tmpRect.origin.x), newr.y() - int(tmpRect.origin.y)); + titleBarRect = qt_hirectForQRect(newr); + } + HIThemeDrawWindowFrame(&titleBarRect, &wdi, cg, kHIThemeOrientationNormal, 0); + if (titlebar->subControls & (SC_TitleBarCloseButton + | SC_TitleBarMaxButton + | SC_TitleBarMinButton + | SC_TitleBarNormalButton)) { + HIThemeWindowWidgetDrawInfo wwdi; + wwdi.version = qt_mac_hitheme_version; + wwdi.widgetState = tds; + if (titlebar->state & State_MouseOver) + wwdi.widgetState = kThemeStateRollover; + wwdi.windowType = QtWinType; + wwdi.attributes = wdi.attributes | kThemeWindowHasFullZoom | kThemeWindowHasCloseBox | kThemeWindowHasCollapseBox; + wwdi.windowState = wdi.state; + wwdi.titleHeight = wdi.titleHeight; + wwdi.titleWidth = wdi.titleWidth; + ThemeDrawState savedControlState = wwdi.widgetState; + uint sc = SC_TitleBarMinButton; + ThemeTitleBarWidget tbw = kThemeWidgetCollapseBox; + bool active = titlebar->state & State_Active; + + while (sc <= SC_TitleBarCloseButton) { + if (sc & titlebar->subControls) { + uint tmp = sc; + wwdi.widgetState = savedControlState; + wwdi.widgetType = tbw; + if (sc == SC_TitleBarMinButton) + tmp |= SC_TitleBarNormalButton; + if (active && (titlebar->activeSubControls & tmp) + && (titlebar->state & State_Sunken)) + wwdi.widgetState = kThemeStatePressed; + // Draw all sub controllers except the dirty close button + // (it is already handled by HIThemeDrawWindowFrame). + if (!(widget && widget->isWindowModified() && tbw == kThemeWidgetCloseBox)) { + HIThemeDrawTitleBarWidget(&titleBarRect, &wwdi, cg, kHIThemeOrientationNormal); + p->paintEngine()->syncState(); + } + } + sc = sc << 1; + tbw = tbw >> 1; + } + } + p->paintEngine()->syncState(); + if (titlebar->subControls & SC_TitleBarLabel) { + int iw = 0; + if (!titlebar->icon.isNull()) { + QCFType<HIShapeRef> titleRegion2; + HIThemeGetWindowShape(&titleBarRect, &wdi, kWindowTitleProxyIconRgn, + &titleRegion2); + ptrHIShapeGetBounds(titleRegion2, &tmpRect); + if (tmpRect.size.width != 1) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + iw = titlebar->icon.actualSize(QSize(iconExtent, iconExtent)).width(); + } + } + if (!titlebar->text.isEmpty()) { + p->save(); + QCFType<HIShapeRef> titleRegion3; + HIThemeGetWindowShape(&titleBarRect, &wdi, kWindowTitleTextRgn, &titleRegion3); + ptrHIShapeGetBounds(titleRegion3, &tmpRect); + p->setClipRect(qt_qrectForHIRect(tmpRect)); + QRect br = p->clipRegion().boundingRect(); + int x = br.x(), + y = br.y() + (titlebar->rect.height() / 2 - p->fontMetrics().height() / 2); + if (br.width() <= (p->fontMetrics().width(titlebar->text) + iw * 2)) + x += iw; + else + x += br.width() / 2 - p->fontMetrics().width(titlebar->text) / 2; + if (iw) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + p->drawPixmap(x - iw, y, + titlebar->icon.pixmap(window, QSize(iconExtent, iconExtent), QIcon::Normal)); + } + drawItemText(p, br, Qt::AlignCenter, opt->palette, tds == kThemeStateActive, + titlebar->text, QPalette::Text); + p->restore(); + } + } + } + break; + case CC_GroupBox: + if (const QStyleOptionGroupBox *gb + = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + + QStyleOptionGroupBox groupBox(*gb); + const bool flat = groupBox.features & QStyleOptionFrame::Flat; + if (!flat) + groupBox.state |= QStyle::State_Mini; // Force mini-sized checkbox to go with small-sized label + else + groupBox.subControls = groupBox.subControls & ~SC_GroupBoxFrame; // We don't like frames and ugly lines + + bool didModifySubControls = false; + if ((!widget || !widget->testAttribute(Qt::WA_SetFont)) + && QApplication::desktopSettingsAware()) { + groupBox.subControls = groupBox.subControls & ~SC_GroupBoxLabel; + didModifySubControls = true; + } + QCommonStyle::drawComplexControl(cc, &groupBox, p, widget); + if (didModifySubControls) { + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor = groupBox.palette.windowText().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); + tti.fontID = flat ? kThemeSystemFont : kThemeSmallSystemFont; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + groupBox.text.count(QLatin1Char('\n')); + QCFString groupText = qt_mac_removeMnemonics(groupBox.text); + QRect r = proxy()->subControlRect(CC_GroupBox, &groupBox, SC_GroupBoxLabel, widget); + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(groupText, &bounds, &tti, cg, kHIThemeOrientationNormal); + p->restore(); + } + } + break; + case CC_ToolButton: + if (const QStyleOptionToolButton *tb + = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { +#ifndef QT_NO_ACCESSIBILITY + if (QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar)) { + if (tb->subControls & SC_ToolButtonMenu) { + QStyleOption arrowOpt = *tb; + arrowOpt.rect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); + arrowOpt.rect.setY(arrowOpt.rect.y() + arrowOpt.rect.height() / 2); + arrowOpt.rect.setHeight(arrowOpt.rect.height() / 2); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); + } else if ((tb->features & QStyleOptionToolButton::HasMenu) + && (tb->toolButtonStyle != Qt::ToolButtonTextOnly && !tb->icon.isNull())) { + drawToolbarButtonArrow(tb->rect, tds, cg); + } + if (tb->state & State_On) { + QWindow *window = 0; + if (widget && widget->window()) + window = widget->window()->windowHandle(); + else if (opt->styleObject) + window = opt->styleObject->property("_q_styleObjectWindow").value<QWindow *>(); + + NSView *view = window ? (NSView *)window->winId() : nil; + bool isKey = false; + if (view) + isKey = [view.window isKeyWindow]; + + QBrush brush(isKey ? QColor(0, 0, 0, 28) + : QColor(0, 0, 0, 21)); + QPainterPath path; + path.addRoundedRect(QRectF(tb->rect.x(), tb->rect.y(), tb->rect.width(), tb->rect.height() + 4), 4, 4); + p->setRenderHint(QPainter::Antialiasing); + p->fillPath(path, brush); + } + proxy()->drawControl(CE_ToolButtonLabel, opt, p, widget); + } else { + ThemeButtonKind bkind = kThemeBevelButton; + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + bkind = kThemeBevelButton; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + bkind = kThemeSmallBevelButton; + break; + } + + QRect button, menuarea; + button = proxy()->subControlRect(cc, tb, SC_ToolButton, widget); + menuarea = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); + State bflags = tb->state, + mflags = tb->state; + if (tb->subControls & SC_ToolButton) + bflags |= State_Sunken; + if (tb->subControls & SC_ToolButtonMenu) + mflags |= State_Sunken; + + if (tb->subControls & SC_ToolButton) { + if (bflags & (State_Sunken | State_On | State_Raised)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + bdi.adornment = kThemeAdornmentNone; + bdi.kind = bkind; + bdi.value = kThemeButtonOff; + if (tb->state & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + if (tb->state & State_Sunken) + bdi.state = kThemeStatePressed; + if (tb->state & State_On) + bdi.value = kThemeButtonOn; + + QRect off_rct(0, 0, 0, 0); + HIRect myRect, macRect; + myRect = CGRectMake(tb->rect.x(), tb->rect.y(), + tb->rect.width(), tb->rect.height()); + HIThemeGetButtonBackgroundBounds(&myRect, &bdi, &macRect); + off_rct.setRect(int(myRect.origin.x - macRect.origin.x), + int(myRect.origin.y - macRect.origin.y), + int(macRect.size.width - myRect.size.width), + int(macRect.size.height - myRect.size.height)); + + myRect = qt_hirectForQRect(button, off_rct); + HIThemeDrawButton(&myRect, &bdi, cg, kHIThemeOrientationNormal, 0); + } + } + + if (tb->subControls & SC_ToolButtonMenu) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + bdi.kind = bkind; + if (tb->state & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + if (tb->state & (State_On | State_Sunken) + || (tb->activeSubControls & SC_ToolButtonMenu)) + bdi.state = kThemeStatePressed; + HIRect hirect = qt_hirectForQRect(menuarea); + HIThemeDrawButton(&hirect, &bdi, cg, kHIThemeOrientationNormal, 0); + QRect r(menuarea.x() + ((menuarea.width() / 2) - 3), menuarea.height() - 8, 8, 8); + HIThemePopupArrowDrawInfo padi; + padi.version = qt_mac_hitheme_version; + padi.state = tds; + padi.orientation = kThemeArrowDown; + padi.size = kThemeArrow7pt; + hirect = qt_hirectForQRect(r); + HIThemeDrawPopupArrow(&hirect, &padi, cg, kHIThemeOrientationNormal); + } else if (tb->features & QStyleOptionToolButton::HasMenu) { + drawToolbarButtonArrow(tb->rect, tds, cg); + } + QRect buttonRect = proxy()->subControlRect(CC_ToolButton, tb, SC_ToolButton, widget); + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + QStyleOptionToolButton label = *tb; + label.rect = buttonRect.adjusted(fw, fw, -fw, -fw); + proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget); + } +#endif + } + break; +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) + QStyleHelper::drawDial(dial, p); + break; +#endif + default: + QCommonStyle::drawComplexControl(cc, opt, p, widget); + break; + } +} + +QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, + const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *widget) const +{ + Q_D(const QMacStyle); + SubControl sc = QStyle::SC_None; + switch (cc) { + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + sc = QCommonStyle::hitTestComplexControl(cc, cmb, pt, widget); + if (!cmb->editable && sc != QStyle::SC_None) + sc = SC_ComboBoxArrow; // A bit of a lie, but what we want + } + break; + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + ControlPartCode part; + HIPoint pos = CGPointMake(pt.x(), pt.y()); + if (HIThemeHitTestTrack(&tdi, &pos, &part)) { + if (part == kControlPageUpPart || part == kControlPageDownPart) + sc = SC_SliderGroove; + else + sc = SC_SliderHandle; + } + } + break; + case CC_ScrollBar: + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIScrollBarTrackInfo sbi; + sbi.version = qt_mac_hitheme_version; + if (!(sb->state & State_Active)) + sbi.enableState = kThemeTrackInactive; + else if (sb->state & State_Enabled) + sbi.enableState = kThemeTrackActive; + else + sbi.enableState = kThemeTrackDisabled; + + // The arrow buttons are not drawn if the scroll bar is to short, + // exclude them from the hit test. + const int scrollBarLength = (sb->orientation == Qt::Horizontal) + ? sb->rect.width() : sb->rect.height(); + if (scrollBarLength < scrollButtonsCutoffSize(scrollButtonsCutoff, QStyleHelper::widgetSizePolicy(widget, opt))) + sbi.enableState = kThemeTrackNothingToScroll; + + sbi.viewsize = sb->pageStep; + HIPoint pos = CGPointMake(pt.x(), pt.y()); + + HIRect macSBRect = qt_hirectForQRect(sb->rect); + ControlPartCode part; + bool reverseHorizontal = (sb->direction == Qt::RightToLeft + && sb->orientation == Qt::Horizontal); + if (HIThemeHitTestScrollBarArrows(&macSBRect, &sbi, sb->orientation == Qt::Horizontal, + &pos, 0, &part)) { + if (part == kControlUpButtonPart) + sc = reverseHorizontal ? SC_ScrollBarAddLine : SC_ScrollBarSubLine; + else if (part == kControlDownButtonPart) + sc = reverseHorizontal ? SC_ScrollBarSubLine : SC_ScrollBarAddLine; + } else { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, sb, &tdi, widget); + if(tdi.enableState == kThemeTrackInactive) + tdi.enableState = kThemeTrackActive; + if (HIThemeHitTestTrack(&tdi, &pos, &part)) { + if (part == kControlPageUpPart) + sc = reverseHorizontal ? SC_ScrollBarAddPage + : SC_ScrollBarSubPage; + else if (part == kControlPageDownPart) + sc = reverseHorizontal ? SC_ScrollBarSubPage + : SC_ScrollBarAddPage; + else + sc = SC_ScrollBarSlider; + } + } + } + break; +/* + I don't know why, but we only get kWindowContentRgn here, which isn't what we want at all. + It would be very nice if this would work. + case QStyle::CC_TitleBar: + if (const QStyleOptionTitleBar *tbar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + HIThemeWindowDrawInfo wdi; + memset(&wdi, 0, sizeof(wdi)); + wdi.version = qt_mac_hitheme_version; + wdi.state = kThemeStateActive; + wdi.windowType = QtWinType; + wdi.titleWidth = tbar->rect.width(); + wdi.titleHeight = tbar->rect.height(); + if (tbar->titleBarState) + wdi.attributes |= kThemeWindowHasFullZoom | kThemeWindowHasCloseBox + | kThemeWindowHasCollapseBox; + else if (tbar->titleBarFlags & Qt::WindowSystemMenuHint) + wdi.attributes |= kThemeWindowHasCloseBox; + QRect tmpRect = tbar->rect; + tmpRect.setHeight(tmpRect.height() + 100); + HIRect hirect = qt_hirectForQRect(tmpRect); + WindowRegionCode hit; + HIPoint hipt = CGPointMake(pt.x(), pt.y()); + if (HIThemeGetWindowRegionHit(&hirect, &wdi, &hipt, &hit)) { + switch (hit) { + case kWindowCloseBoxRgn: + sc = QStyle::SC_TitleBarCloseButton; + break; + case kWindowCollapseBoxRgn: + sc = QStyle::SC_TitleBarMinButton; + break; + case kWindowZoomBoxRgn: + sc = QStyle::SC_TitleBarMaxButton; + break; + case kWindowTitleTextRgn: + sc = QStyle::SC_TitleBarLabel; + break; + default: + qDebug("got something else %d", hit); + break; + } + } + } + break; +*/ + default: + sc = QCommonStyle::hitTestComplexControl(cc, opt, pt, widget); + break; + } + return sc; +} + +QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *widget) const +{ + Q_D(const QMacStyle); + QRect ret; + switch (cc) { + case CC_Slider: + case CC_ScrollBar: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + HIRect macRect; + QCFType<HIShapeRef> shape; + bool scrollBar = cc == CC_ScrollBar; + if ((scrollBar && sc == SC_ScrollBarSlider) + || (!scrollBar && sc == SC_SliderHandle)) { + HIThemeGetTrackThumbShape(&tdi, &shape); + ptrHIShapeGetBounds(shape, &macRect); + } else if (!scrollBar && sc == SC_SliderGroove) { + HIThemeGetTrackBounds(&tdi, &macRect); + } else if (sc == SC_ScrollBarGroove) { // Only scroll bar parts available... + HIThemeGetTrackDragRect(&tdi, &macRect); + } else { + ControlPartCode cpc; + if (sc == SC_ScrollBarSubPage || sc == SC_ScrollBarAddPage) { + cpc = sc == SC_ScrollBarSubPage ? kControlPageDownPart + : kControlPageUpPart; + } else { + cpc = sc == SC_ScrollBarSubLine ? kControlUpButtonPart + : kControlDownButtonPart; + if (slider->direction == Qt::RightToLeft + && slider->orientation == Qt::Horizontal) { + if (cpc == kControlDownButtonPart) + cpc = kControlUpButtonPart; + else if (cpc == kControlUpButtonPart) + cpc = kControlDownButtonPart; + } + } + HIThemeGetTrackPartBounds(&tdi, cpc, &macRect); + } + ret = qt_qrectForHIRect(macRect); + + // Tweak: the dark line between the sub/add line buttons belong to only one of the buttons + // when doing hit-testing, but both of them have to repaint it. Extend the rect to cover + // the line in the cases where HIThemeGetTrackPartBounds returns a rect that doesn't. + if (slider->orientation == Qt::Horizontal) { + if (slider->direction == Qt::LeftToRight && sc == SC_ScrollBarSubLine) + ret.adjust(0, 0, 1, 0); + else if (slider->direction == Qt::RightToLeft && sc == SC_ScrollBarAddLine) + ret.adjust(-1, 0, 1, 0); + } else if (sc == SC_ScrollBarAddLine) { + ret.adjust(0, -1, 0, 1); + } + } + break; + case CC_TitleBar: + if (const QStyleOptionTitleBar *titlebar + = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + HIThemeWindowDrawInfo wdi; + memset(&wdi, 0, sizeof(wdi)); + wdi.version = qt_mac_hitheme_version; + wdi.state = kThemeStateActive; + wdi.windowType = QtWinType; + wdi.titleHeight = titlebar->rect.height(); + wdi.titleWidth = titlebar->rect.width(); + wdi.attributes = kThemeWindowHasTitleText; + if (titlebar->subControls & SC_TitleBarCloseButton) + wdi.attributes |= kThemeWindowHasCloseBox; + if (titlebar->subControls & SC_TitleBarMaxButton + | SC_TitleBarNormalButton) + wdi.attributes |= kThemeWindowHasFullZoom; + if (titlebar->subControls & SC_TitleBarMinButton) + wdi.attributes |= kThemeWindowHasCollapseBox; + WindowRegionCode wrc = kWindowGlobalPortRgn; + + if (sc == SC_TitleBarCloseButton) + wrc = kWindowCloseBoxRgn; + else if (sc == SC_TitleBarMinButton) + wrc = kWindowCollapseBoxRgn; + else if (sc == SC_TitleBarMaxButton) + wrc = kWindowZoomBoxRgn; + else if (sc == SC_TitleBarLabel) + wrc = kWindowTitleTextRgn; + else if (sc == SC_TitleBarSysMenu) + ret.setRect(-1024, -1024, 10, proxy()->pixelMetric(PM_TitleBarHeight, + titlebar, widget)); + if (wrc != kWindowGlobalPortRgn) { + QCFType<HIShapeRef> region; + QRect tmpRect = titlebar->rect; + HIRect titleRect = qt_hirectForQRect(tmpRect); + HIThemeGetWindowShape(&titleRect, &wdi, kWindowTitleBarRgn, ®ion); + ptrHIShapeGetBounds(region, &titleRect); + CFRelease(region); + tmpRect.translate(tmpRect.x() - int(titleRect.origin.x), + tmpRect.y() - int(titleRect.origin.y)); + titleRect = qt_hirectForQRect(tmpRect); + HIThemeGetWindowShape(&titleRect, &wdi, wrc, ®ion); + ptrHIShapeGetBounds(region, &titleRect); + ret = qt_qrectForHIRect(titleRect); + } + } + break; + case CC_ComboBox: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + HIThemeButtonDrawInfo bdi; + d->initComboboxBdi(combo, &bdi, widget, d->getDrawState(opt->state)); + + switch (sc) { + case SC_ComboBoxEditField:{ + ret = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + // 10.10 and above need a slight shift + ret.setHeight(ret.height() - 1); + break; } + case SC_ComboBoxArrow:{ + ret = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + ret.setX(ret.x() + ret.width()); + ret.setWidth(combo->rect.right() - ret.right()); + break; } + case SC_ComboBoxListBoxPopup:{ + if (combo->editable) { + HIRect inner = QMacStylePrivate::comboboxInnerBounds(qt_hirectForQRect(combo->rect), bdi.kind); + QRect editRect = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + const int comboTop = combo->rect.top(); + ret = QRect(qRound(inner.origin.x), + comboTop, + qRound(inner.origin.x - combo->rect.left() + inner.size.width), + editRect.bottom() - comboTop + 2); + } else { + QRect editRect = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + ret = QRect(combo->rect.x() + 4 - 11, + combo->rect.y() + 1, + editRect.width() + 10 + 11, + 1); + } + break; } + default: + break; + } + } + break; + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + const bool flat = groupBox->features & QStyleOptionFrame::Flat; + bool hasNoText = !checkable && groupBox->text.isEmpty(); + switch (sc) { + case SC_GroupBoxLabel: + case SC_GroupBoxCheckBox: { + // Cheat and use the smaller font if we need to + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + bool fontIsSet = (widget && widget->testAttribute(Qt::WA_SetFont)) + || !QApplication::desktopSettingsAware(); + int tw; + int h; + int margin = flat || hasNoText ? 0 : 9; + ret = groupBox->rect.adjusted(margin, 0, -margin, 0); + + if (!fontIsSet) { + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = kThemeStateActive; + tti.fontID = flat ? kThemeSystemFont : kThemeSmallSystemFont; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + groupBox->text.count(QLatin1Char('\n')); + CGFloat width; + CGFloat height; + QCFString groupText = qt_mac_removeMnemonics(groupBox->text); + HIThemeGetTextDimensions(groupText, 0, &tti, &width, &height, 0); + tw = qRound(width); + h = qCeil(height); + } else { + QFontMetricsF fm = QFontMetricsF(groupBox->fontMetrics); + h = qCeil(fm.height()); + tw = qCeil(fm.size(Qt::TextShowMnemonic, groupBox->text).width()); + } + ret.setHeight(h); + + QRect labelRect = alignedRect(groupBox->direction, groupBox->textAlignment, + QSize(tw, h), ret); + if (flat && checkable) + labelRect.moveLeft(labelRect.left() + 4); + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt, widget); + bool rtl = groupBox->direction == Qt::RightToLeft; + if (sc == SC_GroupBoxLabel) { + if (checkable) { + int newSum = indicatorWidth + 1; + int newLeft = labelRect.left() + (rtl ? -newSum : newSum); + labelRect.moveLeft(newLeft); + if (flat) + labelRect.moveTop(labelRect.top() + 3); + else + labelRect.moveTop(labelRect.top() + 4); + } else if (flat) { + int newLeft = labelRect.left() - (rtl ? 3 : -3); + labelRect.moveLeft(newLeft); + labelRect.moveTop(labelRect.top() + 3); + } else { + int newLeft = labelRect.left() - (rtl ? 3 : 2); + labelRect.moveLeft(newLeft); + labelRect.moveTop(labelRect.top() + 4); + } + ret = labelRect; + } + + if (sc == SC_GroupBoxCheckBox) { + int left = rtl ? labelRect.right() - indicatorWidth : labelRect.left() - 1; + int top = flat ? ret.top() + 1 : ret.top() + 5; + ret.setRect(left, top, + indicatorWidth, proxy()->pixelMetric(PM_IndicatorHeight, opt, widget)); + } + break; + } + case SC_GroupBoxContents: + case SC_GroupBoxFrame: { + QFontMetrics fm = groupBox->fontMetrics; + int yOffset = 3; + if (!flat) { + if (widget && !widget->testAttribute(Qt::WA_SetFont) + && QApplication::desktopSettingsAware()) + fm = QFontMetrics(qt_app_fonts_hash()->value("QSmallFont", QFont())); + yOffset = 5; + } + + if (hasNoText) + yOffset = -qCeil(QFontMetricsF(fm).height()); + ret = opt->rect.adjusted(0, qCeil(QFontMetricsF(fm).height()) + yOffset, 0, 0); + if (sc == SC_GroupBoxContents) { + if (flat) + ret.adjust(3, -5, -3, -4); // guess too + else + ret.adjust(3, 3, -3, -4); // guess + } + } + break; + default: + ret = QCommonStyle::subControlRect(cc, groupBox, sc, widget); + break; + } + } + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QAquaWidgetSize aquaSize = d->aquaSizeConstrain(spin, widget); + int spinner_w; + int spinBoxSep; + int fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, spin, widget); + switch (aquaSize) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + spinner_w = 14; + spinBoxSep = 2; + break; + case QAquaSizeSmall: + spinner_w = 12; + spinBoxSep = 2; + break; + case QAquaSizeMini: + spinner_w = 10; + spinBoxSep = 1; + break; + } + + switch (sc) { + case SC_SpinBoxUp: + case SC_SpinBoxDown: { + if (spin->buttonSymbols == QAbstractSpinBox::NoButtons) + break; + + const int y = fw; + const int x = spin->rect.width() - spinner_w; + ret.setRect(x + spin->rect.x(), y + spin->rect.y(), spinner_w, spin->rect.height() - y * 2); + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.kind = kThemeIncDecButton; + int hackTranslateX; + switch (aquaSize) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + bdi.kind = kThemeIncDecButton; + hackTranslateX = 0; + break; + case QAquaSizeSmall: + bdi.kind = kThemeIncDecButtonSmall; + hackTranslateX = -2; + break; + case QAquaSizeMini: + bdi.kind = kThemeIncDecButtonMini; + hackTranslateX = -1; + break; + } + bdi.state = kThemeStateActive; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + HIRect hirect = qt_hirectForQRect(ret); + + HIRect outRect; + HIThemeGetButtonBackgroundBounds(&hirect, &bdi, &outRect); + ret = qt_qrectForHIRect(outRect); + switch (sc) { + case SC_SpinBoxUp: + ret.setHeight(ret.height() / 2); + break; + case SC_SpinBoxDown: + ret.setY(ret.y() + ret.height() / 2); + break; + default: + Q_ASSERT(0); + break; + } + ret.translate(hackTranslateX, 0); // hack: position the buttons correctly (weird that we need this) + ret = visualRect(spin->direction, spin->rect, ret); + break; + } + case SC_SpinBoxEditField: + if (spin->buttonSymbols == QAbstractSpinBox::NoButtons) { + ret.setRect(fw, fw, + spin->rect.width() - fw * 2, + spin->rect.height() - fw * 2); + } else { + ret.setRect(fw, fw, + spin->rect.width() - fw * 2 - spinBoxSep - spinner_w, + spin->rect.height() - fw * 2); + } + ret = visualRect(spin->direction, spin->rect, ret); + break; + default: + ret = QCommonStyle::subControlRect(cc, spin, sc, widget); + break; + } + } + break; +#endif + case CC_ToolButton: + ret = QCommonStyle::subControlRect(cc, opt, sc, widget); + if (sc == SC_ToolButtonMenu +#ifndef QT_NO_ACCESSIBILITY + && !QStyleHelper::hasAncestor(opt->styleObject, QAccessible::ToolBar) +#endif + ) { + ret.adjust(-1, 0, 0, 0); + } + break; + default: + ret = QCommonStyle::subControlRect(cc, opt, sc, widget); + break; + } + return ret; +} + +QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *widget) const +{ + Q_D(const QMacStyle); + QSize sz(csz); + bool useAquaGuideline = true; + + switch (ct) { +#ifndef QT_NO_SPINBOX + case CT_SpinBox: + if (const QStyleOptionSpinBox *vopt = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + // Add button + frame widths + int buttonWidth = 20; + int fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, vopt, widget); + sz += QSize(buttonWidth + 2*fw, 2*fw - 3); + } + break; +#endif + case QStyle::CT_TabWidget: + // the size between the pane and the "contentsRect" (+4,+4) + // (the "contentsRect" is on the inside of the pane) + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + /** + This is supposed to show the relationship between the tabBar and + the stack widget of a QTabWidget. + Unfortunately ascii is not a good way of representing graphics..... + PS: The '=' line is the painted frame. + + top ---+ + | + | + | + | vvv just outside the painted frame is the "pane" + - -|- - - - - - - - - - <-+ + TAB BAR +=====^============ | +2 pixels + - - -|- - -|- - - - - - - <-+ + | | ^ ^^^ just inside the painted frame is the "contentsRect" + | | | + | overlap | + | | | + bottom ------+ <-+ +14 pixels + | + v + ------------------------------ <- top of stack widget + + + To summarize: + * 2 is the distance between the pane and the contentsRect + * The 14 and the 1's are the distance from the contentsRect to the stack widget. + (same value as used in SE_TabWidgetTabContents) + * overlap is how much the pane should overlap the tab bar + */ + // then add the size between the stackwidget and the "contentsRect" +#ifndef QT_NO_TABWIDGET + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QSize extra(0,0); + const int overlap = pixelMetric(PM_TabBarBaseOverlap, opt, widget); + const int gapBetweenTabbarAndStackWidget = 2 + 14 - overlap; + + if (getTabDirection(twf->shape) == kThemeTabNorth || getTabDirection(twf->shape) == kThemeTabSouth) { + extra = QSize(2, gapBetweenTabbarAndStackWidget + 1); + } else { + extra = QSize(gapBetweenTabbarAndStackWidget + 1, 2); + } + sz+= extra; + } +#endif + break; +#ifndef QT_NO_TABBAR + case QStyle::CT_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + const QAquaWidgetSize AquaSize = d->aquaSizeConstrain(opt, widget); + const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont)) + || !QApplication::desktopSettingsAware(); + ThemeTabDirection ttd = getTabDirection(tab->shape); + bool vertTabs = ttd == kThemeTabWest || ttd == kThemeTabEast; + if (vertTabs) + sz = sz.transposed(); + int defaultTabHeight; + int extraHSpace = proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget); + QFontMetrics fm = opt->fontMetrics; + switch (AquaSize) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (tab->documentMode) + defaultTabHeight = 24; + else + defaultTabHeight = 21; + break; + case QAquaSizeSmall: + defaultTabHeight = 18; + break; + case QAquaSizeMini: + defaultTabHeight = 16; + break; + } + bool setWidth = false; + if (differentFont || !tab->icon.isNull()) { + sz.rheight() = qMax(defaultTabHeight, sz.height()); + } else { + QSize textSize = fm.size(Qt::TextShowMnemonic, tab->text); + sz.rheight() = qMax(defaultTabHeight, textSize.height()); + sz.rwidth() = textSize.width(); + setWidth = true; + } + sz.rwidth() += extraHSpace; + + if (vertTabs) + sz = sz.transposed(); + + int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height()); + int maxWidgetWidth = qMax(tab->leftButtonSize.width(), tab->rightButtonSize.width()); + + int widgetWidth = 0; + int widgetHeight = 0; + int padding = 0; + if (tab->leftButtonSize.isValid()) { + padding += 8; + widgetWidth += tab->leftButtonSize.width(); + widgetHeight += tab->leftButtonSize.height(); + } + if (tab->rightButtonSize.isValid()) { + padding += 8; + widgetWidth += tab->rightButtonSize.width(); + widgetHeight += tab->rightButtonSize.height(); + } + + if (vertTabs) { + sz.setHeight(sz.height() + widgetHeight + padding); + sz.setWidth(qMax(sz.width(), maxWidgetWidth)); + } else { + if (setWidth) + sz.setWidth(sz.width() + widgetWidth + padding); + sz.setHeight(qMax(sz.height(), maxWidgetHeight)); + } + } + break; +#endif + case QStyle::CT_PushButton: + // By default, we fit the contents inside a normal rounded push button. + // Do this by add enough space around the contents so that rounded + // borders (including highlighting when active) will show. + sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12; + if (opt->state & QStyle::State_Small) + sz.rheight() += 14; + else + sz.rheight() += 4; + break; + case QStyle::CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + int maxpmw = mi->maxIconWidth; +#ifndef QT_NO_COMBOBOX + const QComboBox *comboBox = qobject_cast<const QComboBox *>(widget); +#endif + int w = sz.width(), + h = sz.height(); + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + w = 10; + SInt16 ash; + GetThemeMenuSeparatorHeight(&ash); + h = ash; + } else { + h = mi->fontMetrics.height() + 2; + if (!mi->icon.isNull()) { +#ifndef QT_NO_COMBOBOX + if (comboBox) { + const QSize &iconSize = comboBox->iconSize(); + h = qMax(h, iconSize.height() + 4); + maxpmw = qMax(maxpmw, iconSize.width()); + } else +#endif + { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4); + } + } + } + if (mi->text.contains(QLatin1Char('\t'))) + w += 12; + if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + w += 20; + if (maxpmw) + w += maxpmw + 6; + // add space for a check. All items have place for a check too. + w += 20; +#ifndef QT_NO_COMBOBOX + if (comboBox && comboBox->isVisible()) { + QStyleOptionComboBox cmb; + cmb.initFrom(comboBox); + cmb.editable = false; + cmb.subControls = QStyle::SC_ComboBoxEditField; + cmb.activeSubControls = QStyle::SC_None; + w = qMax(w, subControlRect(QStyle::CC_ComboBox, &cmb, + QStyle::SC_ComboBoxEditField, + comboBox).width()); + } else +#endif + { + w += 12; + } + sz = QSize(w, h); + } + break; + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(12, 4); // Constants from QWindowsStyle + break; + case CT_ToolButton: + sz.rwidth() += 10; + sz.rheight() += 10; + return sz; + case CT_ComboBox: { + sz.rwidth() += 50; + const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt); + if (cb && !cb->editable) + sz.rheight() += 2; + break; + } + case CT_Menu: { + if (proxy() == this) { + sz = csz; + } else { + QStyleHintReturnMask menuMask; + QStyleOption myOption = *opt; + myOption.rect.setSize(sz); + if (proxy()->styleHint(SH_Menu_Mask, &myOption, widget, &menuMask)) + sz = menuMask.region.boundingRect().size(); + } + break; } + case CT_HeaderSection:{ + const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt); + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + if (header->text.contains(QLatin1Char('\n'))) + useAquaGuideline = false; + break; } + case CT_ScrollBar : + // Make sure that the scroll bar is large enough to display the thumb indicator. + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + const int minimumSize = scrollButtonsCutoffSize(thumbIndicatorCutoff, QStyleHelper::widgetSizePolicy(widget, opt)); + if (slider->orientation == Qt::Horizontal) + sz = sz.expandedTo(QSize(minimumSize, sz.height())); + else + sz = sz.expandedTo(QSize(sz.width(), minimumSize)); + } + break; + case CT_ItemViewItem: + if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { + sz = QCommonStyle::sizeFromContents(ct, vopt, csz, widget); + sz.setHeight(sz.height() + 2); + } + break; + + default: + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + } + + if (useAquaGuideline){ + QSize macsz; + if (d->aquaSizeConstrain(opt, widget, ct, sz, &macsz) != QAquaSizeUnknown) { + if (macsz.width() != -1) + sz.setWidth(macsz.width()); + if (macsz.height() != -1) + sz.setHeight(macsz.height()); + } + } + + // The sizes that Carbon and the guidelines gives us excludes the focus frame. + // We compensate for this by adding some extra space here to make room for the frame when drawing: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){ + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, widget); + int bkind = 0; + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = combo->editable ? kThemeComboBox : kThemePopupButton; + break; + case QAquaSizeSmall: + bkind = combo->editable ? int(kThemeComboBoxSmall) : int(kThemePopupButtonSmall); + break; + case QAquaSizeMini: + bkind = combo->editable ? kThemeComboBoxMini : kThemePopupButtonMini; + break; + } + HIRect tmpRect = {{0, 0}, {0, 0}}; + HIRect diffRect = QMacStylePrivate::comboboxInnerBounds(tmpRect, bkind); + sz.rwidth() -= qRound(diffRect.size.width); + sz.rheight() -= qRound(diffRect.size.height); + } else if (ct == CT_PushButton || ct == CT_ToolButton){ + ThemeButtonKind bkind; + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, widget); + switch (ct) { + default: + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (btn->features & QStyleOptionButton::CommandLinkButton) { + return QCommonStyle::sizeFromContents(ct, opt, sz, widget); + } + } + + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = kThemePushButton; + break; + case QAquaSizeSmall: + bkind = kThemePushButtonSmall; + break; + case QAquaSizeMini: + bkind = kThemePushButtonMini; + break; + } + break; + case CT_ToolButton: + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = kThemeLargeBevelButton; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + bkind = kThemeSmallBevelButton; + } + break; + } + + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeStateActive; + bdi.kind = bkind; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + HIRect macRect, myRect; + myRect = CGRectMake(0, 0, sz.width(), sz.height()); + HIThemeGetButtonBackgroundBounds(&myRect, &bdi, &macRect); + // Mini buttons only return their actual size in HIThemeGetButtonBackgroundBounds, so help them out a bit (guess), + if (bkind == kThemePushButtonMini) + macRect.size.height += 8.; + else if (bkind == kThemePushButtonSmall) + macRect.size.height -= 10; + sz.setWidth(sz.width() + int(macRect.size.width - myRect.size.width)); + sz.setHeight(sz.height() + int(macRect.size.height - myRect.size.height)); + } + return sz; +} + +void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, + bool enabled, const QString &text, QPalette::ColorRole textRole) const +{ + if(flags & Qt::TextShowMnemonic) + flags |= Qt::TextHideMnemonic; + QCommonStyle::drawItemText(p, r, flags, pal, enabled, text, textRole); +} + +bool QMacStyle::event(QEvent *e) +{ + Q_D(QMacStyle); + if(e->type() == QEvent::FocusIn) { + QWidget *f = 0; + QWidget *focusWidget = QApplication::focusWidget(); +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) { + QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : 0; + if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) { + QGraphicsProxyWidget *proxy = static_cast<QGraphicsProxyWidget *>(focusItem); + if (proxy->widget()) + focusWidget = proxy->widget()->focusWidget(); + } + } +#endif + if (focusWidget && focusWidget->testAttribute(Qt::WA_MacShowFocusRect)) { + f = focusWidget; + QWidget *top = f->parentWidget(); + while (top && !top->isWindow() && !(top->windowType() == Qt::SubWindow)) + top = top->parentWidget(); +#ifndef QT_NO_MAINWINDOW + if (qobject_cast<QMainWindow *>(top)) { + QWidget *central = static_cast<QMainWindow *>(top)->centralWidget(); + for (const QWidget *par = f; par; par = par->parentWidget()) { + if (par == central) { + top = central; + break; + } + if (par->isWindow()) + break; + } + } +#endif + } + if (f) { + if(!d->focusWidget) + d->focusWidget = new QFocusFrame(f); + d->focusWidget->setWidget(f); + } else if(d->focusWidget) { + d->focusWidget->setWidget(0); + } + } else if(e->type() == QEvent::FocusOut) { + if(d->focusWidget) + d->focusWidget->setWidget(0); + } + return false; +} + +QIcon QMacStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *opt, + const QWidget *widget) const +{ + switch (standardIcon) { + default: + return QCommonStyle::standardIcon(standardIcon, opt, widget); + case SP_ToolBarHorizontalExtensionButton: + case SP_ToolBarVerticalExtensionButton: { + QPixmap pixmap(QLatin1String(":/qt-project.org/styles/macstyle/images/toolbar-ext.png")); + if (standardIcon == SP_ToolBarVerticalExtensionButton) { + QPixmap pix2(pixmap.height(), pixmap.width()); + pix2.setDevicePixelRatio(pixmap.devicePixelRatio()); + pix2.fill(Qt::transparent); + QPainter p(&pix2); + p.translate(pix2.width(), 0); + p.rotate(90); + p.drawPixmap(0, 0, pixmap); + return pix2; + } + return pixmap; + } + } +} + +int QMacStyle::layoutSpacing(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option, + const QWidget *widget) const +{ + const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; + bool isMetal = (widget && widget->testAttribute(Qt::WA_MacBrushedMetal)); + int controlSize = getControlSize(option, widget); + + if (control2 == QSizePolicy::ButtonBox) { + /* + AHIG seems to prefer a 12-pixel margin between group + boxes and the row of buttons. The 20 pixel comes from + Builder. + */ + if (isMetal // (AHIG, guess, guess) + || (control1 & (QSizePolicy::Frame // guess + | QSizePolicy::GroupBox // (AHIG, guess, guess) + | QSizePolicy::TabWidget // guess + | ButtonMask))) { // AHIG + return_SIZE(14, 8, 8); + } else if (control1 == QSizePolicy::LineEdit) { + return_SIZE(8, 8, 8); // Interface Builder + } else { + return_SIZE(20, 7, 7); // Interface Builder + } + } + + if ((control1 | control2) & ButtonMask) { + if (control1 == QSizePolicy::LineEdit) + return_SIZE(8, 8, 8); // Interface Builder + else if (control2 == QSizePolicy::LineEdit) { + if (orientation == Qt::Vertical) + return_SIZE(20, 7, 7); // Interface Builder + else + return_SIZE(20, 8, 8); + } + return_SIZE(14, 8, 8); // Interface Builder + } + + switch (CT2(control1, control2)) { + case CT1(QSizePolicy::Label): // guess + case CT2(QSizePolicy::Label, QSizePolicy::DefaultType): // guess + case CT2(QSizePolicy::Label, QSizePolicy::CheckBox): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::ComboBox): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::LineEdit): // guess + case CT2(QSizePolicy::Label, QSizePolicy::RadioButton): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::Slider): // guess + case CT2(QSizePolicy::Label, QSizePolicy::SpinBox): // guess + case CT2(QSizePolicy::Label, QSizePolicy::ToolButton): // guess + return_SIZE(8, 6, 5); + case CT1(QSizePolicy::ToolButton): + return 8; // AHIG + case CT1(QSizePolicy::CheckBox): + case CT2(QSizePolicy::CheckBox, QSizePolicy::RadioButton): + case CT2(QSizePolicy::RadioButton, QSizePolicy::CheckBox): + if (orientation == Qt::Vertical) + return_SIZE(8, 8, 7); // AHIG and Builder + break; + case CT1(QSizePolicy::RadioButton): + if (orientation == Qt::Vertical) + return 5; // (Builder, guess, AHIG) + } + + if (orientation == Qt::Horizontal + && (control2 & (QSizePolicy::CheckBox | QSizePolicy::RadioButton))) + return_SIZE(12, 10, 8); // guess + + if ((control1 | control2) & (QSizePolicy::Frame + | QSizePolicy::GroupBox + | QSizePolicy::TabWidget)) { + /* + These values were chosen so that nested container widgets + look good side by side. Builder uses 8, which looks way + too small, and AHIG doesn't say anything. + */ + return_SIZE(16, 10, 10); // guess + } + + if ((control1 | control2) & (QSizePolicy::Line | QSizePolicy::Slider)) + return_SIZE(12, 10, 8); // AHIG + + if ((control1 | control2) & QSizePolicy::LineEdit) + return_SIZE(10, 8, 8); // AHIG + + /* + AHIG and Builder differ by up to 4 pixels for stacked editable + comboboxes. We use some values that work fairly well in all + cases. + */ + if ((control1 | control2) & QSizePolicy::ComboBox) + return_SIZE(10, 8, 7); // guess + + /* + Builder defaults to 8, 6, 5 in lots of cases, but most of the time the + result looks too cramped. + */ + return_SIZE(10, 8, 6); // guess +} + +/* +FontHash::FontHash() +{ + QHash<QByteArray, QFont>::operator=(QGuiApplicationPrivate::platformIntegration()->fontDatabase()->defaultFonts()); +} + +Q_GLOBAL_STATIC(FontHash, app_fonts) +FontHash *qt_app_fonts_hash() +{ + return app_fonts(); +} +*/ +QT_END_NAMESPACE diff --git a/src/plugins/styles/mac/qmacstyle_mac_p.h b/src/plugins/styles/mac/qmacstyle_mac_p.h new file mode 100644 index 0000000000..6011baeea2 --- /dev/null +++ b/src/plugins/styles/mac/qmacstyle_mac_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACSTYLE_MAC_P_H +#define QMACSTYLE_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include <QtWidgets/qcommonstyle.h> + +QT_BEGIN_NAMESPACE + +class QPalette; + +class QPushButton; +class QStyleOptionButton; +class QMacStylePrivate; +class QMacStyle : public QCommonStyle +{ + Q_OBJECT +public: + QMacStyle(); + virtual ~QMacStyle(); + + void polish(QWidget *w); + void unpolish(QWidget *w); + + void polish(QApplication*); + void unpolish(QApplication*); + + void polish(QPalette &pal); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *w = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *w = 0) const; + + int pixelMetric(PixelMetric pm, const QStyleOption *opt = 0, const QWidget *widget = 0) const; + + QPalette standardPalette() const; + + virtual int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, + QStyleHintReturn *shret = 0) const; + + QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *opt, + const QWidget *widget = 0) const; + + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + + virtual void drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, + bool enabled, const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + + bool event(QEvent *e); + + QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, + Qt::Orientation orientation, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QMacStyle) + Q_DECLARE_PRIVATE(QMacStyle) + +#if QT_CONFIG(pushbutton) + friend bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option); +#endif +}; + +QT_END_NAMESPACE + +#endif // QMACSTYLE_MAC_P_H diff --git a/src/plugins/styles/mac/qmacstyle_mac_p_p.h b/src/plugins/styles/mac/qmacstyle_mac_p_p.h new file mode 100644 index 0000000000..6fd6d0c900 --- /dev/null +++ b/src/plugins/styles/mac/qmacstyle_mac_p_p.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMACSTYLE_MAC_P_P_H +#define QMACSTYLE_MAC_P_P_H + +#include <Carbon/Carbon.h> +#undef check + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include <QtWidgets/private/qcommonstyle_p.h> +#include "qmacstyle_mac_p.h" +#include <private/qapplication_p.h> +#include <private/qcombobox_p.h> +#include <private/qpainter_p.h> +#include <private/qstylehelper_p.h> +#include <qapplication.h> +#include <qbitmap.h> +#if QT_CONFIG(checkbox) +#include <qcheckbox.h> +#endif +#include <qcombobox.h> +#if QT_CONFIG(dialogbuttonbox) +#include <qdialogbuttonbox.h> +#endif +#include <qdockwidget.h> +#include <qevent.h> +#include <qfocusframe.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qhash.h> +#include <qheaderview.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistview.h> +#include <qmainwindow.h> +#include <qmap.h> +#include <qmenubar.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <qpixmapcache.h> +#include <qpointer.h> +#include <qprogressbar.h> +#if QT_CONFIG(pushbutton) +#include <qpushbutton.h> +#endif +#include <qradiobutton.h> +#include <qrubberband.h> +#include <qsizegrip.h> +#include <qspinbox.h> +#include <qsplitter.h> +#include <qstyleoption.h> +#include <qtextedit.h> +#include <qtextstream.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qtreeview.h> +#include <qtableview.h> +#include <qwizard.h> +#include <qdebug.h> +#include <qdatetimeedit.h> +#include <qmath.h> +#include <qpair.h> +#include <qvector.h> +#include <QtWidgets/qgraphicsproxywidget.h> +#include <QtWidgets/qgraphicsview.h> + + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +/* + AHIG: + Apple Human Interface Guidelines + http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/ + + Builder: + Apple Interface Builder v. 3.1.1 +*/ + +// this works as long as we have at most 16 different control types +#define CT1(c) CT2(c, c) +#define CT2(c1, c2) ((uint(c1) << 16) | uint(c2)) + +enum QAquaWidgetSize { QAquaSizeLarge = 0, QAquaSizeSmall = 1, QAquaSizeMini = 2, + QAquaSizeUnknown = -1 }; + +enum QCocoaWidgetKind { + QCocoaArrowButton, // Disclosure triangle, like in QTreeView + QCocoaCheckBox, + QCocoaComboBox, // Editable QComboBox + QCocoaPopupButton, // Non-editable QComboBox + QCocoaPullDownButton, // QPushButton with menu + QCocoaPushButton, + QCocoaRadioButton, + QCocoaHorizontalSlider, + QCocoaVerticalSlider +}; + +typedef QPair<QCocoaWidgetKind, QAquaWidgetSize> QCocoaWidget; + +typedef void (^QCocoaDrawRectBlock)(NSRect, CGContextRef); + +#define SIZE(large, small, mini) \ + (controlSize == QAquaSizeLarge ? (large) : controlSize == QAquaSizeSmall ? (small) : (mini)) + +// same as return SIZE(...) but optimized +#define return_SIZE(large, small, mini) \ + do { \ + static const int sizes[] = { (large), (small), (mini) }; \ + return sizes[controlSize]; \ + } while (0) + +#if QT_CONFIG(pushbutton) +bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option); +#endif + +class QMacStylePrivate : public QCommonStylePrivate +{ + Q_DECLARE_PUBLIC(QMacStyle) +public: + QMacStylePrivate(); + ~QMacStylePrivate(); + + // Ideally these wouldn't exist, but since they already exist we need some accessors. + static const int PushButtonLeftOffset; + static const int PushButtonTopOffset; + static const int PushButtonRightOffset; + static const int PushButtonBottomOffset; + static const int MiniButtonH; + static const int SmallButtonH; + static const int BevelButtonW; + static const int BevelButtonH; + static const int PushButtonContentPadding; + + enum Animates { AquaPushButton, AquaProgressBar, AquaListViewItemOpen, AquaScrollBar }; + static ThemeDrawState getDrawState(QStyle::State flags); + QAquaWidgetSize aquaSizeConstrain(const QStyleOption *option, const QWidget *widg, + QStyle::ContentsType ct = QStyle::CT_CustomBase, + QSize szHint=QSize(-1, -1), QSize *insz = 0) const; + QAquaWidgetSize effectiveAquaSizeConstrain(const QStyleOption *option, const QWidget *widg, + QStyle::ContentsType ct = QStyle::CT_CustomBase, + QSize szHint=QSize(-1, -1), QSize *insz = 0) const; + void getSliderInfo(QStyle::ComplexControl cc, const QStyleOptionSlider *slider, + HIThemeTrackDrawInfo *tdi, const QWidget *needToRemoveMe) const; + inline int animateSpeed(Animates) const { return 33; } + + // Utility functions + void drawColorlessButton(const HIRect &macRect, HIThemeButtonDrawInfo *bdi, + QPainter *p, const QStyleOption *opt) const; + + QSize pushButtonSizeFromContents(const QStyleOptionButton *btn) const; + + HIRect pushButtonContentBounds(const QStyleOptionButton *btn, + const HIThemeButtonDrawInfo *bdi) const; + + void initComboboxBdi(const QStyleOptionComboBox *combo, HIThemeButtonDrawInfo *bdi, + const QWidget *widget, const ThemeDrawState &tds) const; + + static HIRect comboboxInnerBounds(const HIRect &outerBounds, int buttonKind); + + static QRect comboboxEditBounds(const QRect &outerBounds, const HIThemeButtonDrawInfo &bdi); + + static void drawCombobox(const HIRect &outerBounds, const HIThemeButtonDrawInfo &bdi, QPainter *p); + static void drawTableHeader(const HIRect &outerBounds, bool drawTopBorder, bool drawLeftBorder, + const HIThemeButtonDrawInfo &bdi, QPainter *p); + bool contentFitsInPushButton(const QStyleOptionButton *btn, HIThemeButtonDrawInfo *bdi, + ThemeButtonKind buttonKindToCheck) const; + void initHIThemePushButton(const QStyleOptionButton *btn, const QWidget *widget, + const ThemeDrawState tds, + HIThemeButtonDrawInfo *bdi) const; + QPixmap generateBackgroundPattern() const; + + void setAutoDefaultButton(QObject *button) const; + + NSView *cocoaControl(QCocoaWidget widget) const; + + void drawNSViewInRect(QCocoaWidget widget, NSView *view, const QRect &rect, QPainter *p, bool isQWidget = true, QCocoaDrawRectBlock drawRectBlock = nil) const; + void resolveCurrentNSView(QWindow *window); + + void drawFocusRing(QPainter *p, const QRect &targetRect, int hMargin, int vMargin, qreal radius = 0) const; + +#ifndef QT_NO_TABBAR + void tabLayout(const QStyleOptionTab *opt, const QWidget *widget, QRect *textRect) const; +#endif + +public: + mutable QPointer<QObject> pressedButton; + mutable QPointer<QObject> defaultButton; + mutable QPointer<QObject> autoDefaultButton; + static QVector<QPointer<QObject> > scrollBars; + + struct ButtonState { + int frame; + enum { ButtonDark, ButtonLight } dir; + } buttonState; + mutable QPointer<QFocusFrame> focusWidget; + CFAbsoluteTime defaultButtonStart; + bool mouseDown; + void* receiver; + void *nsscroller; + void *indicatorBranchButtonCell; + NSView *backingStoreNSView; + QHash<QCocoaWidget, NSView *> cocoaControls; +}; + +QT_END_NAMESPACE + +#endif // QMACSTYLE_MAC_P_P_H diff --git a/src/plugins/styles/styles.pro b/src/plugins/styles/styles.pro new file mode 100644 index 0000000000..542ad1329a --- /dev/null +++ b/src/plugins/styles/styles.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs +QT_FOR_CONFIG += widgets-private + +qtConfig(style-android): SUBDIRS += android + +qtConfig(style-mac): SUBDIRS += mac + +qtConfig(style-windowsvista): SUBDIRS += windowsvista diff --git a/src/plugins/styles/windowsvista/main.cpp b/src/plugins/styles/windowsvista/main.cpp new file mode 100644 index 0000000000..d5048e45b7 --- /dev/null +++ b/src/plugins/styles/windowsvista/main.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include <QtWidgets/qstyleplugin.h> +#include "qwindowsvistastyle_p.h" + +QT_BEGIN_NAMESPACE + +class QWindowsVistaStylePlugin : public QStylePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "windowsvistastyle.json") +public: + QStyle *create(const QString &key); +}; + +QStyle *QWindowsVistaStylePlugin::create(const QString &key) +{ + if (key.compare(QLatin1String("windowsvista"), Qt::CaseInsensitive) == 0) + return new QWindowsVistaStyle(); + + return 0; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp b/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp new file mode 100644 index 0000000000..1a1094a934 --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp @@ -0,0 +1,2487 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsvistastyle_p.h" +#include "qwindowsvistastyle_p_p.h" +#include <qoperatingsystemversion.h> +#include <qscreen.h> +#include <qwindow.h> +#include <private/qstyleanimation_p.h> +#include <private/qstylehelper_p.h> +#include <private/qapplication_p.h> +#include <qpa/qplatformnativeinterface.h> + +QT_BEGIN_NAMESPACE + +static const int windowsItemFrame = 2; // menu item frame width +static const int windowsItemHMargin = 3; // menu item hor text margin +static const int windowsItemVMargin = 4; // menu item ver text margin +static const int windowsArrowHMargin = 6; // arrow horizontal margin +static const int windowsRightBorder = 15; // right border on windows + +#ifndef TMT_CONTENTMARGINS +# define TMT_CONTENTMARGINS 3602 +#endif +#ifndef TMT_SIZINGMARGINS +# define TMT_SIZINGMARGINS 3601 +#endif +#ifndef LISS_NORMAL +# define LISS_NORMAL 1 +# define LISS_HOT 2 +# define LISS_SELECTED 3 +# define LISS_DISABLED 4 +# define LISS_SELECTEDNOTFOCUS 5 +# define LISS_HOTSELECTED 6 +#endif +#ifndef BP_COMMANDLINK +# define BP_COMMANDLINK 6 +# define BP_COMMANDLINKGLYPH 7 +# define CMDLGS_NORMAL 1 +# define CMDLGS_HOT 2 +# define CMDLGS_PRESSED 3 +# define CMDLGS_DISABLED 4 +#endif + +/* \internal + Checks if we should use Vista style , or if we should + fall back to Windows style. +*/ +bool QWindowsVistaStylePrivate::useVista() +{ + return (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based)) + && QWindowsVistaStylePrivate::useXP(); +} + +/* \internal + Checks and returns the style object +*/ +inline QObject *styleObject(const QStyleOption *option) { + return option ? option->styleObject : 0; +} + +/* \internal + Checks if we can animate on a style option +*/ +bool canAnimate(const QStyleOption *option) { + return option + && option->styleObject + && !option->styleObject->property("_q_no_animation").toBool(); +} + +static inline QImage createAnimationBuffer(const QStyleOption *option, const QWidget *widget) +{ + const int devicePixelRatio = widget ? widget->devicePixelRatio() : 1; + QImage result(option->rect.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(devicePixelRatio); + result.fill(0); + return result; +} + +/* \internal + Used by animations to clone a styleoption and shift its offset +*/ +QStyleOption *clonedAnimationStyleOption(const QStyleOption*option) { + QStyleOption *styleOption = 0; + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option)) + styleOption = new QStyleOptionSlider(*slider); + else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option)) + styleOption = new QStyleOptionSpinBox(*spinbox); + else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option)) + styleOption = new QStyleOptionGroupBox(*groupBox); + else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option)) + styleOption = new QStyleOptionComboBox(*combo); + else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option)) + styleOption = new QStyleOptionButton(*button); + else + styleOption = new QStyleOption(*option); + styleOption->rect = QRect(QPoint(0,0), option->rect.size()); + return styleOption; +} + +/* \internal + Used by animations to delete cloned styleoption +*/ +void deleteClonedAnimationStyleOption(const QStyleOption *option) +{ + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option)) + delete slider; + else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option)) + delete spinbox; + else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option)) + delete groupBox; + else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option)) + delete combo; + else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option)) + delete button; + else + delete option; +} + +/*! + \class QWindowsVistaStyle + \brief The QWindowsVistaStyle class provides a look and feel suitable for applications on Microsoft Windows Vista. + \since 4.3 + \ingroup appearance + \inmodule QtWidgets + \internal + + \warning This style is only available on the Windows Vista platform + because it makes use of Windows Vista's style engine. + + \sa QMacStyle, QWindowsXPStyle, QFusionStyle +*/ + +/*! + Constructs a QWindowsVistaStyle object. +*/ +QWindowsVistaStyle::QWindowsVistaStyle() + : QWindowsXPStyle(*new QWindowsVistaStylePrivate) +{ +} + +/*! + Destructor. +*/ +QWindowsVistaStyle::~QWindowsVistaStyle() +{ +} + +//convert Qt state flags to uxtheme button states +static int buttonStateId(int flags, int partId) +{ + int stateId = 0; + if (partId == BP_RADIOBUTTON || partId == BP_CHECKBOX) { + if (!(flags & QStyle::State_Enabled)) + stateId = RBS_UNCHECKEDDISABLED; + else if (flags & QStyle::State_Sunken) + stateId = RBS_UNCHECKEDPRESSED; + else if (flags & QStyle::State_MouseOver) + stateId = RBS_UNCHECKEDHOT; + else + stateId = RBS_UNCHECKEDNORMAL; + + if (flags & QStyle::State_On) + stateId += RBS_CHECKEDNORMAL-1; + + } else if (partId == BP_PUSHBUTTON) { + if (!(flags & QStyle::State_Enabled)) + stateId = PBS_DISABLED; + else if (flags & (QStyle::State_Sunken | QStyle::State_On)) + stateId = PBS_PRESSED; + else if (flags & QStyle::State_MouseOver) + stateId = PBS_HOT; + else + stateId = PBS_NORMAL; + } else { + Q_ASSERT(1); + } + return stateId; +} + +bool QWindowsVistaAnimation::isUpdateNeeded() const +{ + return QWindowsVistaStylePrivate::useVista(); +} + +void QWindowsVistaAnimation::paint(QPainter *painter, const QStyleOption *option) +{ + painter->drawImage(option->rect, currentImage()); +} + +static inline bool supportsStateTransition(QStyle::PrimitiveElement element, + const QStyleOption *option, + const QWidget *widget) +{ + bool result = false; + switch (element) { + case QStyle::PE_IndicatorRadioButton: + case QStyle::PE_IndicatorCheckBox: + result = true; + break; + // QTBUG-40634, do not animate when color is set in palette for PE_PanelLineEdit. + case QStyle::PE_FrameLineEdit: + result = !QWindowsXPStylePrivate::isLineEditBaseColorSet(option, widget); + break; + default: + break; + } + return result; +} + +/*! + \internal + + Animations are used for some state transitions on specific widgets. + + Only one running animation can exist for a widget at any specific + time. Animations can be added through + QWindowsVistaStylePrivate::startAnimation(Animation *) and any + existing animation on a widget can be retrieved with + QWindowsVistaStylePrivate::widgetAnimation(Widget *). + + Once an animation has been started, + QWindowsVistaStylePrivate::timerEvent(QTimerEvent *) will + continuously call update() on the widget until it is stopped, + meaning that drawPrimitive will be called many times until the + transition has completed. During this time, the result will be + retrieved by the Animation::paint(...) function and not by the style + itself. + + To determine if a transition should occur, the style needs to know + the previous state of the widget as well as the current one. This is + solved by updating dynamic properties on the widget every time the + function is called. + + Transitions interrupting existing transitions should always be + smooth, so whenever a hover-transition is started on a pulsating + button, it uses the current frame of the pulse-animation as the + starting image for the hover transition. + + */ + +void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func()); + + int state = option->state; + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + + if ((option->state & State_Enabled) && d->transitionsEnabled() && canAnimate(option)) { + { + QRect oldRect; + QRect newRect; + + if (supportsStateTransition(element, option, widget)) { + // Retrieve and update the dynamic properties tracking + // the previous state of the widget: + QObject *styleObject = option->styleObject; + styleObject->setProperty("_q_no_animation", true); + + int oldState = styleObject->property("_q_stylestate").toInt(); + oldRect = styleObject->property("_q_stylerect").toRect(); + newRect = option->rect; + styleObject->setProperty("_q_stylestate", (int)option->state); + styleObject->setProperty("_q_stylerect", option->rect); + + bool doTransition = oldState && + ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver)); + + if (oldRect != newRect || + (state & State_Enabled) != (oldState & State_Enabled) || + (state & State_Active) != (oldState & State_Active)) + d->stopAnimation(styleObject); + + if (option->state & State_ReadOnly && element == PE_FrameLineEdit) // Do not animate read only line edits + doTransition = false; + + if (doTransition) { + QStyleOption *styleOption = clonedAnimationStyleOption(option); + styleOption->state = (QStyle::State)oldState; + + QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject)); + QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject); + + // We create separate images for the initial and final transition states and store them in the + // Transition object. + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + + // If we have a running animation on the widget already, we will use that to paint the initial + // state of the new transition, this ensures a smooth transition from a current animation such as a + // pulsating default button into the intended target state. + if (!anim) + proxy()->drawPrimitive(element, styleOption, &startPainter, widget); + else + anim->paint(&startPainter, styleOption); + + t->setStartImage(startImage); + + // The end state of the transition is simply the result we would have painted + // if the style was not animated. + styleOption->styleObject = 0; + styleOption->state = option->state; + proxy()->drawPrimitive(element, styleOption, &endPainter, widget); + + + t->setEndImage(endImage); + + HTHEME theme; + int partId; + DWORD duration; + int fromState = 0; + int toState = 0; + + //translate state flags to UXTHEME states : + if (element == PE_FrameLineEdit) { + theme = OpenThemeData(0, L"Edit"); + partId = EP_EDITBORDER_NOSCROLL; + + if (oldState & State_MouseOver) + fromState = ETS_HOT; + else if (oldState & State_HasFocus) + fromState = ETS_FOCUSED; + else + fromState = ETS_NORMAL; + + if (state & State_MouseOver) + toState = ETS_HOT; + else if (state & State_HasFocus) + toState = ETS_FOCUSED; + else + toState = ETS_NORMAL; + + } else { + theme = OpenThemeData(0, L"Button"); + if (element == PE_IndicatorRadioButton) + partId = BP_RADIOBUTTON; + else if (element == PE_IndicatorCheckBox) + partId = BP_CHECKBOX; + else + partId = BP_PUSHBUTTON; + + fromState = buttonStateId(oldState, partId); + toState = buttonStateId(option->state, partId); + } + + // Retrieve the transition time between the states from the system. + if (theme + && SUCCEEDED(GetThemeTransitionDuration(theme, partId, fromState, toState, + TMT_TRANSITIONDURATIONS, &duration))) { + t->setDuration(duration); + } + t->setStartTime(QTime::currentTime()); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(t); + } + styleObject->setProperty("_q_no_animation", false); + } + + } // End of animation part + } + + QRect rect = option->rect; + + switch (element) { + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + int stateId = HSAS_SORTEDDOWN; + if (header->sortIndicator & QStyleOptionHeader::SortDown) + stateId = HSAS_SORTEDUP; //note that the uxtheme sort down indicator is the inverse of ours + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::HeaderTheme, + HP_HEADERSORTARROW, stateId, option->rect); + d->drawBackground(theme); + } + break; + + case PE_IndicatorBranch: + { + XPThemeData theme(widget, painter, QWindowsXPStylePrivate::VistaTreeViewTheme); + static int decoration_size = 0; + if (!decoration_size && theme.isValid()) { + XPThemeData themeSize = theme; + themeSize.partId = TVP_HOTGLYPH; + themeSize.stateId = GLPS_OPENED; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + decoration_size = qRound(qMax(size.width(), size.height())); + } + int mid_h = option->rect.x() + option->rect.width() / 2; + int mid_v = option->rect.y() + option->rect.height() / 2; + int bef_h = mid_h; + int bef_v = mid_v; + int aft_h = mid_h; + int aft_v = mid_v; + if (option->state & State_Children) { + int delta = decoration_size / 2; + theme.rect = QRect(bef_h - delta, bef_v - delta, decoration_size, decoration_size); + theme.partId = option->state & State_MouseOver ? TVP_HOTGLYPH : TVP_GLYPH; + theme.stateId = option->state & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED; + if (option->direction == Qt::RightToLeft) + theme.mirrorHorizontally = true; + d->drawBackground(theme); + bef_h -= delta + 2; + bef_v -= delta + 2; + aft_h += delta - 2; + aft_v += delta - 2; + } +#if 0 + QBrush brush(option->palette.dark().color(), Qt::Dense4Pattern); + if (option->state & State_Item) { + if (option->direction == Qt::RightToLeft) + painter->fillRect(option->rect.left(), mid_v, bef_h - option->rect.left(), 1, brush); + else + painter->fillRect(aft_h, mid_v, option->rect.right() - aft_h + 1, 1, brush); + } + if (option->state & State_Sibling && option->rect.bottom() > aft_v) + painter->fillRect(mid_h, aft_v, 1, option->rect.bottom() - aft_v + 1, brush); + if (option->state & (State_Open | State_Children | State_Item | State_Sibling) && (bef_v > option->rect.y())) + painter->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush); +#endif + } + break; + + case PE_PanelButtonBevel: + case PE_IndicatorCheckBox: + case PE_IndicatorRadioButton: + { + if (QWindowsVistaAnimation *a = + qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))){ + a->paint(painter, option); + } else { + QWindowsXPStyle::drawPrimitive(element, option, painter, widget); + } + } + break; + + case PE_FrameMenu: + { + int stateId = option->state & State_Active ? MB_ACTIVE : MB_INACTIVE; + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPBORDERS, stateId, option->rect); + d->drawBackground(theme); + } + break; + case PE_Frame: { +#ifndef QT_NO_ACCESSIBILITY + if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText) + || QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) || +#else + if ( +#endif + (widget && widget->inherits("QTextEdit"))) { + painter->save(); + int stateId = ETS_NORMAL; + if (!(state & State_Enabled)) + stateId = ETS_DISABLED; + else if (state & State_ReadOnly) + stateId = ETS_READONLY; + else if (state & State_HasFocus) + stateId = ETS_SELECTED; + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::EditTheme, + EP_EDITBORDER_HVSCROLL, stateId, option->rect); + // Since EP_EDITBORDER_HVSCROLL does not us borderfill, theme.noContent cannot be used for clipping + int borderSize = 1; + GetThemeInt(theme.handle(), theme.partId, theme.stateId, TMT_BORDERSIZE, &borderSize); + QRegion clipRegion = option->rect; + QRegion content = option->rect.adjusted(borderSize, borderSize, -borderSize, -borderSize); + clipRegion ^= content; + painter->setClipRegion(clipRegion); + d->drawBackground(theme); + painter->restore(); + } else { + QWindowsXPStyle::drawPrimitive(element, option, painter, widget); + } + } + break; + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + bool isEnabled = option->state & State_Enabled; + if (QWindowsXPStylePrivate::isLineEditBaseColorSet(option, widget)) { + painter->fillRect(panel->rect, panel->palette.brush(QPalette::Base)); + } else { + int partId = EP_BACKGROUND; + int stateId = EBS_NORMAL; + if (!isEnabled) + stateId = EBS_DISABLED; + else if (state & State_ReadOnly) + stateId = EBS_READONLY; + else if (state & State_MouseOver) + stateId = EBS_HOT; + + XPThemeData theme(0, painter, QWindowsXPStylePrivate::EditTheme, + partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + int bgType; + GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &bgType); + if( bgType == BT_IMAGEFILE ) { + d->drawBackground(theme); + } else { + QBrush fillColor = option->palette.brush(QPalette::Base); + if (!isEnabled) { + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(theme.handle(), theme.partId, theme.stateId, TMT_FILLCOLOR, &origin); + // Use only if the fill property comes from our part + if ((origin == PO_PART || origin == PO_STATE)) { + COLORREF bgRef; + GetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef); + fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef))); + } + } + painter->fillRect(option->rect, fillColor); + } + } + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget); + return; + } + break; + + case PE_FrameLineEdit: + if (QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) { + anim->paint(painter, option); + } else { + QPainter *p = painter; + if (QWindowsXPStylePrivate::isItemViewDelegateLineEdit(widget)) { + // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class. + QPen oldPen = p->pen(); + // Inner white border + p->setPen(QPen(option->palette.base().color(), 1)); + p->drawRect(option->rect.adjusted(1, 1, -2, -2)); + // Outer dark border + p->setPen(QPen(option->palette.shadow().color(), 1)); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + return; + } else { + int stateId = ETS_NORMAL; + if (!(state & State_Enabled)) + stateId = ETS_DISABLED; + else if (state & State_ReadOnly) + stateId = ETS_READONLY; + else if (state & State_MouseOver) + stateId = ETS_HOT; + else if (state & State_HasFocus) + stateId = ETS_SELECTED; + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::EditTheme, + EP_EDITBORDER_NOSCROLL, stateId, option->rect); + theme.noContent = true; + painter->save(); + QRegion clipRegion = option->rect; + clipRegion -= option->rect.adjusted(2, 2, -2, -2); + painter->setClipRegion(clipRegion); + d->drawBackground(theme); + painter->restore(); + } + } + break; + + case PE_IndicatorToolBarHandle: + { + XPThemeData theme; + QRect rect; + if (option->state & State_Horizontal) { + theme = XPThemeData(widget, painter, + QWindowsXPStylePrivate::RebarTheme, + RP_GRIPPER, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2)); + rect = option->rect.adjusted(0, 1, 0, -2); + rect.setWidth(4); + } else { + theme = XPThemeData(widget, painter, QWindowsXPStylePrivate::RebarTheme, + RP_GRIPPERVERT, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2)); + rect = option->rect.adjusted(1, 0, -1, 0); + rect.setHeight(4); + } + theme.rect = rect; + d->drawBackground(theme); + } + break; + + case PE_IndicatorToolBarSeparator: + { + QPen pen = painter->pen(); + int margin = 3; + painter->setPen(option->palette.background().color().darker(114)); + if (option->state & State_Horizontal) { + int x1 = option->rect.center().x(); + painter->drawLine(QPoint(x1, option->rect.top() + margin), QPoint(x1, option->rect.bottom() - margin)); + } else { + int y1 = option->rect.center().y(); + painter->drawLine(QPoint(option->rect.left() + margin, y1), QPoint(option->rect.right() - margin, y1)); + } + painter->setPen(pen); + } + break; + + case PE_PanelTipLabel: { + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::ToolTipTheme, + TTP_STANDARD, TTSS_NORMAL, option->rect); + d->drawBackground(theme); + break; + } + + case PE_PanelItemViewItem: + { + const QStyleOptionViewItem *vopt; + bool newStyle = true; + QAbstractItemView::SelectionBehavior selectionBehavior = QAbstractItemView::SelectRows; + QAbstractItemView::SelectionMode selectionMode = QAbstractItemView::NoSelection; + if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget)) { + newStyle = !qobject_cast<const QTableView*>(view); + selectionBehavior = view->selectionBehavior(); + selectionMode = view->selectionMode(); +#ifndef QT_NO_ACCESSIBILITY + } else if (!widget) { + newStyle = !QStyleHelper::hasAncestor(option->styleObject, QAccessible::MenuItem) ; +#endif + } + + if (newStyle && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) { + bool selected = vopt->state & QStyle::State_Selected; + const bool hover = selectionMode != QAbstractItemView::NoSelection && (vopt->state & QStyle::State_MouseOver); + bool active = vopt->state & QStyle::State_Active; + + if (vopt->features & QStyleOptionViewItem::Alternate) + painter->fillRect(vopt->rect, vopt->palette.alternateBase()); + + QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + QRect itemRect = subElementRect(QStyle::SE_ItemViewItemFocusRect, option, widget).adjusted(-1, 0, 1, 0); + itemRect.setTop(vopt->rect.top()); + itemRect.setBottom(vopt->rect.bottom()); + + QSize sectionSize = itemRect.size(); + if (vopt->showDecorationSelected) + sectionSize = vopt->rect.size(); + + if (selectionBehavior == QAbstractItemView::SelectRows) + sectionSize.setWidth(vopt->rect.width()); + QPixmap pixmap; + + if (vopt->backgroundBrush.style() != Qt::NoBrush) { + const QPointF oldBrushOrigin = painter->brushOrigin(); + painter->setBrushOrigin(vopt->rect.topLeft()); + painter->fillRect(vopt->rect, vopt->backgroundBrush); + painter->setBrushOrigin(oldBrushOrigin); + } + + if (hover || selected) { + if (sectionSize.width() > 0 && sectionSize.height() > 0) { + QString key = QString::fromLatin1("qvdelegate-%1-%2-%3-%4-%5").arg(sectionSize.width()) + .arg(sectionSize.height()).arg(selected).arg(active).arg(hover); + if (!QPixmapCache::find(key, pixmap)) { + pixmap = QPixmap(sectionSize); + pixmap.fill(Qt::transparent); + + int state; + if (selected && hover) + state = LISS_HOTSELECTED; + else if (selected && !active) + state = LISS_SELECTEDNOTFOCUS; + else if (selected) + state = LISS_SELECTED; + else + state = LISS_HOT; + + QPainter pixmapPainter(&pixmap); + XPThemeData theme(widget, &pixmapPainter, + QWindowsXPStylePrivate::VistaTreeViewTheme, + LVP_LISTITEM, state, QRect(0, 0, sectionSize.width(), sectionSize.height())); + if (theme.isValid()) { + d->drawBackground(theme); + } else { + QWindowsXPStyle::drawPrimitive(PE_PanelItemViewItem, option, painter, widget); + break;; + } + QPixmapCache::insert(key, pixmap); + } + } + + if (vopt->showDecorationSelected) { + const int frame = 2; //Assumes a 2 pixel pixmap border + QRect srcRect = QRect(0, 0, sectionSize.width(), sectionSize.height()); + QRect pixmapRect = vopt->rect; + bool reverse = vopt->direction == Qt::RightToLeft; + bool leftSection = vopt->viewItemPosition == QStyleOptionViewItem::Beginning; + bool rightSection = vopt->viewItemPosition == QStyleOptionViewItem::End; + if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne + || vopt->viewItemPosition == QStyleOptionViewItem::Invalid) + painter->drawPixmap(pixmapRect.topLeft(), pixmap); + else if (reverse ? rightSection : leftSection){ + painter->drawPixmap(QRect(pixmapRect.topLeft(), + QSize(frame, pixmapRect.height())), pixmap, + QRect(QPoint(0, 0), QSize(frame, pixmapRect.height()))); + painter->drawPixmap(pixmapRect.adjusted(frame, 0, 0, 0), + pixmap, srcRect.adjusted(frame, 0, -frame, 0)); + } else if (reverse ? leftSection : rightSection) { + painter->drawPixmap(QRect(pixmapRect.topRight() - QPoint(frame - 1, 0), + QSize(frame, pixmapRect.height())), pixmap, + QRect(QPoint(pixmapRect.width() - frame, 0), + QSize(frame, pixmapRect.height()))); + painter->drawPixmap(pixmapRect.adjusted(0, 0, -frame, 0), + pixmap, srcRect.adjusted(frame, 0, -frame, 0)); + } else if (vopt->viewItemPosition == QStyleOptionViewItem::Middle) + painter->drawPixmap(pixmapRect, pixmap, + srcRect.adjusted(frame, 0, -frame, 0)); + } else { + if (vopt->text.isEmpty() && vopt->icon.isNull()) + break; + painter->drawPixmap(itemRect.topLeft(), pixmap); + } + } + } else { + QWindowsXPStyle::drawPrimitive(element, option, painter, widget); + } + break; + } + case PE_Widget: + { +#if QT_CONFIG(dialogbuttonbox) + const QDialogButtonBox *buttonBox = 0; + + if (qobject_cast<const QMessageBox *> (widget)) + buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<const QInputDialog *> (widget)) + buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); +#endif // QT_NO_INPUTDIALOG + + if (buttonBox) { + //draw white panel part + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::TaskDialogTheme, + TDLG_PRIMARYPANEL, 0, option->rect); + QRect toprect = option->rect; + toprect.setBottom(buttonBox->geometry().top()); + theme.rect = toprect; + d->drawBackground(theme); + + //draw bottom panel part + QRect buttonRect = option->rect; + buttonRect.setTop(buttonBox->geometry().top()); + theme.rect = buttonRect; + theme.partId = TDLG_SECONDARYPANEL; + d->drawBackground(theme); + } +#endif + } + break; + default: + QWindowsXPStyle::drawPrimitive(element, option, painter, widget); + break; + } +} + + +/*! + \internal + + see drawPrimitive for comments on the animation support + */ +void QWindowsVistaStyle::drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func()); + + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawControl(element, option, painter, widget); + return; + } + + bool selected = option->state & State_Selected; + bool pressed = option->state & State_Sunken; + bool disabled = !(option->state & State_Enabled); + + int state = option->state; + int themeNumber = -1; + + QRect rect(option->rect); + State flags = option->state; + int partId = 0; + int stateId = 0; + + if (d->transitionsEnabled() && canAnimate(option)) + { + if (element == CE_PushButtonBevel) { + QRect oldRect; + QRect newRect; + + QObject *styleObject = option->styleObject; + + int oldState = styleObject->property("_q_stylestate").toInt(); + oldRect = styleObject->property("_q_stylerect").toRect(); + newRect = option->rect; + styleObject->setProperty("_q_stylestate", (int)option->state); + styleObject->setProperty("_q_stylerect", option->rect); + + bool wasDefault = false; + bool isDefault = false; + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + wasDefault = styleObject->property("_q_isdefault").toBool(); + isDefault = button->features & QStyleOptionButton::DefaultButton; + styleObject->setProperty("_q_isdefault", isDefault); + } + + bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver)); + + if (oldRect != newRect || (wasDefault && !isDefault)) { + doTransition = false; + d->stopAnimation(styleObject); + } + + if (doTransition) { + styleObject->setProperty("_q_no_animation", true); + + QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject); + QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject)); + QStyleOption *styleOption = clonedAnimationStyleOption(option); + styleOption->state = (QStyle::State)oldState; + + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + // Use current state of existing animation if already one is running + if (!anim) { + proxy()->drawControl(element, styleOption, &startPainter, widget); + } else { + anim->paint(&startPainter, styleOption); + d->stopAnimation(styleObject); + } + + t->setStartImage(startImage); + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + styleOption->state = option->state; + proxy()->drawControl(element, styleOption, &endPainter, widget); + t->setEndImage(endImage); + + + DWORD duration = 0; + const HTHEME theme = OpenThemeData(0, L"Button"); + + int fromState = buttonStateId(oldState, BP_PUSHBUTTON); + int toState = buttonStateId(option->state, BP_PUSHBUTTON); + if (GetThemeTransitionDuration(theme, BP_PUSHBUTTON, fromState, toState, TMT_TRANSITIONDURATIONS, &duration) == S_OK) + t->setDuration(duration); + else + t->setDuration(0); + t->setStartTime(QTime::currentTime()); + styleObject->setProperty("_q_no_animation", false); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(t); + } + + QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject)); + if (anim) { + anim->paint(painter, option); + return; + } + + } + } + switch (element) { + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) + { + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_PUSHBUTTON; + if (btn->features & QStyleOptionButton::CommandLinkButton) + partId = BP_COMMANDLINK; + bool justFlat = (btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken)); + if (!(flags & State_Enabled) && !(btn->features & QStyleOptionButton::Flat)) + stateId = PBS_DISABLED; + else if (justFlat) + ; + else if (flags & (State_Sunken | State_On)) + stateId = PBS_PRESSED; + else if (flags & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton && (state & State_Active)) + stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + + if (!justFlat) { + + if (d->transitionsEnabled() && (btn->features & QStyleOptionButton::DefaultButton) && + !(state & (State_Sunken | State_On)) && !(state & State_MouseOver) && + (state & State_Enabled) && (state & State_Active)) + { + QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option))); + + if (!anim) { + QImage startImage = createAnimationBuffer(option, widget); + QImage alternateImage = createAnimationBuffer(option, widget); + + QWindowsVistaPulse *pulse = new QWindowsVistaPulse(styleObject(option)); + + QPainter startPainter(&startImage); + stateId = PBS_DEFAULTED; + XPThemeData theme(widget, &startPainter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + + QPainter alternatePainter(&alternateImage); + theme.stateId = PBS_DEFAULTED_ANIMATING; + theme.painter = &alternatePainter; + d->drawBackground(theme); + pulse->setStartImage(startImage); + pulse->setEndImage(alternateImage); + pulse->setStartTime(QTime::currentTime()); + pulse->setDuration(2000); + d->startAnimation(pulse); + anim = pulse; + } + + if (anim) + anim->paint(painter, option); + else { + XPThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + } + } + else { + XPThemeData theme(widget, painter, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + } + } + + if (btn->features & QStyleOptionButton::HasMenu) { + int mbiw = 0, mbih = 0; + XPThemeData theme(widget, 0, QWindowsXPStylePrivate::ToolBarTheme, + TP_DROPDOWNBUTTON); + if (theme.isValid()) { + const QSizeF size = theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + if (!size.isEmpty()) { + mbiw = qRound(size.width()); + mbih = qRound(size.height()); + } + } + QRect ir = subElementRect(SE_PushButtonContents, option, 0); + QStyleOptionButton newBtn = *btn; + newBtn.rect = QStyle::visualRect(option->direction, option->rect, + QRect(ir.right() - mbiw - 2, + option->rect.top() + (option->rect.height()/2) - (mbih/2), + mbiw + 1, mbih + 1)); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget); + } + return; + } + break; + + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *bar + = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + bool isIndeterminate = (bar->minimum == 0 && bar->maximum == 0); + const bool vertical = bar->orientation == Qt::Vertical; + const bool inverted = bar->invertedAppearance; + + if (isIndeterminate || (bar->progress > 0 && (bar->progress < bar->maximum) && d->transitionsEnabled())) { + if (!d->animation(styleObject(option))) + d->startAnimation(new QProgressStyleAnimation(d->animationFps, styleObject(option))); + } else { + d->stopAnimation(styleObject(option)); + } + + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::ProgressTheme, + vertical ? PP_FILLVERT : PP_FILL); + theme.rect = option->rect; + bool reverse = (bar->direction == Qt::LeftToRight && inverted) || (bar->direction == Qt::RightToLeft && !inverted); + QTime current = QTime::currentTime(); + + if (isIndeterminate) { + if (QProgressStyleAnimation *a = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) { + int glowSize = 120; + int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width()); + int animOffset = a->startTime().msecsTo(current) / 4; + if (animOffset > animationWidth) + a->setStartTime(QTime::currentTime()); + painter->save(); + painter->setClipRect(theme.rect); + QRect animRect; + QSize pixmapSize(14, 14); + if (vertical) { + animRect = QRect(theme.rect.left(), + inverted ? rect.top() - glowSize + animOffset : + rect.bottom() + glowSize - animOffset, + rect.width(), glowSize); + pixmapSize.setHeight(animRect.height()); + } else { + animRect = QRect(rect.left() - glowSize + animOffset, + rect.top(), glowSize, rect.height()); + animRect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, + option->rect, animRect); + pixmapSize.setWidth(animRect.width()); + } + QString name = QString::fromLatin1("qiprogress-%1-%2").arg(pixmapSize.width()).arg(pixmapSize.height()); + QPixmap pixmap; + if (!QPixmapCache::find(name, pixmap)) { + QImage image(pixmapSize, QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter imagePainter(&image); + theme.painter = &imagePainter; + theme.partId = vertical ? PP_FILLVERT : PP_FILL; + theme.rect = QRect(QPoint(0,0), animRect.size()); + QLinearGradient alphaGradient(0, 0, vertical ? 0 : image.width(), + vertical ? image.height() : 0); + alphaGradient.setColorAt(0, QColor(0, 0, 0, 0)); + alphaGradient.setColorAt(0.5, QColor(0, 0, 0, 220)); + alphaGradient.setColorAt(1, QColor(0, 0, 0, 0)); + imagePainter.fillRect(image.rect(), alphaGradient); + imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + d->drawBackground(theme); + imagePainter.end(); + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(name, pixmap); + } + painter->drawPixmap(animRect, pixmap); + painter->restore(); + } + } + else { + qint64 progress = qMax<qint64>(bar->progress, bar->minimum); // workaround for bug in QProgressBar + + if (vertical) { + int maxHeight = option->rect.height(); + int minHeight = 0; + double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxHeight); + int height = isIndeterminate ? maxHeight: qMax(int(vc6_workaround), minHeight); + theme.rect.setHeight(height); + if (!inverted) + theme.rect.moveTop(rect.height() - theme.rect.height()); + } else { + int maxWidth = option->rect.width(); + int minWidth = 0; + double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth); + int width = isIndeterminate ? maxWidth : qMax(int(vc6_workaround), minWidth); + theme.rect.setWidth(width); + theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, + option->rect, theme.rect); + } + d->drawBackground(theme); + + if (QProgressStyleAnimation *a = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) { + int glowSize = 140; + int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width()); + int animOffset = a->startTime().msecsTo(current) / 4; + theme.partId = vertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY; + if (animOffset > animationWidth) { + if (bar->progress < bar->maximum) + a->setStartTime(QTime::currentTime()); + else + d->stopAnimation(styleObject(option)); //we stop the glow motion only after it has + //moved out of view + } + painter->save(); + painter->setClipRect(theme.rect); + if (vertical) { + theme.rect = QRect(theme.rect.left(), + inverted ? rect.top() - glowSize + animOffset : + rect.bottom() + glowSize - animOffset, + rect.width(), glowSize); + } else { + theme.rect = QRect(rect.left() - glowSize + animOffset,rect.top(), glowSize, rect.height()); + theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, option->rect, theme.rect); + } + d->drawBackground(theme); + painter->restore(); + } + } + } + break; + + case CE_MenuBarItem: + { + + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem) + break; + + QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText; + QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal); + + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + if (widget && mbi->palette.color(QPalette::Window) != Qt::transparent) { // Not needed for QtQuick Controls + //The rect adjustment is a workaround for the menu not really filling its background. + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_BARBACKGROUND, 0, option->rect.adjusted(-1, 0, 2, 1)); + d->drawBackground(theme); + } + + int stateId = MBI_NORMAL; + if (disabled) + stateId = MBI_DISABLED; + else if (pressed) + stateId = MBI_PUSHED; + else if (selected) + stateId = MBI_HOT; + + XPThemeData theme2(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_BARITEM, stateId, option->rect); + d->drawBackground(theme2); + + if (!pix.isNull()) + drawItemPixmap(painter, mbi->rect, alignment, pix); + else + drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole); + } + } + break; +#ifndef QT_NO_MENU + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + // windows always has a check column, regardless whether we have an icon or not + const qreal factor = QWindowsXPStylePrivate::nativeMetricScaleFactor(widget); + int checkcol = qRound(qreal(25) * factor); + const int gutterWidth = qRound(qreal(3) * factor); + { + XPThemeData theme(widget, 0, QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, MBI_HOT); + XPThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + checkcol = qMax(menuitem->maxIconWidth, qRound(gutterWidth + size.width() + margins.left() + margins.right())); + } + QRect rect = option->rect; + + //draw vertical menu line + if (option->direction == Qt::LeftToRight) + checkcol += rect.x(); + QPoint p1 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.top())); + QPoint p2 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.bottom())); + QRect gutterRect(p1.x(), p1.y(), gutterWidth, p2.y() - p1.y() + 1); + XPThemeData theme2(widget, painter, QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPGUTTER, stateId, gutterRect); + d->drawBackground(theme2); + + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + bool dis = !(menuitem->state & State_Enabled); + bool checked = menuitem->checkType != QStyleOptionMenuItem::NotCheckable + ? menuitem->checked : false; + bool act = menuitem->state & State_Selected; + + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) { + int yoff = y-2 + h / 2; + const int separatorSize = qRound(qreal(6) * QWindowsStylePrivate::nativeMetricScaleFactor(widget)); + QPoint p1 = QPoint(x + checkcol, yoff); + QPoint p2 = QPoint(x + w + separatorSize, yoff); + stateId = MBI_HOT; + QRect subRect(p1.x() + (gutterWidth - menuitem->rect.x()), p1.y(), + p2.x() - p1.x(), separatorSize); + subRect = QStyle::visualRect(option->direction, option->rect, subRect ); + XPThemeData theme2(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPSEPARATOR, stateId, subRect); + d->drawBackground(theme2); + return; + } + + QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(), + menuitem->rect.y(), checkcol - (gutterWidth + menuitem->rect.x()), menuitem->rect.height())); + + if (act) { + stateId = dis ? MBI_DISABLED : MBI_HOT; + XPThemeData theme2(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPITEM, stateId, option->rect); + d->drawBackground(theme2); + } + + if (checked) { + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, + menuitem->icon.isNull() ? MBI_HOT : MBI_PUSHED, vCheckRect); + XPThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + QRect checkRect(0, 0, qRound(size.width() + margins.left() + margins.right()), + qRound(size.height() + margins.bottom() + margins.top())); + checkRect.moveCenter(vCheckRect.center()); + theme.rect = checkRect; + + d->drawBackground(theme); + + if (menuitem->icon.isNull()) { + checkRect = QRect(QPoint(0, 0), size.toSize()); + checkRect.moveCenter(theme.rect.center()); + theme.rect = checkRect; + + theme.partId = MENU_POPUPCHECK; + bool bullet = menuitem->checkType & QStyleOptionMenuItem::Exclusive; + if (dis) + theme.stateId = bullet ? MC_BULLETDISABLED: MC_CHECKMARKDISABLED; + else + theme.stateId = bullet ? MC_BULLETNORMAL: MC_CHECKMARKNORMAL; + d->drawBackground(theme); + } + } + + if (!menuitem->icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + if (act && !dis) + mode = QIcon::Active; + QPixmap pixmap; + if (checked) + pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On); + else + pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode); + const int pixw = pixmap.width() / pixmap.devicePixelRatio(); + const int pixh = pixmap.height() / pixmap.devicePixelRatio(); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center()); + painter->setPen(menuitem->palette.text().color()); + painter->drawPixmap(pmr.topLeft(), pixmap); + } + + painter->setPen(menuitem->palette.buttonText().color()); + + const QColor textColor = menuitem->palette.text().color(); + if (dis) + painter->setPen(textColor); + + int xm = windowsItemFrame + checkcol + windowsItemHMargin + (gutterWidth - menuitem->rect.x()) - 1; + int xpos = menuitem->rect.x() + xm; + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { // draw text + painter->save(); + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget)) + text_flags |= Qt::TextHideMnemonic; + text_flags |= Qt::AlignLeft; + if (t >= 0) { + QRect vShortcutRect = visualRect(option->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom()))); + painter->drawText(vShortcutRect, text_flags, s.mid(t + 1)); + s = s.left(t); + } + QFont font = menuitem->font; + if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem) + font.setBold(true); + painter->setFont(font); + painter->setPen(textColor); + painter->drawText(vTextRect, text_flags, s.left(t)); + painter->restore(); + } + if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + int dim = (h - 2 * windowsItemFrame) / 2; + PrimitiveElement arrow; + arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim; + QRect vSubMenuRect = visualRect(option->direction, menuitem->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim)); + QStyleOptionMenuItem newMI = *menuitem; + newMI.rect = vSubMenuRect; + newMI.state = dis ? State_None : State_Enabled; + proxy()->drawPrimitive(arrow, &newMI, painter, widget); + } + } + break; +#endif // QT_NO_MENU + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + partId = HP_HEADERITEM; + if (flags & State_Sunken) + stateId = HIS_PRESSED; + else if (flags & State_MouseOver) + stateId = HIS_HOT; + else + stateId = HIS_NORMAL; + + if (header->sortIndicator != QStyleOptionHeader::None) + stateId += 3; + + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::HeaderTheme, + partId, stateId, option->rect); + d->drawBackground(theme); + } + break; + case CE_MenuBarEmptyArea: + { + stateId = MBI_NORMAL; + if (!(state & State_Enabled)) + stateId = MBI_DISABLED; + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::MenuTheme, + MENU_BARBACKGROUND, stateId, option->rect); + d->drawBackground(theme); + } + break; + case CE_ToolBar: + if (const QStyleOptionToolBar *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + QPalette pal = option->palette; + pal.setColor(QPalette::Dark, option->palette.background().color().darker(130)); + QStyleOptionToolBar copyOpt = *toolbar; + copyOpt.palette = pal; + QWindowsStyle::drawControl(element, ©Opt, painter, widget); + } + break; + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(widget); + QRect rect = option->rect; + if (dockWidget && dockWidget->isFloating()) { + QWindowsXPStyle::drawControl(element, option, painter, widget); + break; //otherwise fall through + } + + const bool verticalTitleBar = dwOpt->verticalTitleBar; + + if (verticalTitleBar) { + rect = rect.transposed(); + + painter->translate(rect.left() - 1, rect.top() + rect.width()); + painter->rotate(-90); + painter->translate(-rect.left() + 1, -rect.top()); + } + + painter->setBrush(option->palette.background().color().darker(110)); + painter->setPen(option->palette.background().color().darker(130)); + painter->drawRect(rect.adjusted(0, 1, -1, -3)); + + int buttonMargin = 4; + int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget); + int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget); + const QDockWidget *dw = qobject_cast<const QDockWidget *>(widget); + bool isFloating = dw != 0 && dw->isFloating(); + + QRect r = option->rect.adjusted(0, 2, -1, -3); + QRect titleRect = r; + + if (dwOpt->closable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (dwOpt->floatable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (isFloating) { + titleRect.adjust(0, -fw, 0, 0); + if (widget != 0 && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey()) + titleRect.adjust(titleRect.height() + mw, 0, 0, 0); + } else { + titleRect.adjust(mw, 0, 0, 0); + if (!dwOpt->floatable && !dwOpt->closable) + titleRect.adjust(0, 0, -mw, 0); + } + if (!verticalTitleBar) + titleRect = visualRect(dwOpt->direction, r, titleRect); + + if (!dwOpt->title.isEmpty()) { + QString titleText = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, + verticalTitleBar ? titleRect.height() : titleRect.width()); + const int indent = 4; + drawItemText(painter, rect.adjusted(indent + 1, 1, -indent - 1, -1), + Qt::AlignLeft | Qt::AlignVCenter, dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + } + break; +#ifndef QT_NO_ITEMVIEWS + case CE_ItemViewItem: + { + const QStyleOptionViewItem *vopt; + + const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget); + bool newStyle = true; + + if (qobject_cast<const QTableView*>(widget)) + newStyle = false; + + if (newStyle && view && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) { + /* + // We cannot currently get the correct selection color for "explorer style" views + COLORREF cref = 0; + XPThemeData theme(d->treeViewHelper(), 0, QLatin1String("LISTVIEW"), 0, 0); + unsigned int res = GetThemeColor(theme.handle(), LVP_LISTITEM, LISS_SELECTED, TMT_TEXTCOLOR, &cref); + QColor textColor(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + */ + QPalette palette = vopt->palette; + palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text)); + // Note that setting a saturated color here results in ugly XOR colors in the focus rect + palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108)); + QStyleOptionViewItem adjustedOption = *vopt; + adjustedOption.palette = palette; + // We hide the focusrect in singleselection as it is not required + if ((view->selectionMode() == QAbstractItemView::SingleSelection) + && !(vopt->state & State_KeyboardFocusChange)) + adjustedOption.state &= ~State_HasFocus; + QWindowsXPStyle::drawControl(element, &adjustedOption, painter, widget); + } else { + QWindowsXPStyle::drawControl(element, option, painter, widget); + } + break; + } +#endif // QT_NO_ITEMVIEWS +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + QCommonStyle::drawControl(element, option, painter, widget); + break; +#endif // QT_NO_COMBOBOX + default: + QWindowsXPStyle::drawControl(element, option, painter, widget); + break; + } +} + +/*! + \internal + + see drawPrimitive for comments on the animation support + + */ +void QWindowsVistaStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const +{ + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func()); + if (!QWindowsVistaStylePrivate::useVista()) { + QWindowsStyle::drawComplexControl(control, option, painter, widget); + return; + } + + State state = option->state; + SubControls sub = option->subControls; + QRect r = option->rect; + + int partId = 0; + int stateId = 0; + + State flags = option->state; + if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow()) + flags |= State_MouseOver; + + if (d->transitionsEnabled() && canAnimate(option)) + { + + if (control == CC_ScrollBar || control == CC_SpinBox ) { + + QObject *styleObject = option->styleObject; // Can be widget or qquickitem + + int oldState = styleObject->property("_q_stylestate").toInt(); + int oldActiveControls = styleObject->property("_q_stylecontrols").toInt(); + + QRect oldRect = styleObject->property("_q_stylerect").toRect(); + styleObject->setProperty("_q_stylestate", (int)option->state); + styleObject->setProperty("_q_stylecontrols", (int)option->activeSubControls); + styleObject->setProperty("_q_stylerect", option->rect); + + bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver) || + oldActiveControls != int(option->activeSubControls)); + + if (qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect oldSliderPos = styleObject->property("_q_stylesliderpos").toRect(); + QRect currentPos = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + styleObject->setProperty("_q_stylesliderpos", currentPos); + if (oldSliderPos != currentPos) { + doTransition = false; + d->stopAnimation(styleObject); + } + } else if (control == CC_SpinBox) { + //spinboxes have a transition when focus changes + if (!doTransition) + doTransition = (state & State_HasFocus) != (oldState & State_HasFocus); + } + + if (oldRect != option->rect) { + doTransition = false; + d->stopAnimation(styleObject); + } + + if (doTransition) { + QImage startImage = createAnimationBuffer(option, widget); + QPainter startPainter(&startImage); + + QImage endImage = createAnimationBuffer(option, widget); + QPainter endPainter(&endImage); + + QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject)); + QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject); + + // Draw the image that ends the animation by using the current styleoption + QStyleOptionComplex *styleOption = qstyleoption_cast<QStyleOptionComplex*>(clonedAnimationStyleOption(option)); + + styleObject->setProperty("_q_no_animation", true); + + // Draw transition source + if (!anim) { + styleOption->state = (QStyle::State)oldState; + styleOption->activeSubControls = (QStyle::SubControl)oldActiveControls; + proxy()->drawComplexControl(control, styleOption, &startPainter, widget); + } else { + anim->paint(&startPainter, option); + } + t->setStartImage(startImage); + + // Draw transition target + styleOption->state = option->state; + styleOption->activeSubControls = option->activeSubControls; + proxy()->drawComplexControl(control, styleOption, &endPainter, widget); + + styleObject->setProperty("_q_no_animation", false); + + t->setEndImage(endImage); + t->setStartTime(QTime::currentTime()); + + if (option->state & State_MouseOver || option->state & State_Sunken) + t->setDuration(150); + else + t->setDuration(500); + + deleteClonedAnimationStyleOption(styleOption); + d->startAnimation(t); + } + if (QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject))) { + anim->paint(painter, option); + return; + } + } + } + + switch (control) { + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) + { + if (cmb->editable) { + if (sub & SC_ComboBoxEditField) { + partId = EP_EDITBORDER_NOSCROLL; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_MouseOver) + stateId = ETS_HOT; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + + XPThemeData theme(widget, painter, + QWindowsXPStylePrivate::EditTheme, + partId, stateId, r); + + d->drawBackground(theme); + } + if (sub & SC_ComboBoxArrow) { + QRect subRect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ComboboxTheme); + theme.rect = subRect; + partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT; + + if (!(cmb->state & State_Enabled)) + stateId = CBXS_DISABLED; + else if (cmb->state & State_Sunken || cmb->state & State_On) + stateId = CBXS_PRESSED; + else if (cmb->state & State_MouseOver && option->activeSubControls & SC_ComboBoxArrow) + stateId = CBXS_HOT; + else + stateId = CBXS_NORMAL; + + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + + } else { + if (sub & SC_ComboBoxFrame) { + QStyleOptionButton btn; + btn.QStyleOption::operator=(*option); + btn.rect = option->rect.adjusted(-1, -1, 1, 1); + if (sub & SC_ComboBoxArrow) + btn.features = QStyleOptionButton::HasMenu; + proxy()->drawControl(QStyle::CE_PushButton, &btn, painter, widget); + } + } + } + break; + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ScrollBarTheme); + bool maxedOut = (scrollbar->maximum == scrollbar->minimum); + if (maxedOut) + flags &= ~State_Enabled; + + bool isHorz = flags & State_Horizontal; + bool isRTL = option->direction == Qt::RightToLeft; + if (sub & SC_ScrollBarAddLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_LEFTDISABLED : ABS_RIGHTDISABLED) : ABS_DOWNDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_LEFTPRESSED : ABS_RIGHTPRESSED) : ABS_DOWNPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_LEFTHOT : ABS_RIGHTHOT) : ABS_DOWNHOT); + else if (scrollbar->state & State_MouseOver) + stateId = (isHorz ? (isRTL ? ABS_LEFTHOVER : ABS_RIGHTHOVER) : ABS_DOWNHOVER); + else + stateId = (isHorz ? (isRTL ? ABS_LEFTNORMAL : ABS_RIGHTNORMAL) : ABS_DOWNNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSubLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTDISABLED : ABS_LEFTDISABLED) : ABS_UPDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTPRESSED : ABS_LEFTPRESSED) : ABS_UPPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTHOT : ABS_LEFTHOT) : ABS_UPHOT); + else if (scrollbar->state & State_MouseOver) + stateId = (isHorz ? (isRTL ? ABS_RIGHTHOVER : ABS_LEFTHOVER) : ABS_UPHOVER); + else + stateId = (isHorz ? (isRTL ? ABS_RIGHTNORMAL : ABS_LEFTNORMAL) : ABS_UPNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (maxedOut) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget)); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget)); + partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + stateId = SCRBS_DISABLED; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } else { + if (sub & SC_ScrollBarSubPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget); + partId = flags & State_Horizontal ? SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarAddPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget); + partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSlider) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else if (option->state & State_MouseOver) + stateId = SCRBS_HOVER; + else + stateId = SCRBS_NORMAL; + + // Draw handle + theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + theme.stateId = stateId; + d->drawBackground(theme); + + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8) { + const QRect gripperBounds = QWindowsXPStylePrivate::scrollBarGripperBounds(flags, widget, &theme); + // Draw gripper if there is enough space + if (!gripperBounds.isEmpty() && flags & State_Enabled) { + painter->save(); + XPThemeData grippBackground = theme; + grippBackground.partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + theme.rect = gripperBounds; + painter->setClipRegion(d->region(theme));// Only change inside the region of the gripper + d->drawBackground(grippBackground);// The gutter is the grippers background + d->drawBackground(theme); // Transparent gripper ontop of background + painter->restore(); + } + } + } + } + } + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) + { + XPThemeData theme(widget, painter, QWindowsXPStylePrivate::SpinTheme); + if (sb->frame && (sub & SC_SpinBoxFrame)) { + partId = EP_EDITBORDER_NOSCROLL; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_MouseOver) + stateId = ETS_HOT; + else if (flags & State_HasFocus) + stateId = ETS_SELECTED; + else + stateId = ETS_NORMAL; + + XPThemeData ftheme(widget, painter, + QWindowsXPStylePrivate::EditTheme, + partId, stateId, r); + // The spinbox in Windows QStyle is drawn with frameless QLineEdit inside it + // That however breaks with QtQuickControls where this results in transparent + // spinbox background, so if there's no "widget" passed (QtQuickControls case), + // let ftheme.noContent be false, which fixes the spinbox rendering in QQC + ftheme.noContent = (widget != NULL); + d->drawBackground(ftheme); + } + if (sub & SC_SpinBoxUp) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget).adjusted(0, 0, 0, 1); + partId = SPNP_UP; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) || !(flags & State_Enabled)) + stateId = UPS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) + stateId = UPS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_MouseOver)) + stateId = UPS_HOT; + else + stateId = UPS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_SpinBoxDown) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget); + partId = SPNP_DOWN; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) || !(flags & State_Enabled)) + stateId = DNS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) + stateId = DNS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_MouseOver)) + stateId = DNS_HOT; + else + stateId = DNS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + break; +#endif // QT_NO_SPINBOX + default: + QWindowsXPStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +/*! + \internal + */ +QSize QWindowsVistaStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::sizeFromContents(type, option, size, widget); + + QSize sz(size); + switch (type) { + case CT_MenuItem: + sz = QWindowsXPStyle::sizeFromContents(type, option, size, widget); + int minimumHeight; + { + XPThemeData theme(widget, 0, + QWindowsXPStylePrivate::MenuTheme, + MENU_POPUPCHECKBACKGROUND, MBI_HOT); + XPThemeData themeSize = theme; + themeSize.partId = MENU_POPUPCHECK; + themeSize.stateId = 0; + const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + minimumHeight = qMax(qRound(size.height() + margins.bottom() + margins.top()), sz.height()); + sz.rwidth() += qRound(size.width() + margins.left() + margins.right()); + } + + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + if (menuitem->menuItemType != QStyleOptionMenuItem::Separator) + sz.setHeight(minimumHeight); + } + return sz; +#ifndef QT_NO_MENUBAR + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(windowsItemHMargin * 5 + 1, 5); + return sz; +#endif + case CT_ItemViewItem: + sz = QWindowsXPStyle::sizeFromContents(type, option, size, widget); + sz.rheight() += 2; + return sz; + case CT_SpinBox: + { + //Spinbox adds frame twice + sz = QWindowsStyle::sizeFromContents(type, option, size, widget); + int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget); + sz -= QSize(2*border, 2*border); + } + return sz; + case CT_HeaderSection: + { + // When there is a sort indicator it adds to the width but it is shown + // above the text natively and not on the side + if (QStyleOptionHeader *hdr = qstyleoption_cast<QStyleOptionHeader *>(const_cast<QStyleOption *>(option))) { + QStyleOptionHeader::SortIndicator sortInd = hdr->sortIndicator; + hdr->sortIndicator = QStyleOptionHeader::None; + sz = QWindowsXPStyle::sizeFromContents(type, hdr, size, widget); + hdr->sortIndicator = sortInd; + return sz; + } + break; + } + default: + break; + } + return QWindowsXPStyle::sizeFromContents(type, option, size, widget); +} + +/*! + \internal + */ +QRect QWindowsVistaStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::subElementRect(element, option, widget); + + QRect rect = QWindowsXPStyle::subElementRect(element, option, widget); + switch (element) { + + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + MARGINS borderSize; + const HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : 0, L"Button"); + if (theme) { + int stateId = PBS_NORMAL; + if (!(option->state & State_Enabled)) + stateId = PBS_DISABLED; + else if (option->state & State_Sunken) + stateId = PBS_PRESSED; + else if (option->state & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton) + stateId = PBS_DEFAULTED; + + int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget); + rect = option->rect.adjusted(border, border, -border, -border); + + if (SUCCEEDED(GetThemeMargins(theme, NULL, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, NULL, &borderSize))) { + rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight, + -borderSize.cxRightWidth, -borderSize.cyBottomHeight); + rect = visualRect(option->direction, option->rect, rect); + } + } + } + break; + + case SE_HeaderArrow: + { + QRect r = rect; + int h = option->rect.height(); + int w = option->rect.width(); + int x = option->rect.x(); + int y = option->rect.y(); + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget); + + XPThemeData theme(widget, 0, + QWindowsXPStylePrivate::HeaderTheme, + HP_HEADERSORTARROW, HSAS_SORTEDDOWN, option->rect); + + int arrowWidth = 13; + int arrowHeight = 5; + if (theme.isValid()) { + const QSizeF size = theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget); + if (!size.isEmpty()) { + arrowWidth = qRound(size.width()); + arrowHeight = qRound(size.height()); + } + } + if (option->state & State_Horizontal) { + r.setRect(x + w/2 - arrowWidth/2, y , arrowWidth, arrowHeight); + } else { + int vert_size = w / 2; + r.setRect(x + 5, y + h - margin * 2 - vert_size, + w - margin * 2 - 5, vert_size); + } + rect = visualRect(option->direction, option->rect, r); + } + break; + + case SE_HeaderLabel: + { + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget); + QRect r = option->rect; + r.setRect(option->rect.x() + margin, option->rect.y() + margin, + option->rect.width() - margin * 2, option->rect.height() - margin * 2); + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + // Subtract width needed for arrow, if there is one + if (header->sortIndicator != QStyleOptionHeader::None) { + if (!(option->state & State_Horizontal)) //horizontal arrows are positioned on top + r.setHeight(r.height() - (option->rect.width() / 2) - (margin * 2)); + } + } + rect = visualRect(option->direction, option->rect, r); + } + break; + case SE_ProgressBarContents: + rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget); + break; + case SE_ItemViewItemDecoration: + if (qstyleoption_cast<const QStyleOptionViewItem *>(option)) + rect.adjust(-2, 0, 2, 0); + break; + case SE_ItemViewItemFocusRect: + if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) { + QRect textRect = subElementRect(QStyle::SE_ItemViewItemText, option, widget); + QRect displayRect = subElementRect(QStyle::SE_ItemViewItemDecoration, option, widget); + if (!vopt->icon.isNull()) + rect = textRect.united(displayRect); + else + rect = textRect; + rect = rect.adjusted(1, 0, -1, 0); + } + break; + default: + break; + } + return rect; +} + + +/* + This function is used by subControlRect to check if a button + should be drawn for the given subControl given a set of window flags. +*/ +static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb){ + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + const uint flags = tb->titleBarFlags; + bool retVal = false; + switch (sc) { + case QStyle::SC_TitleBarContextHelpButton: + if (flags & Qt::WindowContextHelpButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarMinButton: + if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarNormalButton: + if (isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarMaxButton: + if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarShadeButton: + if (!isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarUnshadeButton: + if (isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarCloseButton: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + case QStyle::SC_TitleBarSysMenu: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + default : + retVal = true; + } + return retVal; +} + + +/*! \internal */ +int QWindowsVistaStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func()); + int ret = 0; + switch (hint) { + case SH_MessageBox_CenterButtons: + ret = false; + break; + case SH_ToolTip_Mask: + if (option) { + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(returnData)) { + ret = true; + XPThemeData themeData(widget, 0, + QWindowsXPStylePrivate::ToolTipTheme, + TTP_STANDARD, TTSS_NORMAL, option->rect); + mask->region = d->region(themeData); + } + } + break; + case SH_Table_GridLineColor: + if (option) + ret = option->palette.color(QPalette::Base).darker(118).rgb(); + else + ret = -1; + break; + case SH_Header_ArrowAlignment: + ret = Qt::AlignTop | Qt::AlignHCenter; + break; + default: + ret = QWindowsXPStyle::styleHint(hint, option, widget, returnData); + break; + } + return ret; +} + + +/*! + \internal + */ +QRect QWindowsVistaStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::subControlRect(control, option, subControl, widget); + + QRect rect = QWindowsXPStyle::subControlRect(control, option, subControl, widget); + switch (control) { +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = cb->rect.x(), + y = cb->rect.y(), + wi = cb->rect.width(), + he = cb->rect.height(); + int xpos = x; + int margin = cb->frame ? 3 : 0; + int bmarg = cb->frame ? 2 : 0; + int arrowButtonWidth = bmarg + 16; + xpos += wi - arrowButtonWidth; + + switch (subControl) { + case SC_ComboBoxFrame: + rect = cb->rect; + break; + case SC_ComboBoxArrow: + rect.setRect(xpos, y , arrowButtonWidth, he); + break; + case SC_ComboBoxEditField: + rect.setRect(x + margin, y + margin, wi - 2 * margin - 16, he - 2 * margin); + break; + case SC_ComboBoxListBoxPopup: + rect = cb->rect; + break; + default: + break; + } + rect = visualRect(cb->direction, cb->rect, rect); + return rect; + } +#endif // QT_NO_COMBOBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + if (!buttonVisible(subControl, tb)) + return rect; + const bool isToolTitle = false; + const int height = tb->rect.height(); + const int width = tb->rect.width(); + const int buttonWidth = + qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * QWindowsStylePrivate::nativeMetricScaleFactor(widget) + - QStyleHelper::dpiScaled(4)); + + const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget); + const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0; + const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0; + const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0; + const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0; + const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0; + + switch (subControl) { + case SC_TitleBarLabel: + rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height); + if (isToolTitle) { + if (sysmenuHint) { + rect.adjust(0, 0, -buttonWidth - 3, 0); + } + if (minimizeHint || maximizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + } else { + if (sysmenuHint) { + const int leftOffset = height - 8; + rect.adjust(leftOffset, 0, 0, 4); + } + if (minimizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (maximizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (contextHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (shadeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + } + rect.translate(0, 2); + rect = visualRect(option->direction, option->rect, rect); + break; + case SC_TitleBarSysMenu: + { + const int controlTop = 6; + const int controlHeight = height - controlTop - 3; + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent)); + if (tb->icon.isNull()) + iconSize = QSize(controlHeight, controlHeight); + int hPad = (controlHeight - iconSize.height())/2; + int vPad = (controlHeight - iconSize.width())/2; + rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height()); + rect.translate(0, 3); + rect = visualRect(option->direction, option->rect, rect); + } + break; + default: + break; + } + } + break; + default: + break; + } + return rect; +} + +/*! + \internal + */ +QStyle::SubControl QWindowsVistaStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::hitTestComplexControl(control, option, pos, widget); + } + return QWindowsXPStyle::hitTestComplexControl(control, option, pos, widget); +} + +int QWindowsVistaStylePrivate::fixedPixelMetric(QStyle::PixelMetric pm) +{ + switch (pm) { + case QStyle::PM_DockWidgetTitleBarButtonMargin: + return 5; + case QStyle::PM_ScrollBarSliderMin: + return 18; + case QStyle::PM_MenuHMargin: + case QStyle::PM_MenuVMargin: + return 0; + case QStyle::PM_MenuPanelWidth: + return 3; + default: + break; + } + return QWindowsVistaStylePrivate::InvalidMetric; +} + +/*! + \internal + */ +int QWindowsVistaStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) + return QWindowsStyle::pixelMetric(metric, option, widget); + + int ret = QWindowsVistaStylePrivate::fixedPixelMetric(metric); + if (ret != QWindowsStylePrivate::InvalidMetric) + return int(QStyleHelper::dpiScaled(ret)); + + return QWindowsXPStyle::pixelMetric(metric, option, widget); +} + +/*! + \internal + */ +QPalette QWindowsVistaStyle::standardPalette() const +{ + return QWindowsXPStyle::standardPalette(); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QApplication *app) +{ + QWindowsXPStyle::polish(app); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QWidget *widget) +{ + QWindowsXPStyle::polish(widget); +#ifndef QT_NO_LINEEDIT + if (qobject_cast<QLineEdit*>(widget)) + widget->setAttribute(Qt::WA_Hover); + else +#endif // QT_NO_LINEEDIT + if (qobject_cast<QGroupBox*>(widget)) + widget->setAttribute(Qt::WA_Hover); + else if (qobject_cast<QCommandLinkButton*>(widget)) { + QFont buttonFont = widget->font(); + buttonFont.setFamily(QLatin1String("Segoe UI")); + widget->setFont(buttonFont); + } + else if (widget->inherits("QTipLabel")){ + //note that since tooltips are not reused + //we do not have to care about unpolishing + widget->setContentsMargins(3, 0, 4, 0); + COLORREF bgRef; + HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : 0, L"TOOLTIP"); + if (theme && SUCCEEDED(GetThemeColor(theme, TTP_STANDARD, TTSS_NORMAL, TMT_TEXTCOLOR, &bgRef))) { + QColor textColor = QColor::fromRgb(bgRef); + QPalette pal; + pal.setColor(QPalette::All, QPalette::ToolTipText, textColor); + widget->setPalette(pal); + } + } else if (qobject_cast<QMessageBox *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 9, 0, 0); +#endif + } +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<QInputDialog *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 9, 0, 0); +#endif + } +#endif // QT_NO_INPUTDIALOG + else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) { + tree->viewport()->setAttribute(Qt::WA_Hover); + } + else if (QListView *list = qobject_cast<QListView *> (widget)) { + list->viewport()->setAttribute(Qt::WA_Hover); + } +} + +/*! + \internal + */ +void QWindowsVistaStyle::unpolish(QWidget *widget) +{ + QWindowsXPStyle::unpolish(widget); + + QWindowsVistaStylePrivate *d = d_func(); + + d->stopAnimation(widget); + +#ifndef QT_NO_LINEEDIT + if (qobject_cast<QLineEdit*>(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else +#endif // QT_NO_LINEEDIT + if (qobject_cast<QGroupBox*>(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else if (qobject_cast<QMessageBox *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 0, 0, 0); +#endif + } +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<QInputDialog *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); +#if QT_CONFIG(dialogbuttonbox) + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 0, 0, 0); +#endif + } +#endif // QT_NO_INPUTDIALOG + else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) { + tree->viewport()->setAttribute(Qt::WA_Hover, false); + } else if (qobject_cast<QCommandLinkButton*>(widget)) { + QFont font = QApplication::font("QCommandLinkButton"); + QFont widgetFont = widget->font(); + widgetFont.setFamily(font.family()); //Only family set by polish + widget->setFont(widgetFont); + } +} + + +/*! + \internal + */ +void QWindowsVistaStyle::unpolish(QApplication *app) +{ + QWindowsXPStyle::unpolish(app); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QPalette &pal) +{ + QWindowsStyle::polish(pal); + pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(104)); +} + +/*! + \internal + */ +QPixmap QWindowsVistaStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); + } + return QWindowsXPStyle::standardPixmap(standardPixmap, option, widget); +} + +QWindowsVistaStylePrivate::QWindowsVistaStylePrivate() : + QWindowsXPStylePrivate() +{ +} + +bool QWindowsVistaStylePrivate::transitionsEnabled() const +{ + BOOL animEnabled = false; + if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &animEnabled, 0)) + { + if (animEnabled) + return true; + } + return false; +} + +/*! +\reimp +*/ +QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::standardIcon(standardIcon, option, widget); + } + + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate *>(d_func()); + switch(standardIcon) { + case SP_CommandLink: + { + XPThemeData theme(0, 0, + QWindowsXPStylePrivate::ButtonTheme, + BP_COMMANDLINKGLYPH, CMDLGS_NORMAL); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QIcon linkGlyph; + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_PRESSED; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_HOT; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + + theme.stateId = CMDLGS_DISABLED; + d->drawBackground(theme); + linkGlyph.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + return linkGlyph; + } + } + break; + default: + break; + } + return QWindowsXPStyle::standardIcon(standardIcon, option, widget); +} + +QT_END_NAMESPACE diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle_p.h b/src/plugins/styles/windowsvista/qwindowsvistastyle_p.h new file mode 100644 index 0000000000..5ffcbc6aa9 --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsvistastyle_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSVISTASTYLE_P_H +#define QWINDOWSVISTASTYLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include "qwindowsxpstyle_p.h" + +QT_BEGIN_NAMESPACE + +class QWindowsVistaStylePrivate; +class QWindowsVistaStyle : public QWindowsXPStyle +{ + Q_OBJECT +public: + QWindowsVistaStyle(); + ~QWindowsVistaStyle(); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget = 0) const; + + QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + void polish(QWidget *widget); + void unpolish(QWidget *widget); + void polish(QPalette &pal); + void polish(QApplication *app); + void unpolish(QApplication *app); + QPalette standardPalette() const; + +private: + Q_DISABLE_COPY(QWindowsVistaStyle) + Q_DECLARE_PRIVATE(QWindowsVistaStyle) + friend class QStyleFactory; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSVISTASTYLE_P_H diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h b/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h new file mode 100644 index 0000000000..ee3a12c641 --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSVISTASTYLE_P_P_H +#define QWINDOWSVISTASTYLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include "qwindowsvistastyle_p.h" +#include "qwindowsxpstyle_p_p.h" +#include <private/qstyleanimation_p.h> +#include <private/qpaintengine_raster_p.h> +#include <qpaintengine.h> +#include <qwidget.h> +#include <qapplication.h> +#include <qpixmapcache.h> +#include <qstyleoption.h> +#if QT_CONFIG(pushbutton) +#include <qpushbutton.h> +#endif +#include <qradiobutton.h> +#include <qlineedit.h> +#include <qgroupbox.h> +#include <qtoolbutton.h> +#include <qspinbox.h> +#include <qtoolbar.h> +#include <qcombobox.h> +#include <qscrollbar.h> +#include <qprogressbar.h> +#include <qdockwidget.h> +#include <qlistview.h> +#include <qtreeview.h> +#include <qtextedit.h> +#include <qmessagebox.h> +#if QT_CONFIG(dialogbuttonbox) +#include <qdialogbuttonbox.h> +#endif +#include <qinputdialog.h> +#include <qtableview.h> +#include <qdatetime.h> +#include <qcommandlinkbutton.h> + +QT_BEGIN_NAMESPACE + +#if !defined(SCHEMA_VERIFY_VSSYM32) +#define TMT_ANIMATIONDURATION 5006 +#define TMT_TRANSITIONDURATIONS 6000 +#define EP_EDITBORDER_NOSCROLL 6 +#define EP_EDITBORDER_HVSCROLL 9 +#define EP_BACKGROUND 3 +#define EBS_NORMAL 1 +#define EBS_HOT 2 +#define EBS_DISABLED 3 +#define EBS_READONLY 5 +#define PBS_DEFAULTED_ANIMATING 6 +#define MBI_NORMAL 1 +#define MBI_HOT 2 +#define MBI_PUSHED 3 +#define MBI_DISABLED 4 +#define MB_ACTIVE 1 +#define MB_INACTIVE 2 +#define PP_FILL 5 +#define PP_FILLVERT 6 +#define PP_MOVEOVERLAY 8 +#define PP_MOVEOVERLAYVERT 10 +#define MENU_BARBACKGROUND 7 +#define MENU_BARITEM 8 +#define MENU_POPUPCHECK 11 +#define MENU_POPUPCHECKBACKGROUND 12 +#define MENU_POPUPGUTTER 13 +#define MENU_POPUPITEM 14 +#define MENU_POPUPBORDERS 10 +#define MENU_POPUPSEPARATOR 15 +#define MC_CHECKMARKNORMAL 1 +#define MC_CHECKMARKDISABLED 2 +#define MC_BULLETNORMAL 3 +#define MC_BULLETDISABLED 4 +#define ABS_UPHOVER 17 +#define ABS_DOWNHOVER 18 +#define ABS_LEFTHOVER 19 +#define ABS_RIGHTHOVER 20 +#define CP_DROPDOWNBUTTONRIGHT 6 +#define CP_DROPDOWNBUTTONLEFT 7 +#define SCRBS_HOVER 5 +#define TVP_HOTGLYPH 4 +#define SPI_GETCLIENTAREAANIMATION 0x1042 +#define TDLG_PRIMARYPANEL 1 +#define TDLG_SECONDARYPANEL 8 +#endif + +class QWindowsVistaAnimation : public QBlendStyleAnimation +{ + Q_OBJECT +public: + QWindowsVistaAnimation(Type type, QObject *target) : QBlendStyleAnimation(type, target) { } + + virtual bool isUpdateNeeded() const; + void paint(QPainter *painter, const QStyleOption *option); +}; + + +// Handles state transition animations +class QWindowsVistaTransition : public QWindowsVistaAnimation +{ + Q_OBJECT +public: + QWindowsVistaTransition(QObject *target) : QWindowsVistaAnimation(Transition, target) {} +}; + + +// Handles pulse animations (default buttons) +class QWindowsVistaPulse: public QWindowsVistaAnimation +{ + Q_OBJECT +public: + QWindowsVistaPulse(QObject *target) : QWindowsVistaAnimation(Pulse, target) {} +}; + + +class QWindowsVistaStylePrivate : public QWindowsXPStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsVistaStyle) + +public: + QWindowsVistaStylePrivate(); + + static int fixedPixelMetric(QStyle::PixelMetric pm); + static inline bool useVista(); + bool transitionsEnabled() const; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSVISTASTYLE_P_P_H diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp b/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp new file mode 100644 index 0000000000..3f59886f1d --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp @@ -0,0 +1,4223 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qwindowsxpstyle_p.h" +#include "qwindowsxpstyle_p_p.h" + +#include <private/qobject_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qapplication_p.h> +#include <qpa/qplatformnativeinterface.h> +#include <private/qstylehelper_p.h> +#include <private/qwidget_p.h> +#include <qpainter.h> +#include <qpaintengine.h> +#include <qwidget.h> +#include <qbackingstore.h> +#include <qapplication.h> +#include <qpixmapcache.h> +#include <private/qapplication_p.h> +#include <qpa/qplatformnativeinterface.h> + +#include <qdesktopwidget.h> +#include <qtoolbutton.h> +#include <qtabbar.h> +#include <qcombobox.h> +#include <qscrollbar.h> +#include <qheaderview.h> +#include <qspinbox.h> +#include <qlistview.h> +#include <qstackedwidget.h> +#if QT_CONFIG(pushbutton) +#include <qpushbutton.h> +#endif +#include <qtoolbar.h> +#include <qlabel.h> +#include <qvarlengtharray.h> +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +// General const values +static const int windowsItemFrame = 2; // menu item frame width +static const int windowsItemHMargin = 3; // menu item hor text margin +static const int windowsItemVMargin = 0; // menu item ver text margin +static const int windowsArrowHMargin = 6; // arrow horizontal margin +static const int windowsRightBorder = 12; // right border on windows + +// External function calls +extern Q_WIDGETS_EXPORT HDC qt_win_display_dc(); +extern QRegion qt_region_from_HRGN(HRGN rgn); + +// Theme names matching the QWindowsXPStylePrivate::Theme enumeration. +static const wchar_t *themeNames[QWindowsXPStylePrivate::NThemes] = +{ + L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW", + L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN", + L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR", + L"TREEVIEW", L"WINDOW", L"STATUS", L"TREEVIEW" +}; + +static inline QBackingStore *backingStoreForWidget(const QWidget *widget) +{ + if (QBackingStore *backingStore = widget->backingStore()) + return backingStore; + if (const QWidget *topLevel = widget->nativeParentWidget()) + if (QBackingStore *topLevelBackingStore = topLevel->backingStore()) + return topLevelBackingStore; + return 0; +} + +static inline HDC hdcForWidgetBackingStore(const QWidget *widget) +{ + if (QBackingStore *backingStore = backingStoreForWidget(widget)) { + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + return static_cast<HDC>(nativeInterface->nativeResourceForBackingStore(QByteArrayLiteral("getDC"), backingStore)); + } + return 0; +} + +// Theme data helper ------------------------------------------------------------------------------ +/* \internal + Returns \c true if the themedata is valid for use. +*/ +bool XPThemeData::isValid() +{ + return QWindowsXPStylePrivate::useXP() && theme >= 0 && handle(); +} + + +/* \internal + Returns the theme engine handle to the specific class. + If the handle hasn't been opened before, it opens the data, and + adds it to a static map, for caching. +*/ +HTHEME XPThemeData::handle() +{ + if (!QWindowsXPStylePrivate::useXP()) + return 0; + + if (!htheme) + htheme = QWindowsXPStylePrivate::createTheme(theme, QWindowsXPStylePrivate::winId(widget)); + return htheme; +} + +/* \internal + Converts a QRect to the native RECT structure. +*/ +RECT XPThemeData::toRECT(const QRect &qr) +{ + RECT r; + r.left = qr.x(); + r.right = qr.x() + qr.width(); + r.top = qr.y(); + r.bottom = qr.y() + qr.height(); + return r; +} + +/* \internal + Returns the native region of a part, if the part is considered + transparent. The region is scaled to the parts size (rect). +*/ +HRGN XPThemeData::mask(QWidget *widget) +{ + if (!IsThemeBackgroundPartiallyTransparent(handle(), partId, stateId)) + return 0; + + HRGN hrgn; + HDC dc = 0; + if (widget) + dc = hdcForWidgetBackingStore(widget); + RECT nativeRect = toRECT(rect); + GetThemeBackgroundRegion(handle(), dc, partId, stateId, &nativeRect, &hrgn); + return hrgn; +} + +// QWindowsXPStylePrivate ------------------------------------------------------------------------- +// Static initializations +QPixmap *QWindowsXPStylePrivate::tabbody = 0; +HWND QWindowsXPStylePrivate::m_vistaTreeViewHelper = 0; +HTHEME QWindowsXPStylePrivate::m_themes[NThemes]; +bool QWindowsXPStylePrivate::use_xp = false; +QBasicAtomicInt QWindowsXPStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting + +static void qt_add_rect(HRGN &winRegion, QRect r) +{ + HRGN rgn = CreateRectRgn(r.left(), r.top(), r.x() + r.width(), r.y() + r.height()); + if (rgn) { + HRGN dest = CreateRectRgn(0,0,0,0); + int result = CombineRgn(dest, winRegion, rgn, RGN_OR); + if (result) { + DeleteObject(winRegion); + winRegion = dest; + } + DeleteObject(rgn); + } +} + +static HRGN qt_hrgn_from_qregion(const QRegion ®ion) +{ + HRGN hRegion = CreateRectRgn(0,0,0,0); + if (region.rectCount() == 1) { + qt_add_rect(hRegion, region.boundingRect()); + return hRegion; + } + for (const QRect &rect : region) + qt_add_rect(hRegion, rect); + return hRegion; +} + +/* \internal + Checks if the theme engine can/should be used, or if we should + fall back to Windows style. +*/ +bool QWindowsXPStylePrivate::useXP(bool update) +{ + if (!update) + return use_xp; + return use_xp = IsThemeActive() && (IsAppThemed() || !QApplication::instance()); +} + +/* \internal + Handles refcounting, and queries the theme engine for usage. +*/ +void QWindowsXPStylePrivate::init(bool force) +{ + if (ref.ref() && !force) + return; + if (!force) // -1 based atomic refcounting + ref.ref(); + + useXP(true); + std::fill(m_themes, m_themes + NThemes, HTHEME(0)); +} + +/* \internal + Cleans up all static data. +*/ +void QWindowsXPStylePrivate::cleanup(bool force) +{ + if(bufferBitmap) { + if (bufferDC && nullBitmap) + SelectObject(bufferDC, nullBitmap); + DeleteObject(bufferBitmap); + bufferBitmap = 0; + } + + if(bufferDC) + DeleteDC(bufferDC); + bufferDC = 0; + + if (ref.deref() && !force) + return; + if (!force) // -1 based atomic refcounting + ref.deref(); + + use_xp = false; + cleanupHandleMap(); + delete tabbody; + tabbody = 0; +} + +/* In order to obtain the correct VistaTreeViewTheme (arrows for PE_IndicatorBranch), + * we need to set the windows "explorer" theme explicitly on a native + * window and open the "TREEVIEW" theme handle passing its window handle + * in order to get Vista-style item view themes (particulary drawBackground() + * for selected items needs this). + * We invoke a service of the native Windows interface to create + * a non-visible window handle, open the theme on it and insert it into + * the cache so that it is found by XPThemeData::handle() first. + */ + +static inline HWND createTreeViewHelperWindow() +{ + if (QPlatformNativeInterface *ni = QGuiApplication::platformNativeInterface()) { + void *hwnd = 0; + void *wndProc = reinterpret_cast<void *>(DefWindowProc); + if (QMetaObject::invokeMethod(ni, "createMessageWindow", Qt::DirectConnection, + Q_RETURN_ARG(void *, hwnd), + Q_ARG(QString, QStringLiteral("QTreeViewThemeHelperWindowClass")), + Q_ARG(QString, QStringLiteral("QTreeViewThemeHelperWindow")), + Q_ARG(void *, wndProc)) && hwnd) { + return reinterpret_cast<HWND>(hwnd); + } + } + return 0; +} + +bool QWindowsXPStylePrivate::initVistaTreeViewTheming() +{ + if (m_vistaTreeViewHelper) + return true; + + m_vistaTreeViewHelper = createTreeViewHelperWindow(); + if (!m_vistaTreeViewHelper) { + qWarning("Unable to create the treeview helper window."); + return false; + } + if (FAILED(SetWindowTheme(m_vistaTreeViewHelper, L"explorer", NULL))) { + qErrnoWarning("SetWindowTheme() failed."); + cleanupVistaTreeViewTheming(); + return false; + } + return true; +} + +void QWindowsXPStylePrivate::cleanupVistaTreeViewTheming() +{ + if (m_vistaTreeViewHelper) { + DestroyWindow(m_vistaTreeViewHelper); + m_vistaTreeViewHelper = 0; + } +} + +/* \internal + Closes all open theme data handles to ensure that we don't leak + resources, and that we don't refere to old handles when for + example the user changes the theme style. +*/ +void QWindowsXPStylePrivate::cleanupHandleMap() +{ + for (int i = 0; i < NThemes; ++i) + if (m_themes[i]) { + CloseThemeData(m_themes[i]); + m_themes[i] = 0; + } + QWindowsXPStylePrivate::cleanupVistaTreeViewTheming(); +} + +HTHEME QWindowsXPStylePrivate::createTheme(int theme, HWND hwnd) +{ + if (Q_UNLIKELY(theme < 0 || theme >= NThemes || !hwnd)) { + qWarning("Invalid parameters #%d, %p", theme, hwnd); + return 0; + } + if (!m_themes[theme]) { + const wchar_t *name = themeNames[theme]; + if (theme == VistaTreeViewTheme && QWindowsXPStylePrivate::initVistaTreeViewTheming()) + hwnd = QWindowsXPStylePrivate::m_vistaTreeViewHelper; + m_themes[theme] = OpenThemeData(hwnd, name); + if (Q_UNLIKELY(!m_themes[theme])) + qErrnoWarning("OpenThemeData() failed for theme %d (%s).", + theme, qPrintable(themeName(theme))); + } + return m_themes[theme]; +} + +QString QWindowsXPStylePrivate::themeName(int theme) +{ + return theme >= 0 && theme < NThemes ? + QString::fromWCharArray(themeNames[theme]) : + QString(); +} + +bool QWindowsXPStylePrivate::isItemViewDelegateLineEdit(const QWidget *widget) +{ + if (!widget) + return false; + const QWidget *parent1 = widget->parentWidget(); + // Exlude dialogs or other toplevels parented on item views. + if (!parent1 || parent1->isWindow()) + return false; + const QWidget *parent2 = parent1->parentWidget(); + return parent2 && widget->inherits("QLineEdit") + && parent2->inherits("QAbstractItemView"); +} + +// Returns whether base color is set for this widget +bool QWindowsXPStylePrivate::isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget) +{ + uint resolveMask = option->palette.resolve(); + if (widget) { + // Since spin box includes a line edit we need to resolve the palette mask also from + // the parent, as while the color is always correct on the palette supplied by panel, + // the mask can still be empty. If either mask specifies custom base color, use that. +#ifndef QT_NO_SPINBOX + if (const QAbstractSpinBox *spinbox = qobject_cast<QAbstractSpinBox*>(widget->parentWidget())) + resolveMask |= spinbox->palette().resolve(); +#endif // QT_NO_SPINBOX + } + return (resolveMask & (1 << QPalette::Base)) != 0; +} + +/*! \internal + This function will always return a valid window handle, and might + create a limbo widget to do so. + We often need a window handle to for example open theme data, so + this function ensures that we get one. +*/ +HWND QWindowsXPStylePrivate::winId(const QWidget *widget) +{ + if (widget) + if (const HWND hwnd = QApplicationPrivate::getHWNDForWidget(const_cast<QWidget *>(widget))) + return hwnd; + + // Find top level with native window (there might be dialogs that do not have one). + const auto topLevels = QApplication::topLevelWidgets(); + for (const QWidget *toplevel : topLevels) { + if (toplevel->windowHandle() && toplevel->windowHandle()->handle()) + if (const HWND topLevelHwnd = QApplicationPrivate::getHWNDForWidget(toplevel)) + return topLevelHwnd; + } + + return GetDesktopWindow(); +} + +/*! \internal + Returns the pointer to a tab widgets body pixmap, scaled to the + height of the screen. This way the theme engine doesn't need to + scale the body for every time we ask for it. (Speed optimization) +*/ +const QPixmap *QWindowsXPStylePrivate::tabBody(QWidget *widget) +{ + if (!tabbody) { + XPThemeData theme(0, 0, QWindowsXPStylePrivate::TabTheme, TABP_BODY); + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + + tabbody = new QPixmap(size.width(), QApplication::desktop()->screenGeometry().height()); + QPainter painter(tabbody); + theme.rect = QRect(QPoint(0, 0), size); + drawBackground(theme); + // We fill with the last line of the themedata, that + // way we don't get a tiled pixmap inside big tabs + QPixmap temp(size.width(), 1); + painter.drawPixmap(0, 0, temp, 0, size.height() - 1, -1, -1); + painter.drawTiledPixmap(0, size.height(), size.width(), tabbody->height() - size.height(), temp); + } + return tabbody; +} + +/*! \internal + Returns a native buffer (DIB section) of at least the size of + ( \a x , \a y ). The buffer has a 32 bit depth, to not lose + the alpha values on proper alpha-pixmaps. +*/ +HBITMAP QWindowsXPStylePrivate::buffer(int w, int h) +{ + // If we already have a HBITMAP which is of adequate size, just return that + if (bufferBitmap) { + if (bufferW >= w && bufferH >= h) + return bufferBitmap; + // Not big enough, discard the old one + if (bufferDC && nullBitmap) + SelectObject(bufferDC, nullBitmap); + DeleteObject(bufferBitmap); + bufferBitmap = 0; + } + + w = qMax(bufferW, w); + h = qMax(bufferH, h); + + if (!bufferDC) { + HDC displayDC = GetDC(0); + bufferDC = CreateCompatibleDC(displayDC); + ReleaseDC(0, displayDC); + } + + // Define the header + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = w; + bmi.bmiHeader.biHeight = -h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + // Create the pixmap + bufferPixels = 0; + bufferBitmap = CreateDIBSection(bufferDC, &bmi, DIB_RGB_COLORS, (void **) &bufferPixels, 0, 0); + GdiFlush(); + nullBitmap = (HBITMAP)SelectObject(bufferDC, bufferBitmap); + + if (Q_UNLIKELY(!bufferBitmap)) { + qErrnoWarning("QWindowsXPStylePrivate::buffer(%dx%d), CreateDIBSection() failed.", w, h); + bufferW = 0; + bufferH = 0; + return 0; + } + if (Q_UNLIKELY(!bufferPixels)) { + qErrnoWarning("QWindowsXPStylePrivate::buffer(%dx%d), CreateDIBSection() did not allocate pixel data.", w, h); + bufferW = 0; + bufferH = 0; + return 0; + } + bufferW = w; + bufferH = h; +#ifdef DEBUG_XP_STYLE + qDebug("Creating new dib section (%d, %d)", w, h); +#endif + return bufferBitmap; +} + +/*! \internal + Returns \c true if the part contains any transparency at all. This does + not indicate what kind of transparency we're dealing with. It can be + - Alpha transparency + - Masked transparency +*/ +bool QWindowsXPStylePrivate::isTransparent(XPThemeData &themeData) +{ + return IsThemeBackgroundPartiallyTransparent(themeData.handle(), themeData.partId, + themeData.stateId); +} + + +/*! \internal + Returns a QRegion of the region of the part +*/ +QRegion QWindowsXPStylePrivate::region(XPThemeData &themeData) +{ + HRGN hRgn = 0; + RECT rect = themeData.toRECT(themeData.rect); + if (!SUCCEEDED(GetThemeBackgroundRegion(themeData.handle(), bufferHDC(), themeData.partId, + themeData.stateId, &rect, &hRgn))) { + return QRegion(); + } + + HRGN dest = CreateRectRgn(0, 0, 0, 0); + const bool success = CombineRgn(dest, hRgn, 0, RGN_COPY) != ERROR; + + QRegion region; + + if (success) { + int numBytes = GetRegionData(dest, 0, 0); + if (numBytes == 0) + return QRegion(); + + char *buf = new char[numBytes]; + if (buf == 0) + return QRegion(); + + RGNDATA *rd = reinterpret_cast<RGNDATA*>(buf); + if (GetRegionData(dest, numBytes, rd) == 0) { + delete [] buf; + return QRegion(); + } + + RECT *r = reinterpret_cast<RECT*>(rd->Buffer); + for (uint i = 0; i < rd->rdh.nCount; ++i) { + QRect rect; + rect.setCoords(r->left, r->top, r->right - 1, r->bottom - 1); + ++r; + region |= rect; + } + + delete [] buf; + } + + DeleteObject(hRgn); + DeleteObject(dest); + + return region; +} + +/*! \internal + Sets the parts region on a window. +*/ +void QWindowsXPStylePrivate::setTransparency(QWidget *widget, XPThemeData &themeData) +{ + HRGN hrgn = themeData.mask(widget); + if (hrgn && widget) + SetWindowRgn(winId(widget), hrgn, true); +} + +/*! \internal + Returns \c true if the native doublebuffer contains pixels with + varying alpha value. +*/ +bool QWindowsXPStylePrivate::hasAlphaChannel(const QRect &rect) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + + int firstAlpha = -1; + for (int y = startY; y < h/2; ++y) { + DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + int alpha = (*buffer) >> 24; + if (firstAlpha == -1) + firstAlpha = alpha; + else if (alpha != firstAlpha) + return true; + } + } + return false; +} + +/*! \internal + When the theme engine paints both a true alpha pixmap and a glyph + into our buffer, the glyph might not contain a proper alpha value. + The rule of thumb for premultiplied pixmaps is that the color + values of a pixel can never be higher than the alpha values, so + we use this to our advantage here, and fix all instances where + this occures. +*/ +bool QWindowsXPStylePrivate::fixAlphaChannel(const QRect &rect) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + bool hasFixedAlphaValue = false; + + for (int y = startY; y < h; ++y) { + DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + uint pixel = *buffer; + int alpha = qAlpha(pixel); + if (qRed(pixel) > alpha || qGreen(pixel) > alpha || qBlue(pixel) > alpha) { + *buffer |= 0xff000000; + hasFixedAlphaValue = true; + } + } + } + return hasFixedAlphaValue; +} + +/*! \internal + Swaps the alpha values on certain pixels: + 0xFF?????? -> 0x00?????? + 0x00?????? -> 0xFF?????? + Used to determin the mask of a non-alpha transparent pixmap in + the native doublebuffer, and swap the alphas so we may paint + the image as a Premultiplied QImage with drawImage(), and obtain + the mask transparency. +*/ +bool QWindowsXPStylePrivate::swapAlphaChannel(const QRect &rect, bool allPixels) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + bool valueChange = false; + + // Flip the alphas, so that 255-alpha pixels are 0, and 0-alpha are 255. + for (int y = startY; y < h; ++y) { + DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + if (allPixels) { + *buffer |= 0xFF000000; + continue; + } + unsigned int alphaValue = (*buffer) & 0xFF000000; + if (alphaValue == 0xFF000000) { + *buffer = 0; + valueChange = true; + } else if (alphaValue == 0) { + *buffer |= 0xFF000000; + valueChange = true; + } + } + } + return valueChange; +} + +enum TransformType { SimpleTransform, HighDpiScalingTransform, ComplexTransform }; + +static inline TransformType transformType(const QTransform &transform, qreal devicePixelRatio) +{ + if (transform.type() <= QTransform::TxTranslate) + return SimpleTransform; + if (transform.type() > QTransform::TxScale) + return ComplexTransform; + return qFuzzyCompare(transform.m11(), devicePixelRatio) + && qFuzzyCompare(transform.m22(), devicePixelRatio) + ? HighDpiScalingTransform : ComplexTransform; +} + +/*! \internal + Main theme drawing function. + Determines the correct lowlevel drawing method depending on several + factors. + Use drawBackgroundThruNativeBuffer() if: + - Painter does not have an HDC + - Theme part is flipped (mirrored horizontally) + else use drawBackgroundDirectly(). + \note drawBackgroundThruNativeBuffer() can return false for large + sizes due to buffer()/CreateDIBSection() failing. +*/ +bool QWindowsXPStylePrivate::drawBackground(XPThemeData &themeData) +{ + if (themeData.rect.isEmpty()) + return true; + + QPainter *painter = themeData.painter; + Q_ASSERT_X(painter != 0, "QWindowsXPStylePrivate::drawBackground()", "Trying to draw a theme part without a painter"); + if (!painter || !painter->isActive()) + return false; + + painter->save(); + + // Access paintDevice via engine since the painter may + // return the clip device which can still be a widget device in case of grabWidget(). + + bool translucentToplevel = false; + const QPaintDevice *paintDevice = painter->device(); + const qreal aditionalDevicePixelRatio = themeData.widget ? themeData.widget->devicePixelRatioF() : qreal(1); + if (paintDevice->devType() == QInternal::Widget) { + const QWidget *window = static_cast<const QWidget *>(paintDevice)->window(); + translucentToplevel = window->testAttribute(Qt::WA_TranslucentBackground); + } + + const TransformType tt = transformType(painter->deviceTransform(), aditionalDevicePixelRatio); + + bool canDrawDirectly = false; + if (themeData.widget && painter->opacity() == 1.0 && !themeData.rotate + && tt != ComplexTransform && !themeData.mirrorVertically + && !translucentToplevel) { + // Draw on backing store DC only for real widgets or backing store images. + const QPaintDevice *enginePaintDevice = painter->paintEngine()->paintDevice(); + switch (enginePaintDevice->devType()) { + case QInternal::Widget: + canDrawDirectly = true; + break; + case QInternal::Image: + // Ensure the backing store has received as resize and is initialized. + if (QBackingStore *bs = backingStoreForWidget(themeData.widget)) + if (bs->size().isValid() && bs->paintDevice() == enginePaintDevice) + canDrawDirectly = true; + } + } + + const HDC dc = canDrawDirectly ? hdcForWidgetBackingStore(themeData.widget) : HDC(0); + const bool result = dc + ? drawBackgroundDirectly(dc, themeData, aditionalDevicePixelRatio) + : drawBackgroundThruNativeBuffer(themeData, aditionalDevicePixelRatio); + painter->restore(); + return result; +} + +static inline QRectF scaleRect(const QRectF &r, qreal factor) +{ + return r.isValid() && factor > 1 + ? QRectF(r.topLeft() * factor, r.size() * factor) + : r; +} + +static QRegion scaleRegion(const QRegion ®ion, qreal factor) +{ + if (region.isEmpty() || qFuzzyCompare(factor, qreal(1))) + return region; + if (region.rectCount() == 1) + return QRegion(scaleRect(QRectF(region.boundingRect()), factor).toRect()); + QRegion result; + foreach (const QRect &rect, region.rects()) + result += QRectF(QPointF(rect.topLeft()) * factor, QSizeF(rect.size() * factor)).toRect(); + return result; +} + +/*! \internal + This function draws the theme parts directly to the paintengines HDC. + Do not use this if you need to perform other transformations on the + resulting data. +*/ +bool QWindowsXPStylePrivate::drawBackgroundDirectly(HDC dc, XPThemeData &themeData, qreal additionalDevicePixelRatio) +{ + QPainter *painter = themeData.painter; + + const QPointF redirectionDelta(painter->deviceMatrix().dx(), painter->deviceMatrix().dy()); + const QRect area = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio).translated(redirectionDelta).toRect(); + + QRegion sysRgn = painter->paintEngine()->systemClip(); + if (sysRgn.isEmpty()) + sysRgn = area; + else + sysRgn &= area; + if (painter->hasClipping()) + sysRgn &= scaleRegion(painter->clipRegion(), additionalDevicePixelRatio).translated(redirectionDelta.toPoint()); + HRGN hrgn = qt_hrgn_from_qregion(sysRgn); + SelectClipRgn(dc, hrgn); + +#ifdef DEBUG_XP_STYLE + printf("---[ DIRECT PAINTING ]------------------> Name(%-10s) Part(%d) State(%d)\n", + qPrintable(themeData.name), themeData.partId, themeData.stateId); + showProperties(themeData); +#endif + + RECT drawRECT = themeData.toRECT(area); + DTBGOPTS drawOptions; + memset(&drawOptions, 0, sizeof(drawOptions)); + drawOptions.dwSize = sizeof(drawOptions); + drawOptions.rcClip = themeData.toRECT(sysRgn.boundingRect()); + drawOptions.dwFlags = DTBG_CLIPRECT + | (themeData.noBorder ? DTBG_OMITBORDER : 0) + | (themeData.noContent ? DTBG_OMITCONTENT : 0) + | (themeData.mirrorHorizontally ? DTBG_MIRRORDC : 0); + + const HRESULT result = DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &drawOptions); + SelectClipRgn(dc, 0); + DeleteObject(hrgn); + return SUCCEEDED(result); +} + +/*! \internal + This function uses a secondary Native doublebuffer for painting parts. + It should only be used when the painteengine doesn't provide a proper + HDC for direct painting (e.g. when doing a grabWidget(), painting to + other pixmaps etc), or when special transformations are needed (e.g. + flips (horizonal mirroring only, vertical are handled by the theme + engine). +*/ +bool QWindowsXPStylePrivate::drawBackgroundThruNativeBuffer(XPThemeData &themeData, + qreal additionalDevicePixelRatio) +{ + QPainter *painter = themeData.painter; + QRectF rectF = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio); + + if ((themeData.rotate + 90) % 180 == 0) { // Catch 90,270,etc.. degree flips. + rectF = QRectF(0, 0, rectF.height(), rectF.width()); + } + rectF.moveTo(0, 0); + QRect rect = rectF.toRect(); + int partId = themeData.partId; + int stateId = themeData.stateId; + int w = rect.width(); + int h = rect.height(); + + // Values initialized later, either from cached values, or from function calls + AlphaChannelType alphaType = UnknownAlpha; + bool stateHasData = true; // We assume so; + bool hasAlpha = false; + bool partIsTransparent; + bool potentialInvalidAlpha; + + QString pixmapCacheKey = QStringLiteral("$qt_xp_"); + pixmapCacheKey.append(themeName(themeData.theme)); + pixmapCacheKey.append(QLatin1Char('p')); + pixmapCacheKey.append(QString::number(partId)); + pixmapCacheKey.append(QLatin1Char('s')); + pixmapCacheKey.append(QString::number(stateId)); + pixmapCacheKey.append(QLatin1Char('s')); + pixmapCacheKey.append(themeData.noBorder ? QLatin1Char('0') : QLatin1Char('1')); + pixmapCacheKey.append(QLatin1Char('b')); + pixmapCacheKey.append(themeData.noContent ? QLatin1Char('0') : QLatin1Char('1')); + pixmapCacheKey.append(QString::number(w)); + pixmapCacheKey.append(QLatin1Char('w')); + pixmapCacheKey.append(QString::number(h)); + pixmapCacheKey.append(QLatin1Char('h')); + pixmapCacheKey.append(QString::number(additionalDevicePixelRatio)); + pixmapCacheKey.append(QLatin1Char('d')); + + QPixmap cachedPixmap; + ThemeMapKey key(themeData); + ThemeMapData data = alphaCache.value(key); + + bool haveCachedPixmap = false; + bool isCached = data.dataValid; + if (isCached) { + partIsTransparent = data.partIsTransparent; + hasAlpha = data.hasAlphaChannel; + alphaType = data.alphaType; + potentialInvalidAlpha = data.hadInvalidAlpha; + + haveCachedPixmap = QPixmapCache::find(pixmapCacheKey, cachedPixmap); + +#ifdef DEBUG_XP_STYLE + char buf[25]; + ::sprintf(buf, "+ Pixmap(%3d, %3d) ]", w, h); + printf("---[ CACHED %s--------> Name(%-10s) Part(%d) State(%d)\n", + haveCachedPixmap ? buf : "]-------------------", + qPrintable(themeData.name), themeData.partId, themeData.stateId); +#endif + } else { + // Not cached, so get values from Theme Engine + BOOL tmt_borderonly = false; + COLORREF tmt_transparentcolor = 0x0; + PROPERTYORIGIN proporigin = PO_NOTFOUND; + GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERONLY, &tmt_borderonly); + GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, TMT_TRANSPARENTCOLOR, &tmt_transparentcolor); + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_CAPTIONMARGINS, &proporigin); + + partIsTransparent = isTransparent(themeData); + + potentialInvalidAlpha = false; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &proporigin); + if (proporigin == PO_PART || proporigin == PO_STATE) { + int tmt_glyphtype = GT_NONE; + GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &tmt_glyphtype); + potentialInvalidAlpha = partIsTransparent && tmt_glyphtype == GT_IMAGEGLYPH; + } + +#ifdef DEBUG_XP_STYLE + printf("---[ NOT CACHED ]-----------------------> Name(%-10s) Part(%d) State(%d)\n", + qPrintable(themeData.name), themeData.partId, themeData.stateId); + printf("-->partIsTransparen = %d\n", partIsTransparent); + printf("-->potentialInvalidAlpha = %d\n", potentialInvalidAlpha); + showProperties(themeData); +#endif + } + bool wasAlphaSwapped = false; + bool wasAlphaFixed = false; + + // OLD PSDK Workaround ------------------------------------------------------------------------ + // See if we need extra clipping for the older PSDK, which does + // not have a DrawThemeBackgroundEx function for DTGB_OMITBORDER + // and DTGB_OMITCONTENT + bool addBorderContentClipping = false; + QRegion extraClip; + QRect area = rect; + if (themeData.noBorder || themeData.noContent) { + extraClip = area; + // We are running on a system where the uxtheme.dll does not have + // the DrawThemeBackgroundEx function, so we need to clip away + // borders or contents manually. + + int borderSize = 0; + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin); + GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize); + + // Clip away border region + if ((origin == PO_CLASS || origin == PO_PART || origin == PO_STATE) && borderSize > 0) { + if (themeData.noBorder) { + extraClip &= area; + area = area.adjusted(-borderSize, -borderSize, borderSize, borderSize); + } + + // Clip away content region + if (themeData.noContent) { + QRegion content = area.adjusted(borderSize, borderSize, -borderSize, -borderSize); + extraClip ^= content; + } + } + addBorderContentClipping = (themeData.noBorder | themeData.noContent); + } + + QImage img; + if (!haveCachedPixmap) { // If the pixmap is not cached, generate it! ------------------------- + if (!buffer(w, h)) // Ensure a buffer of at least (w, h) in size + return false; + HDC dc = bufferHDC(); + + // Clear the buffer + if (alphaType != NoAlpha) { + // Consider have separate "memset" function for small chunks for more speedup + memset(bufferPixels, 0x00, bufferW * h * 4); + } + + // Difference between area and rect + int dx = area.x() - rect.x(); + int dy = area.y() - rect.y(); + + // Adjust so painting rect starts from Origo + rect.moveTo(0,0); + area.moveTo(dx,dy); + DTBGOPTS drawOptions; + drawOptions.dwSize = sizeof(drawOptions); + drawOptions.rcClip = themeData.toRECT(rect); + drawOptions.dwFlags = DTBG_CLIPRECT + | (themeData.noBorder ? DTBG_OMITBORDER : 0) + | (themeData.noContent ? DTBG_OMITCONTENT : 0); + + // Drawing the part into the backing store + RECT wRect(themeData.toRECT(area)); + DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &wRect, &drawOptions); + + // If not cached, analyze the buffer data to figure + // out alpha type, and if it contains data + if (!isCached) { + // SHORTCUT: If the part's state has no data, cache it for NOOP later + if (!stateHasData) { + memset(&data, 0, sizeof(data)); + data.dataValid = true; + alphaCache.insert(key, data); + return true; + } + hasAlpha = hasAlphaChannel(rect); + if (!hasAlpha && partIsTransparent) + potentialInvalidAlpha = true; +#if defined(DEBUG_XP_STYLE) && 1 + dumpNativeDIB(w, h); +#endif + } + + // Fix alpha values, if needed + if (potentialInvalidAlpha) + wasAlphaFixed = fixAlphaChannel(rect); + + QImage::Format format; + if ((partIsTransparent && !wasAlphaSwapped) || (!partIsTransparent && hasAlpha)) { + format = QImage::Format_ARGB32_Premultiplied; + alphaType = RealAlpha; + } else if (wasAlphaSwapped) { + format = QImage::Format_ARGB32_Premultiplied; + alphaType = MaskAlpha; + } else { + format = QImage::Format_RGB32; + // The image data we got from the theme engine does not have any transparency, + // thus the alpha channel is set to 0. + // However, Format_RGB32 requires the alpha part to be set to 0xff, thus + // we must flip it from 0x00 to 0xff + swapAlphaChannel(rect, true); + alphaType = NoAlpha; + } +#if defined(DEBUG_XP_STYLE) && 1 + printf("Image format is: %s\n", alphaType == RealAlpha ? "Real Alpha" : alphaType == MaskAlpha ? "Masked Alpha" : "No Alpha"); +#endif + img = QImage(bufferPixels, bufferW, bufferH, format); + img.setDevicePixelRatio(additionalDevicePixelRatio); + } + + // Blitting backing store + bool useRegion = partIsTransparent && !hasAlpha && !wasAlphaSwapped; + + QRegion newRegion; + QRegion oldRegion; + if (useRegion) { + newRegion = region(themeData); + oldRegion = painter->clipRegion(); + painter->setClipRegion(newRegion); +#if defined(DEBUG_XP_STYLE) && 0 + printf("Using region:\n"); + for (const QRect &r : newRegion) + printf(" (%d, %d, %d, %d)\n", r.x(), r.y(), r.right(), r.bottom()); +#endif + } + + if (addBorderContentClipping) + painter->setClipRegion(extraClip, Qt::IntersectClip); + + if (!themeData.mirrorHorizontally && !themeData.mirrorVertically && !themeData.rotate) { + if (!haveCachedPixmap) + painter->drawImage(themeData.rect, img, rect); + else + painter->drawPixmap(themeData.rect, cachedPixmap); + } else { + // This is _slow_! + // Make a copy containing only the necessary data, and mirror + // on all wanted axes. Then draw the copy. + // If cached, the normal pixmap is cached, instead of caching + // all possible orientations for each part and state. + QImage imgCopy; + if (!haveCachedPixmap) + imgCopy = img.copy(rect); + else + imgCopy = cachedPixmap.toImage(); + + if (themeData.rotate) { + QMatrix rotMatrix; + rotMatrix.rotate(themeData.rotate); + imgCopy = imgCopy.transformed(rotMatrix); + } + if (themeData.mirrorHorizontally || themeData.mirrorVertically) { + imgCopy = imgCopy.mirrored(themeData.mirrorHorizontally, themeData.mirrorVertically); + } + painter->drawImage(themeData.rect, + imgCopy); + } + + if (useRegion || addBorderContentClipping) { + if (oldRegion.isEmpty()) + painter->setClipping(false); + else + painter->setClipRegion(oldRegion); + } + + // Cache the pixmap to avoid expensive swapAlphaChannel() calls + if (!haveCachedPixmap && w && h) { + QPixmap pix = QPixmap::fromImage(img).copy(rect); + QPixmapCache::insert(pixmapCacheKey, pix); +#ifdef DEBUG_XP_STYLE + printf("+++Adding pixmap to cache, size(%d, %d), wasAlphaSwapped(%d), wasAlphaFixed(%d), name(%s)\n", + w, h, wasAlphaSwapped, wasAlphaFixed, qPrintable(pixmapCacheKey)); +#endif + } + + // Add to theme part cache + if (!isCached) { + memset(&data, 0, sizeof(data)); + data.dataValid = true; + data.partIsTransparent = partIsTransparent; + data.alphaType = alphaType; + data.hasAlphaChannel = hasAlpha; + data.wasAlphaSwapped = wasAlphaSwapped; + data.hadInvalidAlpha = wasAlphaFixed; + alphaCache.insert(key, data); + } + return true; +} + + +// ------------------------------------------------------------------------------------------------ + +/*! + \class QWindowsXPStyle + \brief The QWindowsXPStyle class provides a Microsoft Windows XP-like look and feel. + + \ingroup appearance + \inmodule QtWidgets + \internal + + \warning This style is only available on the Windows XP platform + because it makes use of Windows XP's style engine. + + Most of the functions are documented in the base classes + QWindowsStyle, QCommonStyle, and QStyle, but the + QWindowsXPStyle overloads of drawComplexControl(), drawControl(), + drawControlMask(), drawPrimitive(), proxy()->subControlRect(), and + sizeFromContents(), are documented here. + + \image qwindowsxpstyle.png + \sa QMacStyle, QWindowsStyle, QFusionStyle +*/ + +/*! + Constructs a QWindowsStyle +*/ +QWindowsXPStyle::QWindowsXPStyle() + : QWindowsStyle(*new QWindowsXPStylePrivate) +{ +} + +/*! + Destroys the style. +*/ +QWindowsXPStyle::~QWindowsXPStyle() +{ +} + +/*! \reimp */ +void QWindowsXPStyle::unpolish(QApplication *app) +{ + QWindowsStyle::unpolish(app); +} + +/*! \reimp */ +void QWindowsXPStyle::polish(QApplication *app) +{ + QWindowsStyle::polish(app); + if (!QWindowsXPStylePrivate::useXP()) + return; +} + +/*! \reimp */ +void QWindowsXPStyle::polish(QWidget *widget) +{ + QWindowsStyle::polish(widget); + if (!QWindowsXPStylePrivate::useXP()) + return; + + if (false +#if QT_CONFIG(abstractbutton) + || qobject_cast<QAbstractButton*>(widget) +#endif + || qobject_cast<QToolButton*>(widget) + || qobject_cast<QTabBar*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox*>(widget) +#endif // QT_NO_COMBOBOX + || qobject_cast<QScrollBar*>(widget) + || qobject_cast<QSlider*>(widget) + || qobject_cast<QHeaderView*>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox*>(widget) + || qobject_cast<QSpinBox*>(widget) +#endif // QT_NO_SPINBOX + ) { + widget->setAttribute(Qt::WA_Hover); + } + +#ifndef QT_NO_RUBBERBAND + if (qobject_cast<QRubberBand*>(widget)) { + widget->setWindowOpacity(0.6); + } +#endif + if (qobject_cast<QStackedWidget*>(widget) && + qobject_cast<QTabWidget*>(widget->parent())) + widget->parentWidget()->setAttribute(Qt::WA_ContentsPropagated); + + Q_D(QWindowsXPStyle); + if (!d->hasInitColors) { + // Get text color for group box labels + COLORREF cref; + XPThemeData theme(widget, 0, QWindowsXPStylePrivate::ButtonTheme, 0, 0); + GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColor = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_DISABLED, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColorDisabled = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + // Where does this color come from? + //GetThemeColor(theme.handle(), TKP_TICS, TSS_NORMAL, TMT_COLOR, &cref); + d->sliderTickColor = qRgb(165, 162, 148); + d->hasInitColors = true; + } +} + +/*! \reimp */ +void QWindowsXPStyle::polish(QPalette &pal) +{ + QWindowsStyle::polish(pal); + pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(110)); +} + +/*! \reimp */ +void QWindowsXPStyle::unpolish(QWidget *widget) +{ +#ifndef QT_NO_RUBBERBAND + if (qobject_cast<QRubberBand*>(widget)) { + widget->setWindowOpacity(1.0); + } +#endif + Q_D(QWindowsXPStyle); + // Unpolish of widgets is the first thing that + // happens when a theme changes, or the theme + // engine is turned off. So we detect it here. + bool oldState = QWindowsXPStylePrivate::useXP(); + bool newState = QWindowsXPStylePrivate::useXP(true); + if ((oldState != newState) && newState) { + d->cleanup(true); + d->init(true); + } else { + // Cleanup handle map, if just changing style, + // or turning it on. In both cases the values + // already in the map might be old (other style). + d->cleanupHandleMap(); + } + if (false +#if QT_CONFIG(abstractbutton) + || qobject_cast<QAbstractButton*>(widget) +#endif + || qobject_cast<QToolButton*>(widget) + || qobject_cast<QTabBar*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox*>(widget) +#endif // QT_NO_COMBOBOX + || qobject_cast<QScrollBar*>(widget) + || qobject_cast<QSlider*>(widget) + || qobject_cast<QHeaderView*>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox*>(widget) + || qobject_cast<QSpinBox*>(widget) +#endif // QT_NO_SPINBOX + ) { + widget->setAttribute(Qt::WA_Hover, false); + } + QWindowsStyle::unpolish(widget); +} + +/*! \reimp */ +QRect QWindowsXPStyle::subElementRect(SubElement sr, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) { + return QWindowsStyle::subElementRect(sr, option, widget); + } + + QRect rect(option->rect); + switch(sr) { + case SE_DockWidgetCloseButton: + case SE_DockWidgetFloatButton: + rect = QWindowsStyle::subElementRect(sr, option, widget); + return rect.translated(0, 1); + break; + case SE_TabWidgetTabContents: + if (qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) + { + rect = QWindowsStyle::subElementRect(sr, option, widget); + if (sr == SE_TabWidgetTabContents) { + if (const QTabWidget *tabWidget = qobject_cast<const QTabWidget *>(widget)) { + if (tabWidget->documentMode()) + break; + } + + rect.adjust(0, 0, -2, -2); + } + } + break; + case SE_TabWidgetTabBar: { + rect = QWindowsStyle::subElementRect(sr, option, widget); + const QStyleOptionTabWidgetFrame *twfOption = + qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option); + if (twfOption && twfOption->direction == Qt::RightToLeft + && (twfOption->shape == QTabBar::RoundedNorth + || twfOption->shape == QTabBar::RoundedSouth)) + { + QStyleOptionTab otherOption; + otherOption.shape = (twfOption->shape == QTabBar::RoundedNorth + ? QTabBar::RoundedEast : QTabBar::RoundedSouth); + int overlap = proxy()->pixelMetric(PM_TabBarBaseOverlap, &otherOption, widget); + int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + rect.adjust(-overlap + borderThickness, 0, -overlap + borderThickness, 0); + } + break;} + + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + MARGINS borderSize; + if (widget) { + XPThemeData buttontheme(widget, 0, QWindowsXPStylePrivate::ButtonTheme); + HTHEME theme = buttontheme.handle(); + if (theme) { + int stateId; + if (!(option->state & State_Enabled)) + stateId = PBS_DISABLED; + else if (option->state & State_Sunken) + stateId = PBS_PRESSED; + else if (option->state & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton) + stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + + int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget); + rect = option->rect.adjusted(border, border, -border, -border); + + if (SUCCEEDED(GetThemeMargins(theme, NULL, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, NULL, &borderSize))) { + rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight, + -borderSize.cxRightWidth, -borderSize.cyBottomHeight); + rect = visualRect(option->direction, option->rect, rect); + } + } + } + } + break; + case SE_ProgressBarContents: + rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget); + if (option->state & QStyle::State_Horizontal) + rect.adjust(4, 3, -4, -3); + else + rect.adjust(3, 2, -3, -2); + break; + default: + rect = QWindowsStyle::subElementRect(sr, option, widget); + } + return rect; +} + +/*! + \reimp +*/ +void QWindowsXPStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, + const QWidget *widget) const +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + + int themeNumber = -1; + int partId = 0; + int stateId = 0; + QRect rect = option->rect; + State flags = option->state; + bool hMirrored = false; + bool vMirrored = false; + bool noBorder = false; + bool noContent = false; + int rotate = 0; + + switch (pe) { + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb + = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) { + p->save(); + switch (tbb->shape) { + case QTabBar::RoundedNorth: + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.topLeft(), tbb->rect.topRight()); + break; + case QTabBar::RoundedWest: + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.left(), tbb->rect.top(), tbb->rect.left(), tbb->rect.bottom()); + break; + case QTabBar::RoundedSouth: + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.left(), tbb->rect.top(), + tbb->rect.right(), tbb->rect.top()); + break; + case QTabBar::RoundedEast: + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.topLeft(), tbb->rect.bottomLeft()); + break; + case QTabBar::TriangularNorth: + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: + case QTabBar::TriangularSouth: + p->restore(); + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + p->restore(); + } + return; + case PE_PanelButtonBevel: + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_PUSHBUTTON; + if (!(flags & State_Enabled)) + stateId = PBS_DISABLED; + else if ((flags & State_Sunken) || (flags & State_On)) + stateId = PBS_PRESSED; + else if (flags & State_MouseOver) + stateId = PBS_HOT; + //else if (flags & State_ButtonDefault) + // stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + break; + + case PE_PanelButtonTool: + if (widget && widget->inherits("QDockWidgetTitleButton")) { + if (const QWidget *dw = widget->parentWidget()) + if (dw->isWindow()) + return; + } + themeNumber = QWindowsXPStylePrivate::ToolBarTheme; + partId = TP_BUTTON; + if (!(flags & State_Enabled)) + stateId = TS_DISABLED; + else if (flags & State_Sunken) + stateId = TS_PRESSED; + else if (flags & State_MouseOver) + stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT; + else if (flags & State_On) + stateId = TS_CHECKED; + else if (!(flags & State_AutoRaise)) + stateId = TS_HOT; + else + stateId = TS_NORMAL; + break; + + case PE_IndicatorButtonDropDown: + themeNumber = QWindowsXPStylePrivate::ToolBarTheme; + partId = TP_SPLITBUTTONDROPDOWN; + if (!(flags & State_Enabled)) + stateId = TS_DISABLED; + else if (flags & State_Sunken) + stateId = TS_PRESSED; + else if (flags & State_MouseOver) + stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT; + else if (flags & State_On) + stateId = TS_CHECKED; + else if (!(flags & State_AutoRaise)) + stateId = TS_HOT; + else + stateId = TS_NORMAL; + if (option->direction == Qt::RightToLeft) + hMirrored = true; + break; + + case PE_IndicatorCheckBox: + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_CHECKBOX; + if (!(flags & State_Enabled)) + stateId = CBS_UNCHECKEDDISABLED; + else if (flags & State_Sunken) + stateId = CBS_UNCHECKEDPRESSED; + else if (flags & State_MouseOver) + stateId = CBS_UNCHECKEDHOT; + else + stateId = CBS_UNCHECKEDNORMAL; + + if (flags & State_On) + stateId += CBS_CHECKEDNORMAL-1; + else if (flags & State_NoChange) + stateId += CBS_MIXEDNORMAL-1; + + break; + + case PE_IndicatorRadioButton: + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_RADIOBUTTON; + if (!(flags & State_Enabled)) + stateId = RBS_UNCHECKEDDISABLED; + else if (flags & State_Sunken) + stateId = RBS_UNCHECKEDPRESSED; + else if (flags & State_MouseOver) + stateId = RBS_UNCHECKEDHOT; + else + stateId = RBS_UNCHECKEDNORMAL; + + if (flags & State_On) + stateId += RBS_CHECKEDNORMAL-1; + break; + + case PE_IndicatorDockWidgetResizeHandle: + return; + +case PE_Frame: + { + if (flags & State_Raised) + return; + themeNumber = QWindowsXPStylePrivate::ListViewTheme; + partId = LVP_LISTGROUP; + XPThemeData theme(widget, 0, themeNumber, partId, 0); + + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else + stateId = ETS_NORMAL; + int fillType; + if (GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &fillType) == S_OK) { + if (fillType == BT_BORDERFILL) { + COLORREF bcRef; + GetThemeColor(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &bcRef); + QColor bordercolor(qRgb(GetRValue(bcRef), GetGValue(bcRef), GetBValue(bcRef))); + QPen oldPen = p->pen(); + // int borderSize = 1; + // GetThemeInt(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &borderSize); + + // Inner white border + p->setPen(QPen(option->palette.base().color(), 1)); + p->drawRect(option->rect.adjusted(1, 1, -2, -2)); + // Outer dark border + p->setPen(QPen(bordercolor, 1)); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + return; + } else if (fillType == BT_NONE) { + return; + } else { + break; + } + } + } + case PE_FrameLineEdit: { + // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class. + if (QWindowsXPStylePrivate::isItemViewDelegateLineEdit(widget)) { + QPen oldPen = p->pen(); + // Inner white border + p->setPen(QPen(option->palette.base().color(), 1)); + p->drawRect(option->rect.adjusted(1, 1, -2, -2)); + // Outer dark border + p->setPen(QPen(option->palette.shadow().color(), 1)); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + return; + } else if (qstyleoption_cast<const QStyleOptionFrame *>(option)) { + themeNumber = QWindowsXPStylePrivate::EditTheme; + partId = EP_EDITTEXT; + noContent = true; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else + stateId = ETS_NORMAL; + } + break; + } + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + themeNumber = QWindowsXPStylePrivate::EditTheme; + partId = EP_EDITTEXT; + noBorder = true; + bool isEnabled = flags & State_Enabled; + + stateId = isEnabled ? ETS_NORMAL : ETS_DISABLED; + + if (QWindowsXPStylePrivate::isLineEditBaseColorSet(option, widget)) { + p->fillRect(panel->rect, panel->palette.brush(QPalette::Base)); + } else { + XPThemeData theme(0, p, themeNumber, partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + int bgType; + GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &bgType); + if( bgType == BT_IMAGEFILE ) { + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + theme.noBorder = noBorder; + theme.noContent = noContent; + theme.rotate = rotate; + d->drawBackground(theme); + } else { + QBrush fillColor = option->palette.brush(QPalette::Base); + + if (!isEnabled) { + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(theme.handle(), theme.partId, theme.stateId, TMT_FILLCOLOR, &origin); + // Use only if the fill property comes from our part + if ((origin == PO_PART || origin == PO_STATE)) { + COLORREF bgRef; + GetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef); + fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef))); + } + } + p->fillRect(option->rect, fillColor); + } + } + + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, panel, p, widget); + return; + } + break; + + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) + { + themeNumber = QWindowsXPStylePrivate::TabTheme; + partId = TABP_PANE; + + if (widget) { + bool useGradient = true; + const int maxlength = 256; + wchar_t themeFileName[maxlength]; + wchar_t themeColor[maxlength]; + // Due to a a scaling issue with the XP Silver theme, tab gradients are not used with it + if (GetCurrentThemeName(themeFileName, maxlength, themeColor, maxlength, NULL, 0) == S_OK) { + wchar_t *offset = 0; + if ((offset = wcsrchr(themeFileName, QChar(QLatin1Char('\\')).unicode())) != NULL) { + offset++; + if (!lstrcmp(offset, L"Luna.msstyles") && !lstrcmp(offset, L"Metallic")) { + useGradient = false; + } + } + } + // This should work, but currently there's an error in the ::drawBackgroundDirectly() + // code, when using the HDC directly.. + if (useGradient) { + QStyleOptionTabWidgetFrame frameOpt = *tab; + frameOpt.rect = widget->rect(); + QRect contentsRect = subElementRect(SE_TabWidgetTabContents, &frameOpt, widget); + QRegion reg = option->rect; + reg -= contentsRect; + p->setClipRegion(reg); + XPThemeData theme(widget, p, themeNumber, partId, stateId, rect); + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + d->drawBackground(theme); + p->setClipRect(contentsRect); + partId = TABP_BODY; + } + } + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + vMirrored = true; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + rotate = 90; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + rotate = 90; + hMirrored = true; + break; + default: + break; + } + } + break; + + case PE_FrameMenu: + p->save(); + p->setPen(option->palette.dark().color()); + p->drawRect(rect.adjusted(0, 0, -1, -1)); + p->restore(); + return; + + case PE_PanelMenuBar: + break; + + case PE_FrameDockWidget: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) + { + themeNumber = QWindowsXPStylePrivate::WindowTheme; + if (flags & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = proxy()->pixelMetric(PM_DockWidgetFrameWidth, frm, widget); + + XPThemeData theme(widget, p, themeNumber, 0, stateId); + if (!theme.isValid()) + break; + theme.rect = QRect(frm->rect.x(), frm->rect.y(), frm->rect.x()+fwidth, frm->rect.height()-fwidth); theme.partId = WP_SMALLFRAMELEFT; + d->drawBackground(theme); + theme.rect = QRect(frm->rect.width()-fwidth, frm->rect.y(), fwidth, frm->rect.height()-fwidth); + theme.partId = WP_SMALLFRAMERIGHT; + d->drawBackground(theme); + theme.rect = QRect(frm->rect.x(), frm->rect.bottom()-fwidth+1, frm->rect.width(), fwidth); + theme.partId = WP_SMALLFRAMEBOTTOM; + d->drawBackground(theme); + return; + } + break; + + case PE_IndicatorHeaderArrow: + { +#if 0 // XP theme engine doesn't know about this :( + name = QWindowsXPStylePrivate::HeaderTheme; + partId = HP_HEADERSORTARROW; + if (flags & State_Down) + stateId = HSAS_SORTEDDOWN; + else + stateId = HSAS_SORTEDUP; +#else + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + p->save(); + p->setPen(option->palette.dark().color()); + p->translate(0, option->rect.height()/2 - 4); + if (header->sortIndicator & QStyleOptionHeader::SortUp) { // invert logic to follow Windows style guide + p->drawLine(option->rect.x(), option->rect.y(), option->rect.x()+8, option->rect.y()); + p->drawLine(option->rect.x()+1, option->rect.y()+1, option->rect.x()+7, option->rect.y()+1); + p->drawLine(option->rect.x()+2, option->rect.y()+2, option->rect.x()+6, option->rect.y()+2); + p->drawLine(option->rect.x()+3, option->rect.y()+3, option->rect.x()+5, option->rect.y()+3); + p->drawPoint(option->rect.x()+4, option->rect.y()+4); + } else if(header->sortIndicator & QStyleOptionHeader::SortDown) { + p->drawLine(option->rect.x(), option->rect.y()+4, option->rect.x()+8, option->rect.y()+4); + p->drawLine(option->rect.x()+1, option->rect.y()+3, option->rect.x()+7, option->rect.y()+3); + p->drawLine(option->rect.x()+2, option->rect.y()+2, option->rect.x()+6, option->rect.y()+2); + p->drawLine(option->rect.x()+3, option->rect.y()+1, option->rect.x()+5, option->rect.y()+1); + p->drawPoint(option->rect.x()+4, option->rect.y()); + } + p->restore(); + return; + } +#endif + } + break; + + case PE_FrameStatusBarItem: + themeNumber = QWindowsXPStylePrivate::StatusTheme; + partId = SP_PANE; + break; + + case PE_FrameGroupBox: + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_GROUPBOX; + if (!(flags & State_Enabled)) + stateId = GBS_DISABLED; + else + stateId = GBS_NORMAL; + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + if (frame->features & QStyleOptionFrame::Flat) { + // Windows XP does not have a theme part for a flat GroupBox, paint it with the windows style + QRect fr = frame->rect; + QPoint p1(fr.x(), fr.y() + 1); + QPoint p2(fr.x() + fr.width(), p1.y() + 1); + rect = QRect(p1, p2); + themeNumber = -1; + } + } + break; + + case PE_IndicatorProgressChunk: + { + Qt::Orientation orient = Qt::Horizontal; + bool inverted = false; + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + orient = pb->orientation; + inverted = pb->invertedAppearance; + } + if (orient == Qt::Horizontal) { + partId = PP_CHUNK; + rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.height() ); + if (inverted && option->direction == Qt::LeftToRight) + hMirrored = true; + } else { + partId = PP_CHUNKVERT; + rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.height()); + } + themeNumber = QWindowsXPStylePrivate::ProgressTheme; + stateId = 1; + } + break; + + case PE_FrameWindow: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) + { + themeNumber = QWindowsXPStylePrivate::WindowTheme; + if (flags & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = frm->lineWidth + frm->midLineWidth; + + XPThemeData theme(0, p, themeNumber, 0, stateId); + if (!theme.isValid()) + break; + + // May fail due to too-large buffers for large widgets, fall back to Windows style. + theme.rect = QRect(option->rect.x(), option->rect.y()+fwidth, option->rect.x()+fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMELEFT; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + theme.rect = QRect(option->rect.width()-fwidth, option->rect.y()+fwidth, fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMERIGHT; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + theme.rect = QRect(option->rect.x(), option->rect.height()-fwidth, option->rect.width(), fwidth); + theme.partId = WP_FRAMEBOTTOM; + if (!d->drawBackground(theme)) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + theme.rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.y()+fwidth); + theme.partId = WP_CAPTION; + if (!d->drawBackground(theme)) + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + break; + + case PE_IndicatorBranch: + { + static const int decoration_size = 9; + int mid_h = option->rect.x() + option->rect.width() / 2; + int mid_v = option->rect.y() + option->rect.height() / 2; + int bef_h = mid_h; + int bef_v = mid_v; + int aft_h = mid_h; + int aft_v = mid_v; + QBrush brush(option->palette.dark().color(), Qt::Dense4Pattern); + if (option->state & State_Item) { + if (option->direction == Qt::RightToLeft) + p->fillRect(option->rect.left(), mid_v, bef_h - option->rect.left(), 1, brush); + else + p->fillRect(aft_h, mid_v, option->rect.right() - aft_h + 1, 1, brush); + } + if (option->state & State_Sibling) + p->fillRect(mid_h, aft_v, 1, option->rect.bottom() - aft_v + 1, brush); + if (option->state & (State_Open | State_Children | State_Item | State_Sibling)) + p->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush); + if (option->state & State_Children) { + int delta = decoration_size / 2; + bef_h -= delta; + bef_v -= delta; + aft_h += delta; + aft_v += delta; + XPThemeData theme(0, p, QWindowsXPStylePrivate::XpTreeViewTheme); + theme.rect = QRect(bef_h, bef_v, decoration_size, decoration_size); + theme.partId = TVP_GLYPH; + theme.stateId = flags & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED; + d->drawBackground(theme); + } + } + return; + + case PE_IndicatorToolBarSeparator: + if (option->rect.height() < 3) { + // XP style requires a few pixels for the separator + // to be visible. + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + themeNumber = QWindowsXPStylePrivate::ToolBarTheme; + partId = TP_SEPARATOR; + + if (option->state & State_Horizontal) + partId = TP_SEPARATOR; + else + partId = TP_SEPARATORVERT; + + break; + + case PE_IndicatorToolBarHandle: + + themeNumber = QWindowsXPStylePrivate::RebarTheme; + partId = RP_GRIPPER; + if (option->state & State_Horizontal) { + partId = RP_GRIPPER; + rect.adjust(0, 0, -2, 0); + } + else { + partId = RP_GRIPPERVERT; + rect.adjust(0, 0, 0, -2); + } + break; + + case PE_IndicatorItemViewItemCheck: { + QStyleOptionButton button; + button.QStyleOption::operator=(*option); + button.state &= ~State_MouseOver; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, p, widget); + return; + } + + default: + break; + } + + XPThemeData theme(widget, p, themeNumber, partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + theme.noBorder = noBorder; + theme.noContent = noContent; + theme.rotate = rotate; + d->drawBackground(theme); +} + +/*! + \reimp +*/ +void QWindowsXPStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *p, + const QWidget *widget) const +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawControl(element, option, p, widget); + return; + } + + QRect rect(option->rect); + State flags = option->state; + + int rotate = 0; + bool hMirrored = false; + bool vMirrored = false; + + int themeNumber = -1; + int partId = 0; + int stateId = 0; + switch (element) { + case CE_SizeGrip: + { + themeNumber = QWindowsXPStylePrivate::StatusTheme; + partId = SP_GRIPPER; + XPThemeData theme(0, p, themeNumber, partId, 0); + QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + size.rheight()--; + if (const QStyleOptionSizeGrip *sg = qstyleoption_cast<const QStyleOptionSizeGrip *>(option)) { + switch (sg->corner) { + case Qt::BottomRightCorner: + rect = QRect(QPoint(rect.right() - size.width(), rect.bottom() - size.height()), size); + break; + case Qt::BottomLeftCorner: + rect = QRect(QPoint(rect.left() + 1, rect.bottom() - size.height()), size); + hMirrored = true; + break; + case Qt::TopRightCorner: + rect = QRect(QPoint(rect.right() - size.width(), rect.top() + 1), size); + vMirrored = true; + break; + case Qt::TopLeftCorner: + rect = QRect(rect.topLeft() + QPoint(1, 1), size); + hMirrored = vMirrored = true; + } + } + } + break; + + case CE_HeaderSection: + themeNumber = QWindowsXPStylePrivate::HeaderTheme; + partId = HP_HEADERITEM; + if (flags & State_Sunken) + stateId = HIS_PRESSED; + else if (flags & State_MouseOver) + stateId = HIS_HOT; + else + stateId = HIS_NORMAL; + break; + + case CE_Splitter: + p->eraseRect(option->rect); + return; + + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) + { + themeNumber = QWindowsXPStylePrivate::ButtonTheme; + partId = BP_PUSHBUTTON; + bool justFlat = ((btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken))) + || ((btn->features & QStyleOptionButton::CommandLinkButton) + && !(flags & State_MouseOver) + && !(btn->features & QStyleOptionButton::DefaultButton)); + if (!(flags & State_Enabled) && !(btn->features & QStyleOptionButton::Flat)) + stateId = PBS_DISABLED; + else if (justFlat) + ; + else if (flags & (State_Sunken | State_On)) + stateId = PBS_PRESSED; + else if (flags & State_MouseOver) + stateId = PBS_HOT; + else if (btn->features & QStyleOptionButton::DefaultButton) + stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + + if (!justFlat) { + XPThemeData theme(widget, p, themeNumber, partId, stateId, rect); + d->drawBackground(theme); + } + + if (btn->features & QStyleOptionButton::HasMenu) { + int mbiw = 0, mbih = 0; + XPThemeData theme(widget, 0, + QWindowsXPStylePrivate::ToolBarTheme, + TP_SPLITBUTTONDROPDOWN); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + mbiw = size.width(); + mbih = size.height(); + } + + QRect ir = btn->rect; + QStyleOptionButton newBtn = *btn; + newBtn.rect = QRect(ir.right() - mbiw - 1, 1 + (ir.height()/2) - (mbih/2), mbiw, mbih); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + return; + } + break; + case CE_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) + { + stateId = tab->state & State_Enabled ? TIS_NORMAL : TIS_DISABLED; + } + break; + + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) + { + themeNumber = QWindowsXPStylePrivate::TabTheme; + bool isDisabled = !(tab->state & State_Enabled); + bool hasFocus = tab->state & State_HasFocus; + bool isHot = tab->state & State_MouseOver; + bool selected = tab->state & State_Selected; + bool lastTab = tab->position == QStyleOptionTab::End; + bool firstTab = tab->position == QStyleOptionTab::Beginning; + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + bool leftAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignLeft; + bool centerAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignCenter; + int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, option, widget); + + if (isDisabled) + stateId = TIS_DISABLED; + else if (selected) + stateId = TIS_SELECTED; + else if (hasFocus) + stateId = TIS_FOCUSED; + else if (isHot) + stateId = TIS_HOT; + else + stateId = TIS_NORMAL; + + // Selecting proper part depending on position + if (firstTab || onlyOne) { + if (leftAligned) { + partId = TABP_TABITEMLEFTEDGE; + } else if (centerAligned) { + partId = TABP_TABITEM; + } else { // rightAligned + partId = TABP_TABITEMRIGHTEDGE; + } + } else { + partId = TABP_TABITEM; + } + + if (tab->direction == Qt::RightToLeft + && (tab->shape == QTabBar::RoundedNorth + || tab->shape == QTabBar::RoundedSouth)) { + bool temp = firstTab; + firstTab = lastTab; + lastTab = temp; + } + bool begin = firstTab || onlyOne; + bool end = lastTab || onlyOne; + switch (tab->shape) { + case QTabBar::RoundedNorth: + if (selected) + rect.adjust(begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap, borderThickness); + else + rect.adjust(begin? tabOverlap : 0, tabOverlap, end ? -tabOverlap : 0, 0); + break; + case QTabBar::RoundedSouth: + //vMirrored = true; + rotate = 180; // Not 100% correct, but works + if (selected) + rect.adjust(begin ? 0 : -tabOverlap , -borderThickness, end ? 0 : tabOverlap, 0); + else + rect.adjust(begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0 , -tabOverlap); + break; + case QTabBar::RoundedEast: + rotate = 90; + if (selected) { + rect.adjust(-borderThickness, begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap); + }else{ + rect.adjust(0, begin ? tabOverlap : 0, -tabOverlap, end ? -tabOverlap : 0); + } + break; + case QTabBar::RoundedWest: + hMirrored = true; + rotate = 90; + if (selected) { + rect.adjust(0, begin ? 0 : -tabOverlap, borderThickness, end ? 0 : tabOverlap); + }else{ + rect.adjust(tabOverlap, begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0); + } + break; + default: + themeNumber = -1; // Do our own painting for triangular + break; + } + + if (!selected) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + rect.adjust(0,0, 0,-1); + break; + case QTabBar::RoundedSouth: + rect.adjust(0,1, 0,0); + break; + case QTabBar::RoundedEast: + rect.adjust( 1,0, 0,0); + break; + case QTabBar::RoundedWest: + rect.adjust(0,0, -1,0); + break; + default: + break; + } + } + } + break; + + case CE_ProgressBarGroove: + { + Qt::Orientation orient = Qt::Horizontal; + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) + orient = pb->orientation; + partId = (orient == Qt::Horizontal) ? PP_BAR : PP_BARVERT; + themeNumber = QWindowsXPStylePrivate::ProgressTheme; + stateId = 1; + } + break; + + case CE_MenuEmptyArea: + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + int tab = menuitem->tabWidth; + bool dis = !(menuitem->state & State_Enabled); + bool act = menuitem->state & State_Selected; + bool checkable = menuitem->menuHasCheckableItems; + bool checked = checkable ? menuitem->checked : false; + + // windows always has a check column, regardless whether we have an icon or not + int checkcol = qMax(menuitem->maxIconWidth, 12); + + int x, y, w, h; + rect.getRect(&x, &y, &w, &h); + + QBrush fill = menuitem->palette.brush(act ? QPalette::Highlight : QPalette::Button); + p->fillRect(rect, fill); + + if (element == CE_MenuEmptyArea) + break; + + // draw separator ------------------------------------------------- + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) { + int yoff = y-1 + h / 2; + p->setPen(menuitem->palette.dark().color()); + p->drawLine(x, yoff, x+w, yoff); + ++yoff; + p->setPen(menuitem->palette.light().color()); + p->drawLine(x, yoff, x+w, yoff); + return; + } + + int xpos = x; + + // draw icon ------------------------------------------------------ + if (!menuitem->icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + if (act && !dis) + mode = QIcon::Active; + QPixmap pixmap = checked ? + menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On) : + menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode); + const int pixw = pixmap.width() / pixmap.devicePixelRatio(); + const int pixh = pixmap.height() / pixmap.devicePixelRatio(); + QRect iconRect(0, 0, pixw, pixh); + iconRect.moveCenter(QRect(xpos, y, checkcol, h).center()); + QRect vIconRect = visualRect(option->direction, option->rect, iconRect); + p->setPen(menuitem->palette.text().color()); + p->setBrush(Qt::NoBrush); + if (checked) + p->drawRect(vIconRect.adjusted(-1, -1, 0, 0)); + p->drawPixmap(vIconRect.topLeft(), pixmap); + + // draw checkmark ------------------------------------------------- + } else if (checked) { + QStyleOptionMenuItem newMi = *menuitem; + newMi.state = State_None; + if (!dis) + newMi.state |= State_Enabled; + if (act) + newMi.state |= State_On; + + QRect checkMarkRect = QRect(menuitem->rect.x() + windowsItemFrame, + menuitem->rect.y() + windowsItemFrame, + checkcol - 2 * windowsItemFrame, + menuitem->rect.height() - 2*windowsItemFrame); + newMi.rect = visualRect(option->direction, option->rect, checkMarkRect); + proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, p, widget); + } + + QColor textColor = dis ? menuitem->palette.text().color() : + act ? menuitem->palette.highlightedText().color() : menuitem->palette.buttonText().color(); + p->setPen(textColor); + + // draw text ------------------------------------------------------ + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + xpos = menuitem->rect.x() + xm; + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(option->direction, option->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { + p->save(); + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignVCenter|Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine | Qt::AlignLeft; + if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget)) + text_flags |= Qt::TextHideMnemonic; + // draw tab text ---------------- + if (t >= 0) { + QRect vShortcutRect = visualRect(option->direction, option->rect, QRect(textRect.topRight(), menuitem->rect.bottomRight())); + if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, option, widget)) { + p->setPen(menuitem->palette.light().color()); + p->drawText(vShortcutRect.adjusted(1,1,1,1), text_flags, s.mid(t + 1)); + p->setPen(textColor); + } + p->drawText(vShortcutRect, text_flags, s.mid(t + 1)); + s = s.left(t); + } + QFont font = menuitem->font; + if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem) + font.setBold(true); + p->setFont(font); + if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, option, widget)) { + p->setPen(menuitem->palette.light().color()); + p->drawText(vTextRect.adjusted(1,1,1,1), text_flags, s.left(t)); + p->setPen(textColor); + } + p->drawText(vTextRect, text_flags, s); + p->restore(); + } + + // draw sub menu arrow -------------------------------------------- + if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) { + int dim = (h - 2) / 2; + PrimitiveElement arrow; + arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim; + QRect vSubMenuRect = visualRect(option->direction, option->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim)); + QStyleOptionMenuItem newMI = *menuitem; + newMI.rect = vSubMenuRect; + newMI.state = dis ? State_None : State_Enabled; + if (act) + newMI.palette.setColor(QPalette::ButtonText, newMI.palette.highlightedText().color()); + proxy()->drawPrimitive(arrow, &newMI, p, widget); + } + } + return; + + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem) + break; + + bool act = mbi->state & State_Selected; + bool dis = !(mbi->state & State_Enabled); + + QBrush fill = mbi->palette.brush(act ? QPalette::Highlight : QPalette::Button); + QPalette::ColorRole textRole = dis ? QPalette::Text: + act ? QPalette::HighlightedText : QPalette::ButtonText; + QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal); + + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + p->fillRect(rect, fill); + if (!pix.isNull()) + drawItemPixmap(p, mbi->rect, alignment, pix); + else + drawItemText(p, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole); + } + return; +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + int buttonMargin = 4; + int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget); + int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget); + bool isFloating = widget && widget->isWindow(); + bool isActive = dwOpt->state & State_Active; + + const bool verticalTitleBar = dwOpt->verticalTitleBar; + + if (verticalTitleBar) { + rect = rect.transposed(); + + p->translate(rect.left() - 1, rect.top() + rect.width()); + p->rotate(-90); + p->translate(-rect.left() + 1, -rect.top()); + } + QRect r = rect.adjusted(0, 2, -1, -3); + QRect titleRect = r; + + if (dwOpt->closable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (dwOpt->floatable) { + QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (isFloating) { + titleRect.adjust(0, -fw, 0, 0); + if (widget != 0 && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey()) + titleRect.adjust(titleRect.height() + mw, 0, 0, 0); + } else { + titleRect.adjust(mw, 0, 0, 0); + if (!dwOpt->floatable && !dwOpt->closable) + titleRect.adjust(0, 0, -mw, 0); + } + + if (!verticalTitleBar) + titleRect = visualRect(dwOpt->direction, r, titleRect); + + if (!isFloating) { + QPen oldPen = p->pen(); + QString titleText = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width()); + p->setPen(dwOpt->palette.color(QPalette::Dark)); + p->drawRect(r); + + if (!titleText.isEmpty()) { + drawItemText(p, titleRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + + p->setPen(oldPen); + } else { + themeNumber = QWindowsXPStylePrivate::WindowTheme; + if (isActive) + stateId = CS_ACTIVE; + else + stateId = CS_INACTIVE; + + int titleHeight = rect.height() - 2; + rect = rect.adjusted(-fw, -fw, fw, 0); + + XPThemeData theme(widget, p, themeNumber, 0, stateId); + if (!theme.isValid()) + break; + + // Draw small type title bar + theme.rect = rect; + theme.partId = WP_SMALLCAPTION; + d->drawBackground(theme); + + // Figure out maximal button space on title bar + + QIcon ico = widget->windowIcon(); + bool hasIcon = (ico.cacheKey() != QApplication::windowIcon().cacheKey()); + if (hasIcon) { + QPixmap pxIco = ico.pixmap(titleHeight); + if (!verticalTitleBar && dwOpt->direction == Qt::RightToLeft) + p->drawPixmap(rect.width() - titleHeight - pxIco.width(), rect.bottom() - titleHeight - 2, pxIco); + else + p->drawPixmap(fw, rect.bottom() - titleHeight - 2, pxIco); + } + if (!dwOpt->title.isEmpty()) { + QPen oldPen = p->pen(); + QFont oldFont = p->font(); + QFont titleFont = oldFont; + titleFont.setBold(true); + p->setFont(titleFont); + QString titleText + = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width()); + + int result = TST_NONE; + GetThemeEnumValue(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + GetThemeColor(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef); + QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef)); + p->setPen(textShadow); + drawItemText(p, titleRect.adjusted(1, 1, 1, 1), + Qt::AlignLeft | Qt::AlignBottom, dwOpt->palette, + dwOpt->state & State_Enabled, titleText); + } + + COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT); + QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText)); + p->setPen(textColor); + drawItemText(p, titleRect, + Qt::AlignLeft | Qt::AlignBottom, dwOpt->palette, + dwOpt->state & State_Enabled, titleText); + p->setFont(oldFont); + p->setPen(oldPen); + } + + } + + return; + } + break; +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_RUBBERBAND + case CE_RubberBand: + if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) { + QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight); + p->save(); + p->setPen(highlight.darker(120)); + QColor dimHighlight(qMin(highlight.red()/2 + 110, 255), + qMin(highlight.green()/2 + 110, 255), + qMin(highlight.blue()/2 + 110, 255), + (widget && widget->isTopLevel())? 255 : 127); + p->setBrush(dimHighlight); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->restore(); + return; + } +#endif // QT_NO_RUBBERBAND + case CE_HeaderEmptyArea: + if (option->state & State_Horizontal) + { + themeNumber = QWindowsXPStylePrivate::HeaderTheme; + stateId = HIS_NORMAL; + } + else { + QWindowsStyle::drawControl(CE_HeaderEmptyArea, option, p, widget); + return; + } + break; + default: + break; + } + + XPThemeData theme(widget, p, themeNumber, partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawControl(element, option, p, widget); + return; + } + + theme.rotate = rotate; + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + d->drawBackground(theme); +} + +QRect QWindowsXPStylePrivate::scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, XPThemeData *theme) +{ + const bool horizontal = flags & QStyle::State_Horizontal; + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + const QMargins contentsMargin = + (theme->margins(theme->rect, TMT_SIZINGMARGINS) * factor).toMargins(); + theme->partId = horizontal ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + const QSize size = (theme->size() * factor).toSize(); + + const int hSpace = theme->rect.width() - size.width(); + const int vSpace = theme->rect.height() - size.height(); + const bool sufficientSpace = (horizontal && hSpace > (contentsMargin.left() + contentsMargin.right())) + || vSpace > contentsMargin.top() + contentsMargin.bottom(); + return sufficientSpace ? QRect(theme->rect.topLeft() + QPoint(hSpace, vSpace) / 2, size) : QRect(); +} + +/*! + \reimp +*/ +void QWindowsXPStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, + QPainter *p, const QWidget *widget) const +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawComplexControl(cc, option, p, widget); + return; + } + + State flags = option->state; + SubControls sub = option->subControls; + QRect r = option->rect; + + int partId = 0; + int stateId = 0; + if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow()) + flags |= State_MouseOver; + + switch (cc) { +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) + { + XPThemeData theme(widget, p, QWindowsXPStylePrivate::SpinTheme); + + if (sb->frame && (sub & SC_SpinBoxFrame)) { + partId = EP_EDITTEXT; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + + XPThemeData ftheme(widget, p, QWindowsXPStylePrivate::EditTheme, + partId, stateId, r); + ftheme.noContent = true; + d->drawBackground(ftheme); + } + if (sub & SC_SpinBoxUp) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget); + partId = SPNP_UP; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) || !(flags & State_Enabled)) + stateId = UPS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) + stateId = UPS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_MouseOver)) + stateId = UPS_HOT; + else + stateId = UPS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_SpinBoxDown) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget); + partId = SPNP_DOWN; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) || !(flags & State_Enabled)) + stateId = DNS_DISABLED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) + stateId = DNS_PRESSED; + else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_MouseOver)) + stateId = DNS_HOT; + else + stateId = DNS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + break; +#endif // QT_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) + { + if (sub & SC_ComboBoxEditField) { + if (cmb->frame) { + partId = EP_EDITTEXT; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + XPThemeData theme(widget, p, QWindowsXPStylePrivate::EditTheme, partId, stateId, r); + d->drawBackground(theme); + } else { + QBrush editBrush = cmb->palette.brush(QPalette::Base); + p->fillRect(option->rect, editBrush); + } + if (!cmb->editable) { + QRect re = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget); + if (option->state & State_HasFocus) { + p->fillRect(re, option->palette.highlight()); + p->setPen(option->palette.highlightedText().color()); + p->setBackground(option->palette.highlight()); + } else { + p->fillRect(re, option->palette.base()); + p->setPen(option->palette.text().color()); + p->setBackground(option->palette.base()); + } + } + } + + if (sub & SC_ComboBoxArrow) { + XPThemeData theme(widget, p, QWindowsXPStylePrivate::ComboboxTheme); + theme.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + partId = CP_DROPDOWNBUTTON; + if (!(flags & State_Enabled)) + stateId = CBXS_DISABLED; + else if (cmb->activeSubControls == SC_ComboBoxArrow && (cmb->state & State_Sunken)) + stateId = CBXS_PRESSED; + else if (cmb->activeSubControls == SC_ComboBoxArrow && (cmb->state & State_MouseOver)) + stateId = CBXS_HOT; + else + stateId = CBXS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + break; +#endif // QT_NO_COMBOBOX + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, p, QWindowsXPStylePrivate::ScrollBarTheme); + bool maxedOut = (scrollbar->maximum == scrollbar->minimum); + if (maxedOut) + flags &= ~State_Enabled; + + bool isHorz = flags & State_Horizontal; + bool isRTL = option->direction == Qt::RightToLeft; + if (sub & SC_ScrollBarAddLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_LEFTDISABLED : ABS_RIGHTDISABLED) : ABS_DOWNDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_LEFTPRESSED : ABS_RIGHTPRESSED) : ABS_DOWNPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_LEFTHOT : ABS_RIGHTHOT) : ABS_DOWNHOT); + else + stateId = (isHorz ? (isRTL ? ABS_LEFTNORMAL : ABS_RIGHTNORMAL) : ABS_DOWNNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSubLine) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget); + partId = SBP_ARROWBTN; + if (!(flags & State_Enabled)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTDISABLED : ABS_LEFTDISABLED) : ABS_UPDISABLED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_Sunken)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTPRESSED : ABS_LEFTPRESSED) : ABS_UPPRESSED); + else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_MouseOver)) + stateId = (isHorz ? (isRTL ? ABS_RIGHTHOT : ABS_LEFTHOT) : ABS_UPHOT); + else + stateId = (isHorz ? (isRTL ? ABS_RIGHTNORMAL : ABS_LEFTNORMAL) : ABS_UPNORMAL); + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (maxedOut) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget)); + theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget)); + partId = scrollbar->orientation == Qt::Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + stateId = SCRBS_DISABLED; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } else { + if (sub & SC_ScrollBarSubPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget); + partId = flags & State_Horizontal ? SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarAddPage) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget); + partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_ScrollBarSlider) { + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + if (!(flags & State_Enabled)) + stateId = SCRBS_DISABLED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_Sunken)) + stateId = SCRBS_PRESSED; + else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_MouseOver)) + stateId = SCRBS_HOT; + else + stateId = SCRBS_NORMAL; + + // Draw handle + theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + theme.stateId = stateId; + d->drawBackground(theme); + + const QRect gripperBounds = QWindowsXPStylePrivate::scrollBarGripperBounds(flags, widget, &theme); + // Draw gripper if there is enough space + if (!gripperBounds.isEmpty()) { + p->save(); + theme.rect = gripperBounds; + p->setClipRegion(d->region(theme));// Only change inside the region of the gripper + d->drawBackground(theme); // Transparent gripper ontop of background + p->restore(); + } + } + } + } + break; + +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, p, QWindowsXPStylePrivate::TrackBarTheme); + QRect slrect = slider->rect; + QRegion tickreg = slrect; + if (sub & SC_SliderGroove) { + theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); + if (slider->orientation == Qt::Horizontal) { + partId = TKP_TRACK; + stateId = TRS_NORMAL; + theme.rect = QRect(slrect.left(), theme.rect.center().y() - 2, slrect.width(), 4); + } else { + partId = TKP_TRACKVERT; + stateId = TRVS_NORMAL; + theme.rect = QRect(theme.rect.center().x() - 2, slrect.top(), 4, slrect.height()); + } + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + tickreg -= theme.rect; + } + if (sub & SC_SliderTickmarks) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int ticks = slider->tickPosition; + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget); + int interval = slider->tickInterval; + if (interval <= 0) { + interval = slider->singleStep; + if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, + available) + - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + 0, available) < 3) + interval = slider->pageStep; + } + if (!interval) + interval = 1; + int fudge = len / 2; + int pos; + int bothOffset = (ticks & QSlider::TicksAbove && ticks & QSlider::TicksBelow) ? 1 : 0; + p->setPen(d->sliderTickColor); + QVarLengthArray<QLine, 32> lines; + int v = slider->minimum; + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3; + pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + v_, available) + fudge; + if (slider->orientation == Qt::Horizontal) { + if (ticks & QSlider::TicksAbove) + lines.append(QLine(pos, tickOffset - 1 - bothOffset, + pos, tickOffset - 1 - bothOffset - tickLength)); + + if (ticks & QSlider::TicksBelow) + lines.append(QLine(pos, tickOffset + thickness + bothOffset, + pos, tickOffset + thickness + bothOffset + tickLength)); + } else { + if (ticks & QSlider::TicksAbove) + lines.append(QLine(tickOffset - 1 - bothOffset, pos, + tickOffset - 1 - bothOffset - tickLength, pos)); + + if (ticks & QSlider::TicksBelow) + lines.append(QLine(tickOffset + thickness + bothOffset, pos, + tickOffset + thickness + bothOffset + tickLength, pos)); + } + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + if (lines.size() > 0) { + p->save(); + p->translate(slrect.topLeft()); + p->drawLines(lines.constData(), lines.size()); + p->restore(); + } + } + if (sub & SC_SliderHandle) { + theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + if (slider->orientation == Qt::Horizontal) { + if (slider->tickPosition == QSlider::TicksAbove) + partId = TKP_THUMBTOP; + else if (slider->tickPosition == QSlider::TicksBelow) + partId = TKP_THUMBBOTTOM; + else + partId = TKP_THUMB; + + if (!(slider->state & State_Enabled)) + stateId = TUS_DISABLED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken)) + stateId = TUS_PRESSED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver)) + stateId = TUS_HOT; + else if (flags & State_HasFocus) + stateId = TUS_FOCUSED; + else + stateId = TUS_NORMAL; + } else { + if (slider->tickPosition == QSlider::TicksLeft) + partId = TKP_THUMBLEFT; + else if (slider->tickPosition == QSlider::TicksRight) + partId = TKP_THUMBRIGHT; + else + partId = TKP_THUMBVERT; + + if (!(slider->state & State_Enabled)) + stateId = TUVS_DISABLED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken)) + stateId = TUVS_PRESSED; + else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver)) + stateId = TUVS_HOT; + else if (flags & State_HasFocus) + stateId = TUVS_FOCUSED; + else + stateId = TUVS_NORMAL; + } + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; +#endif +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + QRect button, menuarea; + button = proxy()->subControlRect(cc, toolbutton, SC_ToolButton, widget); + menuarea = proxy()->subControlRect(cc, toolbutton, SC_ToolButtonMenu, widget); + + State bflags = toolbutton->state & ~State_Sunken; + State mflags = bflags; + bool autoRaise = flags & State_AutoRaise; + if (autoRaise) { + if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) { + bflags &= ~State_Raised; + } + } + + if (toolbutton->state & State_Sunken) { + if (toolbutton->activeSubControls & SC_ToolButton) { + bflags |= State_Sunken; + mflags |= State_MouseOver | State_Sunken; + } else if (toolbutton->activeSubControls & SC_ToolButtonMenu) { + mflags |= State_Sunken; + bflags |= State_MouseOver; + } + } + + QStyleOption tool = *toolbutton; + if (toolbutton->subControls & SC_ToolButton) { + if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) { + if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup && autoRaise) { + XPThemeData theme(widget, p, QWindowsXPStylePrivate::ToolBarTheme); + theme.partId = TP_SPLITBUTTON; + theme.rect = button; + if (!(bflags & State_Enabled)) + stateId = TS_DISABLED; + else if (bflags & State_Sunken) + stateId = TS_PRESSED; + else if (bflags & State_MouseOver || !(flags & State_AutoRaise)) + stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT; + else if (bflags & State_On) + stateId = TS_CHECKED; + else + stateId = TS_NORMAL; + if (option->direction == Qt::RightToLeft) + theme.mirrorHorizontally = true; + theme.stateId = stateId; + d->drawBackground(theme); + } else { + tool.rect = option->rect; + tool.state = bflags; + if (autoRaise) // for tool bars + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + else + proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, p, widget); + } + } + } + + if (toolbutton->state & State_HasFocus) { + QStyleOptionFocusRect fr; + fr.QStyleOption::operator=(*toolbutton); + fr.rect.adjust(3, 3, -3, -3); + if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup) + fr.rect.adjust(0, 0, -proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, + toolbutton, widget), 0); + proxy()->drawPrimitive(PE_FrameFocusRect, &fr, p, widget); + } + QStyleOptionToolButton label = *toolbutton; + label.state = bflags; + int fw = 2; + if (!autoRaise) + label.state &= ~State_Sunken; + label.rect = button.adjusted(fw, fw, -fw, -fw); + proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget); + + if (toolbutton->subControls & SC_ToolButtonMenu) { + tool.rect = menuarea; + tool.state = mflags; + if (autoRaise) { + proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget); + } else { + tool.state = mflags; + menuarea.adjust(-2, 0, 0, 0); + // Draw menu button + if ((bflags & State_Sunken) != (mflags & State_Sunken)){ + p->save(); + p->setClipRect(menuarea); + tool.rect = option->rect; + proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, p, 0); + p->restore(); + } + // Draw arrow + p->save(); + p->setPen(option->palette.dark().color()); + p->drawLine(menuarea.left(), menuarea.top() + 3, + menuarea.left(), menuarea.bottom() - 3); + p->setPen(option->palette.light().color()); + p->drawLine(menuarea.left() - 1, menuarea.top() + 3, + menuarea.left() - 1, menuarea.bottom() - 3); + + tool.rect = menuarea.adjusted(2, 3, -2, -1); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget); + p->restore(); + } + } else if (toolbutton->features & QStyleOptionToolButton::HasMenu) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, toolbutton, widget); + QRect ir = toolbutton->rect; + QStyleOptionToolButton newBtn = *toolbutton; + newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + } + break; +#endif // QT_NO_TOOLBUTTON + + case CC_TitleBar: + { + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) + { + bool isActive = tb->titleBarState & QStyle::State_Active; + XPThemeData theme(widget, p, QWindowsXPStylePrivate::WindowTheme); + if (sub & SC_TitleBarLabel) { + + partId = (tb->titleBarState & Qt::WindowMinimized) ? WP_MINCAPTION : WP_CAPTION; + theme.rect = option->rect; + if (widget && !widget->isEnabled()) + stateId = CS_DISABLED; + else if (isActive) + stateId = CS_ACTIVE; + else + stateId = CS_INACTIVE; + + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + + QRect ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarLabel, widget); + + int result = TST_NONE; + GetThemeEnumValue(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + GetThemeColor(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef); + QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef)); + p->setPen(textShadow); + p->drawText(ir.x() + 3, ir.y() + 2, ir.width() - 1, ir.height(), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT); + QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText)); + p->setPen(textColor); + p->drawText(ir.x() + 2, ir.y() + 1, ir.width() - 2, ir.height(), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + if (sub & SC_TitleBarSysMenu && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarSysMenu, widget); + partId = WP_SYSBUTTON; + if ((widget && !widget->isEnabled()) || !isActive) + stateId = SBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_Sunken)) + stateId = SBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_MouseOver)) + stateId = SBS_HOT; + else + stateId = SBS_NORMAL; + if (!tb->icon.isNull()) { + tb->icon.paint(p, theme.rect); + } else { + theme.partId = partId; + theme.stateId = stateId; + if (theme.size().isEmpty()) { + int iconSize = proxy()->pixelMetric(PM_SmallIconSize, tb, widget); + QPixmap pm = proxy()->standardIcon(SP_TitleBarMenuButton, tb, widget).pixmap(iconSize, iconSize); + p->save(); + drawItemPixmap(p, theme.rect, Qt::AlignCenter, pm); + p->restore(); + } else { + d->drawBackground(theme); + } + } + } + + if (sub & SC_TitleBarMinButton && tb->titleBarFlags & Qt::WindowMinimizeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarMinButton, widget); + partId = WP_MINBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarMinButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarMinButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarMaxButton && tb->titleBarFlags & Qt::WindowMaximizeButtonHint + && !(tb->titleBarState & Qt::WindowMaximized)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarMaxButton, widget); + partId = WP_MAXBUTTON; + if (widget && !widget->isEnabled()) + stateId = MAXBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarMaxButton && (option->state & State_Sunken)) + stateId = MAXBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarMaxButton && (option->state & State_MouseOver)) + stateId = MAXBS_HOT; + else if (!isActive) + stateId = MAXBS_INACTIVE; + else + stateId = MAXBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarContextHelpButton + && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarContextHelpButton, widget); + partId = WP_HELPBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarContextHelpButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarContextHelpButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + bool drawNormalButton = (sub & SC_TitleBarNormalButton) + && (((tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + && (tb->titleBarState & Qt::WindowMinimized)) + || ((tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + && (tb->titleBarState & Qt::WindowMaximized))); + if (drawNormalButton) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarNormalButton, widget); + partId = WP_RESTOREBUTTON; + if (widget && !widget->isEnabled()) + stateId = RBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarNormalButton && (option->state & State_Sunken)) + stateId = RBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarNormalButton && (option->state & State_MouseOver)) + stateId = RBS_HOT; + else if (!isActive) + stateId = RBS_INACTIVE; + else + stateId = RBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarShadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarShadeButton, widget); + partId = WP_MINBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarShadeButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarShadeButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarUnshadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && tb->titleBarState & Qt::WindowMinimized) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarUnshadeButton, widget); + partId = WP_RESTOREBUTTON; + if (widget && !widget->isEnabled()) + stateId = RBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarUnshadeButton && (option->state & State_Sunken)) + stateId = RBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarUnshadeButton && (option->state & State_MouseOver)) + stateId = RBS_HOT; + else if (!isActive) + stateId = RBS_INACTIVE; + else + stateId = RBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarCloseButton, widget); + //partId = titlebar->testWFlags(Qt::WA_WState_Tool) ? WP_SMALLCLOSEBUTTON : WP_CLOSEBUTTON; + partId = WP_CLOSEBUTTON; + if (widget && !widget->isEnabled()) + stateId = CBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarCloseButton && (option->state & State_Sunken)) + stateId = CBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarCloseButton && (option->state & State_MouseOver)) + stateId = CBS_HOT; + else if (!isActive) + stateId = CBS_INACTIVE; + else + stateId = CBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + } + break; + +#ifndef QT_NO_MDIAREA + case CC_MdiControls: + { + QRect buttonRect; + XPThemeData theme(widget, p, QWindowsXPStylePrivate::WindowTheme, WP_MDICLOSEBUTTON, CBS_NORMAL); + + if (option->subControls & SC_MdiCloseButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiCloseButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDICLOSEBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiCloseButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiCloseButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + if (option->subControls & SC_MdiNormalButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiNormalButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDIRESTOREBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiNormalButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiNormalButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + if (option->subControls & QStyle::SC_MdiMinButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiMinButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDIMINBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiMinButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiMinButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + } + break; +#endif //QT_NO_MDIAREA +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(option)) + QStyleHelper::drawDial(dial, p); + break; +#endif // QT_NO_DIAL + default: + QWindowsStyle::drawComplexControl(cc, option, p, widget); + break; + } +} + +static inline Qt::Orientation progressBarOrientation(const QStyleOption *option = 0) +{ + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) + return pb->orientation; + return Qt::Horizontal; +} + +int QWindowsXPStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option, const QWidget *widget) +{ + switch (pm) { + case QStyle::PM_IndicatorWidth: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).width(); + case QStyle::PM_IndicatorHeight: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).height(); + case QStyle::PM_ExclusiveIndicatorWidth: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).width(); + case QStyle::PM_ExclusiveIndicatorHeight: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).height(); + case QStyle::PM_ProgressBarChunkWidth: + return progressBarOrientation(option) == Qt::Horizontal + ? XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ProgressTheme, PP_CHUNK).width() + : XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::ProgressTheme, PP_CHUNKVERT).height(); + case QStyle::PM_SliderThickness: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::TrackBarTheme, TKP_THUMB).height(); + case QStyle::PM_TitleBarHeight: + return widget && (widget->windowType() == Qt::Tool) + ? GetSystemMetrics(SM_CYSMCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME) + : GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME); + case QStyle::PM_MdiSubWindowFrameWidth: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::WindowTheme, WP_FRAMELEFT, FS_ACTIVE).width(); + case QStyle::PM_DockWidgetFrameWidth: + return XPThemeData::themeSize(widget, 0, QWindowsXPStylePrivate::WindowTheme, WP_SMALLFRAMERIGHT, FS_ACTIVE).width(); + default: + break; + } + return QWindowsXPStylePrivate::InvalidMetric; +} + +/*! \reimp */ +int QWindowsXPStyle::pixelMetric(PixelMetric pm, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::pixelMetric(pm, option, widget); + + int res = QWindowsXPStylePrivate::pixelMetricFromSystemDp(pm, option, widget); + if (res != QWindowsStylePrivate::InvalidMetric) + return qRound(qreal(res) * QWindowsStylePrivate::nativeMetricScaleFactor(widget)); + + res = 0; + switch (pm) { + case PM_MenuBarPanelWidth: + case PM_ButtonDefaultIndicator: + res = 0; + break; + + case PM_DefaultFrameWidth: + res = qobject_cast<const QListView*>(widget) ? 2 : 1; + break; + case PM_MenuPanelWidth: + case PM_SpinBoxFrameWidth: + res = 1; + break; + + case PM_TabBarTabOverlap: + case PM_MenuHMargin: + case PM_MenuVMargin: + res = 2; + break; + + case PM_TabBarBaseOverlap: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + res = 1; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + res = 2; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + res = 3; + break; + } + } + break; + + case PM_SplitterWidth: + res = qMax(int(QStyleHelper::dpiScaled(5.)), QApplication::globalStrut().width()); + break; + + case PM_MdiSubWindowMinimizedWidth: + res = 160; + break; + +#ifndef QT_NO_TOOLBAR + case PM_ToolBarHandleExtent: + res = int(QStyleHelper::dpiScaled(8.)); + break; + +#endif // QT_NO_TOOLBAR + case PM_DockWidgetSeparatorExtent: + case PM_DockWidgetTitleMargin: + res = int(QStyleHelper::dpiScaled(4.)); + break; + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + res = qstyleoption_cast<const QStyleOptionToolButton *>(option) ? 1 : 0; + break; + + default: + res = QWindowsStyle::pixelMetric(pm, option, widget); + } + + return res; +} + +/* + This function is used by subControlRect to check if a button + should be drawn for the given subControl given a set of window flags. +*/ +static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb){ + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + const uint flags = tb->titleBarFlags; + bool retVal = false; + switch (sc) { + case QStyle::SC_TitleBarContextHelpButton: + if (flags & Qt::WindowContextHelpButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarMinButton: + if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarNormalButton: + if (isMinimized && (flags & Qt::WindowMinimizeButtonHint)) + retVal = true; + else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarMaxButton: + if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint)) + retVal = true; + break; + case QStyle::SC_TitleBarShadeButton: + if (!isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarUnshadeButton: + if (isMinimized && flags & Qt::WindowShadeButtonHint) + retVal = true; + break; + case QStyle::SC_TitleBarCloseButton: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + case QStyle::SC_TitleBarSysMenu: + if (flags & Qt::WindowSystemMenuHint) + retVal = true; + break; + default : + retVal = true; + } + return retVal; +} + +/*! + \reimp +*/ +QRect QWindowsXPStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::subControlRect(cc, option, subControl, widget); + + QRect rect; + + switch (cc) { + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + if (!buttonVisible(subControl, tb)) + return rect; + const bool isToolTitle = false; + const int height = tb->rect.height(); + const int width = tb->rect.width(); + const int buttonMargin = int(QStyleHelper::dpiScaled(4)); + const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget); + int buttonHeight = qRound(qreal(GetSystemMetrics(SM_CYSIZE)) * factor) + - buttonMargin; + int buttonWidth = qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * factor) + - buttonMargin; + const int delta = buttonWidth + 2; + int controlTop = option->rect.bottom() - buttonHeight - 2; + const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget); + const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0; + const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0; + const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0; + const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0; + const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0; + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + int offset = 0; + + switch (subControl) { + case SC_TitleBarLabel: + rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height); + if (isToolTitle) { + if (sysmenuHint) { + rect.adjust(0, 0, -buttonWidth - 3, 0); + } + if (minimizeHint || maximizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + } else { + if (sysmenuHint) { + const int leftOffset = height - 8; + rect.adjust(leftOffset, 0, 0, 0); + } + if (minimizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (maximizeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (contextHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + if (shadeHint) + rect.adjust(0, 0, -buttonWidth - 2, 0); + } + break; + + case SC_TitleBarContextHelpButton: + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + offset += delta; + Q_FALLTHROUGH(); + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMinButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarNormalButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarNormalButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMaxButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarShadeButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarUnshadeButton) + break; + Q_FALLTHROUGH(); + case SC_TitleBarCloseButton: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + offset += delta; + else if (subControl == SC_TitleBarCloseButton) + break; + + rect.setRect(width - offset - controlTop + 1, controlTop, + buttonWidth, buttonHeight); + break; + + case SC_TitleBarSysMenu: + { + const int controlTop = 6; + const int controlHeight = height - controlTop - 3; + const int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent)); + if (tb->icon.isNull()) + iconSize = QSize(controlHeight, controlHeight); + int hPad = (controlHeight - iconSize.height())/2; + int vPad = (controlHeight - iconSize.width())/2; + rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height()); + } + break; + default: + break; + } + } + break; + + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = cmb->rect.x(), y = cmb->rect.y(), wi = cmb->rect.width(), he = cmb->rect.height(); + int xpos = x; + xpos += wi - 1 - 16; + + switch (subControl) { + case SC_ComboBoxFrame: + rect = cmb->rect; + break; + + case SC_ComboBoxArrow: + rect = QRect(xpos, y+1, 16, he-2); + break; + + case SC_ComboBoxEditField: + rect = QRect(x+2, y+2, wi-3-16, he-4); + break; + + case SC_ComboBoxListBoxPopup: + rect = cmb->rect; + break; + + default: + break; + } + } + break; +#ifndef QT_NO_MDIAREA + case CC_MdiControls: + { + int numSubControls = 0; + if (option->subControls & SC_MdiCloseButton) + ++numSubControls; + if (option->subControls & SC_MdiMinButton) + ++numSubControls; + if (option->subControls & SC_MdiNormalButton) + ++numSubControls; + if (numSubControls == 0) + break; + + int buttonWidth = option->rect.width() / numSubControls; + int offset = 0; + switch (subControl) { + case SC_MdiCloseButton: + // Only one sub control, no offset needed. + if (numSubControls == 1) + break; + offset += buttonWidth; + Q_FALLTHROUGH(); + case SC_MdiNormalButton: + // No offset needed if + // 1) There's only one sub control + // 2) We have a close button and a normal button (offset already added in SC_MdiClose) + if (numSubControls == 1 || (numSubControls == 2 && !(option->subControls & SC_MdiMinButton))) + break; + if (option->subControls & SC_MdiNormalButton) + offset += buttonWidth; + break; + default: + break; + } + rect = QRect(offset, 0, buttonWidth, option->rect.height()); + break; + } +#endif // QT_NO_MDIAREA + + default: + rect = visualRect(option->direction, option->rect, + QWindowsStyle::subControlRect(cc, option, subControl, widget)); + break; + } + return visualRect(option->direction, option->rect, rect); +} + +/*! + \reimp +*/ +QSize QWindowsXPStyle::sizeFromContents(ContentsType ct, const QStyleOption *option, + const QSize &contentsSize, const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::sizeFromContents(ct, option, contentsSize, widget); + + QSize sz(contentsSize); + switch (ct) { + case CT_LineEdit: + case CT_ComboBox: + { + XPThemeData buttontheme(widget, 0, QWindowsXPStylePrivate::ButtonTheme, BP_PUSHBUTTON, PBS_NORMAL); + if (buttontheme.isValid()) { + const qreal factor = QWindowsXPStylePrivate::nativeMetricScaleFactor(widget); + const QMarginsF borderSize = buttontheme.margins() * factor; + if (!borderSize.isNull()) { + const qreal margin = qreal(2) * factor; + sz.rwidth() += qRound(borderSize.left() + borderSize.right() - margin); + sz.rheight() += int(borderSize.bottom() + borderSize.top() - margin + + qreal(1) / factor - 1); + } + const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin) + 1); + sz += QSize(qMax(pixelMetric(QStyle::PM_ScrollBarExtent, option, widget) + + textMargins, 23), 0); //arrow button + } + } + break; + case CT_SpinBox: + { + //Spinbox adds frame twice + sz = QWindowsStyle::sizeFromContents(ct, option, contentsSize, widget); + int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget); + sz -= QSize(2*border, 2*border); + } + break; + case CT_TabWidget: + sz += QSize(6, 6); + break; + case CT_Menu: + sz += QSize(1, 0); + break; +#ifndef QT_NO_MENUBAR + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(windowsItemHMargin * 5 + 1, 6); + break; +#endif + case CT_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + if (menuitem->menuItemType != QStyleOptionMenuItem::Separator) { + sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget); + sz.setHeight(sz.height() - 2); + return sz; + } + } + sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget); + break; + + case CT_MdiControls: + if (const QStyleOptionComplex *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(option)) { + int width = 0; + if (styleOpt->subControls & SC_MdiMinButton) + width += 17 + 1; + if (styleOpt->subControls & SC_MdiNormalButton) + width += 17 + 1; + if (styleOpt->subControls & SC_MdiCloseButton) + width += 17 + 1; + sz = QSize(width, 19); + } else { + sz = QSize(54, 19); + } + break; + + default: + sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget); + break; + } + + return sz; +} + + +/*! \reimp */ +int QWindowsXPStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::styleHint(hint, option, widget, returnData); + + int res = 0; + switch (hint) { + + case SH_EtchDisabledText: + res = (qobject_cast<const QLabel*>(widget) != 0); + break; + + case SH_SpinControls_DisableOnBounds: + res = 0; + break; + + case SH_TitleBar_AutoRaise: + case SH_TitleBar_NoBorder: + res = 1; + break; + + case SH_GroupBox_TextLabelColor: + if (!widget || (widget && widget->isEnabled())) + res = d->groupBoxTextColor; + else + res = d->groupBoxTextColorDisabled; + break; + + case SH_Table_GridLineColor: + res = 0xC0C0C0; + break; + + case SH_WindowFrame_Mask: + { + res = 1; + QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData); + const QStyleOptionTitleBar *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(option); + if (mask && titlebar) { + // Note certain themes will not return the whole window frame but only the titlebar part when + // queried This function needs to return the entire window mask, hence we will only fetch the mask for the + // titlebar itself and add the remaining part of the window rect at the bottom. + int tbHeight = proxy()->pixelMetric(PM_TitleBarHeight, option, widget); + QRect titleBarRect = option->rect; + titleBarRect.setHeight(tbHeight); + XPThemeData themeData; + if (titlebar->titleBarState & Qt::WindowMinimized) { + themeData = XPThemeData(widget, 0, + QWindowsXPStylePrivate::WindowTheme, + WP_MINCAPTION, CS_ACTIVE, titleBarRect); + } else + themeData = XPThemeData(widget, 0, + QWindowsXPStylePrivate::WindowTheme, + WP_CAPTION, CS_ACTIVE, titleBarRect); + mask->region = d->region(themeData) + + QRect(0, tbHeight, option->rect.width(), option->rect.height() - tbHeight); + } + } + break; +#ifndef QT_NO_RUBBERBAND + case SH_RubberBand_Mask: + if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) { + res = 0; + break; + } +#endif // QT_NO_RUBBERBAND + + case SH_ItemView_DrawDelegateFrame: + res = 1; + break; + + default: + res =QWindowsStyle::styleHint(hint, option, widget, returnData); + } + + return res; +} + +/*! \reimp */ +QPalette QWindowsXPStyle::standardPalette() const +{ + if (QWindowsXPStylePrivate::useXP() && QApplicationPrivate::sys_pal) + return *QApplicationPrivate::sys_pal; + else + return QWindowsStyle::standardPalette(); +} + +/*! + \reimp +*/ +QPixmap QWindowsXPStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); + + switch(standardPixmap) { + case SP_TitleBarMaxButton: + case SP_TitleBarCloseButton: + if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + if (widget && widget->isWindow()) { + XPThemeData theme(widget, 0, QWindowsXPStylePrivate::WindowTheme, WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + return QIcon(QWindowsStyle::standardPixmap(standardPixmap, option, widget)).pixmap(size); + } + } + } + break; + default: + break; + } + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); +} + +/*! + \reimp +*/ +QIcon QWindowsXPStyle::standardIcon(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) { + return QWindowsStyle::standardIcon(standardIcon, option, widget); + } + + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + switch(standardIcon) { + case SP_TitleBarMaxButton: + if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + if (d->dockFloat.isNull()) { + XPThemeData themeSize(0, 0, QWindowsXPStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + XPThemeData theme(0, 0, QWindowsXPStylePrivate::WindowTheme, + WP_MAXBUTTON, MAXBS_NORMAL); + if (theme.isValid()) { + const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = MAXBS_PUSHED; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = MAXBS_HOT; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = MAXBS_INACTIVE; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->dockFloat; + + } + break; + case SP_TitleBarCloseButton: + if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + if (d->dockClose.isNull()) { + XPThemeData theme(0, 0, QWindowsXPStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.partId = WP_CLOSEBUTTON; // #### + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = CBS_PUSHED; + d->drawBackground(theme); + d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = CBS_HOT; + d->drawBackground(theme); + d->dockClose.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = CBS_INACTIVE; + d->drawBackground(theme); + d->dockClose.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->dockClose; + } + break; + case SP_TitleBarNormalButton: + if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + if (d->dockFloat.isNull()) { + XPThemeData themeSize(0, 0, QWindowsXPStylePrivate::WindowTheme, + WP_SMALLCLOSEBUTTON, CBS_NORMAL); + XPThemeData theme(0, 0, QWindowsXPStylePrivate::WindowTheme, + WP_RESTOREBUTTON, RBS_NORMAL); + if (theme.isValid()) { + const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize(); + QPixmap pm(size); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(QPoint(0, 0), size); + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal + pm.fill(Qt::transparent); + theme.stateId = RBS_PUSHED; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed + pm.fill(Qt::transparent); + theme.stateId = RBS_HOT; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover + pm.fill(Qt::transparent); + theme.stateId = RBS_INACTIVE; + d->drawBackground(theme); + d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled + } + } + if (widget && widget->isWindow()) + return d->dockFloat; + + } + break; + default: + break; + } + + return QWindowsStyle::standardIcon(standardIcon, option, widget); +} + +/*! + \internal + + Constructs a QWindowsXPStyle object. +*/ +QWindowsXPStyle::QWindowsXPStyle(QWindowsXPStylePrivate &dd) : QWindowsStyle(dd) +{ +} + + +// Debugging code ---------------------------------------------------------------------[ START ]--- +// The code for this point on is not compiled by default, but only used as assisting +// debugging code when you uncomment the DEBUG_XP_STYLE define at the top of the file. + +#ifdef DEBUG_XP_STYLE +// The schema file expects these to be defined by the user. +#define TMT_ENUMDEF 8 +#define TMT_ENUMVAL TEXT('A') +#define TMT_ENUM TEXT('B') +#define SCHEMA_STRINGS // For 2nd pass on schema file +QT_BEGIN_INCLUDE_NAMESPACE +#include <tmschema.h> +QT_END_INCLUDE_NAMESPACE + +// A property's value, type and name combo +struct PropPair { + int propValue; + int propType; + LPCWSTR propName; +}; + +// Operator for sorting of PropPairs +bool operator<(PropPair a, PropPair b) { + return wcscmp(a.propName, b.propName) < 0; +} + +// Our list of all possible properties +static QList<PropPair> all_props; + + +/*! \internal + Dumps a portion of the full native DIB section double buffer. + The DIB section double buffer is only used when doing special + transformations to the theme part, or when the real double + buffer in the paintengine does not have an HDC we may use + directly. + Since we cannot rely on the pixel data we get from Microsoft + when drawing into the DIB section, we use this function to + see the actual data we got, and can determin the appropriate + action. +*/ +void QWindowsXPStylePrivate::dumpNativeDIB(int w, int h) +{ + if (w && h) { + static int pCount = 0; + DWORD *bufPix = (DWORD*)bufferPixels; + + char *bufferDump = new char[bufferH * bufferW * 16]; + char *bufferPos = bufferDump; + + memset(bufferDump, 0, sizeof(bufferDump)); + bufferPos += sprintf(bufferPos, "const int pixelBufferW%d = %d;\n", pCount, w); + bufferPos += sprintf(bufferPos, "const int pixelBufferH%d = %d;\n", pCount, h); + bufferPos += sprintf(bufferPos, "const unsigned DWORD pixelBuffer%d[] = {", pCount); + for (int iy = 0; iy < h; ++iy) { + bufferPos += sprintf(bufferPos, "\n "); + bufPix = (DWORD*)(bufferPixels + (iy * bufferW * 4)); + for (int ix = 0; ix < w; ++ix) { + bufferPos += sprintf(bufferPos, "0x%08x, ", *bufPix); + ++bufPix; + } + } + bufferPos += sprintf(bufferPos, "\n};\n\n"); + printf(bufferDump); + + delete[] bufferDump; + ++pCount; + } +} + +/*! \internal + Shows the value of a given property for a part. +*/ +static void showProperty(XPThemeData &themeData, const PropPair &prop) +{ + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin); + const char *originStr; + switch(origin) { + case PO_STATE: + originStr = "State "; + break; + case PO_PART: + originStr = "Part "; + break; + case PO_CLASS: + originStr = "Class "; + break; + case PO_GLOBAL: + originStr = "Globl "; + break; + case PO_NOTFOUND: + default: + originStr = "Unkwn "; + break; + } + + switch(prop.propType) { + case TMT_STRING: + { + wchar_t buffer[512]; + GetThemeString(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, buffer, 512); + printf(" (%sString) %-20S: %S\n", originStr, prop.propName, buffer); + } + break; + case TMT_ENUM: + { + int result = -1; + GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sEnum) %-20S: %d\n", originStr, prop.propName, result); + } + break; + case TMT_INT: + { + int result = -1; + GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sint) %-20S: %d\n", originStr, prop.propName, result); + } + break; + case TMT_BOOL: + { + BOOL result = false; + GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sbool) %-20S: %d\n", originStr, prop.propName, result); + } + break; + case TMT_COLOR: + { + COLORREF result = 0; + GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%scolor) %-20S: 0x%08X\n", originStr, prop.propName, result); + } + break; + case TMT_MARGINS: + { + MARGINS result; + memset(&result, 0, sizeof(result)); + GetThemeMargins(themeData.handle(), 0, themeData.partId, themeData.stateId, prop.propValue, 0, &result); + printf(" (%smargins) %-20S: (%d, %d, %d, %d)\n", originStr, + prop.propName, result.cxLeftWidth, result.cyTopHeight, result.cxRightWidth, result.cyBottomHeight); + } + break; + case TMT_FILENAME: + { + wchar_t buffer[512]; + GetThemeFilename(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, buffer, 512); + printf(" (%sfilename)%-20S: %S\n", originStr, prop.propName, buffer); + } + break; + case TMT_SIZE: + { + SIZE result1; + SIZE result2; + SIZE result3; + memset(&result1, 0, sizeof(result1)); + memset(&result2, 0, sizeof(result2)); + memset(&result3, 0, sizeof(result3)); + GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_MIN, &result1); + GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_TRUE, &result2); + GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_DRAW, &result3); + printf(" (%ssize) %-20S: Min (%d, %d), True(%d, %d), Draw(%d, %d)\n", originStr, prop.propName, + result1.cx, result1.cy, result2.cx, result2.cy, result3.cx, result3.cy); + } + break; + case TMT_POSITION: + { + POINT result; + memset(&result, 0, sizeof(result)); + GetThemePosition(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sPosition)%-20S: (%d, %d)\n", originStr, prop.propName, result.x, result.y); + } + break; + case TMT_RECT: + { + RECT result; + memset(&result, 0, sizeof(result)); + GetThemeRect(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sRect) %-20S: (%d, %d, %d, %d)\n", originStr, prop.propName, result.left, result.top, result.right, result.bottom); + } + break; + case TMT_FONT: + { + LOGFONT result; + memset(&result, 0, sizeof(result)); + GetThemeFont(themeData.handle(), 0, themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sFont) %-20S: %S height(%d) width(%d) weight(%d)\n", originStr, prop.propName, + result.lfFaceName, result.lfHeight, result.lfWidth, result.lfWeight); + } + break; + case TMT_INTLIST: + { + INTLIST result; + memset(&result, 0, sizeof(result)); + GetThemeIntList(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result); + printf(" (%sInt list)%-20S: { ", originStr, prop.propName); + for (int i = 0; i < result.iValueCount; ++i) + printf("%d ", result.iValues[i]); + printf("}\n"); + } + break; + default: + printf(" %s%S : Unknown property type (%d)!\n", originStr, prop.propName, prop.propType); + } +} + +/*! \internal + Dump all valid properties for a part. + If it's the first time this function is called, then the name, + enum value and documentation of all properties are shown, as + well as all global properties. +*/ +void QWindowsXPStylePrivate::showProperties(XPThemeData &themeData) +{ + if (!all_props.count()) { + const TMSCHEMAINFO *infoTable = GetSchemaInfo(); + for (int i = 0; i < infoTable->iPropCount; ++i) { + int propType = infoTable->pPropTable[i].bPrimVal; + int propValue = infoTable->pPropTable[i].sEnumVal; + LPCWSTR propName = infoTable->pPropTable[i].pszName; + + switch(propType) { + case TMT_ENUMDEF: + case TMT_ENUMVAL: + continue; + default: + if (propType != propValue) { + PropPair prop; + prop.propValue = propValue; + prop.propName = propName; + prop.propType = propType; + all_props.append(prop); + } + } + } + std::sort(all_props.begin(), all_props.end()); + + {// List all properties + printf("part properties count = %d:\n", all_props.count()); + printf(" Enum Property Name Description\n"); + printf("-----------------------------------------------------------\n"); + wchar_t themeName[256]; + pGetCurrentThemeName(themeName, 256, 0, 0, 0, 0); + for (int j = 0; j < all_props.count(); ++j) { + PropPair prop = all_props.at(j); + wchar_t buf[500]; + GetThemeDocumentationProperty(themeName, prop.propName, buf, 500); + printf("%3d: (%4d) %-20S %S\n", j, prop.propValue, prop.propName, buf); + } + } + + {// Show Global values + printf("Global Properties:\n"); + for (int j = 0; j < all_props.count(); ++j) { + PropPair prop = all_props.at(j); + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin); + if (origin == PO_GLOBAL) { + showProperty(themeData, prop); + } + } + } + } + + for (int j = 0; j < all_props.count(); ++j) { + PropPair prop = all_props.at(j); + PROPERTYORIGIN origin = PO_NOTFOUND; + GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin); + if (origin != PO_NOTFOUND) + { + showProperty(themeData, prop); + } + } +} +#endif +// Debugging code -----------------------------------------------------------------------[ END ]--- + + +QT_END_NAMESPACE diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h b/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h new file mode 100644 index 0000000000..d00620eefa --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSXPSTYLE_P_H +#define QWINDOWSXPSTYLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include <QtWidgets/private/qwindowsstyle_p.h> + +QT_BEGIN_NAMESPACE + +class QWindowsXPStylePrivate; +class QWindowsXPStyle : public QWindowsStyle +{ + Q_OBJECT +public: + QWindowsXPStyle(); + QWindowsXPStyle(QWindowsXPStylePrivate &dd); + ~QWindowsXPStyle(); + + void unpolish(QApplication*); + void polish(QApplication*); + void polish(QWidget*); + void polish(QPalette&); + void unpolish(QWidget*); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, + const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, QPainter *p, + const QWidget *wwidget = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *option, const QWidget *widget = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, + const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p, + const QWidget *widget = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *option, const QSize &contentsSize, + const QWidget *widget = 0) const; + int pixelMetric(PixelMetric pm, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + QPalette standardPalette() const; + QPixmap standardPixmap(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QWindowsXPStyle) + Q_DECLARE_PRIVATE(QWindowsXPStyle) + friend class QStyleFactory; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSXPSTYLE_P_H diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h b/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h new file mode 100644 index 0000000000..721a734829 --- /dev/null +++ b/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSXPSTYLE_P_P_H +#define QWINDOWSXPSTYLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtWidgets/private/qtwidgetsglobal_p.h> +#include "qwindowsxpstyle_p.h" +#include <QtWidgets/private/qwindowsstyle_p_p.h> +#include <qmap.h> +#include <qt_windows.h> + +#include <uxtheme.h> +#include <vssym32.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +// TMT_TEXTSHADOWCOLOR is wrongly defined in mingw +#if TMT_TEXTSHADOWCOLOR != 3818 +#undef TMT_TEXTSHADOWCOLOR +#define TMT_TEXTSHADOWCOLOR 3818 +#endif +#ifndef TST_NONE +# define TST_NONE 0 +#endif + +// These defines are missing from the tmschema, but still exist as +// states for their parts +#ifndef MINBS_INACTIVE +#define MINBS_INACTIVE 5 +#endif +#ifndef MAXBS_INACTIVE +#define MAXBS_INACTIVE 5 +#endif +#ifndef RBS_INACTIVE +#define RBS_INACTIVE 5 +#endif +#ifndef HBS_INACTIVE +#define HBS_INACTIVE 5 +#endif +#ifndef CBS_INACTIVE +#define CBS_INACTIVE 5 +#endif + +// Uncomment define below to build debug assisting code, and output +// #define DEBUG_XP_STYLE + +// Declarations ----------------------------------------------------------------------------------- +class XPThemeData +{ +public: + explicit XPThemeData(const QWidget *w = 0, QPainter *p = 0, int themeIn = -1, + int part = 0, int state = 0, const QRect &r = QRect()) + : widget(w), painter(p), theme(themeIn), htheme(0), partId(part), stateId(state), + mirrorHorizontally(false), mirrorVertically(false), noBorder(false), + noContent(false), rotate(0), rect(r) + {} + + HRGN mask(QWidget *widget); + HTHEME handle(); + + static RECT toRECT(const QRect &qr); + bool isValid(); + + QSizeF size(); + QMarginsF margins(const QRect &rect, int propId = TMT_CONTENTMARGINS); + QMarginsF margins(int propId = TMT_CONTENTMARGINS); + + static QSizeF themeSize(const QWidget *w = 0, QPainter *p = 0, int themeIn = -1, int part = 0, int state = 0); + static QMarginsF themeMargins(const QRect &rect, const QWidget *w = 0, QPainter *p = 0, int themeIn = -1, + int part = 0, int state = 0, int propId = TMT_CONTENTMARGINS); + static QMarginsF themeMargins(const QWidget *w = 0, QPainter *p = 0, int themeIn = -1, + int part = 0, int state = 0, int propId = TMT_CONTENTMARGINS); + + const QWidget *widget; + QPainter *painter; + + int theme; + HTHEME htheme; + int partId; + int stateId; + + uint mirrorHorizontally : 1; + uint mirrorVertically : 1; + uint noBorder : 1; + uint noContent : 1; + uint rotate; + QRect rect; +}; + +struct ThemeMapKey { + int theme; + int partId; + int stateId; + bool noBorder; + bool noContent; + + ThemeMapKey() : partId(-1), stateId(-1) {} + ThemeMapKey(const XPThemeData &data) + : theme(data.theme), partId(data.partId), stateId(data.stateId), + noBorder(data.noBorder), noContent(data.noContent) {} + +}; + +inline uint qHash(const ThemeMapKey &key) +{ return key.theme ^ key.partId ^ key.stateId; } + +inline bool operator==(const ThemeMapKey &k1, const ThemeMapKey &k2) +{ + return k1.theme == k2.theme + && k1.partId == k2.partId + && k1.stateId == k2.stateId; +} + +enum AlphaChannelType { + UnknownAlpha = -1, // Alpha of part & state not yet known + NoAlpha, // Totally opaque, no need to touch alpha (RGB) + MaskAlpha, // Alpha channel must be fixed (ARGB) + RealAlpha // Proper alpha values from Windows (ARGB_Premultiplied) +}; + +struct ThemeMapData { + AlphaChannelType alphaType; // Which type of alpha on part & state + + bool dataValid : 1; // Only used to detect if hash value is ok + bool partIsTransparent : 1; + bool hasAlphaChannel : 1; // True = part & state has real Alpha + bool wasAlphaSwapped : 1; // True = alpha channel needs to be swapped + bool hadInvalidAlpha : 1; // True = alpha channel contained invalid alpha values + + ThemeMapData() : dataValid(false), partIsTransparent(false), + hasAlphaChannel(false), wasAlphaSwapped(false), hadInvalidAlpha(false) {} +}; + +class QWindowsXPStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsXPStyle) +public: + enum Theme { + ButtonTheme, + ComboboxTheme, + EditTheme, + HeaderTheme, + ListViewTheme, + MenuTheme, + ProgressTheme, + RebarTheme, + ScrollBarTheme, + SpinTheme, + TabTheme, + TaskDialogTheme, + ToolBarTheme, + ToolTipTheme, + TrackBarTheme, + XpTreeViewTheme, // '+'/'-' shape treeview indicators (XP) + WindowTheme, + StatusTheme, + VistaTreeViewTheme, // arrow shape treeview indicators (Vista) obtained from "explorer" theme. + NThemes + }; + + QWindowsXPStylePrivate() + : QWindowsStylePrivate(), hasInitColors(false), bufferDC(0), bufferBitmap(0), nullBitmap(0), + bufferPixels(0), bufferW(0), bufferH(0) + { init(); } + + ~QWindowsXPStylePrivate() + { cleanup(); } + + static int pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option = 0, const QWidget *widget = 0); + static int fixedPixelMetric(QStyle::PixelMetric pm, const QStyleOption *option = 0, const QWidget *widget = 0); + + static HWND winId(const QWidget *widget); + + void init(bool force = false); + void cleanup(bool force = false); + void cleanupHandleMap(); + const QPixmap *tabBody(QWidget *widget); + + HBITMAP buffer(int w = 0, int h = 0); + HDC bufferHDC() + { return bufferDC;} + + static bool useXP(bool update = false); + static QRect scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, XPThemeData *theme); + + bool isTransparent(XPThemeData &themeData); + QRegion region(XPThemeData &themeData); + + void setTransparency(QWidget *widget, XPThemeData &themeData); + bool drawBackground(XPThemeData &themeData); + bool drawBackgroundThruNativeBuffer(XPThemeData &themeData, qreal aditionalDevicePixelRatio); + bool drawBackgroundDirectly(HDC dc, XPThemeData &themeData, qreal aditionalDevicePixelRatio); + + bool hasAlphaChannel(const QRect &rect); + bool fixAlphaChannel(const QRect &rect); + bool swapAlphaChannel(const QRect &rect, bool allPixels = false); + + QRgb groupBoxTextColor; + QRgb groupBoxTextColorDisabled; + QRgb sliderTickColor; + bool hasInitColors; + + static HTHEME createTheme(int theme, HWND hwnd); + static QString themeName(int theme); + static inline bool hasTheme(int theme) { return theme >= 0 && theme < NThemes && m_themes[theme]; } + static bool isItemViewDelegateLineEdit(const QWidget *widget); + static bool isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget); + + QIcon dockFloat, dockClose; + +private: +#ifdef DEBUG_XP_STYLE + void dumpNativeDIB(int w, int h); + void showProperties(XPThemeData &themeData); +#endif + + static bool initVistaTreeViewTheming(); + static void cleanupVistaTreeViewTheming(); + + static QBasicAtomicInt ref; + static bool use_xp; + static QPixmap *tabbody; + + QHash<ThemeMapKey, ThemeMapData> alphaCache; + HDC bufferDC; + HBITMAP bufferBitmap; + HBITMAP nullBitmap; + uchar *bufferPixels; + int bufferW, bufferH; + + static HWND m_vistaTreeViewHelper; + static HTHEME m_themes[NThemes]; +}; + +inline QSizeF XPThemeData::size() +{ + QSizeF result(0, 0); + if (isValid()) { + SIZE size; + if (SUCCEEDED(GetThemePartSize(handle(), 0, partId, stateId, 0, TS_TRUE, &size))) + result = QSize(size.cx, size.cy); + } + return result; +} + +inline QMarginsF XPThemeData::margins(const QRect &qRect, int propId) +{ + QMarginsF result(0, 0, 0 ,0); + if (isValid()) { + MARGINS margins; + RECT rect = XPThemeData::toRECT(qRect); + if (SUCCEEDED(GetThemeMargins(handle(), 0, partId, stateId, propId, &rect, &margins))) + result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight); + } + return result; +} + +inline QMarginsF XPThemeData::margins(int propId) +{ + QMarginsF result(0, 0, 0 ,0); + if (isValid()) { + MARGINS margins; + if (SUCCEEDED(GetThemeMargins(handle(), 0, partId, stateId, propId, NULL, &margins))) + result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight); + } + return result; +} + +inline QSizeF XPThemeData::themeSize(const QWidget *w, QPainter *p, int themeIn, int part, int state) +{ + XPThemeData theme(w, p, themeIn, part, state); + return theme.size(); +} + +inline QMarginsF XPThemeData::themeMargins(const QRect &rect, const QWidget *w, QPainter *p, int themeIn, + int part, int state, int propId) +{ + XPThemeData theme(w, p, themeIn, part, state); + return theme.margins(rect, propId); +} + +inline QMarginsF XPThemeData::themeMargins(const QWidget *w, QPainter *p, int themeIn, + int part, int state, int propId) +{ + XPThemeData theme(w, p, themeIn, part, state); + return theme.margins(propId); +} + +QT_END_NAMESPACE + +#endif //QWINDOWSXPSTYLE_P_P_H diff --git a/src/plugins/styles/windowsvista/windowsvista.pro b/src/plugins/styles/windowsvista/windowsvista.pro new file mode 100644 index 0000000000..f82bcfc91b --- /dev/null +++ b/src/plugins/styles/windowsvista/windowsvista.pro @@ -0,0 +1,22 @@ +TARGET = qwindowsvistastyle + +QT += widgets-private + +SOURCES += main.cpp + +HEADERS += qwindowsvistastyle_p.h qwindowsvistastyle_p_p.h +SOURCES += qwindowsvistastyle.cpp + +HEADERS += qwindowsxpstyle_p.h qwindowsxpstyle_p_p.h +SOURCES += qwindowsxpstyle.cpp + +LIBS_PRIVATE += -lgdi32 -luser32 + +# DEFINES/LIBS needed for qwizard_win.cpp and the styles +include(../../../widgets/kernel/win.pri) + +DISTFILES += windowsvistastyle.json + +PLUGIN_TYPE = styles +PLUGIN_CLASS_NAME = QWindowsVistaStylePlugin +load(qt_plugin) diff --git a/src/plugins/styles/windowsvista/windowsvistastyle.json b/src/plugins/styles/windowsvista/windowsvistastyle.json new file mode 100644 index 0000000000..771aa0c600 --- /dev/null +++ b/src/plugins/styles/windowsvista/windowsvistastyle.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "windowsvista" ] +} |