Merge remote-tracking branch 'origin/5.9' into 5.10
Change-Id: I41ca9120a470a905c2f5c168c1de4cf970fa0fff
This commit is contained in:
commit
2e65f6c2a5
|
@ -1466,6 +1466,9 @@ QV4::CompiledData::Unit *QmlUnitGenerator::generate(Document &output, const QV4:
|
|||
QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(data);
|
||||
qmlUnit->unitSize = totalSize;
|
||||
qmlUnit->flags |= QV4::CompiledData::Unit::IsQml;
|
||||
// This unit's memory was allocated with malloc on the heap, so it's
|
||||
// definitely not suitable for StaticData access.
|
||||
qmlUnit->flags &= ~QV4::CompiledData::Unit::StaticData;
|
||||
qmlUnit->offsetToImports = unitSize;
|
||||
qmlUnit->nImports = output.imports.count();
|
||||
qmlUnit->offsetToObjects = unitSize + importSize;
|
||||
|
|
|
@ -324,6 +324,19 @@
|
|||
qmlRegisterSingletonType("Qt.example.qjsvalueApi", 1, 0, "MyApi", example_qjsvalue_singletontype_provider);
|
||||
\endcode
|
||||
|
||||
Alternatively, you can use a C++11 lambda:
|
||||
|
||||
\code
|
||||
qmlRegisterSingletonType("Qt.example.qjsvalueApi", 1, 0, "MyApi", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue {
|
||||
Q_UNUSED(engine)
|
||||
|
||||
static int seedValue = 5;
|
||||
QJSValue example = scriptEngine->newObject();
|
||||
example.setProperty("someProperty", seedValue++);
|
||||
return example;
|
||||
});
|
||||
\endcode
|
||||
|
||||
In order to use the registered singleton type in QML, you must import the singleton type.
|
||||
\qml
|
||||
import QtQuick 2.0
|
||||
|
@ -423,6 +436,18 @@
|
|||
qmlRegisterSingletonType<SingletonTypeExample>("Qt.example.qobjectSingleton", 1, 0, "MyApi", example_qobject_singletontype_provider);
|
||||
\endcode
|
||||
|
||||
Alternatively, you can use a C++11 lambda:
|
||||
|
||||
\code
|
||||
qmlRegisterSingletonType<SingletonTypeExample>("Qt.example.qjsvalueApi", 1, 0, "MyApi", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
|
||||
Q_UNUSED(engine)
|
||||
Q_UNUSED(scriptEngine)
|
||||
|
||||
SingletonTypeExample *example = new SingletonTypeExample();
|
||||
return example;
|
||||
});
|
||||
\endcode
|
||||
|
||||
In order to use the registered singleton type in QML, you must import the singleton type.
|
||||
\qml
|
||||
import QtQuick 2.0
|
||||
|
|
|
@ -38,6 +38,8 @@ imperative code, in the case where complex custom application behavior is needed
|
|||
|
||||
QML source code is generally loaded by the engine through QML \e documents, which are
|
||||
standalone documents of QML code. These can be used to define \l {QML Object Types}{QML object types} that can then be reused throughout an application.
|
||||
Note that type names must begin with an uppercase letter in order
|
||||
to be declared as QML object types in a QML file.
|
||||
|
||||
|
||||
\section1 Import Statements
|
||||
|
|
|
@ -45,6 +45,8 @@ type, as discussed in \l {qtqml-documents-definetypes.html}
|
|||
{Documents as QML object type definitions}, or by defining a QML type from C++
|
||||
and registering the type with the QML engine, as discussed in
|
||||
\l{qtqml-cppintegration-definetypes.html}{Defining QML Types from C++}.
|
||||
Note that in both cases, the type name must begin with an uppercase letter in
|
||||
order to be declared as a QML object type in a QML file.
|
||||
|
||||
|
||||
\section1 Defining Object Types from QML
|
||||
|
|
|
@ -68,6 +68,22 @@ Page *getPage(Value *val) {
|
|||
return reinterpret_cast<Page *>(reinterpret_cast<quintptr>(val) & ~((quintptr)(WTF::pageSize() - 1)));
|
||||
}
|
||||
|
||||
QML_NEARLY_ALWAYS_INLINE void insertInFront(PersistentValueStorage *storage, Page *p)
|
||||
{
|
||||
p->header.next = reinterpret_cast<Page *>(storage->firstPage);
|
||||
p->header.prev = reinterpret_cast<Page **>(&storage->firstPage);
|
||||
if (p->header.next)
|
||||
p->header.next->header.prev = &p->header.next;
|
||||
storage->firstPage = p;
|
||||
}
|
||||
|
||||
QML_NEARLY_ALWAYS_INLINE void unlink(Page *p)
|
||||
{
|
||||
if (p->header.prev)
|
||||
*p->header.prev = p->header.next;
|
||||
if (p->header.next)
|
||||
p->header.next->header.prev = p->header.prev;
|
||||
}
|
||||
|
||||
Page *allocatePage(PersistentValueStorage *storage)
|
||||
{
|
||||
|
@ -78,19 +94,14 @@ Page *allocatePage(PersistentValueStorage *storage)
|
|||
|
||||
p->header.engine = storage->engine;
|
||||
p->header.alloc = page;
|
||||
p->header.next = reinterpret_cast<Page *>(storage->firstPage);
|
||||
p->header.prev = reinterpret_cast<Page **>(&storage->firstPage);
|
||||
p->header.refCount = 0;
|
||||
p->header.freeList = 0;
|
||||
if (p->header.next)
|
||||
p->header.next->header.prev = &p->header.next;
|
||||
insertInFront(storage, p);
|
||||
for (int i = 0; i < kEntriesPerPage - 1; ++i) {
|
||||
p->values[i].setEmpty(i + 1);
|
||||
}
|
||||
p->values[kEntriesPerPage - 1].setEmpty(-1);
|
||||
|
||||
storage->firstPage = p;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -195,6 +206,12 @@ Value *PersistentValueStorage::allocate()
|
|||
|
||||
Value *v = p->values + p->header.freeList;
|
||||
p->header.freeList = v->int_32();
|
||||
|
||||
if (p->header.freeList != -1 && p != firstPage) {
|
||||
unlink(p);
|
||||
insertInFront(this, p);
|
||||
}
|
||||
|
||||
++p->header.refCount;
|
||||
|
||||
v->setRawValue(Encode::undefined());
|
||||
|
@ -237,10 +254,7 @@ ExecutionEngine *PersistentValueStorage::getEngine(Value *v)
|
|||
void PersistentValueStorage::freePage(void *page)
|
||||
{
|
||||
Page *p = static_cast<Page *>(page);
|
||||
if (p->header.prev)
|
||||
*p->header.prev = p->header.next;
|
||||
if (p->header.next)
|
||||
p->header.next->header.prev = p->header.prev;
|
||||
unlink(p);
|
||||
p->header.alloc.deallocate();
|
||||
}
|
||||
|
||||
|
|
|
@ -2080,29 +2080,38 @@ bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &ba
|
|||
// Dynamic plugins are differentiated by their filepath. For static plugins we
|
||||
// don't have that information so we use their address as key instead.
|
||||
const QString uniquePluginID = QString::asprintf("%p", instance);
|
||||
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
|
||||
QMutexLocker lock(&plugins->mutex);
|
||||
{
|
||||
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
|
||||
QMutexLocker lock(&plugins->mutex);
|
||||
|
||||
// Plugin types are global across all engines and should only be
|
||||
// registered once. But each engine still needs to be initialized.
|
||||
bool typesRegistered = plugins->contains(uniquePluginID);
|
||||
bool engineInitialized = initializedPlugins.contains(uniquePluginID);
|
||||
// Plugin types are global across all engines and should only be
|
||||
// registered once. But each engine still needs to be initialized.
|
||||
bool typesRegistered = plugins->contains(uniquePluginID);
|
||||
|
||||
if (typesRegistered) {
|
||||
Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
|
||||
"QQmlImportDatabase::importStaticPlugin",
|
||||
"Internal error: Static plugin imported previously with different uri");
|
||||
} else {
|
||||
RegisteredPlugin plugin;
|
||||
plugin.uri = uri;
|
||||
plugin.loader = 0;
|
||||
plugins->insert(uniquePluginID, plugin);
|
||||
if (typesRegistered) {
|
||||
Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
|
||||
"QQmlImportDatabase::importStaticPlugin",
|
||||
"Internal error: Static plugin imported previously with different uri");
|
||||
} else {
|
||||
RegisteredPlugin plugin;
|
||||
plugin.uri = uri;
|
||||
plugin.loader = 0;
|
||||
plugins->insert(uniquePluginID, plugin);
|
||||
|
||||
if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
|
||||
return false;
|
||||
if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release the lock on plugins early as we're done with the global part. Releasing the lock
|
||||
// also allows other QML loader threads to acquire the lock while this thread is blocking
|
||||
// in the initializeEngine call to the gui thread (which in turn may be busy waiting for
|
||||
// other QML loader threads and thus not process the initializeEngine call).
|
||||
}
|
||||
|
||||
if (!engineInitialized) {
|
||||
// The plugin's per-engine initialization does not need lock protection, as this function is
|
||||
// only called from the engine specific loader thread and importDynamicPlugin as well as
|
||||
// importStaticPlugin are the only places of access.
|
||||
if (!initializedPlugins.contains(uniquePluginID)) {
|
||||
initializedPlugins.insert(uniquePluginID);
|
||||
|
||||
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
|
||||
|
@ -2124,68 +2133,77 @@ bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QStr
|
|||
QFileInfo fileInfo(filePath);
|
||||
const QString absoluteFilePath = fileInfo.absoluteFilePath();
|
||||
|
||||
QObject *instance = nullptr;
|
||||
bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
|
||||
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
|
||||
QMutexLocker lock(&plugins->mutex);
|
||||
bool typesRegistered = plugins->contains(absoluteFilePath);
|
||||
{
|
||||
StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
|
||||
QMutexLocker lock(&plugins->mutex);
|
||||
bool typesRegistered = plugins->contains(absoluteFilePath);
|
||||
|
||||
if (typesRegistered) {
|
||||
Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
|
||||
"QQmlImportDatabase::importDynamicPlugin",
|
||||
"Internal error: Plugin imported previously with different uri");
|
||||
}
|
||||
|
||||
if (!engineInitialized || !typesRegistered) {
|
||||
if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
|
||||
if (errors) {
|
||||
QQmlError error;
|
||||
error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
|
||||
errors->prepend(error);
|
||||
}
|
||||
return false;
|
||||
if (typesRegistered) {
|
||||
Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
|
||||
"QQmlImportDatabase::importDynamicPlugin",
|
||||
"Internal error: Plugin imported previously with different uri");
|
||||
}
|
||||
|
||||
QPluginLoader* loader = 0;
|
||||
if (!typesRegistered) {
|
||||
loader = new QPluginLoader(absoluteFilePath);
|
||||
|
||||
if (!loader->load()) {
|
||||
if (!engineInitialized || !typesRegistered) {
|
||||
if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
|
||||
if (errors) {
|
||||
QQmlError error;
|
||||
error.setDescription(loader->errorString());
|
||||
error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
|
||||
errors->prepend(error);
|
||||
}
|
||||
delete loader;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
loader = plugins->value(absoluteFilePath).loader;
|
||||
|
||||
QPluginLoader* loader = 0;
|
||||
if (!typesRegistered) {
|
||||
loader = new QPluginLoader(absoluteFilePath);
|
||||
|
||||
if (!loader->load()) {
|
||||
if (errors) {
|
||||
QQmlError error;
|
||||
error.setDescription(loader->errorString());
|
||||
errors->prepend(error);
|
||||
}
|
||||
delete loader;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
loader = plugins->value(absoluteFilePath).loader;
|
||||
}
|
||||
|
||||
instance = loader->instance();
|
||||
|
||||
if (!typesRegistered) {
|
||||
RegisteredPlugin plugin;
|
||||
plugin.uri = uri;
|
||||
plugin.loader = loader;
|
||||
plugins->insert(absoluteFilePath, plugin);
|
||||
|
||||
// Continue with shared code path for dynamic and static plugins:
|
||||
if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QObject *instance = loader->instance();
|
||||
// Release the lock on plugins early as we're done with the global part. Releasing the lock
|
||||
// also allows other QML loader threads to acquire the lock while this thread is blocking
|
||||
// in the initializeEngine call to the gui thread (which in turn may be busy waiting for
|
||||
// other QML loader threads and thus not process the initializeEngine call).
|
||||
}
|
||||
|
||||
if (!typesRegistered) {
|
||||
RegisteredPlugin plugin;
|
||||
plugin.uri = uri;
|
||||
plugin.loader = loader;
|
||||
plugins->insert(absoluteFilePath, plugin);
|
||||
|
||||
// Continue with shared code path for dynamic and static plugins:
|
||||
if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
|
||||
return false;
|
||||
if (!engineInitialized) {
|
||||
// The plugin's per-engine initialization does not need lock protection, as this function is
|
||||
// only called from the engine specific loader thread and importDynamicPlugin as well as
|
||||
// importStaticPlugin are the only places of access.
|
||||
initializedPlugins.insert(absoluteFilePath);
|
||||
|
||||
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
|
||||
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
|
||||
ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
|
||||
}
|
||||
|
||||
if (!engineInitialized) {
|
||||
// things on the engine (eg. adding new global objects) have to be done for every
|
||||
// engine.
|
||||
// XXX protect against double initialization
|
||||
initializedPlugins.insert(absoluteFilePath);
|
||||
|
||||
if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
|
||||
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
|
||||
ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1569,6 +1569,12 @@ QString registrationTypeString(QQmlType::RegistrationType typeType)
|
|||
bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *data, const char *uri, const QString &typeName, int majorVersion = -1)
|
||||
{
|
||||
if (!typeName.isEmpty()) {
|
||||
if (typeName.at(0).isLower()) {
|
||||
QString failure(QCoreApplication::translate("qmlRegisterType", "Invalid QML %1 name \"%2\"; type names must begin with an uppercase letter"));
|
||||
data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName));
|
||||
return false;
|
||||
}
|
||||
|
||||
int typeNameLen = typeName.length();
|
||||
for (int ii = 0; ii < typeNameLen; ++ii) {
|
||||
if (!(typeName.at(ii).isLetterOrNumber() || typeName.at(ii) == '_')) {
|
||||
|
@ -1814,6 +1820,9 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
|
|||
else
|
||||
return -1;
|
||||
|
||||
if (!dtype.isValid())
|
||||
return -1;
|
||||
|
||||
QMutexLocker lock(metaTypeDataLock());
|
||||
QQmlMetaTypeData *typeData = metaTypeData();
|
||||
typeData->undeletableTypes.insert(dtype);
|
||||
|
|
|
@ -98,6 +98,8 @@ void Heap::QQmlValueTypeWrapper::destroy()
|
|||
valueType->metaType.destruct(gadgetPtr);
|
||||
::operator delete(gadgetPtr);
|
||||
}
|
||||
if (_propertyCache)
|
||||
_propertyCache->release();
|
||||
Object::destroy();
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ QObjectList or a \l QAbstractItemModel. The first three are useful for exposing
|
|||
simpler datasets, while QAbstractItemModel provides a more flexible solution for
|
||||
more complex models.
|
||||
|
||||
For a video tutorial that takes you through the whole process of exposing a C++
|
||||
model to QML, see the
|
||||
\l {https://youtu.be/9BcAYDlpuT8}{Using C++ Models in QML Tutorial}.
|
||||
|
||||
\section2 QStringList-based Model
|
||||
|
||||
A model may be a simple \l QStringList, which provides the contents of the list
|
||||
|
|
|
@ -1894,6 +1894,9 @@ void QQuickItemViewPrivate::layout()
|
|||
|
||||
inLayout = true;
|
||||
|
||||
// viewBounds contains bounds before any add/remove/move operation to the view
|
||||
QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
|
||||
|
||||
if (!isValid() && !visibleItems.count()) {
|
||||
clear();
|
||||
setPosition(contentStartOffset());
|
||||
|
@ -1960,7 +1963,6 @@ void QQuickItemViewPrivate::layout()
|
|||
|
||||
prepareVisibleItemTransitions();
|
||||
|
||||
QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
|
||||
for (QList<FxViewItem*>::Iterator it = releasePendingTransition.begin();
|
||||
it != releasePendingTransition.end(); ) {
|
||||
FxViewItem *item = *it;
|
||||
|
|
|
@ -686,6 +686,13 @@ void QQuickLoaderPrivate::incubatorStateChanged(QQmlIncubator::Status status)
|
|||
if (status == QQmlIncubator::Ready) {
|
||||
object = incubator->object();
|
||||
item = qmlobject_cast<QQuickItem*>(object);
|
||||
if (!item) {
|
||||
QQuickWindow *window = qmlobject_cast<QQuickWindow*>(object);
|
||||
if (window) {
|
||||
qCDebug(lcTransient) << window << "is transient for" << q->window();
|
||||
window->setTransientParent(q->window());
|
||||
}
|
||||
}
|
||||
emit q->itemChanged();
|
||||
initResize();
|
||||
incubator->clear();
|
||||
|
@ -830,6 +837,18 @@ void QQuickLoader::componentComplete()
|
|||
}
|
||||
}
|
||||
|
||||
void QQuickLoader::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
|
||||
{
|
||||
if (change == ItemSceneChange) {
|
||||
QQuickWindow *loadedWindow = qmlobject_cast<QQuickWindow *>(item());
|
||||
if (loadedWindow) {
|
||||
qCDebug(lcTransient) << loadedWindow << "is transient for" << value.window;
|
||||
loadedWindow->setTransientParent(value.window);
|
||||
}
|
||||
}
|
||||
QQuickItem::itemChange(change, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlsignal QtQuick::Loader::loaded()
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ Q_SIGNALS:
|
|||
protected:
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE;
|
||||
void componentComplete() Q_DECL_OVERRIDE;
|
||||
void itemChange(ItemChange change, const ItemChangeData &value) override;
|
||||
|
||||
private:
|
||||
void setSource(const QUrl &sourceUrl, bool needsClear);
|
||||
|
|
|
@ -1108,7 +1108,8 @@ void QQuickTextInputPrivate::checkIsValid()
|
|||
Q_Q(QQuickTextInput);
|
||||
|
||||
ValidatorState state = hasAcceptableInput(m_text);
|
||||
m_validInput = state != InvalidInput;
|
||||
if (!m_maskData)
|
||||
m_validInput = state != InvalidInput;
|
||||
if (state != AcceptableInput) {
|
||||
if (m_acceptableInput) {
|
||||
m_acceptableInput = false;
|
||||
|
@ -3561,11 +3562,15 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
|
|||
#if QT_CONFIG(validator)
|
||||
if (m_validator) {
|
||||
QString textCopy = m_text;
|
||||
if (m_maskData)
|
||||
textCopy = maskString(0, m_text, true);
|
||||
int cursorCopy = m_cursor;
|
||||
QValidator::State state = m_validator->validate(textCopy, cursorCopy);
|
||||
if (m_maskData)
|
||||
textCopy = m_text;
|
||||
m_validInput = state != QValidator::Invalid;
|
||||
m_acceptableInput = state == QValidator::Acceptable;
|
||||
if (m_validInput) {
|
||||
if (m_validInput && !m_maskData) {
|
||||
if (m_text != textCopy) {
|
||||
internalSetText(textCopy, cursorCopy);
|
||||
return true;
|
||||
|
@ -3574,31 +3579,8 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_maskData) {
|
||||
m_validInput = true;
|
||||
if (m_text.length() != m_maxLength) {
|
||||
m_validInput = false;
|
||||
m_acceptableInput = false;
|
||||
} else {
|
||||
for (int i = 0; i < m_maxLength; ++i) {
|
||||
if (m_maskData[i].separator) {
|
||||
if (m_text.at(i) != m_maskData[i].maskChar) {
|
||||
m_validInput = false;
|
||||
m_acceptableInput = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!isValidInput(m_text.at(i), m_maskData[i].maskChar)) {
|
||||
m_acceptableInput = false;
|
||||
if (m_text.at(i) != m_blank)
|
||||
m_validInput = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_maskData)
|
||||
checkIsValid();
|
||||
|
||||
if (validateFromState >= 0 && wasValidInput && !m_validInput) {
|
||||
if (m_transactions.count())
|
||||
|
|
|
@ -123,7 +123,13 @@ void QQuickWindowQmlImpl::componentComplete()
|
|||
{
|
||||
Q_D(QQuickWindowQmlImpl);
|
||||
d->complete = true;
|
||||
if (transientParent() && !transientParent()->isVisible()) {
|
||||
QQuickItem *itemParent = qmlobject_cast<QQuickItem *>(QObject::parent());
|
||||
if (itemParent && !itemParent->window()) {
|
||||
qCDebug(lcTransient) << "window" << title() << "has invisible Item parent" << itemParent << "transientParent"
|
||||
<< transientParent() << "declared visibility" << d->visibility << "; delaying show";
|
||||
connect(itemParent, &QQuickItem::windowChanged, this,
|
||||
&QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
|
||||
} else if (transientParent() && !transientParent()->isVisible()) {
|
||||
connect(transientParent(), &QQuickWindow::visibleChanged, this,
|
||||
&QQuickWindowQmlImpl::setWindowVisibility, Qt::QueuedConnection);
|
||||
} else {
|
||||
|
@ -137,9 +143,10 @@ void QQuickWindowQmlImpl::setWindowVisibility()
|
|||
if (transientParent() && !transientParent()->isVisible())
|
||||
return;
|
||||
|
||||
if (sender()) {
|
||||
disconnect(transientParent(), &QWindow::visibleChanged, this,
|
||||
&QQuickWindowQmlImpl::setWindowVisibility);
|
||||
if (QQuickItem *senderItem = qmlobject_cast<QQuickItem *>(sender())) {
|
||||
disconnect(senderItem, &QQuickItem::windowChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
|
||||
} else if (sender()) {
|
||||
disconnect(transientParent(), &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::setWindowVisibility);
|
||||
}
|
||||
|
||||
// We have deferred window creation until we have the full picture of what
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <QProcess>
|
||||
#include <QLibraryInfo>
|
||||
#include <QSysInfo>
|
||||
#include <private/qqmlcomponent_p.h>
|
||||
|
||||
class tst_qmlcachegen: public QObject
|
||||
{
|
||||
|
@ -115,6 +116,16 @@ void tst_qmlcachegen::loadGeneratedFile()
|
|||
|
||||
const QString cacheFilePath = testFilePath + QLatin1Char('c');
|
||||
QVERIFY(QFile::exists(cacheFilePath));
|
||||
|
||||
{
|
||||
QFile cache(cacheFilePath);
|
||||
QVERIFY(cache.open(QIODevice::ReadOnly));
|
||||
const QV4::CompiledData::Unit *cacheUnit = reinterpret_cast<const QV4::CompiledData::Unit *>(cache.map(/*offset*/0, sizeof(QV4::CompiledData::Unit)));
|
||||
QVERIFY(cacheUnit);
|
||||
QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::StaticData);
|
||||
QVERIFY(cacheUnit->flags & QV4::CompiledData::Unit::PendingTypeCompilation);
|
||||
}
|
||||
|
||||
QVERIFY(QFile::remove(testFilePath));
|
||||
|
||||
QQmlEngine engine;
|
||||
|
@ -122,6 +133,13 @@ void tst_qmlcachegen::loadGeneratedFile()
|
|||
QScopedPointer<QObject> obj(component.create());
|
||||
QVERIFY(!obj.isNull());
|
||||
QCOMPARE(obj->property("value").toInt(), 42);
|
||||
|
||||
auto componentPrivate = QQmlComponentPrivate::get(&component);
|
||||
QVERIFY(componentPrivate);
|
||||
auto compilationUnit = componentPrivate->compilationUnit;
|
||||
QVERIFY(compilationUnit);
|
||||
QVERIFY(compilationUnit->data);
|
||||
QVERIFY(!(compilationUnit->data->flags & QV4::CompiledData::Unit::StaticData));
|
||||
}
|
||||
|
||||
void tst_qmlcachegen::translationExpressionSupport()
|
||||
|
|
|
@ -279,6 +279,8 @@ private slots:
|
|||
|
||||
void accessDeletedObject();
|
||||
|
||||
void lowercaseTypeNames();
|
||||
|
||||
private:
|
||||
QQmlEngine engine;
|
||||
QStringList defaultImportPathList;
|
||||
|
@ -4903,6 +4905,12 @@ void tst_qqmllanguage::accessDeletedObject()
|
|||
QVERIFY(!o.isNull());
|
||||
}
|
||||
|
||||
void tst_qqmllanguage::lowercaseTypeNames()
|
||||
{
|
||||
QCOMPARE(qmlRegisterType<QObject>("Test", 1, 0, "lowerCaseTypeName"), -1);
|
||||
QCOMPARE(qmlRegisterSingletonType<QObject>("Test", 1, 0, "lowerCaseTypeName", nullptr), -1);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qqmllanguage)
|
||||
|
||||
#include "tst_qqmllanguage.moc"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
module moduleWithStaticPlugin
|
||||
plugin secondStaticPlugin
|
|
@ -0,0 +1,2 @@
|
|||
module moduleWithWaitingPlugin
|
||||
plugin pluginThatWaits
|
|
@ -29,6 +29,9 @@
|
|||
#include <qdir.h>
|
||||
#include <QtQml/qqmlengine.h>
|
||||
#include <QtQml/qqmlcomponent.h>
|
||||
#include <QtQml/qqmlextensionplugin.h>
|
||||
#include <QtCore/qjsondocument.h>
|
||||
#include <QtCore/qjsonarray.h>
|
||||
#include <QDebug>
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
|
@ -73,12 +76,80 @@ private slots:
|
|||
void importsChildPlugin();
|
||||
void importsChildPlugin2();
|
||||
void importsChildPlugin21();
|
||||
void parallelPluginImport();
|
||||
|
||||
private:
|
||||
QString m_importsDirectory;
|
||||
QString m_dataImportsDirectory;
|
||||
};
|
||||
|
||||
class PluginThatWaits : public QQmlExtensionPlugin
|
||||
{
|
||||
public:
|
||||
static QByteArray metaData;
|
||||
|
||||
static QMutex initializeEngineEntered;
|
||||
static QWaitCondition waitingForInitializeEngineEntry;
|
||||
static QMutex leavingInitializeEngine;
|
||||
static QWaitCondition waitingForInitializeEngineLeave;
|
||||
|
||||
void registerTypes(const char *uri) override
|
||||
{
|
||||
qmlRegisterModule(uri, 1, 0);
|
||||
}
|
||||
|
||||
void initializeEngine(QQmlEngine *engine, const char *uri) override
|
||||
{
|
||||
initializeEngineEntered.lock();
|
||||
leavingInitializeEngine.lock();
|
||||
waitingForInitializeEngineEntry.wakeOne();
|
||||
initializeEngineEntered.unlock();
|
||||
waitingForInitializeEngineLeave.wait(&leavingInitializeEngine);
|
||||
leavingInitializeEngine.unlock();
|
||||
}
|
||||
};
|
||||
QByteArray PluginThatWaits::metaData;
|
||||
QMutex PluginThatWaits::initializeEngineEntered;
|
||||
QWaitCondition PluginThatWaits::waitingForInitializeEngineEntry;
|
||||
QMutex PluginThatWaits::leavingInitializeEngine;
|
||||
QWaitCondition PluginThatWaits::waitingForInitializeEngineLeave;
|
||||
|
||||
class SecondStaticPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
public:
|
||||
static QByteArray metaData;
|
||||
|
||||
void registerTypes(const char *uri) override
|
||||
{
|
||||
qmlRegisterModule(uri, 1, 0);
|
||||
}
|
||||
};
|
||||
QByteArray SecondStaticPlugin::metaData;
|
||||
|
||||
template <typename PluginType>
|
||||
void registerStaticPlugin(const char *uri)
|
||||
{
|
||||
QStaticPlugin plugin;
|
||||
plugin.instance = []() {
|
||||
static PluginType plugin;
|
||||
return static_cast<QObject*>(&plugin);
|
||||
};
|
||||
|
||||
QJsonObject md;
|
||||
md.insert(QStringLiteral("IID"), QQmlExtensionInterface_iid);
|
||||
QJsonArray uris;
|
||||
uris.append(uri);
|
||||
md.insert(QStringLiteral("uri"), uris);
|
||||
|
||||
PluginType::metaData.append(QLatin1String("QTMETADATA "));
|
||||
PluginType::metaData.append(QJsonDocument(md).toBinaryData());
|
||||
|
||||
plugin.rawMetaData = []() {
|
||||
return PluginType::metaData.constData();
|
||||
};
|
||||
qRegisterStaticPluginFunction(plugin);
|
||||
};
|
||||
|
||||
void tst_qqmlmoduleplugin::initTestCase()
|
||||
{
|
||||
QQmlDataTest::initTestCase();
|
||||
|
@ -88,6 +159,9 @@ void tst_qqmlmoduleplugin::initTestCase()
|
|||
m_dataImportsDirectory = directory() + QStringLiteral("/imports");
|
||||
QVERIFY2(QFileInfo(m_dataImportsDirectory).isDir(),
|
||||
qPrintable(QString::fromLatin1("Imports directory '%1' does not exist.").arg(m_dataImportsDirectory)));
|
||||
|
||||
registerStaticPlugin<PluginThatWaits>("moduleWithWaitingPlugin");
|
||||
registerStaticPlugin<SecondStaticPlugin>("moduleWithStaticPlugin");
|
||||
}
|
||||
|
||||
#define VERIFY_ERRORS(errorfile) \
|
||||
|
@ -635,6 +709,51 @@ void tst_qqmlmoduleplugin::importsChildPlugin21()
|
|||
delete object;
|
||||
}
|
||||
|
||||
void tst_qqmlmoduleplugin::parallelPluginImport()
|
||||
{
|
||||
QMutexLocker locker(&PluginThatWaits::initializeEngineEntered);
|
||||
|
||||
QThread worker;
|
||||
QObject::connect(&worker, &QThread::started, [&worker](){
|
||||
// Engines in separate threads are tricky, but as long as we do not create a graphical
|
||||
// object and move objects created by the engines across thread boundaries, this is safe.
|
||||
// At the same time this allows us to place the engine's loader thread into the position
|
||||
// where, without the fix for this bug, the global lock is acquired.
|
||||
QQmlEngine engineInThread;
|
||||
|
||||
QQmlComponent component(&engineInThread);
|
||||
component.setData("import moduleWithWaitingPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
|
||||
QUrl());
|
||||
|
||||
QScopedPointer<QObject> obj(component.create());
|
||||
QVERIFY(!obj.isNull());
|
||||
|
||||
worker.quit();
|
||||
});
|
||||
worker.start();
|
||||
|
||||
PluginThatWaits::waitingForInitializeEngineEntry.wait(&PluginThatWaits::initializeEngineEntered);
|
||||
|
||||
{
|
||||
// After acquiring this lock, the engine in the other thread as well as its type loader
|
||||
// thread are blocked. However they should not hold the global plugin lock
|
||||
// qmlEnginePluginsWithRegisteredTypes()->mutex in qqmllimports.cpp, allowing for the load
|
||||
// of a component in a different engine with its own plugin to proceed.
|
||||
QMutexLocker continuationLock(&PluginThatWaits::leavingInitializeEngine);
|
||||
|
||||
QQmlEngine secondEngine;
|
||||
QQmlComponent secondComponent(&secondEngine);
|
||||
secondComponent.setData("import moduleWithStaticPlugin 1.0\nimport QtQml 2.0\nQtObject {}",
|
||||
QUrl());
|
||||
QScopedPointer<QObject> o(secondComponent.create());
|
||||
QVERIFY(!o.isNull());
|
||||
|
||||
PluginThatWaits::waitingForInitializeEngineLeave.wakeOne();
|
||||
}
|
||||
|
||||
worker.wait();
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qqmlmoduleplugin)
|
||||
|
||||
#include "tst_qqmlmoduleplugin.moc"
|
||||
|
|
|
@ -10,4 +10,12 @@ include (../../shared/util.pri)
|
|||
|
||||
TESTDATA = data/* imports/* $$OUT_PWD/imports/*
|
||||
|
||||
waitingPlugin.files = moduleWithWaitingPlugin
|
||||
waitingPlugin.prefix = /qt-project.org/imports/
|
||||
RESOURCES += waitingPlugin
|
||||
|
||||
staticPlugin.files = moduleWithStaticPlugin
|
||||
staticPlugin.prefix = /qt-project.org/imports/
|
||||
RESOURCES += staticPlugin
|
||||
|
||||
QT += core-private gui-private qml-private network testlib
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
Item {
|
||||
width: 400
|
||||
height: 400
|
||||
objectName: "root Item"
|
||||
|
||||
Loader {
|
||||
sourceComponent: Rectangle {
|
||||
objectName: "yellow rectangle"
|
||||
x: 50; y: 50; width: 300; height: 300
|
||||
color: "yellow"
|
||||
Window {
|
||||
objectName: "red transient Window"
|
||||
width: 100
|
||||
height: 100
|
||||
visible: true // makes it harder, because it wants to become visible before root has a window
|
||||
color: "red"
|
||||
title: "red"
|
||||
flags: Qt.Dialog
|
||||
onVisibilityChanged: console.log("visibility " + visibility)
|
||||
onVisibleChanged: console.log("visible " + visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
Item {
|
||||
width: 400
|
||||
height: 400
|
||||
objectName: "root Item"
|
||||
|
||||
Loader {
|
||||
sourceComponent: Window {
|
||||
objectName: "red transient Window"
|
||||
width: 100
|
||||
height: 100
|
||||
visible: true // makes it harder, because it wants to become visible before root has a window
|
||||
color: "red"
|
||||
title: "red"
|
||||
flags: Qt.Dialog
|
||||
onVisibilityChanged: console.log("visibility " + visibility)
|
||||
onVisibleChanged: console.log("visible " + visible)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,11 +32,15 @@
|
|||
#include <QtQml/qqmlengine.h>
|
||||
#include <QtQml/qqmlcomponent.h>
|
||||
#include <QtQml/qqmlincubator.h>
|
||||
#include <QtQuick/qquickview.h>
|
||||
#include <private/qquickloader_p.h>
|
||||
#include <private/qquickwindowmodule_p.h>
|
||||
#include "testhttpserver.h"
|
||||
#include "../../shared/util.h"
|
||||
#include "../shared/geometrytestutil.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
|
||||
|
||||
class SlowComponent : public QQmlComponent
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -112,6 +116,8 @@ private slots:
|
|||
void parented();
|
||||
void sizeBound();
|
||||
void QTBUG_30183();
|
||||
void transientWindow();
|
||||
void nestedTransientWindow();
|
||||
|
||||
void sourceComponentGarbageCollection();
|
||||
|
||||
|
@ -1159,6 +1165,86 @@ void tst_QQuickLoader::QTBUG_30183()
|
|||
QCOMPARE(rect->height(), 120.0);
|
||||
}
|
||||
|
||||
void tst_QQuickLoader::transientWindow() // QTBUG-52944
|
||||
{
|
||||
QQuickView view;
|
||||
view.setSource(testFileUrl("itemLoaderWindow.qml"));
|
||||
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
|
||||
QVERIFY(root);
|
||||
QQuickLoader *loader = root->findChild<QQuickLoader *>();
|
||||
QVERIFY(loader);
|
||||
QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
|
||||
QQuickWindowQmlImpl *loadedWindow = qobject_cast<QQuickWindowQmlImpl *>(loader->item());
|
||||
QVERIFY(loadedWindow);
|
||||
QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
|
||||
|
||||
QElapsedTimer timer;
|
||||
qint64 viewVisibleTime = -1;
|
||||
qint64 loadedWindowVisibleTime = -1;
|
||||
connect(&view, &QWindow::visibleChanged,
|
||||
[&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
|
||||
connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged,
|
||||
[&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
|
||||
timer.start();
|
||||
view.show();
|
||||
|
||||
QTest::qWaitForWindowExposed(&view);
|
||||
QTRY_VERIFY(loadedWindowVisibleTime >= 0);
|
||||
QVERIFY(viewVisibleTime >= 0);
|
||||
|
||||
// now that we're sure they are both visible, which one became visible first?
|
||||
qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
|
||||
QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
|
||||
|
||||
QWindowList windows = QGuiApplication::topLevelWindows();
|
||||
QTRY_COMPARE(windows.size(), 2);
|
||||
|
||||
// TODO Ideally we would now close the outer window and make sure the transient window closes too.
|
||||
// It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
|
||||
// but quitting an autotest doesn't make sense.
|
||||
}
|
||||
|
||||
void tst_QQuickLoader::nestedTransientWindow() // QTBUG-52944
|
||||
{
|
||||
QQuickView view;
|
||||
view.setSource(testFileUrl("itemLoaderItemWindow.qml"));
|
||||
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
|
||||
QVERIFY(root);
|
||||
QQuickLoader *loader = root->findChild<QQuickLoader *>();
|
||||
QVERIFY(loader);
|
||||
QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
|
||||
QQuickItem *loadedItem = qobject_cast<QQuickItem *>(loader->item());
|
||||
QVERIFY(loadedItem);
|
||||
QQuickWindowQmlImpl *loadedWindow = loadedItem->findChild<QQuickWindowQmlImpl *>();
|
||||
QVERIFY(loadedWindow);
|
||||
QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
|
||||
|
||||
QElapsedTimer timer;
|
||||
qint64 viewVisibleTime = -1;
|
||||
qint64 loadedWindowVisibleTime = -1;
|
||||
connect(&view, &QWindow::visibleChanged,
|
||||
[&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
|
||||
connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged,
|
||||
[&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
|
||||
timer.start();
|
||||
view.show();
|
||||
|
||||
QTest::qWaitForWindowExposed(&view);
|
||||
QTRY_VERIFY(loadedWindowVisibleTime >= 0);
|
||||
QVERIFY(viewVisibleTime >= 0);
|
||||
|
||||
// now that we're sure they are both visible, which one became visible first?
|
||||
qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
|
||||
QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
|
||||
|
||||
QWindowList windows = QGuiApplication::topLevelWindows();
|
||||
QTRY_COMPARE(windows.size(), 2);
|
||||
|
||||
// TODO Ideally we would now close the outer window and make sure the transient window closes too.
|
||||
// It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
|
||||
// but quitting an autotest doesn't make sense.
|
||||
}
|
||||
|
||||
void tst_QQuickLoader::sourceComponentGarbageCollection()
|
||||
{
|
||||
QQmlEngine engine;
|
||||
|
|
|
@ -6143,9 +6143,17 @@ void tst_qquicktextinput::keypress_inputMask_withValidator_data()
|
|||
KeyList keys;
|
||||
// inserting '1111.11' then two backspaces
|
||||
keys << Qt::Key_Home << "1111.11" << Qt::Key_Backspace << Qt::Key_Backspace;
|
||||
QTest::newRow("backspaceWithRegExp") << QString("9999.99;_") << 0.0 << 0.0 << 0
|
||||
QTest::newRow("backspaceWithRegExp") << QString("9999;_") << 0.0 << 0.0 << 0
|
||||
<< QString("/^[-]?((\\.\\d+)|(\\d+(\\.\\d+)?))$/")
|
||||
<< keys << QString("1111.") << QString("1111.__");
|
||||
<< keys << QString("11") << QString("11__");
|
||||
}
|
||||
{
|
||||
KeyList keys;
|
||||
// inserting '99' - QTBUG-64616
|
||||
keys << Qt::Key_Home << "99";
|
||||
QTest::newRow("invalidTextWithRegExp") << QString("X9;_") << 0.0 << 0.0 << 0
|
||||
<< QString("/[+-][0+9]/")
|
||||
<< keys << QString("") << QString("__");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue