diff options
Diffstat (limited to 'src/imports')
31 files changed, 1173 insertions, 106 deletions
diff --git a/src/imports/folderlistmodel/fileinfothread_p.h b/src/imports/folderlistmodel/fileinfothread_p.h index 75da12a421..b505ece750 100644 --- a/src/imports/folderlistmodel/fileinfothread_p.h +++ b/src/imports/folderlistmodel/fileinfothread_p.h @@ -94,7 +94,7 @@ public Q_SLOTS: #endif protected: - void run(); + void run() override; void getFileInfos(const QString &path); void findChangeRange(const QList<FileProperty> &list, int &fromIndex, int &toIndex); diff --git a/src/imports/folderlistmodel/fileproperty_p.h b/src/imports/folderlistmodel/fileproperty_p.h index f385cdfdb4..48be4a3d85 100644 --- a/src/imports/folderlistmodel/fileproperty_p.h +++ b/src/imports/folderlistmodel/fileproperty_p.h @@ -57,17 +57,17 @@ class FileProperty { public: - FileProperty(const QFileInfo &info) + FileProperty(const QFileInfo &info) : + mFileName(info.fileName()), + mFilePath(info.filePath()), + mBaseName(info.baseName()), + mSuffix(info.completeSuffix()), + mSize(info.size()), + mIsDir(info.isDir()), + mIsFile(info.isFile()), + mLastModified(info.lastModified()), + mLastRead(info.lastRead()) { - mFileName = info.fileName(); - mFilePath = info.filePath(); - mBaseName = info.baseName(); - mSize = info.size(); - mSuffix = info.completeSuffix(); - mIsDir = info.isDir(); - mIsFile = info.isFile(); - mLastModified = info.lastModified(); - mLastRead = info.lastRead(); } ~FileProperty() {} diff --git a/src/imports/folderlistmodel/qquickfolderlistmodel.h b/src/imports/folderlistmodel/qquickfolderlistmodel.h index aae6df452d..dee73dff3e 100644 --- a/src/imports/folderlistmodel/qquickfolderlistmodel.h +++ b/src/imports/folderlistmodel/qquickfolderlistmodel.h @@ -94,10 +94,10 @@ public: FileUrlRole = Qt::UserRole + 9 }; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual QHash<int, QByteArray> roleNames() const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash<int, QByteArray> roleNames() const override; //![abslistmodel] //![count] @@ -144,8 +144,8 @@ public: Q_INVOKABLE int indexOf(const QUrl &file) const; //![parserstatus] - virtual void classBegin(); - virtual void componentComplete(); + void classBegin() override; + void componentComplete() override; //![parserstatus] int roleFromString(const QString &roleName) const; diff --git a/src/imports/imports.pro b/src/imports/imports.pro index b030cbcf73..45719df874 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -17,6 +17,7 @@ qtHaveModule(quick) { layouts \ qtquick2 \ window \ + sharedimage \ testlib qtConfig(quick-sprite):qtConfig(opengl(es1|es2)?): \ diff --git a/src/imports/layouts/qquickgridlayoutengine_p.h b/src/imports/layouts/qquickgridlayoutengine_p.h index 86f56a5af4..2810e2e5d0 100644 --- a/src/imports/layouts/qquickgridlayoutengine_p.h +++ b/src/imports/layouts/qquickgridlayoutengine_p.h @@ -67,7 +67,7 @@ public: : QGridLayoutItem(row, column, rowSpan, columnSpan, alignment), m_item(item), sizeHintCacheDirty(true), useFallbackToWidthOrHeight(true) {} - QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const override { Q_UNUSED(constraint); // Quick Layouts does not support constraint atm return effectiveSizeHints()[which]; @@ -99,12 +99,12 @@ public: sizeHintCacheDirty = true; } - QLayoutPolicy::Policy sizePolicy(Qt::Orientation orientation) const + QLayoutPolicy::Policy sizePolicy(Qt::Orientation orientation) const override { return QQuickLayout::effectiveSizePolicy_helper(m_item, orientation, attachedLayoutObject(m_item, false)); } - void setGeometry(const QRectF &rect) + void setGeometry(const QRectF &rect) override { QQuickLayoutAttached *info = attachedLayoutObject(m_item, false); const QRectF r = info ? rect.marginsRemoved(info->qMargins()) : rect; diff --git a/src/imports/layouts/qquicklayout.cpp b/src/imports/layouts/qquicklayout.cpp index fd2ff4a73e..df64b593d9 100644 --- a/src/imports/layouts/qquicklayout.cpp +++ b/src/imports/layouts/qquicklayout.cpp @@ -42,6 +42,7 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/private/qnumeric_p.h> #include <QtCore/qmath.h> +#include <QtQml/qqmlinfo.h> #include <limits> /*! @@ -678,7 +679,7 @@ QQuickLayout *QQuickLayoutAttached::parentLayout() const parentItem = parentItem->parentItem(); return qobject_cast<QQuickLayout *>(parentItem); } else { - qWarning("Layout must be attached to Item elements"); + qmlWarning(parent()) << "Layout must be attached to Item elements"; } return 0; } @@ -695,13 +696,20 @@ QQuickLayout::QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent) { } +static QQuickItemPrivate::ChangeTypes changeTypes = + QQuickItemPrivate::SiblingOrder + | QQuickItemPrivate::ImplicitWidth + | QQuickItemPrivate::ImplicitHeight + | QQuickItemPrivate::Destroyed + | QQuickItemPrivate::Visibility; + QQuickLayout::~QQuickLayout() { d_func()->m_isReady = false; const auto childItems = d_func()->childItems; for (QQuickItem *child : childItems) - QQuickItemPrivate::get(child)->removeItemChangeListener(this, QQuickItemPrivate::SiblingOrder); + QQuickItemPrivate::get(child)->removeItemChangeListener(this, changeTypes); } QQuickLayoutAttached *QQuickLayout::qmlAttachedProperties(QObject *object) @@ -765,14 +773,14 @@ void QQuickLayout::itemChange(ItemChange change, const ItemChangeData &value) Q_D(QQuickLayout); QQuickItem *item = value.item; qmlobject_connect(item, QQuickItem, SIGNAL(baselineOffsetChanged(qreal)), this, QQuickLayout, SLOT(invalidateSenderItem())); - QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight | QQuickItemPrivate::Destroyed | QQuickItemPrivate::Visibility); + QQuickItemPrivate::get(item)->addItemChangeListener(this, changeTypes); d->m_hasItemChangeListeners = true; if (isReady()) updateLayoutItems(); } else if (change == ItemChildRemovedChange) { QQuickItem *item = value.item; qmlobject_disconnect(item, QQuickItem, SIGNAL(baselineOffsetChanged(qreal)), this, QQuickLayout, SLOT(invalidateSenderItem())); - QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight | QQuickItemPrivate::Destroyed | QQuickItemPrivate::Visibility); + QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); if (isReady()) updateLayoutItems(); } @@ -820,7 +828,7 @@ void QQuickLayout::deactivateRecur() // When deleting a layout with children, there is no reason for the children to inform the layout that their // e.g. visibility got changed. The layout already knows that all its children will eventually become invisible, so // we therefore remove its change listener. - QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight | QQuickItemPrivate::Destroyed | QQuickItemPrivate::Visibility); + QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); if (QQuickLayout *layout = qobject_cast<QQuickLayout*>(item)) layout->deactivateRecur(); } diff --git a/src/imports/layouts/qquicklayoutstyleinfo.cpp b/src/imports/layouts/qquicklayoutstyleinfo.cpp index c33ceffb2d..5c8be8f306 100644 --- a/src/imports/layouts/qquicklayoutstyleinfo.cpp +++ b/src/imports/layouts/qquicklayoutstyleinfo.cpp @@ -50,7 +50,7 @@ QQuickLayoutStyleInfo::QQuickLayoutStyleInfo() qreal QQuickLayoutStyleInfo::spacing(Qt::Orientation /*orientation*/) const { -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_QNX) || defined(Q_OS_WINRT) +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_QNX) || defined(Q_OS_WINRT) // On Android and iOS the default spacing between each UI element is 8dp qreal spacing = 8.0; #else diff --git a/src/imports/layouts/qquicklinearlayout.cpp b/src/imports/layouts/qquicklinearlayout.cpp index 50b3eed87e..887b9b1fa1 100644 --- a/src/imports/layouts/qquicklinearlayout.cpp +++ b/src/imports/layouts/qquicklinearlayout.cpp @@ -41,6 +41,7 @@ #include "qquickgridlayoutengine_p.h" #include "qquicklayoutstyleinfo_p.h" #include <QtCore/private/qnumeric_p.h> +#include <QtQml/qqmlinfo.h> #include "qdebug.h" #include <limits> @@ -664,34 +665,25 @@ void QQuickGridLayout::insertLayoutItems() int &columnSpan = span[0]; int &rowSpan = span[1]; - bool invalidRowColumn = false; if (info) { if (info->isRowSet() || info->isColumnSet()) { // If row is specified and column is not specified (or vice versa), // the unspecified component of the cell position should default to 0 - row = column = 0; - if (info->isRowSet()) { - row = info->row(); - invalidRowColumn = row < 0; - } - if (info->isColumnSet()) { - column = info->column(); - invalidRowColumn = column < 0; - } - } - if (invalidRowColumn) { - qWarning("QQuickGridLayoutBase::insertLayoutItems: invalid row/column: %d", - row < 0 ? row : column); - return; + // The getters do this for us, as they will return 0 for an + // unset (or negative) value. + row = info->row(); + column = info->column(); } rowSpan = info->rowSpan(); columnSpan = info->columnSpan(); - if (columnSpan < 1 || rowSpan < 1) { - qWarning("QQuickGridLayoutBase::addItem: invalid row span/column span: %d", - rowSpan < 1 ? rowSpan : columnSpan); + if (columnSpan < 1) { + qmlWarning(child) << "Layout: invalid column span: " << columnSpan; return; - } + } else if (rowSpan < 1) { + qmlWarning(child) << "Layout: invalid row span: " << rowSpan; + return; + } alignment = info->alignment(); } diff --git a/src/imports/layouts/qquicklinearlayout_p.h b/src/imports/layouts/qquicklinearlayout_p.h index c289416540..f796c8a855 100644 --- a/src/imports/layouts/qquicklinearlayout_p.h +++ b/src/imports/layouts/qquicklinearlayout_p.h @@ -152,12 +152,12 @@ public: int rows() const; void setRows(int rows); - Q_ENUMS(Flow) enum Flow { LeftToRight, TopToBottom }; + Q_ENUM(Flow) Flow flow() const; void setFlow(Flow flow); - void insertLayoutItems(); + void insertLayoutItems() override; signals: void columnSpacingChanged(); @@ -199,7 +199,7 @@ public: qreal spacing() const; void setSpacing(qreal spacing); - void insertLayoutItems(); + void insertLayoutItems() override; signals: void spacingChanged(); diff --git a/src/imports/localstorage/plugin.cpp b/src/imports/localstorage/plugin.cpp index 9191700b45..8679750842 100644 --- a/src/imports/localstorage/plugin.cpp +++ b/src/imports/localstorage/plugin.cpp @@ -219,24 +219,6 @@ QQmlSqlDatabaseData::~QQmlSqlDatabaseData() { } -static QString qmlsqldatabase_databasesPath(QV4::ExecutionEngine *engine) -{ - return engine->qmlEngine()->offlineStoragePath() + - QDir::separator() + QLatin1String("Databases"); -} - -static void qmlsqldatabase_initDatabasesPath(QV4::ExecutionEngine *engine) -{ - QString databasesPath = qmlsqldatabase_databasesPath(engine); - if (!QDir().mkpath(databasesPath)) - qWarning() << "LocalStorage: can't create path - " << databasesPath; -} - -static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV4::ExecutionEngine *engine) -{ - return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName; -} - static ReturnedValue qmlsqldatabase_rows_index(const QQmlSqlDatabaseWrapper *r, ExecutionEngine *v4, quint32 index, bool *hasProperty = 0) { Scope scope(v4); @@ -450,7 +432,8 @@ static ReturnedValue qmlsqldatabase_changeVersion(CallContext *ctx) if (ok) { *w->d()->version = to_version; #if QT_CONFIG(settings) - QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(), scope.engine) + QLatin1String(".ini"), QSettings::IniFormat); + const QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(scope.engine->qmlEngine()); + QSettings ini(enginePrivate->offlineStorageDatabaseDirectory() + db.connectionName() + QLatin1String(".ini"), QSettings::IniFormat); ini.setValue(QLatin1String("Version"), to_version); #endif } @@ -723,24 +706,23 @@ void QQuickLocalStorage::openDatabaseSync(QQmlV4Function *args) if (scope.engine->qmlEngine()->offlineStoragePath().isEmpty()) V4THROW_SQL2(SQLEXCEPTION_DATABASE_ERR, QQmlEngine::tr("SQL: can't create database, offline storage is disabled.")); - qmlsqldatabase_initDatabasesPath(scope.engine); - - QSqlDatabase database; - QV4::ScopedValue v(scope); QString dbname = (v = (*args)[0])->toQStringNoThrow(); QString dbversion = (v = (*args)[1])->toQStringNoThrow(); QString dbdescription = (v = (*args)[2])->toQStringNoThrow(); int dbestimatedsize = (v = (*args)[3])->toInt32(); FunctionObject *dbcreationCallback = (v = (*args)[4])->as<FunctionObject>(); - - QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData(dbname.toUtf8()); - QString dbid(QLatin1String(md5.result().toHex())); - - QString basename = qmlsqldatabase_databaseFile(dbid, scope.engine); + QString basename = args->v4engine()->qmlEngine()->offlineStorageDatabaseFilePath(dbname); + QFileInfo dbFile(basename); + if (!QDir().mkpath(dbFile.dir().absolutePath())) { + const QString message = QQmlEngine::tr("LocalStorage: can't create path %1"). + arg(QDir::toNativeSeparators(dbFile.dir().absolutePath())); + V4THROW_SQL2(SQLEXCEPTION_DATABASE_ERR, message); + } + QString dbid = dbFile.fileName(); bool created = false; QString version = dbversion; + QSqlDatabase database; { QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat); @@ -812,7 +794,7 @@ public: } void registerTypes(const char *uri) Q_DECL_OVERRIDE { - Q_ASSERT(QLatin1String(uri) == "QtQuick.LocalStorage"); + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQuick.LocalStorage")); qmlRegisterSingletonType<QQuickLocalStorage>(uri, 2, 0, "LocalStorage", module_api_factory); } }; diff --git a/src/imports/settings/qqmlsettings_p.h b/src/imports/settings/qqmlsettings_p.h index 0ed55579dd..ce942d7564 100644 --- a/src/imports/settings/qqmlsettings_p.h +++ b/src/imports/settings/qqmlsettings_p.h @@ -74,10 +74,10 @@ public: void setCategory(const QString &category); protected: - void timerEvent(QTimerEvent *event); + void timerEvent(QTimerEvent *event) override; - void classBegin(); - void componentComplete(); + void classBegin() override; + void componentComplete() override; private: Q_DISABLE_COPY(QQmlSettings) diff --git a/src/imports/sharedimage/plugin.cpp b/src/imports/sharedimage/plugin.cpp new file mode 100644 index 0000000000..f20edc641c --- /dev/null +++ b/src/imports/sharedimage/plugin.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** 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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qqmlextensionplugin.h> +#include <qqmlengine.h> +#include <sharedimageprovider.h> + + +/*! + \qmlmodule QtQuick.SharedImage 1 + \title Qt Quick Shared Image Provider + \ingroup qmlmodules + \brief Adds an image provider which utilizes shared CPU memory + + \section2 Summary + + This module provides functionality to save memory in use cases where + several Qt Quick applications use the same local image files. It does this + by placing the decoded QImage data in shared system memory, making it + accessible to all the processes (see QSharedMemory). + + This module only shares CPU memory. It does not provide sharing of GPU + memory or textures. + + \section2 Usage + + To use this module, import it like this: + \code + import QtQuick.SharedImage 1.0 + \endcode + + The sharing functionality is provided through a QQuickImageProvider. Use + the "image:" scheme for the URL source of the image, followed by the + identifier \e shared, followed by the image file path. For example: + + \code + Image { source: "image://shared/usr/share/wallpapers/mybackground.jpg" } + \endcode + + This will look for the file \e /usr/share/wallpapers/mybackground.jpg. + The first process that does this will read the image file + using normal Qt image loading. The decoded image data will then be placed + in shared memory, using the full file path as key. Later processes + requesting the same image will discover that the data is already available + in shared memory. They will then use that instead of loading the image file + again. + + The shared image data will be kept available until the last process has deleted + its last reference to the shared image, at which point it is automatically released. + + If system memory sharing is not available, the shared image provider falls + back to normal, unshared image loading. + + The file path must be absolute. To use a relative path, make it absolute + using \e Qt.resolvedUrl() and replace the URL scheme. For example: + + \code + ... + property string imagePrefix: Qt.resolvedUrl("../myimages/").replace("file://", "image://shared/") + Image { source: imagePrefix + "myimage.png" } + \endcode + + The shared image module does not provide any directly usable QML types. +*/ + +static void initResources() +{ +#ifdef QT_STATIC + Q_INIT_RESOURCE(qmake_QtQuick_SharedImage); +#endif +} + +QT_BEGIN_NAMESPACE + +class QtQuickSharedImagePlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QtQuickSharedImagePlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { initResources(); } + + void registerTypes(const char *uri) Q_DECL_OVERRIDE + { + Q_ASSERT(uri == QStringLiteral("QtQuick.SharedImage")); + // Need to register *something* to let our version number be known: + qmlRegisterTypeNotAvailable(uri, 1, 0, "nosuchtype", QStringLiteral("Just a dummy type, do not use")); + } + + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + Q_UNUSED(uri); + engine->addImageProvider("shared", new SharedImageProvider); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/sharedimage/qmldir b/src/imports/sharedimage/qmldir new file mode 100644 index 0000000000..64a5aa8ac1 --- /dev/null +++ b/src/imports/sharedimage/qmldir @@ -0,0 +1,3 @@ +module QtQuick.SharedImage +plugin sharedimageplugin +classname QtQuickSharedImagePlugin diff --git a/src/imports/sharedimage/qsharedimageloader.cpp b/src/imports/sharedimage/qsharedimageloader.cpp new file mode 100644 index 0000000000..65cbd92bb4 --- /dev/null +++ b/src/imports/sharedimage/qsharedimageloader.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** 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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsharedimageloader_p.h" +#include <private/qobject_p.h> +#include <private/qimage_p.h> +#include <QSharedMemory> + + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcSharedImage, "qt.quick.sharedimage"); + +struct SharedImageHeader { + quint8 magic; + quint8 version; + quint16 offset; + qint32 width; + qint32 height; + qint32 bpl; + QImage::Format format; +}; +Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4 == 0); + +#ifndef QT_NO_SHAREDMEMORY +struct SharedImageInfo { + QString path; + QPointer<QSharedMemory> shmp; +}; + +void cleanupSharedImage(void *cleanupInfo) +{ + if (!cleanupInfo) + return; + SharedImageInfo *sii = static_cast<SharedImageInfo *>(cleanupInfo); + qCDebug(lcSharedImage) << "Cleanup called for" << sii->path; + if (sii->shmp.isNull()) { + qCDebug(lcSharedImage) << "shm is 0 for" << sii->path; + return; + } + QSharedMemory *shm = sii->shmp.data(); + sii->shmp.clear(); + delete shm; // destructor detaches + delete sii; +} +#else +void cleanupSharedImage(void *) {} +#endif + +class QSharedImageLoaderPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSharedImageLoader) + +public: + QSharedImageLoaderPrivate() + : QObjectPrivate() + {} + + QImage load(const QString &path, QSharedImageLoader::ImageParameters *params); + + void storeImageToMem(void *data, const QImage &img); + + bool verifyMem(const void *data, int size); + + QImage createImageFromMem(const void *data, void *cleanupInfo); + +}; + + +void QSharedImageLoaderPrivate::storeImageToMem(void *data, const QImage &img) +{ + Q_ASSERT(data && !img.isNull()); + + SharedImageHeader *h = static_cast<SharedImageHeader *>(data); + h->magic = 'Q'; + h->version = 1; + h->offset = sizeof(SharedImageHeader); + h->width = img.width(); + h->height = img.height(); + h->bpl = img.bytesPerLine(); + h->format = img.format(); + + uchar *p = static_cast<uchar *>(data) + sizeof(SharedImageHeader); + memcpy(p, img.constBits(), img.byteCount()); +} + + +bool QSharedImageLoaderPrivate::verifyMem(const void *data, int size) +{ + if (!data || size < int(sizeof(SharedImageHeader))) + return false; + + const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data); + if ((h->magic != 'Q') + || (h->version < 1) + || (h->offset < sizeof(SharedImageHeader)) + || (h->width <= 0) + || (h->height <= 0) + || (h->bpl <= 0) + || (h->format <= QImage::Format_Invalid) + || (h->format >= QImage::NImageFormats)) { + return false; + } + + int availSize = size - h->offset; + if (h->height * h->bpl > availSize) + return false; + if ((qt_depthForFormat(h->format) * h->width * h->height) > (8 * availSize)) + return false; + + return true; +} + + +QImage QSharedImageLoaderPrivate::createImageFromMem(const void *data, void *cleanupInfo) +{ + const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data); + const uchar *p = static_cast<const uchar *>(data) + h->offset; + + QImage img(p, h->width, h->height, h->bpl, h->format, cleanupSharedImage, cleanupInfo); + return img; +} + + +QImage QSharedImageLoaderPrivate::load(const QString &path, QSharedImageLoader::ImageParameters *params) +{ +#ifndef QT_NO_SHAREDMEMORY + Q_Q(QSharedImageLoader); + + QImage nil; + if (path.isEmpty()) + return nil; + + QScopedPointer<QSharedMemory> shm(new QSharedMemory(q->key(path, params))); + bool locked = false; + + if (!shm->attach(QSharedMemory::ReadOnly)) { + QImage img = q->loadFile(path, params); + if (img.isNull()) + return nil; + int size = sizeof(SharedImageHeader) + img.byteCount(); + if (shm->create(size)) { + qCDebug(lcSharedImage) << "Created new shm segment of size" << size << "for image" << path; + if (!shm->lock()) { + qCDebug(lcSharedImage) << "Lock1 failed!?" << shm->errorString(); + return nil; + } + locked = true; + storeImageToMem(shm->data(), img); + } else if (shm->error() == QSharedMemory::AlreadyExists) { + // race handling: other process may have created the share while + // we loaded the image, so try again to just attach + if (!shm->attach(QSharedMemory::ReadOnly)) { + qCDebug(lcSharedImage) << "Attach to existing failed?" << shm->errorString(); + return nil; + } + } else { + qCDebug(lcSharedImage) << "Create failed?" << shm->errorString(); + return nil; + } + } + + Q_ASSERT(shm->isAttached()); + + if (!locked) { + if (!shm->lock()) { + qCDebug(lcSharedImage) << "Lock2 failed!?" << shm->errorString(); + return nil; + } + locked = true; + } + + if (!verifyMem(shm->constData(), shm->size())) { + qCDebug(lcSharedImage) << "Verifymem failed!?"; + shm->unlock(); + return nil; + } + + QSharedMemory *shmp = shm.take(); + SharedImageInfo *sii = new SharedImageInfo; + sii->path = path; + sii->shmp = shmp; + QImage shImg = createImageFromMem(shmp->constData(), sii); + + if (!shmp->unlock()) { + qCDebug(lcSharedImage) << "UnLock failed!?"; + } + + return shImg; +#else + Q_UNUSED(path); + Q_UNUSED(params); + return QImage(); +#endif +} + + +QSharedImageLoader::QSharedImageLoader(QObject *parent) + : QObject(*new QSharedImageLoaderPrivate, parent) +{ +} + +QSharedImageLoader::~QSharedImageLoader() +{ +} + +QImage QSharedImageLoader::load(const QString &path, ImageParameters *params) +{ + Q_D(QSharedImageLoader); + + return d->load(path, params); +} + +QImage QSharedImageLoader::loadFile(const QString &path, ImageParameters *params) +{ + Q_UNUSED(params); + + return QImage(path); +} + +QString QSharedImageLoader::key(const QString &path, ImageParameters *params) +{ + Q_UNUSED(params); + + return path; +} + + +QT_END_NAMESPACE diff --git a/src/imports/sharedimage/qsharedimageloader_p.h b/src/imports/sharedimage/qsharedimageloader_p.h new file mode 100644 index 0000000000..afb50e5088 --- /dev/null +++ b/src/imports/sharedimage/qsharedimageloader_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** 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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSHAREDIMAGELOADER_H +#define QSHAREDIMAGELOADER_H + +#include <QImage> +#include <QVariant> +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcSharedImage); + +class QSharedImageLoaderPrivate; + +class QSharedImageLoader : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSharedImageLoader) + +public: + enum ImageParameter { + OriginalSize = 0, + RequestedSize, + NumImageParameters + }; + typedef QVector<QVariant> ImageParameters; + + QSharedImageLoader(QObject *parent = Q_NULLPTR); + ~QSharedImageLoader(); + + QImage load(const QString &path, ImageParameters *params = Q_NULLPTR); + +protected: + virtual QImage loadFile(const QString &path, ImageParameters *params); + virtual QString key(const QString &path, ImageParameters *params); + +private: + Q_DISABLE_COPY(QSharedImageLoader) +}; + +QT_END_NAMESPACE + +#endif // QSHAREDIMAGELOADER_H diff --git a/src/imports/sharedimage/sharedimage.pro b/src/imports/sharedimage/sharedimage.pro new file mode 100644 index 0000000000..523de66ac1 --- /dev/null +++ b/src/imports/sharedimage/sharedimage.pro @@ -0,0 +1,17 @@ +CXX_MODULE = qml +TARGET = sharedimageplugin +TARGETPATH = QtQuick/SharedImage +IMPORT_VERSION = 1.0 + +QT *= quick qml gui-private core-private + +SOURCES += \ + plugin.cpp \ + sharedimageprovider.cpp \ + qsharedimageloader.cpp + +HEADERS += \ + sharedimageprovider.h \ + qsharedimageloader_p.h + +load(qml_plugin) diff --git a/src/imports/sharedimage/sharedimageprovider.cpp b/src/imports/sharedimage/sharedimageprovider.cpp new file mode 100644 index 0000000000..2dd3a130e9 --- /dev/null +++ b/src/imports/sharedimage/sharedimageprovider.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** 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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <sharedimageprovider.h> +#include <qsharedimageloader_p.h> +#include <qquickimageprovider.h> +#include <private/qimage_p.h> +#include <QImageReader> +#include <QFileInfo> +#include <QDir> + +class QuickSharedImageLoader : public QSharedImageLoader +{ + Q_OBJECT + friend class SharedImageProvider; + +public: + QuickSharedImageLoader(QObject *parent = Q_NULLPTR) + : QSharedImageLoader(parent) + { + } + +protected: + QImage loadFile(const QString &path, ImageParameters *params) override + { + QImageReader imgio(path); + QSize realSize = imgio.size(); + QSize requestSize = params ? params->value(RequestedSize).toSize() : QSize(); + + // Following qquickpixmapcache's readImage, from here... + const bool force_scale = imgio.format() == "svg" || imgio.format() == "svgz"; + + if (requestSize.width() > 0 || requestSize.height() > 0) { + QSize s = realSize; + qreal ratio = 0.0; + if (requestSize.width() && (force_scale || requestSize.width() < s.width())) { + ratio = qreal(requestSize.width())/s.width(); + } + if (requestSize.height() && (force_scale || requestSize.height() < s.height())) { + qreal hr = qreal(requestSize.height())/s.height(); + if (ratio == 0.0 || hr < ratio) + ratio = hr; + } + if (ratio > 0.0) { + s.setHeight(qRound(s.height() * ratio)); + s.setWidth(qRound(s.width() * ratio)); + imgio.setScaledSize(s); + } + } + // ... to here + + QImage image; + if (imgio.read(&image)) { + if (realSize.isEmpty()) + realSize = image.size(); + // Make sure we have acceptable format for texture uploader, or it will convert & lose sharing + // This mimics the testing & conversion normally done by the quick pixmapcache & texturefactory + if (image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied) { + QImage::Format newFmt = QImage::Format_RGB32; + if (image.hasAlphaChannel() && image.data_ptr()->checkForAlphaPixels()) + newFmt = QImage::Format_ARGB32_Premultiplied; + qCDebug(lcSharedImage) << "Convert on load from format" << image.format() << "to" << newFmt; + image = image.convertToFormat(newFmt); + } + } + + if (params && params->count() > OriginalSize) + params->replace(OriginalSize, realSize); + + return image; + } + + QString key(const QString &path, ImageParameters *params) override + { + QSize reqSz = params->value(RequestedSize).toSize(); + if (!reqSz.isValid()) + return path; + + QString key = path + QStringLiteral("_%1x%2").arg(reqSz.width()).arg(reqSz.height()); + qCDebug(lcSharedImage) << "KEY:" << key; + return key; + } +}; + + +SharedImageProvider::SharedImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image), loader(new QuickSharedImageLoader) +{ +} + +QImage SharedImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + QFileInfo fi(QDir::root(), id); + QString path = fi.canonicalFilePath(); + if (path.isEmpty()) + return QImage(); + + QSharedImageLoader::ImageParameters params(QSharedImageLoader::NumImageParameters); + params[QSharedImageLoader::RequestedSize].setValue(requestedSize); + + QImage img = loader->load(path, ¶ms); + if (img.isNull()) { + // May be sharing problem, fall back to normal local load + img = loader->loadFile(path, ¶ms); + if (!img.isNull()) + qCWarning(lcSharedImage) << "Sharing problem; loading" << id << "unshared"; + } + + //... QSize realSize = params.value(QSharedImageLoader::OriginalSize).toSize(); + // quickpixmapcache's readImage() reports back the original size, prior to requestedSize scaling, in the *size + // parameter. That value is currently ignored by quick however, which only cares about the present size of the + // returned image. So handling and sharing of info on pre-scaled size is currently not implemented. + if (size) { + *size = img.size(); + } + + return img; +} + +#include "sharedimageprovider.moc" diff --git a/src/imports/sharedimage/sharedimageprovider.h b/src/imports/sharedimage/sharedimageprovider.h new file mode 100644 index 0000000000..a2f6b6ef2f --- /dev/null +++ b/src/imports/sharedimage/sharedimageprovider.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** 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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHAREDIMAGEPROVIDER_H +#define SHAREDIMAGEPROVIDER_H + +#include <QQuickImageProvider> +#include <QScopedPointer> + +class QuickSharedImageLoader; + +class SharedImageProvider : public QQuickImageProvider +{ +public: + SharedImageProvider(); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + +protected: + QScopedPointer<QuickSharedImageLoader> loader; +}; +#endif // SHAREDIMAGEPROVIDER_H diff --git a/src/imports/statemachine/signaltransition.cpp b/src/imports/statemachine/signaltransition.cpp index 2e6381fc08..44fbf69431 100644 --- a/src/imports/statemachine/signaltransition.cpp +++ b/src/imports/statemachine/signaltransition.cpp @@ -127,7 +127,7 @@ void SignalTransition::setSignal(const QJSValue &signal) Q_ASSERT(sender); signalMethod = sender->metaObject()->method(signalObject->signalIndex()); } else { - qmlInfo(this) << tr("Specified signal does not exist."); + qmlWarning(this) << tr("Specified signal does not exist."); return; } diff --git a/src/imports/statemachine/state.cpp b/src/imports/statemachine/state.cpp index 09d246cc1e..f1294b0de0 100644 --- a/src/imports/statemachine/state.cpp +++ b/src/imports/statemachine/state.cpp @@ -54,7 +54,7 @@ void State::componentComplete() static bool once = false; if (!once) { once = true; - qmlInfo(this) << "No top level StateMachine found. Nothing will run without a StateMachine."; + qmlWarning(this) << "No top level StateMachine found. Nothing will run without a StateMachine."; } } } diff --git a/src/imports/statemachine/state.h b/src/imports/statemachine/state.h index 73e4ecda6b..8e8cefab19 100644 --- a/src/imports/statemachine/state.h +++ b/src/imports/statemachine/state.h @@ -58,8 +58,8 @@ class State : public QState, public QQmlParserStatus public: explicit State(QState *parent = 0); - void classBegin() {} - void componentComplete(); + void classBegin() override {} + void componentComplete() override; QQmlListProperty<QObject> children(); diff --git a/src/imports/statemachine/statemachine.cpp b/src/imports/statemachine/statemachine.cpp index 76de01fe94..a9ea5f7a95 100644 --- a/src/imports/statemachine/statemachine.cpp +++ b/src/imports/statemachine/statemachine.cpp @@ -69,7 +69,7 @@ void StateMachine::setRunning(bool running) void StateMachine::componentComplete() { if (QStateMachine::initialState() == NULL && childMode() == QState::ExclusiveStates) - qmlInfo(this) << "No initial state set for StateMachine"; + qmlWarning(this) << "No initial state set for StateMachine"; // Everything is proper setup, now start the state-machine if we got // asked to do so. diff --git a/src/imports/statemachine/statemachine.h b/src/imports/statemachine/statemachine.h index 59a810f387..cb0ab43f8b 100644 --- a/src/imports/statemachine/statemachine.h +++ b/src/imports/statemachine/statemachine.h @@ -62,8 +62,8 @@ class StateMachine : public QStateMachine, public QQmlParserStatus public: explicit StateMachine(QObject *parent = 0); - void classBegin() {} - void componentComplete(); + void classBegin() override {} + void componentComplete() override; QQmlListProperty<QObject> children(); bool isRunning() const; diff --git a/src/imports/statemachine/timeouttransition.cpp b/src/imports/statemachine/timeouttransition.cpp index 4bb1df3c28..0d208b919b 100644 --- a/src/imports/statemachine/timeouttransition.cpp +++ b/src/imports/statemachine/timeouttransition.cpp @@ -72,7 +72,7 @@ void TimeoutTransition::componentComplete() { QState *state = qobject_cast<QState*>(parent()); if (!state) { - qmlInfo(this) << "Parent needs to be a State"; + qmlWarning(this) << "Parent needs to be a State"; return; } diff --git a/src/imports/statemachine/timeouttransition.h b/src/imports/statemachine/timeouttransition.h index 5d71e86bb4..0e5f5377e3 100644 --- a/src/imports/statemachine/timeouttransition.h +++ b/src/imports/statemachine/timeouttransition.h @@ -59,8 +59,8 @@ public: int timeout() const; void setTimeout(int timeout); - void classBegin() {} - void componentComplete(); + void classBegin() override {} + void componentComplete() override; Q_SIGNALS: void timeoutChanged(); diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml index d22ec7c44f..18c70e1169 100644 --- a/src/imports/testlib/TestCase.qml +++ b/src/imports/testlib/TestCase.qml @@ -206,6 +206,59 @@ import Qt.test.qtestroot 1.0 will fail. Use the \l when and windowShown properties to track when the main window has been shown. + \section1 Managing Dynamically Created Test Objects + + A typical pattern with QML tests is to + \l {Dynamic QML Object Creation from JavaScript}{dynamically create} + an item and then destroy it at the end of the test function: + + \code + TestCase { + id: testCase + name: "MyTest" + when: windowShown + + function test_click() { + var item = Qt.createQmlObject("import QtQuick 2.0; Item {}", testCase); + verify(item); + + // Test item... + + item.destroy(); + } + } + \endcode + + The problem with this pattern is that any failures in the test function + will cause the call to \c item.destroy() to be skipped, leaving the item + hanging around in the scene until the test case has finished. This can + result in interference with future tests; for example, by blocking input + events or producing unrelated debug output that makes it difficult to + follow the code's execution. + + By calling \l createTemporaryQmlObject() instead, the object is guaranteed + to be destroyed at the end of the test function: + + \code + TestCase { + id: testCase + name: "MyTest" + when: windowShown + + function test_click() { + var item = createTemporaryQmlObject("import QtQuick 2.0; Item {}", testCase); + verify(item); + + // Test item... + + // Don't need to worry about destroying "item" here. + } + } + \endcode + + For objects that are created via the \l {Component::}{createObject()} function + of \l Component, the \l createTemporaryObject() function can be used. + \sa {QtTest::SignalSpy}{SignalSpy}, {Qt Quick Test Reference Documentation} */ @@ -358,6 +411,8 @@ Item { /*! \internal */ property var qtest_events: qtest_events_normal TestEvent { id: qtest_events_normal } + /*! \internal */ + property var qtest_temporaryObjects: [] /*! \qmlmethod TestCase::fail(message = "") @@ -461,6 +516,105 @@ Item { throw new Error("QtQuickTest::fail") } + /*! + \since 5.9 + \qmlmethod object TestCase::createTemporaryQmlObject(string qml, object parent, string filePath) + + This function dynamically creates a QML object from the given \a qml + string with the specified \a parent. The returned object will be + destroyed (if it was not already) after \l cleanup() has finished + executing, meaning that objects created with this function are + guaranteed to be destroyed after each test, regardless of whether or + not the tests fail. + + If there was an error while creating the object, \c null will be + returned. + + If \a filePath is specified, it will be used for error reporting for + the created object. + + This function calls + \l {QtQml::Qt::createQmlObject()}{Qt.createQmlObject()} internally. + + \sa {Managing Dynamically Created Test Objects} + */ + function createTemporaryQmlObject(qml, parent, filePath) { + if (typeof qml !== "string") { + qtest_results.fail("First argument must be a string of QML; actual type is " + typeof qml, + util.callerFile(), util.callerLine()); + throw new Error("QtQuickTest::fail"); + } + + if (!parent || typeof parent !== "object") { + qtest_results.fail("Second argument must be a valid parent object; actual type is " + typeof parent, + util.callerFile(), util.callerLine()); + throw new Error("QtQuickTest::fail"); + } + + if (filePath !== undefined && typeof filePath !== "string") { + qtest_results.fail("Third argument must be a file path string; actual type is " + typeof filePath, + util.callerFile(), util.callerLine()); + throw new Error("QtQuickTest::fail"); + } + + var object = Qt.createQmlObject(qml, parent, filePath); + qtest_temporaryObjects.push(object); + return object; + } + + /*! + \since 5.9 + \qmlmethod object TestCase::createTemporaryObject(Component component, object parent, object properties) + + This function dynamically creates a QML object from the given + \a component with the specified optional \a parent and \a properties. + The returned object will be destroyed (if it was not already) after + \l cleanup() has finished executing, meaning that objects created with + this function are guaranteed to be destroyed after each test, + regardless of whether or not the tests fail. + + If there was an error while creating the object, \c null will be + returned. + + This function calls + \l {QtQml::Component::createObject()}{component.createObject()} + internally. + + \sa {Managing Dynamically Created Test Objects} + */ + function createTemporaryObject(component, parent, properties) { + if (typeof component !== "object") { + qtest_results.fail("First argument must be a Component; actual type is " + typeof component, + util.callerFile(), util.callerLine()); + throw new Error("QtQuickTest::fail"); + } + + if (properties && typeof properties !== "object") { + qtest_results.fail("Third argument must be an object; actual type is " + typeof properties, + util.callerFile(), util.callerLine()); + throw new Error("QtQuickTest::fail"); + } + + var object = component.createObject(parent, properties ? properties : ({})); + qtest_temporaryObjects.push(object); + return object; + } + + /*! + \internal + + Destroys all temporary objects that still exist. + */ + function qtest_destroyTemporaryObjects() { + for (var i = 0; i < qtest_temporaryObjects.length; ++i) { + var temporaryObject = qtest_temporaryObjects[i]; + // ### the typeof check can be removed when QTBUG-57749 is fixed + if (temporaryObject && typeof temporaryObject.destroy === "function") + temporaryObject.destroy(); + } + qtest_temporaryObjects = []; + } + /*! \internal */ // Determine what is o. // Discussions and reference: http://philrathe.com/articles/equiv @@ -1317,6 +1471,109 @@ Item { qtest_fail("window not shown", 2) } + /*! + \qmlmethod TouchEventSequence TestCase::touchEvent(object item) + + \since 5.9 + + Begins a sequence of touch events through a simulated QTouchDevice::TouchScreen. + Events are delivered to the window containing \a item. + + The returned object is used to enumerate events to be delivered through a single + QTouchEvent. Touches are delivered to the window containing the TestCase unless + otherwise specified. + + \code + Rectangle { + width: 640; height: 480 + + MultiPointTouchArea { + id: area + anchors.fill: parent + + property bool touched: false + + onPressed: touched = true + } + + TestCase { + name: "ItemTests" + when: area.pressed + id: test1 + + function test_touch() { + var touch = touchEvent(area); + touch.press(0, area, 10, 10); + touch.commit(); + verify(area.touched); + } + } + } + \endcode + + \sa TouchEventSequence::press(), TouchEventSequence::move(), TouchEventSequence::release(), TouchEventSequence::stationary(), TouchEventSequence::commit(), QTouchDevice::TouchScreen + */ + + function touchEvent(item) { + if (!item) + qtest_fail("No item given to touchEvent", 1) + + return { + _defaultItem: item, + _sequence: qtest_events.touchEvent(item), + + press: function (id, target, x, y) { + if (!target) + target = this._defaultItem; + if (id === undefined) + qtest_fail("No id given to TouchEventSequence::press", 1); + if (x === undefined) + x = target.width / 2; + if (y === undefined) + y = target.height / 2; + this._sequence.press(id, target, x, y); + return this; + }, + + move: function (id, target, x, y) { + if (!target) + target = this._defaultItem; + if (id === undefined) + qtest_fail("No id given to TouchEventSequence::move", 1); + if (x === undefined) + x = target.width / 2; + if (y === undefined) + y = target.height / 2; + this._sequence.move(id, target, x, y); + return this; + }, + + stationary: function (id) { + if (id === undefined) + qtest_fail("No id given to TouchEventSequence::stationary", 1); + this._sequence.stationary(id); + return this; + }, + + release: function (id, target, x, y) { + if (!target) + target = this._defaultItem; + if (id === undefined) + qtest_fail("No id given to TouchEventSequence::release", 1); + if (x === undefined) + x = target.width / 2; + if (y === undefined) + y = target.height / 2; + this._sequence.release(id, target, x, y); + return this; + }, + + commit: function () { + this._sequence.commit(); + return this; + } + }; + } // Functions that can be overridden in subclasses for init/cleanup duties. /*! @@ -1389,6 +1646,7 @@ Item { qtest_runInternal(prop, arg) qtest_results.finishTestData() qtest_runInternal("cleanup") + qtest_destroyTemporaryObjects() qtest_results.finishTestDataCleanup() // wait(0) will call processEvents() so objects marked for deletion // in the test function will be deleted. diff --git a/src/imports/testlib/main.cpp b/src/imports/testlib/main.cpp index 4e2bd919e9..3c28000e35 100644 --- a/src/imports/testlib/main.cpp +++ b/src/imports/testlib/main.cpp @@ -158,6 +158,7 @@ public: qmlRegisterType<QuickTestResult, 1>(uri,1,1,"TestResult"); qmlRegisterType<QuickTestEvent>(uri,1,0,"TestEvent"); qmlRegisterType<QuickTestUtil>(uri,1,0,"TestUtil"); + qmlRegisterType<QQuickTouchEventSequence>(); } }; diff --git a/src/imports/testlib/qmldir b/src/imports/testlib/qmldir index 9da8ebb4be..e5757f6a88 100644 --- a/src/imports/testlib/qmldir +++ b/src/imports/testlib/qmldir @@ -3,4 +3,5 @@ plugin qmltestplugin classname QTestQmlModule typeinfo plugins.qmltypes TestCase 1.0 TestCase.qml +TestCase 1.2 TestCase.qml SignalSpy 1.0 SignalSpy.qml diff --git a/src/imports/testlib/toucheventsequence.qdoc b/src/imports/testlib/toucheventsequence.qdoc new file mode 100644 index 0000000000..f85a1cd4f9 --- /dev/null +++ b/src/imports/testlib/toucheventsequence.qdoc @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Jeremy Katz +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmltype TouchEventSequence + \inqmlmodule QtTest + \ingroup qtquicktest + \brief TouchEventSequence is used to build and dispatch touch events + for testing. + + \since 5.9 + + A TouchEventSequence is created by calling \l [QML] {TestCase::touchEvent()}{TestCase.touchEvent()}. + The type can not be directly instantiated. Each method provided by the type returns + the same object, allowing chained calls. + + For example: + \code + touchEvent(item).press(0).commit(); + \endcode + is equivalent to: + \code + var sequence = touchEvent(item); + sequence.press(0); + sequence.commit(); + \endcode + + Events are delivered to the window which contains the item specified in touchEvent. + + \sa TestCase::touchEvent(), QTest::QTouchEventSequence +*/ + +/*! + \qmlmethod TouchEventSequence TouchEventSequence::press(int touchId, object item, real x = item.width / 2, real y = item.height / 2) + + Creates a new point identified as \a touchId, at the point indicated by \a x and \a y relative to \a item. + Further use of the same touch point should maintain the same touchId. + + Item defaults to the value provided via touchEvent(). + X and y default to the midpoint of the item. +*/ + +/*! + \qmlmethod TouchEventSequence TouchEventSequence::move(int touchId, object item, real x = item.width / 2, real y = item.height / 2) + + Moves \a touchId to the point indicated by \a x and \a y relative to \a item. + + Item defaults to the value provided via touchEvent(). + X and y default to the midpoint of the item. +*/ + +/*! + \qmlmethod TouchEventSequence TouchEventSequence::release(int touchId, object item, real x = item.width / 2, real y = item.height / 2) + + Removes \a touchId at the point indicated by \a x and \a y relative to \a item. + + Item defaults to the value provided via touchEvent(). + X and y default to the midpoint of the item. +*/ + +/*! + \qmlmethod TouchEventSequence TouchEventSequence::stationary(int touchId) + + Indicates that \a touchId is present but otherwise unchanged from prior events. +*/ + +/*! + \qmlmethod TouchEventSequence TouchEventSequence::commit() + + Sends the touch event composed by prior use of press(), move(), release(), and stationary(). + Following commit's return, the TouchEventSequence can be used to compose a new event. + + \code + var sequence = touchEvent(target); + // Touch the middle of target with 1 point + sequence.press(1); + sequence.commit(); + + // Begin a new event + // Move the point to target's upper left corner + sequence.move(1, target, 0, 0); + sequence.commit(); + \endcode + + Commit is automatically invoked when the TouchEventSequence object is destroyed. +*/ diff --git a/src/imports/xmllistmodel/qqmlxmllistmodel.cpp b/src/imports/xmllistmodel/qqmlxmllistmodel.cpp index 6e9e57a046..61c8665a14 100644 --- a/src/imports/xmllistmodel/qqmlxmllistmodel.cpp +++ b/src/imports/xmllistmodel/qqmlxmllistmodel.cpp @@ -176,7 +176,7 @@ public: QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *); void processJobs(); - virtual bool event(QEvent *e); + bool event(QEvent *e) override; private: QQuickXmlQueryEngine *m_queryEngine; @@ -202,7 +202,7 @@ signals: void error(void*, const QString&); protected: - void run(); + void run() override; private: void processQuery(XmlQueryJob *job); @@ -596,7 +596,7 @@ void QQuickXmlListModelPrivate::append_role(QQmlListProperty<QQuickXmlListModelR int i = _this->d_func()->roleObjects.count(); _this->d_func()->roleObjects.append(role); if (_this->d_func()->roleNames.contains(role->name())) { - qmlInfo(role) << QQuickXmlListModel::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->name()); + qmlWarning(role) << QQuickXmlListModel::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->name()); return; } _this->d_func()->roles.insert(i, _this->d_func()->highestRole); @@ -847,7 +847,7 @@ void QQuickXmlListModel::setQuery(const QString &query) { Q_D(QQuickXmlListModel); if (!query.startsWith(QLatin1Char('/'))) { - qmlInfo(this) << QCoreApplication::translate("QQuickXmlRoleList", "An XmlListModel query must start with '/' or \"//\""); + qmlWarning(this) << QCoreApplication::translate("QQuickXmlRoleList", "An XmlListModel query must start with '/' or \"//\""); return; } @@ -1136,11 +1136,11 @@ void QQuickXmlListModel::queryError(void* object, const QString& error) Q_D(QQuickXmlListModel); for (int i=0; i<d->roleObjects.count(); i++) { if (d->roleObjects.at(i) == static_cast<QQuickXmlListModelRole*>(object)) { - qmlInfo(d->roleObjects.at(i)) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error); + qmlWarning(d->roleObjects.at(i)) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error); return; } } - qmlInfo(this) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error); + qmlWarning(this) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error); } void QQuickXmlListModel::queryCompleted(const QQuickXmlQueryResult &result) diff --git a/src/imports/xmllistmodel/qqmlxmllistmodel_p.h b/src/imports/xmllistmodel/qqmlxmllistmodel_p.h index f0096a9125..e6a0898bb9 100644 --- a/src/imports/xmllistmodel/qqmlxmllistmodel_p.h +++ b/src/imports/xmllistmodel/qqmlxmllistmodel_p.h @@ -79,7 +79,6 @@ class QQuickXmlListModel : public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) - Q_ENUMS(Status) Q_PROPERTY(Status status READ status NOTIFY statusChanged) Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged) @@ -95,10 +94,10 @@ public: QQuickXmlListModel(QObject *parent = 0); ~QQuickXmlListModel(); - QModelIndex index(int row, int column, const QModelIndex &parent) const; - int rowCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; - QHash<int, QByteArray> roleNames() const; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash<int, QByteArray> roleNames() const override; int count() const; @@ -119,13 +118,14 @@ public: Q_INVOKABLE QQmlV4Handle get(int index) const; enum Status { Null, Ready, Loading, Error }; + Q_ENUM(Status) Status status() const; qreal progress() const; Q_INVOKABLE QString errorString() const; - virtual void classBegin(); - virtual void componentComplete(); + void classBegin() override; + void componentComplete() override; Q_SIGNALS: void statusChanged(QQuickXmlListModel::Status); @@ -177,7 +177,7 @@ public: void setQuery(const QString &query) { if (query.startsWith(QLatin1Char('/'))) { - qmlInfo(this) << tr("An XmlRole query must not start with '/'"); + qmlWarning(this) << tr("An XmlRole query must not start with '/'"); return; } if (m_query == query) @@ -194,7 +194,7 @@ public: Q_EMIT isKeyChanged(); } - bool isValid() { + bool isValid() const { return !m_name.isEmpty() && !m_query.isEmpty(); } |