// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include #include using namespace QQuickControlsTestUtils; struct ControlInfo { QString type; QStringList delegates; }; static const ControlInfo ControlInfos[] = { { "AbstractButton", QStringList() << "background" << "contentItem" << "indicator" }, { "ApplicationWindow", QStringList() << "background" }, { "BusyIndicator", QStringList() << "background" << "contentItem" }, { "Button", QStringList() << "background" << "contentItem" }, { "CheckBox", QStringList() << "contentItem" << "indicator" }, { "CheckDelegate", QStringList() << "background" << "contentItem" << "indicator" }, { "ComboBox", QStringList() << "background" << "contentItem" << "indicator" }, // popup not created until needed { "Container", QStringList() << "background" << "contentItem" }, { "Control", QStringList() << "background" << "contentItem" }, { "DelayButton", QStringList() << "background" << "contentItem" }, { "Dial", QStringList() << "background" << "handle" }, { "Dialog", QStringList() << "background" << "contentItem" }, { "DialogButtonBox", QStringList() << "background" << "contentItem" }, { "Drawer", QStringList() << "background" << "contentItem" }, { "Frame", QStringList() << "background" << "contentItem" }, { "GroupBox", QStringList() << "background" << "contentItem" << "label" }, { "ItemDelegate", QStringList() << "background" << "contentItem" }, { "Label", QStringList() << "background" }, { "Menu", QStringList() << "background" << "contentItem" }, { "MenuBar", QStringList() << "background" << "contentItem" }, { "MenuBarItem", QStringList() << "background" << "contentItem" }, { "MenuItem", QStringList() << "arrow" << "background" << "contentItem" << "indicator" }, { "MenuSeparator", QStringList() << "background" << "contentItem" }, { "Page", QStringList() << "background" << "contentItem" }, { "PageIndicator", QStringList() << "background" << "contentItem" }, { "Pane", QStringList() << "background" << "contentItem" }, { "Popup", QStringList() << "background" << "contentItem" }, { "ProgressBar", QStringList() << "background" << "contentItem" }, { "RadioButton", QStringList() << "contentItem" << "indicator" }, { "RadioDelegate", QStringList() << "background" << "contentItem" << "indicator" }, { "RangeSlider", QStringList() << "background" << "first.handle" << "second.handle" }, { "RoundButton", QStringList() << "background" << "contentItem" }, { "ScrollBar", QStringList() << "background" << "contentItem" }, { "ScrollIndicator", QStringList() << "background" << "contentItem" }, { "ScrollView", QStringList() << "background" }, { "Slider", QStringList() << "background" << "handle" }, { "SpinBox", QStringList() << "background" << "contentItem" << "up.indicator" << "down.indicator" }, { "StackView", QStringList() << "background" << "contentItem" }, { "SwipeDelegate", QStringList() << "background" << "contentItem" }, { "SwipeView", QStringList() << "background" << "contentItem" }, { "Switch", QStringList() << "contentItem" << "indicator" }, { "SwitchDelegate", QStringList() << "background" << "contentItem" << "indicator" }, { "TabBar", QStringList() << "background" << "contentItem" }, { "TabButton", QStringList() << "background" << "contentItem" }, { "TextField", QStringList() << "background" }, { "TextArea", QStringList() << "background" }, { "ToolBar", QStringList() << "background" << "contentItem" }, { "ToolButton", QStringList() << "background" << "contentItem" }, { "ToolSeparator", QStringList() << "background" << "contentItem" }, { "ToolTip", QStringList() << "background" << "contentItem" }, { "Tumbler", QStringList() << "background" << "contentItem" } }; static const QString nonCustomizableWarning = ".*The current style does not support customization of this control.*"; class tst_customization : public QQmlDataTest { Q_OBJECT public: tst_customization(); private slots: void initTestCase() override; void cleanupTestCase(); void init() override; void cleanup(); void creation_data(); void creation(); void override_data(); void override(); void comboPopup(); #if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS) void noCustomizationWarningsForDefaultControls_data(); void noCustomizationWarningsForDefaultControls(); #endif private: void reset(); void addHooks(); void removeHooks(); QObject* createControl(const QString &type, const QString &qml, QString *error); QQmlEngine *engine = nullptr; }; typedef QHash QObjectNameHash; Q_GLOBAL_STATIC(QObjectNameHash, qt_objectNames) Q_GLOBAL_STATIC(QStringList, qt_createdQObjects) Q_GLOBAL_STATIC(QStringList, qt_destroyedQObjects) Q_GLOBAL_STATIC(QStringList, qt_destroyedParentQObjects) static int qt_unparentedItemCount = 0; class ItemParentListener : public QQuickItem { Q_OBJECT public: ItemParentListener() { m_slotIndex = metaObject()->indexOfSlot("onParentChanged()"); m_signalIndex = QMetaObjectPrivate::signalIndex(QMetaMethod::fromSignal(&QQuickItem::parentChanged)); } int signalIndex() const { return m_signalIndex; } int slotIndex() const { return m_slotIndex; } public slots: void onParentChanged() { const QQuickItem *item = qobject_cast(sender()); if (!item) return; if (!item->parentItem()) ++qt_unparentedItemCount; } private: int m_slotIndex; int m_signalIndex; }; static ItemParentListener *qt_itemParentListener = nullptr; extern "C" Q_DECL_EXPORT void qt_addQObject(QObject *object) { // objectName is not set at construction time QObject::connect(object, &QObject::objectNameChanged, [object](const QString &objectName) { QString oldObjectName = qt_objectNames()->value(object); if (!oldObjectName.isEmpty()) qt_createdQObjects()->removeOne(oldObjectName); // Only track object names from our QML files, // not e.g. contentItem object names (like "ApplicationWindow"). if (objectName.contains("-")) { qt_createdQObjects()->append(objectName); qt_objectNames()->insert(object, objectName); } }); if (qt_itemParentListener) { static const int signalIndex = qt_itemParentListener->signalIndex(); static const int slotIndex = qt_itemParentListener->slotIndex(); QMetaObject::connect(object, signalIndex, qt_itemParentListener, slotIndex); } } extern "C" Q_DECL_EXPORT void qt_removeQObject(QObject *object) { QString objectName = object->objectName(); if (!objectName.isEmpty()) qt_destroyedQObjects()->append(objectName); qt_objectNames()->remove(object); QObject *parent = object->parent(); if (parent) { QString parentName = parent->objectName(); if (!parentName.isEmpty()) qt_destroyedParentQObjects()->append(parentName); } } // We don't want to fail on warnings until QTBUG-98964 is fixed, // as we deliberately prevent deferred execution in some of the tests here, // which causes warnings. tst_customization::tst_customization() : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings) { } void tst_customization::initTestCase() { QQmlDataTest::initTestCase(); qt_itemParentListener = new ItemParentListener; } void tst_customization::cleanupTestCase() { delete qt_itemParentListener; qt_itemParentListener = nullptr; } void tst_customization::init() { QQmlDataTest::init(); engine = new QQmlEngine(this); engine->addImportPath(testFile("styles")); qtHookData[QHooks::AddQObject] = reinterpret_cast(&qt_addQObject); qtHookData[QHooks::RemoveQObject] = reinterpret_cast(&qt_removeQObject); } void tst_customization::cleanup() { qtHookData[QHooks::AddQObject] = 0; qtHookData[QHooks::RemoveQObject] = 0; delete engine; engine = nullptr; qmlClearTypeRegistrations(); reset(); } void tst_customization::reset() { qt_unparentedItemCount = 0; qt_createdQObjects()->clear(); qt_destroyedQObjects()->clear(); qt_destroyedParentQObjects()->clear(); } QObject* tst_customization::createControl(const QString &name, const QString &qml, QString *error) { QQmlComponent component(engine); component.setData("import QtQuick; import QtQuick.Window; import QtQuick.Controls; " + name.toUtf8() + " { " + qml.toUtf8() + " }", QUrl()); QObject *obj = component.create(); if (!obj) *error = component.errorString(); return obj; } void tst_customization::creation_data() { QTest::addColumn("style"); QTest::addColumn("type"); QTest::addColumn("delegates"); // the "empty" style does not contain any delegates for (const ControlInfo &control : ControlInfos) QTest::newRow(qPrintable("empty:" + control.type)) << "empty" << control.type << QStringList(); // the "incomplete" style is missing bindings to the delegates (must be created regardless) for (const ControlInfo &control : ControlInfos) QTest::newRow(qPrintable("incomplete:" + control.type)) << "incomplete" << control.type << control.delegates; // the "identified" style has IDs in the delegates (prevents deferred execution) for (const ControlInfo &control : ControlInfos) QTest::newRow(qPrintable("identified:" + control.type)) << "identified" << control.type << control.delegates; // the "simple" style simulates a proper style and contains bindings to/in delegates for (const ControlInfo &control : ControlInfos) QTest::newRow(qPrintable("simple:" + control.type)) << "simple" << control.type << control.delegates; // the "override" style overrides all delegates in the "simple" style for (const ControlInfo &control : ControlInfos) QTest::newRow(qPrintable("override:" + control.type)) << "override" << control.type << control.delegates; } void tst_customization::creation() { QFETCH(QString, style); QFETCH(QString, type); QFETCH(QStringList, delegates); QQuickStyle::setStyle(style); QString error; QScopedPointer control(createControl(type, "", &error)); QVERIFY2(control, qPrintable(error)); QByteArray templateType = "QQuick" + type.toUtf8(); QVERIFY2(control->inherits(templateType), qPrintable(type + " does not inherit " + templateType + " (" + control->metaObject()->className() + ")")); // -