From 97fcf3bc987a18f32233fea550eb4a5a22e2b822 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Mon, 4 Mar 2013 10:16:42 +0100 Subject: Introducing the Qt Android port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on the Necessitas project by Bogdan Vatra. Contributors to the Qt5 project: BogDan Vatra Eskil Abrahamsen Blomfeldt hjk Oswald Buddenhagen Paul Olav Tvete Robin Burchell Samuel Rødal Yoann Lopes The full history of the Qt5 port can be found in refs/old-heads/android, SHA-1 249ca9ca2c7d876b91b31df9434dde47f9065d0d Change-Id: Iff1a7b2dbb707c986f2639e65e39ed8f22430120 Reviewed-by: Oswald Buddenhagen Reviewed-by: Lars Knoll --- src/widgets/styles/qandroidstyle.cpp | 1601 ++++++++++++++++++++++++++++++++++ src/widgets/styles/qandroidstyle_p.h | 382 ++++++++ src/widgets/styles/qcommonstyle.cpp | 4 + src/widgets/styles/qstylefactory.cpp | 12 + src/widgets/styles/styles.pri | 7 + 5 files changed, 2006 insertions(+) create mode 100644 src/widgets/styles/qandroidstyle.cpp create mode 100644 src/widgets/styles/qandroidstyle_p.h (limited to 'src/widgets/styles') diff --git a/src/widgets/styles/qandroidstyle.cpp b/src/widgets/styles/qandroidstyle.cpp new file mode 100644 index 0000000000..a3c1063b41 --- /dev/null +++ b/src/widgets/styles/qandroidstyle.cpp @@ -0,0 +1,1601 @@ +/**************************************************************************** +** +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidstyle_p.h" + +#if !defined(QT_NO_STYLE_ANDROID) || defined(QT_PLUGIN) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace { + const int textStyle_bold = 1; + const int textStyle_italic = 2; + + const int typeface_sans = 1; + const int typeface_serif = 2; + const int typeface_monospace = 3; + + const quint32 NO_COLOR = 1; + const quint32 TRANSPARENT_COLOR = 0; +} + + +QAndroidStyle::QAndroidStyle() + : QCommonStyle() +{ + QString stylePath(QLatin1String(qgetenv("MINISTRO_ANDROID_STYLE_PATH"))); + + if (stylePath.isEmpty()) + stylePath = QLatin1String("/data/data/org.kde.necessitas.ministro/files/qt/style/"); + Q_ASSERT(!stylePath.isEmpty()); + + QFile f(stylePath + QLatin1String("style.json")); + if (!f.open(QIODevice::ReadOnly)) + return; + + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error); + if (document.isNull()) { + qCritical() << error.errorString(); + return; + } + + if (!document.isObject()) { + qCritical() << "Style.json does not contain a valid style."; + return; + } + + QJsonObject object = document.object(); + for (QJsonObject::const_iterator objectIterator = object.constBegin(); + objectIterator != object.constEnd(); + ++objectIterator) { + QString key = objectIterator.key(); + QJsonValue value = objectIterator.value(); + if (!value.isObject()) { + qWarning("Style.json structure is unrecognized."); + continue; + } + + QJsonObject item = value.toObject(); + QJsonObject::const_iterator attributeIterator = item.find(QLatin1String("qtClass")); + if (attributeIterator != item.constEnd()) { + // The item has palette and font information for a specific Qt Class (e.g. QWidget, QPushButton, etc.) + const QString qtClassName = attributeIterator.value().toString(); + + // Extract font information + QFont font; + + // Font size (in pixels) + attributeIterator = item.find(QLatin1String("TextAppearance_textSize")); + if (attributeIterator != item.constEnd()) + font.setPixelSize(int(attributeIterator.value().toDouble())); + + // Font style + attributeIterator = item.find(QLatin1String("TextAppearance_textStyle")); + if (attributeIterator != item.constEnd()) { + const int style = int(attributeIterator.value().toDouble()); + font.setBold(style & textStyle_bold); + font.setItalic(style & textStyle_italic); + } + + // Font typeface + attributeIterator = item.find(QLatin1String("TextAppearance_typeface")); + if (attributeIterator != item.constEnd()) { + QFont::StyleHint styleHint = QFont::AnyStyle; + switch (int(attributeIterator.value().toDouble())) { + case typeface_sans: + styleHint = QFont::SansSerif; + break; + case typeface_serif: + styleHint = QFont::Serif; + break; + case typeface_monospace: + styleHint = QFont::Monospace; + break; + } + font.setStyleHint(styleHint, QFont::PreferMatch); + } + QApplication::setFont(font, qtClassName.toUtf8()); + // Extract font information + + // Extract palette information + QPalette palette; + attributeIterator = item.find(QLatin1String("TextAppearance_textColor")); + if (attributeIterator != item.constEnd()) + setPaletteColor(attributeIterator.value().toObject().toVariantMap(), palette, QPalette::WindowText); + + attributeIterator = item.find(QLatin1String("TextAppearance_textColorLink")); + if (attributeIterator != item.constEnd()) + setPaletteColor(attributeIterator.value().toObject().toVariantMap(), palette, QPalette::Link); + + attributeIterator = item.find(QLatin1String("TextAppearance_textColorHighlight")); + if (attributeIterator != item.constEnd()) + palette.setColor(QPalette::Highlight, QRgb(int(attributeIterator.value().toDouble()))); + palette.setColor(QPalette::Window, Qt::black); + QApplication::setPalette(palette, qtClassName.toUtf8()); + if (QLatin1String("QWidget") == qtClassName) + m_standardPalette = palette; + // Extract palette information + } + QAndroidStyle::ItemType itemType = qtControl(key); + if (QC_UnknownType == itemType) + continue; + + switch (itemType) { + case QC_Checkbox: + 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; + } + } + QApplication::setPalette(QApplication::palette("simple_list_item"), "QListView"); + QApplication::setFont(QApplication::font("simple_list_item"), "QListView"); + QApplication::setPalette(QApplication::palette("simple_list_item"), "QAbstractItemView"); + QApplication::setFont(QApplication::font("simple_list_item"), "QAbstractItemView"); +} + +QAndroidStyle::~QAndroidStyle() +{ + qDeleteAll(m_androidControlsHash); +} + + +void QAndroidStyle::setPaletteColor(const QVariantMap &object, + QPalette &palette, + QPalette::ColorRole role) +{ + // QPalette::Active -> ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET + palette.setColor(QPalette::Active, + role, + QRgb(object.value(QLatin1String("ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET")).toInt())); + + // QPalette::Inactive -> ENABLED_STATE_SET + palette.setColor(QPalette::Inactive, + role, + QRgb(object.value(QLatin1String("ENABLED_STATE_SET")).toInt())); + + // QPalette::Disabled -> EMPTY_STATE_SET + palette.setColor(QPalette::Disabled, + role, + QRgb(object.value(QLatin1String("EMPTY_STATE_SET")).toInt())); + + palette.setColor(QPalette::Current, role, palette.color(QPalette::Active, role)); + + if (role == QPalette::WindowText) { + // QPalette::BrightText -> PRESSED + // QPalette::Active -> PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET + palette.setColor(QPalette::Active, + QPalette::BrightText, + QRgb(object.value(QLatin1String("PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET")).toInt())); + + // QPalette::Inactive -> PRESSED_ENABLED_STATE_SET + palette.setColor(QPalette::Inactive, + QPalette::BrightText, + QRgb(object.value(QLatin1String("PRESSED_ENABLED_STATE_SET")).toInt())); + + // QPalette::Disabled -> PRESSED_STATE_SET + palette.setColor(QPalette::Disabled, + QPalette::BrightText, + QRgb(object.value(QLatin1String("PRESSED_STATE_SET")).toInt())); + + palette.setColor(QPalette::Current, QPalette::BrightText, palette.color(QPalette::Active, QPalette::BrightText)); + + // QPalette::HighlightedText -> SELECTED + // QPalette::Active -> ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET + palette.setColor(QPalette::Active, + QPalette::HighlightedText, + QRgb(object.value(QLatin1String("ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET")).toInt())); + + // QPalette::Inactive -> ENABLED_SELECTED_STATE_SET + palette.setColor(QPalette::Inactive, + QPalette::HighlightedText, + QRgb(object.value(QLatin1String("ENABLED_SELECTED_STATE_SET")).toInt())); + + // QPalette::Disabled -> SELECTED_STATE_SET + palette.setColor(QPalette::Disabled, + QPalette::HighlightedText, + QRgb(object.value(QLatin1String("SELECTED_STATE_SET")).toInt())); + + palette.setColor(QPalette::Current, + QPalette::HighlightedText, + palette.color(QPalette::Active, QPalette::HighlightedText)); + + // Same colors for Text + palette.setColor(QPalette::Active, QPalette::Text, palette.color(QPalette::Active, role)); + palette.setColor(QPalette::Inactive, QPalette::Text, palette.color(QPalette::Inactive, role)); + palette.setColor(QPalette::Disabled, QPalette::Text, palette.color(QPalette::Disabled, role)); + palette.setColor(QPalette::Current, QPalette::Text, palette.color(QPalette::Current, role)); + + // And for ButtonText + palette.setColor(QPalette::Active, QPalette::ButtonText, palette.color(QPalette::Active, role)); + palette.setColor(QPalette::Inactive, QPalette::ButtonText, palette.color(QPalette::Inactive, role)); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, palette.color(QPalette::Disabled, role)); + palette.setColor(QPalette::Current, QPalette::ButtonText, palette.color(QPalette::Current, role)); + } +} + +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; + case CC_GroupBox: + return QC_View; + 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_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; + + 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_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()) + it.value()->drawControl(opt, p, w); + else + QCommonStyle::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()) { + it.value()->drawControl(opt, p, w); + + switch (itemType) { + case QC_Button: + if (const QStyleOptionButton *buttonOption = + qstyleoption_cast(opt)) { + QMargins padding = it.value()->padding(); + QStyleOptionButton copy (*buttonOption); + copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom()); + QCommonStyle::drawControl(CE_PushButtonLabel, ©, p, w); + } + break; + case QC_Checkbox: + case QC_RadioButton: + if (const QStyleOptionButton *btn = + qstyleoption_cast(opt)) { + const bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt(*btn); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, w); + QCommonStyle::drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, w); + } + break; + case QC_Combobox: + if (const QStyleOptionComboBox *comboboxOption = + qstyleoption_cast(opt)) { + QMargins padding = it.value()->padding(); + QStyleOptionComboBox copy (*comboboxOption); + copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom()); + p->setFont(QApplication::font("simple_spinner_item")); + p->setPen(QApplication::palette("QPushButton").color(QPalette::Active, QPalette::Text)); + QCommonStyle::drawControl(CE_ComboBoxLabel, comboboxOption, p, w); + } + break; + default: + break; + } + } + else + QCommonStyle::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 QCommonStyle::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); + else + QCommonStyle::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(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 QCommonStyle::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); + return QCommonStyle::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: + return 0; + default: + return QCommonStyle::pixelMetric(metric, option, widget); + } + +} + +QSize QAndroidStyle::sizeFromContents(ContentsType ct, + const QStyleOption *opt, + const QSize &contentsSize, + const QWidget *w) const +{ + QSize sz=QCommonStyle::sizeFromContents(ct, opt, contentsSize, w); + 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); + return sz; +} + +QPixmap QAndroidStyle::standardPixmap(StandardPixmap standardPixmap, + const QStyleOption *opt, + const QWidget *widget) const +{ + return QCommonStyle::standardPixmap(standardPixmap, opt, widget); +} + +QPixmap QAndroidStyle::generatedIconPixmap(QIcon::Mode iconMode, + const QPixmap &pixmap, + const QStyleOption *opt) const +{ + return QCommonStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + +QPalette QAndroidStyle::standardPalette() const +{ + return m_standardPalette; +} + +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(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; +} + + +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.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 & array) +{ + foreach (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 int32_t x0 = m_chunkData.xDivs[0]; + const int32_t 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(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 (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) +{ + QVariantList states = drawable.value(QLatin1String("stateslist")).toList(); + foreach (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<draw(painter, opt); +} + +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; + foreach (const StateType & state, m_states) { + if (int(opt->state) == state.first) + return state.second; + uint cost = 0; + + int difference = int(opt->state^state.first); + + if (difference & QStyle::State_Active) + cost += 1000; + + if (difference & QStyle::State_Enabled) + cost += 1000; + + if ((m_itemType == QC_Button || m_itemType == QC_EditText) && (difference & QStyle::State_Raised)) + cost += 1000; + + if ((m_itemType == QC_Button || m_itemType == QC_EditText) && (difference & QStyle::State_Sunken)) + cost += 1000; + + if (difference & QStyle::State_Off) + cost += 1000; + + if (difference & QStyle::State_On) + cost += 1000; + + if (difference & QStyle::State_HasFocus) + cost += 1000; + + if (difference & QStyle::State_Selected) + cost += 1000; + + if (cost < bestCost) { + bestCost = cost; + bestMatch = state.second; + } + } + return bestMatch; +} + +int QAndroidStyle::AndroidStateDrawable::extractState(const QVariantMap &value) +{ + int state = QStyle::State_None; + foreach (const QString key, value.keys()) { + bool val = value.value(key).toString() == QLatin1String("true"); + if (key == QLatin1String("enabled") && val) { + state |= QStyle::State_Enabled; + continue; + } + + if (key == QLatin1String("window_focused") && val) { + state |= QStyle::State_Active; + continue; + } + + if (key == QLatin1String("focused") && val) { + state |= QStyle::State_HasFocus; + continue; + } + + if (key == QLatin1String("checked")) { + state |= val ? QStyle::State_On : QStyle::State_Off; + continue; + } + + if (key == QLatin1String("pressed")) { + state |= val ? QStyle::State_Raised : QStyle::State_Sunken; + state |= QStyle::State_Enabled | QStyle::State_HasFocus; + continue; + } + + if (key == QLatin1String("selected") && val) { + state |= QStyle::State_Selected; + state |= QStyle::State_Enabled | QStyle::State_HasFocus; + continue; + } + + if (key == QLatin1String("active") && val) { + state |= QStyle::State_Active; + continue; + } + + // Keep misspelling for compatibility + if (key == QLatin1String("backgroud") && val) + return -1; + } + return state; +} + +QAndroidStyle::AndroidLayerDrawable::AndroidLayerDrawable(const QVariantMap &drawable, + QAndroidStyle::ItemType itemType) + : AndroidDrawable(drawable, itemType) +{ + QVariantList layers = drawable.value(QLatin1String("layers")).toList(); + foreach (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() +{ + foreach (const LayerType &layer, m_layers) + delete layer.second; +} + +QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidLayerDrawable::type() const +{ + return QAndroidStyle::Layer; +} + +void QAndroidStyle::AndroidLayerDrawable::draw(QPainter *painter, const QStyleOption *opt) const +{ + foreach (const LayerType &layer, m_layers) + layer.second->draw(painter, opt); +} + +QAndroidStyle::AndroidDrawable *QAndroidStyle::AndroidLayerDrawable::layer(int id) const +{ + foreach (const LayerType &layer, m_layers) + if (layer.first == id) + return layer.second; + return 0; +} + +QSize QAndroidStyle::AndroidLayerDrawable::size() const +{ + QSize sz; + foreach (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); +} + +QRect QAndroidStyle::AndroidControl::subElementRect(QStyle::SubElement /*subElement*/, + const QStyleOption *option, + const QWidget */*widget*/) const +{ + if (const AndroidDrawable *drawable=m_background) { + if (drawable->type() == State) + drawable = static_cast(m_background)->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=m_background) { + + if (drawable->type() == State) + drawable = static_cast(m_background)->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(m_background)->bestAndroidStateMatch(0); + return drawable->padding(); + } + return QMargins(); +} + +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); + 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); +} + +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 *progressBarOption = + qstyleoption_cast(option)) { + QStyleOptionProgressBarV2 progressBarV2(*progressBarOption); + if (m_progressDrawable->type() == QAndroidStyle::Layer) { + QAndroidStyle::AndroidDrawable *clipDrawable = static_cast(m_progressDrawable)->layer(m_progressId); + if (clipDrawable->type() == QAndroidStyle::Clip) + static_cast(clipDrawable)->setFactor(double(progressBarV2.progress/(progressBarV2.maximum-progressBarV2.minimum)), + progressBarV2.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(option)) { + QStyleOptionProgressBarV2 progressBarV2(*progressBarOption); + const bool horizontal = progressBarV2.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_maxSize.height()) + r.setHeight(m_maxSize.height()); + } else { + 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(opt)) { + QStyleOptionProgressBarV2 progressBarV2(*progressBarOption); + if (progressBarV2.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(option)) { + double factor = double(styleOption->sliderPosition/(styleOption->maximum-styleOption->minimum)); + if (m_progressDrawable->type()==QAndroidStyle::Layer) { + QAndroidStyle::AndroidDrawable *clipDrawable = static_cast(m_progressDrawable)->layer(m_progressId); + if (clipDrawable->type() == QAndroidStyle::Clip) + static_cast(clipDrawable)->setFactor(factor, styleOption->orientation); + } + const AndroidDrawable *drawable=m_seekBarThumb; + if (drawable->type() == State) + drawable = static_cast(m_seekBarThumb)->bestAndroidStateMatch(option); + QStyleOption copy(*option); + copy.rect.setY((copy.rect.height()-m_minSize.height())/2); + copy.rect.setHeight(m_minSize.height()); + copy.rect.setWidth(copy.rect.width()-drawable->size().width()); + copy.rect.translate(drawable->size().width()/2,0); + m_progressDrawable->draw(p, ©); + if (styleOption->orientation == Qt::Vertical) + qCritical() << "Vertical slider are not supported"; + int pos = (double(copy.rect.width()*factor - drawable->size().width()) / 2); + copy.rect.translate(pos, 0); + copy.rect.setSize(drawable->size()); + m_seekBarThumb->draw(p, ©); + } +} + +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(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(option); + + if (m_seekBarThumb && sc == SC_SliderHandle && styleOption) { + const AndroidDrawable *drawable = m_seekBarThumb; + if (drawable->type() == State) + drawable = static_cast(m_seekBarThumb)->bestAndroidStateMatch(option); + + QRect r(option->rect); + double factor = double(styleOption->sliderPosition/(styleOption->maximum-styleOption->minimum)); + int pos=(double(option->rect.width()*factor - 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; + return AndroidControl::subControlRect(option, sc, widget); +} + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_STYLE_ANDROID) || defined(QT_PLUGIN) diff --git a/src/widgets/styles/qandroidstyle_p.h b/src/widgets/styles/qandroidstyle_p.h new file mode 100644 index 0000000000..35fa61983a --- /dev/null +++ b/src/widgets/styles/qandroidstyle_p.h @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.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 +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_STYLE_ANDROID) + +class Q_GUI_EXPORT QAndroidStyle : public QCommonStyle +{ + 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 xDivs; + QVector yDivs; + QVector 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); + 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 &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); + + private: + typedef QPair StateType; + QList m_states; + }; + + class AndroidLayerDrawable: public AndroidDrawable + { + public: + AndroidLayerDrawable(const QVariantMap &drawable, QAndroidStyle::ItemType itemType); + ~AndroidLayerDrawable(); + virtual AndroidDrawableType type() const; + virtual void draw(QPainter *painter, const QStyleOption *opt) const; + AndroidDrawable *layer(int id) const; + QSize size() const; + private: + typedef QPair LayerType; + QList m_layers; + }; + + 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(); + protected: + 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); + + protected: + 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 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; + + virtual QPalette standardPalette() const; +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); + + static void setPaletteColor(const QVariantMap &object, + QPalette &palette, + QPalette::ColorRole role); +private: + typedef QHash AndroidControlsHash; + AndroidControlsHash m_androidControlsHash; + QPalette m_standardPalette; +}; + +#endif // QT_NO_STYLE_ANDROID + +QT_END_NAMESPACE + +#endif // QANDROIDSTYLE_P_H diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 1a41b6dbaa..52c733b8ec 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -5100,7 +5100,11 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget ret = theme->themeHint(QPlatformTheme::ToolButtonStyle).toInt(); break; case SH_RequestSoftwareInputPanel: +#ifdef Q_OS_ANDROID + ret = RSIP_OnMouseClick; +#else ret = RSIP_OnMouseClickAndAlreadyFocused; +#endif break; case SH_ScrollBar_Transient: ret = false; diff --git a/src/widgets/styles/qstylefactory.cpp b/src/widgets/styles/qstylefactory.cpp index 471d3b748d..5028371e23 100644 --- a/src/widgets/styles/qstylefactory.cpp +++ b/src/widgets/styles/qstylefactory.cpp @@ -48,6 +48,9 @@ #include "qwindowsstyle_p.h" #ifndef QT_NO_STYLE_FUSION #include "qfusionstyle_p.h" +#ifndef QT_NO_STYLE_ANDROID +#include "qandroidstyle_p.h" +#endif #endif #ifndef QT_NO_STYLE_GTK #include "qgtkstyle_p.h" @@ -143,6 +146,11 @@ QStyle *QStyleFactory::create(const QString& key) ret = new QFusionStyle; else #endif +#ifndef QT_NO_STYLE_ANDROID + if (style == QLatin1String("android")) + ret = new QAndroidStyle; + else +#endif #ifndef QT_NO_STYLE_GTK if (style == QLatin1String("gtk") || style == QLatin1String("gtk+")) ret = new QGtkStyle; @@ -206,6 +214,10 @@ QStringList QStyleFactory::keys() (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based))) list << QLatin1String("WindowsVista"); #endif +#ifndef QT_NO_STYLE_ANDROID + if (!list.contains(QLatin1String("Android"))) + list << QLatin1String("Android"); +#endif #ifndef QT_NO_STYLE_GTK if (!list.contains(QLatin1String("GTK+"))) list << QLatin1String("GTK+"); diff --git a/src/widgets/styles/styles.pri b/src/widgets/styles/styles.pri index b15eb1fa48..9f23fb30cc 100644 --- a/src/widgets/styles/styles.pri +++ b/src/widgets/styles/styles.pri @@ -136,3 +136,10 @@ contains( styles, windowsmobile ) { } else { DEFINES += QT_NO_STYLE_WINDOWSMOBILE } + +contains( styles, android ) { + HEADERS += styles/qandroidstyle_p.h + SOURCES += styles/qandroidstyle.cpp +} else { + DEFINES += QT_NO_STYLE_ANDROID +} -- cgit v1.2.3