Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qandroidplatformtheme.cpp
Go to the documentation of this file.
1// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "androidjnimain.h"
5#include "androidjnimenu.h"
12
13#include <QCoreApplication>
14#include <QDebug>
15#include <QFileInfo>
16#include <QJsonDocument>
17#include <QVariant>
18
19#include <private/qguiapplication_p.h>
20#include <private/qhighdpiscaling_p.h>
22
24
25using namespace Qt::StringLiterals;
26
27namespace {
28 const int textStyle_bold = 1;
29 const int textStyle_italic = 2;
30
31 const int typeface_sans = 1;
32 const int typeface_serif = 2;
33 const int typeface_monospace = 3;
34}
35
36static int fontType(const QString &androidControl)
37{
38 if (androidControl == "defaultStyle"_L1)
40 if (androidControl == "textViewStyle"_L1)
42 else if (androidControl == "buttonStyle"_L1)
44 else if (androidControl == "checkboxStyle"_L1)
46 else if (androidControl == "radioButtonStyle"_L1)
48 else if (androidControl == "simple_list_item_single_choice"_L1)
50 else if (androidControl == "simple_spinner_dropdown_item"_L1)
52 else if (androidControl == "spinnerStyle"_L1)
54 else if (androidControl == "simple_list_item"_L1)
56 return -1;
57}
58
59static int paletteType(const QString &androidControl)
60{
61 if (androidControl == "defaultStyle"_L1)
63 if (androidControl == "textViewStyle"_L1)
65 else if (androidControl == "buttonStyle"_L1)
67 else if (androidControl == "checkboxStyle"_L1)
69 else if (androidControl == "radioButtonStyle"_L1)
71 else if (androidControl == "simple_list_item_single_choice"_L1)
73 else if (androidControl == "editTextStyle"_L1)
75 else if (androidControl == "spinnerStyle"_L1)
77 return -1;
78}
79
80static void setPaletteColor(const QVariantMap &object,
83{
84 // QPalette::Active -> ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET
86 role,
87 QRgb(object.value("ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET"_L1).toInt()));
88
89 // QPalette::Inactive -> ENABLED_STATE_SET
91 role,
92 QRgb(object.value("ENABLED_STATE_SET"_L1).toInt()));
93
94 // QPalette::Disabled -> EMPTY_STATE_SET
96 role,
97 QRgb(object.value("EMPTY_STATE_SET"_L1).toInt()));
98
99 palette.setColor(QPalette::Current, role, palette.color(QPalette::Active, role));
100
101 if (role == QPalette::WindowText) {
102 // QPalette::BrightText -> PRESSED
103 // QPalette::Active -> PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET
104 palette.setColor(QPalette::Active,
106 QRgb(object.value("PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET"_L1).toInt()));
107
108 // QPalette::Inactive -> PRESSED_ENABLED_STATE_SET
111 QRgb(object.value("PRESSED_ENABLED_STATE_SET"_L1).toInt()));
112
113 // QPalette::Disabled -> PRESSED_STATE_SET
116 QRgb(object.value("PRESSED_STATE_SET"_L1).toInt()));
117
119
120 // QPalette::HighlightedText -> SELECTED
121 // QPalette::Active -> ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET
122 palette.setColor(QPalette::Active,
124 QRgb(object.value("ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET"_L1).toInt()));
125
126 // QPalette::Inactive -> ENABLED_SELECTED_STATE_SET
129 QRgb(object.value("ENABLED_SELECTED_STATE_SET"_L1).toInt()));
130
131 // QPalette::Disabled -> SELECTED_STATE_SET
134 QRgb(object.value("SELECTED_STATE_SET"_L1).toInt()));
135
136 palette.setColor(QPalette::Current,
139
140 // Same colors for Text
145
146 // And for ButtonText
151 }
152}
153
155{
156 QString stylePath(QLatin1StringView(qgetenv("ANDROID_STYLE_PATH")));
157 const QLatin1Char slashChar('/');
158 if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
159 stylePath += slashChar;
160
162 stylePath += "darkUiMode/"_L1;
163
164 Q_ASSERT(!stylePath.isEmpty());
165
166 QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME"));
167 if (!androidTheme.isEmpty() && !androidTheme.endsWith(slashChar))
168 androidTheme += slashChar;
169
170 if (!androidTheme.isEmpty() && QFileInfo::exists(stylePath + androidTheme + "style.json"_L1))
171 stylePath += androidTheme;
172
173 QFile f(stylePath + "style.json"_L1);
174 if (!f.open(QIODevice::ReadOnly))
175 return QJsonObject();
176
178 QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error);
179 if (Q_UNLIKELY(document.isNull())) {
180 qCritical() << error.errorString();
181 return QJsonObject();
182 }
183
184 if (Q_UNLIKELY(!document.isObject())) {
185 qCritical("Style.json does not contain a valid style.");
186 return QJsonObject();
187 }
188 return document.object();
189}
190
191static void loadAndroidStyle(QPalette *defaultPalette, std::shared_ptr<AndroidStyle> &style)
192{
193 double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0;
194 if (style) {
195 style->m_standardPalette = QPalette();
196 style->m_palettes.clear();
197 style->m_fonts.clear();
198 style->m_QWidgetsFonts.clear();
199 } else {
200 style = std::make_shared<AndroidStyle>();
201 }
202
203 style->m_styleData = AndroidStyle::loadStyleData();
204
205 if (style->m_styleData.isEmpty())
206 return;
207
208 {
209 QFont font("Droid Sans Mono"_L1, 14.0 * 100 / 72);
210 style->m_fonts.insert(QPlatformTheme::FixedFont, font);
211 }
212
213 for (QJsonObject::const_iterator objectIterator = style->m_styleData.constBegin();
214 objectIterator != style->m_styleData.constEnd();
215 ++objectIterator) {
216 QString key = objectIterator.key();
217 QJsonValue value = objectIterator.value();
218 if (!value.isObject()) {
219 qWarning("Style.json structure is unrecognized.");
220 continue;
221 }
222 QJsonObject item = value.toObject();
223 QJsonObject::const_iterator attributeIterator = item.find("qtClass"_L1);
224 QByteArray qtClassName;
225 if (attributeIterator != item.constEnd()) {
226 // The item has palette and font information for a specific Qt Class (e.g. QWidget, QPushButton, etc.)
227 qtClassName = attributeIterator.value().toString().toLatin1();
228 }
229 const int ft = fontType(key);
230 if (ft > -1 || !qtClassName.isEmpty()) {
231 // Extract font information
232 QFont font;
233
234 // Font size (in pixels)
235 attributeIterator = item.find("TextAppearance_textSize"_L1);
236 if (attributeIterator != item.constEnd())
237 font.setPixelSize(int(attributeIterator.value().toDouble() / pixelDensity));
238
239 // Font style
240 attributeIterator = item.find("TextAppearance_textStyle"_L1);
241 if (attributeIterator != item.constEnd()) {
242 const int style = int(attributeIterator.value().toDouble());
243 font.setBold(style & textStyle_bold);
244 font.setItalic(style & textStyle_italic);
245 }
246
247 // Font typeface
248 attributeIterator = item.find("TextAppearance_typeface"_L1);
249 if (attributeIterator != item.constEnd()) {
251 switch (int(attributeIterator.value().toDouble())) {
252 case typeface_sans:
253 styleHint = QFont::SansSerif;
254 break;
255 case typeface_serif:
256 styleHint = QFont::Serif;
257 break;
258 case typeface_monospace:
259 styleHint = QFont::Monospace;
260 break;
261 }
263 }
264 if (!qtClassName.isEmpty())
265 style->m_QWidgetsFonts.insert(qtClassName, font);
266
267 if (ft > -1) {
268 style->m_fonts.insert(ft, font);
271 }
272 // Extract font information
273 }
274
275 const int pt = paletteType(key);
276 if (pt > -1 || !qtClassName.isEmpty()) {
277 // Extract palette information
278 QPalette palette = *defaultPalette;
279
280 attributeIterator = item.find("defaultTextColorPrimary"_L1);
281 if (attributeIterator != item.constEnd())
282 palette.setColor(QPalette::WindowText, QRgb(int(attributeIterator.value().toDouble())));
283
284 attributeIterator = item.find("defaultBackgroundColor"_L1);
285 if (attributeIterator != item.constEnd())
286 palette.setColor(QPalette::Window, QRgb(int(attributeIterator.value().toDouble())));
287
288 attributeIterator = item.find("TextAppearance_textColor"_L1);
289 if (attributeIterator != item.constEnd())
291
292 attributeIterator = item.find("TextAppearance_textColorLink"_L1);
293 if (attributeIterator != item.constEnd())
295
296 attributeIterator = item.find("TextAppearance_textColorHighlight"_L1);
297 if (attributeIterator != item.constEnd())
298 palette.setColor(QPalette::Highlight, QRgb(int(attributeIterator.value().toDouble())));
299
301 *defaultPalette = style->m_standardPalette = palette;
302
303 if (pt > -1)
304 style->m_palettes.insert(pt, palette);
305 // Extract palette information
306 }
307 }
308}
309
310QAndroidPlatformTheme *QAndroidPlatformTheme::m_instance = nullptr;
311
313 QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
314{
315 if (androidPlatformNativeInterface && !m_instance) {
316 m_instance = new QAndroidPlatformTheme(androidPlatformNativeInterface);
317 }
318 return m_instance;
319}
320
321QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
322{
323 updateStyle();
324
325 androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
326
327 // default in case the style has not set a font
328 m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
329}
330
332{
333 m_instance = nullptr;
334}
335
337{
338 updateStyle();
340}
341
343{
344 QColor windowText = Qt::black;
345 QColor background(229, 229, 229);
346 QColor light = background.lighter(150);
347 QColor mid(background.darker(130));
348 QColor midLight = mid.lighter(110);
349 QColor base(249, 249, 249);
350 QColor disabledBase(background);
351 QColor dark = background.darker(150);
352 QColor darkDisabled = dark.darker(110);
354 QColor highlightedText = Qt::black;
355 QColor disabledText = QColor(190, 190, 190);
356 QColor button(241, 241, 241);
357 QColor shadow(201, 201, 201);
358 QColor highlight(148, 210, 231);
359 QColor disabledShadow = shadow.lighter(150);
360
362 // Colors were prepared based on Theme.DeviceDefault.DayNight
363 windowText = QColor(250, 250, 250);
364 background = QColor(48, 48, 48);
365 light = background.darker(150);
366 mid = background.lighter(130);
367 midLight = mid.darker(110);
368 base = background;
369 disabledBase = background;
370 dark = background.darker(150);
371 darkDisabled = dark.darker(110);
372 text = QColor(250, 250, 250);
373 highlightedText = QColor(250, 250, 250);
374 disabledText = QColor(96, 96, 96);
375 button = QColor(48, 48, 48);
376 shadow = QColor(32, 32, 32);
377 highlight = QColor(102, 178, 204);
378 disabledShadow = shadow.darker(150);
379 }
380
381 m_defaultPalette = QPalette(windowText,background,light,dark,mid,text,base);
382 m_defaultPalette.setBrush(QPalette::Midlight, midLight);
383 m_defaultPalette.setBrush(QPalette::Button, button);
384 m_defaultPalette.setBrush(QPalette::Shadow, shadow);
385 m_defaultPalette.setBrush(QPalette::HighlightedText, highlightedText);
386
387 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Text, disabledText);
388 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::WindowText, disabledText);
389 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledText);
390 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Base, disabledBase);
391 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled);
392 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow);
393
394 m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
395 m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
396 m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150));
397
398 loadAndroidStyle(&m_defaultPalette, m_androidStyleData);
399}
400
402{
403 return new QAndroidPlatformMenuBar;
404}
405
407{
408 return new QAndroidPlatformMenu;
409}
410
412{
413 return new QAndroidPlatformMenuItem;
414}
415
417{
419}
420
422{
424}
425
427{
428 switch (type) {
432
435
438
441
445
448
449 default:
451 }
452}
453
455{
456 if (m_androidStyleData) {
457 auto it = m_androidStyleData->m_palettes.find(paletteType(type));
458 if (it != m_androidStyleData->m_palettes.end())
459 return &(it.value());
460 }
461 return &m_defaultPalette;
462}
463
465{
466 switch (type) {
469
472
473 default:
474 return type;
475 }
476}
477
479{
480 if (m_androidStyleData) {
481 auto it = m_androidStyleData->m_fonts.find(fontType(type));
482 if (it != m_androidStyleData->m_fonts.end())
483 return &(it.value());
484 }
485
487 return &m_systemFont;
488 return 0;
489}
490
492{
493 switch (hint) {
494 case StyleNames:
495 if (qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_STYLE")
496 && m_androidStyleData) {
497 return QStringList("android"_L1);
498 }
499 return QStringList("Fusion"_L1);
503 {
504 int minimumDistance = qEnvironmentVariableIntValue("QT_ANDROID_MINIMUM_MOUSE_DOUBLE_CLICK_DISTANCE");
505 int ret = minimumDistance;
506
507 QAndroidPlatformIntegration *platformIntegration
509 QAndroidPlatformScreen *platformScreen = platformIntegration->screen();
510 if (platformScreen != 0) {
511 QScreen *screen = platformScreen->screen();
512 qreal dotsPerInch = screen->physicalDotsPerInch();
513
514 // Allow 15% of an inch between clicks when double clicking
515 int distance = qRound(dotsPerInch * 0.15);
516 if (distance > minimumDistance)
517 ret = distance;
518 }
519
520 if (ret > 0)
521 return ret;
522
524 }
525 default:
527 }
528}
529
531{
532 switch (button) {
534 return QCoreApplication::translate("QAndroidPlatformTheme", "Yes");
536 return QCoreApplication::translate("QAndroidPlatformTheme", "Yes to All");
538 return QCoreApplication::translate("QAndroidPlatformTheme", "No");
540 return QCoreApplication::translate("QAndroidPlatformTheme", "No to All");
541 }
543}
544
546{
547 if (type == MessageDialog)
548 return qEnvironmentVariableIntValue("QT_USE_ANDROID_NATIVE_DIALOGS") == 1;
549 if (type == FileDialog)
550 return true;
551 return false;
552}
553
555{
556 switch (type) {
557 case MessageDialog:
559 case FileDialog:
561 default:
562 return 0;
563 }
564}
565
std::shared_ptr< AndroidStyle > m_androidStyle
Qt::ColorScheme colorScheme() const override
const QPalette * palette(Palette type=SystemPalette) const override
QVariant themeHint(ThemeHint hint) const override
static QAndroidPlatformTheme * instance(QAndroidPlatformNativeInterface *androidPlatformNativeInterface=nullptr)
QPlatformMenu * createPlatformMenu() const override
QString standardButtonText(int button) const override
Returns the text of a standard button.
QPlatformDialogHelper * createPlatformDialogHelper(DialogType type) const override
const QFont * font(Font type=SystemFont) const override
bool usePlatformNativeDialog(DialogType type) const override
QPlatformMenuItem * createPlatformMenuItem() const override
QPlatformMenuBar * createPlatformMenuBar() const override
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
QColor darker(int f=200) const noexcept
Definition qcolor.cpp:2857
QColor lighter(int f=150) const noexcept
Definition qcolor.cpp:2812
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
\threadsafe
bool exists() const
Returns true if the file exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
\reentrant
Definition qfont.h:20
StyleHint
Style hints are used by the \l{QFont}{font matching} algorithm to find an appropriate default family ...
Definition qfont.h:23
@ AnyStyle
Definition qfont.h:29
@ Monospace
Definition qfont.h:31
@ Serif
Definition qfont.h:25
@ SansSerif
Definition qfont.h:24
void setPixelSize(int)
Sets the font size to pixelSize pixels, with a maxiumum size of an unsigned 16-bit integer.
Definition qfont.cpp:1034
void setBold(bool)
If enable is true sets the font's weight to \l{Weight}{QFont::Bold}; otherwise sets the weight to \l{...
Definition qfont.h:312
void setItalic(bool b)
Sets the style() of the font to QFont::StyleItalic if enable is true; otherwise the style is set to Q...
Definition qfont.h:320
void setStyleHint(StyleHint, StyleStrategy=PreferDefault)
Sets the style hint and strategy to hint and strategy, respectively.
Definition qfont.cpp:1482
@ PreferMatch
Definition qfont.h:42
static QPlatformIntegration * platformIntegration()
static void setFont(const QFont &)
Changes the default application font to font.
static bool isActive()
\inmodule QtCore\reentrant
static QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error=nullptr)
Parses json as a UTF-8 encoded JSON document, and creates a QJsonDocument from it.
QJsonValueConstRef value() const
Returns the current item's value.
\inmodule QtCore\reentrant
Definition qjsonobject.h:20
QVariantMap toVariantMap() const
Converts this object to a QVariantMap.
QString toString(const QString &defaultValue={}) const
Definition qjsonvalue.h:142
double toDouble(double defaultValue=0) const
Definition qjsonvalue.h:140
Q_CORE_EXPORT QJsonObject toObject() const
\inmodule QtCore\reentrant
Definition qjsonvalue.h:24
The QPalette class contains color groups for each widget state.
Definition qpalette.h:19
void setBrush(ColorRole cr, const QBrush &brush)
Sets the brush for the given color role to the specified brush for all groups in the palette.
Definition qpalette.h:150
@ Current
Definition qpalette.h:48
@ Inactive
Definition qpalette.h:48
@ Disabled
Definition qpalette.h:48
@ HighlightedText
Definition qpalette.h:52
@ BrightText
Definition qpalette.h:51
@ ButtonText
Definition qpalette.h:51
@ WindowText
Definition qpalette.h:50
@ Highlight
Definition qpalette.h:52
@ Midlight
Definition qpalette.h:50
The QPlatformDialogHelper class allows for platform-specific customization of dialogs.
QScreen * screen() const
virtual QVariant themeHint(ThemeHint hint) const
virtual QString standardButtonText(int button) const
Returns the text of a standard button.
ThemeHint
This enum describes the available theme hints.
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal physicalDotsPerInch
the number of physical dots or pixels per inch
Definition qscreen.h:55
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toLatin1() const &
Definition qstring.h:559
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5350
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore
Definition qvariant.h:64
static void handleThemeChange(QWindow *window=nullptr)
QString text
QPushButton * button
[2]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
double pixelDensity()
ColorScheme
Definition qnamespace.h:49
@ black
Definition qnamespace.h:29
static int fontType(const QString &androidControl)
static void setPaletteColor(const QVariantMap &object, QPalette &palette, QPalette::ColorRole role)
static int paletteType(const QString &androidControl)
static void loadAndroidStyle(QPalette *defaultPalette, std::shared_ptr< AndroidStyle > &style)
#define Q_FALLTHROUGH()
#define Q_UNLIKELY(x)
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
#define qCritical
Definition qlogging.h:163
#define qWarning
Definition qlogging.h:162
return ret
GLuint64 key
GLfloat GLfloat f
GLsizei GLsizei GLfloat distance
GLenum type
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
QScreen * screen
[1]
Definition main.cpp:29
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
double qreal
Definition qtypes.h:92
static int toInt(const QChar &qc, int R)
QGraphicsItem * item
static QJsonObject loadStyleData()
\inmodule QtCore\reentrant
\inmodule QtCore \reentrant
Definition qchar.h:17