1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "androidjnimain.h"
41 #include "androidjnimenu.h"
42 #include "qandroidplatformtheme.h"
43 #include "qandroidplatformmenubar.h"
44 #include "qandroidplatformmenu.h"
45 #include "qandroidplatformmenuitem.h"
46 #include "qandroidplatformdialoghelpers.h"
47 #include "qandroidplatformfiledialoghelper.h"
48
49 #include <QCoreApplication>
50 #include <QDebug>
51 #include <QFileInfo>
52 #include <QJsonDocument>
53 #include <QVariant>
54
55 #include <private/qguiapplication_p.h>
56 #include <private/qhighdpiscaling_p.h>
57 #include <qandroidplatformintegration.h>
58
59 QT_BEGIN_NAMESPACE
60
61 namespace {
62 const int textStyle_bold = 1;
63 const int textStyle_italic = 2;
64
65 const int typeface_sans = 1;
66 const int typeface_serif = 2;
67 const int typeface_monospace = 3;
68 }
69
fontType(const QString & androidControl)70 static int fontType(const QString &androidControl)
71 {
72 if (androidControl == QLatin1String("defaultStyle"))
73 return QPlatformTheme::SystemFont;
74 if (androidControl == QLatin1String("textViewStyle"))
75 return QPlatformTheme::LabelFont;
76 else if (androidControl == QLatin1String("buttonStyle"))
77 return QPlatformTheme::PushButtonFont;
78 else if (androidControl == QLatin1String("checkboxStyle"))
79 return QPlatformTheme::CheckBoxFont;
80 else if (androidControl == QLatin1String("radioButtonStyle"))
81 return QPlatformTheme::RadioButtonFont;
82 else if (androidControl == QLatin1String("simple_list_item_single_choice"))
83 return QPlatformTheme::ItemViewFont;
84 else if (androidControl == QLatin1String("simple_spinner_dropdown_item"))
85 return QPlatformTheme::ComboMenuItemFont;
86 else if (androidControl == QLatin1String("spinnerStyle"))
87 return QPlatformTheme::ComboLineEditFont;
88 else if (androidControl == QLatin1String("simple_list_item"))
89 return QPlatformTheme::ListViewFont;
90 return -1;
91 }
92
paletteType(const QString & androidControl)93 static int paletteType(const QString &androidControl)
94 {
95 if (androidControl == QLatin1String("defaultStyle"))
96 return QPlatformTheme::SystemPalette;
97 if (androidControl == QLatin1String("textViewStyle"))
98 return QPlatformTheme::LabelPalette;
99 else if (androidControl == QLatin1String("buttonStyle"))
100 return QPlatformTheme::ButtonPalette;
101 else if (androidControl == QLatin1String("checkboxStyle"))
102 return QPlatformTheme::CheckBoxPalette;
103 else if (androidControl == QLatin1String("radioButtonStyle"))
104 return QPlatformTheme::RadioButtonPalette;
105 else if (androidControl == QLatin1String("simple_list_item_single_choice"))
106 return QPlatformTheme::ItemViewPalette;
107 else if (androidControl == QLatin1String("editTextStyle"))
108 return QPlatformTheme::TextLineEditPalette;
109 else if (androidControl == QLatin1String("spinnerStyle"))
110 return QPlatformTheme::ComboBoxPalette;
111 return -1;
112 }
113
setPaletteColor(const QVariantMap & object,QPalette & palette,QPalette::ColorRole role)114 static void setPaletteColor(const QVariantMap &object,
115 QPalette &palette,
116 QPalette::ColorRole role)
117 {
118 // QPalette::Active -> ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET
119 palette.setColor(QPalette::Active,
120 role,
121 QRgb(object.value(QLatin1String("ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET")).toInt()));
122
123 // QPalette::Inactive -> ENABLED_STATE_SET
124 palette.setColor(QPalette::Inactive,
125 role,
126 QRgb(object.value(QLatin1String("ENABLED_STATE_SET")).toInt()));
127
128 // QPalette::Disabled -> EMPTY_STATE_SET
129 palette.setColor(QPalette::Disabled,
130 role,
131 QRgb(object.value(QLatin1String("EMPTY_STATE_SET")).toInt()));
132
133 palette.setColor(QPalette::Current, role, palette.color(QPalette::Active, role));
134
135 if (role == QPalette::WindowText) {
136 // QPalette::BrightText -> PRESSED
137 // QPalette::Active -> PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET
138 palette.setColor(QPalette::Active,
139 QPalette::BrightText,
140 QRgb(object.value(QLatin1String("PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET")).toInt()));
141
142 // QPalette::Inactive -> PRESSED_ENABLED_STATE_SET
143 palette.setColor(QPalette::Inactive,
144 QPalette::BrightText,
145 QRgb(object.value(QLatin1String("PRESSED_ENABLED_STATE_SET")).toInt()));
146
147 // QPalette::Disabled -> PRESSED_STATE_SET
148 palette.setColor(QPalette::Disabled,
149 QPalette::BrightText,
150 QRgb(object.value(QLatin1String("PRESSED_STATE_SET")).toInt()));
151
152 palette.setColor(QPalette::Current, QPalette::BrightText, palette.color(QPalette::Active, QPalette::BrightText));
153
154 // QPalette::HighlightedText -> SELECTED
155 // QPalette::Active -> ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET
156 palette.setColor(QPalette::Active,
157 QPalette::HighlightedText,
158 QRgb(object.value(QLatin1String("ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET")).toInt()));
159
160 // QPalette::Inactive -> ENABLED_SELECTED_STATE_SET
161 palette.setColor(QPalette::Inactive,
162 QPalette::HighlightedText,
163 QRgb(object.value(QLatin1String("ENABLED_SELECTED_STATE_SET")).toInt()));
164
165 // QPalette::Disabled -> SELECTED_STATE_SET
166 palette.setColor(QPalette::Disabled,
167 QPalette::HighlightedText,
168 QRgb(object.value(QLatin1String("SELECTED_STATE_SET")).toInt()));
169
170 palette.setColor(QPalette::Current,
171 QPalette::HighlightedText,
172 palette.color(QPalette::Active, QPalette::HighlightedText));
173
174 // Same colors for Text
175 palette.setColor(QPalette::Active, QPalette::Text, palette.color(QPalette::Active, role));
176 palette.setColor(QPalette::Inactive, QPalette::Text, palette.color(QPalette::Inactive, role));
177 palette.setColor(QPalette::Disabled, QPalette::Text, palette.color(QPalette::Disabled, role));
178 palette.setColor(QPalette::Current, QPalette::Text, palette.color(QPalette::Current, role));
179
180 // And for ButtonText
181 palette.setColor(QPalette::Active, QPalette::ButtonText, palette.color(QPalette::Active, role));
182 palette.setColor(QPalette::Inactive, QPalette::ButtonText, palette.color(QPalette::Inactive, role));
183 palette.setColor(QPalette::Disabled, QPalette::ButtonText, palette.color(QPalette::Disabled, role));
184 palette.setColor(QPalette::Current, QPalette::ButtonText, palette.color(QPalette::Current, role));
185 }
186 }
187
loadStyleData()188 QJsonObject AndroidStyle::loadStyleData()
189 {
190 QString stylePath(QLatin1String(qgetenv("MINISTRO_ANDROID_STYLE_PATH")));
191 const QLatin1Char slashChar('/');
192 if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
193 stylePath += slashChar;
194
195 QString androidTheme = QLatin1String(qgetenv("QT_ANDROID_THEME"));
196 if (!androidTheme.isEmpty() && !androidTheme.endsWith(slashChar))
197 androidTheme += slashChar;
198
199 if (stylePath.isEmpty()) {
200 stylePath = QLatin1String("/data/data/org.kde.necessitas.ministro/files/dl/style/")
201 + QLatin1String(qgetenv("QT_ANDROID_THEME_DISPLAY_DPI")) + slashChar;
202 }
203 Q_ASSERT(!stylePath.isEmpty());
204
205 if (!androidTheme.isEmpty() && QFileInfo::exists(stylePath + androidTheme + QLatin1String("style.json")))
206 stylePath += androidTheme;
207
208 QFile f(stylePath + QLatin1String("style.json"));
209 if (!f.open(QIODevice::ReadOnly))
210 return QJsonObject();
211
212 QJsonParseError error;
213 QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error);
214 if (Q_UNLIKELY(document.isNull())) {
215 qCritical() << error.errorString();
216 return QJsonObject();
217 }
218
219 if (Q_UNLIKELY(!document.isObject())) {
220 qCritical("Style.json does not contain a valid style.");
221 return QJsonObject();
222 }
223 return document.object();
224 }
225
loadAndroidStyle(QPalette * defaultPalette)226 static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette)
227 {
228 double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0;
229 std::shared_ptr<AndroidStyle> style = std::make_shared<AndroidStyle>();
230 style->m_styleData = AndroidStyle::loadStyleData();
231 if (style->m_styleData.isEmpty())
232 return std::shared_ptr<AndroidStyle>();
233
234 for (QJsonObject::const_iterator objectIterator = style->m_styleData.constBegin();
235 objectIterator != style->m_styleData.constEnd();
236 ++objectIterator) {
237 QString key = objectIterator.key();
238 QJsonValue value = objectIterator.value();
239 if (!value.isObject()) {
240 qWarning("Style.json structure is unrecognized.");
241 continue;
242 }
243 QJsonObject item = value.toObject();
244 QJsonObject::const_iterator attributeIterator = item.find(QLatin1String("qtClass"));
245 QByteArray qtClassName;
246 if (attributeIterator != item.constEnd()) {
247 // The item has palette and font information for a specific Qt Class (e.g. QWidget, QPushButton, etc.)
248 qtClassName = attributeIterator.value().toString().toLatin1();
249 }
250 const int ft = fontType(key);
251 if (ft > -1 || !qtClassName.isEmpty()) {
252 // Extract font information
253 QFont font;
254
255 // Font size (in pixels)
256 attributeIterator = item.find(QLatin1String("TextAppearance_textSize"));
257 if (attributeIterator != item.constEnd())
258 font.setPixelSize(int(attributeIterator.value().toDouble() / pixelDensity));
259
260 // Font style
261 attributeIterator = item.find(QLatin1String("TextAppearance_textStyle"));
262 if (attributeIterator != item.constEnd()) {
263 const int style = int(attributeIterator.value().toDouble());
264 font.setBold(style & textStyle_bold);
265 font.setItalic(style & textStyle_italic);
266 }
267
268 // Font typeface
269 attributeIterator = item.find(QLatin1String("TextAppearance_typeface"));
270 if (attributeIterator != item.constEnd()) {
271 QFont::StyleHint styleHint = QFont::AnyStyle;
272 switch (int(attributeIterator.value().toDouble())) {
273 case typeface_sans:
274 styleHint = QFont::SansSerif;
275 break;
276 case typeface_serif:
277 styleHint = QFont::Serif;
278 break;
279 case typeface_monospace:
280 styleHint = QFont::Monospace;
281 break;
282 }
283 font.setStyleHint(styleHint, QFont::PreferMatch);
284 }
285 if (!qtClassName.isEmpty())
286 style->m_QWidgetsFonts.insert(qtClassName, font);
287
288 if (ft > -1) {
289 style->m_fonts.insert(ft, font);
290 if (ft == QPlatformTheme::SystemFont)
291 QGuiApplication::setFont(font);
292 }
293 // Extract font information
294 }
295
296 const int pt = paletteType(key);
297 if (pt > -1 || !qtClassName.isEmpty()) {
298 // Extract palette information
299 QPalette palette = *defaultPalette;
300
301 attributeIterator = item.find(QLatin1String("defaultTextColorPrimary"));
302 if (attributeIterator != item.constEnd())
303 palette.setColor(QPalette::WindowText, QRgb(int(attributeIterator.value().toDouble())));
304
305 attributeIterator = item.find(QLatin1String("defaultBackgroundColor"));
306 if (attributeIterator != item.constEnd())
307 palette.setColor(QPalette::Background, QRgb(int(attributeIterator.value().toDouble())));
308
309 attributeIterator = item.find(QLatin1String("TextAppearance_textColor"));
310 if (attributeIterator != item.constEnd())
311 setPaletteColor(attributeIterator.value().toObject().toVariantMap(), palette, QPalette::WindowText);
312
313 attributeIterator = item.find(QLatin1String("TextAppearance_textColorLink"));
314 if (attributeIterator != item.constEnd())
315 setPaletteColor(attributeIterator.value().toObject().toVariantMap(), palette, QPalette::Link);
316
317 attributeIterator = item.find(QLatin1String("TextAppearance_textColorHighlight"));
318 if (attributeIterator != item.constEnd())
319 palette.setColor(QPalette::Highlight, QRgb(int(attributeIterator.value().toDouble())));
320
321 if (pt == QPlatformTheme::SystemPalette)
322 *defaultPalette = style->m_standardPalette = palette;
323
324 if (pt > -1)
325 style->m_palettes.insert(pt, palette);
326 // Extract palette information
327 }
328 }
329 return style;
330 }
331
QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface)332 QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
333 {
334 QColor background(229, 229, 229);
335 QColor light = background.lighter(150);
336 QColor mid(background.darker(130));
337 QColor midLight = mid.lighter(110);
338 QColor base(249, 249, 249);
339 QColor disabledBase(background);
340 QColor dark = background.darker(150);
341 QColor darkDisabled = dark.darker(110);
342 QColor text = Qt::black;
343 QColor highlightedText = Qt::black;
344 QColor disabledText = QColor(190, 190, 190);
345 QColor button(241, 241, 241);
346 QColor shadow(201, 201, 201);
347 QColor highlight(148, 210, 231);
348 QColor disabledShadow = shadow.lighter(150);
349
350 m_defaultPalette = QPalette(Qt::black,background,light,dark,mid,text,base);
351 m_defaultPalette.setBrush(QPalette::Midlight, midLight);
352 m_defaultPalette.setBrush(QPalette::Button, button);
353 m_defaultPalette.setBrush(QPalette::Shadow, shadow);
354 m_defaultPalette.setBrush(QPalette::HighlightedText, highlightedText);
355
356 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Text, disabledText);
357 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::WindowText, disabledText);
358 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledText);
359 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Base, disabledBase);
360 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled);
361 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow);
362
363 m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
364 m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
365 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150));
366 m_androidStyleData = loadAndroidStyle(&m_defaultPalette);
367 QGuiApplication::setPalette(m_defaultPalette);
368 androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
369
370 // default in case the style has not set a font
371 m_systemFont = QFont(QLatin1String("Roboto"), 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
372 }
373
createPlatformMenuBar() const374 QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const
375 {
376 return new QAndroidPlatformMenuBar;
377 }
378
createPlatformMenu() const379 QPlatformMenu *QAndroidPlatformTheme::createPlatformMenu() const
380 {
381 return new QAndroidPlatformMenu;
382 }
383
createPlatformMenuItem() const384 QPlatformMenuItem *QAndroidPlatformTheme::createPlatformMenuItem() const
385 {
386 return new QAndroidPlatformMenuItem;
387 }
388
showPlatformMenuBar()389 void QAndroidPlatformTheme::showPlatformMenuBar()
390 {
391 QtAndroidMenu::openOptionsMenu();
392 }
393
paletteType(QPlatformTheme::Palette type)394 static inline int paletteType(QPlatformTheme::Palette type)
395 {
396 switch (type) {
397 case QPlatformTheme::ToolButtonPalette:
398 case QPlatformTheme::ButtonPalette:
399 return QPlatformTheme::ButtonPalette;
400
401 case QPlatformTheme::CheckBoxPalette:
402 return QPlatformTheme::CheckBoxPalette;
403
404 case QPlatformTheme::RadioButtonPalette:
405 return QPlatformTheme::RadioButtonPalette;
406
407 case QPlatformTheme::ComboBoxPalette:
408 return QPlatformTheme::ComboBoxPalette;
409
410 case QPlatformTheme::TextEditPalette:
411 case QPlatformTheme::TextLineEditPalette:
412 return QPlatformTheme::TextLineEditPalette;
413
414 case QPlatformTheme::ItemViewPalette:
415 return QPlatformTheme::ItemViewPalette;
416
417 default:
418 return QPlatformTheme::SystemPalette;
419 }
420 }
421
palette(Palette type) const422 const QPalette *QAndroidPlatformTheme::palette(Palette type) const
423 {
424 if (m_androidStyleData) {
425 auto it = m_androidStyleData->m_palettes.find(paletteType(type));
426 if (it != m_androidStyleData->m_palettes.end())
427 return &(it.value());
428 }
429 return &m_defaultPalette;
430 }
431
fontType(QPlatformTheme::Font type)432 static inline int fontType(QPlatformTheme::Font type)
433 {
434 switch (type) {
435 case QPlatformTheme::LabelFont:
436 return QPlatformTheme::SystemFont;
437
438 case QPlatformTheme::ToolButtonFont:
439 return QPlatformTheme::PushButtonFont;
440
441 default:
442 return type;
443 }
444 }
445
font(Font type) const446 const QFont *QAndroidPlatformTheme::font(Font type) const
447 {
448 if (m_androidStyleData) {
449 auto it = m_androidStyleData->m_fonts.find(fontType(type));
450 if (it != m_androidStyleData->m_fonts.end())
451 return &(it.value());
452 }
453
454 if (type == QPlatformTheme::SystemFont)
455 return &m_systemFont;
456 return 0;
457 }
458
themeHint(ThemeHint hint) const459 QVariant QAndroidPlatformTheme::themeHint(ThemeHint hint) const
460 {
461 switch (hint) {
462 case StyleNames:
463 if (qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_STYLE")
464 && m_androidStyleData) {
465 return QStringList(QLatin1String("android"));
466 }
467 return QStringList(QLatin1String("fusion"));
468 case DialogButtonBoxLayout:
469 return QVariant(QPlatformDialogHelper::AndroidLayout);
470 case MouseDoubleClickDistance:
471 {
472 int minimumDistance = qEnvironmentVariableIntValue("QT_ANDROID_MINIMUM_MOUSE_DOUBLE_CLICK_DISTANCE");
473 int ret = minimumDistance;
474
475 QAndroidPlatformIntegration *platformIntegration
476 = static_cast<QAndroidPlatformIntegration *>(QGuiApplicationPrivate::platformIntegration());
477 QAndroidPlatformScreen *platformScreen = platformIntegration->screen();
478 if (platformScreen != 0) {
479 QScreen *screen = platformScreen->screen();
480 qreal dotsPerInch = screen->physicalDotsPerInch();
481
482 // Allow 15% of an inch between clicks when double clicking
483 int distance = qRound(dotsPerInch * 0.15);
484 if (distance > minimumDistance)
485 ret = distance;
486 }
487
488 if (ret > 0)
489 return ret;
490
491 Q_FALLTHROUGH();
492 }
493 default:
494 return QPlatformTheme::themeHint(hint);
495 }
496 }
497
standardButtonText(int button) const498 QString QAndroidPlatformTheme::standardButtonText(int button) const
499 {
500 switch (button) {
501 case QPlatformDialogHelper::Yes:
502 return QCoreApplication::translate("QAndroidPlatformTheme", "Yes");
503 case QPlatformDialogHelper::YesToAll:
504 return QCoreApplication::translate("QAndroidPlatformTheme", "Yes to All");
505 case QPlatformDialogHelper::No:
506 return QCoreApplication::translate("QAndroidPlatformTheme", "No");
507 case QPlatformDialogHelper::NoToAll:
508 return QCoreApplication::translate("QAndroidPlatformTheme", "No to All");
509 }
510 return QPlatformTheme::standardButtonText(button);
511 }
512
usePlatformNativeDialog(QPlatformTheme::DialogType type) const513 bool QAndroidPlatformTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const
514 {
515 if (type == MessageDialog)
516 return qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_DIALOGS") == 1;
517 if (type == FileDialog)
518 return true;
519 return false;
520 }
521
createPlatformDialogHelper(QPlatformTheme::DialogType type) const522 QPlatformDialogHelper *QAndroidPlatformTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const
523 {
524 switch (type) {
525 case MessageDialog:
526 return new QtAndroidDialogHelpers::QAndroidPlatformMessageDialogHelper;
527 case FileDialog:
528 return new QtAndroidFileDialogHelper::QAndroidPlatformFileDialogHelper;
529 default:
530 return 0;
531 }
532 }
533
534 QT_END_NAMESPACE
535