| /**************************************************************************** |
| ** |
| ** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org> |
| ** 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 "androidjnimain.h" |
| #include "androidjnimenu.h" |
| #include "qandroidplatformtheme.h" |
| #include "qandroidplatformmenubar.h" |
| #include "qandroidplatformmenu.h" |
| #include "qandroidplatformmenuitem.h" |
| #include "qandroidplatformdialoghelpers.h" |
| #include "qandroidplatformfiledialoghelper.h" |
| |
| #include <QCoreApplication> |
| #include <QDebug> |
| #include <QFileInfo> |
| #include <QJsonDocument> |
| #include <QVariant> |
| |
| #include <private/qguiapplication_p.h> |
| #include <private/qhighdpiscaling_p.h> |
| #include <qandroidplatformintegration.h> |
| |
| 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; |
| } |
| |
| static int fontType(const QString &androidControl) |
| { |
| if (androidControl == QLatin1String("defaultStyle")) |
| return QPlatformTheme::SystemFont; |
| if (androidControl == QLatin1String("textViewStyle")) |
| return QPlatformTheme::LabelFont; |
| else if (androidControl == QLatin1String("buttonStyle")) |
| return QPlatformTheme::PushButtonFont; |
| else if (androidControl == QLatin1String("checkboxStyle")) |
| return QPlatformTheme::CheckBoxFont; |
| else if (androidControl == QLatin1String("radioButtonStyle")) |
| return QPlatformTheme::RadioButtonFont; |
| else if (androidControl == QLatin1String("simple_list_item_single_choice")) |
| return QPlatformTheme::ItemViewFont; |
| else if (androidControl == QLatin1String("simple_spinner_dropdown_item")) |
| return QPlatformTheme::ComboMenuItemFont; |
| else if (androidControl == QLatin1String("spinnerStyle")) |
| return QPlatformTheme::ComboLineEditFont; |
| else if (androidControl == QLatin1String("simple_list_item")) |
| return QPlatformTheme::ListViewFont; |
| return -1; |
| } |
| |
| static int paletteType(const QString &androidControl) |
| { |
| if (androidControl == QLatin1String("defaultStyle")) |
| return QPlatformTheme::SystemPalette; |
| if (androidControl == QLatin1String("textViewStyle")) |
| return QPlatformTheme::LabelPalette; |
| else if (androidControl == QLatin1String("buttonStyle")) |
| return QPlatformTheme::ButtonPalette; |
| else if (androidControl == QLatin1String("checkboxStyle")) |
| return QPlatformTheme::CheckBoxPalette; |
| else if (androidControl == QLatin1String("radioButtonStyle")) |
| return QPlatformTheme::RadioButtonPalette; |
| else if (androidControl == QLatin1String("simple_list_item_single_choice")) |
| return QPlatformTheme::ItemViewPalette; |
| else if (androidControl == QLatin1String("editTextStyle")) |
| return QPlatformTheme::TextLineEditPalette; |
| else if (androidControl == QLatin1String("spinnerStyle")) |
| return QPlatformTheme::ComboBoxPalette; |
| return -1; |
| } |
| |
| static void 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)); |
| } |
| } |
| |
| QJsonObject AndroidStyle::loadStyleData() |
| { |
| QString stylePath(QLatin1String(qgetenv("MINISTRO_ANDROID_STYLE_PATH"))); |
| const QLatin1Char slashChar('/'); |
| if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar)) |
| stylePath += slashChar; |
| |
| QString androidTheme = QLatin1String(qgetenv("QT_ANDROID_THEME")); |
| if (!androidTheme.isEmpty() && !androidTheme.endsWith(slashChar)) |
| androidTheme += slashChar; |
| |
| if (stylePath.isEmpty()) { |
| stylePath = QLatin1String("/data/data/org.kde.necessitas.ministro/files/dl/style/") |
| + QLatin1String(qgetenv("QT_ANDROID_THEME_DISPLAY_DPI")) + slashChar; |
| } |
| Q_ASSERT(!stylePath.isEmpty()); |
| |
| if (!androidTheme.isEmpty() && QFileInfo::exists(stylePath + androidTheme + QLatin1String("style.json"))) |
| stylePath += androidTheme; |
| |
| QFile f(stylePath + QLatin1String("style.json")); |
| if (!f.open(QIODevice::ReadOnly)) |
| return QJsonObject(); |
| |
| QJsonParseError error; |
| QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error); |
| if (Q_UNLIKELY(document.isNull())) { |
| qCritical() << error.errorString(); |
| return QJsonObject(); |
| } |
| |
| if (Q_UNLIKELY(!document.isObject())) { |
| qCritical("Style.json does not contain a valid style."); |
| return QJsonObject(); |
| } |
| return document.object(); |
| } |
| |
| static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette) |
| { |
| double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0; |
| std::shared_ptr<AndroidStyle> style = std::make_shared<AndroidStyle>(); |
| style->m_styleData = AndroidStyle::loadStyleData(); |
| if (style->m_styleData.isEmpty()) |
| return std::shared_ptr<AndroidStyle>(); |
| |
| for (QJsonObject::const_iterator objectIterator = style->m_styleData.constBegin(); |
| objectIterator != style->m_styleData.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")); |
| QByteArray qtClassName; |
| if (attributeIterator != item.constEnd()) { |
| // The item has palette and font information for a specific Qt Class (e.g. QWidget, QPushButton, etc.) |
| qtClassName = attributeIterator.value().toString().toLatin1(); |
| } |
| const int ft = fontType(key); |
| if (ft > -1 || !qtClassName.isEmpty()) { |
| // 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() / pixelDensity)); |
| |
| // 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); |
| } |
| if (!qtClassName.isEmpty()) |
| style->m_QWidgetsFonts.insert(qtClassName, font); |
| |
| if (ft > -1) { |
| style->m_fonts.insert(ft, font); |
| if (ft == QPlatformTheme::SystemFont) |
| QGuiApplication::setFont(font); |
| } |
| // Extract font information |
| } |
| |
| const int pt = paletteType(key); |
| if (pt > -1 || !qtClassName.isEmpty()) { |
| // Extract palette information |
| QPalette palette = *defaultPalette; |
| |
| attributeIterator = item.find(QLatin1String("defaultTextColorPrimary")); |
| if (attributeIterator != item.constEnd()) |
| palette.setColor(QPalette::WindowText, QRgb(int(attributeIterator.value().toDouble()))); |
| |
| attributeIterator = item.find(QLatin1String("defaultBackgroundColor")); |
| if (attributeIterator != item.constEnd()) |
| palette.setColor(QPalette::Background, QRgb(int(attributeIterator.value().toDouble()))); |
| |
| 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()))); |
| |
| if (pt == QPlatformTheme::SystemPalette) |
| *defaultPalette = style->m_standardPalette = palette; |
| |
| if (pt > -1) |
| style->m_palettes.insert(pt, palette); |
| // Extract palette information |
| } |
| } |
| return style; |
| } |
| |
| QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface) |
| { |
| QColor background(229, 229, 229); |
| QColor light = background.lighter(150); |
| QColor mid(background.darker(130)); |
| QColor midLight = mid.lighter(110); |
| QColor base(249, 249, 249); |
| QColor disabledBase(background); |
| QColor dark = background.darker(150); |
| QColor darkDisabled = dark.darker(110); |
| QColor text = Qt::black; |
| QColor highlightedText = Qt::black; |
| QColor disabledText = QColor(190, 190, 190); |
| QColor button(241, 241, 241); |
| QColor shadow(201, 201, 201); |
| QColor highlight(148, 210, 231); |
| QColor disabledShadow = shadow.lighter(150); |
| |
| m_defaultPalette = QPalette(Qt::black,background,light,dark,mid,text,base); |
| m_defaultPalette.setBrush(QPalette::Midlight, midLight); |
| m_defaultPalette.setBrush(QPalette::Button, button); |
| m_defaultPalette.setBrush(QPalette::Shadow, shadow); |
| m_defaultPalette.setBrush(QPalette::HighlightedText, highlightedText); |
| |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Text, disabledText); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::WindowText, disabledText); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledText); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Base, disabledBase); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow); |
| |
| m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight); |
| m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight); |
| m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150)); |
| m_androidStyleData = loadAndroidStyle(&m_defaultPalette); |
| QGuiApplication::setPalette(m_defaultPalette); |
| androidPlatformNativeInterface->m_androidStyle = m_androidStyleData; |
| |
| // default in case the style has not set a font |
| m_systemFont = QFont(QLatin1String("Roboto"), 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi |
| } |
| |
| QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const |
| { |
| return new QAndroidPlatformMenuBar; |
| } |
| |
| QPlatformMenu *QAndroidPlatformTheme::createPlatformMenu() const |
| { |
| return new QAndroidPlatformMenu; |
| } |
| |
| QPlatformMenuItem *QAndroidPlatformTheme::createPlatformMenuItem() const |
| { |
| return new QAndroidPlatformMenuItem; |
| } |
| |
| void QAndroidPlatformTheme::showPlatformMenuBar() |
| { |
| QtAndroidMenu::openOptionsMenu(); |
| } |
| |
| static inline int paletteType(QPlatformTheme::Palette type) |
| { |
| switch (type) { |
| case QPlatformTheme::ToolButtonPalette: |
| case QPlatformTheme::ButtonPalette: |
| return QPlatformTheme::ButtonPalette; |
| |
| case QPlatformTheme::CheckBoxPalette: |
| return QPlatformTheme::CheckBoxPalette; |
| |
| case QPlatformTheme::RadioButtonPalette: |
| return QPlatformTheme::RadioButtonPalette; |
| |
| case QPlatformTheme::ComboBoxPalette: |
| return QPlatformTheme::ComboBoxPalette; |
| |
| case QPlatformTheme::TextEditPalette: |
| case QPlatformTheme::TextLineEditPalette: |
| return QPlatformTheme::TextLineEditPalette; |
| |
| case QPlatformTheme::ItemViewPalette: |
| return QPlatformTheme::ItemViewPalette; |
| |
| default: |
| return QPlatformTheme::SystemPalette; |
| } |
| } |
| |
| const QPalette *QAndroidPlatformTheme::palette(Palette type) const |
| { |
| if (m_androidStyleData) { |
| auto it = m_androidStyleData->m_palettes.find(paletteType(type)); |
| if (it != m_androidStyleData->m_palettes.end()) |
| return &(it.value()); |
| } |
| return &m_defaultPalette; |
| } |
| |
| static inline int fontType(QPlatformTheme::Font type) |
| { |
| switch (type) { |
| case QPlatformTheme::LabelFont: |
| return QPlatformTheme::SystemFont; |
| |
| case QPlatformTheme::ToolButtonFont: |
| return QPlatformTheme::PushButtonFont; |
| |
| default: |
| return type; |
| } |
| } |
| |
| const QFont *QAndroidPlatformTheme::font(Font type) const |
| { |
| if (m_androidStyleData) { |
| auto it = m_androidStyleData->m_fonts.find(fontType(type)); |
| if (it != m_androidStyleData->m_fonts.end()) |
| return &(it.value()); |
| } |
| |
| if (type == QPlatformTheme::SystemFont) |
| return &m_systemFont; |
| return 0; |
| } |
| |
| QVariant QAndroidPlatformTheme::themeHint(ThemeHint hint) const |
| { |
| switch (hint) { |
| case StyleNames: |
| if (qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_STYLE") |
| && m_androidStyleData) { |
| return QStringList(QLatin1String("android")); |
| } |
| return QStringList(QLatin1String("fusion")); |
| case DialogButtonBoxLayout: |
| return QVariant(QPlatformDialogHelper::AndroidLayout); |
| case MouseDoubleClickDistance: |
| { |
| int minimumDistance = qEnvironmentVariableIntValue("QT_ANDROID_MINIMUM_MOUSE_DOUBLE_CLICK_DISTANCE"); |
| int ret = minimumDistance; |
| |
| QAndroidPlatformIntegration *platformIntegration |
| = static_cast<QAndroidPlatformIntegration *>(QGuiApplicationPrivate::platformIntegration()); |
| QAndroidPlatformScreen *platformScreen = platformIntegration->screen(); |
| if (platformScreen != 0) { |
| QScreen *screen = platformScreen->screen(); |
| qreal dotsPerInch = screen->physicalDotsPerInch(); |
| |
| // Allow 15% of an inch between clicks when double clicking |
| int distance = qRound(dotsPerInch * 0.15); |
| if (distance > minimumDistance) |
| ret = distance; |
| } |
| |
| if (ret > 0) |
| return ret; |
| |
| Q_FALLTHROUGH(); |
| } |
| default: |
| return QPlatformTheme::themeHint(hint); |
| } |
| } |
| |
| QString QAndroidPlatformTheme::standardButtonText(int button) const |
| { |
| switch (button) { |
| case QPlatformDialogHelper::Yes: |
| return QCoreApplication::translate("QAndroidPlatformTheme", "Yes"); |
| case QPlatformDialogHelper::YesToAll: |
| return QCoreApplication::translate("QAndroidPlatformTheme", "Yes to All"); |
| case QPlatformDialogHelper::No: |
| return QCoreApplication::translate("QAndroidPlatformTheme", "No"); |
| case QPlatformDialogHelper::NoToAll: |
| return QCoreApplication::translate("QAndroidPlatformTheme", "No to All"); |
| } |
| return QPlatformTheme::standardButtonText(button); |
| } |
| |
| bool QAndroidPlatformTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const |
| { |
| if (type == MessageDialog) |
| return qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_DIALOGS") == 1; |
| if (type == FileDialog) |
| return true; |
| return false; |
| } |
| |
| QPlatformDialogHelper *QAndroidPlatformTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const |
| { |
| switch (type) { |
| case MessageDialog: |
| return new QtAndroidDialogHelpers::QAndroidPlatformMessageDialogHelper; |
| case FileDialog: |
| return new QtAndroidFileDialogHelper::QAndroidPlatformFileDialogHelper; |
| default: |
| return 0; |
| } |
| } |
| |
| QT_END_NAMESPACE |