diff options
author | Tarja Sundqvist <[email protected]> | 2025-01-29 15:28:42 +0200 |
---|---|---|
committer | Tarja Sundqvist <[email protected]> | 2025-01-29 15:28:42 +0200 |
commit | a7c766a9863605eb81e8f0cdb4d2b93e087b5bde (patch) | |
tree | 02d4a35b6bcfd4322a01b013e887a029fe760fe4 | |
parent | d8a396b76168b66b8cd938f61d8d958ebdb14871 (diff) | |
parent | 5880b113a6dcd08cb84b542f86b9f6b90b626514 (diff) |
Merge tag 'v6.2.12-lts' into tqtc/lts-6.2-opensourcev6.2.12-lts-lgpl
Qt 6.2.12-lts release
Conflicts solved:
dependencies.yaml
Change-Id: Ia7410afbc9d94f061fb13da84b6361428725b530
46 files changed, 762 insertions, 164 deletions
diff --git a/.cmake.conf b/.cmake.conf index 842b347ced..7733def214 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,2 +1,2 @@ -set(QT_REPO_MODULE_VERSION "6.2.11") +set(QT_REPO_MODULE_VERSION "6.2.12") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "") diff --git a/.qmake.conf b/.qmake.conf index 7f5f2c33a7..ac797c6928 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -5,4 +5,4 @@ DEFINES += QT_NO_JAVA_STYLE_ITERATORS QQC2_SOURCE_TREE = $$PWD -MODULE_VERSION = 6.2.11 +MODULE_VERSION = 6.2.12 diff --git a/examples/quick/quickwidgets/quickwidget/main.cpp b/examples/quick/quickwidgets/quickwidget/main.cpp index 191adcc451..f7885bd0a2 100644 --- a/examples/quick/quickwidgets/quickwidget/main.cpp +++ b/examples/quick/quickwidgets/quickwidget/main.cpp @@ -108,14 +108,22 @@ MainWindow::MainWindow() setCentralWidget(centralWidget); QMenu *fileMenu = menuBar()->addMenu(tr("&File")); - fileMenu->addAction(tr("Grab framebuffer"), this, &MainWindow::grabFramebuffer); - fileMenu->addAction(tr("Render to pixmap"), this, &MainWindow::renderToPixmap); - fileMenu->addAction(tr("Grab via grabToImage"), this, &MainWindow::grabToImage); + auto grabAction = fileMenu->addAction(tr("Grab framebuffer"), this, &MainWindow::grabFramebuffer); + auto renderAction = fileMenu->addAction(tr("Render to pixmap"), this, &MainWindow::renderToPixmap); + auto grabToImageAction = fileMenu->addAction(tr("Grab via grabToImage"), this, &MainWindow::grabToImage); fileMenu->addAction(tr("Quit"), qApp, &QCoreApplication::quit); QMenu *windowMenu = menuBar()->addMenu(tr("&Window")); windowMenu->addAction(tr("Add tab widget"), this, [this, centralWidget] { createQuickWidgetsInTabs(centralWidget); }); + + connect(m_quickWidget, &QObject::destroyed, this, + [this, grabAction, renderAction, grabToImageAction] { + m_quickWidget = nullptr; + grabAction->setEnabled(false); + renderAction->setEnabled(false); + grabToImageAction->setEnabled(false); + }); } void MainWindow::createQuickWidgetsInTabs(QMdiArea *mdiArea) @@ -146,6 +154,7 @@ void MainWindow::quickWidgetStatusChanged(QQuickWidget::Status status) { if (status == QQuickWidget::Error) { QStringList errors; + Q_ASSERT(m_quickWidget); const auto widgetErrors = m_quickWidget->errors(); for (const QQmlError &error : widgetErrors) errors.append(error.toString()); @@ -170,12 +179,14 @@ template<class T> void saveToFile(QWidget *parent, T *saveable) void MainWindow::grabFramebuffer() { + Q_ASSERT(m_quickWidget); QImage image = m_quickWidget->grabFramebuffer(); saveToFile(this, &image); } void MainWindow::renderToPixmap() { + Q_ASSERT(m_quickWidget); QPixmap pixmap(m_quickWidget->size()); m_quickWidget->render(&pixmap); saveToFile(this, &pixmap); @@ -188,6 +199,7 @@ void MainWindow::grabToImage() fd.setDefaultSuffix("png"); fd.selectFile("test_grabToImage.png"); if (fd.exec() == QDialog::Accepted) { + Q_ASSERT(m_quickWidget); QMetaObject::invokeMethod(m_quickWidget->rootObject(), "performLayerBasedGrab", Q_ARG(QVariant, fd.selectedFiles().first())); } diff --git a/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp b/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp index d799f913a4..b2817683b4 100644 --- a/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp +++ b/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp @@ -115,10 +115,7 @@ void* OSAllocator::reserveUncommitted(size_t bytes, Usage usage, bool writable, if (result == MAP_FAILED) CRASH(); - while (madvise(result, bytes, MADV_DONTNEED)) { - if (errno != EAGAIN) - CRASH(); - } + while (madvise(result, bytes, MADV_DONTNEED) == -1 && errno == EAGAIN) { } if (fd != -1) close(fd); @@ -251,8 +248,10 @@ void OSAllocator::decommit(void* address, size_t bytes) mmap(address, bytes, PROT_NONE, MAP_FIXED | MAP_LAZY | MAP_PRIVATE | MAP_ANON, -1, 0); #elif OS(LINUX) while (madvise(address, bytes, MADV_DONTNEED)) { - if (errno != EAGAIN) - CRASH(); + if (errno != EAGAIN) { + memset(address, 0, bytes); // We rely on madvise to zero-out the memory + break; + } } if (mprotect(address, bytes, PROT_NONE)) CRASH(); diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index d805d37feb..cc38a30998 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -994,6 +994,35 @@ function(_qt_internal_write_deferred_qmldir_file target) endfunction() +function(_qt_internal_set_qml_target_multi_config_output_directory target output_directory) + # In multi-config builds we need to make sure that at least one configuration has the dynamic + # plugin that is located next to qmldir file, otherwise QML engine won't be able to load the + # plugin. + get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + if(is_multi_config) + # We don't care about static plugins here since, they are linked at build time and + # their location doesn't affect the runtime. + get_target_property(target_type ${target} TYPE) + if(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY") + if(NOT "${output_directory}") + set(output_directory "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + list(GET CMAKE_CONFIGURATION_TYPES 0 default_config) + string(JOIN "" output_directory_with_default_config + "$<IF:$<CONFIG:${default_config}>," + "${output_directory}," + "${output_directory}/$<CONFIG>" + ">" + ) + set_target_properties(${target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${output_directory_with_default_config}" + LIBRARY_OUTPUT_DIRECTORY "${output_directory_with_default_config}" + ) + endif() + endif() +endfunction() + function(qt6_add_qml_plugin target) set(args_option STATIC @@ -1188,6 +1217,8 @@ function(qt6_add_qml_plugin target) ) endif() + _qt_internal_set_qml_target_multi_config_output_directory(${target} "${arg_OUTPUT_DIRECTORY}") + if(NOT arg_NO_GENERATE_PLUGIN_SOURCE) set(generated_cpp_file_name_base "${target}_${arg_CLASS_NAME}") set(register_types_function_name "qml_register_types_${escaped_uri}") diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 9453120eb6..1452027dcb 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -664,6 +664,8 @@ struct Binding } bool evaluatesToString() const { return type() == Type_String || isTranslationBinding(); } + bool isNumberBinding() const { return type() == Type_Number; } + bool valueAsBoolean() const { if (type() == Type_Boolean) diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index cc4f4a2747..63f329515b 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -452,7 +452,7 @@ Declares that any \l QML_ELEMENT, \l QML_NAMED_ELEMENT(), \l QML_ANONYMOUS, \l QML_INTERFACE, \l QML_UNCREATABLE(), \l QML_SINGLETON, \l QML_ADDED_IN_MINOR_VERSION(), \l QML_REMOVED_IN_MINOR_VERSION(), - \l QML_ATTACHED(), \l QML_EXTENDED(), or \l QML_EXTENDED_NAMESPACE() macros + \l QML_EXTENDED(), or \l QML_EXTENDED_NAMESPACE() macros in the enclosing C++ type do not apply to the enclosing type but instead to \a FOREIGN_TYPE. The enclosing type still needs to be registered with the \l {The Meta-Object System}{meta object system} using a \l Q_GADGET or @@ -466,6 +466,9 @@ the element will be named like the struct it is contained in, not the foreign type. See \l {Extending QML - Extension Objects Example} for an example. + \note QML_ATTACHED() can currently not be redirected like this. It has to be + specificed in the same type that implements qmlAttachedProperties(). + \sa QML_ELEMENT, QML_NAMED_ELEMENT(), QML_FOREIGN_NAMESPACE() */ diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 564cd47203..de0ac543da 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -75,8 +75,11 @@ void Object::setInternalClass(Heap::InternalClass *ic) // Pick the members of the old IC that are still valid in the new IC. // Order them by index in memberData (or inline data). Scoped<MemberData> newMembers(scope, MemberData::allocate(scope.engine, ic->size)); - for (uint i = 0; i < ic->size; ++i) - newMembers->set(scope.engine, i, get(ic->nameMap.at(i))); + for (uint i = 0; i < ic->size; ++i) { + // Note that some members might have been deleted. The key may be invalid. + const PropertyKey key = ic->nameMap.at(i); + newMembers->set(scope.engine, i, key.isValid() ? get(key) : Encode::undefined()); + } p->internalClass.set(scope.engine, ic); const uint nInline = p->vtable()->nInlineProperties; diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 91bace3b01..c584a4f42a 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -1248,9 +1248,7 @@ void QObjectWrapper::destroyObject(bool lastCall) if (!h->object()->parent() && !ddata->indestructible) { if (ddata && ddata->ownContext) { Q_ASSERT(ddata->ownContext.data() == ddata->context); - ddata->ownContext->emitDestruction(); - if (ddata->ownContext->contextObject() == h->object()) - ddata->ownContext->setContextObject(nullptr); + ddata->ownContext->deepClearContextObject(h->object()); ddata->ownContext = nullptr; ddata->context = nullptr; } diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index c5ec43bbb7..127fdf7ebb 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -1069,7 +1069,8 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType storeResult = storeObjectProperty(&l, qmlScopeObject, value); } else { QVariant var(propType); - propType.convert(type, value, propType, var.data()); + QV4::ExecutionEngine *v4 = engine->handle(); + v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); storeResult = storeObjectProperty(&l, qmlScopeObject, var.data()); } @@ -1085,7 +1086,8 @@ void AOTCompiledContext::storeNameSloppy(uint nameIndex, void *value, QMetaType storeResult = storeFallbackProperty(&l, qmlScopeObject, value); } else { QVariant var(propType); - propType.convert(type, value, propType, var.data()); + QV4::ExecutionEngine *v4 = engine->handle(); + v4->metaTypeFromJS(v4->metaTypeToJS(type, value), propType, var.data()); storeResult = storeFallbackProperty(&l, qmlScopeObject, var.data()); } break; diff --git a/src/qml/qml/qqmlcontextdata_p.h b/src/qml/qml/qqmlcontextdata_p.h index 146a1c97dd..92e9329506 100644 --- a/src/qml/qml/qqmlcontextdata_p.h +++ b/src/qml/qml/qqmlcontextdata_p.h @@ -157,6 +157,28 @@ public: QObject *contextObject() const { return m_contextObject; } void setContextObject(QObject *contextObject) { m_contextObject = contextObject; } + template<typename HandleSelf, typename HandleLinked> + void deepClearContextObject( + QObject *contextObject, HandleSelf &&handleSelf, HandleLinked &&handleLinked) { + for (QQmlContextData *lc = m_linkedContext.data(); lc; lc = lc->m_linkedContext.data()) { + handleLinked(lc); + if (lc->m_contextObject == contextObject) + lc->m_contextObject = nullptr; + } + + handleSelf(this); + if (m_contextObject == contextObject) + m_contextObject = nullptr; + } + + void deepClearContextObject(QObject *contextObject) + { + deepClearContextObject( + contextObject, + [](QQmlContextData *self) { self->emitDestruction(); }, + [](QQmlContextData *){}); + } + QQmlEngine *engine() const { return m_engine; } void setEngine(QQmlEngine *engine) { m_engine = engine; } diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index e14931ddba..18058e951c 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -505,23 +505,16 @@ QQmlEnginePrivate::~QQmlEnginePrivate() void QQmlPrivate::qdeclarativeelement_destructor(QObject *o) { if (QQmlData *d = QQmlData::get(o)) { + const auto invalidate = [](QQmlContextData *c) {c->invalidate();}; if (d->ownContext) { - for (QQmlRefPointer<QQmlContextData> lc = d->ownContext->linkedContext().data(); lc; - lc = lc->linkedContext()) { - lc->invalidate(); - if (lc->contextObject() == o) - lc->setContextObject(nullptr); - } - d->ownContext->invalidate(); - if (d->ownContext->contextObject() == o) - d->ownContext->setContextObject(nullptr); + d->ownContext->deepClearContextObject(o, invalidate, invalidate); d->ownContext = nullptr; d->context = nullptr; + Q_ASSERT(!d->outerContext || d->outerContext->contextObject() != o); + } else if (d->outerContext && d->outerContext->contextObject() == o) { + d->outerContext->deepClearContextObject(o, invalidate, invalidate); } - if (d->outerContext && d->outerContext->contextObject() == o) - d->outerContext->setContextObject(nullptr); - // Mark this object as in the process of deletion to // prevent it resolving in bindings QQmlData::markAsDeleted(o); @@ -681,9 +674,7 @@ void QQmlData::setQueuedForDeletion(QObject *object) if (QQmlData *ddata = QQmlData::get(object)) { if (ddata->ownContext) { Q_ASSERT(ddata->ownContext.data() == ddata->context); - ddata->context->emitDestruction(); - if (ddata->ownContext->contextObject() == object) - ddata->ownContext->setContextObject(nullptr); + ddata->ownContext->deepClearContextObject(object); ddata->ownContext = nullptr; ddata->context = nullptr; } diff --git a/src/qml/qml/qqmlextensionplugin.cpp b/src/qml/qml/qqmlextensionplugin.cpp index c2fc291d48..9256d13bb3 100644 --- a/src/qml/qml/qqmlextensionplugin.cpp +++ b/src/qml/qml/qqmlextensionplugin.cpp @@ -186,8 +186,20 @@ void QQmlEngineExtensionPlugin::initializeEngine(QQmlEngine *engine, const char \since 6.2 \relates QQmlEngineExtensionPlugin - Ensures the plugin whose metadata-declaring class is named \a PluginName - is linked into static builds. + Ensures the plugin whose metadata-declaring plugin extension class is named + \a PluginName is linked into static builds. For the modules created using + \l qt_add_qml_module, the default plugin extension class name is computed + from the QML module URI by replacing dots with underscores, unless the + \c CLASS_NAME argument is specified. + + For example: + \badcode + qt_add_qml_module(myplugin + # The plugin extension class name in this case is my_Company_QmlComponents. + URI my.Company.QmlComponents + ... + ) + \endcode \sa Q_IMPORT_PLUGIN */ diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index b544852953..6df7fa6ce1 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -355,7 +355,10 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const int propertyType = property->propType().id(); if (property->isEnum()) { - if (binding->hasFlag(QV4::CompiledData::Binding::IsResolvedEnum)) { + if (binding->hasFlag(QV4::CompiledData::Binding::IsResolvedEnum) || + // TODO: For historical reasons you can assign any number to an enum property alias + // This can be fixed with an opt-out mechanism, for example a pragma. + (property->isAlias() && binding->isNumberBinding())) { propertyType = QMetaType::Int; } else { // ### This should be resolved earlier at compile time and the binding value should be changed accordingly. diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index 3ce665c24f..35dc954725 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -947,10 +947,10 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor resettable = property->isResettable(); bindable = property->isBindable(); - // Copy type flags - propertyFlags->copyPropertyTypeFlags(property->flags()); if (property->isVarProperty()) propertyFlags->type = QQmlPropertyData::Flags::QVariantType; + else + propertyFlags->copyPropertyTypeFlags(property->flags()); }; // for deep aliases, valueTypeIndex is always set diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index 6a36c279e8..f7adc97206 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -386,6 +386,11 @@ QQmlError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *prope if (binding->hasFlag(QV4::CompiledData::Binding::IsResolvedEnum)) return noError; + // TODO: For historical reasons you can assign any number to an enum property alias + // This can be fixed with an opt-out mechanism, for example a pragma. + if (property->isAlias() && binding->isNumberBinding()) + return noError; + QString value = compilationUnit->bindingValueAsString(binding); QMetaProperty p = propertyCache->firstCppMetaObject()->property(property->coreIndex()); bool ok; diff --git a/src/qmltest/TestCase.qml b/src/qmltest/TestCase.qml index 2b817ad7dd..46d7503554 100644 --- a/src/qmltest/TestCase.qml +++ b/src/qmltest/TestCase.qml @@ -1281,7 +1281,16 @@ Item { Waits for \a ms milliseconds while processing Qt events. - \sa sleep(), waitForRendering() + \note This methods uses a precise timer to do the actual waiting. The + event you are waiting for may not. In particular, any animations as + well as the \l{Timer} QML type can use either precise or coarse + timers, depending on various factors. For a coarse timer you have + to expect a drift of around 5% in relation to the precise timer used + by TestCase::wait(). Qt cannot give hard guarantees on the drift, + though, because the operating system usually doesn't offer hard + guarantees on timers. + + \sa sleep(), waitForRendering(), Qt::TimerType */ function wait(ms) { qtest_results.wait(ms) diff --git a/src/quick/doc/snippets/qml/externaldrag.qml b/src/quick/doc/snippets/qml/externaldrag.qml index 5a88be2d4b..ba7feecc1b 100644 --- a/src/quick/doc/snippets/qml/externaldrag.qml +++ b/src/quick/doc/snippets/qml/externaldrag.qml @@ -59,7 +59,6 @@ Item { color: "green" radius: 5 - Drag.active: dragHandler.active Drag.dragType: Drag.Automatic Drag.supportedActions: Qt.CopyAction Drag.mimeData: { @@ -77,8 +76,11 @@ Item { onActiveChanged: if (active) { parent.grabToImage(function(result) { - parent.Drag.imageSource = result.url; + parent.Drag.imageSource = result.url + parent.Drag.active = true }) + } else { + parent.Drag.active = false } } } diff --git a/src/quick/doc/src/concepts/input/focus.qdoc b/src/quick/doc/src/concepts/input/focus.qdoc index 6dd5a4b4bc..0adc0b557f 100644 --- a/src/quick/doc/src/concepts/input/focus.qdoc +++ b/src/quick/doc/src/concepts/input/focus.qdoc @@ -42,8 +42,8 @@ scope based extension to Qt's traditional keyboard focus model. When the user presses or releases a key, the following occurs: \list 1 \li Qt receives the key action and generates a key event. -\li If a \l QQuickWindow is the active window, the key event -is delivered to it. +\li If a \l QQuickWindow is the \l{QGuiApplication::focusWindow()}{focus window} +of the application, the key event is delivered to it. \li The key event is delivered by the scene to the \l Item with \e {active focus}. If no item has active focus, the key event is ignored. \li If the \l QQuickItem with active focus accepts the key event, propagation diff --git a/src/quick/handlers/qquicksinglepointhandler.cpp b/src/quick/handlers/qquicksinglepointhandler.cpp index 22b475637a..e717b9b5dc 100644 --- a/src/quick/handlers/qquicksinglepointhandler.cpp +++ b/src/quick/handlers/qquicksinglepointhandler.cpp @@ -141,11 +141,16 @@ void QQuickSinglePointHandler::handlePointerEventImpl(QPointerEvent *event) void QQuickSinglePointHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) { - if (point.state() != QEventPoint::Released) - return; + if (point.state() == QEventPoint::Released) { + // If it's a mouse or tablet event, with buttons, + // do not deactivate unless all acceptable buttons are released. + if (event->isSinglePointEvent()) { + const Qt::MouseButtons releasedButtons = static_cast<QSinglePointEvent *>(event)->buttons(); + if ((releasedButtons & acceptedButtons()) != Qt::NoButton) + return; + } - const Qt::MouseButtons releasedButtons = static_cast<QSinglePointEvent *>(event)->buttons(); - if ((releasedButtons & acceptedButtons()) == Qt::NoButton) { + // Deactivate this handler on release setExclusiveGrab(event, point, false); d_func()->reset(); } diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index f8f56557ae..9b0334b851 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1885,21 +1885,6 @@ qreal QQuickTableViewPrivate::sizeHintForRow(int row) const return rowHeight; } -void QQuickTableViewPrivate::updateTableSize() -{ - // tableSize is the same as row and column count, and will always - // be the same as the number of rows and columns in the model. - Q_Q(QQuickTableView); - - const QSize prevTableSize = tableSize; - tableSize = calculateTableSize(); - - if (prevTableSize.width() != tableSize.width()) - emit q->columnsChanged(); - if (prevTableSize.height() != tableSize.height()) - emit q->rowsChanged(); -} - QSize QQuickTableViewPrivate::calculateTableSize() { QSize size(0, 0); @@ -2314,6 +2299,7 @@ void QQuickTableViewPrivate::processRebuildTable() Q_TABLEVIEW_UNREACHABLE(rebuildOptions); } + tableSizeBeforeRebuild = tableSize; edgesBeforeRebuild = loadedItems.isEmpty() ? QMargins() : QMargins(q->leftColumn(), q->topRow(), q->rightColumn(), q->bottomRow()); } @@ -2389,6 +2375,10 @@ void QQuickTableViewPrivate::processRebuildTable() } if (rebuildState == RebuildState::Done) { + if (tableSizeBeforeRebuild.width() != tableSize.width()) + emit q->columnsChanged(); + if (tableSizeBeforeRebuild.height() != tableSize.height()) + emit q->rowsChanged(); if (edgesBeforeRebuild.left() != q->leftColumn()) emit q->leftColumnChanged(); if (edgesBeforeRebuild.right() != q->rightColumn()) @@ -2532,7 +2522,7 @@ void QQuickTableViewPrivate::calculateTopLeft(QPoint &topLeftCell, QPointF &topL void QQuickTableViewPrivate::loadInitialTable() { - updateTableSize(); + tableSize = calculateTableSize(); QPoint topLeft; QPointF topLeftPos; diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 3f969ab195..c4f1ba1157 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -339,6 +339,7 @@ public: QRectF selectionEndCellRect; QMargins edgesBeforeRebuild; + QSize tableSizeBeforeRebuild; const static QPoint kLeft; const static QPoint kRight; diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index ce2010e489..cef0dd2900 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -233,7 +233,7 @@ void QQuickTextPrivate::setBottomPadding(qreal value, bool reset) Used to decide if the Text should use antialiasing or not. Only Text with renderType of Text.NativeRendering can disable antialiasing. - The default is true. + The default is \c true. */ void QQuickText::q_updateLayout() @@ -1310,8 +1310,8 @@ void QQuickTextPrivate::ensureDoc() \inherits Item \brief Specifies how to add formatted text to a scene. - Text items can display both plain and rich text. For example, red text with - a specific font and size can be defined like this: + Text items can display both plain and rich text. For example, you can define + red text with a specific font and size like this: \qml Text { @@ -1322,25 +1322,47 @@ void QQuickTextPrivate::ensureDoc() } \endqml - Rich text is defined using HTML-style markup: + Use HTML-style markup or Markdown to define rich text: + \if defined(onlinedocs) + \tab {build-qt-app}{tab-html}{HTML-style}{checked} + \tab {build-qt-app}{tab-md}{Markdown}{} + \tabcontent {tab-html} + \else + \section1 Using HTML-style + \endif \qml Text { text: "<b>Hello</b> <i>World!</i>" } \endqml + \if defined(onlinedocs) + \endtabcontent + \tabcontent {tab-md} + \else + \section1 Using Markdown + \endif + \qml + Text { + text: "**Hello** *World!*" + } + \endqml + \if defined(onlinedocs) + \endtabcontent + \endif \image declarative-text.png - If height and width are not explicitly set, Text will attempt to determine how - much room is needed and set it accordingly. Unless \l wrapMode is set, it will always - prefer width to height (all text will be placed on a single line). + If height and width are not explicitly set, Text will try to determine how + much room is needed and set it accordingly. Unless \l wrapMode is set, it + will always prefer width to height (all text will be placed on a single + line). - The \l elide property can alternatively be used to fit a single line of - plain text to a set width. + To fit a single line of plain text to a set width, you can use the \l elide + property. - Note that the \l{Supported HTML Subset} is limited. Also, if the text contains - HTML img tags that load remote images, the text is reloaded. + Note that the \l{Supported HTML Subset} is limited. Also, if the text + contains HTML img tags that load remote images, the text is reloaded. Text provides read-only text. For editable text, see \l TextEdit. @@ -1368,7 +1390,7 @@ QQuickText::~QQuickText() \qmlproperty bool QtQuick::Text::clip This property holds whether the text is clipped. - Note that if the text does not fit in the bounding rectangle it will be abruptly chopped. + Note that if the text does not fit in the bounding rectangle, it will be abruptly chopped. If you want to display potentially long text in a limited space, you probably want to use \c elide instead. */ @@ -1457,7 +1479,8 @@ QQuickText::~QQuickText() Sets the family name of the font. - The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + The family name is case insensitive and may optionally include a foundry + name, for example "Helvetica [Cronyx]". If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. If the family isn't available a family will be set using the font matching algorithm. */ @@ -1581,7 +1604,7 @@ QQuickText::~QQuickText() \value Font.PreferDefaultHinting Use the default hinting level for the target platform. \value Font.PreferNoHinting If possible, render text without hinting the outlines of the glyphs. The text layout will be typographically accurate, using the same metrics - as are used e.g. when printing. + as are used, for example, when printing. \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting, but align glyphs to the pixel grid in the vertical direction. The text will appear crisper on displays where the density is too low to give an accurate rendering @@ -1618,7 +1641,7 @@ QQuickText::~QQuickText() Sometimes, a font will apply complex rules to a set of characters in order to display them correctly. In some writing systems, such as Brahmic scripts, this is - required in order for the text to be legible, but in e.g. Latin script, it is merely + required in order for the text to be legible, but in for example Latin script, it is merely a cosmetic feature. Setting the \c preferShaping property to false will disable all such features when they are not required, which will improve performance in most cases. @@ -1628,6 +1651,7 @@ QQuickText::~QQuickText() Text { text: "Some text"; font.preferShaping: false } \endqml */ + QFont QQuickText::font() const { Q_D(const QQuickText); @@ -2154,7 +2178,7 @@ void QQuickText::resetMaximumLineCount() \l {https://guides.github.com/features/mastering-markdown/}{GitHub} extensions for tables and task lists (since 5.14) - If the text format is \c Text.AutoText the Text item + If the text format is \c Text.AutoText, the Text item will automatically determine whether the text should be treated as styled text. This determination is made using Qt::mightBeRichText(), which can detect the presence of an HTML tag on the first line of text, @@ -2285,7 +2309,7 @@ void QQuickText::setElideMode(QQuickText::TextElideMode mode) /*! \qmlproperty url QtQuick::Text::baseUrl - This property specifies a base URL which is used to resolve relative URLs + This property specifies a base URL that is used to resolve relative URLs within the text. Urls are resolved to be within the same directory as the target of the base @@ -2555,7 +2579,7 @@ void QQuickText::updatePolish() \qmlproperty real QtQuick::Text::contentWidth Returns the width of the text, including width past the width - which is covered due to insufficient wrapping if WrapMode is set. + that is covered due to insufficient wrapping if WrapMode is set. */ qreal QQuickText::contentWidth() const { @@ -2567,7 +2591,7 @@ qreal QQuickText::contentWidth() const \qmlproperty real QtQuick::Text::contentHeight Returns the height of the text, including height past the height - which is covered due to there being more text than fits in the set height. + that is covered due to there being more text than fits in the set height. */ qreal QQuickText::contentHeight() const { @@ -3294,7 +3318,7 @@ void QQuickText::resetBottomPadding() */ /*! - \qmlproperty string QtQuick::Text::fontInfo.pixelSize + \qmlproperty int QtQuick::Text::fontInfo.pixelSize \since 5.9 The pixel size of the font info that has been resolved for the current font @@ -3331,7 +3355,7 @@ QJSValue QQuickText::fontInfo() const in a text flow. Note that the advance can be negative if the text flows from - the right to the left. + right to left. */ QSizeF QQuickText::advance() const { diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index a08f46a1b4..59ea429e69 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -3346,6 +3346,8 @@ void QQuickWindow::endExternalCommands() Setting visible to false is the same as setting \l visibility to \l {QWindow::}{Hidden}. + The default value is \c false, unless overridden by setting \l visibility. + \sa visibility */ diff --git a/src/quick/util/qquickanimatorjob.cpp b/src/quick/util/qquickanimatorjob.cpp index bdb47653df..69e31acde7 100644 --- a/src/quick/util/qquickanimatorjob.cpp +++ b/src/quick/util/qquickanimatorjob.cpp @@ -625,7 +625,7 @@ void QQuickUniformAnimatorJob::setTarget(QQuickItem *target) void QQuickUniformAnimatorJob::updateCurrentTime(int time) { - if (!m_effect) + if (!m_effect || m_target != m_effect) return; m_value = m_from + (m_to - m_from) * progress(time); @@ -650,7 +650,7 @@ void QQuickUniformAnimatorJob::postSync() void QQuickUniformAnimatorJob::invalidate() { - m_effect = nullptr; + } QT_END_NAMESPACE diff --git a/src/quick/util/qquickanimatorjob_p.h b/src/quick/util/qquickanimatorjob_p.h index 171c0b302f..e2c91aef6b 100644 --- a/src/quick/util/qquickanimatorjob_p.h +++ b/src/quick/util/qquickanimatorjob_p.h @@ -316,7 +316,7 @@ public: private: QByteArray m_uniform; - QQuickShaderEffect *m_effect = nullptr; + QPointer<QQuickShaderEffect> m_effect; }; QT_END_NAMESPACE diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 7a6905b5de..11138fcbb7 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -1944,6 +1944,16 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) } if (!relevantPassiveGrabbers.isEmpty()) deliverToPassiveGrabbers(relevantPassiveGrabbers, event); + + // Ensure that HoverHandlers are updated, in case no items got dirty so far and there's no update request + if (event->type() == QEvent::TouchUpdate) { + for (auto hoverItem : hoverItems) { + if (auto item = hoverItem.first) { + deliverHoverEventToItem(item, point.scenePosition(), point.sceneLastPosition(), + event->modifiers(), event->timestamp(), false); + } + } + } } if (done) diff --git a/src/quickshapes/qquickshape.cpp b/src/quickshapes/qquickshape.cpp index ce091fab07..2a50a35d9a 100644 --- a/src/quickshapes/qquickshape.cpp +++ b/src/quickshapes/qquickshape.cpp @@ -675,6 +675,7 @@ QQuickShape::~QQuickShape() /*! \qmlproperty enumeration QtQuick.Shapes::Shape::rendererType + \readonly This property determines which path rendering backend is active. @@ -759,6 +760,7 @@ void QQuickShape::setVendorExtensionsEnabled(bool enable) /*! \qmlproperty enumeration QtQuick.Shapes::Shape::status + \readonly This property determines the status of the Shape and is relevant when Shape.asynchronous is set to \c true. diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 7883ca721d..c2c6f3f43f 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -292,6 +292,8 @@ private slots: void garbageCollectedObjectMethodBase(); void spreadNoOverflow(); + void deleteDefineCycle(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -5816,6 +5818,25 @@ void tst_QJSEngine::spreadNoOverflow() QCOMPARE(result.errorType(), QJSValue::RangeError); } +void tst_QJSEngine::deleteDefineCycle() +{ + QJSEngine engine; + QStringList stackTrace; + + QJSValue result = engine.evaluate(QString::fromLatin1(R"( + let global = ({}) + + for (let j = 0; j < 1000; j++) { + for (let i = 0; i < 2; i++) { + const name = "test" + i + delete global[name] + Object.defineProperty(global, name, { get() { return 0 }, configurable: true }) + } + } + )"), {}, 1, &stackTrace); + QVERIFY(stackTrace.isEmpty()); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qqmlcontext/data/A.qml b/tests/auto/qml/qqmlcontext/data/A.qml new file mode 100644 index 0000000000..1a44f005af --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/A.qml @@ -0,0 +1,6 @@ +import QtQml + +B { + id: b + property int y: 2 +} diff --git a/tests/auto/qml/qqmlcontext/data/B.qml b/tests/auto/qml/qqmlcontext/data/B.qml new file mode 100644 index 0000000000..7754728304 --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/B.qml @@ -0,0 +1,6 @@ +import QtQml + +C { + id: z + property int z: 3 +} diff --git a/tests/auto/qml/qqmlcontext/data/C.qml b/tests/auto/qml/qqmlcontext/data/C.qml new file mode 100644 index 0000000000..6afd23aa6c --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/C.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + id: outer + objectName: "the" + "C" +} diff --git a/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml b/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml new file mode 100644 index 0000000000..7b1f46d7d1 --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property A a: A {} +} diff --git a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp index 75bf580947..990acfd11c 100644 --- a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp +++ b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp @@ -74,6 +74,7 @@ private slots: void outerContextObject(); void contextObjectHierarchy(); void destroyContextProperty(); + void destroyContextObject(); void numericContextProperty(); @@ -1006,6 +1007,30 @@ void tst_qqmlcontext::destroyContextProperty() // TODO: Or are we? } +void tst_qqmlcontext::destroyContextObject() +{ + QQmlEngine engine; + QList<QQmlRefPointer<QQmlContextData>> contexts; + QQmlComponent component(&engine, testFileUrl("destroyContextObject.qml")); + QScopedPointer<QObject> root(component.create()); + + QPointer<QObject> a = root->property("a").value<QObject *>(); + QVERIFY(a); + + for (QQmlRefPointer<QQmlContextData> context = QQmlData::get(a)->ownContext; + context; context = context->parent()) { + contexts.append(context); + } + + QObject *deleted = a.data(); + root.reset(); + + QVERIFY(a.isNull()); + + for (const auto &context : contexts) + QVERIFY(context->contextObject() != deleted); +} + void tst_qqmlcontext::numericContextProperty() { QQmlEngine engine; diff --git a/tests/auto/qml/qqmllanguage/data/AliasHolder.qml b/tests/auto/qml/qqmllanguage/data/AliasHolder.qml new file mode 100644 index 0000000000..42aed6ed26 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/AliasHolder.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + property alias strokeStyle: path.restoreMode + property Binding p: Binding { id: path } +} diff --git a/tests/auto/qml/qqmllanguage/data/aliasWriter.qml b/tests/auto/qml/qqmllanguage/data/aliasWriter.qml new file mode 100644 index 0000000000..4001c2af34 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/aliasWriter.qml @@ -0,0 +1,5 @@ +import QtQml + +AliasHolder { + strokeStyle: 1 +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 41fb24a76c..3d1f023425 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -393,6 +393,8 @@ private slots: void typeAnnotationCycle(); void deepAliasOnICOrReadonly(); + void writeNumberToEnumAlias(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -6734,6 +6736,17 @@ void tst_qqmllanguage::deepAliasOnICOrReadonly() "Invalid property assignment: \"readonlyRectX\" is a read-only property"))); } +void tst_qqmllanguage::writeNumberToEnumAlias() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("aliasWriter.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("strokeStyle").toInt(), 1); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverHandler.qml b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverHandler.qml new file mode 100644 index 0000000000..60dfc53c40 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/hoverHandler.qml @@ -0,0 +1,17 @@ +import QtQuick + +Item { + width: 320 + height: 240 + + Rectangle { + width: 100 + height: 100 + anchors.centerIn: parent + color: hh.hovered ? "lightsteelblue" : "beige" + + HoverHandler { + id: hh + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index 5d729c0428..993ca5fb51 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -65,9 +65,12 @@ private slots: void movingItemWithHoverHandler(); void margin(); void window(); + void touchDrag(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); + + QScopedPointer<QPointingDevice> touchscreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; void tst_HoverHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) @@ -440,6 +443,46 @@ void tst_HoverHandler::window() // QTBUG-98717 #endif } +void tst_HoverHandler::touchDrag() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("hoverHandler.qml"))); + const QQuickItem *root = window.rootObject(); + QQuickHoverHandler *handler = root->findChild<QQuickHoverHandler *>(); + QVERIFY(handler); + + // polishAndSync() calls flushFrameSynchronousEvents() before emitting afterAnimating() + QSignalSpy frameSyncSpy(&window, &QQuickWindow::afterAnimating); + + const QPoint out(root->width() - 1, root->height() / 2); + QPoint in(root->width() / 2, root->height() / 2); + + QTest::touchEvent(&window, touchscreen.get()).press(0, out, &window); + QQuickTouchUtils::flush(&window); + QCOMPARE(handler->isHovered(), false); + + frameSyncSpy.clear(); + QTest::touchEvent(&window, touchscreen.get()).move(0, in, &window); + QQuickTouchUtils::flush(&window); + QTRY_COMPARE(handler->isHovered(), true); + QCOMPARE(handler->point().scenePosition(), in); + + in += {10, 10}; + QTest::touchEvent(&window, touchscreen.get()).move(0, in, &window); + QQuickTouchUtils::flush(&window); + // ensure that the color change is visible + QTRY_VERIFY(frameSyncSpy.size() >= 1); + QCOMPARE(handler->isHovered(), true); + QCOMPARE(handler->point().scenePosition(), in); + + QTest::touchEvent(&window, touchscreen.get()).move(0, out, &window); + QQuickTouchUtils::flush(&window); + QTRY_VERIFY(frameSyncSpy.size() >= 2); + QCOMPARE(handler->isHovered(), false); + + QTest::touchEvent(&window, touchscreen.get()).release(0, out, &window); +} + QTEST_MAIN(tst_HoverHandler) #include "tst_qquickhoverhandler.moc" diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/data/nestedAndSibling.qml b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/nestedAndSibling.qml new file mode 100644 index 0000000000..7732c42082 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/data/nestedAndSibling.qml @@ -0,0 +1,39 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 + +Item { + width: 360 + height: 280 + + Rectangle { + width: 200; height: 200; x: 100; y: 10 + color: th1.pressed ? "blue" : "lightblue" + + TapHandler { + id: th1 + objectName: "th1" + } + + Rectangle { + width: 200; height: 200; x: 50; y: 50 + color: th2.pressed ? "steelblue" : "lightsteelblue" + + TapHandler { + id: th2 + objectName: "th2" + } + } + } + + Rectangle { + width: 200; height: 200; x: 10; y: 50 + color: th3.pressed ? "goldenrod" : "beige" + + TapHandler { + id: th3 + objectName: "th3" + } + } +} + diff --git a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp index 02d79c724c..2dbc2d4365 100644 --- a/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquicktaphandler/tst_qquicktaphandler.cpp @@ -73,6 +73,8 @@ private slots: void rightLongPressIgnoreWheel(); void negativeZStackingOrder(); void nonTopLevelParentWindow(); + void nestedAndSiblingPropagation_data(); + void nestedAndSiblingPropagation(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName, @@ -827,6 +829,57 @@ void tst_TapHandler::nonTopLevelParentWindow() // QTBUG-91716 QCOMPARE(root->property("tapCount").toInt(), 2); } +void tst_TapHandler::nestedAndSiblingPropagation_data() +{ + QTest::addColumn<const QPointingDevice *>("device"); + QTest::addColumn<QQuickTapHandler::GesturePolicy>("gesturePolicy"); + QTest::addColumn<bool>("expectPropagation"); + + const QPointingDevice *constTouchDevice = touchDevice; + + QTest::newRow("primary, DragThreshold") << QPointingDevice::primaryPointingDevice() + << QQuickTapHandler::GesturePolicy::DragThreshold << true; + QTest::newRow("primary, WithinBounds") << QPointingDevice::primaryPointingDevice() + << QQuickTapHandler::GesturePolicy::WithinBounds << false; + QTest::newRow("primary, ReleaseWithinBounds") << QPointingDevice::primaryPointingDevice() + << QQuickTapHandler::GesturePolicy::ReleaseWithinBounds << false; + + QTest::newRow("touch, DragThreshold") << constTouchDevice + << QQuickTapHandler::GesturePolicy::DragThreshold << true; + QTest::newRow("touch, WithinBounds") << constTouchDevice + << QQuickTapHandler::GesturePolicy::WithinBounds << false; + QTest::newRow("touch, ReleaseWithinBounds") << constTouchDevice + << QQuickTapHandler::GesturePolicy::ReleaseWithinBounds << false; +} + +void tst_TapHandler::nestedAndSiblingPropagation() // QTBUG-117387 +{ + QFETCH(const QPointingDevice *, device); + QFETCH(QQuickTapHandler::GesturePolicy, gesturePolicy); + QFETCH(bool, expectPropagation); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("nestedAndSibling.qml"))); + QQuickItem *root = window.rootObject(); + QQuickTapHandler *th1 = root->findChild<QQuickTapHandler*>("th1"); + QVERIFY(th1); + th1->setGesturePolicy(gesturePolicy); + QQuickTapHandler *th2 = root->findChild<QQuickTapHandler*>("th2"); + QVERIFY(th2); + th2->setGesturePolicy(gesturePolicy); + QQuickTapHandler *th3 = root->findChild<QQuickTapHandler*>("th3"); + QVERIFY(th3); + th3->setGesturePolicy(gesturePolicy); + + QPoint middle(180, 140); + QQuickTest::pointerPress(device, &window, 0, middle); + QVERIFY(th3->isPressed()); // it's on top + QCOMPARE(th2->isPressed(), expectPropagation); + QCOMPARE(th1->isPressed(), expectPropagation); + + QQuickTest::pointerRelease(device, &window, 0, middle); +} + QTEST_MAIN(tst_TapHandler) #include "tst_qquicktaphandler.moc" diff --git a/tests/auto/quick/qquicktableview/data/positionlast.qml b/tests/auto/quick/qquicktableview/data/positionlast.qml new file mode 100644 index 0000000000..b500f846ac --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/positionlast.qml @@ -0,0 +1,52 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick + +Item { + width: 300 + height: 300 + + property alias tableView: tableView + property bool positionOnRowsChanged: false + property bool positionOnColumnsChanged: false + property bool positionOnContentHeightChanged: false + property bool positionOnContentWidthChanged: false + + TableView { + id: tableView + anchors.fill: parent + clip: true + + delegate: Rectangle { + implicitWidth: 100 + implicitHeight: 100 + Text { + anchors.centerIn: parent + text: "row:" + row + "\ncol:" + column + font.pixelSize: 10 + } + } + + onRowsChanged: { + if (positionOnRowsChanged) + positionViewAtRow(rows - 1, Qt.AlignBottom) + } + + onColumnsChanged: { + if (positionOnColumnsChanged) + positionViewAtColumn(columns - 1, Qt.AlignRight) + } + + onContentHeightChanged: { + if (positionOnContentHeightChanged) + positionViewAtRow(rows - 1, Qt.AlignBottom) + } + + onContentWidthChanged: { + if (positionOnContentWidthChanged) + positionViewAtColumn(columns - 1, Qt.AlignRight) + } + } + +} diff --git a/tests/auto/quick/qquicktableview/testmodel.h b/tests/auto/quick/qquicktableview/testmodel.h index 46e9b446d5..9ab7545761 100644 --- a/tests/auto/quick/qquicktableview/testmodel.h +++ b/tests/auto/quick/qquicktableview/testmodel.h @@ -207,6 +207,11 @@ public: insertRow(row, QModelIndex()); } + Q_INVOKABLE void addColumn(int column) + { + insertColumn(column, QModelIndex()); + } + signals: void rowCountChanged(); void columnCountChanged(); diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index ec568a21e4..5991621376 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -198,6 +198,10 @@ private slots: void positionViewAtRowClamped(); void positionViewAtColumnClamped_data(); void positionViewAtColumnClamped(); + void positionViewAtLastRow_data(); + void positionViewAtLastRow(); + void positionViewAtLastColumn_data(); + void positionViewAtLastColumn(); void itemAtCell_data(); void itemAtCell(); void leftRightTopBottomProperties_data(); @@ -3526,6 +3530,102 @@ void tst_QQuickTableView::positionViewAtColumnClamped() QCOMPARE(tableView->contentX(), column < 50 ? 0 : tableView->contentWidth() - tableView->width()); } +void tst_QQuickTableView::positionViewAtLastRow_data() +{ + QTest::addColumn<QString>("signalToTest"); + + QTest::newRow("positionOnRowsChanged") << "positionOnRowsChanged"; + QTest::newRow("positionOnContentHeightChanged") << "positionOnContentHeightChanged"; +} + +void tst_QQuickTableView::positionViewAtLastRow() +{ + // Check that we can make TableView always scroll to the + // last row in the model by positioning the view upon + // a rowsChanged callback + QFETCH(QString, signalToTest); + + LOAD_TABLEVIEW("positionlast.qml"); + + // Use a very large model to indirectly test that we "fast-flick" to + // the end at start-up (instead of loading and unloading rows, which + // would take forever). + TestModel model(2000000, 2000000); + tableView->setModel(QVariant::fromValue(&model)); + + view->rootObject()->setProperty(signalToTest.toUtf8().constData(), true); + + WAIT_UNTIL_POLISHED; + + const qreal delegateSize = 100.; + const qreal viewportRowCount = tableView->height() / delegateSize; + + // Check that the viewport is positioned at the last row at start-up + QCOMPARE(tableView->rows(), model.rowCount()); + QCOMPARE(tableView->bottomRow(), model.rowCount() - 1); + QCOMPARE(tableView->contentY(), (model.rowCount() - viewportRowCount) * delegateSize); + + // Check that the viewport is positioned at the last + // row after more rows are added. + for (int row = 0; row < 2; ++row) { + model.addRow(model.rowCount() - 1); + + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->rows(), model.rowCount()); + QCOMPARE(tableView->bottomRow(), model.rowCount() - 1); + QCOMPARE(tableView->contentY(), (model.rowCount() - viewportRowCount) * delegateSize); + } +} + +void tst_QQuickTableView::positionViewAtLastColumn_data() +{ + QTest::addColumn<QString>("signalToTest"); + + QTest::newRow("positionOnColumnsChanged") << "positionOnColumnsChanged"; + QTest::newRow("positionOnContentWidthChanged") << "positionOnContentWidthChanged"; +} + +void tst_QQuickTableView::positionViewAtLastColumn() +{ + // Check that we can make TableView always scroll to the + // last column in the model by positioning the view upon + // a columnsChanged callback + QFETCH(QString, signalToTest); + + LOAD_TABLEVIEW("positionlast.qml"); + + // Use a very large model to indirectly test that we "fast-flick" to + // the end at start-up (instead of loading and unloading columns, which + // would take forever). + TestModel model(2000000, 2000000); + tableView->setModel(QVariant::fromValue(&model)); + + view->rootObject()->setProperty(signalToTest.toUtf8().constData(), true); + + WAIT_UNTIL_POLISHED; + + const qreal delegateSize = 100.; + const qreal viewportColumnCount = tableView->width() / delegateSize; + + // Check that the viewport is positioned at the last column at start-up + QCOMPARE(tableView->columns(), model.columnCount()); + QCOMPARE(tableView->rightColumn(), model.columnCount() - 1); + QCOMPARE(tableView->contentX(), (model.columnCount() - viewportColumnCount) * delegateSize); + + // Check that the viewport is positioned at the last + // column after more columns are added. + for (int column = 0; column < 2; ++column) { + model.addColumn(model.columnCount() - 1); + + WAIT_UNTIL_POLISHED; + + QCOMPARE(tableView->columns(), model.columnCount()); + QCOMPARE(tableView->rightColumn(), model.columnCount() - 1); + QCOMPARE(tableView->contentX(), (model.columnCount() - viewportColumnCount) * delegateSize); + } +} + void tst_QQuickTableView::itemAtCell_data() { QTest::addColumn<QPoint>("cell"); diff --git a/tests/baseline/scenegraph/scenegraph/tst_baseline_scenegraph.cpp b/tests/baseline/scenegraph/scenegraph/tst_baseline_scenegraph.cpp index 8155a5e759..fab6554012 100644 --- a/tests/baseline/scenegraph/scenegraph/tst_baseline_scenegraph.cpp +++ b/tests/baseline/scenegraph/scenegraph/tst_baseline_scenegraph.cpp @@ -58,6 +58,7 @@ public: private Q_SLOTS: void initTestCase(); + void init(); void cleanup(); #ifdef TEXTLESS_TEST void testNoTextRendering_data(); @@ -128,6 +129,11 @@ void tst_Scenegraph::initTestCase() QSKIP(msg); } +void tst_Scenegraph::init() +{ + // This gets called for every row. QSKIP if current item is blacklisted on the baseline server: + QBASELINE_SKIP_IF_BLACKLISTED; +} void tst_Scenegraph::cleanup() { diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index 8803170ff2..b331e42978 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -28,14 +28,11 @@ #include "qmlprofilerdata.h" -#include <QStringList> -#include <QUrl> -#include <QHash> -#include <QFile> -#include <QXmlStreamReader> -#include <QRegularExpression> -#include <QQueue> -#include <QStack> +#include <QtCore/qfile.h> +#include <QtCore/qqueue.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qurl.h> +#include <QtCore/qxmlstream.h> #include <limits> @@ -400,6 +397,137 @@ private: QXmlStreamWriter stream; }; +template<typename SendEvent> +struct DataIterator +{ + DataIterator( + const QmlProfilerDataPrivate *d, + SendEvent &&sendEvent) + : d(d) + , sendEvent(std::move(sendEvent)) + {} + + void run(); + +private: + void handleRangeEvent(const QQmlProfilerEvent &event, const QQmlProfilerEventType &type); + void sendPending(); + void endLevel0(); + + const QmlProfilerDataPrivate *d = nullptr; + const SendEvent sendEvent; + + QQueue<QQmlProfilerEvent> pointEvents; + QList<QQmlProfilerEvent> rangeStarts[MaximumRangeType]; + QList<qint64> rangeEnds[MaximumRangeType]; + + int level = 0; +}; + +template<typename SendEvent> +void DataIterator<SendEvent>::handleRangeEvent( + const QQmlProfilerEvent &event, const QQmlProfilerEventType &type) +{ + QList<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()]; + switch (event.rangeStage()) { + case RangeStart: { + ++level; + starts.append(event); + break; + } + case RangeEnd: { + const qint64 invalidTimestamp = -1; + QList<qint64> &ends = rangeEnds[type.rangeType()]; + + // -1 because all valid timestamps are >= 0. + ends.resize(starts.size(), invalidTimestamp); + + qsizetype i = starts.size(); + while (ends[--i] != invalidTimestamp) {} + + Q_ASSERT(i >= 0); + Q_ASSERT(starts[i].timestamp() <= event.timestamp()); + + ends[i] = event.timestamp(); + if (--level == 0) + endLevel0(); + break; + } + default: + break; + } +} + +template<typename SendEvent> +void DataIterator<SendEvent>::sendPending() +{ + // Send all pending events in the order of their start times. + + qsizetype index[MaximumRangeType] = { 0, 0, 0, 0, 0, 0 }; + while (true) { + + // Find the range type with the minimum start time. + qsizetype minimum = MaximumRangeType; + qint64 minimumTime = std::numeric_limits<qint64>::max(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + const QList<QQmlProfilerEvent> &starts = rangeStarts[i]; + if (starts.size() == index[i]) + continue; + const qint64 timestamp = starts[index[i]].timestamp(); + if (timestamp < minimumTime) { + minimumTime = timestamp; + minimum = i; + } + } + if (minimum == MaximumRangeType) + break; + + // Send all point events that happened before the range we've found. + while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) + sendEvent(pointEvents.dequeue(), 0); + + // Send the range itself + sendEvent(rangeStarts[minimum][index[minimum]], + rangeEnds[minimum][index[minimum]] - minimumTime); + + // Bump the index so that we don't send the same range again + ++index[minimum]; + } +} + +template<typename SendEvent> +void DataIterator<SendEvent>::endLevel0() +{ + sendPending(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + rangeStarts[i].clear(); + rangeEnds[i].clear(); + } +} + +template<typename SendEvent> +void DataIterator<SendEvent>::run() +{ + for (const QQmlProfilerEvent &event : std::as_const(d->events)) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + if (type.rangeType() != MaximumRangeType) + handleRangeEvent(event, type); + else if (level == 0) + sendEvent(event, 0); + else + pointEvents.enqueue(event); + } + + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + while (rangeEnds[i].size() < rangeStarts[i].size()) { + rangeEnds[i].append(d->traceEndTime); + --level; + } + } + + sendPending(); +} + bool QmlProfilerData::save(const QString &filename) { if (isEmpty()) { @@ -467,6 +595,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeStartElement("profilerDataModel"); auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) { + Q_ASSERT(duration >= 0); const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); stream.writeStartElement("range"); stream.writeAttribute("startTime", event.timestamp()); @@ -506,74 +635,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeEndElement(); }; - QQueue<QQmlProfilerEvent> pointEvents; - QQueue<QQmlProfilerEvent> rangeStarts[MaximumRangeType]; - QStack<qint64> rangeEnds[MaximumRangeType]; - int level = 0; - - auto sendPending = [&]() { - forever { - int minimum = MaximumRangeType; - qint64 minimumTime = std::numeric_limits<qint64>::max(); - for (int i = 0; i < MaximumRangeType; ++i) { - const QQueue<QQmlProfilerEvent> &starts = rangeStarts[i]; - if (starts.isEmpty()) - continue; - if (starts.head().timestamp() < minimumTime) { - minimumTime = starts.head().timestamp(); - minimum = i; - } - } - if (minimum == MaximumRangeType) - break; - - while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) - sendEvent(pointEvents.dequeue()); - - sendEvent(rangeStarts[minimum].dequeue(), - rangeEnds[minimum].pop() - minimumTime); - } - }; - - for (const QQmlProfilerEvent &event : qAsConst(d->events)) { - const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); - - if (type.rangeType() != MaximumRangeType) { - QQueue<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()]; - switch (event.rangeStage()) { - case RangeStart: { - ++level; - starts.enqueue(event); - break; - } - case RangeEnd: { - QStack<qint64> &ends = rangeEnds[type.rangeType()]; - if (starts.length() > ends.length()) { - ends.push(event.timestamp()); - if (--level == 0) - sendPending(); - } - break; - } - default: - break; - } - } else { - if (level == 0) - sendEvent(event); - else - pointEvents.enqueue(event); - } - } - - for (int i = 0; i < MaximumRangeType; ++i) { - while (rangeEnds[i].length() < rangeStarts[i].length()) { - rangeEnds[i].push(d->traceEndTime); - --level; - } - } - - sendPending(); + DataIterator(d, std::move(sendEvent)).run(); stream.writeEndElement(); // profilerDataModel |