Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidstyle.cpp
Go to the documentation of this file.
1// Copyright (C) 2013 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// Qt-Security score:significant reason:default
4
6
7#include <QFile>
8#include <QFont>
9#include <QApplication>
10#include <qdrawutil.h>
11#include <QPixmapCache>
12#include <QFileInfo>
13#include <QStyleOption>
14#include <QPainter>
15#include <QJsonDocument>
16#include <QJsonObject>
17#include <QDebug>
18
19#include <QGuiApplication>
20#include <qpa/qplatformnativeinterface.h>
21#include <qpa/qplatformtheme.h>
22
23QT_BEGIN_NAMESPACE
24
25namespace {
26 const quint32 NO_COLOR = 1;
28}
29
30QAndroidStyle::QAndroidStyle()
31 : QFusionStyle()
32{
33 QPixmapCache::clear();
34 checkBoxControl = NULL;
35 QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
36 QPalette *standardPalette = reinterpret_cast<QPalette *>(nativeInterface->nativeResourceForIntegration("AndroidStandardPalette"));
37 if (standardPalette)
38 m_standardPalette = *standardPalette;
39
40 QHash<QByteArray, QFont> *qwidgetsFonts = reinterpret_cast<QHash<QByteArray, QFont> *>(nativeInterface->nativeResourceForIntegration("AndroidQWidgetFonts"));
41 if (qwidgetsFonts) {
42 for (auto it = qwidgetsFonts->constBegin(); it != qwidgetsFonts->constEnd(); ++it)
43 QApplication::setFont(it.value(), it.key());
44 qwidgetsFonts->clear(); // free the memory
45 }
46
47 QJsonObject *object = reinterpret_cast<QJsonObject *>(nativeInterface->nativeResourceForIntegration("AndroidStyleData"));
48 if (!object)
49 return;
50
51 for (QJsonObject::const_iterator objectIterator = object->constBegin();
52 objectIterator != object->constEnd();
53 ++objectIterator) {
54 QString key = objectIterator.key();
55 QJsonValue value = objectIterator.value();
56 if (Q_UNLIKELY(!value.isObject())) {
57 qWarning("Style.json structure is unrecognized.");
58 continue;
59 }
60
61 QJsonObject item = value.toObject();
62 QAndroidStyle::ItemType itemType = qtControl(key);
63 if (QC_UnknownType == itemType)
64 continue;
65
66 switch (itemType) {
67 case QC_Checkbox:
68 checkBoxControl = new AndroidCompoundButtonControl(item.toVariantMap(), itemType);
69 m_androidControlsHash[int(itemType)] = checkBoxControl;
70 break;
71 case QC_RadioButton:
72 m_androidControlsHash[int(itemType)] = new AndroidCompoundButtonControl(item.toVariantMap(),
73 itemType);
74 break;
75
76 case QC_ProgressBar:
77 m_androidControlsHash[int(itemType)] = new AndroidProgressBarControl(item.toVariantMap(),
78 itemType);
79 break;
80
81 case QC_Slider:
82 m_androidControlsHash[int(itemType)] = new AndroidSeekBarControl(item.toVariantMap(),
83 itemType);
84 break;
85
86 case QC_Combobox:
87 m_androidControlsHash[int(itemType)] = new AndroidSpinnerControl(item.toVariantMap(),
88 itemType);
89 break;
90
91 default:
92 m_androidControlsHash[int(itemType)] = new AndroidControl(item.toVariantMap(),
93 itemType);
94 break;
95 }
96 }
97 *object = QJsonObject(); // free memory
98}
99
100QAndroidStyle::~QAndroidStyle()
101{
102 qDeleteAll(m_androidControlsHash);
103}
104
105QAndroidStyle::ItemType QAndroidStyle::qtControl(const QString &android)
106{
107 if (android == QLatin1String("buttonStyle"))
108 return QC_Button;
109 if (android == QLatin1String("editTextStyle"))
110 return QC_EditText;
111 if (android == QLatin1String("radioButtonStyle"))
112 return QC_RadioButton;
113 if (android == QLatin1String("checkboxStyle"))
114 return QC_Checkbox;
115 if (android == QLatin1String("textViewStyle"))
116 return QC_View;
117 if (android == QLatin1String("buttonStyleToggle"))
118 return QC_Switch;
119 if (android == QLatin1String("spinnerStyle"))
120 return QC_Combobox;
121 if (android == QLatin1String("progressBarStyleHorizontal"))
122 return QC_ProgressBar;
123 if (android == QLatin1String("seekBarStyle"))
124 return QC_Slider;
125
126 return QC_UnknownType;
127}
128
129QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ComplexControl control)
130{
131 switch (control) {
132 case CC_ComboBox:
133 return QC_Combobox;
134 case CC_Slider:
135 return QC_Slider;
136 default:
137 return QC_UnknownType;
138 }
139}
140
141QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ContentsType contentsType)
142{
143 switch (contentsType) {
144 case CT_PushButton:
145 return QC_Button;
146 case CT_CheckBox:
147 return QC_Checkbox;
148 case CT_RadioButton:
149 return QC_RadioButton;
150 case CT_ComboBox:
151 return QC_Combobox;
152 case CT_ProgressBar:
153 return QC_ProgressBar;
154 case CT_Slider:
155 return QC_Slider;
156 case CT_ScrollBar:
157 return QC_Slider;
158 case CT_TabWidget:
159 return QC_Tab;
160 case CT_TabBarTab:
161 return QC_TabButton;
162 case CT_LineEdit:
163 return QC_EditText;
164 case CT_GroupBox:
165 return QC_GroupBox;
166 default:
167 return QC_UnknownType;
168 }
169}
170
171QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::ControlElement controlElement)
172{
173 switch (controlElement) {
174 case CE_PushButton:
175 case CE_PushButtonBevel:
176 case CE_PushButtonLabel:
177 return QC_Button;
178
179 case CE_CheckBox:
180 case CE_CheckBoxLabel:
181 return QC_Checkbox;
182
183 case CE_RadioButton:
184 case CE_RadioButtonLabel:
185 return QC_RadioButton;
186
187 case CE_TabBarTab:
188 case CE_TabBarTabShape:
189 case CE_TabBarTabLabel:
190 return QC_Tab;
191
192 case CE_ProgressBar:
193 case CE_ProgressBarGroove:
194 case CE_ProgressBarContents:
195 case CE_ProgressBarLabel:
196 return QC_ProgressBar;
197
198 case CE_ComboBoxLabel:
199 return QC_Combobox;
200
201 case CE_ShapedFrame:
202 return QC_View;
203
204 default:
205 return QC_UnknownType;
206 }
207}
208
209QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::PrimitiveElement primitiveElement)
210{
211 switch (primitiveElement) {
212 case QStyle::PE_PanelLineEdit:
213 case QStyle::PE_FrameLineEdit:
214 return QC_EditText;
215
216 case QStyle::PE_IndicatorItemViewItemCheck:
217 case QStyle::PE_IndicatorCheckBox:
218 return QC_Checkbox;
219
220 case QStyle::PE_FrameWindow:
221 case QStyle::PE_Widget:
222 case QStyle::PE_Frame:
223 case QStyle::PE_FrameFocusRect:
224 return QC_View;
225 default:
226 return QC_UnknownType;
227 }
228}
229
230QAndroidStyle::ItemType QAndroidStyle::qtControl(QStyle::SubElement subElement)
231{
232 switch (subElement) {
233 case QStyle::SE_LineEditContents:
234 return QC_EditText;
235
236 case QStyle::SE_PushButtonContents:
237 case QStyle::SE_PushButtonFocusRect:
238 return QC_Button;
239
240 case SE_RadioButtonContents:
241 return QC_RadioButton;
242
243 case SE_CheckBoxContents:
244 return QC_Checkbox;
245
246 default:
247 return QC_UnknownType;
248 }
249}
250
251void QAndroidStyle::drawPrimitive(PrimitiveElement pe,
252 const QStyleOption *opt,
253 QPainter *p,
254 const QWidget *w) const
255{
256 const ItemType itemType = qtControl(pe);
257 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
258 ? m_androidControlsHash.find(itemType)
259 : m_androidControlsHash.end();
260 if (it != m_androidControlsHash.end()) {
261 if (itemType != QC_EditText) {
262 it.value()->drawControl(opt, p, w);
263 } else {
264 QStyleOption copy(*opt);
265 copy.state &= ~QStyle::State_Sunken;
266 it.value()->drawControl(&copy, p, w);
267 }
268 } else if (pe == PE_FrameGroupBox) {
269 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
270 if (frame->features & QStyleOptionFrame::Flat) {
271 QRect fr = frame->rect;
272 QPoint p1(fr.x(), fr.y() + 1);
273 QPoint p2(fr.x() + fr.width(), p1.y());
274 qDrawShadeLine(p, p1, p2, frame->palette, true,
275 frame->lineWidth, frame->midLineWidth);
276 } else {
277 qDrawShadeRect(p, frame->rect.x(), frame->rect.y(), frame->rect.width(),
278 frame->rect.height(), frame->palette, true,
279 frame->lineWidth, frame->midLineWidth);
280 }
281 }
282 } else {
283 QFusionStyle::drawPrimitive(pe, opt, p, w);
284 }
285}
286
287
288void QAndroidStyle::drawControl(QStyle::ControlElement element,
289 const QStyleOption *opt,
290 QPainter *p,
291 const QWidget *w) const
292{
293 const ItemType itemType = qtControl(element);
294 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
295 ? m_androidControlsHash.find(itemType)
296 : m_androidControlsHash.end();
297 if (it != m_androidControlsHash.end()) {
298 AndroidControl *androidControl = it.value();
299
300 if (element != QStyle::CE_CheckBoxLabel
301 && element != QStyle::CE_PushButtonLabel
302 && element != QStyle::CE_RadioButtonLabel
303 && element != QStyle::CE_TabBarTabLabel
304 && element != QStyle::CE_ProgressBarLabel) {
305 androidControl->drawControl(opt, p, w);
306 }
307
308 if (element != QStyle::CE_PushButtonBevel
309 && element != QStyle::CE_TabBarTabShape
310 && element != QStyle::CE_ProgressBarGroove) {
311 switch (itemType) {
312 case QC_Button:
313 if (const QStyleOptionButton *buttonOption =
314 qstyleoption_cast<const QStyleOptionButton *>(opt)) {
315 QMargins padding = androidControl->padding();
316 QStyleOptionButton copy(*buttonOption);
317 copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom());
318 QFusionStyle::drawControl(CE_PushButtonLabel, &copy, p, w);
319 }
320 break;
321 case QC_Checkbox:
322 case QC_RadioButton:
323 if (const QStyleOptionButton *btn =
324 qstyleoption_cast<const QStyleOptionButton *>(opt)) {
325 const bool isRadio = (element == CE_RadioButton);
326 QStyleOptionButton subopt(*btn);
327 subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents
328 : SE_CheckBoxContents, btn, w);
329 QFusionStyle::drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, w);
330 }
331 break;
332 case QC_Combobox:
333 if (const QStyleOptionComboBox *comboboxOption =
334 qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
335 QMargins padding = androidControl->padding();
336 QStyleOptionComboBox copy (*comboboxOption);
337 copy.rect.adjust(padding.left(), padding.top(), -padding.right(), -padding.bottom());
338 QFusionStyle::drawControl(CE_ComboBoxLabel, comboboxOption, p, w);
339 }
340 break;
341 default:
342 QFusionStyle::drawControl(element, opt, p, w);
343 break;
344 }
345 }
346 } else {
347 QFusionStyle::drawControl(element, opt, p, w);
348 }
349}
350
351QRect QAndroidStyle::subElementRect(SubElement subElement,
352 const QStyleOption *option,
353 const QWidget *widget) const
354{
355 const ItemType itemType = qtControl(subElement);
356 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
357 ? m_androidControlsHash.find(itemType)
358 : m_androidControlsHash.end();
359 if (it != m_androidControlsHash.end())
360 return it.value()->subElementRect(subElement, option, widget);
361 return QFusionStyle::subElementRect(subElement, option, widget);
362}
363
364void QAndroidStyle::drawComplexControl(ComplexControl cc,
365 const QStyleOptionComplex *opt,
366 QPainter *p,
367 const QWidget *widget) const
368{
369 const ItemType itemType = qtControl(cc);
370 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
371 ? m_androidControlsHash.find(itemType)
372 : m_androidControlsHash.end();
373 if (it != m_androidControlsHash.end()) {
374 it.value()->drawControl(opt, p, widget);
375 return;
376 }
377 if (cc == CC_GroupBox) {
378 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
379 // Draw frame
380 QRect textRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, widget);
381 QRect checkBoxRect;
382 if (groupBox->subControls & SC_GroupBoxCheckBox)
383 checkBoxRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxCheckBox, widget);
384 if (groupBox->subControls & QStyle::SC_GroupBoxFrame) {
385 QStyleOptionFrame frame;
386 frame.QStyleOption::operator=(*groupBox);
387 frame.features = groupBox->features;
388 frame.lineWidth = groupBox->lineWidth;
389 frame.midLineWidth = groupBox->midLineWidth;
390 frame.rect = subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, widget);
391 p->save();
392 QRegion region(groupBox->rect);
393 if (!groupBox->text.isEmpty()) {
394 bool ltr = groupBox->direction == Qt::LeftToRight;
395 QRect finalRect;
396 if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) {
397 finalRect = checkBoxRect.united(textRect);
398 finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0);
399 } else {
400 finalRect = textRect;
401 }
402 region -= finalRect;
403 }
404 p->setClipRegion(region);
405 drawPrimitive(PE_FrameGroupBox, &frame, p, widget);
406 p->restore();
407 }
408
409 // Draw title
410 if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) {
411 QColor textColor = groupBox->textColor;
412 if (textColor.isValid())
413 p->setPen(textColor);
414 int alignment = int(groupBox->textAlignment);
415 if (!styleHint(QStyle::SH_UnderlineShortcut, opt, widget))
416 alignment |= Qt::TextHideMnemonic;
417
418 drawItemText(p, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment,
419 groupBox->palette, groupBox->state & State_Enabled, groupBox->text,
420 textColor.isValid() ? QPalette::NoRole : QPalette::WindowText);
421
422 if (groupBox->state & State_HasFocus) {
423 QStyleOptionFocusRect fropt;
424 fropt.QStyleOption::operator=(*groupBox);
425 fropt.rect = textRect;
426 drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
427 }
428 }
429
430 // Draw checkbox
431 if (groupBox->subControls & SC_GroupBoxCheckBox) {
432 QStyleOptionButton box;
433 box.QStyleOption::operator=(*groupBox);
434 box.rect = checkBoxRect;
435 checkBoxControl->drawControl(&box, p, widget);
436 }
437 }
438 return;
439 }
440 QFusionStyle::drawComplexControl(cc, opt, p, widget);
441}
442
443QStyle::SubControl QAndroidStyle::hitTestComplexControl(ComplexControl cc,
444 const QStyleOptionComplex *opt,
445 const QPoint &pt,
446 const QWidget *widget) const
447{
448 const ItemType itemType = qtControl(cc);
449 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
450 ? m_androidControlsHash.find(itemType)
451 : m_androidControlsHash.end();
452 if (it != m_androidControlsHash.end()) {
453 switch (cc) {
454 case CC_Slider:
455 if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {
456 QRect r = it.value()->subControlRect(slider, SC_SliderHandle, widget);
457 if (r.isValid() && r.contains(pt)) {
458 return SC_SliderHandle;
459 } else {
460 r = it.value()->subControlRect(slider, SC_SliderGroove, widget);
461 if (r.isValid() && r.contains(pt))
462 return SC_SliderGroove;
463 }
464 }
465 break;
466 default:
467 break;
468 }
469 }
470 return QFusionStyle::hitTestComplexControl(cc, opt, pt, widget);
471}
472
473QRect QAndroidStyle::subControlRect(ComplexControl cc,
474 const QStyleOptionComplex *opt,
475 SubControl sc,
476 const QWidget *widget) const
477{
478 const ItemType itemType = qtControl(cc);
479 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
480 ? m_androidControlsHash.find(itemType)
481 : m_androidControlsHash.end();
482 if (it != m_androidControlsHash.end())
483 return it.value()->subControlRect(opt, sc, widget);
484 QRect rect = opt->rect;
485 switch (cc) {
486 case CC_GroupBox: {
487 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
488 QSize textSize = opt->fontMetrics.boundingRect(groupBox->text).size() + QSize(2, 2);
489 QSize checkBoxSize = checkBoxControl->size(opt);
490 int indicatorWidth = checkBoxSize.width();
491 int indicatorHeight = checkBoxSize.height();
492 QRect checkBoxRect;
493 if (opt->subControls & QStyle::SC_GroupBoxCheckBox) {
494 checkBoxRect.setWidth(indicatorWidth);
495 checkBoxRect.setHeight(indicatorHeight);
496 }
497 checkBoxRect.moveLeft(1);
498 QRect textRect = checkBoxRect;
499 textRect.setSize(textSize);
500 if (opt->subControls & QStyle::SC_GroupBoxCheckBox)
501 textRect.translate(indicatorWidth + 5, (indicatorHeight - textSize.height()) / 2);
502 if (sc == SC_GroupBoxFrame) {
503 rect = opt->rect.adjusted(0, 0, 0, 0);
504 rect.translate(0, textRect.height() / 2);
505 rect.setHeight(rect.height() - textRect.height() / 2);
506 } else if (sc == SC_GroupBoxContents) {
507 QRect frameRect = opt->rect.adjusted(0, 0, 0, -groupBox->lineWidth);
508 int margin = 3;
509 int leftMarginExtension = 0;
510 int topMargin = qMax(pixelMetric(PM_ExclusiveIndicatorHeight), opt->fontMetrics.height()) + groupBox->lineWidth;
511 frameRect.adjust(leftMarginExtension + margin, margin + topMargin, -margin, -margin - groupBox->lineWidth);
512 frameRect.translate(0, textRect.height() / 2);
513 rect = frameRect;
514 rect.setHeight(rect.height() - textRect.height() / 2);
515 } else if (sc == SC_GroupBoxCheckBox) {
516 rect = checkBoxRect;
517 } else if (sc == SC_GroupBoxLabel) {
518 rect = textRect;
519 }
520 return visualRect(opt->direction, opt->rect, rect);
521 }
522
523 return rect;
524 }
525
526 default:
527 break;
528 }
529
530
531 return QFusionStyle::subControlRect(cc, opt, sc, widget);
532}
533
534int QAndroidStyle::pixelMetric(PixelMetric metric, const QStyleOption *option,
535 const QWidget *widget) const
536{
537 switch (metric) {
538 case PM_ButtonMargin:
539 case PM_FocusFrameVMargin:
540 case PM_FocusFrameHMargin:
541 case PM_ComboBoxFrameWidth:
542 case PM_SpinBoxFrameWidth:
543 case PM_ScrollBarExtent:
544 return 0;
545 case PM_IndicatorWidth:
546 return checkBoxControl->size(option).width();
547 case PM_IndicatorHeight:
548 return checkBoxControl->size(option).height();
549 default:
550 return QFusionStyle::pixelMetric(metric, option, widget);
551 }
552
553}
554
555QSize QAndroidStyle::sizeFromContents(ContentsType ct,
556 const QStyleOption *opt,
557 const QSize &contentsSize,
558 const QWidget *w) const
559{
560 QSize sz = QFusionStyle::sizeFromContents(ct, opt, contentsSize, w);
561 if (ct == CT_HeaderSection) {
562 if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) {
563 bool nullIcon = hdr->icon.isNull();
564 int margin = pixelMetric(QStyle::PM_HeaderMargin, hdr, w);
565 int iconSize = nullIcon ? 0 : checkBoxControl->size(opt).width();
566 QSize txt;
567/*
568 * These next 4 lines are a bad hack to fix a bug in case a QStyleSheet is applied at QApplication level.
569 * In that case, even if the stylesheet does not refer to headers, the header font is changed to application
570 * font, which is wrong. Even worst, hdr->fontMetrics(...) does not report the proper size.
571 */
572 if (qApp->styleSheet().isEmpty())
573 txt = hdr->fontMetrics.size(0, hdr->text);
574 else
575 txt = QFontMetrics(QApplication::font()).size(0, hdr->text);
576
577 sz.setHeight(margin + qMax(iconSize, txt.height()) + margin);
578 sz.setWidth((nullIcon ? 0 : margin) + iconSize
579 + (hdr->text.isNull() ? 0 : margin) + txt.width() + margin);
580 if (hdr->sortIndicator != QStyleOptionHeader::None) {
581 int margin = pixelMetric(QStyle::PM_HeaderMargin, hdr, w);
582 if (hdr->orientation == Qt::Horizontal)
583 sz.rwidth() += sz.height() + margin;
584 else
585 sz.rheight() += sz.width() + margin;
586 }
587 return sz;
588 }
589 }
590 const ItemType itemType = qtControl(ct);
591 AndroidControlsHash::const_iterator it = itemType != QC_UnknownType
592 ? m_androidControlsHash.find(itemType)
593 : m_androidControlsHash.end();
594 if (it != m_androidControlsHash.end())
595 return it.value()->sizeFromContents(opt, sz, w);
596 if (ct == CT_GroupBox) {
597 if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) {
598 QSize textSize = opt->fontMetrics.boundingRect(groupBox->text).size() + QSize(2, 2);
599 QSize checkBoxSize = checkBoxControl->size(opt);
600 int indicatorWidth = checkBoxSize.width();
601 int indicatorHeight = checkBoxSize.height();
602 QRect checkBoxRect;
603 if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) {
604 checkBoxRect.setWidth(indicatorWidth);
605 checkBoxRect.setHeight(indicatorHeight);
606 }
607 checkBoxRect.moveLeft(1);
608 QRect textRect = checkBoxRect;
609 textRect.setSize(textSize);
610 if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox)
611 textRect.translate(indicatorWidth + 5, (indicatorHeight - textSize.height()) / 2);
612 QRect u = textRect.united(checkBoxRect);
613 sz = QSize(sz.width(), sz.height() + u.height());
614 }
615 }
616 return sz;
617}
618
619QPixmap QAndroidStyle::standardPixmap(StandardPixmap standardPixmap,
620 const QStyleOption *opt,
621 const QWidget *widget) const
622{
623 return QFusionStyle::standardPixmap(standardPixmap, opt, widget);
624}
625
626QPixmap QAndroidStyle::generatedIconPixmap(QIcon::Mode iconMode,
627 const QPixmap &pixmap,
628 const QStyleOption *opt) const
629{
630 return QFusionStyle::generatedIconPixmap(iconMode, pixmap, opt);
631}
632
633int QAndroidStyle::styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const
634{
635 switch (hint) {
636 case SH_Slider_AbsoluteSetButtons:
637 return Qt::LeftButton;
638
639 case SH_Slider_PageSetButtons:
640 return 0;
641
642 case SH_RequestSoftwareInputPanel:
643 return RSIP_OnMouseClick;
644
645 case SH_SpinBox_SelectOnStep:
646 return 0;
647
648 default:
649 return QFusionStyle::styleHint(hint, option, widget, returnData);
650 }
651}
652
653QPalette QAndroidStyle::standardPalette() const
654{
655 return m_standardPalette;
656}
657
658void QAndroidStyle::polish(QWidget *widget)
659{
660 widget->setAttribute(Qt::WA_StyledBackground, true);
661}
662
663void QAndroidStyle::unpolish(QWidget *widget)
664{
665 widget->setAttribute(Qt::WA_StyledBackground, false);
666}
667
668QAndroidStyle::AndroidDrawable::AndroidDrawable(const QVariantMap &drawable,
669 QAndroidStyle::ItemType itemType)
670{
671 initPadding(drawable);
672 m_itemType = itemType;
673}
674
675QAndroidStyle::AndroidDrawable::~AndroidDrawable()
676{
677}
678
679void QAndroidStyle::AndroidDrawable::initPadding(const QVariantMap &drawable)
680{
681 QVariantMap::const_iterator it = drawable.find(QLatin1String("padding"));
682 if (it != drawable.end())
683 m_padding = extractMargins(it.value().toMap());
684}
685
686const QMargins &QAndroidStyle::AndroidDrawable::padding() const
687{
688 return m_padding;
689}
690
691QSize QAndroidStyle::AndroidDrawable::size() const
692{
693 if (type() == Image || type() == NinePatch)
694 return static_cast<const QAndroidStyle::AndroidImageDrawable *>(this)->size();
695
696 return QSize();
697}
698
699QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidDrawable::fromMap(const QVariantMap &drawable,
700 ItemType itemType)
701{
702 const QString type = drawable.value(QLatin1String("type")).toString();
703 if (type == QLatin1String("image"))
704 return new QAndroidStyle::AndroidImageDrawable(drawable, itemType);
705 if (type == QLatin1String("9patch"))
706 return new QAndroidStyle::Android9PatchDrawable(drawable, itemType);
707 if (type == QLatin1String("stateslist"))
708 return new QAndroidStyle::AndroidStateDrawable(drawable, itemType);
709 if (type == QLatin1String("layer"))
710 return new QAndroidStyle::AndroidLayerDrawable(drawable, itemType);
711 if (type == QLatin1String("gradient"))
712 return new QAndroidStyle::AndroidGradientDrawable(drawable, itemType);
713 if (type == QLatin1String("clipDrawable"))
714 return new QAndroidStyle::AndroidClipDrawable(drawable, itemType);
715 if (type == QLatin1String("color"))
716 return new QAndroidStyle::AndroidColorDrawable(drawable, itemType);
717 return 0;
718}
719
720QMargins QAndroidStyle::AndroidDrawable::extractMargins(const QVariantMap &value)
721{
722 QMargins m;
723 m.setLeft(value.value(QLatin1String("left")).toInt());
724 m.setRight(value.value(QLatin1String("right")).toInt());
725 m.setTop(value.value(QLatin1String("top")).toInt());
726 m.setBottom(value.value(QLatin1String("bottom")).toInt());
727 return m;
728}
729
730void QAndroidStyle::AndroidDrawable::setPaddingLeftToSizeWidth()
731{
732 QSize sz = size();
733 if (m_padding.isNull() && !sz.isNull())
734 m_padding.setLeft(sz.width());
735}
736
737
738QAndroidStyle::AndroidImageDrawable::AndroidImageDrawable(const QVariantMap &drawable,
739 QAndroidStyle::ItemType itemType)
740 : AndroidDrawable(drawable, itemType)
741{
742 m_filePath = drawable.value(QLatin1String("path")).toString();
743 m_size.setHeight(drawable.value(QLatin1String("height")).toInt());
744 m_size.setWidth(drawable.value(QLatin1String("width")).toInt());
745}
746
747QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidImageDrawable::type() const
748{
749 return QAndroidStyle::Image;
750}
751
752void QAndroidStyle::AndroidImageDrawable::draw(QPainter *painter, const QStyleOption *opt) const
753{
754 if (m_hashKey.isEmpty())
755 m_hashKey = QFileInfo(m_filePath).fileName();
756
757 QPixmap pm;
758 if (!QPixmapCache::find(m_hashKey, &pm)) {
759 pm.load(m_filePath);
760 QPixmapCache::insert(m_hashKey, pm);
761 }
762
763 painter->drawPixmap(opt->rect.x(), opt->rect.y() + (opt->rect.height() - pm.height()) / 2, pm);
764}
765
766QSize QAndroidStyle::AndroidImageDrawable::size() const
767{
768 return m_size;
769}
770
771QAndroidStyle::AndroidColorDrawable::AndroidColorDrawable(const QVariantMap &drawable,
772 ItemType itemType)
773 : AndroidDrawable(drawable, itemType)
774{
775 m_color.setRgba(QRgb(drawable.value(QLatin1String("color")).toInt()));
776}
777
778QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidColorDrawable::type() const
779{
780 return QAndroidStyle::Color;
781}
782
783void QAndroidStyle::AndroidColorDrawable::draw(QPainter *painter, const QStyleOption *opt) const
784{
785 painter->fillRect(opt->rect, m_color);
786}
787
788QAndroidStyle::Android9PatchDrawable::Android9PatchDrawable(const QVariantMap &drawable,
789 QAndroidStyle::ItemType itemType)
790 : AndroidImageDrawable(drawable.value(QLatin1String("drawable")).toMap(), itemType)
791{
792 initPadding(drawable);
793 QVariantMap chunk = drawable.value(QLatin1String("chunkInfo")).toMap();
794 extractIntArray(chunk.value(QLatin1String("xdivs")).toList(), m_chunkData.xDivs);
795 extractIntArray(chunk.value(QLatin1String("ydivs")).toList(), m_chunkData.yDivs);
796 extractIntArray(chunk.value(QLatin1String("colors")).toList(), m_chunkData.colors);
797}
798
799QAndroidStyle::AndroidDrawableType QAndroidStyle::Android9PatchDrawable::type() const
800{
801 return QAndroidStyle::NinePatch;
802}
803
804int QAndroidStyle::Android9PatchDrawable::calculateStretch(int boundsLimit,
805 int startingPoint,
806 int srcSpace,
807 int numStrechyPixelsRemaining,
808 int numFixedPixelsRemaining)
809{
810 int spaceRemaining = boundsLimit - startingPoint;
811 int stretchySpaceRemaining = spaceRemaining - numFixedPixelsRemaining;
812 return (float(srcSpace) * stretchySpaceRemaining / numStrechyPixelsRemaining + .5);
813}
814
815void QAndroidStyle::Android9PatchDrawable::extractIntArray(const QVariantList &values,
816 QList<int> & array)
817{
818 for (const QVariant &value : values)
819 array << value.toInt();
820}
821
822
823void QAndroidStyle::Android9PatchDrawable::draw(QPainter *painter, const QStyleOption *opt) const
824{
825 if (m_hashKey.isEmpty())
826 m_hashKey = QFileInfo(m_filePath).fileName();
827
828 QPixmap pixmap;
829 if (!QPixmapCache::find(m_hashKey, &pixmap)) {
830 pixmap.load(m_filePath);
831 QPixmapCache::insert(m_hashKey, pixmap);
832 }
833
834 const QRect &bounds = opt->rect;
835
836 // shamelessly stolen from Android's sources (NinepatchImpl.cpp) and adapted for Qt
837 const int pixmapWidth = pixmap.width();
838 const int pixmapHeight = pixmap.height();
839
840 if (bounds.isNull() || !pixmapWidth || !pixmapHeight)
841 return;
842
843 QPainter::RenderHints savedHints = painter->renderHints();
844
845 // The patchs doesn't need smooth transform !
846 painter->setRenderHints(QPainter::SmoothPixmapTransform, false);
847
848 QRectF dst;
849 QRectF src;
850
851 const qint32 x0 = m_chunkData.xDivs[0];
852 const qint32 y0 = m_chunkData.yDivs[0];
853 const quint8 numXDivs = m_chunkData.xDivs.size();
854 const quint8 numYDivs = m_chunkData.yDivs.size();
855 int i;
856 int j;
857 int colorIndex = 0;
858 quint32 color;
859 bool xIsStretchable;
860 const bool initialXIsStretchable = (x0 == 0);
861 bool yIsStretchable = (y0 == 0);
862 const int bitmapWidth = pixmap.width();
863 const int bitmapHeight = pixmap.height();
864
865 int *dstRights = static_cast<int *>(alloca((numXDivs + 1) * sizeof(int)));
866 bool dstRightsHaveBeenCached = false;
867
868 int numStretchyXPixelsRemaining = 0;
869 for (i = 0; i < numXDivs; i += 2)
870 numStretchyXPixelsRemaining += m_chunkData.xDivs[i + 1] - m_chunkData.xDivs[i];
871
872 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
873 int numStretchyYPixelsRemaining = 0;
874 for (i = 0; i < numYDivs; i += 2)
875 numStretchyYPixelsRemaining += m_chunkData.yDivs[i + 1] - m_chunkData.yDivs[i];
876
877 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
878 src.setTop(0);
879 dst.setTop(bounds.top());
880 // The first row always starts with the top being at y=0 and the bottom
881 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
882 // the first row is stretchable along the Y axis, otherwise it is fixed.
883 // The last row always ends with the bottom being bitmap.height and the top
884 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
885 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
886 // the Y axis, otherwise it is fixed.
887 //
888 // The first and last columns are similarly treated with respect to the X
889 // axis.
890 //
891 // The above is to help explain some of the special casing that goes on the
892 // code below.
893
894 // The initial yDiv and whether the first row is considered stretchable or
895 // not depends on whether yDiv[0] was zero or not.
896 for (j = yIsStretchable ? 1 : 0;
897 j <= numYDivs && src.top() < bitmapHeight;
898 j++, yIsStretchable = !yIsStretchable) {
899 src.setLeft(0);
900 dst.setLeft(bounds.left());
901 if (j == numYDivs) {
902 src.setBottom(bitmapHeight);
903 dst.setBottom(bounds.bottom());
904 } else {
905 src.setBottom(m_chunkData.yDivs[j]);
906 const int srcYSize = src.bottom() - src.top();
907 if (yIsStretchable) {
908 dst.setBottom(dst.top() + calculateStretch(bounds.bottom(), dst.top(),
909 srcYSize,
910 numStretchyYPixelsRemaining,
911 numFixedYPixelsRemaining));
912 numStretchyYPixelsRemaining -= srcYSize;
913 } else {
914 dst.setBottom(dst.top() + srcYSize);
915 numFixedYPixelsRemaining -= srcYSize;
916 }
917 }
918
919 xIsStretchable = initialXIsStretchable;
920 // The initial xDiv and whether the first column is considered
921 // stretchable or not depends on whether xDiv[0] was zero or not.
922 for (i = xIsStretchable ? 1 : 0;
923 i <= numXDivs && src.left() < bitmapWidth;
924 i++, xIsStretchable = !xIsStretchable) {
925 color = m_chunkData.colors[colorIndex++];
926 if (color != TRANSPARENT_COLOR)
927 color = NO_COLOR;
928 if (i == numXDivs) {
929 src.setRight(bitmapWidth);
930 dst.setRight(bounds.right());
931 } else {
932 src.setRight(m_chunkData.xDivs[i]);
933 if (dstRightsHaveBeenCached) {
934 dst.setRight(dstRights[i]);
935 } else {
936 const int srcXSize = src.right() - src.left();
937 if (xIsStretchable) {
938 dst.setRight(dst.left() + calculateStretch(bounds.right(), dst.left(),
939 srcXSize,
940 numStretchyXPixelsRemaining,
941 numFixedXPixelsRemaining));
942 numStretchyXPixelsRemaining -= srcXSize;
943 } else {
944 dst.setRight(dst.left() + srcXSize);
945 numFixedXPixelsRemaining -= srcXSize;
946 }
947 dstRights[i] = dst.right();
948 }
949 }
950 // If this horizontal patch is too small to be displayed, leave
951 // the destination left edge where it is and go on to the next patch
952 // in the source.
953 if (src.left() >= src.right()) {
954 src.setLeft(src.right());
955 continue;
956 }
957 // Make sure that we actually have room to draw any bits
958 if (dst.right() <= dst.left() || dst.bottom() <= dst.top()) {
959 goto nextDiv;
960 }
961 // If this patch is transparent, skip and don't draw.
962 if (color == TRANSPARENT_COLOR)
963 goto nextDiv;
964 if (color != NO_COLOR)
965 painter->fillRect(dst, QRgb(color));
966 else
967 painter->drawPixmap(dst, pixmap, src);
968nextDiv:
969 src.setLeft(src.right());
970 dst.setLeft(dst.right());
971 }
972 src.setTop(src.bottom());
973 dst.setTop(dst.bottom());
974 dstRightsHaveBeenCached = true;
975 }
976 painter->setRenderHints(savedHints);
977}
978
979QAndroidStyle::AndroidGradientDrawable::AndroidGradientDrawable(const QVariantMap &drawable,
980 QAndroidStyle::ItemType itemType)
981 : AndroidDrawable(drawable, itemType), m_orientation(TOP_BOTTOM)
982{
983 m_radius = drawable.value(QLatin1String("radius")).toInt();
984 if (m_radius < 0)
985 m_radius = 0;
986
987 QVariantList colors = drawable.value(QLatin1String("colors")).toList();
988 QVariantList positions = drawable.value(QLatin1String("positions")).toList();
989 int min = colors.size() < positions.size() ? colors.size() : positions.size();
990 for (int i = 0; i < min; i++)
991 m_gradient.setColorAt(positions.at(i).toDouble(), QRgb(colors.at(i).toInt()));
992
993 QByteArray orientation = drawable.value(QLatin1String("orientation")).toByteArray();
994 if (orientation == "TOP_BOTTOM") // draw the gradient from the top to the bottom
995 m_orientation = TOP_BOTTOM;
996 else if (orientation == "TR_BL") // draw the gradient from the top-right to the bottom-left
997 m_orientation = TR_BL;
998 else if (orientation == "RIGHT_LEFT") // draw the gradient from the right to the left
999 m_orientation = RIGHT_LEFT;
1000 else if (orientation == "BR_TL") // draw the gradient from the bottom-right to the top-left
1001 m_orientation = BR_TL;
1002 else if (orientation == "BOTTOM_TOP") // draw the gradient from the bottom to the top
1003 m_orientation = BOTTOM_TOP;
1004 else if (orientation == "BL_TR") // draw the gradient from the bottom-left to the top-right
1005 m_orientation = BL_TR;
1006 else if (orientation == "LEFT_RIGHT") // draw the gradient from the left to the right
1007 m_orientation = LEFT_RIGHT;
1008 else if (orientation == "TL_BR") // draw the gradient from the top-left to the bottom-right
1009 m_orientation = TL_BR;
1010 else
1011 qWarning("AndroidGradientDrawable: unknown orientation");
1012}
1013
1014QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidGradientDrawable::type() const
1015{
1016 return QAndroidStyle::Gradient;
1017}
1018
1019void QAndroidStyle::AndroidGradientDrawable::draw(QPainter *painter, const QStyleOption *opt) const
1020{
1021 const int width = opt->rect.width();
1022 const int height = opt->rect.height();
1023 switch (m_orientation) {
1024 case TOP_BOTTOM:
1025 // draw the gradient from the top to the bottom
1026 m_gradient.setStart(width / 2, 0);
1027 m_gradient.setFinalStop(width / 2, height);
1028 break;
1029 case TR_BL:
1030 // draw the gradient from the top-right to the bottom-left
1031 m_gradient.setStart(width, 0);
1032 m_gradient.setFinalStop(0, height);
1033 break;
1034 case RIGHT_LEFT:
1035 // draw the gradient from the right to the left
1036 m_gradient.setStart(width, height / 2);
1037 m_gradient.setFinalStop(0, height / 2);
1038 break;
1039 case BR_TL:
1040 // draw the gradient from the bottom-right to the top-left
1041 m_gradient.setStart(width, height);
1042 m_gradient.setFinalStop(0, 0);
1043 break;
1044 case BOTTOM_TOP:
1045 // draw the gradient from the bottom to the top
1046 m_gradient.setStart(width / 2, height);
1047 m_gradient.setFinalStop(width / 2, 0);
1048 break;
1049 case BL_TR:
1050 // draw the gradient from the bottom-left to the top-right
1051 m_gradient.setStart(0, height);
1052 m_gradient.setFinalStop(width, 0);
1053 break;
1054 case LEFT_RIGHT:
1055 // draw the gradient from the left to the right
1056 m_gradient.setStart(0, height / 2);
1057 m_gradient.setFinalStop(width, height / 2);
1058 break;
1059 case TL_BR:
1060 // draw the gradient from the top-left to the bottom-right
1061 m_gradient.setStart(0, 0);
1062 m_gradient.setFinalStop(width, height);
1063 break;
1064 }
1065
1066 const QBrush &oldBrush = painter->brush();
1067 const QPen oldPen = painter->pen();
1068 painter->setPen(Qt::NoPen);
1069 painter->setBrush(m_gradient);
1070 painter->drawRoundedRect(opt->rect, m_radius, m_radius);
1071 painter->setBrush(oldBrush);
1072 painter->setPen(oldPen);
1073}
1074
1075QSize QAndroidStyle::AndroidGradientDrawable::size() const
1076{
1077 return QSize(m_radius * 2, m_radius * 2);
1078}
1079
1080QAndroidStyle::AndroidClipDrawable::AndroidClipDrawable(const QVariantMap &drawable,
1081 QAndroidStyle::ItemType itemType)
1082 : AndroidDrawable(drawable, itemType)
1083{
1084 m_drawable = fromMap(drawable.value(QLatin1String("drawable")).toMap(), itemType);
1085 m_factor = 0;
1086 m_orientation = Qt::Horizontal;
1087}
1088
1089QAndroidStyle::AndroidClipDrawable::~AndroidClipDrawable()
1090{
1091 delete m_drawable;
1092}
1093
1094QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidClipDrawable::type() const
1095{
1096 return QAndroidStyle::Clip;
1097}
1098
1099void QAndroidStyle::AndroidClipDrawable::setFactor(double factor, Qt::Orientation orientation)
1100{
1101 m_factor = factor;
1102 m_orientation = orientation;
1103}
1104
1105void QAndroidStyle::AndroidClipDrawable::draw(QPainter *painter, const QStyleOption *opt) const
1106{
1107 QStyleOption copy(*opt);
1108 if (m_orientation == Qt::Horizontal)
1109 copy.rect.setWidth(copy.rect.width() * m_factor);
1110 else
1111 copy.rect.setHeight(copy.rect.height() * m_factor);
1112
1113 m_drawable->draw(painter, &copy);
1114}
1115
1116QAndroidStyle::AndroidStateDrawable::AndroidStateDrawable(const QVariantMap &drawable,
1117 QAndroidStyle::ItemType itemType)
1118 : AndroidDrawable(drawable, itemType)
1119{
1120 const QVariantList states = drawable.value(QLatin1String("stateslist")).toList();
1121 for (const QVariant &stateVariant : states) {
1122 QVariantMap state = stateVariant.toMap();
1123 const int s = extractState(state.value(QLatin1String("states")).toMap());
1124 if (-1 == s)
1125 continue;
1126 const AndroidDrawable *ad = fromMap(state.value(QLatin1String("drawable")).toMap(), itemType);
1127 if (!ad)
1128 continue;
1129 StateType item;
1130 item.first = s;
1131 item.second = ad;
1132 m_states<<item;
1133 }
1134}
1135
1136QAndroidStyle::AndroidStateDrawable::~AndroidStateDrawable()
1137{
1138 for (const StateType &type : std::as_const(m_states))
1139 delete type.second;
1140}
1141
1142QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidStateDrawable::type() const
1143{
1144 return QAndroidStyle::State;
1145}
1146
1147void QAndroidStyle::AndroidStateDrawable::draw(QPainter *painter, const QStyleOption *opt) const
1148{
1149 const AndroidDrawable *drawable = bestAndroidStateMatch(opt);
1150 if (drawable)
1151 drawable->draw(painter, opt);
1152}
1153QSize QAndroidStyle::AndroidStateDrawable::sizeImage(const QStyleOption *opt) const
1154{
1155 QSize s;
1156 const AndroidDrawable *drawable = bestAndroidStateMatch(opt);
1157 if (drawable)
1158 s = drawable->size();
1159 return s;
1160}
1161
1162const QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidStateDrawable::bestAndroidStateMatch(const QStyleOption *opt) const
1163{
1164 const AndroidDrawable *bestMatch = nullptr;
1165 if (!opt) {
1166 if (m_states.size())
1167 return m_states[0].second;
1168 return bestMatch;
1169 }
1170
1171 uint bestCost = 0xffff;
1172 for (const StateType & state : m_states) {
1173 if (int(opt->state) == state.first)
1174 return state.second;
1175 uint cost = 1;
1176
1177 int difference = int(opt->state^state.first);
1178
1179 if (difference & QStyle::State_Active)
1180 cost <<= 1;
1181
1182 if (difference & QStyle::State_Enabled)
1183 cost <<= 1;
1184
1185 if (difference & QStyle::State_Raised)
1186 cost <<= 1;
1187
1188 if (difference & QStyle::State_Sunken)
1189 cost <<= 1;
1190
1191 if (difference & QStyle::State_Off)
1192 cost <<= 1;
1193
1194 if (difference & QStyle::State_On)
1195 cost <<= 1;
1196
1197 if (difference & QStyle::State_HasFocus)
1198 cost <<= 1;
1199
1200 if (difference & QStyle::State_Selected)
1201 cost <<= 1;
1202
1203 if (cost < bestCost) {
1204 bestCost = cost;
1205 bestMatch = state.second;
1206 }
1207 }
1208 return bestMatch;
1209}
1210
1211int QAndroidStyle::AndroidStateDrawable::extractState(const QVariantMap &value)
1212{
1213 QStyle::State state = QStyle::State_Enabled | QStyle::State_Active;
1214 for (auto it = value.cbegin(), end = value.cend(); it != end; ++it) {
1215 const QString &key = it.key();
1216 bool val = it.value().toString() == QLatin1String("true");
1217 if (key == QLatin1String("enabled")) {
1218 state.setFlag(QStyle::State_Enabled, val);
1219 continue;
1220 }
1221
1222 if (key == QLatin1String("window_focused")) {
1223 state.setFlag(QStyle::State_Active, val);
1224 continue;
1225 }
1226
1227 if (key == QLatin1String("focused")) {
1228 state.setFlag(QStyle::State_HasFocus, val);
1229 continue;
1230 }
1231
1232 if (key == QLatin1String("checked")) {
1233 state |= val ? QStyle::State_On : QStyle::State_Off;
1234 continue;
1235 }
1236
1237 if (key == QLatin1String("pressed")) {
1238 state |= val ? QStyle::State_Sunken : QStyle::State_Raised;
1239 continue;
1240 }
1241
1242 if (key == QLatin1String("selected")) {
1243 state.setFlag(QStyle::State_Selected, val);
1244 continue;
1245 }
1246
1247 if (key == QLatin1String("active")) {
1248 state.setFlag(QStyle::State_Active, val);
1249 continue;
1250 }
1251
1252 if (key == QLatin1String("multiline"))
1253 return 0;
1254
1255 if (key == QLatin1String("background") && val)
1256 return -1;
1257 }
1258 return static_cast<int>(state);
1259}
1260
1261void QAndroidStyle::AndroidStateDrawable::setPaddingLeftToSizeWidth()
1262{
1263 for (const StateType &type : std::as_const(m_states))
1264 const_cast<AndroidDrawable *>(type.second)->setPaddingLeftToSizeWidth();
1265}
1266
1267QAndroidStyle::AndroidLayerDrawable::AndroidLayerDrawable(const QVariantMap &drawable,
1268 QAndroidStyle::ItemType itemType)
1269 : AndroidDrawable(drawable, itemType)
1270{
1271 m_id = 0;
1272 m_factor = 1;
1273 m_orientation = Qt::Horizontal;
1274 const QVariantList layers = drawable.value(QLatin1String("layers")).toList();
1275 for (const QVariant &layer : layers) {
1276 QVariantMap layerMap = layer.toMap();
1277 AndroidDrawable *ad = fromMap(layerMap, itemType);
1278 if (ad) {
1279 LayerType l;
1280 l.second = ad;
1281 l.first = layerMap.value(QLatin1String("id")).toInt();
1282 m_layers << l;
1283 }
1284 }
1285}
1286
1287QAndroidStyle::AndroidLayerDrawable::~AndroidLayerDrawable()
1288{
1289 for (const LayerType &layer : std::as_const(m_layers))
1290 delete layer.second;
1291}
1292
1293QAndroidStyle::AndroidDrawableType QAndroidStyle::AndroidLayerDrawable::type() const
1294{
1295 return QAndroidStyle::Layer;
1296}
1297
1298void QAndroidStyle::AndroidLayerDrawable::setFactor(int id, double factor, Qt::Orientation orientation)
1299{
1300 m_id = id;
1301 m_factor = factor;
1302 m_orientation = orientation;
1303}
1304
1305void QAndroidStyle::AndroidLayerDrawable::draw(QPainter *painter, const QStyleOption *opt) const
1306{
1307 for (const LayerType &layer : m_layers) {
1308 if (layer.first == m_id) {
1309 QStyleOption copy(*opt);
1310 if (m_orientation == Qt::Horizontal)
1311 copy.rect.setWidth(copy.rect.width() * m_factor);
1312 else
1313 copy.rect.setHeight(copy.rect.height() * m_factor);
1314 layer.second->draw(painter, &copy);
1315 } else {
1316 layer.second->draw(painter, opt);
1317 }
1318 }
1319}
1320
1321QAndroidStyle::AndroidDrawable *QAndroidStyle::AndroidLayerDrawable::layer(int id) const
1322{
1323 for (const LayerType &layer : m_layers) {
1324 if (layer.first == id)
1325 return layer.second;
1326 }
1327 return 0;
1328}
1329
1330QSize QAndroidStyle::AndroidLayerDrawable::size() const
1331{
1332 QSize sz;
1333 for (const LayerType &layer : m_layers)
1334 sz = sz.expandedTo(layer.second->size());
1335 return sz;
1336}
1337
1338QAndroidStyle::AndroidControl::AndroidControl(const QVariantMap &control,
1339 QAndroidStyle::ItemType itemType)
1340{
1341 QVariantMap::const_iterator it = control.find(QLatin1String("View_background"));
1342 if (it != control.end())
1343 m_background = AndroidDrawable::fromMap(it.value().toMap(), itemType);
1344 else
1345 m_background = 0;
1346
1347 it = control.find(QLatin1String("View_minWidth"));
1348 if (it != control.end())
1349 m_minSize.setWidth(it.value().toInt());
1350
1351 it = control.find(QLatin1String("View_minHeight"));
1352 if (it != control.end())
1353 m_minSize.setHeight(it.value().toInt());
1354
1355 it = control.find(QLatin1String("View_maxWidth"));
1356 if (it != control.end())
1357 m_maxSize.setWidth(it.value().toInt());
1358
1359 it = control.find(QLatin1String("View_maxHeight"));
1360 if (it != control.end())
1361 m_maxSize.setHeight(it.value().toInt());
1362}
1363
1364QAndroidStyle::AndroidControl::~AndroidControl()
1365{
1366 delete m_background;
1367}
1368
1369void QAndroidStyle::AndroidControl::drawControl(const QStyleOption *opt, QPainter *p, const QWidget * /* w */)
1370{
1371 if (m_background) {
1372 m_background->draw(p, opt);
1373 } else {
1374 if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
1375 if ((frame->state & State_Sunken) || (frame->state & State_Raised)) {
1376 qDrawShadePanel(p, frame->rect, frame->palette, frame->state & State_Sunken,
1377 frame->lineWidth);
1378 } else {
1379 qDrawPlainRect(p, frame->rect, frame->palette.windowText().color(), frame->lineWidth);
1380 }
1381 } else {
1382 if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(opt)) {
1383 QColor bg = fropt->backgroundColor;
1384 QPen oldPen = p->pen();
1385 if (bg.isValid()) {
1386 int h, s, v;
1387 bg.getHsv(&h, &s, &v);
1388 if (v >= 128)
1389 p->setPen(Qt::black);
1390 else
1391 p->setPen(Qt::white);
1392 } else {
1393 p->setPen(opt->palette.windowText().color());
1394 }
1395 QRect focusRect = opt->rect.adjusted(1, 1, -1, -1);
1396 p->drawRect(focusRect.adjusted(0, 0, -1, -1)); //draw pen inclusive
1397 p->setPen(oldPen);
1398 } else {
1399 p->fillRect(opt->rect, opt->palette.window());
1400 }
1401 }
1402 }
1403}
1404
1405QRect QAndroidStyle::AndroidControl::subElementRect(QStyle::SubElement /* subElement */,
1406 const QStyleOption *option,
1407 const QWidget * /* widget */) const
1408{
1409 if (const AndroidDrawable *drawable = backgroundDrawable()) {
1410 if (drawable->type() == State)
1411 drawable = static_cast<const AndroidStateDrawable *>(backgroundDrawable())->bestAndroidStateMatch(option);
1412
1413 const QMargins &padding = drawable->padding();
1414
1415 QRect r = option->rect.adjusted(padding.left(), padding.top(),
1416 -padding.right(), -padding.bottom());
1417
1418 if (r.width() < m_minSize.width())
1419 r.setWidth(m_minSize.width());
1420
1421 if (r.height() < m_minSize.height())
1422 r.setHeight(m_minSize.height());
1423
1424 return visualRect(option->direction, option->rect, r);
1425 }
1426 return option->rect;
1427}
1428
1429QRect QAndroidStyle::AndroidControl::subControlRect(const QStyleOptionComplex *option,
1430 QStyle::SubControl /*sc*/,
1431 const QWidget *widget) const
1432{
1433 return subElementRect(QStyle::SE_CustomBase, option, widget);
1434}
1435
1436QSize QAndroidStyle::AndroidControl::sizeFromContents(const QStyleOption *opt,
1437 const QSize &contentsSize,
1438 const QWidget * /* w */) const
1439{
1440 QSize sz;
1441 if (const AndroidDrawable *drawable = backgroundDrawable()) {
1442
1443 if (drawable->type() == State)
1444 drawable = static_cast<const AndroidStateDrawable*>(backgroundDrawable())->bestAndroidStateMatch(opt);
1445 const QMargins &padding = drawable->padding();
1446 sz.setWidth(padding.left() + padding.right());
1447 sz.setHeight(padding.top() + padding.bottom());
1448 if (sz.isEmpty())
1449 sz = drawable->size();
1450 }
1451 sz += contentsSize;
1452 if (contentsSize.height() < opt->fontMetrics.height())
1453 sz.setHeight(sz.height() + (opt->fontMetrics.height() - contentsSize.height()));
1454 if (sz.height() < m_minSize.height())
1455 sz.setHeight(m_minSize.height());
1456 if (sz.width() < m_minSize.width())
1457 sz.setWidth(m_minSize.width());
1458 return sz;
1459}
1460
1461QMargins QAndroidStyle::AndroidControl::padding()
1462{
1463 if (const AndroidDrawable *drawable = m_background) {
1464 if (drawable->type() == State)
1465 drawable = static_cast<const AndroidStateDrawable *>(m_background)->bestAndroidStateMatch(0);
1466 return drawable->padding();
1467 }
1468 return QMargins();
1469}
1470
1471QSize QAndroidStyle::AndroidControl::size(const QStyleOption *option)
1472{
1473 if (const AndroidDrawable *drawable = backgroundDrawable()) {
1474 if (drawable->type() == State)
1475 drawable = static_cast<const AndroidStateDrawable *>(backgroundDrawable())->bestAndroidStateMatch(option);
1476 return drawable->size();
1477 }
1478 return QSize();
1479}
1480
1481const QAndroidStyle::AndroidDrawable *QAndroidStyle::AndroidControl::backgroundDrawable() const
1482{
1483 return m_background;
1484}
1485
1486QAndroidStyle::AndroidCompoundButtonControl::AndroidCompoundButtonControl(const QVariantMap &control,
1487 ItemType itemType)
1488 : AndroidControl(control, itemType)
1489{
1490 QVariantMap::const_iterator it = control.find(QLatin1String("CompoundButton_button"));
1491 if (it != control.end()) {
1492 m_button = AndroidDrawable::fromMap(it.value().toMap(), itemType);
1493 const_cast<AndroidDrawable *>(m_button)->setPaddingLeftToSizeWidth();
1494 } else {
1495 m_button = 0;
1496 }
1497}
1498
1499QAndroidStyle::AndroidCompoundButtonControl::~AndroidCompoundButtonControl()
1500{
1501 delete m_button;
1502}
1503
1504void QAndroidStyle::AndroidCompoundButtonControl::drawControl(const QStyleOption *opt,
1505 QPainter *p,
1506 const QWidget *w)
1507{
1508 AndroidControl::drawControl(opt, p, w);
1509 if (m_button)
1510 m_button->draw(p, opt);
1511}
1512
1513QMargins QAndroidStyle::AndroidCompoundButtonControl::padding()
1514{
1515 if (m_button)
1516 return m_button->padding();
1517 return AndroidControl::padding();
1518}
1519
1520QSize QAndroidStyle::AndroidCompoundButtonControl::size(const QStyleOption *option)
1521{
1522 if (m_button) {
1523 if (m_button->type() == State)
1524 return static_cast<const AndroidStateDrawable *>(m_button)->bestAndroidStateMatch(option)->size();
1525 return m_button->size();
1526 }
1527 return AndroidControl::size(option);
1528}
1529
1530const QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidCompoundButtonControl::backgroundDrawable() const
1531{
1532 return m_background ? m_background : m_button;
1533}
1534
1535QAndroidStyle::AndroidProgressBarControl::AndroidProgressBarControl(const QVariantMap &control,
1536 ItemType itemType)
1537 : AndroidControl(control, itemType)
1538{
1539 QVariantMap::const_iterator it = control.find(QLatin1String("ProgressBar_indeterminateDrawable"));
1540 if (it != control.end())
1541 m_indeterminateDrawable = AndroidDrawable::fromMap(it.value().toMap(), itemType);
1542 else
1543 m_indeterminateDrawable = 0;
1544
1545 it = control.find(QLatin1String("ProgressBar_progressDrawable"));
1546 if (it != control.end())
1547 m_progressDrawable = AndroidDrawable::fromMap(it.value().toMap(), itemType);
1548 else
1549 m_progressDrawable = 0;
1550
1551 it = control.find(QLatin1String("ProgressBar_progress_id"));
1552 if (it != control.end())
1553 m_progressId = it.value().toInt();
1554
1555 it = control.find(QLatin1String("ProgressBar_secondaryProgress_id"));
1556 if (it != control.end())
1557 m_secondaryProgress_id = it.value().toInt();
1558
1559 it = control.find(QLatin1String("ProgressBar_minWidth"));
1560 if (it != control.end())
1561 m_minSize.setWidth(it.value().toInt());
1562
1563 it = control.find(QLatin1String("ProgressBar_minHeight"));
1564 if (it != control.end())
1565 m_minSize.setHeight(it.value().toInt());
1566
1567 it = control.find(QLatin1String("ProgressBar_maxWidth"));
1568 if (it != control.end())
1569 m_maxSize.setWidth(it.value().toInt());
1570
1571 it = control.find(QLatin1String("ProgressBar_maxHeight"));
1572 if (it != control.end())
1573 m_maxSize.setHeight(it.value().toInt());
1574}
1575
1576QAndroidStyle::AndroidProgressBarControl::~AndroidProgressBarControl()
1577{
1578 delete m_progressDrawable;
1579 delete m_indeterminateDrawable;
1580}
1581
1582void QAndroidStyle::AndroidProgressBarControl::drawControl(const QStyleOption *option, QPainter *p, const QWidget * /* w */)
1583{
1584 if (!m_progressDrawable)
1585 return;
1586
1587 if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
1588 if (m_progressDrawable->type() == QAndroidStyle::Layer) {
1589 const double fraction = double(qint64(pb->progress) - pb->minimum) / (qint64(pb->maximum) - pb->minimum);
1590 QAndroidStyle::AndroidDrawable *clipDrawable = static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->layer(m_progressId);
1591 const Qt::Orientation orientation = pb->state & QStyle::State_Horizontal ? Qt::Horizontal : Qt::Vertical;
1592 if (clipDrawable->type() == QAndroidStyle::Clip)
1593 static_cast<AndroidClipDrawable *>(clipDrawable)->setFactor(fraction, orientation);
1594 else
1595 static_cast<AndroidLayerDrawable *>(m_progressDrawable)->setFactor(m_progressId, fraction, orientation);
1596 }
1597 m_progressDrawable->draw(p, option);
1598 }
1599}
1600
1601QRect QAndroidStyle::AndroidProgressBarControl::subElementRect(QStyle::SubElement subElement,
1602 const QStyleOption *option,
1603 const QWidget *widget) const
1604{
1605 if (const QStyleOptionProgressBar *progressBarOption =
1606 qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
1607 const bool horizontal = progressBarOption->state & QStyle::State_Horizontal;
1608 if (!m_background)
1609 return option->rect;
1610
1611 QMargins padding = m_background->padding();
1612 QRect p(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top());
1613 padding = m_indeterminateDrawable->padding();
1614 p |= QRect(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top());
1615 padding = m_progressDrawable->padding();
1616 p |= QRect(padding.left(), padding.top(), padding.right() - padding.left(), padding.bottom() - padding.top());
1617 QRect r = option->rect.adjusted(p.left(), p.top(), -p.right(), -p.bottom());
1618
1619 if (horizontal) {
1620 if (r.height()<m_minSize.height())
1621 r.setHeight(m_minSize.height());
1622
1623 if (r.height()>m_maxSize.height())
1624 r.setHeight(m_maxSize.height());
1625 } else {
1626 if (r.width()<m_minSize.width())
1627 r.setWidth(m_minSize.width());
1628
1629 if (r.width()>m_maxSize.width())
1630 r.setWidth(m_maxSize.width());
1631 }
1632 return visualRect(option->direction, option->rect, r);
1633 }
1634 return AndroidControl::subElementRect(subElement, option, widget);
1635}
1636
1637QSize QAndroidStyle::AndroidProgressBarControl::sizeFromContents(const QStyleOption *opt,
1638 const QSize &contentsSize,
1639 const QWidget * /* w */) const
1640{
1641 QSize sz(contentsSize);
1642 if (sz.height() < m_minSize.height())
1643 sz.setHeight(m_minSize.height());
1644 if (sz.width() < m_minSize.width())
1645 sz.setWidth(m_minSize.width());
1646
1647 if (const QStyleOptionProgressBar *progressBarOption =
1648 qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) {
1649 if (progressBarOption->state & QStyle::State_Horizontal) {
1650 if (sz.width() > m_maxSize.width())
1651 sz.setWidth(m_maxSize.width());
1652 } else {
1653 if (sz.height() > m_maxSize.height())
1654 sz.setHeight(m_maxSize.height());
1655 }
1656 }
1657 return contentsSize;
1658}
1659
1660QAndroidStyle::AndroidSeekBarControl::AndroidSeekBarControl(const QVariantMap &control,
1661 ItemType itemType)
1662 : AndroidProgressBarControl(control, itemType)
1663{
1664 QVariantMap::const_iterator it = control.find(QLatin1String("SeekBar_thumb"));
1665 if (it != control.end())
1666 m_seekBarThumb = AndroidDrawable::fromMap(it.value().toMap(), itemType);
1667 else
1668 m_seekBarThumb = 0;
1669}
1670
1671QAndroidStyle::AndroidSeekBarControl::~AndroidSeekBarControl()
1672{
1673 delete m_seekBarThumb;
1674}
1675
1676void QAndroidStyle::AndroidSeekBarControl::drawControl(const QStyleOption *option,
1677 QPainter *p,
1678 const QWidget * /* w */)
1679{
1680 if (!m_seekBarThumb || !m_progressDrawable)
1681 return;
1682
1683 if (const QStyleOptionSlider *styleOption =
1684 qstyleoption_cast<const QStyleOptionSlider *>(option)) {
1685 double factor = double(styleOption->sliderPosition - styleOption->minimum)
1686 / double(styleOption->maximum - styleOption->minimum);
1687
1688 // Android does not have a vertical slider. To support the vertical orientation, we rotate
1689 // the painter and pretend that we are horizontal.
1690 if (styleOption->orientation == Qt::Vertical)
1691 factor = 1 - factor;
1692
1693 if (m_progressDrawable->type() == QAndroidStyle::Layer) {
1694 QAndroidStyle::AndroidDrawable *clipDrawable = static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->layer(m_progressId);
1695 if (clipDrawable->type() == QAndroidStyle::Clip)
1696 static_cast<QAndroidStyle::AndroidClipDrawable *>(clipDrawable)->setFactor(factor, Qt::Horizontal);
1697 else
1698 static_cast<QAndroidStyle::AndroidLayerDrawable *>(m_progressDrawable)->setFactor(m_progressId, factor, Qt::Horizontal);
1699 }
1700 const AndroidDrawable *drawable = m_seekBarThumb;
1701 if (drawable->type() == State)
1702 drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(option);
1703 QStyleOption copy(*option);
1704
1705 p->save();
1706
1707 if (styleOption->orientation == Qt::Vertical) {
1708 // rotate the painter, and transform the rectangle to match
1709 p->rotate(90);
1710 copy.rect = QRect(copy.rect.y(), copy.rect.x() - copy.rect.width(), copy.rect.height(), copy.rect.width());
1711 }
1712
1713 copy.rect.setHeight(m_progressDrawable->size().height());
1714 copy.rect.setWidth(copy.rect.width() - drawable->size().width());
1715 const int yTranslate = abs(drawable->size().height() - copy.rect.height()) / 2;
1716 copy.rect.translate(drawable->size().width() / 2, yTranslate);
1717 m_progressDrawable->draw(p, &copy);
1718 int pos = copy.rect.width() * factor - drawable->size().width() / 2;
1719 copy.rect.translate(pos, -yTranslate);
1720 copy.rect.setSize(drawable->size());
1721 m_seekBarThumb->draw(p, &copy);
1722
1723 p->restore();
1724 }
1725}
1726
1727QSize QAndroidStyle::AndroidSeekBarControl::sizeFromContents(const QStyleOption *opt,
1728 const QSize &contentsSize,
1729 const QWidget *w) const
1730{
1731 QSize sz = AndroidProgressBarControl::sizeFromContents(opt, contentsSize, w);
1732 if (!m_seekBarThumb)
1733 return sz;
1734 const AndroidDrawable *drawable = m_seekBarThumb;
1735 if (drawable->type() == State)
1736 drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(opt);
1737 return sz.expandedTo(drawable->size());
1738}
1739
1740QRect QAndroidStyle::AndroidSeekBarControl::subControlRect(const QStyleOptionComplex *option,
1741 SubControl sc,
1742 const QWidget * /* widget */) const
1743{
1744 const QStyleOptionSlider *styleOption =
1745 qstyleoption_cast<const QStyleOptionSlider *>(option);
1746
1747 if (m_seekBarThumb && sc == SC_SliderHandle && styleOption) {
1748 const AndroidDrawable *drawable = m_seekBarThumb;
1749 if (drawable->type() == State)
1750 drawable = static_cast<const QAndroidStyle::AndroidStateDrawable *>(m_seekBarThumb)->bestAndroidStateMatch(option);
1751
1752 QRect r(option->rect);
1753 double factor = double(styleOption->sliderPosition - styleOption->minimum)
1754 / (styleOption->maximum - styleOption->minimum);
1755 if (styleOption->orientation == Qt::Vertical) {
1756 int pos = option->rect.height() * (1 - factor) - double(drawable->size().height() / 2);
1757 r.setY(r.y() + pos);
1758 } else {
1759 int pos = option->rect.width() * factor - double(drawable->size().width() / 2);
1760 r.setX(r.x() + pos);
1761 }
1762 r.setSize(drawable->size());
1763 return r;
1764 }
1765 return option->rect;
1766}
1767
1768QAndroidStyle::AndroidSpinnerControl::AndroidSpinnerControl(const QVariantMap &control,
1769 QAndroidStyle::ItemType itemType)
1770 : AndroidControl(control, itemType)
1771{}
1772
1773QRect QAndroidStyle::AndroidSpinnerControl::subControlRect(const QStyleOptionComplex *option,
1774 SubControl sc,
1775 const QWidget *widget) const
1776{
1777 if (sc == QStyle::SC_ComboBoxListBoxPopup)
1778 return option->rect;
1779 if (sc == QStyle::SC_ComboBoxArrow) {
1780 const QRect editField = subControlRect(option, QStyle::SC_ComboBoxEditField, widget);
1781 return QRect(editField.topRight(), QSize(option->rect.width() - editField.width(), option->rect.height()));
1782 }
1783 return AndroidControl::subControlRect(option, sc, widget);
1784}
1785
1786QT_END_NAMESPACE
const quint32 TRANSPARENT_COLOR
const quint32 NO_COLOR