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