Use qmlRegisterModuleImport() to register styles

This patch completes the cumulative work done in previous patches.

- Uses qmlRegisterModuleImport() to register styles. This has some
  added requirements:
  - Each style must now be a QML module -- that is, it must have a
    qmldir file.
  - As a result of the above, the module must be available within the
    QML import path in order to be found.
  - The various forms of accepted style names have been reduced down to
    one ("Material", "MyStyle", etc). See below for an explanation of
    why.
  - The following API in QQuickStyle is removed:
    addStylePath(), availableStyles(), path(), stylePathList(). These
    no longer make sense now that we reuse the existing QML import
    system.
- Adds the tst_qquickstyleselector auto test back as "styleimports".

qmlRegisterModuleImport() vs resolvedUrl()

    Previously we would use QQuickStyleSelector to select individual
    QML files based on which style was set. We'd do this once when
    QtQuick.Controls was first imported.

    With Qt 6, and the requirement that each style be a proper QML
    module, qmlRegisterModuleImport() was introduced. This allows us
    to "link" one import with another. For an example of what this
    looks like in practice, suppose the style was set to "MyStyle",
    and the fallback to "Material". The "QtQuick.Controls" import
    will be linked to "MyStyle", "MyStyle" to
    "QtQuick.Controls.Material", and as a final fallback (for controls
    like Action which only the Default style implements),
    "QtQuick.Controls.Material" to "QtQuick.Controls.Default".
    This is the same behavior as in Qt 5 (see qquickstyleselector.cpp):

    // 1) requested style (e.g. "MyStyle", included in d->selectors)
    // 2) fallback style (e.g. "Material", included in d->selectors)
    // 3) default style (empty selector, not in d->selectors)

    This is a necessary step to enable compilation of QML to C++.

Reducing the set of accepted style names

    The problem

    In QtQuickControls2Plugin() we need to call
    QQuickStylePrivate::init(baseUrl()) in order to detect if the style
    is a custom style in QQuickStyleSpec::resolve() (by checking if the
    style path starts with the base URL). In Qt 5, init() is called in
    QtQuickControls2Plugin::registerTypes(), but in Qt 6 that's too
    late, because we need to call qmlRegisterModuleImport() in the
    constructor. qmlRegisterModuleImport() itself requires the style to
    have already been set in order to create the correct import URI
    ("QtQuick.Controls.X" for built-in styles, "MyCustomStyle" for
    custom styles).

    The solution

    By reducing the valid forms for style names down to one:

    ./myapp -style MyStyle

    we solve the problem of needing baseUrl() to determine if the
    style is a custom style or not, but needing to call it too early
    (since we now call qmlRegisterModuleImport() in
    QtQuickControls2Plugin(), which itself requires the style to have
    already been set). baseUrl() can't have been set before the
    constructor is finished.

    All of the various forms for _setting_ a style are still valid;
    environment variables, qtquickcontrols2.conf, etc.

[ChangeLog][Important Behavior Changes] Custom styles must now have
a qmldir that lists the files that the style implements. For example,
for a style that only implements Button:
---
module MyStyle
Button 1.0 Button.qml
---
In addition, there is now only one valid, case-sensitive form for style
names: "Material", "MyStyle", etc.
These changes are done to help enable the compilation of QML code to
C++, as well as improve tooling capabilities.

[ChangeLog][Important Behavior Changes] The following API was removed:
- QQuickStyle::addStylePath()
- QQuickStyle::availableStyles()
- QQuickStyle::path()
- QQuickStyle::stylePathList()
- QT_QUICK_CONTROLS_STYLE_PATH
This API is no longer necessary and/or able to be provided now that
styles are treated as regular QML modules.

Task-number: QTBUG-82922
Change-Id: I3b281131903c7c3c1cf0616eb7486a872dccd730
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Mitch Curtis 2020-04-17 14:32:55 +02:00
parent 92879d8f6b
commit 501bc44bb0
68 changed files with 1377 additions and 711 deletions

View File

@ -73,7 +73,11 @@ int main(int argc, char *argv[])
QQuickStyle::setStyle(settings.value("style").toString());
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
const QStringList builtInStyles = { QLatin1String("Default"), QLatin1String("Fusion"),
QLatin1String("Imagine"), QLatin1String("Material"), QLatin1String("Universal") };
engine.rootContext()->setContextProperty("builtInStyles", builtInStyles);
engine.load(QUrl("qrc:/gallery.qml"));
if (engine.rootObjects().isEmpty())
return -1;

View File

@ -287,7 +287,7 @@ ApplicationWindow {
ComboBox {
id: styleBox
property int styleIndex: -1
model: availableStyles
model: builtInStyles
Component.onCompleted: {
styleIndex = find(settings.style, Qt.MatchFixedString)
if (styleIndex !== -1)

View File

@ -38,6 +38,7 @@
#include "qquickdefaulttheme_p.h"
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
QT_BEGIN_NAMESPACE
@ -50,7 +51,10 @@ public:
QtQuickControls2DefaultStylePlugin(QObject *parent = nullptr);
QString name() const override;
void initializeTheme(QQuickTheme *theme) override;
void registerTypes(const char *uri) override;
QQuickDefaultTheme theme;
};
QtQuickControls2DefaultStylePlugin::QtQuickControls2DefaultStylePlugin(QObject *parent) : QQuickStylePlugin(parent)
@ -62,9 +66,11 @@ QString QtQuickControls2DefaultStylePlugin::name() const
return QStringLiteral("Default");
}
void QtQuickControls2DefaultStylePlugin::initializeTheme(QQuickTheme *theme)
void QtQuickControls2DefaultStylePlugin::registerTypes(const char *uri)
{
QQuickDefaultTheme::initialize(theme);
QQuickStylePlugin::registerTypes(uri);
theme.initialize(QQuickTheme::instance());
}
QT_END_NAMESPACE

View File

@ -112,7 +112,7 @@
\section2 Definition of a Style
In Qt Quick Controls, a style is essentially an interchangeable set of
QML files within a single directory. There are three requirements for a style
QML files within a single directory. There are four requirements for a style
to be \l {Using Styles in Qt Quick Controls}{usable}:
\list
@ -125,21 +125,37 @@
If we instead used the corresponding type from the \l {Qt Quick Controls}
{QtQuick.Controls} import as we did in the previous section, it would not work:
the control we were defining would try to derive from itself.
\li The files must be in a directory in the filesystem or in the
\l {The Qt Resource System}{resource system}.
\li A \l {Module Definition qmldir Files}{qmldir} file must exist alongside
the QML file(s). Below is an example of a simple \c qmldir file for a style that
provides a button:
For example, these are all valid paths to a style:
\badcode
module MyStyle
Button 2.15 Button.qml
\endcode
The directory structure for such a style looks like this:
\badcode
MyStyle
├─── Button.qml
└─── qmldir
\endcode
\li The files must be in a directory that is findable via the \l {QML Import Path}.
For example, if the path to \e MyStyle directory mentioned above was
\c /home/user/MyApp/MyStyle, then \c /home/user/MyApp must be added to
the QML import path.
To \l {Using Styles in Qt Quick Controls}{use} \e MyStyle in \e MyApp,
refer to it by name:
\list
\li \c {./myapp -style /home/absolute/path/to/my/style}
\li \c {./myapp -style :/mystyle}
\li \c {./myapp -style relative/path/to/my/style}
\li \c {./myapp -style MyStyle}
\li \c {./MyApp -style MyStyle}
\endlist
The third and fourth paths will be looked up within the QML engine's import path
list. This is the same as what happens when you pass \c Material as the style,
for example.
The style name must match the casing of the style directory; passing
\e mystyle or \e MYSTYLE is not supported.
\endlist
By default, the styling system uses the Default style as a fallback for
@ -302,7 +318,7 @@
style will illustrate the elevation with a drop shadow; the higher the
elevation, the larger the shadow.
The first step is to \l {Qt Creator: Creating Qt Quick Projects}{create a new Qt Quick
The first step is to \l {Creating Qt Quick Projects}{create a new Qt Quick
Controls 2 application} in Qt Creator. After that, we
\l {Qt Creator: Creating C++ Classes}{add a C++ type} that stores the elevation. Since
the type will be used for every control supported by our style, and because
@ -390,6 +406,8 @@
qmlRegisterUncreatableType<MyStyle>("MyStyle", 1, 0, "MyStyle", "MyStyle is an attached property");
QQmlApplicationEngine engine;
// Make the directory containing our style known to the QML engine.
engine.addImportPath(":/");
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
@ -465,7 +483,7 @@
One button has no elevation, and the other has an elevation of \c 10.
With that in place, we can run our example. To tell the application to
use our new style, we pass \c {-style :/mystyle} as an application
use our new style, we pass \c {-style MyStyle} as an application
argument, but there are \l {Using Styles in Qt Quick Controls}{many
ways} to specify the style to use.

View File

@ -41,17 +41,11 @@
\li \c QT_QUICK_CONTROLS_STYLE
\li Specifies the default \l {Styling Qt Quick Controls}{Qt Quick Controls style}.
The value can be either one of the built-in styles, for example \c "Material",
or the path to a custom style such as \c ":/mystyle".
or a custom style such as \c "MyStyle".
\row
\li \c QT_QUICK_CONTROLS_FALLBACK_STYLE
\li Specifies a fallback style for \l {Creating a Custom Style}{custom styles}.
The value can be one of the built-in styles, for example \c "Material",
\row
\li \c QT_QUICK_CONTROLS_STYLE_PATH
\li Specifies a list of additional paths that are used to lookup \l {Styling Qt Quick Controls}
{Qt Quick Controls styles}. Multiple path entries must be \l {QDir::listSeparator}{separated}
by \c ':' under Unix and \c ';' under Windows. By default, styles are looked up from
\c $QML2_IMPORT_PATH/QtQuick/Controls.
\row
\li \c QT_QUICK_CONTROLS_CONF
\li Specifies the location of the \l {Qt Quick Controls configuration file}.

View File

@ -1,7 +1,7 @@
module QtQuick.Controls.Fusion
plugin qtquickcontrols2fusionstyleplugin
classname QtQuickControls2FusionStylePlugin
depends QtQuick.Controls 2.5
import QtQuick.Controls.Default auto
# QtQuick.Controls 2.0 (originally introduced in Qt 5.7)
ApplicationWindow 2.0 ApplicationWindow.qml

View File

@ -34,12 +34,13 @@
**
****************************************************************************/
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQml/qqml.h>
#include "qquickfusionstyle_p.h"
#include "qquickfusiontheme_p.h"
#include <QtQml/qqml.h>
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
QT_BEGIN_NAMESPACE
class QtQuickControls2FusionStylePlugin : public QQuickStylePlugin
@ -51,7 +52,10 @@ public:
QtQuickControls2FusionStylePlugin(QObject *parent = nullptr);
QString name() const override;
void initializeTheme(QQuickTheme *theme) override;
void registerTypes(const char *uri) override;
QQuickFusionTheme theme;
};
QtQuickControls2FusionStylePlugin::QtQuickControls2FusionStylePlugin(QObject *parent) : QQuickStylePlugin(parent)
@ -63,9 +67,11 @@ QString QtQuickControls2FusionStylePlugin::name() const
return QStringLiteral("Fusion");
}
void QtQuickControls2FusionStylePlugin::initializeTheme(QQuickTheme *theme)
void QtQuickControls2FusionStylePlugin::registerTypes(const char *uri)
{
QQuickFusionTheme::initialize(theme);
QQuickStylePlugin::registerTypes(uri);
theme.initialize(QQuickTheme::instance());
}
QT_END_NAMESPACE

View File

@ -1,7 +1,7 @@
module QtQuick.Controls.Imagine
plugin qtquickcontrols2imaginestyleplugin
classname QtQuickControls2ImagineStylePlugin
depends QtQuick.Controls 2.5
import QtQuick.Controls.Default auto
# QtQuick.Controls 2.0 (originally introduced in Qt 5.7)
ApplicationWindow 2.0 ApplicationWindow.qml

View File

@ -34,13 +34,14 @@
**
****************************************************************************/
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtCore/qloggingcategory.h>
#include <QtQml/qqml.h>
#include "qquickimaginestyle_p.h"
#include "qquickimaginetheme_p.h"
#include <QtCore/qloggingcategory.h>
#include <QtQml/qqml.h>
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
QT_BEGIN_NAMESPACE
class QtQuickControls2ImagineStylePlugin : public QQuickStylePlugin
@ -52,7 +53,10 @@ public:
QtQuickControls2ImagineStylePlugin(QObject *parent = nullptr);
QString name() const override;
void initializeTheme(QQuickTheme *theme) override;
void registerTypes(const char *uri) override;
QQuickImagineTheme theme;
};
QtQuickControls2ImagineStylePlugin::QtQuickControls2ImagineStylePlugin(QObject *parent) : QQuickStylePlugin(parent)
@ -64,9 +68,11 @@ QString QtQuickControls2ImagineStylePlugin::name() const
return QStringLiteral("Imagine");
}
void QtQuickControls2ImagineStylePlugin::initializeTheme(QQuickTheme *theme)
void QtQuickControls2ImagineStylePlugin::registerTypes(const char *uri)
{
QQuickImagineTheme::initialize(theme);
QQuickStylePlugin::registerTypes(uri);
theme.initialize(QQuickTheme::instance());
}
QT_END_NAMESPACE

View File

@ -1,7 +1,7 @@
module QtQuick.Controls.Material
plugin qtquickcontrols2materialstyleplugin
classname QtQuickControls2MaterialStylePlugin
depends QtQuick.Controls 2.5
import QtQuick.Controls.Default auto
# QtQuick.Controls 2.0 (originally introduced in Qt 5.7)
ApplicationWindow 2.0 ApplicationWindow.qml

View File

@ -34,12 +34,12 @@
**
****************************************************************************/
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include "qquickmaterialstyle_p.h"
#include "qquickmaterialtheme_p.h"
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickControls2Impl/private/qquickpaddedrectangle_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
QT_BEGIN_NAMESPACE
@ -52,12 +52,14 @@ public:
QtQuickControls2MaterialStylePlugin(QObject *parent = nullptr);
QString name() const override;
void initializeTheme(QQuickTheme *theme) override;
void registerTypes(const char *uri) override;
QQuickMaterialTheme theme;
};
QtQuickControls2MaterialStylePlugin::QtQuickControls2MaterialStylePlugin(QObject *parent) : QQuickStylePlugin(parent)
{
QQuickMaterialStyle::initGlobals();
}
QString QtQuickControls2MaterialStylePlugin::name() const
@ -65,9 +67,12 @@ QString QtQuickControls2MaterialStylePlugin::name() const
return QStringLiteral("Material");
}
void QtQuickControls2MaterialStylePlugin::initializeTheme(QQuickTheme *theme)
void QtQuickControls2MaterialStylePlugin::registerTypes(const char *uri)
{
QQuickMaterialTheme::initialize(theme);
QQuickStylePlugin::registerTypes(uri);
QQuickMaterialStyle::initGlobals();
theme.initialize(QQuickTheme::instance());
}
QT_END_NAMESPACE

View File

@ -37,6 +37,7 @@
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qpluginloader.h>
#include <QtCore/private/qfileselector_p.h>
#include <QtQml/qqmlfile.h>
@ -50,6 +51,8 @@
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQtQuickControlsStylePlugin, "qt.quick.controls.qtquickcontrols2plugin")
class QtQuickControls2Plugin : public QQmlExtensionPlugin
{
Q_OBJECT
@ -59,17 +62,41 @@ public:
QtQuickControls2Plugin(QObject *parent = nullptr);
~QtQuickControls2Plugin();
void initializeEngine(QQmlEngine *engine, const char *uri) override;
void registerTypes(const char *uri) override;
void unregisterTypes() override;
private:
void init();
QList<QQuickStylePlugin *> loadStylePlugins();
QQuickTheme *createTheme(const QString &name);
bool registeredFallbackImport = false;
};
static const char *qtQuickControlsUri = "QtQuick.Controls";
QString styleUri()
{
const QString style = QQuickStyle::name();
if (!QQuickStylePrivate::isCustomStyle()) {
// The style set is a built-in style.
const QString styleName = QQuickStylePrivate::effectiveStyleName(style);
return QString::fromLatin1("QtQuick.Controls.%1").arg(styleName);
}
// This is a custom style, so just use the name as the import uri.
QString styleName = style;
if (styleName.startsWith(QLatin1String(":/")))
styleName.remove(0, 2);
return styleName;
}
QString fallbackStyleUri()
{
// The fallback style must be a built-in style, so we don't need to check for custom styles here.
const QString fallbackStyle = QQuickStylePrivate::fallbackStyle();
const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(fallbackStyle);
return QString::fromLatin1("QtQuick.Controls.%1").arg(fallbackStyleName);
}
QtQuickControls2Plugin::QtQuickControls2Plugin(QObject *parent) : QQmlExtensionPlugin(parent)
{
}
@ -80,88 +107,83 @@ QtQuickControls2Plugin::~QtQuickControls2Plugin()
// initialization and cleanup, as plugins are not unloaded on macOS.
}
void QtQuickControls2Plugin::initializeEngine(QQmlEngine *engine, const char */*uri*/)
void QtQuickControls2Plugin::registerTypes(const char *uri)
{
engine->addUrlInterceptor(&QQuickStylePrivate::urlInterceptor);
init();
}
qCDebug(lcQtQuickControlsStylePlugin) << "registerTypes() called with uri" << uri;
void QtQuickControls2Plugin::registerTypes(const char */*uri*/)
{
QQuickStylePrivate::init(baseUrl());
// It's OK that the style is resolved more than once; some accessors like name() cause it to be called, for example.
QQuickStylePrivate::init();
const QString styleName = QQuickStylePrivate::effectiveStyleName(QQuickStyle::name());
const QString fallbackStyleName = QQuickStylePrivate::effectiveStyleName(QQuickStylePrivate::fallbackStyle());
qCDebug(lcQtQuickControlsStylePlugin) << "style:" << QQuickStyle::name() << "effective style:" << styleName
<< "fallback style:" << QQuickStylePrivate::fallbackStyle() << "effective fallback style:" << fallbackStyleName;
createTheme(styleName);
// If the style is Default, we don't need to register the fallback because the Default style
// provides all controls. Also, if we didn't return early here, we can get an infinite import loop
// when the style is set to Default.
if (styleName != fallbackStyleName && styleName != QLatin1String("Default")) {
const QString fallbackstyleUri = ::fallbackStyleUri();
qCDebug(lcQtQuickControlsStylePlugin) << "calling qmlRegisterModuleImport() to register fallback style with"
<< "uri \"" << qtQuickControlsUri << "\" moduleMajor" << QQmlModuleImportModuleAny << "import" << fallbackstyleUri
<< "importMajor" << QQmlModuleImportAuto;
// The fallback style must be a built-in style, so we match the version number.
qmlRegisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, fallbackstyleUri.toUtf8().constData(),
QQmlModuleImportAuto, QQmlModuleImportAuto);
registeredFallbackImport = true;
}
const QString styleUri = ::styleUri();
// If the user imports QtQuick.Controls 2.15, and they're using the Material style, we should import version 2.15.
// However, if they import QtQuick.Controls 2.15, but are using a custom style, we want to use the latest version
// number of their style.
const int importMajor = !QQuickStylePrivate::isCustomStyle() ? QQmlModuleImportAuto : QQmlModuleImportLatest;
qCDebug(lcQtQuickControlsStylePlugin).nospace() << "calling qmlRegisterModuleImport() to register primary style with"
<< " uri \"" << qtQuickControlsUri << "\" moduleMajor " << importMajor << " import " << styleUri
<< " importMajor " << importMajor;
qmlRegisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, styleUri.toUtf8().constData(), importMajor);
const QString style = QQuickStyle::name();
if (!style.isEmpty())
QFileSelectorPrivate::addStatics(QStringList() << style.toLower());
QFileSelectorPrivate::addStatics(QStringList() << style);
}
void QtQuickControls2Plugin::unregisterTypes()
{
qCDebug(lcQtQuickControlsStylePlugin) << "unregisterTypes() called";
if (registeredFallbackImport) {
const QString fallbackStyleUri = ::fallbackStyleUri();
qmlUnregisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, fallbackStyleUri.toUtf8().constData(),
QQmlModuleImportAuto, QQmlModuleImportAuto);
}
const QString primary = QQuickStylePrivate::effectiveStyleName(QQuickStyle::name());
const QString styleUri = ::styleUri();
const int importMajor = !QQuickStylePrivate::isCustomStyle() ? QQmlModuleImportAuto : QQmlModuleImportLatest;
qmlUnregisterModuleImport(qtQuickControlsUri, QQmlModuleImportModuleAny, styleUri.toUtf8().constData(), importMajor);
QQuickStylePrivate::reset();
}
void QtQuickControls2Plugin::init()
{
const QString style = QQuickStyle::name();
QQuickTheme *theme = createTheme(style.isEmpty() ? QLatin1String("Default") : style);
/*!
\internal
// load the style's plugins to get access to its resources and initialize the theme
QList<QQuickStylePlugin *> stylePlugins = loadStylePlugins();
for (QQuickStylePlugin *stylePlugin : stylePlugins)
stylePlugin->initializeTheme(theme);
qDeleteAll(stylePlugins);
}
Responsible for setting the font and palette settings that were specified in the
qtquickcontrols2.conf file.
QList<QQuickStylePlugin *> QtQuickControls2Plugin::loadStylePlugins()
{
QList<QQuickStylePlugin *> stylePlugins;
QFileInfo fileInfo = QQmlFile::urlToLocalFileOrQrc(resolvedUrl(QStringLiteral("qmldir")));
if (fileInfo.exists() && fileInfo.path() != QQmlFile::urlToLocalFileOrQrc(baseUrl())) {
QFile file(fileInfo.filePath());
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QQmlDirParser parser;
parser.parse(QString::fromUtf8(file.readAll()));
if (!parser.hasError()) {
#ifdef QT_STATIC
const auto plugins = QPluginLoader::staticInstances();
for (QObject *instance : plugins) {
QQuickStylePlugin *stylePlugin = qobject_cast<QQuickStylePlugin *>(instance);
if (!stylePlugin || !parser.classNames().contains(QLatin1String(instance->metaObject()->className())))
continue;
stylePlugins += stylePlugin;
}
#elif QT_CONFIG(library)
QPluginLoader loader;
const auto plugins = parser.plugins();
for (const QQmlDirParser::Plugin &plugin : plugins) {
QDir dir = fileInfo.dir();
if (!plugin.path.isEmpty() && !dir.cd(plugin.path))
continue;
QString filePath = dir.filePath(plugin.name);
#if defined(Q_OS_MACOS) && defined(QT_DEBUG)
// Avoid mismatching plugins on macOS so that we don't end up loading both debug and
// release versions of the same Qt libraries (due to the plugin's dependencies).
filePath += QStringLiteral("_debug");
#endif // Q_OS_MACOS && QT_DEBUG
#if defined(Q_OS_WIN) && defined(QT_DEBUG)
// Debug versions of plugins have a "d" prefix on Windows.
filePath += QLatin1Char('d');
#endif // Q_OS_WIN && QT_DEBUG
loader.setFileName(filePath);
QQuickStylePlugin *stylePlugin = qobject_cast<QQuickStylePlugin *>(loader.instance());
if (stylePlugin)
stylePlugins += stylePlugin;
}
#endif
}
}
}
return stylePlugins;
}
Style-specific settings (e.g. Variant=Dense) are read in the constructor of the
appropriate style plugin (e.g. QtQuickControls2MaterialStylePlugin).
Implicit style-specific font and palette values are assigned in the relevant theme
(e.g. QQuickMaterialTheme).
*/
QQuickTheme *QtQuickControls2Plugin::createTheme(const QString &name)
{
qCDebug(lcQtQuickControlsStylePlugin) << "creating QQuickTheme instance to be initialized by style-specific theme of" << name;
QQuickTheme *theme = new QQuickTheme;
#if QT_CONFIG(settings)
QQuickThemePrivate *p = QQuickThemePrivate::get(theme);

View File

@ -1,7 +1,7 @@
module QtQuick.Controls.Universal
plugin qtquickcontrols2universalstyleplugin
classname QtQuickControls2UniversalStylePlugin
depends QtQuick.Controls 2.5
import QtQuick.Controls.Default auto
# QtQuick.Controls 2.0 (originally introduced in Qt 5.7)
ApplicationWindow 2.0 ApplicationWindow.qml

View File

@ -34,11 +34,12 @@
**
****************************************************************************/
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include "qquickuniversalstyle_p.h"
#include "qquickuniversaltheme_p.h"
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p.h>
QT_BEGIN_NAMESPACE
class QtQuickControls2UniversalStylePlugin : public QQuickStylePlugin
@ -50,12 +51,14 @@ public:
QtQuickControls2UniversalStylePlugin(QObject *parent = nullptr);
QString name() const override;
void initializeTheme(QQuickTheme *theme) override;
void registerTypes(const char *uri) override;
QQuickUniversalTheme theme;
};
QtQuickControls2UniversalStylePlugin::QtQuickControls2UniversalStylePlugin(QObject *parent) : QQuickStylePlugin(parent)
{
QQuickUniversalStyle::initGlobals();
}
QString QtQuickControls2UniversalStylePlugin::name() const
@ -63,9 +66,12 @@ QString QtQuickControls2UniversalStylePlugin::name() const
return QStringLiteral("Universal");
}
void QtQuickControls2UniversalStylePlugin::initializeTheme(QQuickTheme *theme)
void QtQuickControls2UniversalStylePlugin::registerTypes(const char *uri)
{
QQuickUniversalTheme::initialize(theme);
QQuickStylePlugin::registerTypes(uri);
QQuickUniversalStyle::initGlobals();
theme.initialize(QQuickTheme::instance());
}
QT_END_NAMESPACE

View File

@ -92,54 +92,23 @@ Q_LOGGING_CATEGORY(lcQtQuickControlsStyle, "qt.quick.controls.style")
Qt Quick Controls. It is not possible to change the style after the QML
types have been registered.
The style can also be specified as a path to a custom style, such as
\c ":/mystyle". See \l {Creating a Custom Style} for more details about
building custom styles. Custom styles do not need to implement all controls.
By default, the styling system uses the \l {Default style} as a fallback
for controls that a custom style does not provide. It is possible to
specify a different fallback style to customize or extend one of the
built-in styles.
To create your own custom style, see \l {Creating a Custom Style}. Custom
styles do not need to implement all controls. By default, the styling
system uses the \l {Default style} as a fallback for controls that a custom
style does not provide. It is possible to specify a different fallback
style to customize or extend one of the built-in styles.
\code
QQuickStyle::setStyle(":/mystyle");
QQuickStyle::setStyle("MyStyle");
QQuickStyle::setFallbackStyle("Material");
\endcode
\sa {Styling Qt Quick Controls}
*/
static QStringList envPathList(const QByteArray &var)
{
QStringList paths;
if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) {
const QByteArray value = qgetenv(var);
paths += QString::fromLocal8Bit(value).split(QDir::listSeparator(), Qt::SkipEmptyParts);
}
return paths;
}
static QStringList defaultImportPathList()
{
QStringList importPaths;
importPaths.reserve(3);
#ifdef Q_OS_ANDROID
// androiddeployqt puts the QML files inside a resource file and they are not
// showing up in the Qml2ImportsPath as a result
importPaths += QStringLiteral(":/android_rcc_bundle/qml");
#else
# ifndef QT_STATIC
importPaths += QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
# endif
#endif
importPaths += envPathList("QML2_IMPORT_PATH");
importPaths += QStringLiteral(":/qt-project.org/imports");
importPaths += QCoreApplication::applicationDirPath();
return importPaths;
}
struct QQuickStyleSpec
{
QQuickStyleSpec() : custom(false), resolved(false) { }
QQuickStyleSpec() { }
QString name()
{
@ -160,6 +129,13 @@ struct QQuickStyleSpec
void setStyle(const QString &s)
{
qCDebug(lcQtQuickControlsStyle) << "style" << s << "set on QQuickStyleSpec";
if (s.contains(QLatin1Char('/'))) {
qWarning() << "Style names must not contain paths; see the \"Definition of a Style\" documentation for more information";
return;
}
qCDebug(lcQtQuickControlsStyle) << "clearing resolved flag and resolving";
style = s;
resolved = false;
resolve();
@ -171,27 +147,9 @@ struct QQuickStyleSpec
fallbackMethod = method;
}
static QString findStyle(const QString &path, const QString &name)
void resolve()
{
QDir dir(path);
if (!dir.exists())
return QString();
if (name.isEmpty())
return dir.absolutePath() + QLatin1Char('/');
const QStringList entries = dir.entryList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString &entry : entries) {
if (entry.compare(name, Qt::CaseInsensitive) == 0)
return dir.absoluteFilePath(entry);
}
return QString();
}
void resolve(const QUrl &baseUrl = QUrl())
{
qCDebug(lcQtQuickControlsStyle) << "resolving style with baseUrl" << baseUrl;
qCDebug(lcQtQuickControlsStyle) << "resolving style";
if (style.isEmpty())
style = QGuiApplicationPrivate::styleOverride;
@ -211,53 +169,33 @@ struct QQuickStyleSpec
}
#endif
// resolve a path relative to the config
QString configPath = QFileInfo(resolveConfigFilePath()).path();
QString stylePath = findStyle(configPath, style);
if (!stylePath.isEmpty()) {
style = stylePath;
resolved = true;
auto builtInStyleList = QQuickStylePrivate::builtInStyles();
if (!fallbackStyle.isEmpty() && !builtInStyleList.contains(fallbackStyle)) {
qWarning().nospace().noquote() << fallbackMethod << ": the specified fallback style \"" <<
fallbackStyle << "\" is not one of the built-in Qt Quick Controls 2 styles";
fallbackStyle.clear();
}
custom = style.contains(QLatin1Char('/'));
// Find the config file.
resolveConfigFilePath();
if (baseUrl.isValid()) {
QString path = QQmlFile::urlToLocalFileOrQrc(baseUrl);
QString stylePath = findStyle(path, style);
if (!stylePath.isEmpty()) {
style = stylePath;
resolved = true;
}
}
custom = !builtInStyleList.contains(QQuickStylePrivate::effectiveStyleName(style));
if (QGuiApplication::instance()) {
if (!custom) {
const QStringList stylePaths = QQuickStylePrivate::stylePaths();
for (const QString &path : stylePaths) {
QString stylePath = findStyle(path, style);
if (!stylePath.isEmpty()) {
custom = !stylePath.startsWith(QQmlFile::urlToLocalFileOrQrc(baseUrl));
style = stylePath;
resolved = true;
break;
}
}
}
resolved = true;
}
resolved = true;
qCDebug(lcQtQuickControlsStyle).nospace() << "done resolving:"
<< "\n style=" << style
<< "\n custom=" << custom
<< "\n resolved=" << resolved
<< "\n style=" << style
<< "\n fallbackStyle=" << fallbackStyle
<< "\n fallbackMethod=" << fallbackMethod
<< "\n configFilePath=" << configFilePath
<< "\n customStylePaths=" << customStylePaths;
<< "\n configFilePath=" << configFilePath;
}
void reset()
{
qCDebug(lcQtQuickControlsStyle) << "resetting values to their defaults";
custom = false;
resolved = false;
style.clear();
@ -281,10 +219,10 @@ struct QQuickStyleSpec
}
// Is this a custom style defined by the user and not "built-in" style?
bool custom;
// Did we manage to find a valid style path?
bool resolved;
// The full path to the style.
bool custom = false;
// Have we resolved the style yet?
bool resolved = false;
// The name of the style.
QString style;
// The built-in style to use if the requested style cannot be found.
QString fallbackStyle;
@ -292,102 +230,13 @@ struct QQuickStyleSpec
QByteArray fallbackMethod;
// The path to the qtquickcontrols2.conf file.
QString configFilePath;
// An extra list of directories where we search for available styles before any other directories.
QStringList customStylePaths;
};
Q_GLOBAL_STATIC(QQuickStyleSpec, styleSpec)
static QStringList parseStylePathsWithColon(const QString &var)
QString QQuickStylePrivate::effectiveStyleName(const QString &styleName)
{
QStringList paths;
const QChar colon = QLatin1Char(':');
int currentIndex = 0;
do {
int nextColonIndex = -1;
QString path;
if (var.at(currentIndex) == colon) {
// This is either a list separator, or a qrc path.
if (var.at(currentIndex + 1) == colon) {
// It's a double colon (list separator followed by qrc path);
// find the end of the path.
nextColonIndex = var.indexOf(colon, currentIndex + 2);
path = var.mid(currentIndex + 1,
nextColonIndex == -1 ? -1 : nextColonIndex - currentIndex - 1);
} else {
// It's a single colon.
nextColonIndex = var.indexOf(colon, currentIndex + 1);
if (currentIndex == 0) {
// If we're at the start of the string, then it's a qrc path.
path = var.mid(currentIndex,
nextColonIndex == -1 ? -1 : nextColonIndex - currentIndex);
} else {
// Otherwise, it's a separator.
path = var.mid(currentIndex + 1,
nextColonIndex == -1 ? -1 : nextColonIndex - currentIndex - 1);
}
}
} else {
// It's a file path.
nextColonIndex = var.indexOf(colon, currentIndex);
path = var.mid(currentIndex,
nextColonIndex == -1 ? -1 : nextColonIndex - currentIndex);
}
paths += path;
currentIndex = nextColonIndex;
// Keep going until we can't find any more colons,
// or we're at the last character.
} while (currentIndex != -1 && currentIndex < var.size() - 1);
return paths;
}
QStringList QQuickStylePrivate::stylePaths(bool resolve)
{
// user-requested style path
QStringList paths;
if (resolve) {
QString path = styleSpec()->path();
if (path.endsWith(QLatin1Char('/')))
path.chop(1);
if (!path.isEmpty())
paths += path;
}
if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE_PATH"))) {
const QString value = QString::fromLocal8Bit(qgetenv("QT_QUICK_CONTROLS_STYLE_PATH"));
const QChar listSeparator = QDir::listSeparator();
if (listSeparator == QLatin1Char(':')) {
// Split manually to avoid breaking paths on systems where : is the list separator,
// since it's also used for qrc paths.
paths += parseStylePathsWithColon(value);
} else {
// Fast/simpler path for systems where something other than : is used as
// the list separator (such as ';').
const QStringList customPaths = value.split(listSeparator, Qt::SkipEmptyParts);
paths += customPaths;
}
}
// system/custom style paths
paths += styleSpec()->customStylePaths;
paths += envPathList("QT_QUICK_CONTROLS_STYLE_PATH");
// built-in import paths
const QString targetPath = QStringLiteral("QtQuick/Controls");
const QStringList importPaths = defaultImportPathList();
for (const QString &importPath : importPaths) {
QDir dir(importPath);
if (dir.cd(targetPath))
paths += dir.absolutePath();
}
paths.removeDuplicates();
return paths;
return !styleName.isEmpty() ? styleName : QLatin1String("Default");
}
QString QQuickStylePrivate::fallbackStyle()
@ -400,27 +249,15 @@ bool QQuickStylePrivate::isCustomStyle()
return styleSpec()->custom;
}
void QQuickStylePrivate::init(const QUrl &baseUrl)
bool QQuickStylePrivate::isResolved()
{
return styleSpec()->resolved;
}
void QQuickStylePrivate::init()
{
QQuickStyleSpec *spec = styleSpec();
spec->resolve(baseUrl);
if (!spec->fallbackStyle.isEmpty()) {
QString fallbackStyle;
const QStringList stylePaths = QQuickStylePrivate::stylePaths();
for (const QString &path : stylePaths) {
fallbackStyle = spec->findStyle(path, spec->fallbackStyle);
if (!fallbackStyle.isEmpty())
break;
}
if (fallbackStyle.isEmpty()) {
if (spec->fallbackStyle.compare(QStringLiteral("Default")) != 0) {
qWarning() << "ERROR: unable to locate fallback style" << spec->fallbackStyle;
qInfo().nospace().noquote() << spec->fallbackMethod << ": the fallback style must be the name of one of the built-in Qt Quick Controls 2 styles.";
}
spec->fallbackStyle.clear();
}
}
spec->resolve();
}
void QQuickStylePrivate::reset()
@ -541,6 +378,12 @@ bool QQuickStylePrivate::isDarkSystemTheme()
return dark;
}
QStringList QQuickStylePrivate::builtInStyles()
{
return { QLatin1String("Default"), QLatin1String("Fusion"),
QLatin1String("Imagine"), QLatin1String("Material"), QLatin1String("Universal") };
}
/*!
Returns the name of the application style.
@ -553,19 +396,6 @@ QString QQuickStyle::name()
return styleSpec()->name();
}
/*!
Returns the path of an overridden application style, or an empty
string if the style is one of the built-in Qt Quick Controls 2 styles.
\note The application style can be specified by passing a \c -style command
line argument. Therefore \c path() may not return a fully resolved
value if called before constructing a QGuiApplication.
*/
QString QQuickStyle::path()
{
return styleSpec()->path();
}
/*!
Sets the application style to \a style.
@ -612,88 +442,4 @@ void QQuickStyle::setFallbackStyle(const QString &style)
styleSpec()->setFallbackStyle(style, "QQuickStyle::setFallbackStyle()");
}
/*!
\since 5.9
Returns the names of the available styles.
\note The method must be called \b after creating an instance of QGuiApplication.
\sa stylePathList(), addStylePath()
*/
QStringList QQuickStyle::availableStyles()
{
QStringList styles;
if (!QGuiApplication::instance()) {
qWarning() << "ERROR: QQuickStyle::availableStyles() must be called after creating an instance of QGuiApplication.";
return styles;
}
const QStringList stylePaths = QQuickStylePrivate::stylePaths();
for (const QString &path : stylePaths) {
const QList<QFileInfo> entries = QDir(path).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &entry : entries) {
const QString name = entry.fileName();
if (!name.endsWith(QLatin1String(".dSYM")) && name != QLatin1String("designer"))
styles += name;
}
}
styles.prepend(QStringLiteral("Default"));
styles.removeDuplicates();
return styles;
}
/*!
\since 5.12
Returns the list of directories where Qt Quick Controls 2 searches for available styles.
By default, the list contains paths specified in the \c QT_QUICK_CONTROLS_STYLE_PATH
environment variable, and any existing \c QtQuick/Controls sub-directories in
\l QQmlEngine::importPathList().
\sa addStylePath(), availableStyles()
*/
QStringList QQuickStyle::stylePathList()
{
return QQuickStylePrivate::stylePaths();
}
/*!
\since 5.12
Adds \a path as a directory where Qt Quick Controls 2 searches for available styles.
The \a path may be any local filesystem directory or \l {The Qt Resource System}{Qt Resource} directory.
For example, the following paths are all valid:
\list
\li \c {/path/to/styles/}
\li \c {file:///path/to/styles/}
\li \c {:/path/to/styles/}
\li \c {qrc:/path/to/styles/})
\endlist
The \a path will be converted into \l {QDir::canonicalPath}{canonical form} before it is added to
the style path list.
The newly added \a path will be first in the stylePathList().
\sa stylePathList(), availableStyles()
*/
void QQuickStyle::addStylePath(const QString &path)
{
if (path.isEmpty())
return;
const QUrl url = QUrl(path);
if (url.isRelative() || url.scheme() == QLatin1String("file")
|| (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
styleSpec()->customStylePaths.prepend(QDir(path).canonicalPath());
} else if (url.scheme() == QLatin1String("qrc")) {
styleSpec()->customStylePaths.prepend(QLatin1Char(':') + url.path());
} else {
styleSpec()->customStylePaths.prepend(path);
}
}
QT_END_NAMESPACE

View File

@ -47,12 +47,8 @@ class Q_QUICKCONTROLS2_EXPORT QQuickStyle
{
public:
static QString name();
static QString path();
static void setStyle(const QString &style);
static void setFallbackStyle(const QString &style);
static QStringList availableStyles();
static QStringList stylePathList();
static void addStylePath(const QString &path);
};
QT_END_NAMESPACE

View File

@ -48,7 +48,6 @@
// We mean it.
//
#include <QtCore/qurl.h>
#include <QtCore/qsharedpointer.h>
#include <QtQuickControls2/qtquickcontrols2global.h>
@ -59,16 +58,19 @@ class QSettings;
class Q_QUICKCONTROLS2_EXPORT QQuickStylePrivate
{
public:
static QStringList stylePaths(bool resolve = false);
static QString effectiveStyleName(const QString &styleName);
static QString fallbackStyle();
static bool isCustomStyle();
static void init(const QUrl &baseUrl);
static bool isResolved();
static bool exists();
static void init();
static void reset();
static QString configFilePath();
static QSharedPointer<QSettings> settings(const QString &group = QString());
static const QFont *readFont(const QSharedPointer<QSettings> &settings);
static const QPalette *readPalette(const QSharedPointer<QSettings> &settings);
static bool isDarkSystemTheme();
static QStringList builtInStyles();
};
QT_END_NAMESPACE

View File

@ -34,14 +34,20 @@
**
****************************************************************************/
#include "qquickstyle.h"
#include "qquickstyle_p.h"
#include "qquickstyleplugin_p.h"
#include <QtCore/qloggingcategory.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlfile.h>
#include <QtQuickTemplates2/private/qquicktheme_p_p.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcStylePlugin, "qt.quick.controls.styleplugin")
QQuickStylePlugin::QQuickStylePlugin(QObject *parent)
: QQmlExtensionPlugin(parent)
{
@ -56,4 +62,14 @@ QString QQuickStylePlugin::name() const
return QString();
}
void QQuickStylePlugin::registerTypes(const char *uri)
{
qCDebug(lcStylePlugin).nospace() << "registerTypes called with uri " << uri << "; plugin name is " << name();
}
void QQuickStylePlugin::unregisterTypes()
{
qCDebug(lcStylePlugin) << "unregisterTypes called; plugin name is" << name();
}
QT_END_NAMESPACE

View File

@ -64,9 +64,9 @@ public:
~QQuickStylePlugin();
virtual QString name() const;
virtual void initializeTheme(QQuickTheme *theme) = 0;
void registerTypes(const char *uri) override;
void unregisterTypes() override;
private:
Q_DISABLE_COPY(QQuickStylePlugin)

View File

@ -29,4 +29,5 @@ SUBDIRS += \
revisions \
sanity \
snippets \
styleimports \
translation

View File

@ -4,7 +4,7 @@ SOURCES += tst_customization.cpp
macos:CONFIG -= app_bundle
QT += core-private gui-private qml-private quick-private testlib quicktemplates2-private quickcontrols2
QT += core-private gui-private qml-private quick-private testlib quicktemplates2-private quickcontrols2 quickcontrols2-private
include (../shared/util.pri)

View File

@ -0,0 +1,59 @@
module empty
AbstractButton 6.0 AbstractButton.qml
Action 6.0 Action.qml
ActionGroup 6.0 ActionGroup.qml
ApplicationWindow 6.0 ApplicationWindow.qml
BusyIndicator 6.0 BusyIndicator.qml
Button 6.0 Button.qml
ButtonGroup 6.0 ButtonGroup.qml
CheckBox 6.0 CheckBox.qml
CheckDelegate 6.0 CheckDelegate.qml
ComboBox 6.0 ComboBox.qml
Container 6.0 Container.qml
Control 6.0 Control.qml
DelayButton 6.0 DelayButton.qml
Dial 6.0 Dial.qml
Dialog 6.0 Dialog.qml
DialogButtonBox 6.0 DialogButtonBox.qml
Drawer 6.0 Drawer.qml
Frame 6.0 Frame.qml
GroupBox 6.0 GroupBox.qml
HorizontalHeaderView 6.0 HorizontalHeaderView.qml
ItemDelegate 6.0 ItemDelegate.qml
Label 6.0 Label.qml
Menu 6.0 Menu.qml
MenuBar 6.0 MenuBar.qml
MenuBarItem 6.0 MenuBarItem.qml
MenuItem 6.0 MenuItem.qml
MenuSeparator 6.0 MenuSeparator.qml
Page 6.0 Page.qml
PageIndicator 6.0 PageIndicator.qml
Pane 6.0 Pane.qml
Popup 6.0 Popup.qml
ProgressBar 6.0 ProgressBar.qml
RadioButton 6.0 RadioButton.qml
RadioDelegate 6.0 RadioDelegate.qml
RangeSlider 6.0 RangeSlider.qml
RoundButton 6.0 RoundButton.qml
ScrollBar 6.0 ScrollBar.qml
ScrollIndicator 6.0 ScrollIndicator.qml
ScrollView 6.0 ScrollView.qml
Slider 6.0 Slider.qml
SpinBox 6.0 SpinBox.qml
SplitView 6.0 SplitView.qml
StackView 6.0 StackView.qml
SwipeDelegate 6.0 SwipeDelegate.qml
SwipeView 6.0 SwipeView.qml
Switch 6.0 Switch.qml
SwitchDelegate 6.0 SwitchDelegate.qml
TabBar 6.0 TabBar.qml
TabButton 6.0 TabButton.qml
TextArea 6.0 TextArea.qml
TextField 6.0 TextField.qml
ToolBar 6.0 ToolBar.qml
ToolButton 6.0 ToolButton.qml
ToolSeparator 6.0 ToolSeparator.qml
ToolTip 6.0 ToolTip.qml
Tumbler 6.0 Tumbler.qml
VerticalHeaderView 6.0 VerticalHeaderView.qml

View File

@ -0,0 +1,59 @@
module identified
AbstractButton 6.0 AbstractButton.qml
Action 6.0 Action.qml
ActionGroup 6.0 ActionGroup.qml
ApplicationWindow 6.0 ApplicationWindow.qml
BusyIndicator 6.0 BusyIndicator.qml
Button 6.0 Button.qml
ButtonGroup 6.0 ButtonGroup.qml
CheckBox 6.0 CheckBox.qml
CheckDelegate 6.0 CheckDelegate.qml
ComboBox 6.0 ComboBox.qml
Container 6.0 Container.qml
Control 6.0 Control.qml
DelayButton 6.0 DelayButton.qml
Dial 6.0 Dial.qml
Dialog 6.0 Dialog.qml
DialogButtonBox 6.0 DialogButtonBox.qml
Drawer 6.0 Drawer.qml
Frame 6.0 Frame.qml
GroupBox 6.0 GroupBox.qml
HorizontalHeaderView 6.0 HorizontalHeaderView.qml
ItemDelegate 6.0 ItemDelegate.qml
Label 6.0 Label.qml
Menu 6.0 Menu.qml
MenuBar 6.0 MenuBar.qml
MenuBarItem 6.0 MenuBarItem.qml
MenuItem 6.0 MenuItem.qml
MenuSeparator 6.0 MenuSeparator.qml
Page 6.0 Page.qml
PageIndicator 6.0 PageIndicator.qml
Pane 6.0 Pane.qml
Popup 6.0 Popup.qml
ProgressBar 6.0 ProgressBar.qml
RadioButton 6.0 RadioButton.qml
RadioDelegate 6.0 RadioDelegate.qml
RangeSlider 6.0 RangeSlider.qml
RoundButton 6.0 RoundButton.qml
ScrollBar 6.0 ScrollBar.qml
ScrollIndicator 6.0 ScrollIndicator.qml
ScrollView 6.0 ScrollView.qml
Slider 6.0 Slider.qml
SpinBox 6.0 SpinBox.qml
SplitView 6.0 SplitView.qml
StackView 6.0 StackView.qml
SwipeDelegate 6.0 SwipeDelegate.qml
SwipeView 6.0 SwipeView.qml
Switch 6.0 Switch.qml
SwitchDelegate 6.0 SwitchDelegate.qml
TabBar 6.0 TabBar.qml
TabButton 6.0 TabButton.qml
TextArea 6.0 TextArea.qml
TextField 6.0 TextField.qml
ToolBar 6.0 ToolBar.qml
ToolButton 6.0 ToolButton.qml
ToolSeparator 6.0 ToolSeparator.qml
ToolTip 6.0 ToolTip.qml
Tumbler 6.0 Tumbler.qml
VerticalHeaderView 6.0 VerticalHeaderView.qml

View File

@ -0,0 +1,59 @@
module incomplete
AbstractButton 6.0 AbstractButton.qml
Action 6.0 Action.qml
ActionGroup 6.0 ActionGroup.qml
ApplicationWindow 6.0 ApplicationWindow.qml
BusyIndicator 6.0 BusyIndicator.qml
Button 6.0 Button.qml
ButtonGroup 6.0 ButtonGroup.qml
CheckBox 6.0 CheckBox.qml
CheckDelegate 6.0 CheckDelegate.qml
ComboBox 6.0 ComboBox.qml
Container 6.0 Container.qml
Control 6.0 Control.qml
DelayButton 6.0 DelayButton.qml
Dial 6.0 Dial.qml
Dialog 6.0 Dialog.qml
DialogButtonBox 6.0 DialogButtonBox.qml
Drawer 6.0 Drawer.qml
Frame 6.0 Frame.qml
GroupBox 6.0 GroupBox.qml
HorizontalHeaderView 6.0 HorizontalHeaderView.qml
ItemDelegate 6.0 ItemDelegate.qml
Label 6.0 Label.qml
Menu 6.0 Menu.qml
MenuBar 6.0 MenuBar.qml
MenuBarItem 6.0 MenuBarItem.qml
MenuItem 6.0 MenuItem.qml
MenuSeparator 6.0 MenuSeparator.qml
Page 6.0 Page.qml
PageIndicator 6.0 PageIndicator.qml
Pane 6.0 Pane.qml
Popup 6.0 Popup.qml
ProgressBar 6.0 ProgressBar.qml
RadioButton 6.0 RadioButton.qml
RadioDelegate 6.0 RadioDelegate.qml
RangeSlider 6.0 RangeSlider.qml
RoundButton 6.0 RoundButton.qml
ScrollBar 6.0 ScrollBar.qml
ScrollIndicator 6.0 ScrollIndicator.qml
ScrollView 6.0 ScrollView.qml
Slider 6.0 Slider.qml
SpinBox 6.0 SpinBox.qml
SplitView 6.0 SplitView.qml
StackView 6.0 StackView.qml
SwipeDelegate 6.0 SwipeDelegate.qml
SwipeView 6.0 SwipeView.qml
Switch 6.0 Switch.qml
SwitchDelegate 6.0 SwitchDelegate.qml
TabBar 6.0 TabBar.qml
TabButton 6.0 TabButton.qml
TextArea 6.0 TextArea.qml
TextField 6.0 TextField.qml
ToolBar 6.0 ToolBar.qml
ToolButton 6.0 ToolButton.qml
ToolSeparator 6.0 ToolSeparator.qml
ToolTip 6.0 ToolTip.qml
Tumbler 6.0 Tumbler.qml
VerticalHeaderView 6.0 VerticalHeaderView.qml

View File

@ -0,0 +1,59 @@
module override
AbstractButton 6.0 AbstractButton.qml
Action 6.0 Action.qml
ActionGroup 6.0 ActionGroup.qml
ApplicationWindow 6.0 ApplicationWindow.qml
BusyIndicator 6.0 BusyIndicator.qml
Button 6.0 Button.qml
ButtonGroup 6.0 ButtonGroup.qml
CheckBox 6.0 CheckBox.qml
CheckDelegate 6.0 CheckDelegate.qml
ComboBox 6.0 ComboBox.qml
Container 6.0 Container.qml
Control 6.0 Control.qml
DelayButton 6.0 DelayButton.qml
Dial 6.0 Dial.qml
Dialog 6.0 Dialog.qml
DialogButtonBox 6.0 DialogButtonBox.qml
Drawer 6.0 Drawer.qml
Frame 6.0 Frame.qml
GroupBox 6.0 GroupBox.qml
HorizontalHeaderView 6.0 HorizontalHeaderView.qml
ItemDelegate 6.0 ItemDelegate.qml
Label 6.0 Label.qml
Menu 6.0 Menu.qml
MenuBar 6.0 MenuBar.qml
MenuBarItem 6.0 MenuBarItem.qml
MenuItem 6.0 MenuItem.qml
MenuSeparator 6.0 MenuSeparator.qml
Page 6.0 Page.qml
PageIndicator 6.0 PageIndicator.qml
Pane 6.0 Pane.qml
Popup 6.0 Popup.qml
ProgressBar 6.0 ProgressBar.qml
RadioButton 6.0 RadioButton.qml
RadioDelegate 6.0 RadioDelegate.qml
RangeSlider 6.0 RangeSlider.qml
RoundButton 6.0 RoundButton.qml
ScrollBar 6.0 ScrollBar.qml
ScrollIndicator 6.0 ScrollIndicator.qml
ScrollView 6.0 ScrollView.qml
Slider 6.0 Slider.qml
SpinBox 6.0 SpinBox.qml
SplitView 6.0 SplitView.qml
StackView 6.0 StackView.qml
SwipeDelegate 6.0 SwipeDelegate.qml
SwipeView 6.0 SwipeView.qml
Switch 6.0 Switch.qml
SwitchDelegate 6.0 SwitchDelegate.qml
TabBar 6.0 TabBar.qml
TabButton 6.0 TabButton.qml
TextArea 6.0 TextArea.qml
TextField 6.0 TextField.qml
ToolBar 6.0 ToolBar.qml
ToolButton 6.0 ToolButton.qml
ToolSeparator 6.0 ToolSeparator.qml
ToolTip 6.0 ToolTip.qml
Tumbler 6.0 Tumbler.qml
VerticalHeaderView 6.0 VerticalHeaderView.qml

View File

@ -0,0 +1,59 @@
module simple
AbstractButton 6.0 AbstractButton.qml
Action 6.0 Action.qml
ActionGroup 6.0 ActionGroup.qml
ApplicationWindow 6.0 ApplicationWindow.qml
BusyIndicator 6.0 BusyIndicator.qml
Button 6.0 Button.qml
ButtonGroup 6.0 ButtonGroup.qml
CheckBox 6.0 CheckBox.qml
CheckDelegate 6.0 CheckDelegate.qml
ComboBox 6.0 ComboBox.qml
Container 6.0 Container.qml
Control 6.0 Control.qml
DelayButton 6.0 DelayButton.qml
Dial 6.0 Dial.qml
Dialog 6.0 Dialog.qml
DialogButtonBox 6.0 DialogButtonBox.qml
Drawer 6.0 Drawer.qml
Frame 6.0 Frame.qml
GroupBox 6.0 GroupBox.qml
HorizontalHeaderView 6.0 HorizontalHeaderView.qml
ItemDelegate 6.0 ItemDelegate.qml
Label 6.0 Label.qml
Menu 6.0 Menu.qml
MenuBar 6.0 MenuBar.qml
MenuBarItem 6.0 MenuBarItem.qml
MenuItem 6.0 MenuItem.qml
MenuSeparator 6.0 MenuSeparator.qml
Page 6.0 Page.qml
PageIndicator 6.0 PageIndicator.qml
Pane 6.0 Pane.qml
Popup 6.0 Popup.qml
ProgressBar 6.0 ProgressBar.qml
RadioButton 6.0 RadioButton.qml
RadioDelegate 6.0 RadioDelegate.qml
RangeSlider 6.0 RangeSlider.qml
RoundButton 6.0 RoundButton.qml
ScrollBar 6.0 ScrollBar.qml
ScrollIndicator 6.0 ScrollIndicator.qml
ScrollView 6.0 ScrollView.qml
Slider 6.0 Slider.qml
SpinBox 6.0 SpinBox.qml
SplitView 6.0 SplitView.qml
StackView 6.0 StackView.qml
SwipeDelegate 6.0 SwipeDelegate.qml
SwipeView 6.0 SwipeView.qml
Switch 6.0 Switch.qml
SwitchDelegate 6.0 SwitchDelegate.qml
TabBar 6.0 TabBar.qml
TabButton 6.0 TabButton.qml
TextArea 6.0 TextArea.qml
TextField 6.0 TextField.qml
ToolBar 6.0 ToolBar.qml
ToolButton 6.0 ToolButton.qml
ToolSeparator 6.0 ToolSeparator.qml
ToolTip 6.0 ToolTip.qml
Tumbler 6.0 Tumbler.qml
VerticalHeaderView 6.0 VerticalHeaderView.qml

View File

@ -42,6 +42,7 @@
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickwindow.h>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickControls2/private/qquickstyle_p.h>
#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
#include "../shared/visualtestutil.h"
@ -227,6 +228,7 @@ void tst_customization::cleanupTestCase()
void tst_customization::init()
{
engine = new QQmlEngine(this);
engine->addImportPath(testFile("styles"));
qtHookData[QHooks::AddQObject] = reinterpret_cast<quintptr>(&qt_addQObject);
qtHookData[QHooks::RemoveQObject] = reinterpret_cast<quintptr>(&qt_removeQObject);
@ -296,7 +298,7 @@ void tst_customization::creation()
QFETCH(QString, type);
QFETCH(QStringList, delegates);
QQuickStyle::setStyle(testFile("styles/" + style));
QQuickStyle::setStyle(style);
QString error;
QScopedPointer<QObject> control(createControl(type, "", &error));
@ -363,7 +365,7 @@ void tst_customization::override_data()
#ifndef Q_OS_MACOS // QTBUG-65671
// test that the built-in styles don't have undesired IDs in their delegates
const QStringList styles = QStringList() << "Default" << "Fusion" << "Material" << "Universal"; // ### TODO: QQuickStyle::availableStyles();
const QStringList styles = QQuickStylePrivate::builtInStyles();
for (const QString &style : styles) {
for (const ControlInfo &control : ControlInfos)
QTest::newRow(qPrintable(style + ":" + control.type)) << style << control.type << control.delegates << "" << false;
@ -380,11 +382,7 @@ void tst_customization::override()
QFETCH(QString, nonDeferred);
QFETCH(bool, identify);
const QString testStyle = testFile("styles/" + style);
if (QDir(testStyle).exists())
QQuickStyle::setStyle(testStyle);
else
QQuickStyle::setStyle(style);
QQuickStyle::setStyle(style);
QString qml;
qml += QString("objectName: '%1-%2-override'; ").arg(type.toLower()).arg(style);
@ -478,7 +476,7 @@ void tst_customization::override()
void tst_customization::comboPopup()
{
QQuickStyle::setStyle(testFile("styles/simple"));
QQuickStyle::setStyle("simple");
{
// test that ComboBox::popup is created when accessed

View File

@ -1,2 +1,2 @@
import QtQuick.Templates as T
T.Button { }
T.Label {}

View File

@ -0,0 +1,2 @@
module CmdLineArgStyle
Control 1.0 Control.qml

View File

@ -1,2 +1,2 @@
import QtQuick.Templates as T
T.Button { }
T.Label {}

View File

@ -0,0 +1,2 @@
module Custom
Label 1.0 Label.qml

View File

@ -1,2 +1,2 @@
import QtQuick.Templates as T
T.Button { }
T.Label {}

View File

@ -0,0 +1,2 @@
module EnvVarFallbackStyle
Control 1.0 Control.qml

View File

@ -1,2 +1,2 @@
import QtQuick.Templates as T
T.Button { }
T.Label {}

View File

@ -0,0 +1,2 @@
module EnvVarStyle
Control 1.0 Control.qml

View File

@ -1,5 +1,5 @@
[Controls]
Style=:/Custom
Style=Custom
[Custom]
Font\PixelSize=3

View File

@ -11,18 +11,16 @@ include (../shared/util.pri)
TESTDATA = $$PWD/data/*
qrcStyles1.files = $$files(qrcStyles1/QrcStyle1/*.qml)
qrcStyles1.prefix = /
RESOURCES += qrcStyles1
OTHER_FILES += \
data/CmdLineArgStyle/Control.qml \
data/CmdLineArgStyle/qmldir \
data/EnvVarStyle/Control.qml \
data/EnvVarStyle/qmldir \
data/EnvVarFallbackStyle/Control.qml \
data/EnvVarFallbackStyle/qmldir
qrcStyles2.files = $$files(qrcStyles2/QrcStyle2/*.qml)
qrcStyles2.prefix = /
RESOURCES += qrcStyles2
qrcStyles3.files = $$files(qrcStyles3/QrcStyle3/*.qml)
qrcStyles3.prefix = /
RESOURCES += qrcStyles3
qrcStyles4.files = $$files(qrcStyles4/QrcStyle4/*.qml)
qrcStyles4.prefix = /
RESOURCES += qrcStyles4
custom.files = \
data/Custom/Label.qml \
data/Custom/qmldir
custom.prefix = /
RESOURCES += custom

View File

@ -57,11 +57,6 @@ private slots:
void configurationFile();
void commandLineArgument();
void environmentVariables();
void availableStyles();
void qrcStylePaths_data();
void qrcStylePaths();
void qrcInQtQuickControlsStylePathEnvVar_data();
void qrcInQtQuickControlsStylePathEnvVar();
private:
void loadControls();
@ -74,7 +69,6 @@ void tst_QQuickStyle::cleanup()
QGuiApplicationPrivate::styleOverride.clear();
qunsetenv("QT_QUICK_CONTROLS_STYLE");
qunsetenv("QT_QUICK_CONTROLS_STYLE_PATH");
qunsetenv("QT_QUICK_CONTROLS_FALLBACK_STYLE");
qunsetenv("QT_QUICK_CONTROLS_CONF");
}
@ -82,6 +76,7 @@ void tst_QQuickStyle::cleanup()
void tst_QQuickStyle::loadControls()
{
QQmlEngine engine;
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0; import QtQuick.Controls 2.1; Control { }", QUrl());
@ -97,11 +92,9 @@ void tst_QQuickStyle::unloadControls()
void tst_QQuickStyle::lookup()
{
QVERIFY(QQuickStyle::name().isEmpty());
QVERIFY(!QQuickStyle::path().isEmpty());
QQuickStyle::setStyle("material");
QQuickStyle::setStyle("Material");
QCOMPARE(QQuickStyle::name(), QString("Material"));
QVERIFY(!QQuickStyle::path().isEmpty());
loadControls();
@ -110,7 +103,6 @@ void tst_QQuickStyle::lookup()
QCOMPARE(QQuickTheme::instance()->font(QQuickTheme::TextArea).pixelSize(), 16);
QCOMPARE(QQuickStyle::name(), QString("Material"));
QVERIFY(!QQuickStyle::path().isEmpty());
}
void tst_QQuickStyle::configurationFile_data()
@ -137,6 +129,7 @@ void tst_QQuickStyle::configurationFile()
// Load a control. The import causes the configuration file to be read.
QQmlEngine engine;
engine.addImportPath(":/data");
QQmlComponent labelComponent(&engine);
labelComponent.setData("import QtQuick 2.0; import QtQuick.Controls 2.12; Label {}", QUrl());
@ -144,8 +137,6 @@ void tst_QQuickStyle::configurationFile()
QVERIFY2(!object.isNull(), qPrintable(labelComponent.errorString()));
QCOMPARE(QQuickStyle::name(), expectedStyle);
if (!expectedPath.isEmpty())
QCOMPARE(QQuickStyle::path(), expectedPath);
// Test that fonts and palettes specified in configuration files are respected.
QQuickLabel *label = qobject_cast<QQuickLabel *>(object.data());
@ -169,179 +160,10 @@ void tst_QQuickStyle::environmentVariables()
{
qputenv("QT_QUICK_CONTROLS_STYLE", "EnvVarStyle");
qputenv("QT_QUICK_CONTROLS_FALLBACK_STYLE", "EnvVarFallbackStyle");
QTest::ignoreMessage(QtWarningMsg, "QT_QUICK_CONTROLS_FALLBACK_STYLE: the specified fallback style" \
" \"EnvVarFallbackStyle\" is not one of the built-in Qt Quick Controls 2 styles");
QCOMPARE(QQuickStyle::name(), QString("EnvVarStyle"));
QCOMPARE(QQuickStylePrivate::fallbackStyle(), QString("EnvVarFallbackStyle"));
}
void tst_QQuickStyle::availableStyles()
{
QString path = QFINDTESTDATA("data");
QVERIFY(!path.isEmpty());
QQuickStyle::addStylePath(path);
QStringList paths = QQuickStylePrivate::stylePaths();
#ifndef Q_OS_WIN
QVERIFY(paths.contains(path));
#else
QVERIFY(paths.contains(path, Qt::CaseInsensitive));
#endif
const QStringList styles = QQuickStyle::availableStyles();
QVERIFY(!styles.isEmpty());
QCOMPARE(styles.first(), QString("Default"));
QVERIFY(!styles.contains("designer"));
// QTBUG-60973
for (const QString &style : styles) {
QVERIFY2(!style.endsWith(".dSYM"), qPrintable(style));
}
}
void tst_QQuickStyle::qrcStylePaths_data()
{
QTest::addColumn<QString>("stylePath");
QTest::addColumn<QString>("expectedStyleName");
QTest::addRow("qrc:/qrcStyles1") << QString::fromLatin1("qrc:/qrcStyles1") << QString::fromLatin1("QrcStyle1");
QTest::addRow(":/qrcStyles2") << QString::fromLatin1(":/qrcStyles2") << QString::fromLatin1("QrcStyle2");
}
void tst_QQuickStyle::qrcStylePaths()
{
QFETCH(QString, stylePath);
QFETCH(QString, expectedStyleName);
QQuickStyle::addStylePath(stylePath);
const QStringList paths = QQuickStylePrivate::stylePaths();
QString expectedStylePath = stylePath;
if (expectedStylePath.startsWith(QLatin1String("qrc")))
expectedStylePath.remove(0, 3);
if (!paths.contains(expectedStylePath)) {
QString message;
QDebug stream(&message);
stream.nospace() << "QQuickStylePrivate::stylePaths() doesn't contain " << expectedStylePath << ":\n" << paths;
QFAIL(qPrintable(message));
}
const QStringList styles = QQuickStyle::availableStyles();
QVERIFY(!styles.isEmpty());
if (!styles.contains(expectedStyleName)) {
QString message;
QDebug stream(&message);
stream.nospace() << "QQuickStyle::availableStyles() doesn't contain " << expectedStyleName << ":\n" << styles;
QFAIL(qPrintable(message));
}
}
void tst_QQuickStyle::qrcInQtQuickControlsStylePathEnvVar_data()
{
QTest::addColumn<QString>("environmentVariable");
QTest::addColumn<QStringList>("expectedAvailableStyles");
const QChar listSeparator = QDir::listSeparator();
const QStringList defaultAvailableStyles = QQuickStyle::availableStyles();
{
QString environmentVariable;
QDebug stream(&environmentVariable);
// We use qrcStyles3 and qrcStyles4 in order to not conflict with
// qrcStylePaths(), since we currently have no way of clearing customStylePaths.
stream.noquote().nospace() << "/some/bogus/path/" << listSeparator
<< ":/qrcStyles3";
QStringList expectedAvailableStyles = defaultAvailableStyles;
// We need to keep the Default style at the start of the list,
// as that's what availableStyles() does.
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle3"));
QTest::addRow("%s", qPrintable(environmentVariable))
<< environmentVariable << expectedAvailableStyles;
}
{
QString environmentVariable;
QDebug stream(&environmentVariable);
stream.noquote().nospace() << ":/qrcStyles4" << listSeparator
<< "/some/bogus/path";
QStringList expectedAvailableStyles = defaultAvailableStyles;
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle4"));
QTest::addRow("%s", qPrintable(environmentVariable))
<< environmentVariable << expectedAvailableStyles;
}
{
QString environmentVariable;
QDebug stream(&environmentVariable);
stream.noquote().nospace() << ":/qrcStyles3" << listSeparator
<< ":/qrcStyles4" << listSeparator
<< QFINDTESTDATA("data/dummyStyles");
QStringList expectedAvailableStyles = defaultAvailableStyles;
expectedAvailableStyles.insert(1, QLatin1String("DummyStyle"));
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle4"));
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle3"));
QTest::addRow("%s", qPrintable(environmentVariable))
<< environmentVariable << expectedAvailableStyles;
}
{
QString environmentVariable;
QDebug stream(&environmentVariable);
stream.noquote().nospace() << QFINDTESTDATA("data/dummyStyles") << listSeparator
<< ":/qrcStyles3" << listSeparator
<< ":/qrcStyles4";
QStringList expectedAvailableStyles = defaultAvailableStyles;
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle4"));
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle3"));
expectedAvailableStyles.insert(1, QLatin1String("DummyStyle"));
QTest::addRow("%s", qPrintable(environmentVariable))
<< environmentVariable << expectedAvailableStyles;
}
{
QString environmentVariable;
QDebug stream(&environmentVariable);
// Same as the last row, except it adds a superfluous separator
// to ensure that it handles it gracefully rather than failing an assertion.
stream.noquote().nospace() << QFINDTESTDATA("data/dummyStyles") << listSeparator
<< ":/qrcStyles3" << listSeparator
<< ":/qrcStyles4" << listSeparator;
QStringList expectedAvailableStyles = defaultAvailableStyles;
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle4"));
expectedAvailableStyles.insert(1, QLatin1String("QrcStyle3"));
expectedAvailableStyles.insert(1, QLatin1String("DummyStyle"));
QTest::addRow("%s", qPrintable(environmentVariable))
<< environmentVariable << expectedAvailableStyles;
}
}
/*
Tests that qrc paths work with QT_QUICK_CONTROLS_STYLE_PATH.
*/
void tst_QQuickStyle::qrcInQtQuickControlsStylePathEnvVar()
{
QFETCH(QString, environmentVariable);
QFETCH(QStringList, expectedAvailableStyles);
qputenv("QT_QUICK_CONTROLS_STYLE_PATH", environmentVariable.toLocal8Bit());
const QStringList availableStyles = QQuickStyle::availableStyles();
if (availableStyles != expectedAvailableStyles) {
QString failureMessage;
QDebug stream(&failureMessage);
stream << "Mismatch in actual vs expected available styles:"
<< "\n Expected:" << expectedAvailableStyles
<< "\n Actual:" << availableStyles;
QFAIL(qPrintable(failureMessage));
}
QCOMPARE(QQuickStylePrivate::fallbackStyle(), QString());
}
QTEST_MAIN(tst_QQuickStyle)

View File

@ -2,9 +2,9 @@
*
[attachedObjects:material/SwitchDelegate.qml]
*
[ids:controls/HorizontalHeaderView.qml]
[ids:default/HorizontalHeaderView.qml]
*
[ids:controls/VerticalHeaderView.qml]
[ids:default/VerticalHeaderView.qml]
*
[ids:fusion/HorizontalHeaderView.qml]
*

View File

@ -1,7 +1,7 @@
TEMPLATE = app
TARGET = tst_sanity
QT += qml testlib core-private qml-private
QT += qml testlib core-private qml-private quickcontrols2
CONFIG += testcase
macos:CONFIG -= app_bundle

View File

@ -37,6 +37,9 @@
#include <QtTest>
#include <QtQml>
#include <QtCore/private/qhooks_p.h>
#include <QtCore/qpair.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qset.h>
#include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmljslexer_p.h>
#include <QtQml/private/qqmljsparser_p.h>
@ -81,8 +84,9 @@ private slots:
void ids_data();
private:
QQmlEngine engine;
QMap<QString, QString> files;
QMap<QString, QString> sourceQmlFiles;
QMap<QString, QString> installedQmlFiles;
QQuickStyleHelper styleHelper;
};
void tst_Sanity::init()
@ -160,19 +164,34 @@ void tst_Sanity::initTestCase()
const QStringList qmlTypeNames = QQmlMetaType::qmlTypeNames();
// Collect the files from each style in the source tree.
QDirIterator it(QQC2_IMPORT_PATH, QStringList() << "*.qml" << "*.js", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (qmlTypeNames.contains(QStringLiteral("QtQuick.Templates/") + info.baseName()))
files.insert(info.dir().dirName() + "/" + info.fileName(), info.filePath());
sourceQmlFiles.insert(info.dir().dirName() + "/" + info.fileName(), info.filePath());
}
// Then, collect the files from each installed style directory.
const QVector<QPair<QString, QString>> styleRelativePaths = {
{ "controls/default", "QtQuick/Controls/Default" },
{ "controls/fusion", "QtQuick/Controls/Fusion" },
{ "controls/material", "QtQuick/Controls/Material" },
{ "controls/universal", "QtQuick/Controls/Universal" },
};
for (const auto stylePathPair : styleRelativePaths) {
forEachControl(&engine, stylePathPair.first, stylePathPair.second, QStringList(),
[&](const QString &relativePath, const QUrl &absoluteUrl) {
installedQmlFiles.insert(relativePath, absoluteUrl.toLocalFile());
});
}
}
void tst_Sanity::jsFiles()
{
QMap<QString, QString>::const_iterator it;
for (it = files.constBegin(); it != files.constEnd(); ++it) {
for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) {
if (QFileInfo(it.value()).suffix() == QStringLiteral("js"))
QFAIL(qPrintable(it.value() + ": JS files are not allowed"));
}
@ -204,7 +223,7 @@ void tst_Sanity::functions_data()
QTest::addColumn<QString>("filePath");
QMap<QString, QString>::const_iterator it;
for (it = files.constBegin(); it != files.constEnd(); ++it)
for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it)
QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
}
@ -241,7 +260,7 @@ void tst_Sanity::signalHandlers_data()
QTest::addColumn<QString>("filePath");
QMap<QString, QString>::const_iterator it;
for (it = files.constBegin(); it != files.constEnd(); ++it)
for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it)
QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
}
@ -273,7 +292,7 @@ void tst_Sanity::anchors_data()
QTest::addColumn<QString>("filePath");
QMap<QString, QString>::const_iterator it;
for (it = files.constBegin(); it != files.constEnd(); ++it)
for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it)
QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
}
@ -338,37 +357,339 @@ void tst_Sanity::ids_data()
QTest::addColumn<QString>("filePath");
QMap<QString, QString>::const_iterator it;
for (it = files.constBegin(); it != files.constEnd(); ++it)
for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it)
QTest::newRow(qPrintable(it.key())) << it.key() << it.value();
}
typedef QPair<QString, QString> StringPair;
typedef QSet<StringPair> StringPairSet;
void tst_Sanity::attachedObjects()
{
QFETCH(QUrl, url);
QFETCH(QStringList, ignoredAttachedClassNames);
QFETCH(StringPairSet, expectedAttachedClassNames);
QQmlComponent component(&engine);
component.loadUrl(url);
const QString tagStr = QString::fromLatin1(QTest::currentDataTag());
QStringList styleAndFileName = tagStr.split('/');
QCOMPARE(styleAndFileName.size(), 2);
QString style = styleAndFileName.first();
if (styleHelper.updateStyle(style))
qt_qobjects->clear();
// Turn e.g. "Default/Button.qml" into "default/Button.qml".
QString styleRelativePath = tagStr;
styleRelativePath[0] = styleRelativePath.at(0).toLower();
// Get the absolute path to the installed file.
const QString controlFilePath = installedQmlFiles.value(styleRelativePath);
QQmlComponent component(styleHelper.engine.data());
component.loadUrl(QUrl::fromLocalFile(controlFilePath));
QSet<QString> classNames;
QScopedPointer<QObject> object(component.create());
QVERIFY2(object.data(), qPrintable(component.errorString()));
for (QObject *object : qAsConst(*qt_qobjects)) {
if (object->parent() == &engine)
// The goal of this test is to check that every unique attached type is used only once
// within each QML file. To track this, we remove expected pairs of class names as we
// encounter them, so that we know when something unexpected shows up.
StringPairSet remainingAttachedClassNames = expectedAttachedClassNames;
// Intentional copy, as QDebug creates a QObject-derived instance which would modify the list.
const auto qobjectsCopy = *qt_qobjects;
for (QObject *object : qobjectsCopy) {
const QString attachedClassName = object->metaObject()->className();
if (object->parent() == styleHelper.engine.data())
continue; // allow "global" instances
QString className = object->metaObject()->className();
if (className.endsWith("Attached") || className.endsWith("Style"))
QVERIFY2(!classNames.contains(className), qPrintable(QString("Multiple %1 instances").arg(className)));
classNames.insert(className);
// objects without parents would be singletons such as QQuickFusionStyle, and we're not interested in them.
if ((attachedClassName.endsWith("Attached") || attachedClassName.endsWith("Style")) && object->parent()) {
QString attacheeClassName = QString::fromLatin1(object->parent()->metaObject()->className());
const QString qmlTypeToken = QStringLiteral("QMLTYPE");
if (attacheeClassName.contains(qmlTypeToken)) {
// Remove the numbers from the class name, as they can change between runs; e.g.:
// Menu_QMLTYPE_222 => Menu_QMLTYPE
const int qmlTypeTokenIndex = attacheeClassName.indexOf(qmlTypeToken);
QVERIFY(qmlTypeTokenIndex != -1);
attacheeClassName = attacheeClassName.mid(0, attacheeClassName.indexOf(qmlTypeToken) + qmlTypeToken.size());
}
const StringPair classNamePair = { attachedClassName, attacheeClassName };
QVERIFY2(remainingAttachedClassNames.contains(classNamePair), qPrintable(QString::fromLatin1(
"Found an unexpected usage of an attached type: %1 is attached to %2. Either an incorrect usage was added, or the list of expected usages needs to be updated. Expected attached class names for %3 are:\n %4")
.arg(attachedClassName).arg(attacheeClassName).arg(tagStr).arg(QDebug::toString(expectedAttachedClassNames))));
remainingAttachedClassNames.remove(classNamePair);
}
}
QVERIFY2(remainingAttachedClassNames.isEmpty(), qPrintable(QString::fromLatin1(
"Not all expected attached class name usages were found; the following usages are missing:\n %1")
.arg(QDebug::toString(remainingAttachedClassNames))));
}
void tst_Sanity::attachedObjects_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls/default", "QtQuick/Controls/Default");
addTestRowForEachControl(&engine, "controls/fusion", "QtQuick/Controls/Fusion", QStringList() << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator");
addTestRowForEachControl(&engine, "controls/material", "QtQuick/Controls/Material", QStringList() << "Ripple" << "SliderHandle" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator" << "BoxShadow" << "ElevationEffect" << "CursorDelegate");
addTestRowForEachControl(&engine, "controls/universal", "QtQuick/Controls/Universal", QStringList() << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator");
QTest::addColumn<QStringList>("ignoredAttachedClassNames");
QTest::addColumn<StringPairSet>("expectedAttachedClassNames");
QStringList ignoredNames;
// We used to just check that there were no duplicate QMetaObject class names,
// but that doesn't account for attached objects loaded by composite controls,
// such as DialogButtonBox, which is loaded by Dialog.
// So now we list all controls and the attached types we expect them to use.
QTest::newRow("Default/AbstractButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Action.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ActionGroup.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ApplicationWindow.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/BusyIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Button.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ButtonGroup.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/CheckBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/CheckDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ComboBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Container.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Control.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/DelayButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Dial.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Dialog.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Dialog_QMLTYPE" }};
QTest::newRow("Default/DialogButtonBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Drawer.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Drawer_QMLTYPE" }};
QTest::newRow("Default/Frame.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/GroupBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/HorizontalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ItemDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Label.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Menu.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Menu_QMLTYPE" },
{ "QQuickScrollIndicatorAttached", "QQuickListView" },
{ "QQuickWindowAttached", "QQuickListView" }
};
QTest::newRow("Default/MenuBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/MenuBarItem.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/MenuItem.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/MenuSeparator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Page.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/PageIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Pane.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Popup.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Popup_QMLTYPE" }};
QTest::newRow("Default/ProgressBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/RadioButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/RadioDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/RangeSlider.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/RoundButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ScrollBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ScrollIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ScrollView.qml") << ignoredNames << StringPairSet {{ "QQuickScrollBarAttached", "ScrollView_QMLTYPE" }};
QTest::newRow("Default/Slider.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/SpinBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/SplitView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/StackView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/SwipeDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/SwipeView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Switch.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/SwitchDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/TabBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/TabButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/TextArea.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/TextField.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ToolBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ToolButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ToolSeparator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/ToolTip.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/Tumbler.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Default/VerticalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ApplicationWindow.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/BusyIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Button.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/CheckBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/CheckDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ComboBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/DelayButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Dial.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Dialog.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Dialog_QMLTYPE" }};
QTest::newRow("Fusion/DialogButtonBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Drawer.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Drawer_QMLTYPE" }};
QTest::newRow("Fusion/Frame.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/GroupBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/HorizontalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ItemDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Label.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Menu.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Menu_QMLTYPE" },
{ "QQuickScrollIndicatorAttached", "QQuickListView" },
{ "QQuickWindowAttached", "QQuickListView" }
};
QTest::newRow("Fusion/MenuBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/MenuBarItem.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/MenuItem.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/MenuSeparator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Page.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/PageIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Pane.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Popup.qml") << ignoredNames << StringPairSet {{ "QQuickOverlayAttached", "Popup_QMLTYPE" }};
QTest::newRow("Fusion/ProgressBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/RadioButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/RadioDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/RangeSlider.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/RoundButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ScrollBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ScrollIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Slider.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/SpinBox.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/SplitView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/SwipeDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Switch.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/SwitchDelegate.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/TabBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/TabButton.qml") << ignoredNames << StringPairSet {{ "QQuickTabBarAttached", "TabButton_QMLTYPE" }};
QTest::newRow("Fusion/TextArea.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/TextField.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ToolBar.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ToolButton.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ToolSeparator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/ToolTip.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/Tumbler.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Fusion/VerticalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/ApplicationWindow.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ApplicationWindow_QMLTYPE" }};
QTest::newRow("Material/BusyIndicator.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "BusyIndicator_QMLTYPE" }};
QTest::newRow("Material/Button.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Button_QMLTYPE" }};
QTest::newRow("Material/CheckBox.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "CheckBox_QMLTYPE" }};
QTest::newRow("Material/CheckDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "CheckDelegate_QMLTYPE" }};
QTest::newRow("Material/ComboBox.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ComboBox_QMLTYPE" }};
QTest::newRow("Material/DelayButton.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "DelayButton_QMLTYPE" }};
QTest::newRow("Material/Dial.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Dial_QMLTYPE" }};
QTest::newRow("Material/Dialog.qml") << ignoredNames << StringPairSet {
{ "QQuickMaterialStyle", "DialogButtonBox_QMLTYPE" },
{ "QQuickOverlayAttached", "Dialog_QMLTYPE" },
{ "QQuickMaterialStyle", "Dialog_QMLTYPE" },
{ "QQuickMaterialStyle", "Label_QMLTYPE" }
};
QTest::newRow("Material/DialogButtonBox.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "DialogButtonBox_QMLTYPE" }};
QTest::newRow("Material/Drawer.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Drawer_QMLTYPE" },
{ "QQuickMaterialStyle", "Drawer_QMLTYPE" }
};
QTest::newRow("Material/Frame.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Frame_QMLTYPE" }};
QTest::newRow("Material/GroupBox.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "GroupBox_QMLTYPE" }};
QTest::newRow("Material/HorizontalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/ItemDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ItemDelegate_QMLTYPE" }};
QTest::newRow("Material/Label.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Label_QMLTYPE" }};
QTest::newRow("Material/Menu.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Menu_QMLTYPE" },
{ "QQuickMaterialStyle", "Menu_QMLTYPE" },
{ "QQuickScrollIndicatorAttached", "QQuickListView" },
{ "QQuickWindowAttached", "QQuickListView" },
{ "QQuickMaterialStyle", "ScrollIndicator_QMLTYPE" }
};
QTest::newRow("Material/MenuBar.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "MenuBar_QMLTYPE" }};
QTest::newRow("Material/MenuBarItem.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "MenuBarItem_QMLTYPE" }};
QTest::newRow("Material/MenuItem.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "MenuItem_QMLTYPE" }};
QTest::newRow("Material/MenuSeparator.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "MenuSeparator_QMLTYPE" }};
QTest::newRow("Material/Page.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Page_QMLTYPE" }};
QTest::newRow("Material/PageIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/Pane.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Pane_QMLTYPE" }};
QTest::newRow("Material/Popup.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Popup_QMLTYPE" },
{ "QQuickMaterialStyle", "Popup_QMLTYPE" }
};
QTest::newRow("Material/ProgressBar.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ProgressBar_QMLTYPE" }};
QTest::newRow("Material/RadioButton.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "RadioButton_QMLTYPE" }};
QTest::newRow("Material/RadioDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "RadioDelegate_QMLTYPE" }};
QTest::newRow("Material/RangeSlider.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "RangeSlider_QMLTYPE" }};
QTest::newRow("Material/RoundButton.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "RoundButton_QMLTYPE" }};
QTest::newRow("Material/ScrollBar.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ScrollBar_QMLTYPE" }};
QTest::newRow("Material/ScrollIndicator.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ScrollIndicator_QMLTYPE" }};
QTest::newRow("Material/Slider.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "Slider_QMLTYPE" }};
QTest::newRow("Material/SpinBox.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "SpinBox_QMLTYPE" }};
QTest::newRow("Material/SplitView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/StackView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/SwipeDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "SwipeDelegate_QMLTYPE" }};
QTest::newRow("Material/SwipeView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/Switch.qml") << ignoredNames << StringPairSet {
{ "QQuickMaterialStyle", "SwitchIndicator_QMLTYPE" },
{ "QQuickMaterialStyle", "Switch_QMLTYPE" }
};
QTest::newRow("Material/SwitchDelegate.qml") << ignoredNames << StringPairSet {
{ "QQuickMaterialStyle", "SwitchDelegate_QMLTYPE" },
{ "QQuickMaterialStyle", "SwitchIndicator_QMLTYPE" }
};
QTest::newRow("Material/TabBar.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "TabBar_QMLTYPE" }};
QTest::newRow("Material/TabButton.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "TabButton_QMLTYPE" }};
QTest::newRow("Material/TextArea.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "TextArea_QMLTYPE" }};
QTest::newRow("Material/TextField.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "TextField_QMLTYPE" }};
QTest::newRow("Material/ToolBar.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ToolBar_QMLTYPE" }};
QTest::newRow("Material/ToolButton.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ToolButton_QMLTYPE" }};
QTest::newRow("Material/ToolSeparator.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ToolSeparator_QMLTYPE" }};
QTest::newRow("Material/ToolTip.qml") << ignoredNames << StringPairSet {{ "QQuickMaterialStyle", "ToolTip_QMLTYPE" }};
QTest::newRow("Material/Tumbler.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Material/VerticalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/ApplicationWindow.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ApplicationWindow_QMLTYPE" }};
QTest::newRow("Universal/BusyIndicator.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "BusyIndicator_QMLTYPE" }};
QTest::newRow("Universal/Button.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Button_QMLTYPE" }};
QTest::newRow("Universal/CheckBox.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "CheckBox_QMLTYPE" }};
QTest::newRow("Universal/CheckDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "CheckDelegate_QMLTYPE" }};
QTest::newRow("Universal/ComboBox.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ComboBox_QMLTYPE" }};
QTest::newRow("Universal/DelayButton.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "DelayButton_QMLTYPE" }};
QTest::newRow("Universal/Dial.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Dial_QMLTYPE" }};
QTest::newRow("Universal/Dialog.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Dialog_QMLTYPE" },
{ "QQuickUniversalStyle", "Label_QMLTYPE" },
{ "QQuickUniversalStyle", "Dialog_QMLTYPE" },
{ "QQuickUniversalStyle", "DialogButtonBox_QMLTYPE" }
};
QTest::newRow("Universal/DialogButtonBox.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "DialogButtonBox_QMLTYPE" }};
QTest::newRow("Universal/Drawer.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Drawer_QMLTYPE" },
{ "QQuickUniversalStyle", "Drawer_QMLTYPE" }
};
QTest::newRow("Universal/Frame.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Frame_QMLTYPE" }};
QTest::newRow("Universal/GroupBox.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "GroupBox_QMLTYPE" }};
QTest::newRow("Universal/HorizontalHeaderView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/ItemDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ItemDelegate_QMLTYPE" }};
QTest::newRow("Universal/Label.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Label_QMLTYPE" }};
QTest::newRow("Universal/Menu.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Menu_QMLTYPE" },
{ "QQuickUniversalStyle", "Menu_QMLTYPE" },
{ "QQuickScrollIndicatorAttached", "QQuickListView" },
{ "QQuickWindowAttached", "QQuickListView" },
{ "QQuickUniversalStyle", "ScrollIndicator_QMLTYPE" }
};
QTest::newRow("Universal/MenuBar.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "MenuBar_QMLTYPE" }};
QTest::newRow("Universal/MenuBarItem.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "MenuBarItem_QMLTYPE" }};
QTest::newRow("Universal/MenuItem.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "MenuItem_QMLTYPE" }};
QTest::newRow("Universal/MenuSeparator.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "MenuSeparator_QMLTYPE" }};
QTest::newRow("Universal/Page.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Page_QMLTYPE" }};
QTest::newRow("Universal/PageIndicator.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/Pane.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Pane_QMLTYPE" }};
QTest::newRow("Universal/Popup.qml") << ignoredNames << StringPairSet {
{ "QQuickOverlayAttached", "Popup_QMLTYPE" },
{ "QQuickUniversalStyle", "Popup_QMLTYPE" }
};
QTest::newRow("Universal/ProgressBar.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ProgressBar_QMLTYPE" }};
QTest::newRow("Universal/RadioButton.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "RadioButton_QMLTYPE" }};
QTest::newRow("Universal/RadioDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "RadioDelegate_QMLTYPE" }};
QTest::newRow("Universal/RangeSlider.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "RangeSlider_QMLTYPE" }};
QTest::newRow("Universal/RoundButton.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "RoundButton_QMLTYPE" }};
QTest::newRow("Universal/ScrollBar.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ScrollBar_QMLTYPE" }};
QTest::newRow("Universal/ScrollIndicator.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ScrollIndicator_QMLTYPE" }};
QTest::newRow("Universal/Slider.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Slider_QMLTYPE" }};
QTest::newRow("Universal/SpinBox.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "SpinBox_QMLTYPE" }};
QTest::newRow("Universal/SplitView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/StackView.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/SwipeDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "SwipeDelegate_QMLTYPE" }};
QTest::newRow("Universal/Switch.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "Switch_QMLTYPE" }};
QTest::newRow("Universal/SwitchDelegate.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "SwitchDelegate_QMLTYPE" }};
QTest::newRow("Universal/TabBar.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "TabBar_QMLTYPE" }};
QTest::newRow("Universal/TabButton.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "TabButton_QMLTYPE" }};
QTest::newRow("Universal/TextArea.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "TextArea_QMLTYPE" }};
QTest::newRow("Universal/TextField.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "TextField_QMLTYPE" }};
QTest::newRow("Universal/ToolBar.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ToolBar_QMLTYPE" }};
QTest::newRow("Universal/ToolButton.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ToolButton_QMLTYPE" }};
QTest::newRow("Universal/ToolSeparator.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ToolSeparator_QMLTYPE" }};
QTest::newRow("Universal/ToolTip.qml") << ignoredNames << StringPairSet {{ "QQuickUniversalStyle", "ToolTip_QMLTYPE" }};
QTest::newRow("Universal/Tumbler.qml") << ignoredNames << StringPairSet {};
QTest::newRow("Universal/VerticalHeaderView.qml") << ignoredNames << StringPairSet {};
}
QTEST_MAIN(tst_Sanity)

View File

@ -42,11 +42,12 @@
#include <QtGui/qguiapplication.h>
#include <QtQml/qqml.h>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickControls2/private/qquickstyle_p.h>
static QStringList testStyles()
{
if (QQuickStyle::name().isEmpty())
return QQuickStyle::availableStyles();
return QQuickStylePrivate::builtInStyles();
return QStringList(QQuickStyle::name());
}

View File

@ -1,4 +1,4 @@
QT += testlib-private core-private gui-private qml-private quick-private quicktemplates2-private quickcontrols2
QT += testlib-private core-private gui-private qml-private quick-private quicktemplates2-private quickcontrols2 quickcontrols2-private
HEADERS += $$PWD/visualtestutil.h \
$$PWD/util.h \

View File

@ -95,12 +95,12 @@ void QQuickVisualTestUtil::centerOnScreen(QQuickWindow *window)
window->setFramePosition(screenGeometry.center() - offset);
}
void QQuickVisualTestUtil::addTestRowForEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skiplist)
void QQuickVisualTestUtil::forEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skipList, QQuickVisualTestUtil::ForEachCallback callback)
{
// We cannot use QQmlComponent to load QML files directly from the source tree.
// For styles that use internal QML types (eg. material/Ripple.qml), the source
// dir would be added as an "implicit" import path overriding the actual import
// path (qtbase/qml/QtQuick/Controls/Material). => The QML engine fails to load
// path (qtbase/qml/QtQuick/Controls.2/Material). => The QML engine fails to load
// the style C++ plugin from the implicit import path (the source dir).
//
// Therefore we only use the source tree for finding out the set of QML files that
@ -111,7 +111,7 @@ void QQuickVisualTestUtil::addTestRowForEachControl(QQmlEngine *engine, const QS
const QFileInfoList entries = QDir(QQC2_IMPORT_PATH "/" + sourcePath).entryInfoList(QStringList("*.qml"), QDir::Files);
for (const QFileInfo &entry : entries) {
QString name = entry.baseName();
if (!skiplist.contains(name)) {
if (!skipList.contains(name)) {
const auto importPathList = engine->importPathList();
for (const QString &importPath : importPathList) {
QString name = entry.dir().dirName() + "/" + entry.fileName();
@ -119,13 +119,13 @@ void QQuickVisualTestUtil::addTestRowForEachControl(QQmlEngine *engine, const QS
if (filePath.startsWith(":"))
filePath.prepend("qrc");
if (QFile::exists(filePath)) {
QTest::newRow(qPrintable(name)) << QUrl::fromLocalFile(filePath);
callback(name, QUrl::fromLocalFile(filePath));
break;
} else {
QUrl url(filePath);
filePath = QQmlFile::urlToLocalFileOrQrc(filePath);
if (!filePath.isEmpty() && QFile::exists(filePath)) {
QTest::newRow(qPrintable(name)) << url;
callback(name, url);
break;
}
}
@ -133,3 +133,10 @@ void QQuickVisualTestUtil::addTestRowForEachControl(QQmlEngine *engine, const QS
}
}
}
void QQuickVisualTestUtil::addTestRowForEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skipList)
{
forEachControl(engine, sourcePath, targetPath, skipList, [&](const QString &relativePath, const QUrl &absoluteUrl) {
QTest::newRow(qPrintable(relativePath)) << absoluteUrl;
});
}

View File

@ -37,11 +37,12 @@
#ifndef QQUICKVISUALTESTUTIL_H
#define QQUICKVISUALTESTUTIL_H
#include <functional>
#include <QtQuick/QQuickItem>
#include <QtQml/QQmlExpression>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
#include "util.h"
@ -162,7 +163,33 @@ namespace QQuickVisualTestUtil
QByteArray errorMessage;
};
void addTestRowForEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skiplist = QStringList());
struct QQuickStyleHelper
{
bool updateStyle(const QString &style)
{
// If it's not the first time a style has been set and the new style is not different, do nothing.
if (!currentStyle.isEmpty() && style == currentStyle)
return false;
engine.reset(new QQmlEngine);
currentStyle = style;
qmlClearTypeRegistrations();
QQuickStyle::setStyle(style);
QQmlComponent component(engine.data());
component.setData(QString("import QtQuick\nimport QtQuick.Controls\n Control { }").toUtf8(), QUrl());
return true;
}
QString currentStyle;
QScopedPointer<QQmlEngine> engine;
};
typedef std::function<void(const QString &/*relativePath*/, const QUrl &/*absoluteUrl*/)> ForEachCallback;
void forEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skipList, ForEachCallback callback);
void addTestRowForEachControl(QQmlEngine *engine, const QString &sourcePath, const QString &targetPath, const QStringList &skipList = QStringList());
}
#define QQUICK_VERIFY_POLISH(item) \

View File

@ -1,7 +1,7 @@
TEMPLATE = app
TARGET = tst_snippets
QT += quick quickcontrols2 testlib
QT += quick quickcontrols2 quickcontrols2-private testlib
CONFIG += testcase
macos:CONFIG -= app_bundle

View File

@ -36,7 +36,8 @@
#include <QtTest>
#include <QtQuick>
#include <QtQuickControls2>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickControls2/private/qquickstyle_p.h>
typedef QPair<QString, QString> QStringPair;
@ -114,14 +115,14 @@ void tst_Snippets::verify()
if (takeScreenshots) {
const QString currentDataTag = QLatin1String(QTest::currentDataTag());
static const QString applicationStyle = QQuickStyle::name().isEmpty() ? "Default" : QQuickStyle::name();
static const QStringList availableStyles = QQuickStyle::availableStyles();
static const QStringList builtInStyles = QQuickStylePrivate::builtInStyles();
bool isStyledSnippet = false;
const QString snippetStyle = currentDataTag.section("-", 1, 1);
for (const QString &availableStyle : availableStyles) {
if (!snippetStyle.compare(availableStyle, Qt::CaseInsensitive)) {
if (applicationStyle != availableStyle)
QSKIP(qPrintable(QString("%1 style specific snippet. Running with the %2 style.").arg(availableStyle, applicationStyle)));
for (const QString &style : builtInStyles) {
if (!snippetStyle.compare(style, Qt::CaseInsensitive)) {
if (applicationStyle != style)
QSKIP(qPrintable(QString("%1 style specific snippet. Running with the %2 style.").arg(style, applicationStyle)));
isStyledSnippet = true;
}
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "ResourceStyle"
}

View File

@ -0,0 +1,2 @@
module ResourceStyle
Button 2.15 Button.qml

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Action {
objectName: "data"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "data"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "FileSystemStyle"
}

View File

@ -0,0 +1,2 @@
module FileSystemStyle
Button 2.15 Button.qml

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Label {
objectName: "data"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "PlatformStyle/+linux"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "PlatformStyle/+macos"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "PlatformStyle/+windows"
}

View File

@ -0,0 +1,4 @@
import QtQuick.Templates 2.15 as T
T.Button {
objectName: "PlatformStyle/Button.qml"
}

View File

@ -0,0 +1,2 @@
module PlatformStyle
Button 2.15 Button.qml

View File

@ -0,0 +1,64 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
ApplicationWindow {
title: "Test Application Window"
width: 400
height: 400
property alias button: button
Button {
id: button
}
}

View File

@ -0,0 +1,4 @@
module data
Action 2.15 Action.qml
Button 2.15 Button.qml
Label 2.15 Label.qml

View File

@ -0,0 +1,29 @@
CONFIG += testcase
TARGET = tst_styleimports
SOURCES += tst_styleimports.cpp
macos:CONFIG -= app_bundle
QT += core-private gui-private qml-private quick-private quickcontrols2-private testlib
include (../shared/util.pri)
resourcestyle.prefix = /
resourcestyle.files += \
$$PWD/ResourceStyle/Button.qml \
$$PWD/ResourceStyle/qmldir
RESOURCES += resourcestyle
TESTDATA = data/*
OTHER_FILES += \
data/*.qml \
data/qmldir \
data/FileSystemStyle/*.qml \
data/FileSystemStyle/qmldir \
data/PlatformStyle/*.qml \
data/PlatformStyle/+linux/*.qml \
data/PlatformStyle/+macos/*.qml \
data/PlatformStyle/+windows/*.qml \
data/PlatformStyle/qmldir

View File

@ -0,0 +1,205 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtCore/qregularexpression.h>
#include <QtTest/qtest.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlapplicationengine.h>
#include <QtQml/qqmlengine.h>
#include <QtQuick/qquickwindow.h>
#include <QtQuickControls2/qquickstyle.h>
#include <QtQuickControls2/private/qquickstyle_p.h>
#include "../shared/util.h"
class tst_StyleImports : public QQmlDataTest
{
Q_OBJECT
private slots:
void initTestCase();
void cleanup();
void select_data();
void select();
void platformSelectors();
};
void tst_StyleImports::initTestCase()
{
QQmlDataTest::initTestCase();
}
void tst_StyleImports::cleanup()
{
qmlClearTypeRegistrations();
}
void tst_StyleImports::select_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("style");
QTest::addColumn<QString>("fallback");
QTest::addColumn<QString>("expected");
// Action.qml exists in the "data" style and the Default style.
QTest::newRow("control=Action,style=empty,fallback=empty") << "Action.qml" << "" << "" << "Default";
QTest::newRow("control=Action,style=fs,fallback=empty") << "Action.qml" << "FileSystemStyle" << "" << "Default";
QTest::newRow("control=Action,style=qrc,fallback=empty") << "Action.qml" << "ResourceStyle" << "" << "Default";
QTest::newRow("control=Action,style=nosuch,fallback=empty") << "Action.qml" << "NoSuchStyle" << "" << "Default";
QTest::newRow("control=Action,style=data,fallback=empty") << "Action.qml" << "data" << "" << "data";
QTest::newRow("control=Action,style=empty,fallback=mat") << "Action.qml" << "" << "Material" << "";
QTest::newRow("control=Action,style=fs,fallback=mat") << "Action.qml" << "FileSystemStyle" << "Material" << "Default";
QTest::newRow("control=Action,style=qrc,fallback=mat") << "Action.qml" << "ResourceStyle" << "Material" << "Default";
QTest::newRow("control=Action,style=nosuch,fallback=mat") << "Action.qml" << "NoSuchStyle" << "Material" << "Default";
QTest::newRow("control=Action,style=data,fallback=mat") << "Action.qml" << "data" << "Material" << "data";
// ScrollView.qml only exists in the Default style.
QTest::newRow("control=ScrollView,style=empty,fallback=empty") << "ScrollView.qml" << "" << "" << "Default";
QTest::newRow("control=ScrollView,style=fs,fallback=empty") << "ScrollView.qml" << "FileSystemStyle" << "" << "Default";
QTest::newRow("control=ScrollView,style=qrc,fallback=empty") << "ScrollView.qml" << "ResourceStyle" << "" << "Default";
QTest::newRow("control=ScrollView,style=nosuch,fallback=empty") << "ScrollView.qml" << "NoSuchStyle" << "" << "Default";
QTest::newRow("control=ScrollView,style=data,fallback=empty") << "ScrollView.qml" << "data" << "" << "Default";
QTest::newRow("control=ScrollView,style=empty,fallback=mat") << "ScrollView.qml" << "" << "Material" << "Default";
QTest::newRow("control=ScrollView,style=fs,fallback=mat") << "ScrollView.qml" << "FileSystemStyle" << "Material" << "Default";
QTest::newRow("control=ScrollView,style=qrc,fallback=mat") << "ScrollView.qml" << "ResourceStyle" << "Material" << "Default";
QTest::newRow("control=ScrollView,style=nosuch,fallback=mat") << "ScrollView.qml" << "NoSuchStyle" << "Material" << "Default";
QTest::newRow("control=ScrollView,style=data,fallback=mat") << "ScrollView.qml" << "data" << "Material" << "Default";
// Label.qml exists in the "data", Default and Material styles.
QTest::newRow("control=Label,style=none,fallback=none") << "Label.qml" << "" << "" << "Default";
QTest::newRow("control=Label,style=fs,fallback=none") << "Label.qml" << "FileSystemStyle" << "" << "Default";
QTest::newRow("control=Label,style=qrc,fallback=none") << "Label.qml" << "ResourceStyle" << "" << "Default";
QTest::newRow("control=Label,style=nosuch,fallback=none") << "Label.qml" << "NoSuchStyle" << "" << "Default";
QTest::newRow("control=Label,style=data,fallback=none") << "Label.qml" << "data" << "" << "data";
QTest::newRow("control=Label,style=none,fallback=mat") << "Label.qml" << "" << "Material" << "Default";
QTest::newRow("control=Label,style=fs,fallback=mat") << "Label.qml" << "FileSystemStyle" << "Material" << "Default";
QTest::newRow("control=Label,style=qrc,fallback=mat") << "Label.qml" << "ResourceStyle" << "Material" << "Default";
QTest::newRow("control=Label,style=nosuch,fallback=mat") << "Label.qml" << "NoSuchStyle" << "Material" << "Default";
QTest::newRow("control=Label,style=data,fallback=mat") << "Label.qml" << "data" << "Material" << "data";
// Button.qml exists in all styles including the fs and qrc styles
QTest::newRow("control=Button,style=none,fallback=none") << "Button.qml" << "" << "" << "Default";
QTest::newRow("control=Button,style=fs,fallback=none") << "Button.qml" << "FileSystemStyle" << "" << "FileSystemStyle";
QTest::newRow("control=Button,style=qrc,fallback=none") << "Button.qml" << "ResourceStyle" << "" << "ResourceStyle";
QTest::newRow("control=Button,style=nosuch,fallback=none") << "Button.qml" << "NoSuchStyle" << "" << "Default";
QTest::newRow("control=Button,style=data,fallback=none") << "Button.qml" << "data" << "" << "data";
QTest::newRow("control=Button,style=none,fallback=mat") << "Button.qml" << "" << "Material" << "Default";
QTest::newRow("control=Button,style=fs,fallback=mat") << "Button.qml" << "FileSystemStyle" << "Material" << "FileSystemStyle";
QTest::newRow("control=Button,style=qrc,fallback=mat") << "Button.qml" << "ResourceStyle" << "Material" << "ResourceStyle";
QTest::newRow("control=Button,style=nosuch,fallback=mat") << "Button.qml" << "NoSuchStyle" << "Material" << "Default";
QTest::newRow("control=Button,style=data,fallback=mat") << "Button.qml" << "data" << "Material" << "data";
}
void tst_StyleImports::select()
{
QFETCH(QString, file);
QFETCH(QString, style);
QFETCH(QString, fallback);
QFETCH(QString, expected);
// In Qt 5, there were several accepted forms for style names.
// In Qt 6, the only accepted form is the base name of the style directory.
const bool invalidStyleName = style.contains(QLatin1Char('/'));
if (invalidStyleName)
QTest::ignoreMessage(QtWarningMsg,
"Style names must not contain paths; see the \"Definition of a Style\" documentation for more information");
QQuickStyle::setStyle(style);
QQuickStyle::setFallbackStyle(fallback);
QQmlEngine engine;
engine.addImportPath(directory());
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine);
const QString controlName = file.mid(0, file.indexOf(QLatin1Char('.')));
component.setData(QString::fromLatin1("import QtQuick 2.15; import QtQuick.Controls 2.15; %1 { }").arg(controlName).toUtf8(), QUrl());
const bool nonExistentStyle = style == QLatin1String("NoSuchStyle");
if (nonExistentStyle)
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
QScopedPointer<QObject> object(component.create());
if (nonExistentStyle) {
QVERIFY(object.isNull());
return;
}
QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
// TODO: test built-in styles below too
// We can't check for the attached style object since that API is in a plugin,
// and it's not possible to use e.g. the baseUrl of the QQmlContext
// nor the metaObject to test it either.
if (!QQuickStylePrivate::builtInStyles().contains(expected)) {
// We're expecting a custom style.
QCOMPARE(object->objectName(), expected);
}
}
void tst_StyleImports::platformSelectors()
{
QQuickStyle::setStyle(QLatin1String("PlatformStyle"));
QQmlApplicationEngine engine;
engine.addImportPath(dataDirectory());
engine.load(testFileUrl("platformSelectors.qml"));
QQuickWindow *window = qobject_cast<QQuickWindow*>(engine.rootObjects().first());
QVERIFY(window);
QObject *button = window->property("button").value<QObject*>();
QVERIFY(button);
#if defined(Q_OS_LINUX)
QCOMPARE(button->objectName(), "PlatformStyle/+linux");
#elif defined(Q_OS_MACOS)
QCOMPARE(button->objectName(), "PlatformStyle/+macos");
#elif defined(Q_OS_WIN)
QCOMPARE(button->objectName(), "PlatformStyle/+windows");
#else
QCOMPARE(button->objectName(), "PlatformStyle/Button.qml");
#endif
}
QTEST_MAIN(tst_StyleImports)
#include "tst_styleimports.moc"

View File

@ -1,7 +1,7 @@
TEMPLATE = app
TARGET = tst_creationtime
QT += qml testlib
QT += qml testlib quickcontrols2
CONFIG += testcase
macos:CONFIG -= app_bundle

View File

@ -34,8 +34,10 @@
**
****************************************************************************/
#include <QtQml>
#include <QtCore/qscopedpointer.h>
#include <QtTest>
#include <QtQml>
#include <QtQuickControls2/qquickstyle.h>
#include "../../auto/shared/visualtestutil.h"
@ -46,10 +48,11 @@ class tst_CreationTime : public QObject
Q_OBJECT
private slots:
void initTestCase();
void init();
void controls();
void controls_data();
void defaultStyle();
void defaultStyle_data();
void fusion();
void fusion_data();
@ -64,17 +67,29 @@ private slots:
void universal_data();
private:
QQmlEngine engine;
QQuickStyleHelper styleHelper;
};
void tst_CreationTime::initTestCase()
{
styleHelper.engine.reset(new QQmlEngine);
}
void tst_CreationTime::init()
{
engine.clearComponentCache();
styleHelper.engine->clearComponentCache();
}
static void doBenchmark(QQmlEngine *engine, const QUrl &url)
static void doBenchmark(QQuickStyleHelper &styleHelper, const QUrl &url)
{
QQmlComponent component(engine);
const QString tagStr = QString::fromLatin1(QTest::currentDataTag());
QStringList styleAndFileName = tagStr.split('/');
QCOMPARE(styleAndFileName.size(), 2);
QString style = styleAndFileName.first();
style[0] = style.at(0).toUpper();
styleHelper.updateStyle(style);
QQmlComponent component(styleHelper.engine.data());
component.loadUrl(url);
QObjectList objects;
@ -87,64 +102,64 @@ static void doBenchmark(QQmlEngine *engine, const QUrl &url)
qDeleteAll(objects);
}
void tst_CreationTime::controls()
void tst_CreationTime::defaultStyle()
{
QFETCH(QUrl, url);
doBenchmark(&engine, url);
doBenchmark(styleHelper, url);
}
void tst_CreationTime::controls_data()
void tst_CreationTime::defaultStyle_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls", "QtQuick/Controls", QStringList() << "ApplicationWindow");
addTestRowForEachControl(styleHelper.engine.data(), "controls/default", "QtQuick/Controls/Default", QStringList() << "ApplicationWindow");
}
void tst_CreationTime::fusion()
{
QFETCH(QUrl, url);
doBenchmark(&engine, url);
doBenchmark(styleHelper, url);
}
void tst_CreationTime::fusion_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls/fusion", "QtQuick/Controls/Fusion", QStringList() << "ApplicationWindow" << "ButtonPanel" << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator");
addTestRowForEachControl(styleHelper.engine.data(), "controls/fusion", "QtQuick/Controls/Fusion", QStringList() << "ApplicationWindow" << "ButtonPanel" << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator");
}
void tst_CreationTime::imagine()
{
QFETCH(QUrl, url);
doBenchmark(&engine, url);
doBenchmark(styleHelper, url);
}
void tst_CreationTime::imagine_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls/imagine", "QtQuick/Controls/Imagine", QStringList() << "ApplicationWindow");
addTestRowForEachControl(styleHelper.engine.data(), "controls/imagine", "QtQuick/Controls/Imagine", QStringList() << "ApplicationWindow");
}
void tst_CreationTime::material()
{
QFETCH(QUrl, url);
doBenchmark(&engine, url);
doBenchmark(styleHelper, url);
}
void tst_CreationTime::material_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls/material", "QtQuick/Controls/Material", QStringList() << "ApplicationWindow" << "Ripple" << "SliderHandle" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator" << "BoxShadow" << "ElevationEffect" << "CursorDelegate");
addTestRowForEachControl(styleHelper.engine.data(), "controls/material", "QtQuick/Controls/Material", QStringList() << "ApplicationWindow" << "Ripple" << "SliderHandle" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator" << "BoxShadow" << "ElevationEffect" << "CursorDelegate");
}
void tst_CreationTime::universal()
{
QFETCH(QUrl, url);
doBenchmark(&engine, url);
doBenchmark(styleHelper, url);
}
void tst_CreationTime::universal_data()
{
QTest::addColumn<QUrl>("url");
addTestRowForEachControl(&engine, "controls/universal", "QtQuick/Controls/Universal", QStringList() << "ApplicationWindow" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator");
addTestRowForEachControl(styleHelper.engine.data(), "controls/universal", "QtQuick/Controls/Universal", QStringList() << "ApplicationWindow" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator");
}
QTEST_MAIN(tst_CreationTime)

View File

@ -93,7 +93,7 @@ void tst_ObjectCount::cleanup()
static void initTestRows(QQmlEngine *engine)
{
addTestRowForEachControl(engine, "controls", "QtQuick/Controls");
addTestRowForEachControl(engine, "controls/default", "QtQuick/Controls/Default");
addTestRowForEachControl(engine, "controls/fusion", "QtQuick/Controls/Fusion", QStringList() << "ButtonPanel" << "CheckIndicator" << "RadioIndicator" << "SliderGroove" << "SliderHandle" << "SwitchIndicator");
addTestRowForEachControl(engine, "controls/imagine", "QtQuick/Controls/Imagine");
addTestRowForEachControl(engine, "controls/material", "QtQuick/Controls/Material", QStringList() << "Ripple" << "SliderHandle" << "CheckIndicator" << "RadioIndicator" << "SwitchIndicator" << "BoxShadow" << "ElevationEffect" << "CursorDelegate");

View File

@ -55,6 +55,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include <QtQuickControls2/private/qquickstyle_p.h>
#include "assetfixer.h"
#include "clipboard.h"
@ -85,7 +86,7 @@ int main(int argc, char *argv[])
qmlRegisterType<Clipboard>("App", 1, 0, "Clipboard");
qmlRegisterType<DirectoryValidator>("App", 1, 0, "DirectoryValidator");
engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
engine.rootContext()->setContextProperty("availableStyles", QQuickStylePrivate::builtInStyles());
engine.load(QUrl(QStringLiteral("qrc:/testbench.qml")));

View File

@ -1,6 +1,6 @@
TEMPLATE = app
QT += qml quick quickcontrols2
QT += qml quick quickcontrols2 quickcontrols2-private
CONFIG += c++11
HEADERS += \