diff --git a/local/recipes/qt/qtdeclarative/source/.cmake.conf b/local/recipes/qt/qtdeclarative/source/.cmake.conf index aad5e10a4a..715cb92e1f 100644 --- a/local/recipes/qt/qtdeclarative/source/.cmake.conf +++ b/local/recipes/qt/qtdeclarative/source/.cmake.conf @@ -1,10 +1,11 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.11.1") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES - "QT_LEAN_HEADERS=1" + "QT_LEAN_HEADERS=2" "QT_NO_QASCONST=1" "QT_NO_FOREACH=1" + "QT_NO_QPAIR=1" "QT_NO_QSNPRINTF=1" "QT_NO_URL_CAST_FROM_STRING=1" ) diff --git a/local/recipes/qt/qtdeclarative/source/.tag b/local/recipes/qt/qtdeclarative/source/.tag index b1552451c1..5c21ff21ba 100644 --- a/local/recipes/qt/qtdeclarative/source/.tag +++ b/local/recipes/qt/qtdeclarative/source/.tag @@ -1 +1 @@ -a59ffcedd7cf65b8d2be249c9656f7d540a4a104 +e50599b560e38d5991de59020671c01a42d1d40e diff --git a/local/recipes/qt/qtdeclarative/source/coin/axivion/start_analysis.sh b/local/recipes/qt/qtdeclarative/source/coin/axivion/start_analysis.sh index 8222e2566c..f48c8b1465 100755 --- a/local/recipes/qt/qtdeclarative/source/coin/axivion/start_analysis.sh +++ b/local/recipes/qt/qtdeclarative/source/coin/axivion/start_analysis.sh @@ -14,6 +14,24 @@ gccsetup --cc gcc --cxx g++ --config "$BAUHAUS_CONFIG" cd "$CAFECC_BASEPATH" BAUHAUS_IR_COMPRESSION=none COMPILE_ONLY=1 cmake -G Ninja -DAXIVION_ANALYSIS_TOOLCHAIN_FILE=/home/qt/bauhaus-suite/profiles/cmake/axivion-launcher-toolchain.cmake -DCMAKE_PREFIX_PATH=/home/qt/work/qt/qtdeclarative/build -DCMAKE_PROJECT_INCLUDE_BEFORE=/home/qt/bauhaus-suite/profiles/cmake/axivion-before-project-hook.cmake -B build -S . --fresh cmake --build build -j4 + +ROOT_DIR=src +MAGIC="Qt-Security score:critical" + +INCLUDE_FILES="" + +while IFS= read -r file; do + if [ -z "$INCLUDE_FILES" ]; then + INCLUDE_FILES="$file" + else + INCLUDE_FILES="$INCLUDE_FILES:$file" + fi +done < #include class SongDataGenerator diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/itemswitcher/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/itemswitcher/CMakeLists.txt index 309069e59e..d08f092b71 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/itemswitcher/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/itemswitcher/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(itemswitcherexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(itemswitcherexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(itemswitcherexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/neumorphicpanel/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/neumorphicpanel/CMakeLists.txt index f7b8306005..e000a1d57e 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/neumorphicpanel/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/neumorphicpanel/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(neumorphicpanelexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(neumorphicpanelexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(neumorphicpanelexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/testbed/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/testbed/CMakeLists.txt index b243bdbd5e..c1caa5a723 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/testbed/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/multieffect/testbed/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(testbedexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(testbedexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(testbedexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/affectors/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/affectors/CMakeLists.txt index 606b1b4f16..0566e7a9f0 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/affectors/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/affectors/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(affectorsexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(affectorsexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(affectorsexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/emitters/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/emitters/CMakeLists.txt index d3a8180cb2..ade811c602 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/emitters/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/emitters/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(emittersexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(emittersexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(emittersexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/imageparticle/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/imageparticle/CMakeLists.txt index 4c078929d1..0ddf329ef6 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/imageparticle/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/imageparticle/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(imageparticleexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(imageparticleexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(imageparticleexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/system/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/system/CMakeLists.txt index 3d465d759c..a67fd11cf1 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/particles/system/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/particles/system/CMakeLists.txt @@ -14,6 +14,8 @@ qt_add_executable(systemexample WIN32 MACOSX_BUNDLE main.cpp ) +set_target_properties(systemexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(systemexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/pointerhandlers/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/pointerhandlers/CMakeLists.txt index 028ac9a099..65703cd46b 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/pointerhandlers/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/pointerhandlers/CMakeLists.txt @@ -12,6 +12,8 @@ add_subdirectory("../shared" "shared") qt_add_executable(pointerhandlersexample WIN32 MACOSX_BUNDLE main.cpp) +set_target_properties(pointerhandlersexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(pointerhandlersexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/shapes/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/shapes/CMakeLists.txt index d7a12ed4bd..c9de740560 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/shapes/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/shapes/CMakeLists.txt @@ -16,6 +16,8 @@ qt_add_executable(shapesexample main.cpp ) +set_target_properties(shapesexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(shapesexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/weatherforecast/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/weatherforecast/CMakeLists.txt index c5d3887016..c3698be476 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/weatherforecast/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/quickshapes/weatherforecast/CMakeLists.txt @@ -16,6 +16,8 @@ qt_add_executable(weatherforecastexample main.cpp ) +set_target_properties(weatherforecastexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(weatherforecastexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/quickwidgets/quickwidget/main.cpp b/local/recipes/qt/qtdeclarative/source/examples/quick/quickwidgets/quickwidget/main.cpp index f8e0b78844..871041719a 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/quickwidgets/quickwidget/main.cpp +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/quickwidgets/quickwidget/main.cpp @@ -108,7 +108,8 @@ void MainWindow::createQuickWidgetsInTabs(QMdiArea *mdiArea) widget->setSource(QUrl("qrc:quickwidget/rotatingsquare.qml")); widget->setWindowTitle(QString::asprintf("Tab %d", i + 1)); QPushButton *btn = new QPushButton(msgToTopLevel, widget); - connect(btn, &QPushButton::clicked, widget, [=] { + connect(btn, &QPushButton::clicked, + widget, [this, widget, btn, tabWidget, msgFromTopLevel, msgToTopLevel] { if (widget->parent()) { widget->setAttribute(Qt::WA_DeleteOnClose, true); widget->setParent(nullptr); diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.cpp b/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.cpp index f858e20e8f..10feeadaa9 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.cpp +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.cpp @@ -167,6 +167,15 @@ void QuickRenderer::ensureTexture() qreal dpr = m_quickWindow->devicePixelRatio(); QSize textureSize = m_quickWindow->size() * dpr; QOpenGLFunctions *f = m_context->functions(); + + if (m_textureId && m_textureSize != textureSize) { + f->glDeleteTextures(1, &m_textureId); + m_textureId = 0; + } + + if (m_textureId) + return; + f->glGenTextures(1, &m_textureId); f->glBindTexture(GL_TEXTURE_2D, m_textureId); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -174,6 +183,7 @@ void QuickRenderer::ensureTexture() f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureSize.width(), textureSize.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); m_quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(m_textureId, textureSize)); + m_textureSize = textureSize; } void QuickRenderer::render(QMutexLocker *lock) diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.h b/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.h index e505080081..67b385092e 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.h +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/rendercontrol/rendercontrol_opengl/window_multithreaded.h @@ -62,6 +62,7 @@ private: QQuickRenderControl *m_renderControl; QRhi *m_rhi; uint m_textureId; + QSize m_textureSize; CubeRenderer *m_cubeRenderer; QMutex m_quitMutex; bool m_quit; diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/text/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/text/CMakeLists.txt index c9b0aa79df..8464553ea5 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/text/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/text/CMakeLists.txt @@ -16,6 +16,8 @@ qt_add_executable(textexample main.cpp ) +set_target_properties(textexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(textexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/text/fonts/banner.qml b/local/recipes/qt/qtdeclarative/source/examples/quick/text/fonts/banner.qml index aae93baea2..c053a30f1d 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/text/fonts/banner.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/text/fonts/banner.qml @@ -28,16 +28,19 @@ Rectangle { font.pixelSize: screen.pixelSize color: screen.textColor text: screen.text + renderType: Text.CurveRendering } Text { font.pixelSize: screen.pixelSize color: screen.textColor text: screen.text + renderType: Text.CurveRendering } Text { font.pixelSize: screen.pixelSize color: screen.textColor text: screen.text + renderType: Text.CurveRendering } } } diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/views/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/views/CMakeLists.txt index 6acced3b22..829e12efc8 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/views/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/views/CMakeLists.txt @@ -18,6 +18,8 @@ qt_add_executable(viewsexample main.cpp ) +set_target_properties(viewsexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(viewsexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quick/window/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quick/window/CMakeLists.txt index 9104974a11..babda332cf 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quick/window/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quick/window/CMakeLists.txt @@ -16,6 +16,8 @@ qt_add_executable(windowexample main.cpp ) +set_target_properties(windowexample PROPERTIES FOLDER "qtc_runnable") + target_link_libraries(windowexample PRIVATE Qt6::Core Qt6::Gui diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactDialog.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactDialog.qml index 3f287447ef..7ded19c0fe 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactDialog.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactDialog.qml @@ -34,16 +34,21 @@ Dialog { focus: true modal: true - title: qsTr("Add Contact") standardButtons: Dialog.Ok | Dialog.Cancel contentItem: ContactForm { id: form + Binding { + target: dialog.standardButton(Dialog.Ok) + property: "enabled" + value: form.fullName.text + && form.address.text + && form.city.text + && form.number.text + } } onAccepted: { - if (form.fullName.text && form.address.text && form.city.text && form.number.text) { - finished(form.fullName.text, form.address.text, form.city.text, form.number.text); - } + finished(form.fullName.text, form.address.text, form.city.text, form.number.text); } } diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactForm.ui.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactForm.ui.qml index 56c9186194..7c878b551c 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactForm.ui.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactForm.ui.qml @@ -68,5 +68,6 @@ GridLayout { Layout.minimumWidth: grid.minimumInputSize Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline placeholderText: grid.placeholderText + inputMethodHints: Qt.ImhDigitsOnly } } diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactList.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactList.qml index 121b38f352..ebb321c439 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactList.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/ContactList.qml @@ -18,9 +18,9 @@ ApplicationWindow { id: contactDialog onFinished: function(fullName, address, city, number) { if (window.currentContact === -1) - contactView.model.append(fullName, address, city, number) + contactView.model.add(fullName, address, city, number) else - contactView.model.set(window.currentContact, fullName, address, city, number) + contactView.model.edit(window.currentContact, fullName, address, city, number) } } diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.cpp b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.cpp index 85ebad0515..aecf6797f6 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.cpp @@ -53,23 +53,20 @@ QVariantMap ContactModel::get(int row) const return { {"fullName", contact.fullName}, {"address", contact.address}, {"city", contact.city}, {"number", contact.number} }; } -void ContactModel::append(const QString &fullName, const QString &address, const QString &city, const QString &number) +void ContactModel::add(const QString &fullName, const QString &address, const QString &city, const QString &number) { int row = 0; - while (row < m_contacts.count() && fullName > m_contacts.at(row).fullName) + while (row < m_contacts.count() && (fullName.compare(m_contacts.at(row).fullName, Qt::CaseInsensitive) > 0)) ++row; beginInsertRows(QModelIndex(), row, row); m_contacts.insert(row, {fullName, address, city, number}); endInsertRows(); } -void ContactModel::set(int row, const QString &fullName, const QString &address, const QString &city, const QString &number) +void ContactModel::edit(int row, const QString &fullName, const QString &address, const QString &city, const QString &number) { - if (row < 0 || row >= m_contacts.count()) - return; - - m_contacts.replace(row, { fullName, address, city, number }); - dataChanged(index(row, 0), index(row, 0), { FullNameRole, AddressRole, CityRole, NumberRole }); + remove(row); + add(fullName, address, city, number); } void ContactModel::remove(int row) diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.h b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.h index 78a06b8132..3aecabd3fa 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.h +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/contactlist/contactmodel.h @@ -28,8 +28,8 @@ public: QHash roleNames() const; Q_INVOKABLE QVariantMap get(int row) const; - Q_INVOKABLE void append(const QString &fullName, const QString &address, const QString &city, const QString &number); - Q_INVOKABLE void set(int row, const QString &fullName, const QString &address, const QString &city, const QString &number); + Q_INVOKABLE void add(const QString &fullName, const QString &address, const QString &city, const QString &number); + Q_INVOKABLE void edit(int row, const QString &fullName, const QString &address, const QString &city, const QString &number); Q_INVOKABLE void remove(int row); private: diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/CMakeLists.txt index 35c5c1ae31..5f31449715 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/CMakeLists.txt @@ -6,13 +6,15 @@ project(StyleKitExample VERSION 1.0 LANGUAGES CXX) find_package(Qt6 6.11 REQUIRED Core Gui Qml Quick QuickControls2) find_program(QSB_EXECUTABLE qsb HINTS "${QT_HOST_PATH}/bin") +if(NOT QSB_EXECUTABLE OR NOT EXISTS "${QSB_EXECUTABLE}") + message(FATAL_ERROR "qsb not found. Please install Qt Shader Tools to build this example.") +endif() qt_standard_project_setup(REQUIRES 6.11) set(STYLES styles/Plain.qml styles/Haze.qml - styles/Experimental.qml styles/Vitrum.qml styles/CustomDelegates.qml ) diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Experimental.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Experimental.qml deleted file mode 100644 index 564d7346d8..0000000000 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Experimental.qml +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright (C) 2025 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import Qt.labs.StyleKit - -Style { - id: style - - control { - leftPadding: 10 - topPadding: 5 - rightPadding: 10 - bottomPadding: 5 - - background { - implicitWidth: 100 - implicitHeight: 40 - shadow { - opacity: 0.8 - scale: 1.1 - verticalOffset: 5 - horizontalOffset: 2 - } - } - - handle { - implicitWidth: 25 - implicitHeight: 25 - radius: 25 - color: "lightslategray" - } - - indicator { - foreground.margins: 2 - - // first.color: "green" - // second.color: "yellow" - // up.color: "red" // alias. Can control.indicator.up have control.indicator as alt? - // down.color: "blue" - } - - text.color: "blue" - - transition: Transition { - StyleAnimation { - animateColors: true - animateBackgroundBorder: true - duration: 300 - } - } - - hovered { - transition: null - background { - border.width: 4 - } - handle { - border.width: 2 - } - text.color: "yellow" - } - - pressed { - background { - scale: 0.95 - } - } - } - - pane { - background { - border.width: 0 - implicitWidth: 200 - implicitHeight: 200 - shadow.visible: false - } - } - - frame { - background { - border.width: 1 - shadow.visible: true - } - } - - button { - background { - radius: 8 - opacity: 0.8 - - gradient: Gradient { - GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} - GradientStop { position: 1.0; color: Qt.alpha("black", 0.4)} - } - } - } - - radioButton { - indicator { - foreground { - margins: 4 - radius: 25 / 2 - border.width: 0 - } - } - } - - textInput { - background { - implicitWidth: 200 - } - } - - spinBox { - padding: 0 - background { - implicitWidth: 100 - } - } - - comboBox { - background { - implicitWidth: 200 - implicitHeight: 30 - gradient: null - } - indicator { - foreground.scale: 0.5 - } - pressed.background.scale: 1.0 - variations: [contextMenu, mini, alert, "dont crash"] - } - - StyleVariation { - id: contextMenu - itemDelegate { - background { - radius: 0 - border.width: 0 - shadow.color: "transparent" - gradient: null - } - hovered.background.color: "pink" - } - - control.transition: null - } - - StyleVariation { - id: mini - control.background.implicitHeight: 10 - } - - StyleVariation { - id: alert - control.background.color: "red" - } - - slider { - background.implicitWidth: 180 - indicator { - implicitHeight: 8 - radius: 8 - foreground { - radius: 8 - } - } - handle { - radius: 25 - first { - topRightRadius: 0 - bottomRightRadius: 0 - color: "orange" - } - second { - topLeftRadius: 0 - bottomLeftRadius: 0 - color: "yellow" - } - } - vertical { - handle { - first { - radius: 25 - topLeftRadius: 0 - bottomLeftRadius: 0 - } - second { - radius: 25 - topRightRadius: 0 - bottomRightRadius: 0 - } - } - } - } - - switchControl { - indicator { - implicitWidth: 60 - implicitHeight: 30 - radius: 5 - foreground.radius: 4 - } - handle { - leftMargin: 3 - rightMargin: 3 - } - } - - itemDelegate { - background { - radius: 0 - border.width: 0 - shadow.visible: false - gradient: null - } - transition: null - hovered { - background.color: style.palette.highlight - } - } - - // THEMES - - light: Theme { - control { - background { - border.color: "lightgray" - shadow.color: "#808080" - } - - checked { - background.shadow.color: "white" - background.color: "blue" - } - - focused { - background.border.color: "white" - background.shadow.color: "white" - } - - hovered { - background { - border.color: "white" - shadow.color: "white" - } - } - - disabled { - background { - color: "#a7a7a7" - shadow.color: "transparent" - } - } - } - - button { - background.color: "lightgray" - } - - textField { - variations: StyleVariation { - button { - background { - radius: 0 - implicitWidth: 50 - implicitHeight: 20 - margins: 4 - color: "orange" - shadow.color: "transparent" - } - } - } - } - - palettes { - system.window: "#989898" - textField.text: "#4e4e4e" - button { - buttonText: "white" - highlightedText: "white" - brightText: "#4e4e4e" - disabled.buttonText: "#4e4e4e" - disabled.highlightedText: "#4e4e4e" - } - } - } - - dark: Theme { - control { - background { - border.color: "#3d373b" - shadow.color: "#404040" - color: "#8e848a" - } - - handle { - border.color: "black" - } - - focused { - background { - border.color: "white" - shadow.color: "white" - color: "#bbbbbb" - } - } - - hovered { - background { - border.color: "white" - shadow.color: "white" - } - } - - disabled { - background { - color: "red"//"#766e73" - shadow.color: "transparent" - } - checked.background.color: "green" - } - } - - textField { - variations: StyleVariation { - button { - background { - radius: 0 - implicitWidth: 50 - implicitHeight: 20 - margins: 4 - color: "lightblue" - shadow.color: "transparent" - } - } - } - } - - palettes { - system.window: "#544e52" - textField.text: "black" - button { - buttonText: "white" - highlightedText: "white" - brightText: "white" - disabled.buttonText: "darkgray" - disabled.highlightedText: "darkgray" - } - } - } - - CustomTheme { - name: "HighContrast" - theme: Theme { - control { - background { - border.color: "black" - shadow.color: "transparent" - color: "white" - gradient: null - } - - indicator { - implicitWidth: 30 - implicitHeight: 30 - } - - handle { - border.color: "black" - implicitWidth: 30 - implicitHeight: 30 - radius: 30 - } - - hovered { - background { - border.color: "red" - border.width: 3 - } - indicator { - border.color: "red" - border.width: 3 - } - } - - checked { - background { - border.color: "red" - border.width: 3 - } - } - - disabled { - background.color: "gray" - } - } - - slider { - indicator { - implicitWidth: 180 - implicitHeight: 8 - } - } - - switchControl { - spacing: 2 - indicator { - implicitWidth: 60 - implicitHeight: 34 - } - } - - palettes { - system.window: "white" - textField.text: "black" - button { - buttonText: "black" - highlightedText: "black" - brightText: "black" - disabled.buttonText: "white" - disabled.highlightedText: "white" - } - } - } - } - - CustomTheme { - name: "Green" - theme: Theme { - control { - background { - border.color: "lightgray" - shadow.color: "#404040" - color: "#a0c0a0" - } - - handle { - border.color: "lightgray" - shadow.color: "#404040" - color: "#a0c0a0" - } - - indicator { - foreground.color: "lime" - foreground.image.color: "black" - } - - hovered { - background { - border.color: "lightgreen" - shadow.color: "lightgreen" - color: "lightgreen" - } - handle { - border.color: "lightgreen" - shadow.color: "lightgreen" - color: "lightgreen" - } - } - - checked { - background { - shadow.color: "lightgreen" - color: "lightgreen" - } - } - - disabled { - background { - color: "#80a080" - shadow.color: "transparent" - } - } - } - - palettes { - system.window: "#547454" - textField.text: "green" - checkBox.buttonText: "white" - button { - buttonText: "black" - highlightedText: "white" - disabled.buttonText: "lightgray" - disabled.highlightedText: "lightgray" - } - } - } - } - - CustomTheme { - name: "Empty" - theme: Theme {} - } -} diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Haze.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Haze.qml index cdeda4a3de..e2ff50b6bf 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Haze.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Haze.qml @@ -256,15 +256,6 @@ Style { } } - StyleVariation { - name: "alert" - abstractButton.background { - color: "orchid" - border.color: "orchid" - shadow.color: "orchid" - } - } - /* You can also set one or more StyleVariations on a control type. Unlike Instance * variations—which apply only to specific control instances—type variations are applied * to *all* instances of a control type without requiring the application to use attached @@ -413,6 +404,15 @@ Style { } } + StyleVariation { + name: "alert" + abstractButton.background { + color: "orchid" + border.color: "orchid" + shadow.color: "orchid" + } + } + CustomControl { controlType: fancyButton background { @@ -513,7 +513,6 @@ Style { } scrollBar { - background.color: "#8e848a" indicator.foreground.color: "white" } @@ -534,6 +533,15 @@ Style { background.shadow.visible: false } + StyleVariation { + name: "alert" + abstractButton.background { + color: "orchid" + border.color: "orchid" + shadow.color: "orchid" + } + } + CustomControl { controlType: fancyButton background { @@ -690,8 +698,6 @@ Style { background.implicitHeight: 15 indicator.implicitWidth: 15 indicator.implicitHeight: 15 - background.color: "#8e848a" - background.border.width: 1 indicator.border.width: 3 indicator.foreground.margins: 3 indicator.foreground.color: "lightgray" diff --git a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Vitrum.qml b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Vitrum.qml index 03973c28b1..347ff2bd95 100644 --- a/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Vitrum.qml +++ b/local/recipes/qt/qtdeclarative/source/examples/quickcontrols/stylekit/styles/Vitrum.qml @@ -234,6 +234,7 @@ Style { scrollBar { padding: 2 background.implicitHeight: 20 + background.visible: false indicator.implicitHeight: 20 vertical { background.implicitWidth: 20 diff --git a/local/recipes/qt/qtdeclarative/source/qt_cmdline.cmake b/local/recipes/qt/qtdeclarative/source/qt_cmdline.cmake index 2fc41a5f66..7674cd66b2 100644 --- a/local/recipes/qt/qtdeclarative/source/qt_cmdline.cmake +++ b/local/recipes/qt/qtdeclarative/source/qt_cmdline.cmake @@ -5,4 +5,5 @@ qt_commandline_subconfig(src/qml) qt_commandline_subconfig(src/qmlmodels) qt_commandline_subconfig(src/quick) qt_commandline_subconfig(src/quickcontrols) +qt_commandline_subconfig(src/quickshapes) qt_commandline_subconfig(src/quicktemplates) diff --git a/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/stubs/wtf/text/WTFString.h b/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/stubs/wtf/text/WTFString.h index 17346593f9..c03d015c3d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/stubs/wtf/text/WTFString.h +++ b/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/stubs/wtf/text/WTFString.h @@ -6,6 +6,9 @@ #include #include #include + +#include + #include namespace WTF { @@ -27,6 +30,15 @@ public: bool operator!() const { return isEmpty(); } void dump(PrintStream &) const {} + +private: + friend size_t qHash(const String &key, size_t seed = 0) noexcept + { + // delegate to qHash(QString) + const QString &s = key; + using QT_PREPEND_NAMESPACE(qHash); + return qHash(s, seed); + } }; template <> diff --git a/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/wtf/CheckedArithmetic.h b/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/wtf/CheckedArithmetic.h index f10ef25547..1aeb76434a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/wtf/CheckedArithmetic.h +++ b/local/recipes/qt/qtdeclarative/source/src/3rdparty/masm/wtf/CheckedArithmetic.h @@ -397,36 +397,6 @@ template struct ArithmeticOperations struct ArithmeticOperations { - static inline bool add(unsigned lhs, long rhs, ResultType& result) WARN_UNUSED_RETURN - { - int64_t temp = static_cast(lhs) + static_cast(rhs); - if (temp < std::numeric_limits::min()) return false; - if (temp > std::numeric_limits::max()) return false; - result = static_cast(temp); - return true; - } - static inline bool sub(unsigned lhs, long rhs, ResultType& result) WARN_UNUSED_RETURN - { - int64_t temp = static_cast(lhs) - static_cast(rhs); - if (temp < std::numeric_limits::min()) return false; - if (temp > std::numeric_limits::max()) return false; - result = static_cast(temp); - return true; - } - static inline bool multiply(unsigned lhs, long rhs, ResultType& result) WARN_UNUSED_RETURN - { - int64_t temp = static_cast(lhs) * static_cast(rhs); - if (temp < std::numeric_limits::min()) return false; - if (temp > std::numeric_limits::max()) return false; - result = static_cast(temp); - return true; - } - static inline bool equals(unsigned lhs, long rhs) - { - return static_cast(lhs) == static_cast(rhs); - } -}; template static inline bool safeAdd(U lhs, V rhs, R& result) { return ArithmeticOperations::add(lhs, rhs, result); diff --git a/local/recipes/qt/qtdeclarative/source/src/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/CMakeLists.txt index 302bbe0053..8fee586af1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/CMakeLists.txt @@ -34,7 +34,7 @@ if(QT_FEATURE_qml_xmllistmodel) add_subdirectory(qmlxmllistmodel) endif() -if(TARGET Qt::Gui AND QT_FEATURE_qml_animation) +if(TARGET Qt::Gui AND TARGET Qt::qsb AND QT_FEATURE_qml_animation) add_subdirectory(quick) add_subdirectory(quicklayouts) @@ -117,7 +117,7 @@ if(QT_FEATURE_qml_network) endif() endif() -if(TARGET Qt::Gui AND QT_FEATURE_qml_animation) +if(TARGET Qt::Gui AND TARGET Qt::qsb AND QT_FEATURE_qml_animation) find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS Test) if(QT_FEATURE_testlib AND TARGET Qt::Test) add_subdirectory(qmltest) diff --git a/local/recipes/qt/qtdeclarative/source/src/core/doc/src/includes/qmlpermissions.qdocinc b/local/recipes/qt/qtdeclarative/source/src/core/doc/src/includes/qmlpermissions.qdocinc index 6255394511..eb7908cd15 100644 --- a/local/recipes/qt/qtdeclarative/source/src/core/doc/src/includes/qmlpermissions.qdocinc +++ b/local/recipes/qt/qtdeclarative/source/src/core/doc/src/includes/qmlpermissions.qdocinc @@ -26,9 +26,9 @@ //! [status-docs] //! [request-docs] - \qmlmethod \1::request() + \qmlmethod void \1::request() Requests the permission with its current properties. - \sa QCoreApplication::requestPermission + \sa QCoreApplication::requestPermission() //! [request-docs] diff --git a/local/recipes/qt/qtdeclarative/source/src/effects/qquickmultieffect.cpp b/local/recipes/qt/qtdeclarative/source/src/effects/qquickmultieffect.cpp index 5182c9db0f..3fdcb059f3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/effects/qquickmultieffect.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/effects/qquickmultieffect.cpp @@ -67,6 +67,7 @@ Q_STATIC_LOGGING_CATEGORY(lcQuickEffect, "qt.quick.effects") \table 70% \row \li \image multieffect-example1.png + {Qt logo in grayscale showing full desaturation effect} \li \qml import QtQuick import QtQuick.Effects @@ -97,6 +98,7 @@ Q_STATIC_LOGGING_CATEGORY(lcQuickEffect, "qt.quick.effects") \table 70% \row \li \image multieffect-example1.png + {Qt logo in grayscale showing full desaturation effect} \li \qml import QtQuick import QtQuick.Effects @@ -120,6 +122,8 @@ Q_STATIC_LOGGING_CATEGORY(lcQuickEffect, "qt.quick.effects") \table 70% \row \li \image multieffect-example2.png + {Qt logo with green glow showing brightness, saturation, and + blur effects combined} \li \qml import QtQuick import QtQuick.Effects @@ -145,6 +149,8 @@ Q_STATIC_LOGGING_CATEGORY(lcQuickEffect, "qt.quick.effects") \table 70% \row \li \image multieffect-example3.png + {Four stages of Qt logo fading with mask and colorization, + from visible green to dissolved pink particles} \li \qml import QtQuick import QtQuick.Effects @@ -250,6 +256,8 @@ void QQuickMultiEffect::setSource(QQuickItem *item) Note that \l paddingRect is always added to the size. \image multieffect-example4.png + {Blurred images comparing autoPaddingEnabled true with extra + space versus false with clipped edges} \sa paddingRect @@ -282,6 +290,8 @@ void QQuickMultiEffect::setAutoPaddingEnabled(bool enabled) false so that the shadow fits inside the MultiEffect item. \image multieffect-example5.png + {Two Qt logos with shadows comparing paddingRect values, showing + clipped shadow at zero versus full shadow with padding} \sa autoPaddingEnabled diff --git a/local/recipes/qt/qtdeclarative/source/src/effects/qquickrectangularshadow.cpp b/local/recipes/qt/qtdeclarative/source/src/effects/qquickrectangularshadow.cpp index c5c9fcf24d..fa3755acb9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/effects/qquickrectangularshadow.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/effects/qquickrectangularshadow.cpp @@ -26,6 +26,7 @@ QT_BEGIN_NAMESPACE \table 70% \row \li \image rectangularshadow-example-1.png + {Rounded rectangle with soft shadow offset to upper left} \li \qml import QtQuick import QtQuick.Effects @@ -96,23 +97,31 @@ QT_BEGIN_NAMESPACE \li offset: (0, 0) \br blur: 20 \br spread: 0 \br - \li \image rectangularshadow-css-1.png - \li \image rectangularshadow-item-1.png + \li \image rectangularshadow-css-1.png {CSS box-shadow with blur 20} + \li \image rectangularshadow-item-1.png {RectangularShadow with blur 20} \li \image rectangularshadow-itemblur-1.png + {RectangularShadow with blur 24 for closer CSS match} \row \li offset: (-10, -20) \br blur: 40 \br spread: 0 \br \li \image rectangularshadow-css-2.png + {CSS box-shadow with offset and blur 40} \li \image rectangularshadow-item-2.png + {RectangularShadow with offset and blur 40} \li \image rectangularshadow-itemblur-2.png + {RectangularShadow with blur 48 for closer CSS match} \row \li offset: (-10, -20) \br blur: 40 \br spread: 10 \br \li \image rectangularshadow-css-3.png + {CSS box-shadow with offset, blur 40, and spread 10} \li \image rectangularshadow-item-3.png + {RectangularShadow with offset, blur 40, and spread 10} \li \image rectangularshadow-itemblur-3.png + {RectangularShadow with blur 48 and spread 10 for closer CSS + match} \endtable diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltablemodel.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltablemodel.cpp index 5b7c54ee51..f87e167b83 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltablemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltablemodel.cpp @@ -128,6 +128,33 @@ Q_STATIC_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") \l [QtQuickControls2]{TextField}, and so that delegate is declared last as a fallback. + \section1 Using the TableModel with External JSON Sources + + If the structure of the data in a JSON object matches the structure defined + by the TableModel, it can be assigned directly to the model using the rows + property. All rows in the data must conform to the columns defined by the model. + + For example, if the model is defined as: + + \snippet main.qml model + + The source has the required data but not necessarily in the + required structure, you can download and parse it into a form that + conforms to the structure that the model defines and assign the result directly. + + \snippet main.qml parse + + and the result can be assigned directly + + \snippet main.qml assign + + You can also provide the JSON object by importing a JavaScript file as a + module. See the documentation for \l TreeModel for an example on how to do this. + + \warning Ensure that the JSON data comes from a trusted source. Since the + model dynamically populates its rows based on the input, malformed or + untrusted JSON can lead to unexpected behavior or performance issues. + \sa TableModelColumn, TableView, QAbstractTableModel */ diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreemodel.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreemodel.cpp index e595210b9a..0c481374de 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreemodel.cpp @@ -56,6 +56,42 @@ static const QString ROWS_PROPERTY_NAME = u"rows"_s; To add new rows, use \l appendRow(). To modify existing rows, use \l setRow(), \l removeRow() and \l clear(). + + \section1 Using the TreeModel with External JSON Sources + + While the intended use of the TreeModel is to define the JSON data in-place in + the QML file, you can use any JSON object with the model as long as + its structure matches the structure defined by the TreeModel. If all the rows + of the JSON object conform to the columns defined by the model, you can + assign it directly through the rows property. + + The source of the JSON object can be a file. For example, if the model is + defined as + + \snippet setRowsViaJSON.qml model + + and the JSON object stored in the file and looks like + + \snippet TreeData.js file + + then this object can be provided by importing the JavaScript file as a + + module + + \snippet setRowsViaJSON.qml import + + and then it can be assigned directly + + \snippet setRowsViaJSON.qml assignment + + \warning Ensure that the JSON data comes from a trusted source. Since the + model dynamically populates its rows based on the input, malformed or + untrusted JSON can lead to unexpected behavior or performance issues. + + See \l TableModel for an another example where a JSON object is parsed + into and assigned as a JavaScript array. + + \sa TableModelColumn, TreeView */ QQmlTreeModel::QQmlTreeModel(QObject *parent) @@ -122,12 +158,21 @@ void QQmlTreeModel::setRowsPrivate(const QVariantList &rowsAsVariantList) beginResetModel(); + // In case the model is empty and we cannot insert any new rows in the loop below, + // we don't want to emit rowsChanged + const bool wasEmpty = mRows.empty(); + // We don't clear the column or role data, because a TreeModel should not be reused in that way. // Once it has valid data, its columns and roles are fixed. mRows.clear(); - for (const auto &rowAsVariant : rowsAsVariantList) - mRows.push_back(std::make_unique(rowAsVariant)); + for (const auto &rowAsVariant : rowsAsVariantList) { + if (rowAsVariant.canConvert()) + mRows.push_back(std::make_unique(rowAsVariant)); + else + qmlWarning(this) << "Cannot create tree row as the row does not contain " + << "key-value pairs"; + } // Gather metadata the first time rows is set. // If we call setrows on an empty model, mInitialRows will be empty, but mRows is not @@ -135,7 +180,14 @@ void QQmlTreeModel::setRowsPrivate(const QVariantList &rowsAsVariantList) fetchColumnMetadata(); endResetModel(); - emit rowsChanged(); + + // was empty, still empty => no emit + // was empty, now non-empty => emit + // was not empty, now empty => emit + // was not empty, now non-empty => emit (there was a clear in-between) + + if (!wasEmpty || !mRows.empty()) + emit rowsChanged(); } QVariant QQmlTreeModel::dataPrivate(const QModelIndex &index, const QString &roleName) const @@ -212,17 +264,23 @@ void QQmlTreeModel::appendRow(QModelIndex parent, const QVariant &row) qmlWarning(this) << "append: could not find any node at the specified index" << " - the new row will be appended to root"; - beginInsertRows(QModelIndex(), + if (data.canConvert()) { + beginInsertRows(QModelIndex(), static_cast(mRows.size()), static_cast(mRows.size())); - mRows.push_back(std::make_unique(data)); + mRows.push_back(std::make_unique(data)); - // Gather metadata the first time a row is added. - if (mColumnMetadata.isEmpty()) - fetchColumnMetadata(); + // Gather metadata the first time a row is added. + if (mColumnMetadata.isEmpty()) + fetchColumnMetadata(); - endInsertRows(); + endInsertRows(); + } else { + qmlWarning(this) << "Cannot create tree row as the row does not contain " + << "key-value pairs"; + return; + } } emit rowsChanged(); @@ -415,6 +473,7 @@ QModelIndex QQmlTreeModel::index(int row, int column, const QModelIndex &parent) \table \row \li \inlineimage treemodel.svg + {Tree diagram showing nodes A through F with index paths} \li \list \li The root of the tree is special, as it can be referenced by an invalid diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreerow.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreerow.cpp index 811d184e9d..452633f991 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreerow.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/models/qqmltreerow.cpp @@ -20,7 +20,7 @@ QQmlTreeRow::QQmlTreeRow(QQmlTreeRow *parentItem) QQmlTreeRow::QQmlTreeRow(const QVariant &data, QQmlTreeRow *parentItem) : m_parent(parentItem) { - QVariantMap variantMap = data.toJsonObject().toVariantMap(); + QVariantMap variantMap = data.toMap(); unpackVariantMap(variantMap); } diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuitem.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuitem.cpp index 78cd91fd93..6cdf5ec1e0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuitem.cpp @@ -30,6 +30,7 @@ QT_BEGIN_NAMESPACE The MenuItem type provides a QML API for native platform menu items. \image qtlabsplatform-menu.png + {Native menu with Zoom In and Zoom Out items} A menu item consists of an \l icon, \l text, and \l shortcut. diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuseparator.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuseparator.cpp index 407ade3d5d..8c8378b70a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuseparator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/platform/qquicklabsplatformmenuseparator.cpp @@ -18,6 +18,7 @@ QT_BEGIN_NAMESPACE that has the \l {MenuItem::}{separator} property set to \c true by default. \image qtlabsplatform-menubar.png + {Native menubar with Edit menu showing Cut, Copy, and Paste} \labs diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/CMakeLists.txt index 8a7c20b8d4..b7e16bb921 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/CMakeLists.txt @@ -10,6 +10,7 @@ qt_internal_add_qml_module(LabsStyleKit IMPORTS QtQuick.Controls.Basic SOURCES qqstylekit_p.h qqstylekit.cpp + qqstylekitanimation.cpp qqstylekitcontrol_p.h qqstylekitcontrol.cpp qqstylekitcontrols_p.h qqstylekitcontrols.cpp qqstylekitcontrolstate_p.h qqstylekitcontrolstate.cpp diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/ProgressBar.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/ProgressBar.qml index 0d5437919f..330e758ce1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/ProgressBar.qml +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/ProgressBar.qml @@ -37,72 +37,102 @@ T.ProgressBar { palette: control.palette } - contentItem: IndicatorDelegate { - quickControl: control - indicatorStyle: styleReader.indicator - secondProgress: control.visualPosition + StyleKitLayout { + id: progressBarLayout + container: control.contentItem + layoutItems: [ + StyleKitLayoutItem { + id: indicatorItem + item: indicator + margins { + left: styleReader.indicator.leftMargin + right: styleReader.indicator.rightMargin + top: styleReader.indicator.topMargin + bottom: styleReader.indicator.bottomMargin + } + alignment: styleReader.indicator.alignment + fillWidth: styleReader.indicator.implicitWidth === Style.Stretch + fillHeight: styleReader.indicator.implicitHeight === Style.Stretch + } + ] + } - Connections { - target: control - function onIndeterminateChanged() { - if (indeterminate) { - control.contentItem.firstProgress = 0 - control.contentItem.secondProgress = 0 - control.contentItem.indeterminateAnim.restart() - } else { - control.contentItem.indeterminateAnim.stop() - control.contentItem.firstProgress = 0 - control.contentItem.secondProgress = Qt.binding(()=>{ return control.visualPosition }) + contentItem: Item { + implicitWidth: Math.max(indicator.implicitWidth, progressBarLayout.implicitWidth) + implicitHeight: Math.max(indicator.implicitHeight, progressBarLayout.implicitHeight) + + IndicatorDelegate { + id: indicator + quickControl: control + indicatorStyle: styleReader.indicator + secondProgress: control.visualPosition + x: indicatorItem.x + y: indicatorItem.y + width: indicatorItem.width + height: indicatorItem.height + + Connections { + target: control + function onIndeterminateChanged() { + if (indeterminate) { + control.contentItem.firstProgress = 0 + control.contentItem.secondProgress = 0 + control.contentItem.indeterminateAnim.restart() + } else { + control.contentItem.indeterminateAnim.stop() + control.contentItem.firstProgress = 0 + control.contentItem.secondProgress = Qt.binding(()=>{ return control.visualPosition }) + } } } - } - Behavior on firstProgress { - enabled: !control.indeterminate && !control.contentItem.indeterminateAnim.running - NumberAnimation { - duration: 100 - easing.type: Easing.Linear + Behavior on firstProgress { + enabled: !control.indeterminate && !indicator.indeterminateAnim.running + NumberAnimation { + duration: 100 + easing.type: Easing.Linear + } } - } - Behavior on secondProgress { - enabled: !control.indeterminate && !control.contentItem.indeterminateAnim.running - NumberAnimation { - duration: 100 - easing.type: Easing.Linear + Behavior on secondProgress { + enabled: !control.indeterminate && !indicator.indeterminateAnim.running + NumberAnimation { + duration: 100 + easing.type: Easing.Linear + } } - } - property Animation indeterminateAnim: SequentialAnimation { - running: control.indeterminate - loops: Animation.Infinite - NumberAnimation { - target: control.contentItem - property: "secondProgress" - to: 1 - duration: 300 - easing.type: Easing.Linear - } - NumberAnimation { - target: control.contentItem - property: "firstProgress" - to: 0.95 - duration: 600 - easing.type: Easing.OutExpo - } - NumberAnimation { - target: control.contentItem - property: "firstProgress" - to: 0 - duration: 300 - easing.type: Easing.Linear - } - NumberAnimation { - target: control.contentItem - property: "secondProgress" - to: 0.05 - duration: 600 - easing.type: Easing.OutExpo + property Animation indeterminateAnim: SequentialAnimation { + running: control.indeterminate + loops: Animation.Infinite + NumberAnimation { + target: indicator + property: "secondProgress" + to: 1 + duration: 300 + easing.type: Easing.Linear + } + NumberAnimation { + target: indicator + property: "firstProgress" + to: 0.95 + duration: 600 + easing.type: Easing.OutExpo + } + NumberAnimation { + target: indicator + property: "firstProgress" + to: 0 + duration: 300 + easing.type: Easing.Linear + } + NumberAnimation { + target: indicator + property: "secondProgress" + to: 0.05 + duration: 600 + easing.type: Easing.OutExpo + } } } } diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/StyleAnimation.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/StyleAnimation.qml index 4fd23f8954..0f09d2bf90 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/StyleAnimation.qml +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/StyleAnimation.qml @@ -1,18 +1,9 @@ -// Copyright (C) 2025 The Qt Company Ltd. +// Copyright (C) 2026 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 // Qt-Security score:significant reason:default import QtQuick -/*! - \qmltype StyleAnimation - \inqmlmodule Qt.labs.StyleKit - \inherits ParallelAnimation - \brief Animates style property changes during state transitions. - - \labs -*/ - ParallelAnimation { id: root property alias easing: colorAnimation.easing @@ -45,7 +36,6 @@ ParallelAnimation { readonly property string __geometryProps: "$.implicitWidth, $.implicitHeight, " + "$.margins, $.leftMargin, $.rightMargin, $.topMargin, $.bottomMargin, " - + "$.leftPadding, $.rightPadding, $.topPadding, $.bottomPadding, " + "$.minimumWidth, " readonly property string __colorProps: "$.color, $.border.color, $.image.color, $.shadow.color, " @@ -57,7 +47,6 @@ ParallelAnimation { "$.shadow.verticalOffset, $.shadow.horizontalOffset, " + "$.shadow.scale, $.shadow.blur, " - function __animateIndicators(doAnimate, anim, props) { if (!doAnimate) return diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/qtlabsstylekit.qdocconf b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/qtlabsstylekit.qdocconf index d5d7584c0e..14bd11be20 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/qtlabsstylekit.qdocconf +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/qtlabsstylekit.qdocconf @@ -13,10 +13,7 @@ qhp.QtLabsStyleKit.virtualFolder = qtlabsstylekit qhp.QtLabsStyleKit.indexTitle = Qt Labs StyleKit qhp.QtLabsStyleKit.indexRoot = -qhp.QtLabsStyleKit.subprojects = manual qmltypes -qhp.QtLabsStyleKit.subprojects.manual.title = Qt Labs StyleKit -qhp.QtLabsStyleKit.subprojects.manual.indexTitle = Qt Labs StyleKit -qhp.QtLabsStyleKit.subprojects.manual.selectors = group:none +qhp.QtLabsStyleKit.subprojects = qmltypes qhp.QtLabsStyleKit.subprojects.qmltypes.title = QML Types qhp.QtLabsStyleKit.subprojects.qmltypes.indexTitle = Qt Labs StyleKit QML Types diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/ControlsSnippets.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/ControlsSnippets.qml index 0991eccf5b..4af32063d1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/ControlsSnippets.qml +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/ControlsSnippets.qml @@ -175,6 +175,11 @@ ApplicationWindow { color: "white" border.width: 0 } + variations: StyleVariation { + toolBar.background.color: "cornflowerblue" + toolButton.background.color: "skyblue" + toolButton.background.border.color: "aliceblue" + } } //! [page] @@ -273,15 +278,9 @@ ApplicationWindow { scrollBar { padding: 0 background { - // For scroll bars, we typically don't want a background, only a - // draggable indicator and a groove. - visible: false - // Note: even if the background is hidden, its implicit size is still used to - // determine the overall size of the scroll bar. So we set implicitHeight equal to - // the height of the indicator, to make it appear on the side of, and not on top - // of, the content area in a ScrollView. For a horizontal scroll bar, on the other - // hand, the width is automatically set by the view to match its own width. + // Set the groove size and color implicitHeight: 10 + color: "white" } indicator { radius: 255 @@ -289,6 +288,7 @@ ApplicationWindow { // view to be the relative size of the view's content that is currently visible. implicitHeight: 10 color: "#c0c0c0" + // The indicator is so simple that it doesn't need a foreground foreground.visible: false } vertical { @@ -303,8 +303,8 @@ ApplicationWindow { //! [scrollIndicator] scrollIndicator { - // For scroll indicators, we typically don't want a background, only a - // sliding indicator + // A scroll indicator typically has no groove, just a sliding + // indicator. So hide the background, since it represents the groove. background.visible: false indicator { // For horizontal indicators, we only set the height. The width is set by the @@ -633,6 +633,14 @@ ApplicationWindow { width: 200 text: "TextField" } + + Page { + header: ToolBar { + ToolButton { + text: "<" + } + } + } } } } diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/CustomControlSnippets.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/CustomControlSnippets.qml new file mode 100644 index 0000000000..b6c02119e0 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/CustomControlSnippets.qml @@ -0,0 +1,76 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +ApplicationWindow { + id: app + width: 1024 + height: 800 + visible: true + + StyleKit.style: + //! [custom control style] + // MyStyle.qml + + Style { + id: style + readonly property int myControlType: 0 + CustomControl { + controlType: style.myControlType + background { + implicitWidth: 120 + implicitHeight: 30 + radius: 0 + } + hovered.background.color: "lightslategray" + pressed.background.color: "skyblue" + } + } + //! [custom control style] + + //! [custom control] + // Main.qml + + component MyControl : Rectangle { + StyleReader { + id: styleReader + controlType: StyleKit.style.myControlType + hovered: hoverHandler.hovered + pressed: tapHandler.pressed + palette: app.palette + } + + HoverHandler { id: hoverHandler } + TapHandler { id: tapHandler } + + implicitWidth: styleReader.background.implicitWidth + implicitHeight: styleReader.background.implicitHeight + color: styleReader.background.color + radius: styleReader.background.radius + + Text { + font: styleReader.font + anchors.centerIn: parent + text: "ok" + } + } + //! [custom control] + + // The rest of the file is not a part of the docs. It just implements a small + // UI to allow testing the style from the command line using the 'qml' app. + + ScrollView { + anchors.fill: parent + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + MyControl { + } + + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleAnimationSnippets.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleAnimationSnippets.qml new file mode 100644 index 0000000000..fb2a66e3c1 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleAnimationSnippets.qml @@ -0,0 +1,106 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +ApplicationWindow { + id: app + width: 1024 + height: 800 + visible: true + + StyleKit.style: Style { + + //! [transition] + comboBox { + background.color: "lightgray" + hovered.background.color: "plum" + + indicator.color: "white" + hovered.indicator.color: "pink" + hovered.indicator.border.width: 4 + + transition: Transition { + StyleAnimation { + animateBackgroundColors: true + animateIndicatorColors: true + animateIndicatorBorder: true + easing.type: Easing.OutQuad + duration: 500 + } + } + } + //! [transition] + + //! [custom transition] + checkBox { + background.color: "lightgray" + hovered.background.color: "plum" + + indicator.color: "white" + hovered.indicator.color: "pink" + hovered.indicator.border.width: 4 + + transition: Transition { + ColorAnimation { + properties: "background.color, background.border.color, background.image.color, background.shadow.color" + + ", indicator.color, indicator.border.color, indicator.image.color, indicator.shadow.color" + easing.type: Easing.OutQuad + duration: 500 + } + NumberAnimation { + properties: "indicator.border.width" + easing.type: Easing.OutQuad + duration: 500 + } + } + } + //! [custom transition] + + //! [mixed transition] + slider { + handle.color: "white" + hovered.handle.color: "seagreen" + hovered.handle.border.width: 8 + + transition: Transition { + StyleAnimation { + animateHandleColors: true + easing.type: Easing.OutQuad + duration: 500 + } + NumberAnimation { + properties: "handle.border.width" + easing.type: Easing.OutBounce + duration: 1000 + } + } + } + //! [mixed transition] + } + + // The rest of the file is not a part of the docs. It just implements a small + // UI to allow testing the style from the command line using the 'qml' app. + + ScrollView { + anchors.fill: parent + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + ComboBox { + model: ["apple", "banana", "orange"] + } + + CheckBox { + text: "CheckBox" + } + + Slider { + width: 200 + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleReaderSnippets.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleReaderSnippets.qml new file mode 100644 index 0000000000..989554c6db --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/snippets/StyleReaderSnippets.qml @@ -0,0 +1,60 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +ApplicationWindow { + id: app + width: 1024 + height: 800 + visible: true + + StyleKit.style: Style { + button.pressed.background.color: "magenta" + button.hovered.text.bold: true + } + //! [custom control] + // Main.qml + + component CustomButton : Rectangle { + StyleReader { + id: styleReader + controlType: StyleReader.Button + hovered: hoverHandler.hovered + pressed: tapHandler.pressed + palette: app.palette + } + + implicitWidth: styleReader.background.implicitWidth + implicitHeight: styleReader.background.implicitHeight + color: styleReader.background.color + radius: styleReader.background.radius + + HoverHandler { id: hoverHandler } + TapHandler { id: tapHandler } + + Text { + font: styleReader.font + anchors.centerIn: parent + text: "ok" + } + } + //! [custom control] + + // The rest of the file is not a part of the docs. It just implements a small + // UI to allow testing the style from the command line using the 'qml' app. + + ScrollView { + anchors.fill: parent + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + CustomButton { + } + + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-example.qdoc b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-example.qdoc index 90310097a7..af4fa7317d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-example.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-example.qdoc @@ -9,6 +9,7 @@ \ingroup qtquickcontrols-examples \image qtlabsstylekit-example.webp + {StyleKit demo showing buttons, checkboxes, sliders, and settings} This example shows how to style applications with \l {Qt Labs StyleKit}{StyleKit}. It includes several styles that each demonstrate different aspects of styling: @@ -38,4 +39,7 @@ \sa {Qt Labs StyleKit}, Style, Theme \include examples-run.qdocinc + + \note This example requires Qt Shader Tools, an optional library available + through the Qt Maintenance Tool. */ diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-fallbackstyle.qdoc b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-fallbackstyle.qdoc index a0c7c8ef6f..7cf6a86e36 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-fallbackstyle.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-fallbackstyle.qdoc @@ -18,5 +18,5 @@ \snippet ReferenceFallbackStyle.qml 1 - \sa {Style::fallbackStyle}{Style.fallbackStyle}, {qtlabsstylekit-property-resolution.html}{Property Resolution} + \sa {Style::fallbackStyle}{Style.fallbackStyle} */ diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-qmltypes.qdoc b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-qmltypes.qdoc index e88aa8a382..523484b2f1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-qmltypes.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/doc/src/qtlabsstylekit-qmltypes.qdoc @@ -3,7 +3,7 @@ /*! \qmlmodule Qt.labs.StyleKit - \techpreview + \preliminary \title Qt Labs StyleKit QML Types \ingroup qmlmodules \brief Provides QML types for declarative styling of Qt Quick Controls. diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/FallbackStyle.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/FallbackStyle.qml index af0b033d39..f61397bd2d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/FallbackStyle.qml +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/FallbackStyle.qml @@ -113,7 +113,7 @@ BaseStyle { } comboBox { - text.alignment: Qt.AlignVCenter + text.alignment: Qt.AlignVCenter | Qt.AlignLeft background.implicitWidth: 150 indicator { color: __transparent @@ -204,7 +204,6 @@ BaseStyle { padding: 0 background { implicitHeight: 10 - visible: false } indicator { implicitHeight: 10 diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/Shadow.qml b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/Shadow.qml index 7b12d690a5..90e1f7292e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/Shadow.qml +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/Shadow.qml @@ -22,7 +22,7 @@ RectangularShadow { topRightRadius: delegateStyle.topRightRadius bottomLeftRadius: delegateStyle.bottomLeftRadius bottomRightRadius: delegateStyle.bottomRightRadius - visible: delegateStyle.shadow.visible + visible: delegateStyle.visible && delegateStyle.shadow.visible offset: Qt.vector2d( delegateStyle.shadow.horizontalOffset, delegateStyle.shadow.verticalOffset) diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitdelegatecontainer.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitdelegatecontainer.cpp index 167ee79aea..9de6145435 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitdelegatecontainer.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitdelegatecontainer.cpp @@ -73,8 +73,24 @@ bool QQStyleKitDelegateContainer::usingDefaultDelegate() const void QQStyleKitDelegateContainer::updateImplicitSize() { - setImplicitWidth(m_delegateInstance ? m_delegateInstance->implicitWidth() : 0); - setImplicitHeight(m_delegateInstance ? m_delegateInstance->implicitHeight() : 0); + qreal implicitWidth = 0; + qreal implicitHeight = 0; + + if (m_delegateInstance) { + /* The delegate instance is expected to bind its implicit size to the + * value from the style, but may override it if needed. */ + implicitWidth = m_delegateInstance->implicitWidth(); + implicitHeight = m_delegateInstance->implicitHeight(); + } else if (m_delegateProperties) { + /* The delegate instance may not exist yet (e.g. because it is hidden), + * but the container should still report the style's implicit size so + * that it reserves the correct space in a layout. */ + implicitWidth = qMax(m_delegateProperties->minimumWidth(), m_delegateProperties->implicitWidth()); + implicitHeight = m_delegateProperties->implicitHeight(); + } + + setImplicitWidth(implicitWidth); + setImplicitHeight(implicitHeight); } void QQStyleKitDelegateContainer::maybeCreateDelegate() @@ -244,6 +260,8 @@ void QQStyleKitDelegateContainer::componentComplete() maybeCreateShadow(); } }); + + updateImplicitSize(); } QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitlayout.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitlayout.cpp index ef7b8598cb..734e161b05 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitlayout.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/impl/qqstylekitlayout.cpp @@ -58,15 +58,12 @@ static qreal vAlignY(QQStyleKitLayoutItem *li, qreal containerY, qreal container const auto itemHeight = height(li, containerHeight); const auto vAlign = li->alignment() & Qt::AlignVertical_Mask; const auto margins = li->margins(); - switch (vAlign) { - case Qt::AlignTop: + if (vAlign & Qt::AlignTop) return containerY + margins.top(); - case Qt::AlignBottom: + if (vAlign & Qt::AlignBottom) return containerY + containerHeight - itemHeight - margins.bottom(); - default: // AlignVCenter - return containerY + margins.top() - + (containerHeight - margins.top() - margins.bottom() - itemHeight) / 2.0; - } + return containerY + margins.top() + + (containerHeight - margins.top() - margins.bottom() - itemHeight) / 2.0; } QQStyleKitLayoutItem::QQStyleKitLayoutItem(QObject *parent) diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekit.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekit.cpp index b3185e8298..25ea507a16 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekit.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekit.cpp @@ -104,7 +104,7 @@ QQStyleKitAttached::QQStyleKitAttached(QObject *parent) : QObject(parent) { connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this](){ - if (m_style) + if (m_style && QString::compare(m_style->themeName(), "System"_L1, Qt::CaseInsensitive) == 0) m_style->recreateTheme(); }); } diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitanimation.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitanimation.cpp new file mode 100644 index 0000000000..299dc16919 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitanimation.cpp @@ -0,0 +1,229 @@ +// Copyright (C) 2026 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 + +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmltype StyleAnimation + \inqmlmodule Qt.labs.StyleKit + \inherits ParallelAnimation + \brief A convenience type for animating style properties. + + StyleAnimation is an \l [QtQuick]{Animation} that animates style properties + when a control changes \l {ControlStateStyle}{state}. For example, setting + \l animateBackgroundColors to \c true will animate all color properties of + the \l {ControlStyle::}{background}, such as \l {DelegateStyle::color}{background.color}, + \l {BorderStyle::color}{background.border.color}, \l {ImageStyle::color}{background.image.color}, + and \l {ShadowStyle::color}{background.shadow.color}. It is used inside a + \l {ControlStyle::}{transition}: + + \snippet StyleAnimationSnippets.qml transition + + StyleAnimation is just a convenience \l [QtQuick]{Animation} that handles listing the + affected style properties automatically. The snippet above could also be written as: + + \snippet StyleAnimationSnippets.qml custom transition + + A StyleAnimation can also be used together with other animations: + + \snippet StyleAnimationSnippets.qml mixed transition + + \labs + + \sa {ControlStyleProperties::transition}{ControlStyle.transition}, ControlStyle +*/ + +/*! + \qmlproperty EasingCurve StyleAnimation::easing + + The easing curve applied to the animation. + + \sa duration, PropertyAnimation +*/ + +/*! + \qmlproperty int StyleAnimation::duration + + The duration in milliseconds of the animation. + + \sa easing, PropertyAnimation +*/ + +/*! + \qmlproperty bool StyleAnimation::animateColors + + When \c true, animates all color properties across all delegates. This is a + shorthand for enabling \l animateBackgroundColors, \l animateHandleColors, + and \l animateIndicatorColors at once. + + Note that \l ColorAnimation also animates all color properties that + change during a transition when \l {PropertyAnimation::}{property} and + \l {PropertyAnimation::}{properties} are both left unset. This makes it a + an alternative to StyleAnimation when only colors need to be animated. + + \sa animateBackgroundColors, animateHandleColors, animateIndicatorColors +*/ + +/*! + \qmlproperty bool StyleAnimation::animateControlGeometry + + When \c true, animates the control's geometry properties, such as + \l {ControlStyleProperties::spacing}{spacing} and + \l {ControlStyleProperties::padding}{padding}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundColors + + When \c true, animates the color properties of the + \l {ControlStyleProperties::}{background} delegate, + including \l {BorderStyle::color}{border} and \l {ShadowStyle::color}{shadow} colors. + + \sa animateColors, animateBackgroundGeometry +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundGeometry + + When \c true, animates the geometry properties of the + \l {ControlStyleProperties::}{background} delegate, + such as \l {DelegateStyle::implicitWidth}{implicit size} + and \l {DelegateStyle::}{margins}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundRadii + + When \c true, animates the corner \l {DelegateStyle::radius}{radii} of the + \l {ControlStyleProperties::}{background} delegate. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundBorder + + When \c true, animates the \l {BorderStyle::width}{border width} of the + \l {ControlStyleProperties::}{background} delegate. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundShadow + + When \c true, animates the shadow properties of the + \l {ControlStyleProperties::}{background} delegate, + such as \l {ShadowStyle::verticalOffset}{vertical offset}, + \l {ShadowStyle::horizontalOffset}{horizontal offset}, + \l {ShadowStyle::scale}{scale}, and \l {ShadowStyle::blur}{blur}. + The shadow \l {ShadowStyle::color}{color} is animated by \l animateHandleColors. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateBackgroundScaleAndRotation + + When \c true, animates the and rotation of the + \l {ControlStyleProperties::}{background} delegate. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleColors + + When \c true, animates the color properties of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}, + including \l {BorderStyle::color}{border} and \l {ShadowStyle::color}{shadow} colors. + + \sa animateColors, animateHandleGeometry +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleGeometry + + When \c true, animates the geometry properties of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}, + such as \l {DelegateStyle::implicitWidth}{implicit size} and + \l {DelegateStyle::}{margins}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleRadii + + When \c true, animates the corner \l {DelegateStyle::radius}{radii} of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleBorder + + When \c true, animates the \l {BorderStyle::width}{border width} of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleShadow + + When \c true, animates the shadow properties of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}, + such as \l {ShadowStyle::verticalOffset}{vertical offset}, + \l {ShadowStyle::horizontalOffset}{horizontal offset}, + \l {ShadowStyle::scale}{scale}, and \l {ShadowStyle::blur}{blur}. + The shadow \l {ShadowStyle::color}{color} is animated by \l animateHandleColors. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateHandleScaleAndRotation + + When \c true, animates the \l {DelegateStyle::}{scale} and \l {DelegateStyle::}{rotation} of the + \l {ControlStyleProperties::}{handle} delegate and its \l {HandleStyle}{sub-handles}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorColors + + When \c true, animates the color properties of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}, + including \l {BorderStyle::color}{border} and \l {ShadowStyle::color}{shadow} colors. + + \sa animateColors, animateIndicatorGeometry +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorGeometry + + When \c true, animates the geometry properties of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}, + such as \l {DelegateStyle::implicitWidth}{implicit size} and \l {DelegateStyle::}{margins}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorRadii + + When \c true, animates the corner \l {DelegateStyle::radius}{radii} of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorBorder + + When \c true, animates the \l {BorderStyle::width}{border width} of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorShadow + + When \c true, animates the shadow properties of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}, + such as \l {ShadowStyle::verticalOffset}{vertical offset}, + \l {ShadowStyle::horizontalOffset}{horizontal offset}, + \l {ShadowStyle::scale}{scale}, and \l {ShadowStyle::blur}{blur}. + The shadow \l {ShadowStyle::color}{color} is animated by \l animateHandleColors. +*/ + +/*! + \qmlproperty bool StyleAnimation::animateIndicatorScaleAndRotation + + When \c true, animates the scale and rotation of the + \l {ControlStyleProperties::}{indicator} delegate and its \l {IndicatorStyle}{sub-indicators}. +*/ + +QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties.cpp index d9eddb1424..fb5095abbd 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties.cpp @@ -9,131 +9,28 @@ QT_BEGIN_NAMESPACE -// ************* ControlStateStyle **************** +// ************* ControlStyleProperties **************** /*! - \qmltype ControlStateStyle + \qmltype ControlStyleProperties \inqmlmodule Qt.labs.StyleKit - \brief Defines the style for a control in a given state. + \brief Defines the stylable properties for a control. - A ControlStateStyle contains the properties that can be styled for each - state a control can be in. This includes the visual building blocks - \l background, \l indicator, \l handle, and \l text, along with - layout properties such as \l padding and \l spacing, and more. + ControlStyleProperties defines the stylable properties for a + control: the visual building blocks \l background, \l indicator, \l handle, + and \l text, as well as layout properties like \l padding, \l spacing, + and \l transition. - A \l ControlStyle inherits ControlStateStyle, since it represents the - \e normal state — that is, properties set directly on a - ControlStyle describe how the control looks when no other state - is active. State-specific overrides are set through nested states - such as \l pressed, \l hovered, and \l checked, and can be - animated by a \l transition. - - Nested states are not mutually exclusive. Multiple states can be active at - the same time — for example, a button can be both hovered and - pressed simultaneously. When several states are active, all matching - state overrides are applied. If the same property is set in multiple - active states, conflicts are resolved using the following priority - order: \l pressed, \l hovered, \l highlighted, \l focused, - \l checked, \l vertical. So for example \c {pressed.background.color} - wins over \c {checked.background.color} if the control is both - \l pressed and \l checked. - - The \l disabled state is an exception: a disabled control cannot - be interacted with, so the \l pressed, \l hovered, \l highlighted, - and \l focused states will not apply. However, \l disabled can - still be combined with states like \l checked and \l vertical. - - The more deeply nested a state is, the more qualified it is. - For example, \c {hovered.pressed.background.color} takes precedence over - \c {hovered.background.color} when both \l hovered and \l pressed are - active. The nesting order does not matter: - \c {hovered.pressed} and \c {pressed.hovered} are equivalent. - However, if both forms are used at the same time, which one wins is - undefined. - - Deeper nesting of states can also be used to resolve conflicts. If the same property - is set in both \l hovered and \l checked, the priority order means - the \l hovered value wins. If you would rather have the checked value win, or - use an altogether different value in that situation, you can override - the property in \c {hovered.checked}, which then takes precedence over both. - - The following snippet shows how to style a button differently - depending on its state: - - \snippet ControlStateStyle_states.qml States + The style properties are accessed through inherited types such as + \l ControlStyle and \l StyleReader. \labs - \sa ControlStyle, DelegateStyle, - {qtlabsstylekit-fallbackstyle.html}{FallbackStyle Reference} + \sa ControlStyle, StyleReader, ControlStateStyle, DelegateStyle */ /*! - \qmlproperty ControlStateStyle ControlStateStyle::pressed - - Style overrides applied when the control is pressed (e.g. the - user is holding down a mouse button on it). - - \sa hovered, highlighted, focused, checked, vertical, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::hovered - - Style overrides applied when the mouse cursor is over the control. - - \sa pressed, highlighted, focused, checked, vertical, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::highlighted - - Style overrides applied when the control is highlighted. A control - is typically highlighted in order to draw the user's attention towards it. - - \sa pressed, hovered, focused, checked, vertical, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::focused - - Style overrides applied when the control has input focus. - - \sa pressed, hovered, highlighted, checked, vertical, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::checked - - Style overrides applied when the control is in a checked or - toggled-on state (e.g. a checked \l CheckBox or a toggled - \l Button). - - \sa pressed, hovered, highlighted, focused, vertical, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::vertical - - Style overrides applied when the control has a vertical - orientation (e.g. a vertical \l Slider or \l ScrollBar). - - \sa pressed, hovered, highlighted, focused, checked, disabled -*/ - -/*! - \qmlproperty ControlStateStyle ControlStateStyle::disabled - - Style overrides applied when the control is disabled. - - A disabled control cannot be interacted with, so \l pressed, - \l hovered, \l highlighted, and \l focused will not be applied - at the same time as disabled. - - \sa pressed, hovered, highlighted, focused, checked, vertical -*/ -/*! - \qmlproperty DelegateStyle ControlStateStyle::background + \qmlproperty DelegateStyle ControlStyleProperties::background Grouped property for styling the background of a control. @@ -153,7 +50,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty real ControlStateStyle::bottomPadding + \qmlproperty real ControlStyleProperties::bottomPadding The \l [QtQuickControls]{Control Layout}{bottom padding} of the control. If not set, falls back to \l padding. @@ -162,7 +59,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty HandleStyle ControlStateStyle::handle + \qmlproperty HandleStyle ControlStyleProperties::handle Grouped property for styling the handle of a control. @@ -175,7 +72,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty IndicatorStyle ControlStateStyle::indicator + \qmlproperty IndicatorStyle ControlStyleProperties::indicator Grouped property for styling the indicator of a control. For a \l {AbstractStylableControls::}{checkBox}, the indicator is the @@ -187,7 +84,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty real ControlStateStyle::leftPadding + \qmlproperty real ControlStyleProperties::leftPadding The \l [QtQuickControls]{Control Layout}{left padding} of the control. If not set, falls back to \l padding. @@ -196,7 +93,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty real ControlStateStyle::padding + \qmlproperty real ControlStyleProperties::padding The uniform spacing between the \l [QtQuickControls]{Control Layout}{control's content area and the @@ -208,7 +105,7 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty real ControlStateStyle::rightPadding + \qmlproperty real ControlStyleProperties::rightPadding The \l [QtQuickControls]{Control Layout}{right padding} of the control. If not set, falls back to \l padding. @@ -217,20 +114,20 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty real ControlStateStyle::spacing + \qmlproperty real ControlStyleProperties::spacing The spacing between visual elements inside the control, for example between an indicator and a label. */ /*! - \qmlproperty TextStyle ControlStateStyle::text + \qmlproperty TextStyle ControlStyleProperties::text Grouped property for styling the text label of a control. */ /*! - \qmlproperty real ControlStateStyle::topPadding + \qmlproperty real ControlStyleProperties::topPadding The \l [QtQuickControls]{Control Layout}{top padding} of the control. If not set, falls back to \l padding. @@ -239,10 +136,11 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlproperty Transition ControlStateStyle::transition + \qmlproperty Transition ControlStyleProperties::transition A \l [QtQuick]{Transition} used to animate style properties when the - control enters a new state, such as \l hovered or \l pressed. If set + control enters a new state, such as \l {ControlStateStyle::}{hovered} + or \l {ControlStateStyle::}{pressed}. If set to \c null (the default), property changes are applied immediately without animation. @@ -269,8 +167,8 @@ QT_BEGIN_NAMESPACE \brief Defines the visual appearance of an element within a control. DelegateStyle describes the visual appearance of a visual element - within a \l ControlStyle, such as its \l {ControlStateStyle::}{background}, - \l {ControlStateStyle::}{indicator}, or a sub-element like + within a \l ControlStyle, such as its \l {ControlStyleProperties::}{background}, + \l {ControlStyleProperties::}{indicator}, or a sub-element like \l {IndicatorStyle::foreground}{indicator.foreground}. It provides properties for controlling \l {DelegateStyle::implicitWidth}{size}, @@ -376,7 +274,8 @@ QT_BEGIN_NAMESPACE \l {Qt Quick Controls}{Qt Quick Control}. The default value is \c null, in which case \l StyledItem is used - for rendering instead. + for rendering instead. If you want to hide the delegate altogether + (and prevent if from being created), set \l visible to \c false. The delegate needs to define the following required properties: \qml @@ -400,7 +299,7 @@ QT_BEGIN_NAMESPACE \note If a DelegateStyle has a \l {shadow}{drop shadow} defined, it will be drawn separately by a \l {ShadowStyle::delegate}{shadow delegate.} - \sa data, StyledItem + \sa data, visible, StyledItem */ /*! @@ -550,6 +449,9 @@ QT_BEGIN_NAMESPACE should not draw a background, such as \l [QtQuickControls]{CheckBox}, \l [QtQuickControls]{RadioButton}, and \l [QtQuickControls]{Slider}. + When \c visible is \c false, the \l delegate will not be created. If you need + a hidden delegate to be created, consider setting \l opacity to \c 0 instead. + \sa opacity */ @@ -866,7 +768,7 @@ QT_BEGIN_NAMESPACE \l {TextStyle::}{italic}, and \l {TextStyle::pointSize}{font size}. - It is available as the \l {ControlStateStyle::text}{text} + It is available as the \l {ControlStyleProperties::text}{text} grouped property on every \l ControlStateStyle. \labs diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties_p.h index 427e74ac50..9d0c438ed4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolproperties_p.h @@ -148,7 +148,7 @@ signals: // ************* QQStyleKitBorderProperties **************** -class QQStyleKitBorderProperties : public QQStyleKitPropertyGroup +class Q_LABSSTYLEKIT_EXPORT QQStyleKitBorderProperties : public QQStyleKitPropertyGroup { Q_OBJECT Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged FINAL) @@ -546,7 +546,7 @@ signals: * only be read), and if so, offer a storage for storing those values. That storage is typically * a map that stores _only_ the properties that are written to, and nothing else. */ -class QQStyleKitControlProperties : public QQStyleKitPropertyGroup +class Q_LABSSTYLEKIT_EXPORT QQStyleKitControlProperties : public QQStyleKitPropertyGroup { Q_OBJECT Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged FINAL) @@ -560,8 +560,9 @@ class QQStyleKitControlProperties : public QQStyleKitPropertyGroup Q_PROPERTY(QQStyleKitIndicatorWithSubTypes *indicator READ indicator NOTIFY indicatorChanged FINAL) Q_PROPERTY(QQStyleKitTextProperties *text READ text NOTIFY textChanged FINAL) Q_PROPERTY(QQuickTransition *transition READ transition WRITE setTransition NOTIFY transitionChanged FINAL) - // This type is merged with ControlState, from a QML API point of view - QML_ANONYMOUS + + QML_UNCREATABLE("This component can only be instantiated by StyleKit") + QML_NAMED_ELEMENT(ControlStyleProperties) public: QQStyleKitControlProperties(QQSK::PropertyGroup group, QObject *parent = nullptr); diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols.cpp index 955ff58616..5f8e2a886e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols.cpp @@ -3,7 +3,6 @@ #include "qqstylekitcontrols_p.h" #include "qqstylekitcontrol_p.h" -#include "qqstylekitvariation_p.h" #include "qqstylekitcustomcontrol_p.h" QT_BEGIN_NAMESPACE @@ -63,7 +62,7 @@ QT_BEGIN_NAMESPACE \snippet ControlsSnippets.qml abstractButton - \sa {qtlabsstylekit-controls.html}{Control Types} + \sa {Qt Labs StyleKit}{Control Types} */ /*! @@ -174,6 +173,13 @@ QT_BEGIN_NAMESPACE Unset properties fall back to \l pane. + Note that the \l [QtQuickControls]{Page::header}{header} and + \l [QtQuickControls]{Page::footer}{footer} of a \l [QtQuickControls]{Page} are + typically set by the application to a \l [QtQuickControls]{ToolBar} or + \l [QtQuickControls]{TabBar}, and those controls are styled separately. + To give a \l [QtQuickControls]{ToolBar} alternative styling when used inside + a \l [QtQuickControls]{Page}, use a \l StyleVariation: + \snippet ControlsSnippets.qml page */ @@ -229,8 +235,8 @@ QT_BEGIN_NAMESPACE \qmlproperty ControlStyle AbstractStylableControls::scrollBar Grouped property for styling \l [QtQuickControls]{ScrollBar}. - For a scroll bar, the groove is styled through the indicator, while the handle is - styled through the indicator's foreground. + The groove is styled through \l {ControlStyleProperties::}{background}, and the + handle through \l {ControlStyleProperties::}{indicator}. Unset properties fall back to \l control. @@ -241,6 +247,8 @@ QT_BEGIN_NAMESPACE \qmlproperty ControlStyle AbstractStylableControls::scrollIndicator Grouped property for styling \l [QtQuickControls]{ScrollIndicator}. + The groove is styled through \l {ControlStyleProperties::}{background}, and the + handle through \l {ControlStyleProperties::}{indicator}. Unset properties fall back to \l control. @@ -407,17 +415,6 @@ QQStyleKitControl* QQStyleKitControls::getControl(QQStyleKitExtendableControlTyp return m_controls[controlType]; } - -QList QQStyleKitControls::variations() const -{ - QList list; - for (auto *obj : children()) { - if (auto *variation = qobject_cast(obj)) - list.append(variation); - } - return list; -} - #define IMPLEMENT_ACCESSORS(NAME, TYPE) \ QQStyleKitControl *QQStyleKitControls::NAME() const \ { \ diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols_p.h index 78c34b0d60..60d23ee6dd 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrols_p.h @@ -109,8 +109,6 @@ public: Q_INVOKABLE QQStyleKitControl *getControl(QQStyleKitExtendableControlType controlType) const; - QList variations() const; - QQmlListProperty data(); const QList children() const; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolstate.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolstate.cpp index c0275a26b8..fad2e509d8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolstate.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcontrolstate.cpp @@ -6,6 +6,129 @@ QT_BEGIN_NAMESPACE +// ************* ControlStateStyle **************** + +/*! + \qmltype ControlStateStyle + \inqmlmodule Qt.labs.StyleKit + \inherits ControlStyleProperties + \brief Describes the style of a control in a given state. + + ControlStateStyle describes the style of a control in a particular state. + A \l ControlStyle inherits ControlStateStyle, since it represents the + \e normal state — properties set directly on a \l ControlStyle describe + how the control looks when no other state is active. State-specific + overrides are then set through nested states such as \l pressed, + \l hovered, and \l checked. + + Nested states are not mutually exclusive. Multiple states can be active at + the same time — for example, a button can be both hovered and + pressed simultaneously. When several states are active, all matching + state overrides are applied. If the same property is set in multiple + active states, conflicts are resolved using the following priority + order: \l pressed, \l hovered, \l highlighted, \l focused, + \l checked, \l vertical. So for example \c {pressed.background.color} + wins over \c {checked.background.color} if the control is both + \l pressed and \l checked. + + The \l disabled state is an exception: a disabled control cannot + be interacted with, so the \l pressed, \l hovered, \l highlighted, + and \l focused states will not apply. However, \l disabled can + still be combined with states like \l checked and \l vertical. + + The more deeply nested a state is, the more qualified it is. + For example, \c {hovered.pressed.background.color} takes precedence over + \c {hovered.background.color} when both \l hovered and \l pressed are + active. The nesting order does not matter: + \c {hovered.pressed} and \c {pressed.hovered} are equivalent. + However, if both forms are used at the same time, which one wins is + undefined. + + Deeper nesting of states can also be used to resolve conflicts. If the same property + is set in both \l hovered and \l checked, the priority order means + the \l hovered value wins. If you would rather have the checked value win, or + use an altogether different value in that situation, you can override + the property in \c {hovered.checked}, which then takes precedence over both. + + The following snippet shows how to style a button differently + depending on its state: + + \snippet ControlStateStyle_states.qml States + + \labs + + \sa ControlStyle, DelegateStyle, + {qtlabsstylekit-fallbackstyle.html}{FallbackStyle Reference} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::pressed + + Style overrides applied when the control is \l {StyleReader::}{pressed}. + + \sa hovered, highlighted, focused, checked, vertical, disabled, + {StyleReader::pressed}{StyleReader.pressed} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::hovered + + Style overrides applied when the control is \l {StyleReader::}{hovered}. + + \sa pressed, highlighted, focused, checked, vertical, disabled, + {StyleReader::hovered}{StyleReader.hovered} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::highlighted + + Style overrides applied when the control is \l {StyleReader::}{highlighted}. + + \sa pressed, hovered, focused, checked, vertical, disabled, + {StyleReader::highlighted}{StyleReader.highlighted} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::focused + + Style overrides applied when the control is \l {StyleReader::}{focused}. + + \sa pressed, hovered, highlighted, checked, vertical, disabled, + {StyleReader::focused}{StyleReader.focused} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::checked + + Style overrides applied when the control is \l {StyleReader::}{checked}. + + \sa pressed, hovered, highlighted, focused, vertical, disabled, + {StyleReader::checked}{StyleReader.checked} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::vertical + + Style overrides applied when the control is \l {StyleReader::}{vertical} + (for example, a vertical \l Slider or \l ScrollBar). + + \sa pressed, hovered, highlighted, focused, checked, disabled, + {StyleReader::vertical}{StyleReader.vertical} +*/ + +/*! + \qmlproperty ControlStateStyle ControlStateStyle::disabled + + Style overrides applied when the control is \l {StyleReader::}{disabled}. + + A disabled control cannot be interacted with, so \l pressed, + \l hovered, \l highlighted, and \l focused will not be applied + at the same time as disabled. + + \sa pressed, hovered, highlighted, focused, checked, vertical, + {StyleReader::enabled}{StyleReader.enabled} +*/ + QQStyleKitControlState::QQStyleKitControlState(QObject *parent) : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent) { diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcustomcontrol.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcustomcontrol.cpp index acacd35e6c..c0e710a6c0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcustomcontrol.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitcustomcontrol.cpp @@ -9,13 +9,52 @@ QT_BEGIN_NAMESPACE \qmltype CustomControl \inqmlmodule Qt.labs.StyleKit \inherits ControlStyle - \brief Defines styling for a custom control type. + \brief Defines styling for a custom (non-built-in) control. + + CustomControl allows you to define and style controls that are not part of + \l {Qt Quick Controls}. This is convenient if you have additional controls + or components that should be styled according to the active \l Style and \l Theme. + + Like the built-in control types (such as + \l {AbstractStylableControls::}{abstractButton}, + \l {AbstractStylableControls::}{pane}, and + \l {AbstractStylableControls::}{slider}), CustomControl inherits + \l ControlStyle. Unlike built-in types, which are implicitly connected to + their control type, a CustomControl requires \l controlType to be set + explicitly. Apart from that, they work exactly the same way. + + A \l Style or \l Theme can define as many custom controls as needed, and a + CustomControl in a Theme can have the same \l controlType as one in the \l Style. + That is no different from, for example, a \l {AbstractStylableControls::}{slider} + being styled by both the \l Style and the \l Theme. The fallback logic is the same. + + Any style properties not set on a CustomControl fall back to those set + on a \l {AbstractStylableControls::}{control}. + + The following snippet shows how to define styling for a custom control: + + \snippet CustomControlSnippets.qml custom control style + + And the following snippet shows an example on how to implement a custom + control that uses it: + + \snippet CustomControlSnippets.qml custom control \labs + + \sa StyleReader, ControlStyle, Style */ /*! \qmlproperty int CustomControl::controlType + + A unique integer identifying this custom control type. Set the same value + on the \l {StyleReader::controlType}{StyleReader.controlType} in your + custom control implementation to connect it to this style definition. + + Custom control types must be in the range \c 0 to \c 100000. + + \sa {StyleReader::controlType}{StyleReader.controlType} */ using namespace Qt::StringLiterals; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdebug.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdebug.cpp index 5cf9177706..85806cd66d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdebug.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdebug.cpp @@ -6,6 +6,7 @@ #include "qqstylekitcontrols_p.h" #include "qqstylekitstyle_p.h" #include "qqstylekittheme_p.h" +#include "qqstylekitvariation_p.h" QT_BEGIN_NAMESPACE @@ -27,17 +28,18 @@ QT_BEGIN_NAMESPACE To start tracing, assign the \l{The id Attribute}{id} of a \l Control to the \l control property: \code - StyleKit.debug.control = myButton + StyleKit.debug.control: myButton \endcode Each resolved property is printed as a single line showing where the value came from and what it resolved to: \code - [read] StyleReader[Normal].button.background.color -> button[Normal] = #ff0000 + [read] StyleReader[Hovered].button.background.color -> MyStyle.Theme(Dark).button[Hovered] = #ff0000 \endcode - Use \l filter to limit the output to properties of interest. + Tracing the property reads of a StyleReader can produce a large amount of output. Use \l filter to + limit the output to properties of interest. \labs @@ -47,7 +49,7 @@ QT_BEGIN_NAMESPACE /*! \qmlproperty Item StyleKitDebug::control - The \l{The id Attribute}{id} of the \l Control to trace. + The \l{The id Attribute}{id} of the \l Control or \l StyleReader to trace. When set, StyleKit logs all style property reads for this item to the debug output. Set to \c null to stop tracing. @@ -61,10 +63,21 @@ QT_BEGIN_NAMESPACE A regular expression used to filter the debug output. Only lines matching the pattern are printed. By default, all output is shown. - For example, to show only reads of the background color: + For example, to only trace the background color: \code - StyleKit.debug.filter = "background.color" + StyleKit.debug.filter: "background.color" + \endcode + + To trace all background colors, including properties + such as \l {DelegateStyle::color}{background.color}, + \l {BorderStyle::color}{background.border.color}, + \l {ImageStyle::color}{background.image.color}, + and \l {ShadowStyle::color}{background.shadow.color}, + use a regular expression: + + \code + StyleKit.debug.filter: "background.*color" \endcode \sa control @@ -85,6 +98,9 @@ QString QQStyleKitDebug::enumToString(EnumType enumValue) } QString QQStyleKitDebug::objectName(const QObject *obj) { + if (!obj->objectName().isEmpty()) + return obj->objectName(); + QString str = QString::fromLatin1(obj->metaObject()->className()); int idx = str.indexOf("_QMLTYPE"_L1); if (idx != -1) @@ -94,9 +110,7 @@ QString QQStyleKitDebug::objectName(const QObject *obj) { if (str.startsWith(prefix)) str = str.mid(prefix.length()); } - const QString name = obj->objectName(); - if (!name.isEmpty()) - str = str + "("_L1 + name + ")"_L1; + return str; } @@ -123,16 +137,19 @@ QString QQStyleKitDebug::propertyPath(const QQStyleKitPropertyGroup *group, cons QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *control) { + if (!control->objectName().isEmpty()) + return control->objectName(); + const QObject *parentObj = control->parent(); if (!parentObj) return ""_L1; - auto *controls = qobject_cast(parentObj); - if (!controls) { - return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1; - } - const int startIndex = QQStyleKitControlProperties::staticMetaObject.propertyOffset(); - const int endIndex = QQStyleKitControlProperties::staticMetaObject.propertyCount(); + auto *controls = qobject_cast(parentObj); + if (!controls) + return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1; + + const int startIndex = QQStyleKitControls::staticMetaObject.propertyOffset(); + const int endIndex = QQStyleKitControls::staticMetaObject.propertyCount(); const QMetaObject* parentMeta = parentObj->metaObject(); for (int i = startIndex; i < endIndex; ++i) { @@ -145,7 +162,8 @@ QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *cont if (propObj == control) return QString::fromUtf8(prop.name()); } - return ""_L1; + + return ""_L1; } QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *properties, QObject *from) @@ -161,10 +179,9 @@ QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *propertie path.prepend(theme->name() + kDot); } else if (auto *theme = qobject_cast(obj)) { // Note: only one theme is instantiated at a time - if (auto style = theme->style()) - path.prepend(style->themeName()); - else - path.prepend(objectName(obj)); + path.prepend("Theme("_L1 + theme->style()->m_effectiveThemeName + ")"_L1); + } else if (auto *variation = qobject_cast(obj)) { + path.prepend("StyleVariation("_L1 + variation->name() + ")"_L1); } else if (auto *control = qobject_cast(obj)) { path.prepend(controlToString(control)); } else if (auto *reader = qobject_cast(obj)) { @@ -210,7 +227,7 @@ void QQStyleKitDebug::notifyPropertyRead( QString storagePath; if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) { /* We read an interpolated value stored directly in the reader itself. While this - * can be interesting to print out whe debugging the styling engine itself, it + * can be interesting to print out when debugging the styling engine itself, it * comes across as noise when inspecting control-to-style mappings. Ignore. */ #if 0 storagePath = "[local storage] "_L1; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdelegate.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdelegate.cpp index 7c83309028..72ce4db85f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdelegate.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitdelegate.cpp @@ -111,7 +111,7 @@ void QQStyleKitDelegate::updateImplicitSize() * 3. Zero * The implicit size is read-only because it's calculated in C++ from internal * child items that are intentionally not exposed to QML. */ - const qreal impWidthInStyle = m_delegateProperties->implicitWidth(); + const qreal impWidthInStyle = qMax(m_delegateProperties->minimumWidth(), m_delegateProperties->implicitWidth()); const qreal impHeightInStyle = m_delegateProperties->implicitHeight(); setImplicitWidth(impWidthInStyle > 0 || !m_imageOverlay ? impWidthInStyle : m_imageOverlay->implicitWidth()); setImplicitHeight(impHeightInStyle > 0 || !m_imageOverlay ? impHeightInStyle : m_imageOverlay->implicitHeight()); diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver.cpp index 579c28976b..4759c68c6b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver.cpp @@ -113,11 +113,34 @@ void QQStyleKitPropertyResolver::cacheReaderState(QQSK::State state) s_cachedStateList.append(QQSK::StateFlag::Disabled); } +void QQStyleKitPropertyResolver::addVariationToReader( + QQStyleKitReader *styleReader, + QQStyleKitStyleAndThemeBase *styleOrTheme, + QQStyleKitVariation *variation) +{ + /* Add the variation to the StyleReader's list of effective variations. + * A variation may appear more than once in a type variation list or in + * an instance variation list, but it only needs to be added once - the + * first in the list will shadow the other ones anyway. */ + if (!styleReader->m_effectiveVariations.contains(variation)) + styleReader->m_effectiveVariations.append(variation); + + /* We also record in which Style or Theme the variation was found, so that + * it only takes effect for that specific Style or Theme during property + * propagation. Note that the same type variation can be used from both the + * Style and the Theme (its ID can be added to several variation lists). */ + if (!variation->m_usageContext.contains(styleOrTheme)) + variation->m_usageContext.append(styleOrTheme); +} + void QQStyleKitPropertyResolver::addTypeVariationsToReader( QQStyleKitReader *styleReader, - const QQStyleKitExtendableControlType parentType, - const QQStyleKitStyle *style) + QQStyleKitStyleAndThemeBase *styleOrTheme, + const AttachedVariationList &attachedVariations) { + const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType(); + const auto styleReaderBaseType = baseTypesForType(styleReaderType); + static PropertyPathIds ids; if (ids.property.property() == QQSK::Property::NoProperty) { /* ids is made static, since the 'variations' path will be the same for all @@ -129,40 +152,36 @@ void QQStyleKitPropertyResolver::addTypeVariationsToReader( ids.subTypeAlternative = PropertyPathId(); } - const auto parentBaseTypes = baseTypesForType(parentType); - const QVariant inStyleVariationsVar = readPropertyInStyle(ids, parentType, parentBaseTypes, style); - if (!inStyleVariationsVar.isValid()) - return; + for (const QQStyleKitVariationAttached *attached : attachedVariations) { + const auto parentType = attached->controlType(); + const auto parentBaseTypes = baseTypesForType(parentType); - /* Inside each Type Variation, check if the control type that styleReader represents has - * been defined. If so, it means that the variation might affect it, and should therefore be - * added to the style readers list of effective variations. */ - const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType(); - const auto styleReaderBaseType = baseTypesForType(styleReaderType); - - const auto inStyleVariations = *qvariant_cast *>(inStyleVariationsVar); - for (auto *variation : inStyleVariations) { - if (!variation) { - // This happens if the user adds non QQStyleKitVariation elements to the QML array + /* Search for the 'variations' property set on the parentType, or any of its base + * types, using normal propagation and fallback logic. Unlike when resolving other style + * properties, we limit the search to the style or theme we're processing, since it matters which + * style or theme a variation belongs when they're used to resolve other style properties later. */ + const QVariant typeVariationsVariant = readPropertyInRelevantControls(styleOrTheme, ids, parentType, parentBaseTypes); + if (!typeVariationsVariant.isValid()) continue; - } - /* Note: when we read the variations property from the style, it returns the varations - * set in the most specific storage/state/control (because of propagation). And those - * are the only ones that will take effect. This means that even if there are variations - * in the fallback style for the requested type (control) that overrides some properties, - * and the style/theme has variations that overrides something else, the variations in - * the fallback style will, in that case, not be used. The properties not set in the - * effective variation will instead propagate back to be read from the type in the theme - * or the style. This approach is easier to understand and work with, since the propagation - * always flow in one direction, and doesn't jump back and forth between variations, styles - * and themes. */ - if (variation->getControl(styleReaderType)) { - styleReader->m_effectiveInStyleVariations.append(variation); - } else { - for (int type : styleReaderBaseType) { - if (variation->getControl(type)) { - styleReader->m_effectiveInStyleVariations.append(variation); - break; + + const auto typeVariations = *qvariant_cast *>(typeVariationsVariant); + + for (QQStyleKitVariation *variation : typeVariations) { + /* Inside each type variation, check if the control type that styleReader represents has + * been defined. If so, it means that the variation _might_ affect it, and should therefore + * be added to the style readers list of effective variations. */ + if (!variation) { + /* The variation will be nullptr if non-QQStyleKitVariation elements + * are added to the 'variations' list from QML (such as strings). */ + continue; + } + + if (variation->getControl(styleReaderType)) { + addVariationToReader(styleReader, styleOrTheme, variation); + } else { + for (int type : styleReaderBaseType) { + if (variation->getControl(type)) + addVariationToReader(styleReader, styleOrTheme, variation); } } } @@ -170,8 +189,9 @@ void QQStyleKitPropertyResolver::addTypeVariationsToReader( } void QQStyleKitPropertyResolver::addInstanceVariationsToReader( - QQStyleKitReader *styleReader, const QStringList &inAppVariationNames, - const QVarLengthArray &stylesAndThemes) + QQStyleKitReader *styleReader, + QQStyleKitStyleAndThemeBase *styleOrTheme, + const AttachedVariationList &attachedVariations) { /* Add the variations set from the application to the list of effective variations * in the styleReader. But, to speed up property look-up later on, we only add the @@ -181,24 +201,22 @@ void QQStyleKitPropertyResolver::addInstanceVariationsToReader( const QQStyleKitExtendableControlType styleReaderType = styleReader->controlType(); const auto styleReaderBaseTypes = baseTypesForType(styleReaderType); - for (const QString &attachedVariationName : inAppVariationNames) { - for (const QQStyleKitControls *styleOrTheme : stylesAndThemes) { - const QList variationsInStyleOrTheme = styleOrTheme->variations(); - for (QQStyleKitVariation *variationInStyleOrTheme : variationsInStyleOrTheme) { - if (variationInStyleOrTheme->name() != attachedVariationName) + for (const QQStyleKitVariationAttached *attached : attachedVariations) { + for (const QString &instanceVariationName : attached->variations()) { + for (QQStyleKitVariation *variation : styleOrTheme->m_styleVariations) { + if (variation->name() != instanceVariationName) continue; - /* Invariant: we found a variation in a Style or a Theme with a name that matches + + /* Invariant: we found a variation in the given Style or a Theme with a name that matches * a name in the attached variation list. Check if the found variation contains the * type, or the subtypes, of the style reader. If not, it doesn't affect it and can * therefore be skipped. */ - if (variationInStyleOrTheme->getControl(styleReaderType)) { - styleReader->m_effectiveInAppVariations.append(variationInStyleOrTheme); + if (variation->getControl(styleReaderType)) { + addVariationToReader(styleReader, styleOrTheme, variation); } else { for (int baseType : styleReaderBaseTypes) { - if (variationInStyleOrTheme->getControl(baseType)) { - styleReader->m_effectiveInAppVariations.append(variationInStyleOrTheme); - break; - } + if (variation->getControl(baseType)) + addVariationToReader(styleReader, styleOrTheme, variation); } } } @@ -207,7 +225,7 @@ void QQStyleKitPropertyResolver::addInstanceVariationsToReader( } void QQStyleKitPropertyResolver::rebuildVariationsForReader( - QQStyleKitReader *styleReader, const QQStyleKitStyle *style) + QQStyleKitReader *styleReader, QQStyleKitStyle *style) { /* Traverse up the parent chain of \a styleReader, and for each parent, look for an * instance of QQStyleKitVariationAttached. And for each attached object, check if it @@ -218,42 +236,32 @@ void QQStyleKitPropertyResolver::rebuildVariationsForReader( * latter should only affect descendant StyleKitReaders of a specific type. */ Q_ASSERT(styleReader->m_effectiveVariationsDirty); styleReader->m_effectiveVariationsDirty = false; - styleReader->m_effectiveInAppVariations.clear(); - styleReader->m_effectiveInStyleVariations.clear(); + styleReader->m_effectiveVariations.clear(); - const bool hasInstanceVariations = QQStyleKitVariationAttached::s_instanceVariationCount > 0; - const bool hasTypeVariations = QQStyleKitVariation::s_typeVariationCount > 0; - if (!hasTypeVariations && !hasInstanceVariations) { - /* No variations are defined in the current style or theme. In that case - * it doesn't matter if the application has variations set - they - * will anyway not map to any variations in the style. */ + if (!style->m_hasVariations) return; - } - /* We need to search through all variations defined in either the style or the theme, - * including the ones in the fallback styles, since the variation property propagates, - * like all the other style properties. */ - QVarLengthArray stylesAndThemes; - const QQStyleKitStyle *styleOrFallbackStyle = style; - while (styleOrFallbackStyle) { - if (const auto *theme = styleOrFallbackStyle->theme()) - stylesAndThemes.append(theme); - stylesAndThemes.append(styleOrFallbackStyle); - styleOrFallbackStyle = styleOrFallbackStyle->fallbackStyle(); - } - - QObject *parentObj = styleReader; - while (parentObj) { - QObject *attachedObject = qmlAttachedPropertiesObject(parentObj, false); - if (attachedObject) { - auto *variationAtt = static_cast(attachedObject); - if (hasInstanceVariations) - addInstanceVariationsToReader(styleReader, variationAtt->variations(), stylesAndThemes); - if (hasTypeVariations) - addTypeVariationsToReader(styleReader, variationAtt->controlType(), style); + /* Walk up the parent chain and collect all attached StyleVariation objects that + * may affect this StyleReader. Their variation lists affect instance variations, + * and their control type may affect type variations. */ + AttachedVariationList attachedVariations; + for (QObject *current = styleReader; current; current = current->parent()) { + if (const QObject *attachedObject = qmlAttachedPropertiesObject(current, false)) { + const auto *attached = static_cast(attachedObject); + attachedVariations.append(attached); } + } - parentObj = parentObj->parent(); + if (attachedVariations.isEmpty()) + return; + + for (QQStyleKitStyle *current = style; current; current = current->fallbackStyle()) { + if (QQStyleKitTheme *theme = current->theme()) { + addInstanceVariationsToReader(styleReader, theme, attachedVariations); + addTypeVariationsToReader(styleReader, theme, attachedVariations); + } + addInstanceVariationsToReader(styleReader, current, attachedVariations); + addTypeVariationsToReader(styleReader, current, attachedVariations); } } @@ -444,44 +452,39 @@ QVariant QQStyleKitPropertyResolver::readPropertyInRelevantControls( return {}; } -QVariant QQStyleKitPropertyResolver::readPropertyInStyle( +QVariant QQStyleKitPropertyResolver::readPropertyInVariations( + const QList> &variations, + const QQStyleKitStyleAndThemeBase *styleOrTheme, const PropertyPathIds &ids, const QQStyleKitExtendableControlType exactType, - const QList baseTypes, - const QQStyleKitStyle *style) + const QList baseTypes) { - QVariant value; - - while (true) { - value = readPropertyInRelevantControls(style->theme(), ids, exactType, baseTypes); - if (value.isValid()) - break; - value = readPropertyInRelevantControls(style, ids, exactType, baseTypes); - if (value.isValid()) - break; - - if (auto *fallbackStyle = style->fallbackStyle()) { - /* Recurse into the fallback style, and search for the property there. If not - * found, and the fallback style has a fallback style, the recursion continues. */ - fallbackStyle->syncFromQPalette(style->effectivePalette()); - value = readPropertyInStyle(ids, exactType, baseTypes, fallbackStyle); - if (value.isValid()) + bool foundAtLeastOneVariation = false; + for (const QPointer &variation : variations) { + if (!variation) + continue; + if (!variation->m_usageContext.contains(styleOrTheme)) { + if (foundAtLeastOneVariation) { + /* Optimization: We have found at least one effective variation in the list, but this + * one has a different usage context. This means that the remaining variations will also + * have a different usage context, since the variations were added to the list sorted on context. + * So we can end the iteration. */ break; + } + /* The variations list is sorted on usage context. And the first variation in the list with + * the correct usage context has yet to be found. So continue to the next variation. */ + continue; } - - break; + foundAtLeastOneVariation = true; + const QVariant value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes); + if (value.isValid()) + return value; } - - if (Q_UNLIKELY(QQStyleKitDebug::enabled())) { - if (!value.isValid()) - QQStyleKitDebug::notifyPropertyNotResolved(ids.property); - } - - return value; + return {}; } -QVariant QQStyleKitPropertyResolver::readProperty( - const PropertyPathIds &ids, QQStyleKitReader *styleReader, QQStyleKitStyle *style) +QVariant QQStyleKitPropertyResolver::readPropertyInStyle( + QQStyleKitStyle *style, const PropertyPathIds &ids, QQStyleKitReader *styleReader) { /* Sync the palette of the style with the palette of the current reader. Note * that this can cause palette bindings in the style to change, which will @@ -502,27 +505,30 @@ QVariant QQStyleKitPropertyResolver::readProperty( QVariant value; while (true) { - for (const QPointer &variation : std::as_const(styleReader->m_effectiveInAppVariations)) { - if (!variation) - continue; - value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes); - if (value.isValid()) - break; - } + value = readPropertyInVariations(styleReader->m_effectiveVariations, style->theme(), ids, exactType, baseTypes); if (value.isValid()) break; - for (const QPointer &variation : std::as_const(styleReader->m_effectiveInStyleVariations)) { - if (!variation) - continue; - value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes); - if (value.isValid()) - break; - } + value = readPropertyInRelevantControls(style->theme(), ids, exactType, baseTypes); if (value.isValid()) break; - value = readPropertyInStyle(ids, exactType, baseTypes, style); + value = readPropertyInVariations(styleReader->m_effectiveVariations, style, ids, exactType, baseTypes); + if (value.isValid()) + break; + + value = readPropertyInRelevantControls(style, ids, exactType, baseTypes); + if (value.isValid()) + break; + + if (auto *fallbackStyle = style->fallbackStyle()) { + /* Recurse into the fallback style, and search for the property there. If not + * found, and the fallback style has a fallback style, the recursion continues. */ + value = readPropertyInStyle(fallbackStyle, ids, styleReader); + if (value.isValid()) + break; + } + break; } @@ -543,17 +549,16 @@ QVariant QQStyleKitPropertyResolver::readStyleProperty( const QQSK::PropertyPathFlags pathFlags = group->pathFlags(); const QQSK::Subclass subclass = controlProperties->subclass(); - if (subclass == QQSK::Subclass::QQStyleKitState) { + if (subclass != QQSK::Subclass::QQStyleKitReader) { /* Due to propagation, we must know the reader’s state in order to resolve the value * of a given property. For example, the background delegate’s color may follow a * completely different lookup path—and therefore produce a different result—depending * on whether the reader is a Slider or a Button, and whether it is hovered, pressed, etc. - * For this reason, when a property is accessed directly from a control (or one of its - * states) within the StyleKit, full propagation cannot be supported: the necessary - * reader context is unavailable. + * For this reason, when a property is not read via a QQStyleKitReader, propagation and + * fall back logic cannot be supported. * However, to still allow static (i.e., non-propagating) bindings between properties - * inside a Style, we fall back to simply returning the value defined on the accessed - * control itself. */ + * _inside_ a Style, we fall back to return the value set directly on the accessed control. */ + Q_ASSERT(subclass == QQSK::Subclass::QQStyleKitState); const QQStyleKitControlState *controlState = controlProperties->asQQStyleKitState(); const QQStyleKitControl *control = controlState->control(); const PropertyPathId propertyPathId = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype); @@ -575,57 +580,52 @@ QVariant QQStyleKitPropertyResolver::readStyleProperty( return {}; } - if (subclass == QQSK::Subclass::QQStyleKitReader) { - QQStyleKitDebug::groupBeingRead = group; - QQStyleKitReader *styleReader = controlProperties->asQQStyleKitReader(); + QQStyleKitDebug::groupBeingRead = group; + QQStyleKitReader *styleReader = controlProperties->asQQStyleKitReader(); - if (s_isReadingProperty) { - if (!s_styleWarningsIssued) { - s_styleWarningsIssued = true; - qmlWarning(styleReader) << "The style property '" << property << "' was read " - << "before finishing the read of another style property. " - << "This is likely to cause a style glitch."; - } + if (s_isReadingProperty) { + if (!s_styleWarningsIssued) { + s_styleWarningsIssued = true; + qmlWarning(styleReader) << "The style property '" << property << "' was read " + << "before finishing the read of another style property. " + << "This is likely to cause a style glitch."; } - QScopedValueRollback rollback(s_isReadingProperty, true); + } + QScopedValueRollback rollback(s_isReadingProperty, true); - PropertyPathIds ids; - ids.property = group->propertyPathId(property, PropertyPathId::Flag::ExcludeSubtype); - ids.alternative = group->propertyPathId(alternative, PropertyPathId::Flag::ExcludeSubtype); - const bool insideSubType = pathFlags & - (QQSK::PropertyPathFlag::DelegateSubtype1 | QQSK::PropertyPathFlag::DelegateSubtype2); + PropertyPathIds ids; + ids.property = group->propertyPathId(property, PropertyPathId::Flag::ExcludeSubtype); + ids.alternative = group->propertyPathId(alternative, PropertyPathId::Flag::ExcludeSubtype); + const bool insideSubType = pathFlags & + (QQSK::PropertyPathFlag::DelegateSubtype1 | QQSK::PropertyPathFlag::DelegateSubtype2); + if (insideSubType) { + ids.subTypeProperty = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype); + ids.subTypeAlternative = group->propertyPathId(alternative, PropertyPathId::Flag::IncludeSubtype); + } else { + ids.subTypeProperty = PropertyPathId(); + ids.subTypeAlternative = PropertyPathId(); + } + + if (!pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) { + /* A style reader can have a storage that contains local property overrides (that is, + * interpolated values from an ongoing transition). When searching for a property, we + * therefore need to check this storage first. The exception is if the property was read + * inside the 'global' group, which means that we should read the values directly + * from the style. */ if (insideSubType) { - ids.subTypeProperty = group->propertyPathId(property, PropertyPathId::Flag::IncludeSubtype); - ids.subTypeAlternative = group->propertyPathId(alternative, PropertyPathId::Flag::IncludeSubtype); - } else { - ids.subTypeProperty = PropertyPathId(); - ids.subTypeAlternative = PropertyPathId(); - } - - if (!pathFlags.testFlag(QQSK::PropertyPathFlag::Global)) { - /* A style reader can have a storage that contains local property overrides (that is, - * interpolated values from an ongoing transition). When searching for a property, we - * therefore need to check this storage first. The exception is if the property was read - * inside the 'global' group, which means that we should read the values directly - * from the style. */ - if (insideSubType) { - const QVariant value = readPropertyInStorageForState( - ids.subTypeProperty, ids.subTypeAlternative, styleReader, QQSK::StateFlag::Normal); - if (value.isValid()) - return value; - } const QVariant value = readPropertyInStorageForState( - ids.property, ids.alternative, styleReader, QQSK::StateFlag::Normal); + ids.subTypeProperty, ids.subTypeAlternative, styleReader, QQSK::StateFlag::Normal); if (value.isValid()) return value; } - - return readProperty(ids, styleReader, style); + const QVariant value = readPropertyInStorageForState( + ids.property, ids.alternative, styleReader, QQSK::StateFlag::Normal); + if (value.isValid()) + return value; } - Q_UNREACHABLE(); - return {}; + return readPropertyInStyle(style, ids, styleReader); } bool QQStyleKitPropertyResolver::writeStyleProperty( diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver_p.h index 12ffc916f1..449afaed81 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitpropertyresolver_p.h @@ -25,9 +25,15 @@ QT_BEGIN_NAMESPACE class QQStyleKitControl; class QQStyleKitControls; class QQStyleKitStyle; +class QQStyleKitStyleAndThemeBase; class QQStyleKitReader; class QQStyleKitPropertyGroup; +class QQStyleKitVariation; +class QQStyleKitVariationAttached; +// The stack-allocated capacity should be large enough to cover the maximum expected +// nesting depth of controls in the application, to avoid heap allocation. +using AttachedVariationList = QVarLengthArray; class QQStyleKitPropertyResolver { Q_GADGET @@ -89,29 +95,36 @@ private: const QQStyleKitExtendableControlType exactType, const QList baseTypes); - static QVariant readPropertyInStyle( + static QVariant readPropertyInVariations( + const QList> &variations, + const QQStyleKitStyleAndThemeBase *styleOrTheme, const PropertyPathIds &ids, const QQStyleKitExtendableControlType exactType, - const QList baseTypes, - const QQStyleKitStyle *style); + const QList baseTypes); - static QVariant readProperty( + static QVariant readPropertyInStyle( + QQStyleKitStyle *style, const PropertyPathIds &ids, - QQStyleKitReader *styleReader, - QQStyleKitStyle *style); + QQStyleKitReader *styleReader); static void cacheReaderState(QQSK::State state); + + static inline void addVariationToReader( + QQStyleKitReader *styleReader, + QQStyleKitStyleAndThemeBase *styleOrTheme, + QQStyleKitVariation *variation); + static void addInstanceVariationsToReader( QQStyleKitReader *styleReader, - const QStringList &inAppVariationNames, - const QVarLengthArray &stylesAndThemes); + QQStyleKitStyleAndThemeBase *styleOrTheme, + const AttachedVariationList &attachedVariations); + static void addTypeVariationsToReader( QQStyleKitReader *styleReader, - const QQStyleKitExtendableControlType parentType, - const QQStyleKitStyle *style); - static void rebuildVariationsForReader( - QQStyleKitReader *styleReader, - const QQStyleKitStyle *style); + QQStyleKitStyleAndThemeBase *styleOrTheme, + const AttachedVariationList &attachedVariations); + + static void rebuildVariationsForReader(QQStyleKitReader *styleReader, QQStyleKitStyle *style); }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader.cpp index c97d0a348d..9570324e18 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader.cpp @@ -13,6 +13,215 @@ QT_BEGIN_NAMESPACE +/*! + \qmltype StyleReader + \inqmlmodule Qt.labs.StyleKit + \inherits ControlStyleProperties + \brief Reads properties from the active Style for a specific control. + + StyleReader is the bridge between a control in \l {Qt Quick Controls} + and the \l {StyleKit::style}{active style}. It exposes all the style + properties — \l {ControlStyleProperties::background}{backgrounds}, + \l {ControlStyleProperties::indicator}{indicators}, + \l {ControlStyleProperties::handle}{handles}, + \l {ControlStyleProperties::text}{text}, + \l {ControlStyleProperties::padding}{padding}, + and more — that a control, and its delegates, should bind to. + All built-in \c StyleKit controls use a StyleReader + internally for this purpose. + + The state properties — \l hovered, \l pressed, \l focused, \l checked, + \l highlighted, etc. — tell StyleReader which state the control is in. + It uses these to resolve and read the correct property values from + the \l Style, taking \l {Theme}{Themes}, \l {StyleVariation}{StyleVariations}, + \l {AbstractStylableControls}{fallback types}, and property propagation + into account. + + When implementing a \l CustomControl, you can follow the same approach as + the built-in controls: + + \snippet StyleReaderSnippets.qml custom control + + \note The style properties inherited from \l ControlStyleProperties do not map + directly to the \l Style. Instead, they reflect a cache of potentially interpolated + values during an active \l {ControlStyle::}{transition}. Writing to them will \e not + update the corresponding properties in the \l Style and may interfere with ongoing + transitions. Write directly to the \l Style when you need to change a style property. + + \labs + + \sa CustomControl, Style, {StyleVariation::controlType}{StyleVariation.controlType} +*/ + +/*! + \qmlproperty enumeration StyleReader::controlType + + Identifies which control type in the \l Style this reader reads + properties from. This can either be set to one of the predefined values + below, or to a \l {CustomControl::controlType}{custom control type} defined in the + \l Style. + + \value StyleReader.Button \l {Button} + \value StyleReader.CheckBox \l {CheckBox} + \value StyleReader.ComboBox \l {ComboBox} + \value StyleReader.Frame \l {Frame} + \value StyleReader.GroupBox \l {GroupBox} + \value StyleReader.ItemDelegate \l {ItemDelegate} + \value StyleReader.Label \l {Label} + \value StyleReader.Menu \l {Menu} + \value StyleReader.Dialog \l {Dialog} + \value StyleReader.Page \l {Page} + \value StyleReader.Pane \l {Pane} + \value StyleReader.Popup \l {Popup} + \value StyleReader.ProgressBar \l {ProgressBar} + \value StyleReader.RadioButton \l {RadioButton} + \value StyleReader.ScrollBar \l {ScrollBar} + \value StyleReader.ScrollView \l {ScrollView} + \value StyleReader.SearchField \l {SearchField} + \value StyleReader.Slider \l {Slider} + \value StyleReader.SpinBox \l {SpinBox} + \value StyleReader.Switch \l {Switch} + \value StyleReader.TabBar \l {TabBar} + \value StyleReader.TabButton \l {TabButton} + \value StyleReader.TextArea \l {TextArea} + \value StyleReader.TextField \l {TextField} + \value StyleReader.ToolBar \l {ToolBar} + \value StyleReader.ToolButton \l {ToolButton} + \value StyleReader.ToolSeparator \l {ToolSeparator} + + \sa {StyleVariation::controlType}{StyleVariation.controlType} +*/ + +/*! + \qmlproperty bool StyleReader::enabled + + Whether the control is enabled. + + Bind this to the control's \l {Item::enabled}{enabled} property. + + The default value is \c true. + + \sa {ControlStateStyle::disabled}{ControlStateStyle.disabled} +*/ + +/*! + \qmlproperty bool StyleReader::hovered + + Whether the control is hovered. + + Bind this to the control's \l {Control::hovered}{hovered} property. + + The default value is \c false. + + \sa {ControlStateStyle::hovered}{ControlStateStyle.hovered} +*/ + +/*! + \qmlproperty bool StyleReader::pressed + + Whether the control is pressed. + + Bind this to the control's \l {AbstractButton::pressed}{pressed} property. + + The default value is \c false. + + \sa {ControlStateStyle::pressed}{ControlStateStyle.pressed} +*/ + +/*! + \qmlproperty bool StyleReader::focused + + Whether the control has active focus. + + Bind this to the control's \l {Item::activeFocus}{activeFocus} property. + + The default value is \c false. + + \sa {ControlStateStyle::focused}{ControlStateStyle.focused} +*/ + +/*! + \qmlproperty bool StyleReader::checked + + Whether the control is checked. + + Bind this to the control's \l {AbstractButton::checked}{checked} property. + + The default value is \c false. + + \sa {ControlStateStyle::checked}{ControlStateStyle.checked} +*/ + +/*! + \qmlproperty bool StyleReader::highlighted + + Whether the control is highlighted. + + Bind this to the control's \l {Button::highlighted}{highlighted} property. + + The default value is \c false. + + \sa {ControlStateStyle::highlighted}{ControlStateStyle.highlighted} +*/ + +/*! + \qmlproperty bool StyleReader::vertical + + Whether the control is oriented vertically. + + Bind this to the control's \l {Slider::orientation}{orientation}. + + The default value is \c false. + + \sa {ControlStateStyle::vertical}{ControlStateStyle.vertical} +*/ + +/*! + \qmlproperty palette StyleReader::palette + + The palette of the control. StyleKit uses this to resolve color properties + that bind the \l {Style::}{palette} in the \l Style. + + Bind this to the control's \l {Control::palette}{palette} property. + + \sa {Style::palette}{Style.palette} +*/ + +/*! + \qmlproperty font StyleReader::font + \readonly + + The effective font for this control type, as defined by the + \l {AbstractStyle::fonts}{style}. This also takes into account any font + overrides set in the \l {ControlStyle::}{text} properties of the style. + + Bind the control's \l {Control::font}{font} property to this property. + + \note Unlike properties such as \l hovered, \l pressed and \l palette — which + is forwarded from the control to the StyleReader — \l font is an output. Bind the + control's \l {Control::font}{font} property to this value, not the other way around. +*/ + +/*! + \qmlproperty ControlStyleProperties StyleReader::global + \readonly + + Provides direct access to the style properties, bypassing any ongoing + \l {ControlStyle::}{transition}. + + While a state transition is ongoing, style properties read from a StyleReader + may return interpolated values. By prepending \c global to the property path, + you bypass the transition and get the end-state values immediately. + + For example, when transitioning from \l hovered to \l pressed, \c {background.color} + may return an interpolated value between the \l {DelegateStyle::}{color} in the + \l {ControlStateStyle::}{hovered} state and the color in the + \l {ControlStateStyle::}{pressed} state. \c {global.background.color}, on the + other hand, returns the color in the pressed state directly. + + \sa {StyleKit::transitionsEnabled}{transitionsEnabled} +*/ + using namespace Qt::StringLiterals; static const QString kAlternate1 = "A1"_L1; @@ -53,17 +262,7 @@ static quint64 textFontOverridesSignature(const QQStyleKitTextProperties *t) QList QQStyleKitReader::s_allReaders; QMap QQStyleKitReader::s_propertyChangesComponents; -/*! - \qmltype StyleReader - \inqmlmodule Qt.labs.StyleKit - \brief Provides access to control state and palette for styling. - \labs -*/ - -/*! - \qmlproperty palette StyleReader::palette -*/ QQStyleKitReader::QQStyleKitReader(QObject *parent) : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent) @@ -110,9 +309,9 @@ QQuickStateGroup *QQStyleKitReader::stateGroup() QQmlComponent *QQStyleKitReader::createControlChangesComponent() const { - static const QLatin1String propertyName("control"_L1); - if (s_propertyChangesComponents.contains(propertyName)) - return s_propertyChangesComponents.value(propertyName); + const auto key = u"control"_s; + if (auto r = s_propertyChangesComponents.value(key, nullptr)) + return r; const QString qmlControlCode = QString::fromUtf8(R"( import QtQuick @@ -140,14 +339,14 @@ QQmlComponent *QQStyleKitReader::createControlChangesComponent() const QQmlComponent *component = new QQmlComponent(qmlEngine(style())); component->setData(qmlControlCode.toUtf8(), QUrl()); Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData()); - s_propertyChangesComponents.insert(propertyName, component); + s_propertyChangesComponents.insert(key, component); return component; } QQmlComponent *QQStyleKitReader::createDelegateChangesComponent(const QString &delegateName) const { - if (s_propertyChangesComponents.contains(delegateName)) - return s_propertyChangesComponents.value(delegateName); + if (auto r = s_propertyChangesComponents.value(delegateName, nullptr)) + return r; static const QString qmlTemplateCode = QString::fromUtf8(R"( import QtQuick diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader_p.h index 02de9b6669..e804c0166a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitreader_p.h @@ -29,7 +29,7 @@ class QQuickPalette; class QQStyleKitVariation; class QQStyleKitPropertyResolver; -class QQStyleKitReader : public QQStyleKitControlProperties +class Q_LABSSTYLEKIT_EXPORT QQStyleKitReader : public QQStyleKitControlProperties { Q_OBJECT Q_PROPERTY(QQStyleKitExtendableControlType controlType READ controlType WRITE setControlType NOTIFY controlTypeChanged FINAL) @@ -197,8 +197,7 @@ private: QQSK::Delegates m_trackedDelegates = QQSK::Delegate::NoDelegate; QPointer m_parentReader; - QList> m_effectiveInAppVariations; - QList> m_effectiveInStyleVariations; + QList> m_effectiveVariations; QQStyleKitControlProperties m_global; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle.cpp index ed591bf5ce..6be1a6f6c0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle.cpp @@ -4,6 +4,7 @@ #include "qqstylekit_p.h" #include "qqstylekitstyle_p.h" #include "qqstylekittheme_p.h" +#include "qqstylekitvariation_p.h" #include "qqstylekitcustomtheme_p.h" #include "qqstylekitcontrolproperties_p.h" #include "qqstylekitpropertyresolver_p.h" @@ -51,7 +52,7 @@ QT_BEGIN_NAMESPACE \labs \sa Theme, CustomTheme, StyleVariation, ControlStyle, DelegateStyle, - CustomControl, {qtlabsstylekit-property-resolution.html}{Property Resolution} + CustomControl */ /*! @@ -91,7 +92,7 @@ QT_BEGIN_NAMESPACE \snippet StyleSnippets.qml dark - \sa light, themeName, {qtlabsstylekit-theme.html}{Theme} + \sa light, themeName */ /*! @@ -110,8 +111,6 @@ QT_BEGIN_NAMESPACE you to set many more properties than otherwise needed. A reference implementation of a fallback style can be found \l {qtlabsstylekit-fallbackstyle.html}{here.} - - \sa {qtlabsstylekit-property-resolution.html}{Property Resolution} */ /*! @@ -123,7 +122,7 @@ QT_BEGIN_NAMESPACE \snippet StyleSnippets.qml light - \sa dark, themeName, {qtlabsstylekit-theme.html}{Theme} + \sa dark, themeName */ /*! @@ -337,7 +336,8 @@ void QQStyleKitStyle::setThemeName(const QString &themeName) return; m_themeName = themeName; - recreateTheme(); + if (m_completed) + recreateTheme(); emit themeNameChanged(); } @@ -377,18 +377,15 @@ void QQStyleKitStyle::recreateTheme() qmlWarning(this) << "Custom theme '" << effectiveThemeName << "' has no theme component set"; } - if (m_effectiveThemeName == effectiveThemeName) { - // Switching theme name from e.g "System" to "Light" might not - // actually change the currently effective theme. - emit themeNameChanged(); + if (m_effectiveThemeName == effectiveThemeName) return; - } if (m_theme) { m_theme->deleteLater(); m_theme = nullptr; } + m_effectiveThemeName = effectiveThemeName; m_currentThemeComponent = effectiveThemeComponent; if (effectiveThemeComponent) { @@ -426,9 +423,11 @@ void QQStyleKitStyle::recreateTheme() m_theme->fonts()->setFallbackFont(fonts()); if (m_theme->palettes()) m_theme->palettes()->setFallbackPalette(palettes()); + if (this == current()) { m_theme->updateThemePalettes(); m_theme->updateThemeFonts(); + QQStyleKitVariation::resetVariationsForStyle(this); QQStyleKitReader::resetAll(); } @@ -510,4 +509,3 @@ void QQStyleKitStyle::componentComplete() QT_END_NAMESPACE #include "moc_qqstylekitstyle_p.cpp" - diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle_p.h index cc866084ac..8044b38a0e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyle_p.h @@ -107,6 +107,7 @@ private: bool m_completed = false; bool m_isUpdatingPalette = false; + bool m_hasVariations = false; QQuickDeferredPointer m_fallbackStyle; QPointer m_light; @@ -124,6 +125,8 @@ private: friend class QQStyleKitAttached; friend class QQStyleKitPropertyGroup; friend class QQStyleKitPropertyResolver; + friend class QQStyleKitDebug; + friend class QQStyleKitVariation; }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase.cpp index 8b16a8c186..8f310f2649 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqstylekitstyleandthemebase_p.h" +#include "qqstylekitvariation_p.h" QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase_p.h index f2ccec1655..a82e4b102a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitstyleandthemebase_p.h @@ -23,6 +23,8 @@ QT_BEGIN_NAMESPACE +class QQStyleKitVariation; + class QQStyleKitStyleAndThemeBase : public QQStyleKitControls { Q_OBJECT @@ -41,12 +43,17 @@ signals: void fontsChanged(); void palettesChanged(); -private: +protected: + QList m_styleVariations; +private: QQStyleKitFont m_fonts; QQStyleKitPalette m_palettes; Q_DISABLE_COPY(QQStyleKitStyleAndThemeBase) + + friend class QQStyleKitPropertyResolver; + friend class QQStyleKitVariation; }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekittheme.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekittheme.cpp index 7a7dcb6b76..be5f30acf0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekittheme.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekittheme.cpp @@ -133,14 +133,15 @@ void QQStyleKitTheme::updateThemePalettes() // Apply from farthest fallback -> local for (int i = fbChain.size() - 1; i >= 0; --i) { const QQStyleKitPalette *fb = fbChain[i]; - if (fb->isSet(scope)) { - if (auto *p = (fb->*getter)()) - result = p->toQPalette().resolve(result); - } + // Apply system palette first as a base, and override with scope-specific palette if (scope != QQuickTheme::System && fb->isSet(QQuickTheme::System)) { if (auto *sys = fb->system()) result = sys->toQPalette().resolve(result); } + if (fb->isSet(scope)) { + if (auto *p = (fb->*getter)()) + result = p->toQPalette().resolve(result); + } } return result; @@ -185,12 +186,13 @@ void QQStyleKitTheme::updateThemeFonts() // Apply from farthest fallback -> local for (int i = fbChain.size() - 1; i >= 0; --i) { const QQStyleKitFont *fb = fbChain[i]; - if (fb->isSet(scope)) { - result = fb->fontForScope(scope).resolve(result); - } + // Apply system font first as a base, and override with scope-specific font if (scope != QQuickTheme::System && fb->isSet(QQuickTheme::System)) { result = fb->fontForScope(QQuickTheme::System).resolve(result); } + if (fb->isSet(scope)) { + result = fb->fontForScope(scope).resolve(result); + } } return result; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation.cpp index bd8d04fd49..17c555b72f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqstylekitvariation_p.h" +#include "qqstylekitstyleandthemebase_p.h" +#include "qqstylekitstyle_p.h" +#include "qqstylekittheme_p.h" QT_BEGIN_NAMESPACE @@ -122,28 +125,43 @@ QT_BEGIN_NAMESPACE /*! \qmlattachedproperty int StyleVariation::controlType - This property identifies the control type of the item it is attached to. + This property identifies the \l {StyleReader::controlType}{control type} of + the item it is attached to. StyleKit uses it to resolve \l {Type Variations}{type variations} for descendant controls — if a parent item's \c controlType matches a control type that has \l {ControlStyle::}{variations} defined in the \l Style, those variations apply to all descendant controls. - Built-in StyleKit controls set this property automatically. Custom controls + Built-in controls set this property automatically. Custom controls must set it explicitly to participate in type variation resolution. - \sa {ControlStyle::variations}{ControlStyle.variations}, StyleReader + \sa {ControlStyle::variations}{ControlStyle.variations}, + {StyleReader::controlType}{StyleReader.controlType} */ -int QQStyleKitVariation::s_typeVariationCount = 0; -int QQStyleKitVariationAttached::s_instanceVariationCount = 0; - QQStyleKitVariation::QQStyleKitVariation(QObject *parent) : QQStyleKitControls(parent) { - /* As an optimization, keep track of how many type variations that are defined - * inside the style. That way we can skip looking for them later if s_typeVariationCount == 0. */ - ++s_typeVariationCount; +} + +void QQStyleKitVariation::componentComplete() +{ + QQStyleKitControls::componentComplete(); + + /* Whenever there is a Style or Theme change, the list of StyleVariations that affects a + * StyleReader needs to be rebuilt. And in order to know which StyleVariations that should + * be taken into consideration, we need to know which Style each StyleVariation belongs to. */ + bool styleOrThemeFound = false; + for (QObject *current = parent(); current; current = current->parent()) { + if (auto *styleOrTheme = qobject_cast(current)) { + styleOrTheme->m_styleVariations.append(this); + styleOrThemeFound = true; + break; + } + } + if (!styleOrThemeFound) + qmlWarning(this) << "A StyleVariation needs to be a descendant of the Style it belongs to!"; } QQStyleKitVariationAttached *QQStyleKitVariation::qmlAttachedProperties(QObject *object) @@ -180,16 +198,11 @@ void QQStyleKitVariationAttached::setVariations(const QStringList &variations) if (m_variations == variations) return; - /* As an optimization, we count the number of instance variations set from the application. - * That way, if s_instanceVariationCount == 1, for example, and we found a variation while - * resolving the effective variations for a specific QQStyleReader, we can stop the search. */ - s_instanceVariationCount++; - m_variations = variations; emit variationsChanged(); } -QQStyleKitExtendableControlType QQStyleKitVariationAttached::controlType() +QQStyleKitExtendableControlType QQStyleKitVariationAttached::controlType() const { return m_controlType; } @@ -203,6 +216,39 @@ void QQStyleKitVariationAttached::setControlType(QQStyleKitExtendableControlType emit controlTypeChanged(); } +void QQStyleKitVariation::resetVariationsForStyle(QQStyleKitStyle *style) +{ + /* The usage context stored in a StyleVariation tells which Style or Theme uses it. + * After a theme change, we therefore need to rebuild this context list, since the + * old theme needs to be removed, and the new theme might need to be added. + * Since we also need to update which variations will now be effective for the existing + * StyleReaders, we simply clear the context here, and leave it to QQStyleKitPropertyResolver + * to rebuild both the effective variations and the context in one pass later. + * (QQStyleKitReader::resetReadersForStyle() will set the m_effectiveVariationsDirty + * flag, which will trigger the rebuild). + * + * As an optimization, we also update m_hasVariations to reflect whether any variations + * exist at all. If not, variations don't need to be taken into consideration during + * style property look-up. */ + style->m_hasVariations = false; + + for (QQStyleKitStyle *current = style; current; current = current->fallbackStyle()) { + if (!current->m_styleVariations.isEmpty()) { + style->m_hasVariations = true; + for (QQStyleKitVariation *variation : current->m_styleVariations) + variation->m_usageContext.clear(); + } + + if (QQStyleKitTheme *theme = current->theme()) { + if (!theme->m_styleVariations.isEmpty()) { + style->m_hasVariations = true; + for (QQStyleKitVariation *variation : theme->m_styleVariations) + variation->m_usageContext.clear(); + } + } + } +} + QT_END_NAMESPACE #include "moc_qqstylekitvariation_p.cpp" diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation_p.h b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation_p.h index a6682edaeb..139cc76d08 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/labs/stylekit/qqstylekitvariation_p.h @@ -22,6 +22,8 @@ QT_BEGIN_NAMESPACE class QQStyleKitVariationAttached; +class QQStyleKitStyleAndThemeBase; +class QQStyleKitStyle; class QQStyleKitVariation : public QQStyleKitControls { @@ -33,6 +35,8 @@ class QQStyleKitVariation : public QQStyleKitControls public: QQStyleKitVariation(QObject *parent = nullptr); + void componentComplete() override; + QString name() const; void setName(const QString &name); @@ -44,11 +48,13 @@ Q_SIGNALS: private: Q_DISABLE_COPY(QQStyleKitVariation) - QString m_name; + static void resetVariationsForStyle(QQStyleKitStyle *style); - static int s_typeVariationCount; + QString m_name; + QList> m_usageContext; friend class QQStyleKitPropertyResolver; + friend class QQStyleKitStyle; }; class QQStyleKitVariationAttached : public QObject @@ -63,7 +69,7 @@ public: QStringList variations() const; void setVariations(const QStringList &variations); - QQStyleKitExtendableControlType controlType(); + QQStyleKitExtendableControlType controlType() const; void setControlType(QQStyleKitExtendableControlType type); signals: @@ -74,8 +80,6 @@ public: QStringList m_variations; QQStyleKitExtendableControlType m_controlType = QQStyleKitReader::ControlType::Unspecified; - static int s_instanceVariationCount; - friend class QQStyleKit; friend class QQStyleKitPropertyResolver; }; diff --git a/local/recipes/qt/qtdeclarative/source/src/labs/wavefrontmesh/qwavefrontmesh.cpp b/local/recipes/qt/qtdeclarative/source/src/labs/wavefrontmesh/qwavefrontmesh.cpp index 566e16e318..88c130ab6f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/labs/wavefrontmesh/qwavefrontmesh.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/labs/wavefrontmesh/qwavefrontmesh.cpp @@ -94,6 +94,7 @@ public: \table \row \li \image qtlabs-wavefrontmesh.png + {Torus shape with gradient texture coordinates} \li \qml import QtQuick 2.\1 import Qt.labs.wavefrontmesh 1.\1 diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickcumulativedirection.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickcumulativedirection.cpp index dd42c71d36..d620003973 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickcumulativedirection.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickcumulativedirection.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickcumulativedirection_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle.cpp index b7bc957c13..12b28af41e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2021 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include @@ -509,13 +510,13 @@ void fillUniformArrayFromImage(float* array, const QImage& img, int size) \table \row \li qrc:///particleresources/star.png - \li \inlineimage particles/star.png + \li \inlineimage particles/star.png {White star particle} \row \li qrc:///particleresources/glowdot.png - \li \inlineimage particles/glowdot.png + \li \inlineimage particles/glowdot.png {White glowing dot particle} \row \li qrc:///particleresources/fuzzydot.png - \li \inlineimage particles/fuzzydot.png + \li \inlineimage particles/fuzzydot.png {White fuzzy dot particle} \endtable Note that the images are white and semi-transparent, to allow colorization diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle_p.h b/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle_p.h index 04719c9aa8..3bfd814cef 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickimageparticle_p.h @@ -170,7 +170,7 @@ class Q_QUICKPARTICLES_EXPORT QQuickImageParticle : public QQuickParticlePainter Q_PROPERTY(qreal alpha READ alpha WRITE setAlpha NOTIFY alphaChanged RESET resetColor) Q_PROPERTY(qreal alphaVariation READ alphaVariation WRITE setAlphaVariation NOTIFY alphaVariationChanged RESET resetColor) - Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged RESET resetRotation) + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged RESET resetRotation OVERRIDE) Q_PROPERTY(qreal rotationVariation READ rotationVariation WRITE setRotationVariation NOTIFY rotationVariationChanged RESET resetRotation) Q_PROPERTY(qreal rotationVelocity READ rotationVelocity WRITE setRotationVelocity NOTIFY rotationVelocityChanged RESET resetRotation) Q_PROPERTY(qreal rotationVelocityVariation READ rotationVelocityVariation WRITE setRotationVelocityVariation NOTIFY rotationVelocityVariationChanged RESET resetRotation) diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickitemparticle.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickitemparticle.cpp index c38fe57f20..1cb2c173a8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickitemparticle.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickitemparticle.cpp @@ -1,9 +1,11 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickitemparticle_p.h" +#include "qquickparticleemitter_p.h" #include #include #include @@ -293,9 +295,14 @@ QQuickItemParticleAttached *QQuickItemParticle::qmlAttachedProperties(QObject *o bool QQuickItemParticle::clockShouldUpdate() const { - QQuickItem *parentItem = qobject_cast(parent()); - return (m_system && m_system->isRunning() && !m_system->isPaused() && m_system->isEnabled() - && ((parentItem && parentItem->isEnabled()) || !parentItem) && isEnabled()); + // Determine if the QQuickItemParticle clock should stop by looking at the system running + // state and any parent Emitter enabled state. Note that this enabled state is the overridden + // QQuickParticleEmitter::enabled, and not the unrelated QQuickItem::enabled (which controls + // if the item reacts to input events). + QQuickParticleEmitter *parentEmitter = qobject_cast(parent()); + bool parentEmitterDisabled = parentEmitter && !parentEmitter->isEnabled(); + bool systemRunning = m_system && (m_system->isRunning() || m_system->isPaused()); + return systemRunning && !parentEmitterDisabled; } void QQuickItemParticle::reconnectParent(QQuickItem *parentItem) diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector.cpp index d0cba7a658..1a97971d08 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickparticleaffector_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector_p.h b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector_p.h index 9c6ff7df1f..ac6ba4401e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleaffector_p.h @@ -29,7 +29,7 @@ class Q_QUICKPARTICLES_EXPORT QQuickParticleAffector : public QQuickItem Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged) Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged) Q_PROPERTY(QStringList whenCollidingWith READ whenCollidingWith WRITE setWhenCollidingWith NOTIFY whenCollidingWithChanged) - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged OVERRIDE) Q_PROPERTY(bool once READ onceOff WRITE setOnceOff NOTIFY onceChanged) Q_PROPERTY(QQuickParticleExtruder* shape READ shape WRITE setShape NOTIFY shapeChanged) diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter.cpp index 920800b015..8476829d30 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickparticleemitter_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter_p.h b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter_p.h index 2b1a8734da..498a4c550c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticleemitter_p.h @@ -34,7 +34,7 @@ class Q_QUICKPARTICLES_EXPORT QQuickParticleEmitter : public QQuickItem Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged) Q_PROPERTY(QString group READ group WRITE setGroup NOTIFY groupChanged) Q_PROPERTY(QQuickParticleExtruder* shape READ extruder WRITE setExtruder NOTIFY extruderChanged) - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged OVERRIDE) Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(qreal emitRate READ particlesPerSecond WRITE setParticlesPerSecond NOTIFY particlesPerSecondChanged) diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlegroup.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlegroup.cpp index 8225a49bd7..71c03b1ee2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlegroup.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlegroup.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickparticlegroup_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlepainter.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlepainter.cpp index db9741ce00..f57fdb67f5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlepainter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickparticlepainter.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickparticlepainter_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquicktrailemitter.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquicktrailemitter.cpp index 871ec6d9b5..46426b55a5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquicktrailemitter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquicktrailemitter.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquicktrailemitter_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/particles/qquickturbulence.cpp b/local/recipes/qt/qtdeclarative/source/src/particles/qquickturbulence.cpp index d14acc8b43..ad35d261a2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/particles/qquickturbulence.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/particles/qquickturbulence.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2016 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 +#include #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses #include "qquickturbulence_p.h" diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/qml/CMakeLists.txt index 1063b42e96..68c3a57edc 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/qml/CMakeLists.txt @@ -388,7 +388,6 @@ qt_internal_add_qml_module(Qml ENABLE_JIT_CONSTANT_BLINDING=0 ENABLE_LLINT=0 JS_EXPORT_PRIVATE= # special case - QT_NO_FOREACH QT_NO_INTEGER_EVENT_COORDINATES QT_NO_URL_CAST_FROM_STRING WTFInvokeCrashHook=qmlWTFInvokeCrashHook diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4compileddata_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4compileddata_p.h index 1b824bc0f8..565c586de9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4compileddata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4compileddata_p.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #if QT_CONFIG(temporaryfile) #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4staticvalue_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4staticvalue_p.h index ad3553cdfc..c99e958af3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4staticvalue_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/common/qv4staticvalue_p.h @@ -617,7 +617,8 @@ struct StaticValue # error "unsupported pointer size" #endif }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct Encode { static constexpr ReturnedValue undefined() { diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder.cpp index 41dcc09aa0..89baf97ee9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder.cpp @@ -11,6 +11,8 @@ #include #include #include +#include + #include QT_USE_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder_p.h index 604fcf04f5..99b79c2878 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/compiler/qqmlirbuilder_p.h @@ -33,6 +33,7 @@ class QQmlPropertyCache; class QQmlContextData; class QQmlTypeNameCache; struct QQmlIRLoader; +class QTypeRevision; namespace QmlIR { @@ -369,6 +370,8 @@ public: QString appendSignal(Signal *signal); QString appendProperty(Property *prop, const QString &propertyName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation); QString appendAlias(Alias *prop, const QString &aliasName, bool isDefaultProperty, const QQmlJS::SourceLocation &defaultToken, QQmlJS::SourceLocation *errorLocation); + void setFirstAlias(Alias *alias) { aliases->first = alias; } + void appendFunction(QmlIR::Function *f); void appendInlineComponent(InlineComponent *ic); void appendRequiredPropertyExtraData(RequiredPropertyExtraData *extraData); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebug.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebug.cpp index c8b316d9a5..209ec66fae 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebug.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebug.cpp @@ -9,9 +9,15 @@ #include #include +#include + #include #include +#ifdef Q_OS_WIN +#include +#endif + QT_REQUIRE_CONFIG(qml_debug); QT_BEGIN_NAMESPACE @@ -58,7 +64,18 @@ Q_CONSTINIT static std::atomic_flag s_printedWarning = Q_ATOMIC_FLAG_INIT; void QQmlDebuggingEnabler::enableDebugging(bool printWarning) { if (printWarning && !s_printedWarning.test_and_set(std::memory_order_relaxed)) { - fprintf(stderr, "QML debugging is enabled. Only use this in a safe environment.\n"); + QByteArray warning("QML debugging is enabled. Only use this in a safe environment.\n"); + if (qEnvironmentVariableIsSet("CLICOLOR_FORCE") || qEnvironmentVariableIsSet("QTC_RUN") + || qEnvironmentVariableIsSet("VSCODE_CLI")) { + warning = QByteArray("\033[38;2;255;0;0m") + warning + "\033[0m"; + } +#ifdef Q_OS_WIN + // Debuggers on Windows (except vscode) do not display the stderr output + if (!qEnvironmentVariableIsSet("VSCODE_CLI")) + OutputDebugStringA(warning); +#endif + + fprintf(stderr, "%s", warning.constData()); fflush(stderr); // We really want to print this warning, even if stderr is buffered } QQmlEnginePrivate::qml_debugging_enabled.store(true, std::memory_order_relaxed); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebugconnector.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebugconnector.cpp index 89570ac2f8..79d1c01bf4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebugconnector.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/debugger/qqmldebugconnector.cpp @@ -37,7 +37,7 @@ struct QQmlDebugConnectorParams { QCoreApplicationPrivate *appD = static_cast(QObjectPrivate::get(qApp)); if (appD) - arguments = appD->qmljsDebugArgumentsString(); + arguments = appD->qmljs_debug_arguments; } } }; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/qtqml.qdocconf b/local/recipes/qt/qtdeclarative/source/src/qml/doc/qtqml.qdocconf index 708a27aefa..b0dc0e18b6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/qtqml.qdocconf +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/qtqml.qdocconf @@ -58,6 +58,9 @@ exampledirs += ../../../examples/qml \ imagedirs += images +# Allow these languages to be used in code blocks without warnings: +codelanguages += cmake + # Exclude documentation for the compiler category: these are not mature enough to be documented excludefiles += src/qmllint/compiler.qdoc \ src/qmllint/deferred-property-id.qdoc \ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-properties.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-properties.qdoc index 2347ba6897..ff1e1bb1c6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-properties.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-properties.qdoc @@ -19,7 +19,7 @@ global CMake properties: \title QT_QMLLINTER_TARGETS_FOLDER -\brief Sets the FOLDER property for targets that belong to the QML linter. +\summary{Sets the FOLDER property for targets that belong to the QML linter.} \cmakepropertysince 6.5 \preliminarycmakeproperty @@ -208,7 +208,7 @@ C++ during qmltc compilation. */ /*! -\page cmake-source-file-property-qt-qml-generate-java-class.html +\page cmake-source-file-property-qt-qml-generate-android-java-class.html \ingroup cmake-source-file-properties-qtqml \ingroup cmake-android-build-properties diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-variables.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-variables.qdoc index ad49126458..c818b89842 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-variables.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/cmake-variables.qdoc @@ -16,7 +16,7 @@ global CMake variables: \title QT_QML_OUTPUT_DIRECTORY -\brief Base output directory below which QML modules will be created by default. +\summary{Base output directory below which QML modules will be created by default.} The \l{qt6_add_qml_module}{qt6_add_qml_module()} command accepts an \c OUTPUT_DIRECTORY argument which specifies where the QML module's \c qmldir @@ -43,7 +43,7 @@ modules, or to merge sets of QML modules under a common base point. \title QT_QML_GENERATE_QMLLS_INI_NO_CMAKE_CALLS -\brief Customizes the value of no-cmake-calls in .qmlls.ini files for \QMLLS. +\summary{Customizes the value of no-cmake-calls in .qmlls.ini files for \QMLLS.} \cmakevariablesince 6.10 \c QT_QML_GENERATE_QMLLS_INI_NO_CMAKE_CALLS is a boolean that controls the \c no-cmake-calls value in @@ -68,7 +68,7 @@ This variable has no effect if \l{QT_QML_GENERATE_QMLLS_INI} is disabled. \title QT_QML_GENERATE_QMLLS_INI -\brief Enables autogeneration of .qmlls.ini files for \QMLLS. +\summary{Enables autogeneration of .qmlls.ini files for \QMLLS.} \warning This variable is deprecated since Qt 6.10. No replacement needed. \cmakevariablesince 6.7 @@ -106,7 +106,7 @@ subdirectories} that define QML Modules. \title QT_QML_NO_CACHEGEN -\brief Disables compilation of QML files into bytecode or C++ code. +\summary{Disables compilation of QML files into bytecode or C++ code.} \cmakevariablesince 6.8.1 \c QT_QML_NO_CACHEGEN is a CMake variable that can be set to disable the compilation of QML files @@ -142,7 +142,7 @@ qt_add_qml_module(MyModule \title QT_QML_GENERATE_AOTSTATS -\brief Controls whether QML Compiler statistics collection is enabled. +\summary{Controls whether QML Compiler statistics collection is enabled.} \cmakevariablesince 6.8.3 When a QML project is configured using \l{qt_add_qml_module}, by default, @@ -167,7 +167,7 @@ set(QT_QML_GENERATE_AOTSTATS OFF) \title QT_QMLLINT_CONTEXT_PROPERTY_DUMP -\brief Enables dumping of context properties in lint targets. +\summary{Enables dumping of context properties in lint targets.} \cmakevariablesince 6.11 When the \c QT_QMLLINT_CONTEXT_PROPERTY_DUMP variable is enabled, \l{qmllint-auto}{linting targets} diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0004.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0004.qdoc index 9d3428e52b..e72c8ebbcb 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0004.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0004.qdoc @@ -22,7 +22,58 @@ The \c OLD behavior of this policy is that a qmldir file is only generated for the root directory of a module. The \c NEW behavior of this policy is that for each directory with QML files in -a module a separate qmldir file is generated. +a module a separate qmldir file is generated. Each generated qmldir contains a +\c prefer directive that redirects the implicit import to the module's canonical +resource location. + +\section1 Background + +The QML engine always performs an implicit import of the directory a QML file +resides in. For files in the module root directory, this implicit import +resolves to the module itself, so all sibling components are visible. However, +when QML files are placed in subdirectories, the implicit import resolves to +that subdirectory instead. Components in the subdirectory then cannot see other +components in the same module without adding an explicit \c import statement. +This is a frequent source of errors. + +With the \c NEW behavior, the build system generates a qmldir file in each +subdirectory containing a \c prefer directive pointing to the module's canonical +resource location. This makes the implicit import of every QML file in the +module equivalent to importing the module itself, so all components can find +each other regardless of their directory. + +\section1 Example + +Consider a module with QML files split across multiple subdirectories: + +\badcode +qt_add_qml_module(mymodule + URI MyModule + QML_FILES + Main.qml + controls/MyButton.qml + views/MyView.qml +) +\endcode + +A common need is for \c{views/MyView.qml} to use \c{MyButton} from the +\c controls subdirectory. With the \c OLD behavior this fails silently: +\c{views/MyView.qml} implicitly imports the \c views subdirectory, so +\c{MyButton} is not found unless you add \c{import MyModule} explicitly. + +With the \c NEW behavior, an extra qmldir is generated in both the \c controls +and \c views subdirectories. Each redirects the implicit import to the module's +canonical resource location, so \c{views/MyView.qml} can use \c{MyButton} +and any other type in the module without an explicit import. + +You can set the policy explicitly in your project before calling +\c{qt_add_qml_module()}: +\badcode +qt_policy(SET QTP0004 NEW) +\endcode +Alternatively, call +\l{qt6_standard_project_setup}{qt_standard_project_setup()} with a +\c REQUIRES version of 6.8 or later. Qt 6.8 issues warnings if you do not explicitly set the policy. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0005.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0005.qdoc index 25d5175789..24d3cdb34d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0005.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/policy/qtp0005.qdoc @@ -8,29 +8,88 @@ \title QTP0005 \keyword qt_cmake_policy_qtp0005 -\summary {qt_add_qml_module's DEPENDENCIES argument accepts targets} +\summary {qt_add_qml_module dependency keywords accept CMake targets} -This policy was introduced in Qt 6.8. It allows passing targets to -\l{qt_add_qml_module}{qt_add_qml_module()} \c DEPENDENCIES, \c IMPORTS, \c -OPTIONAL_IMPORTS and \c DEFAULT_IMPORTS. +This policy was introduced in Qt 6.8. It allows passing CMake targets to +\l{qt_add_qml_module}{qt_add_qml_module()} via the \c DEPENDENCIES, \c IMPORTS, +\c OPTIONAL_IMPORTS, and \c DEFAULT_IMPORTS keywords. -Enabling this policy means that the arguments which are passed to the key words -can be prefixed with TARGET, and are then treated as a target name. +Enabling this policy means that arguments passed to those keywords can be prefixed +with \c TARGET, and are then treated as a CMake target name. The QML module URI +and import path are derived automatically from the target. -The \c OLD behavior of this policy is that the "TARGET name" is treated as two -URIs, "TARGET" and "name". +The \c OLD behavior of this policy is that the token sequence \c{TARGET name} is +treated as two separate URIs, \c{TARGET} and \c{name}. -The \c NEW behavior of this policy is that \c TARGET is considered a keyword, -and the URI is extracted from the target which follows next. It is a hard error -if the name following \c TARGET does not name a target, or if that target does +The \c NEW behavior of this policy is that \c TARGET is a keyword. The URI is +extracted from the QML module target that follows. It is a hard error if the name +following \c TARGET does not refer to an existing target, or if that target does not correspond to a QML module. In both the \c NEW and the \c OLD behavior it is possible to specify a module version by appending a slash and the version. See \l{Declaring module dependencies} for more details. -Qt 6.8 issues warnings if you pass a URI to \c DEPENDENCIES which coincides -with a target name. +\section1 Background + +Before this policy, QML module dependencies had to be declared by their string +URI, for example \c{QtQuick} or \c{com.example.mymodule}. This meant that the +URI and, when needed, the import path of the dependency had to be kept consistent +with the CMake target that provides the module. + +With the \c NEW behavior, you can refer to a dependency by its CMake target +name instead. The build system then determines the correct URI and import path +from the target automatically. This removes the need to manually specify +\l{qt_add_qml_module}{IMPORT_PATH} entries for dependencies outside the default +QML import path, reduces redundancy, and eliminates a class of copy-paste +mistakes. + +\section1 Example + +Consider a module that depends on another module defined in the same project. +With the \c OLD behavior (or before Qt 6.8), the URI must be spelled out +explicitly: + +\badcode +qt_add_qml_module(my_module + URI MyModule + VERSION 1.0 + DEPENDENCIES + OtherModule +) +\endcode + +With the \c NEW behavior (policy QTP0005 set to \c NEW), you can refer to the +dependency by its CMake target name: + +\badcode +qt_policy(SET QTP0005 NEW) + +qt_add_qml_module(other_module + URI OtherModule + VERSION 1.0 +) + +qt_add_qml_module(my_module + URI MyModule + VERSION 1.0 + DEPENDENCIES + TARGET other_module +) +\endcode + +The same \c TARGET syntax is available for \c IMPORTS, \c OPTIONAL_IMPORTS, +and \c DEFAULT_IMPORTS. + +As shown above, you can set the policy explicitly with +\c{qt_policy(SET QTP0005 NEW)} before calling \c{qt_add_qml_module()}. +Alternatively, call +\l{qt6_standard_project_setup}{qt_standard_project_setup()} with a +\c REQUIRES version of 6.8 or later to enable all policies up to that version. + +Qt 6.8 issues a deprecation warning if you use \c TARGET as a token in +\c DEPENDENCIES, \c IMPORTS, \c OPTIONAL_IMPORTS, or \c DEFAULT_IMPORTS while +the policy is set to \c OLD. Use the \l qt_policy command to suppress the warning by explicitly setting the policy to \c OLD or \c NEW. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_module.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_module.qdoc index 589dbc4e96..ecf12e6f14 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_module.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_module.qdoc @@ -8,7 +8,7 @@ \title qt_add_qml_module \keyword qt6_add_qml_module -\brief Defines a QML module. +\summary{Defines a QML module.} \cmakecommandsince 6.2 @@ -74,6 +74,10 @@ they are consistent. It also sets up and coordinates things like cached compilation of \c{.qml} sources, resource embedding, linting checks, and auto-generation of some key module files. +For a conceptual overview with common usage examples, see +\l{Tying it all together with CMake}. For advanced topics like custom +directory layouts and versioning, see \l{Writing QML Modules}. + \section2 Target Structure A QML module can be structured in a few different ways. The following scenarios @@ -403,7 +407,51 @@ While processing QML_FILES, the following source file properties are respected: \section1 Arguments -\section2 Required arguments +The arguments to \c{qt_add_qml_module} are organized into the following +categories: + +\table +\header + \li Category + \li Arguments +\row + \li \l{Essential arguments} + \li \c target, \c URI, \c STATIC, \c SHARED +\row + \li \l{Versions} + \li \c VERSION, \l{PAST_MAJOR_VERSIONS}, + \l{Keeping module versions in sync}{FOLLOW_FOREIGN_VERSIONING} +\row + \li \l{Adding sources and resources to the module}{Sources and resources} + \li \c QML_FILES, \c SOURCES, \c RESOURCES, \l{RESOURCE_PREFIX}, \l{NO_RESOURCE_TARGET_PATH}, + \l{DISCARD_QML_CONTENTS} +\row + \li \l{Declaring module dependencies}{Module dependencies} + \li \l{IMPORTS}, \c OPTIONAL_IMPORTS, \c DEFAULT_IMPORTS, \c DEPENDENCIES, + \l{IMPORT_PATH} +\row + \li \l{Targets and plugin targets}{Plugin configuration} + \li \l{PLUGIN_TARGET}, \l{NO_PLUGIN}, \l{NO_PLUGIN_OPTIONAL}, + \l{NO_CREATE_PLUGIN_TARGET}, \l{NO_GENERATE_PLUGIN_SOURCE}, + \l{CLASS_NAME} +\row + \li \l{Automatic type registration}{Code generation and tooling} + \li \c NO_GENERATE_QMLTYPES, \c NO_GENERATE_QMLDIR, \c NO_GENERATE_EXTRA_QMLDIRS, + \c TYPEINFO, \c NO_CACHEGEN, \c NO_LINT, + \l{qmlimportscanner and NO_IMPORT_SCAN}{NO_IMPORT_SCAN} +\row + \li \l{OUTPUT_DIRECTORY}{Output and installation} + \li \l{OUTPUT_DIRECTORY}, \c OUTPUT_TARGETS, \l{NO_GENERATE_QTCONF} +\row + \li \l{C++ namespaces of generated code}{Other} + \li \c NAMESPACE, \c DESIGNER_SUPPORTED +\row + \li \l{Arguments for qmltc}{QML type compiler (qmltc)} + \li \l{ENABLE_TYPE_COMPILER}, \c TYPE_COMPILER_NAMESPACE, + \c QMLTC_EXPORT_DIRECTIVE, \c QMLTC_EXPORT_FILE_NAME +\endtable + +\section2 Essential arguments The \c target specifies the name of the backing target for the QML module. By default, it is created as a shared library if Qt was built as shared @@ -514,6 +562,12 @@ under the \c RESOURCE_PREFIX instead. This can be achieved by specifying the \c NO_RESOURCE_TARGET_PATH option, which may only be used if the backing target is an executable. +\note The resource path, the \l{OUTPUT_DIRECTORY}{output directory} on disk, +and the \l{IMPORT_PATH}{import path} are all related. When changing any of +these, ensure the others are consistent so that the QML engine and tooling can +find the module. See \l{Custom Directory Layouts} in \l{Writing QML Modules} for +a complete explanation. + \target PAST_MAJOR_VERSIONS \section2 Registering past major versions @@ -575,6 +629,22 @@ like \c qmllint. Versions can be specified in the same way as for \c IMPORTS. Each module listed here will be added as an \c{optional import} entry in the generated \l{Module Definition qmldir Files}{qmldir} file. +Entries in \c OPTIONAL_IMPORTS may also refer to CMake targets by prefixing the +target name with the \c TARGET keyword. In this case, the QML module URI is +determined automatically from the target. This requires opting into the CMake +policy \l{QTP0005}. + +For example: + +\badcode +qt_add_qml_module(my_module + URI MyModule + VERSION 1.0 + OPTIONAL_IMPORTS + TARGET OptionalQmlModule +) +\endcode + \c DEFAULT_IMPORTS specifies which of the optional imports are the default entries that should be loaded by tooling. One entry should be specified for every group of \c OPTIONAL_IMPORTS in the module. As optional imports are only resolved at runtime, @@ -584,6 +654,25 @@ default import; tooling will then pick it. If you have one optional import that gets used at runtime without any further configuration, that is an ideal candidate for the default import. +Entries in \c DEFAULT_IMPORTS may also refer to CMake targets by prefixing the +target name with the \c TARGET keyword. In this case, the QML module URI is +determined automatically from the target. This requires opting into the CMake +policy \l{QTP0005}. + +For example: + +\badcode +qt_add_qml_module(my_module + URI MyModule + VERSION 1.0 + OPTIONAL_IMPORTS + TARGET BackendA + TARGET BackendB + DEFAULT_IMPORTS + TARGET BackendA +) +\endcode + \c DEPENDENCIES provides a list of other QML modules that this module depends on, but doesn't necessarily import. It would typically be used for dependencies that only exist at the C++ level, such as a module registering a class to QML @@ -646,14 +735,22 @@ qt_add_qml_module(my_module ) \endcode -\note Using TARGET in DEPENDENCIES or IMPORTS does not automatically -link against the given target. It is used only to derive QML metadata, import paths, -and to establish build-order dependencies. If the QML module or its generated plugin -requires symbols from that target, the target must still be linked explicitly using -target_link_libraries(). +\note Using \c{TARGET } in \c DEPENDENCIES, \c IMPORTS, +\c OPTIONAL_IMPORTS, or \c DEFAULT_IMPORTS does not automatically link against +the given target. It is used only to derive QML metadata, import paths, and to +establish build-order dependencies. If the QML module or its generated plugin +requires symbols from that target, the target must still be linked explicitly +using \c target_link_libraries(). -\note The used with the \c TARGET keyword must be a target built -by your project (not an imported target provided by find_package). +\note The \c{} used with the \c TARGET keyword must be a target +built by your project (not an imported target provided by \c{find_package}). + +\note When using \c{TARGET } in \c DEPENDENCIES, \c IMPORTS, +\c OPTIONAL_IMPORTS, or \c DEFAULT_IMPORTS, only the direct dependency on the +specified target is established. Transitive QML module dependencies of the +referenced target (that is, QML modules which that target itself depends on) +are \e not automatically added. Each QML module dependency must be declared +explicitly. \note Adding the module to \c DEPENDENCIES is not necessary if the module is already imported via the \c IMPORTS option. The recommended way is to @@ -680,6 +777,23 @@ ensuring that resources are set up and loaded correctly. \target PLUGIN_TARGET \section2 Targets and plugin targets +The following options control how the plugin target is created and configured. +For most modules, the defaults are appropriate and none of these options are +needed. Common scenarios: + +\list +\li \b{Default} — a separate plugin target is created automatically with a + generated source file. The plugin is optional (not loaded when the backing + library is linked directly). +\li \b{Custom plugin} — use \l{NO_GENERATE_PLUGIN_SOURCE} to provide your own + plugin class implementation. You should also set \l{CLASS_NAME} to match + your custom plugin class. Typically also requires + \l{NO_PLUGIN_OPTIONAL} since the plugin then contains non-trivial code. +\li \b{No plugin} — use \l{NO_PLUGIN} when the module is always linked + directly and never loaded dynamically. +\li \b{Executable target} — no plugin is created automatically. +\endlist + \c PLUGIN_TARGET specifies the plugin target associated with the QML module. The \c PLUGIN_TARGET can be the same as the backing \c target, in which case there will be no separate backing target. @@ -749,10 +863,38 @@ requires. \section2 Automatic type registration +\c{qt_add_qml_module} automatically generates several files. The following +options control what is generated. In most cases, the defaults are correct and +none of these options are needed. + +\table +\header + \li Option + \li What it disables +\row + \li \c NO_GENERATE_QMLTYPES + \li Typeinfo file (\c{.qmltypes}) and C++ type registration code +\row + \li \c NO_GENERATE_QMLDIR + \li The \c qmldir module definition file (implies \c NO_GENERATE_EXTRA_QMLDIRS) +\row + \li \c NO_GENERATE_EXTRA_QMLDIRS + \li Additional \c qmldir files for subdirectories (see \l{QTP0004}) +\row + \li \c NO_CACHEGEN + \li Bytecode compilation of \c{.qml}, \c{.js}, and \c{.mjs} files +\row + \li \c NO_LINT + \li The \c{*_qmllint} linting target +\row + \li \l{qmlimportscanner and NO_IMPORT_SCAN}{NO_IMPORT_SCAN} + \li Automatic import scanning (static builds: configure-time; executables: build-time) +\endtable + Type registration is automatically performed for the backing target's C++ -sources that are processed by AUTOMOC. This will generate a typeinfo file in the -\l{OUTPUT_DIRECTORY}{output directory}, the file name being the \c target name -with \c{.qmltypes} appended. This file name can be changed using the +sources that are processed by \c{AUTOMOC}. This will generate a typeinfo file in +the \l{OUTPUT_DIRECTORY}{output directory}, the file name being the \c target +name with \c{.qmltypes} appended. This file name can be changed using the \c TYPEINFO option if desired, but this should not normally be necessary. The file name is also recorded as a \c typeinfo entry in the generated \l{Module Definition qmldir Files}{qmldir} file. Automatic type registration diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_plugin.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_plugin.qdoc index bca75d7a95..9a08c50695 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_plugin.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_add_qml_plugin.qdoc @@ -8,7 +8,7 @@ \title qt_add_qml_plugin \keyword qt6_add_qml_plugin -\brief Defines a plugin associated with a QML module. +\summary{Defines a plugin associated with a QML module.} \include cmake-find-package-qml.qdocinc diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_generate_foreign_qml_types.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_generate_foreign_qml_types.qdoc index 82ee6d7585..5d5688e2ff 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_generate_foreign_qml_types.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_generate_foreign_qml_types.qdoc @@ -14,12 +14,11 @@ \section1 Synopsis -\badcode +\code [cmake] qt_generate_foreign_qml_types( source_target destination_qml_target ) - \endcode \versionlessCMakeCommandsNote qt6_generate_foreign_qml_types() @@ -33,7 +32,7 @@ types in the QML module \c destination_qml_target. This can be useful when one wants to create a library with optional QML integration, without depending directly on QML. -\badcode +\code // myclass.h #include @@ -46,7 +45,7 @@ class MyClass : public QObject }; \endcode -\badcode +\code [cmake] # CMakeLists.txt qt_add_library(mylib myclass.h ...) target_link_libraries(mylib PRIVATE Qt::Core Qt::QmlIntegration) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_import_qml_plugins.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_import_qml_plugins.qdoc index b8cef81bb3..ecac91e1c8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_import_qml_plugins.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_import_qml_plugins.qdoc @@ -8,7 +8,7 @@ \title qt_import_qml_plugins \keyword qt6_import_qml_plugins -\brief Ensures QML plugins needed by a target are imported for static builds. +\summary{Ensures QML plugins needed by a target are imported for static builds.} \cmakecommandsince 6.0 diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_target_qml_sources.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_target_qml_sources.qdoc index 3394894592..3bc80f4414 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_target_qml_sources.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cmake/qt_target_qml_sources.qdoc @@ -8,7 +8,7 @@ \title qt_target_qml_sources \keyword qt6_target_qml_sources -\brief Add qml files and resources to an existing QML module target. +\summary{Adds QML files and resources to an existing QML module target.} \cmakecommandsince 6.2 diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/data.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/data.qdoc index 19297160ce..cfbb64db1b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/data.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/data.qdoc @@ -261,7 +261,7 @@ its \l {QDateTime::}{date()} method, using the local time form of the date unless the UTC form of it coincides with the start of the next day, in which case the UTC form is used. -This slighly eccentric arrangement is a work-around for the fact that +This slightly eccentric arrangement is a work-around for the fact that JavaScript's construction of a \c Date object from a date-only string uses the UTC start of the day, but \c{new Date(y, m, d)} uses the local time start of the indicated date, as discussed in a note at the @@ -401,8 +401,8 @@ Enum classes are registered in QML as scoped and unscoped properties. The \c Ready value will be registered as \c Message.Status.Ready and \c Message.Ready . When using enum classes, there can be multiple enums using the same identifiers. -The unscoped registration will be overwriten by the last registered enum. For classes -that contain such name conficts it is possible to disable the unscoped registration by +The unscoped registration will be overwritten by the last registered enum. For classes +that contain such name conflicts it is possible to disable the unscoped registration by annotating your class with a special Q_CLASSINFO macro. Use the name \c RegisterEnumClassesUnscoped with the value \c false to prevent scoped enums from being merged into the same name space. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/definetypes.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/definetypes.qdoc index 10a9fd79cc..f5916e3a2f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -296,7 +296,7 @@ are instantiable from QML. not instantiable from QML, and you cannot declare QML properties with it. Using C++ properties of this type from QML will do the expected interface casts, though. -\li QML_UNCREATABLE(reason) combined with with QML_ELEMENT or QML_NAMED_ELEMENT +\li QML_UNCREATABLE(reason) combined with QML_ELEMENT or QML_NAMED_ELEMENT registers a named C++ type that is not instantiable but should be identifiable as a type to the QML type system. This is useful if a type's enums or attached properties should be accessible from QML but the type itself should not be diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/extending-tutorial.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/extending-tutorial.qdoc index 707c8d62b0..cdf05d9855 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/extending-tutorial.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/extending-tutorial.qdoc @@ -435,7 +435,7 @@ When building this example on Windows or Linux, the \c Charts directory will be located at the same level as the application that uses our new import module. This way, the QML engine will find our module as the default search path for QML imports includes the directory of the application executable. On \macos, the -plugin binary is copied to \c Contents/PlugIns in the the application bundle. +plugin binary is copied to \c Contents/PlugIns in the application bundle. With qmake, this path is set in \c {chapter6-plugins/app.pro}: \quotefromfile tutorials/extending-qml/chapter6-plugins/app.pro diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc index e148f54415..db73d2ee71 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/cppintegration/interactqmlfromcpp.qdoc @@ -115,7 +115,7 @@ One option is to register the interface as a singleton by adding the \l QML_SINGLETON macro to the interface, exposing it to all components. Following that, the interface becomes available via a simple import statement: -\code +\qml import my.company.module Item { @@ -123,7 +123,7 @@ Item { CppInterface.foo(); } } -\endcode +\endqml Use this approach if you need your interface in more places than the root component, as simply passing down an object would require explicitly passing it on to other diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/deployment.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/deployment.qdoc index a9eed6d578..cd9c8f08ae 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/deployment.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/deployment.qdoc @@ -231,7 +231,7 @@ only be used from C++ code to refer to resource files. \note When using qmake, you need to manually place your files in the resource system. See the \l{qmake Manual} and the \l{The Qt Resource System}{general -documentation} on the resouce system for how to do this. It's advisable to +documentation} on the resource system for how to do this. It's advisable to still follow the path naming convention outlined above. \section1 Related Information diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/javascript/imports.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/javascript/imports.qdoc index a0eb5c1406..7102f6a157 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/javascript/imports.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/javascript/imports.qdoc @@ -134,9 +134,9 @@ In particular, this may be useful in order to access functionality provided via a singleton type; see QML_SINGLETON for more information. Your JavaScript resource by default can access all imports of the component -that imports the resource. It does not have access to the componpents's imports +that imports the resource. It does not have access to the components's imports if it is declared as a stateless library (using \c{.pragma library}) or contains -an explicit \c{.import} statment. +an explicit \c{.import} statement. \note The .import syntax doesn't work for scripts used in \l {WorkerScript} diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/profiling.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/profiling.qdoc index 0ba89bfb5e..5d68f85ea4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/profiling.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/profiling.qdoc @@ -10,7 +10,7 @@ \section1 QML Profiler With the \l{\QC: Profiling QML Applications}{QML Profiler} you can analyze -your QML code for performance performance issues. The profiler is part of +your QML code for performance issues. The profiler is part of both \QC and \QDS. The best way to use it is to utilize \{QC}'s or \{QDS}'s defaults for build and run settings. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/scope.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/scope.qdoc index 5bb32a2c42..0417031cda 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/scope.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/scope.qdoc @@ -265,6 +265,137 @@ Text { } \endqml +\section2 Do Not Reference Root Object \c id From Other QML Components + +Avoid accessing the \c id of a root object from outside the QML file where +it is defined. \c id values are only valid within the same document, and relying +on them from other components breaks encapsulation and component boundaries. + +\code +// Main.qml +import QtQuick + +Item { + id: root + CustomItem { } +} + +// CustomItem.qml +import QtQuick + +Rectangle { + width: root.width + height: root.height / 2 + color: "red" +} +\endcode + +In the above example, \c root is not declared in \c CustomItem.qml. The code still works +because \c CustomItem is instantiated in a context where \c root exists, which is +permitted by QML’s dynamic scoping rules. However, this creates an implicit dependency on +the outer scope, making the component unreliable and harder to reuse when it is loaded +dynamically or used in a different context. This pattern can also lead to subtle bugs. +For example, if another object in the scope chain uses the same \c id, it may shadow the +expected one, resulting in unexpected behaviour. + +To make components reliable and reusable, access external state only through +explicit properties, property aliases, or signals. Avoid relying on outer-scope +\c id values. + +The following example replaces dynamic scoping with an explicit component API. + +\code +// Main.qml +import QtQuick +import QtQuick.Window + +Window { + id: root + width: 400 + height: 300 + visible: true + + CustomItem { + width: root.width + height: root.height + boxColor: "blue" + } +} + +// CustomItem.qml +import QtQuick + +Item { + id: container + property alias boxColor: rect.color + + Rectangle { + id: rect + width: container.width + height: container.height / 2 + color: "red" + } +} +\endcode + +Apply the same rule to all internal objects declared with an \c id inside a component. + +\code +// MyItem.qml +import QtQuick + +Item { + Item { + id: internalCounter + property int count: 5 + } +} + +// main.qml +import QtQuick + +MyItem { + Component.onCompleted: { + console.log(internalCounter.count); + } +} +\endcode + +In the above example, accessing the \c id \c internalCounter from another QML file causes a +\c ReferenceError because an \c id is document-local and cannot be accessed outside the +QML file in which it is declared. + +If an internal object needs to expose state or behaviour, expose it explicitly through properties, +property aliases, or signals. + +The following example exposes the internal state through an explicit component API instead of +relying on direct access to a document-local \c id. + +\code +// MyItem.qml +import QtQuick + +Item { + Item { + id: internalCounter + property int count: 5 + } + property alias internalCount: internalCounter.count +} + +// main.qml +import QtQuick + +MyItem { + Component.onCompleted: { + console.log(internalCount); + } +} +\endcode + +This helps maintain clear component boundaries and supports better scalability +and testability. + \section1 Overridden Properties QML permits property names defined in an object declaration to be overridden by properties diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/structure.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/structure.qdoc index ab3797a434..087fcb99b5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/structure.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/documents/structure.qdoc @@ -25,7 +25,7 @@ QML documents are always encoded in UTF-8 format. Pragmas are instructions to the QML engine itself that can be used to specify certain characteristics of objects in the current file or to modify how the -engine interprets code. The following pragmas are exaplained in details below. +engine interprets code. The following pragmas are explained in details below. \table \header @@ -495,13 +495,13 @@ signals and it can have signal handlers. A QML file must only contain \b {a single root object definition}. The following is invalid and will generate an error: -\badcode +\qml // MyQmlFile.qml import QtQuick 2.0 Rectangle { width: 200; height: 200; color: "red" } Rectangle { width: 200; height: 200; color: "blue" } // invalid! -\endcode +\endqml This is because a .qml file automatically defines a QML type, which encapsulates a \e single QML object definition. This is discussed further in \l{qtqml-documents-definetypes.html}{Documents as QML object type definitions}. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/cmake-module-integration.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/cmake-module-integration.qdoc index 7b4e836e9b..cc2da45f8c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/cmake-module-integration.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/cmake-module-integration.qdoc @@ -6,35 +6,222 @@ \title Tying it all together with CMake \brief Building QML modules with CMake -When creating QML modules, CMake provides the infrastructure to properly register -QML types, generate necessary metadata, and ensure your module is correctly packaged -and deployable. This page outlines the recommended workflow. +The \l{qt_add_qml_module} CMake command is the recommended way to define QML +modules. It automates type registration, resource embedding, plugin creation, +and tooling integration. This page explains the concepts behind +\c{qt_add_qml_module} and walks through common usage patterns. -\section1 Using qt_add_qml_module +For the full reference of all options, see \l{qt_add_qml_module}. -The \l{qt_add_qml_module} command is the standard and recommended way to create QML modules. -It handles all the complex details of QML module creation: +\section1 Key Concepts -\code -qt_add_qml_module(my_qml_module - URI MyModule +Before looking at examples, it helps to understand what \c{qt_add_qml_module} +actually creates: + +\list +\li A \e{backing target} — a library (or executable) containing the module's + C++ code, compiled QML files, and resources. This is the first argument to + \c{qt_add_qml_module} and is the target you link against. It can either + be an existing target (created earlier with \c{qt_add_executable} or + \c{qt_add_library}) or \c{qt_add_qml_module} will create it for you. +\li A \e{plugin target} — a small library that allows the QML engine to load + the module dynamically at run time. It is created automatically and is + separate from the backing target. When an executable links directly to the + backing target, the plugin is not loaded at run time. The plugin can be + omitted entirely using \l{qt_add_qml_module}{NO_PLUGIN}. +\li A \e{qmldir file} — a module metadata file that tells the QML engine what + types the module provides and where to find them. It is generated + automatically. +\li A \e{typeinfo file} (\c{.qmltypes}) — machine-readable type information + used by QML tooling (\l{qmlcachegen}, \l{qmllint}, \l{\QMLLS}{qmlls}, + Qt Creator). Also generated automatically. +\endlist + +\section1 Directory Structure + +The source directory structure should mirror the module's URI. The URI is +converted to a path by replacing dots with forward slashes. For example, a +module with URI \c{MyCompany.Controls} should live in: + +\badcode +src/MyCompany/Controls/CMakeLists.txt +src/MyCompany/Controls/Button.qml +src/MyCompany/Controls/mywidget.cpp +src/MyCompany/Controls/mywidget.h +\endcode + +This convention allows the QML engine and tooling to find modules without +additional configuration. If your directory structure doesn't match the URI, +you'll need to set \l{OUTPUT_DIRECTORY} so that the build output lands in a +path matching the URI, and \l{IMPORT_PATH} so that the QML engine and tooling +can locate the module. + +\section1 Common Patterns + +\section2 QML-only Application + +The simplest case: an executable with only QML files and no C++ types. + +\badcode +cmake_minimum_required(VERSION 3.16) +project(myapp LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Quick) +qt_standard_project_setup(REQUIRES 6.8) + +qt_add_executable(myapp) +qt_add_qml_module(myapp + URI MyApp QML_FILES - MyType.qml - AnotherType.qml + Main.qml + Button.qml + RESOURCES + images/logo.png +) + +target_link_libraries(myapp PRIVATE Qt6::Quick) +\endcode + +Because the backing target is an executable, no plugin is created. The QML +files are compiled and embedded as resources. The \c{RESOURCES} keyword adds +non-QML files (images, fonts, etc.) to the same resource hierarchy. + +\section2 Application with C++ Types + +An application that exposes C++ types to QML: + +\badcode +qt_add_executable(myapp) +qt_add_qml_module(myapp + URI MyApp + QML_FILES + Main.qml SOURCES - mytype.cpp mytype.h + backend.cpp backend.h ) \endcode -This single command: -\list -\li Creates the module target -\li Registers QML types from C++ and QML files -\li Generates a qmldir file -\li Handles type registration -\li Sets up proper import paths -\li Enables QML tooling support (\l{qmllint}, \l{\QMLLS}{qmlls}, etc.) -\endlist +The C++ types in \c{backend.h} must be annotated with \l{QML_ELEMENT} (or +similar macros like \l{QML_NAMED_ELEMENT}) and must use the \l{Q_OBJECT} +or \l{Q_GADGET} macro. Type registration happens automatically via +\c{AUTOMOC}. See \l{Registering C++ Types with the QML Type System} for +a full overview of the available registration macros. + +\section2 Reusable Library Module + +A QML module packaged as a library that other projects or modules can import: + +\badcode +# In src/MyCompany/Controls/CMakeLists.txt +qt_add_qml_module(mycontrols + URI MyCompany.Controls + QML_FILES + Button.qml + Slider.qml + SOURCES + theme.cpp theme.h +) +\endcode + +This creates two targets: \c{mycontrols} (the backing library) and +\c{mycontrolsplugin} (the plugin). Other modules import it with +\c{import MyCompany.Controls} in QML. Applications that link to +\c{mycontrols} directly don't need to load the plugin at run time. + +\section2 Application Using a Library Module + +Using the \c{mycontrols} module from the \l{Reusable Library Module} example +above: + +\badcode +# In the application's CMakeLists.txt +qt_add_executable(myapp) +qt_add_qml_module(myapp + URI MyApp + QML_FILES Main.qml + DEPENDENCIES TARGET mycontrols +) + +target_link_libraries(myapp PRIVATE mycontrols) +\endcode + +The \l{qt_add_qml_module}{DEPENDENCIES} line ensures that QML tooling +(\l{qmlcachegen}, \l{qmllint}, \l{\QMLLS}{qmlls}) can find the types provided by +\c{MyCompany.Controls}. + +\badcode +// In Main.qml +import MyCompany.Controls + +Button { text: "Click me" } +\endcode + +\section2 Module with Singletons + +QML files providing singleton types need a source file property set +\e{before} the \c{qt_add_qml_module} call: + +\badcode +set_source_files_properties(Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) + +qt_add_qml_module(mymodule + URI MyModule + QML_FILES + Theme.qml + Main.qml +) +\endcode + +The QML file must also contain \c{pragma Singleton}. Both the CMake property +and the QML pragma are required. + +\section2 Module with Custom Plugin + +When you need to perform custom initialization (for example, registering an +image provider), you can provide your own \l{QQmlEngineExtensionPlugin} +subclass. + +\badcode +# In CMakeLists.txt +qt_add_qml_module(mymodule + URI MyModule + NO_GENERATE_PLUGIN_SOURCE + NO_PLUGIN_OPTIONAL + CLASS_NAME MyModulePlugin + QML_FILES + Main.qml + SOURCES + myimageprovider.cpp myimageprovider.h +) + +# Add the custom plugin source to the plugin target +target_sources(mymoduleplugin PRIVATE plugin.cpp) +\endcode + +\badcode +// In plugin.cpp +#include +#include "myimageprovider.h" + +class MyModulePlugin : public QQmlEngineExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid) +public: + void initializeEngine(QQmlEngine *engine, const char *uri) override + { + engine->addImageProvider("myprovider", new MyImageProvider); + } +}; + +#include "plugin.moc" +\endcode + +\l{qt_add_qml_module}{NO_GENERATE_PLUGIN_SOURCE} tells the build system not to +generate the default plugin source. \l{qt_add_qml_module}{CLASS_NAME} must +match the class name in your implementation. +\l{qt_add_qml_module}{NO_PLUGIN_OPTIONAL} ensures the plugin is always loaded, +since it contains initialization logic that would be skipped otherwise. \section1 Adding Further QML Files @@ -48,12 +235,13 @@ qt_target_qml_sources(my_qml_module ) \endcode -This can be done based on platform, configuration or other factors. +This is useful for conditionally including files based on platform or +configuration. \section1 Detailed CMake Reference For complete details on all CMake commands, properties, variables, and policies, see \l{CMake Integration for QML}. -\sa {QML Modules}, {qt_add_qml_module} +\sa {QML Modules}, {qt_add_qml_module}, {Writing QML Modules} */ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc index 0c45987dab..8188c26ebb 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/qmldir.qdoc @@ -320,7 +320,7 @@ A module can offer multiple major versions but the clients have access to one major version only at a time. For example, importing \c{MyExampleModule 2.0} provides access to that major version only and not the previous major version. Although you can organize the artifacts that belong -to different major versions under a sigle directory and a \c qmldir file, it is +to different major versions under a single directory and a \c qmldir file, it is recommended to use different directories for each major version. If you choose to go with the earlier approach (one directory and a \c qmldir file), try to use the version suffix for the file names. For example, artifacts diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/topic.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/topic.qdoc index 5d3ac27ed4..e6f687088c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/topic.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/modules/topic.qdoc @@ -108,7 +108,7 @@ QML extension module developer may wish to implement some types in a C++ plugin (as opposed to defining them via QML documents) to achieve better performance or for greater flexibility. -Every C++ plugin for QML has an initialiatization function which is called by +Every C++ plugin for QML has an initialization function which is called by the QML engine when it loads the plugin. This initialization function must register any types that the plugin provides, but must not do anything else (for example, instantiating QObjects is not allowed). diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/imports.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/imports.qdoc index 6bc7e99d02..1506d86eef 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/imports.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/imports.qdoc @@ -67,7 +67,7 @@ An example of an unqualified import with version would be \qml import QtQuick 2.10 \endqml -In that case, any types defined in QtQuick 2.11 and higher or in any higher major +In that case, any types defined in Qt Quick 2.11 and higher or in any higher major version, like 6.0, would not be available to the file. An example of a qualified module import is as follows: diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc index 074c662d87..e71647e632 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/objectattributes.qdoc @@ -570,41 +570,79 @@ another object's definition without declaring it as a value for a particular property. Declaring a property with the optional \c default keyword marks it as the -default property. For example, say there is a file MyLabel.qml with a default -property \c someText: +default property. For example, say there is a file Framer.qml with a default +property \c focusItem: \qml -// MyLabel.qml +// Framer.qml import QtQuick -Text { - default property var someText - - text: `Hello, ${someText.text}` +Row { + default property Item focusItem + property Item leftItem: Rectangle { + width: 10 + height: parent.height + color: "red" + } + property Item rightItem: Rectangle { + width: 10 + height: parent.height + color: "blue" + } + children: [leftItem, focusItem, rightItem] } \endqml -The \c someText value could be assigned to in a \c MyLabel object definition, -like this: +The \c focusItem value could be assigned to in a \c Framer object +definition, like this: \qml -MyLabel { - Text { text: "world!" } +Framer { + Text { text: "Hello, world!" } } \endqml This has exactly the same effect as the following: \qml -MyLabel { - someText: Text { text: "world!" } +Framer { + focusItem: Text { text: "Hello, world!" } } \endqml -However, since the \c someText property has been marked as the default +However, since the \c focusItem property has been marked as the default property, it is not necessary to explicitly assign the \l Text object to this property. +While a property of any type can be marked as a \c default property, +it is generally only helpful to mark properties of type \c var, of \l{QML +Object Types}{object type}, and their respective \l{QML Sequence +Types}{sequence types}: As only object instances are +assigned to the default property, there is no benefit in QML to having for +example a \c default string property. + +Consider the following TextHolder type: + +\qml * +// TextHolder.qml +Item { + property default string mytext +} +\endqml +By itself, this is fine. However, one cannot assign a string literal to +\c mytext without explicitly mentioning the property name: +\qml +TextHolder { + /* The following would be a syntax error, and will not assign + to the mytext property: + "some text" + + The line below is the only way to assign the value: + \1/ + mytext: "some text" +} +\endqml + You will notice that child objects can be added to any \l {Item}-based type without explicitly adding them to the \l {Item::children}{children} property. This is because the default property of \l Item is its \c data property, and diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/signals.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/signals.qdoc index 9914eba029..0462469531 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/signals.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/syntax/signals.qdoc @@ -226,7 +226,7 @@ provided by the signal. Usage of the \c arguments special object should generally be avoided in favor of the usage of named parameters, which are more explicit and -work consistently indepedently of the usage of an arrow or a non-arrow +work consistently independently of the usage of an arrow or a non-arrow function. \section2 Using the Connections type diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/typesystem/topic.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/typesystem/topic.qdoc index 20634991df..4a932e83b9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/typesystem/topic.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllanguageref/typesystem/topic.qdoc @@ -56,7 +56,7 @@ for in-depth information about sequence types. Enumerations are treated as special properties of types in QML. -See the the documentation about \l{QML Enumerations} for in-depth +See the documentation about \l{QML Enumerations} for in-depth information about enumerations. \section1 Singleton Types diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/comma.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/comma.qdoc index 9cfe4092e3..8ececd293b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/comma.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/comma.qdoc @@ -69,7 +69,7 @@ approaches: \endqml \li If changing the function signature is undesirable for API reasons, consider replacing the function with a Q_PROPERTY instead, so that change - notifications for it can be emitted when the depenency is modified in C++: + notifications for it can be emitted when the dependency is modified in C++: \code void Config::setFontSize(int fontSize) { if (m_fontSize == fontSize) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/missing-property.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/missing-property.qdoc index ade8615ce9..baaf540570 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/missing-property.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/missing-property.qdoc @@ -84,13 +84,23 @@ Item { \section2 What happened? You accessed a member in a field member expression that can't be found by QML tooling. - A field member expression is an expression of the form \c {someId.someProperty}. +The member might not exist at all, for example when you made a typo. Or it might exist +at runtime (because it exists on a specific sub-type that is always present), but not +on the statically declared type. + \section2 Why is this bad? -The QML tooling can't find this member, and the QML engine probably can't either. +The QML tooling can't find this member, meaning that features like go-to-definition and +autocomplete won't work. +At runtime, there might be an error or you might get an incorrect result if the member +really does not exist. +There won't be an error if the member actually always exist at runtime, however in that +case there can still be a performance cost due to missed optimizations. \section2 Example + +\section3 Property does not actually exist \qml import QtQuick @@ -110,8 +120,57 @@ Item { property int myInt property int myInt2: 1 + self.myInt } - \endqml +\section3 Property exists, but type is not precise enough +\qml +import QtQuick +import QtQuick.Controls.Basic + +Item { + component Message : Item { + required property string sender + required property string text + } + ListView { + id: messageView + delegate: Message {} + } + + Button { + text: "Reply to %1".arg(messageView.currentItem.sender) // not ok + } +} +\endqml + +To fix this warning, \l{Type annotations and assertions}{cast the object} you +read from to the more specific type: + +\qml +import QtQuick +import QtQuick.Controls.Basic + +Item { + component Message : Item { + required property string sender + required property string text + } + ListView { + id: messageView + delegate: Message {} + } + + Button { + text: "Reply to %1".arg((messageView.currentItem as Message).sender) // now ok + } +} +\endqml + +In the example, the declared type of \c{messageView}'s +\l{ListView::}{currentItem} property is \l Item, which has no \c sender +property: \c sender is defined only in \c Message. Here, we know however that +\c currentItem always contains a \c Message, so we can cast \c currentItem to +\c Message using a \l{Type annotations and assertions}{type assertion}. + */ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/unresolved-type.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/unresolved-type.qdoc index ef64e8c43e..f4668a23a9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/unresolved-type.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmllint/unresolved-type.qdoc @@ -39,7 +39,7 @@ The type can't be found by QML tooling, and most likely not by the QML engine. \qml import QtQuick -Itme { ... } +Item { ... } \endqml To fix this warning, correct the typo: \qml @@ -116,7 +116,7 @@ in \c SomeModule, then you need to state these C++ dependencies in the QML modul To achieve that, \l{qt-add-qml-module.html#declaring-module-dependencies}{add the dependencies} to the QML module definition. This can be done with \c DEPENDENCIES, for example: -\code +\code [cmake] qt_add_qml_module( ... DEPENDENCIES diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmlsingletons.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmlsingletons.qdoc index 6c3205291a..f684949a2b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmlsingletons.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/qmlsingletons.qdoc @@ -31,13 +31,13 @@ When using CMake, the qmldir is automatically created by \l{qt_add_qml_module}. To indicate that the QML file should be turned into a singleton, you need to set the \c{QT_QML_SINGLETON_TYPE} file property on it: -\code +\code [cmake] set_source_files_properties(MySingleton.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) \endcode You can pass multiple files at once to \c{set_source_files_properties}: -\code +\code [cmake] set(plain_qml_files MyItem1.qml MyItem2.qml @@ -61,7 +61,7 @@ qt_add_qml_module(myapp If you aren't using \c{qt_add_qml_module}, you'll need to manually create a \l{Module Definition qmldir Files}{qmldir file}. There, you'll need to mark your singletons accordingly: -\code +\code [text] module MyModule singleton MySingleton 1.0 MySingleton.qml singleton MyOtherSingleton 1.0 MyOtherSingleton.qml @@ -305,7 +305,9 @@ class RemoteBookModel : public QAbstractItemModel Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // ... }; +\endcode +\qml // bookbrowser.qml Row { ListView { @@ -316,7 +318,7 @@ Row { } } -\endcode +\endqml \section2 Passing initial state diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc index 2cabd34861..0ab9b0a90e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc @@ -99,7 +99,8 @@ controlled via a \l{Settings File}{settings file}.: \li \c{-n}, \c{--normalize} \li NormalizeOrder \li disabled/false - \li Reorders the attributes of the objects according to the QML Coding Guidelines. + \li Reorders and sorts the attributes of the objects according to the QML Coding Guidelines. + Incompatible with \c{--group-attributes-together}. In a command line invocation, the behavior can be enabled by passing the flag. @@ -125,7 +126,7 @@ controlled via a \l{Settings File}{settings file}.: \li \c{--objects-spacing} \li ObjectsSpacing \li disabled/false - \li Ensure spaces between objects (only works with \c{normalize}). + \li Ensure spaces between objects (only works with \c{normalize} or \c{group-attributes-together}). In a command line invocation, the behavior can be enabled by passing the flag. @@ -135,7 +136,7 @@ controlled via a \l{Settings File}{settings file}.: \li \c{--functions-spacing} \li FunctionsSpacing \li disabled/false - \li Ensure spaces between functions (only works with \c{normalize}). + \li Ensure spaces between functions (only works with \c{normalize} or \c{group-attributes-together}). In a command line invocation, the behavior can be enabled by passing the flag. @@ -145,7 +146,8 @@ controlled via a \l{Settings File}{settings file}.: \li \c{--group-attributes-together} \li GroupAttributesTogether \li disabled/false - \li Reorders and groups the attributes of the objects according to the QML Coding Guidelines. Implies \c{--normalize}. + \li Reorders but does not sort the attributes of the objects according to the QML Coding Guidelines. + Incompatible with \c{--normalize}. In a command line invocation, the behavior can be enabled by passing the flag. @@ -155,7 +157,7 @@ controlled via a \l{Settings File}{settings file}.: \li \c{--single-line-empty-objects} \li SingleLineEmptyObjects \li disabled/false - \li Write empty objects on a single line (only works with \c{normalize}). + \li Write empty objects on a single line (only works with \c{normalize} or \c{group-attributes-together}). In a command line invocation, the behavior can be enabled by passing the flag. @@ -186,8 +188,99 @@ qmlformat writes the formatted version of the file to stdout. If you wish to have your file updated in-place specify the \c{-i} flag. \section3 Grouping Properties, Functions, and Signals Together -With \c{-n} or \c{--normalize} flag, \e qmlformat groups all properties, functions, -and signals together, instead of retaining the order you specified. +With \c{-n} or \c{--normalize} flag, \e qmlformat groups and sorts all properties, functions, +and signals by name, instead of retaining the existing order. +For example: +\qml +import QtQuick + +QtObject { + signal s2() + property int h + function z() {} + property int w + function y() {} + id: asdf + signal s1() + + property Item myItem2: Item { + TextEdit {} + Rectangle {} + } + property Item myItem: Item { + Rectangle {} + TextEdit {} + } +} +\endqml + +will be formatted to: +\qml +import QtQuick + +QtObject { + id: asdf + + property int h + property Item myItem: Item { + Rectangle { + } + TextEdit { + } + } + property Item myItem2: Item { + TextEdit { + } + Rectangle { + } + } + property int w + + signal s1 + signal s2 + + function y() { + } + function z() { + } +} +\enqml + +To group the attributes without sorting by name, use \c{--group-attributes-together} instead. +This formats the previous snippet into: +\qml +import QtQuick + +QtObject { + id: asdf + + property int h + property int w + property Item myItem2: Item { + TextEdit { + } + Rectangle { + } + } + property Item myItem: Item { + Rectangle { + } + TextEdit { + } + } + + signal s2 + signal s1 + + function z() { + } + function y() { + } +} +\endqml + +Note that this option takes precedence over \c{--normalize}. + \section3 Settings File You can configure \e qmlformat by including a settings file \c{.qmlformat.ini} in your diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmllint.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmllint.qdoc index 3eb4875aa8..9839450664 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmllint.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmllint.qdoc @@ -10,7 +10,7 @@ \brief A tool for verifying the syntax of QML files and warning about anti-patterns. -\e qmllint is a tool shipped with Qt, that verifies the syntatic validity of +\e qmllint is a tool shipped with Qt, that verifies the syntactic validity of QML files. You can \l{Using qmllint with CMake}{integrate qmllint into your build system} for ease of use. It also warns about some QML anti-patterns. See \{Configuring qmllint warnings} for how to disable a specific warning type. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlls.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlls.qdoc index 4e3e635e66..5be91b70b2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlls.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtqml-tooling-qmlls.qdoc @@ -238,7 +238,6 @@ Despite covering many common QML features, \QMLLS is still in development with s yet to be supported: \list \li Suggesting autocompletions on invalid QML files. - \li Navigating to definitions of objects defined in C++. \li Autocompleting context properties \endlist diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtquickcompiler/qtqml-qml-script-compiler.qdoc b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtquickcompiler/qtqml-qml-script-compiler.qdoc index d31a7a8f10..e76bcde5a9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtquickcompiler/qtqml-qml-script-compiler.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qml/doc/src/tools/qtquickcompiler/qtqml-qml-script-compiler.qdoc @@ -86,7 +86,7 @@ To enable direct mode, you should consider the followings: ) \endcode - \li Link all the relavant private Qt modules instead of their public counterparts. + \li Link all the relevant private Qt modules instead of their public counterparts. \badcode find_package(Qt6 COMPONENTS QmlPrivate QuickPrivate) @@ -129,13 +129,13 @@ showing these results per binding or function, the statistics are also summarized per file, per module, and at the project level. This can give a quick overview of how well a project is being compiled. -To show statistics, invoke the \e{all_aotstats} cmake target. +To show statistics, invoke the \e{all_aotstats} CMake target. The collection of compilation statistics is enabled by default. In order to disable it, set the \l{QT_QML_GENERATE_AOTSTATS} CMake variable to OFF before the first call to \l{qt_add_qml_module}. \note These statistics are only available for modules registered through the -\l{qt_add_qml_module} cmake API +\l{qt_add_qml_module} CMake API */ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjsengine.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjsengine.h index ed978b0941..d4dfe42abe 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjsengine.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjsengine.h @@ -115,6 +115,10 @@ public: template inline T fromVariant(const QVariant &value) { + // MSVC doesn't like the constexpr chains and complains about "unreachable code" + QT_WARNING_PUSH + QT_WARNING_DISABLE_MSVC(4702) + if constexpr (std::is_same_v) return value; @@ -192,6 +196,8 @@ public: QMetaType::convert(value.metaType(), value.constData(), targetType, &t); return t; }(); + + QT_WARNING_POP } template diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjslist.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjslist.h index 5c9f748ed8..6e3bcc5723 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjslist.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsapi/qjslist.h @@ -343,7 +343,8 @@ private: }; // QJSListForInIterator must not require initialization so that we can jump over it with goto. -static_assert(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct QJSListForOfIterator { @@ -362,7 +363,8 @@ private: }; // QJSListForOfIterator must not require initialization so that we can jump over it with goto. -static_assert(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4arraydata_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4arraydata_p.h index 337812ce3b..7f2b0210a8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4arraydata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4arraydata_p.h @@ -94,7 +94,8 @@ DECLARE_HEAP_OBJECT(ArrayData, Base) { uint mappedIndex(uint index) const; }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct SimpleArrayData : public ArrayData { uint mappedIndex(uint index) const { index += offset; if (index >= values.alloc) index -= values.alloc; return index; } @@ -107,7 +108,8 @@ struct SimpleArrayData : public ArrayData { return attrs ? attrs[i] : Attr_Data; } }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct SparseArrayData : public ArrayData { void destroy() { diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4context_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4context_p.h index 45ee61de4e..64c04f473b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4context_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4context_p.h @@ -57,12 +57,13 @@ DECLARE_HEAP_OBJECT(ExecutionContext, Base) { quint8 padding_[4]; #endif }; -Q_STATIC_ASSERT(std::is_trivial_v); -Q_STATIC_ASSERT(sizeof(ExecutionContext) == sizeof(Base) + sizeof(ExecutionContextData) + QT_POINTER_SIZE); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); +static_assert(sizeof(ExecutionContext) == sizeof(Base) + sizeof(ExecutionContextData) + QT_POINTER_SIZE); -Q_STATIC_ASSERT(std::is_standard_layout::value); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, outer) == 0); -Q_STATIC_ASSERT(offsetof(ExecutionContextData, activation) == offsetof(ExecutionContextData, outer) + QT_POINTER_SIZE); +static_assert(std::is_standard_layout::value); +static_assert(offsetof(ExecutionContextData, outer) == 0); +static_assert(offsetof(ExecutionContextData, activation) == offsetof(ExecutionContextData, outer) + QT_POINTER_SIZE); #define CallContextMembers(class, Member) \ Member(class, Pointer, JavaScriptFunctionObject *, function) \ @@ -90,7 +91,8 @@ DECLARE_HEAP_OBJECT(CallContext, ExecutionContext) { locals.values[i] = Value::emptyValue(); } }; -Q_STATIC_ASSERT(std::is_trivial_v); +Q_STATIC_ASSERT(std::is_trivially_copyable_v); +Q_STATIC_ASSERT(std::is_trivially_default_constructible_v); Q_STATIC_ASSERT(std::is_standard_layout::value); Q_STATIC_ASSERT(offsetof(CallContextData, function) == 0); //### The following size check fails on Win8. With the ValueArray at the end of the diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4managed_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4managed_p.h index 6eaea9a1e9..ef0f922fa3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4managed_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4managed_p.h @@ -60,7 +60,8 @@ inline void qYouForgotTheQ_MANAGED_Macro(T1, T2) {} DataClass() = delete; \ Q_DISABLE_COPY(DataClass) \ V4_MANAGED_ITSELF(DataClass, superClass) \ - Q_STATIC_ASSERT(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); \ + static_assert(std::is_trivially_default_constructible_v); #define Q_MANAGED_TYPE(type) \ public: \ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4memberdata_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4memberdata_p.h index 89bd7ea000..24dd7d1fee 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4memberdata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4memberdata_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 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 +// Qt-Security score:significant reason:default #ifndef QV4MEMBERDATA_H #define QV4MEMBERDATA_H @@ -29,7 +30,8 @@ namespace Heap { DECLARE_HEAP_OBJECT(MemberData, Base) { DECLARE_MARKOBJECTS(MemberData) }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4regexp_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4regexp_p.h index 754c171dee..26b0899925 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4regexp_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4regexp_p.h @@ -74,7 +74,8 @@ struct RegExp : Base { int captureCount() const { return subPatternCount + 1; } }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4resolvedtypereference_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4resolvedtypereference_p.h index 087c77fddf..48d6496a2a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4resolvedtypereference_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4resolvedtypereference_p.h @@ -22,6 +22,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QCryptographicHash; @@ -41,9 +43,12 @@ public: QQmlType type() const { return m_type; } void setType(QQmlType type) { m_type = std::move(type); } - // The compilation unit is only stored for type references we need to create objects from. - // For everything else the QQmlType and QQmlPropertyCache are enough. - // Furthermore, if the reference points to a type from the same compilation unit, it also won't + // The compilation unit is stored for all composite type references that aren't + // self-references. It is needed both for object creation and for dependency + // checksum computation. Cyclic references between compilation units cannot + // occur because the type loader detects circular dependencies at resolution + // time, before buildTypeResolutionCaches runs. + // If the reference points to a type from the same compilation unit, it won't // hold a compilation unit since the object creator can just use the one it has already. QQmlRefPointer compilationUnit() { diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4string_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4string_p.h index 5dadc654fc..828e5664f0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4string_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4string_p.h @@ -114,7 +114,8 @@ struct Q_QML_EXPORT String : StringOrSymbol { private: static void append(const String *data, QChar *ch); }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct ComplexString : String { void init(String *l, String *n); @@ -127,7 +128,8 @@ struct ComplexString : String { }; int len; }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); inline int String::length() const { diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4value_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4value_p.h index f582fcfa71..faa379ac82 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4value_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4value_p.h @@ -230,8 +230,9 @@ struct Q_QML_EXPORT Value : public StaticValue template Value &operator=(const Scoped &t); }; -Q_STATIC_ASSERT(std::is_trivial_v); -Q_STATIC_ASSERT(sizeof(Value) == sizeof(StaticValue)); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); +static_assert(sizeof(Value) == sizeof(StaticValue)); template<> inline StaticValue &StaticValue::operator=(const Value &value) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4vtable_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4vtable_p.h index 21324cec97..b8bae7ba72 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4vtable_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/jsruntime/qv4vtable_p.h @@ -277,7 +277,8 @@ const QV4::VTable classname::static_vtbl = DEFINE_MANAGED_VTABLE_INT(classname, dptr->_checkIsInitialized(); \ return dptr; \ } \ - Q_STATIC_ASSERT(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); \ + static_assert(std::is_trivially_default_constructible_v); #define V4_PROTOTYPE(p) \ static QV4::Object *defaultPrototype(QV4::ExecutionEngine *e) \ diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4heap_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4heap_p.h index 9940c54777..46b53c1cea 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4heap_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4heap_p.h @@ -54,7 +54,8 @@ private: Base *ptr; }; typedef Pointer V4PointerCheck; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); struct Q_QML_EXPORT Base { void *operator new(size_t) = delete; @@ -127,13 +128,14 @@ struct Q_QML_EXPORT Base { Q_ALWAYS_INLINE void _setDestroyed() {} #endif }; -Q_STATIC_ASSERT(std::is_trivial_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_default_constructible_v); // This class needs to consist only of pointer sized members to allow // for a size/offset translation when cross-compiling between 32- and // 64-bit. -Q_STATIC_ASSERT(std::is_standard_layout::value); -Q_STATIC_ASSERT(offsetof(Base, internalClass) == 0); -Q_STATIC_ASSERT(sizeof(Base) == QT_POINTER_SIZE); +static_assert(std::is_standard_layout::value); +static_assert(offsetof(Base, internalClass) == 0); +static_assert(sizeof(Base) == QT_POINTER_SIZE); inline void Base::mark(QV4::MarkStack *markStack) @@ -218,7 +220,8 @@ private: QtSharedPointer::ExternalRefCountData *d; T *qObject; }; -Q_STATIC_ASSERT(std::is_trivial_v>); +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_trivially_default_constructible_v>); #endif } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4mm_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4mm_p.h index 1e27abc631..f82ce779b9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4mm_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/memory/qv4mm_p.h @@ -271,7 +271,8 @@ public: template inline typename ManagedType::Data *allocManaged(std::size_t size, Heap::InternalClass *ic) { - Q_STATIC_ASSERT(std::is_trivial_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_default_constructible_v); size = align(size); typename ManagedType::Data *d = static_cast(allocData(size)); d->internalClass.set(engine, ic); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljs.g b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljs.g index b966e9d2aa..8b08e0b636 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljs.g +++ b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljs.g @@ -1769,6 +1769,34 @@ QmlIdentifier: T_IDENTIFIER | ECMAContextualKeyword | QMLContextualKeyword; +--- error rules for QML Identifier, used to generate more sensible warnings + +--- enum is ECMA future reserved, but also part of QML +FaultyQmlIdentifierQML: T_ENUM | T_PRAGMA | QMLReservedWord; +QmlIdentifier: FaultyQmlIdentifierQML; +/. + case $rule_number: { + syntaxError(loc(1), + QString::fromLatin1("Reserved keyword \"%1\" cannot be used as a QML identifier.") + .arg(stringRef(1)) + ); + } break; +./ + + +FaultyQmlIdentifierECMA: ECMAKeyword | ECMABooleanLiteral | ECMANullLiteral; +QmlIdentifier: FaultyQmlIdentifierECMA; +/. + case $rule_number: { + syntaxError(loc(1), + QString::fromLatin1("ECMAScript keyword \"%1\" cannot be used as a QML identifier.") + .arg(stringRef(1)) + ); + } break; +./ +--- end error rules + + --- ECMA Identifiers (262 ES 7 12.1) --- since qlarl doesn't support "NOT" we can't define a set IdentifierName \ ECMAReservedWord --- introducing JsIdentifier as a workaround, diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsast_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsast_p.h index 1070023be0..0ad2f9b359 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsast_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsast_p.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsglobal_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsglobal_p.h index 1ca98b2041..51a20faa28 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsglobal_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/parser/qqmljsglobal_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 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 +// Qt-Security score:significant reason:default #ifndef QQMLJSGLOBAL_P_H #define QQMLJSGLOBAL_P_H diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/ftw/qhashedstring_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/ftw/qhashedstring_p.h index 668adf722f..f18bac60ea 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/ftw/qhashedstring_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/ftw/qhashedstring_p.h @@ -52,6 +52,9 @@ private: friend class QHashedStringRef; friend class QStringHashNode; + friend size_t qHash(const QHashedString &key, size_t seed = 0) + { return size_t{key.hash()} ^ seed; } + inline void computeHash() const; mutable quint32 m_hash = 0; }; @@ -100,6 +103,9 @@ public: private: friend class QHashedString; + friend size_t qHash(const QHashedStringRef &key, size_t seed = 0) + { return size_t{key.hash()} ^ seed; } + inline void computeHash() const; const QChar *m_data = nullptr; @@ -134,16 +140,6 @@ private: mutable quint32 m_hash = 0; }; -inline size_t qHash(const QHashedString &string) -{ - return uint(string.hash()); -} - -inline size_t qHash(const QHashedStringRef &string) -{ - return uint(string.hash()); -} - QHashedString::QHashedString() : QString() { diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.cpp index ddcbe11920..8b83e314a3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.cpp @@ -303,10 +303,12 @@ int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, Clears all stored type registrations, such as those produced with \l qmlRegisterType(). + \warning Do not call this function while a QQmlEngine exists or behavior will be undefined. - Any existing QQmlEngines must be deleted before calling this function. This function - only affects the application global cache. Delete the QQmlEngine to clear all cached - data relating to that engine. + Any existing QQmlEngines must be deleted before calling this function. + + This function only affects the application global cache. Delete the QQmlEngine to clear + all cached data relating to that engine. */ void qmlClearTypeRegistrations() // Declared in qqml.h { @@ -2276,9 +2278,7 @@ bool AOTCompiledContext::callQmlContextPropertyLookup(uint index, void **args, i return false; } -enum MatchScore { NoMatch, ExactMatch, }; - -static MatchScore resolveQObjectMethodOverload( +static void resolveQObjectMethodOverload( QV4::QObjectMethod *method, QV4::Lookup *lookup, int relativeMethodIndex) { Q_ASSERT(lookup->qobjectMethodLookup.method.get() == method->d()); @@ -2286,13 +2286,17 @@ static MatchScore resolveQObjectMethodOverload( const auto *d = method->d(); const int methodCount = d->methodCount; - if (relativeMethodIndex == -1 && methodCount == 1) { - // QML-declared signals do not have a meaningful method index and cannot be overloaded. - // They still show up as QObjectMethod rather than ArrowFunction. If they didn't, we - // wouldn't have to care. - Q_ASSERT(d->methods[0].metaMethod().methodType() == QMetaMethod::Signal); + if (methodCount == 1) { + // No need to resolve the overload if there's only one method. + // This can be: + // 1. A QML-declared signal where the relativeMethodIndex is -1. + // 2. A QML-declared function in an object compiled via qmltc where the + // relativeMethodIndex isn't actually the metaobject index. + // NB: QML-declared functions can't be overloaded, so there is no + // ambiguity here. + // 3. A C++-declared invokable. In that case this is an optimization. lookup->qobjectMethodLookup.propertyData = d->methods; - return ExactMatch; + return; } for (int i = 0, end = d->methodCount; i != end; ++i) { @@ -2301,10 +2305,10 @@ static MatchScore resolveQObjectMethodOverload( continue; lookup->qobjectMethodLookup.propertyData = d->methods + i; - return ExactMatch; + return; } - return NoMatch; + Q_UNREACHABLE(); } static bool tryEnsureMethodsCache(QV4::QObjectMethod *method, QObject *object) @@ -2336,10 +2340,8 @@ void AOTCompiledContext::initCallQmlContextPropertyLookup(uint index, int relati scope, lookup->contextGetter(scope.engine, thisObject)); if (auto *method = function->as()) { Q_ASSERT(lookup->call == QV4::Lookup::Call::ContextGetterScopeObjectMethod); - if (tryEnsureMethodsCache(method, qmlScopeObject)) { - const auto match = resolveQObjectMethodOverload(method, lookup, relativeMethodIndex); - Q_ASSERT(match == ExactMatch); - } + if (tryEnsureMethodsCache(method, qmlScopeObject)) + resolveQObjectMethodOverload(method, lookup, relativeMethodIndex); return; } @@ -2515,10 +2517,8 @@ void AOTCompiledContext::initCallObjectPropertyLookup( QV4::ScopedValue thisObject(scope, QV4::QObjectWrapper::wrap(scope.engine, object)); QV4::ScopedFunctionObject function(scope, lookup->getter(scope.engine, thisObject)); if (auto *method = function->as()) { - if (tryEnsureMethodsCache(method, object)) { - const auto match = resolveQObjectMethodOverload(method, lookup, relativeMethodIndex); - Q_ASSERT(match == ExactMatch); - } + if (tryEnsureMethodsCache(method, object)) + resolveQObjectMethodOverload(method, lookup, relativeMethodIndex); return; } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.h index 371a3783ed..571695b486 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqml.h @@ -12,7 +12,10 @@ #include #include #include +#include +#if !defined(QT_LEAN_HEADERS) || QT_LEAN_HEADERS < 3 #include +#endif #define QML_VERSION 0x020000 #define QML_VERSION_STR "2.0" diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlapplicationengine.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlapplicationengine.cpp index 285f4a44c6..3f6555aabf 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlapplicationengine.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlapplicationengine.cpp @@ -223,9 +223,9 @@ void QQmlApplicationEnginePrivate::updateTranslationDirectory(const QUrl &url) } \endcode - Unlike QQuickView, QQmlApplicationEngine does not automatically create a root - window. If you are using visual items from Qt Quick, you will need to place - them inside of a \l [QML] {Window}. + \note Unlike QQuickView, QQmlApplicationEngine does not automatically create a + root window. If you are using visual items from Qt Quick, you will need to + place them inside a \l [QML] {Window}. You can also use QCoreApplication with QQmlApplicationEngine, if you are not using any QML modules which require a QGuiApplication (such as \c QtQuick). diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlbuiltinfunctions.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlbuiltinfunctions.cpp index 9fd3a5188b..a938b25e68 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -142,7 +142,7 @@ The following functions are also on the Qt object. */ /*! - \qmlproperty object Qt::platform + \qmlproperty var Qt::platform \since 5.1 The \c platform object provides info about the underlying platform. @@ -217,7 +217,7 @@ The following functions are also on the Qt object. */ /*! - \qmlproperty object Qt::styleHints + \qmlproperty QtObject Qt::styleHints \since 5.5 The \c styleHints object provides platform-specific style hints and settings. @@ -231,7 +231,7 @@ The following functions are also on the Qt object. */ /*! -\qmlmethod object Qt::include(string url, jsobject callback) +\qmlmethod var Qt::include(string url, jsobject callback) \deprecated This method should not be used. Use ECMAScript modules, and the native @@ -455,7 +455,7 @@ QSizeF QtObject::size(double w, double h) const } /*! - \qmlmethod font Qt::font(object fontSpecifier) + \qmlmethod font Qt::font(var fontSpecifier) Returns a font with the properties specified in the \a fontSpecifier object or the nearest matching font. The \a fontSpecifier object should contain @@ -1418,7 +1418,7 @@ void QtObject::exit(int retCode) const } /*! -\qmlmethod object Qt::createQmlObject(string qml, object parent, url url) +\qmlmethod QtObject Qt::createQmlObject(string qml, QtObject parent, url url) Compiles the given \a qml string into a component and then returns a new object created from that component. The new object will have the specified \a parent. Returns \c null if there was diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlcomponent.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlcomponent.cpp index a3980affdd..749408bb75 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlcomponent.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlcomponent.cpp @@ -1758,7 +1758,7 @@ static void QQmlComponent_setQmlParent(QObject *me, QObject *parent) } /*! - \qmlmethod QtObject Component::createObject(QtObject parent, object properties) + \qmlmethod QtObject Component::createObject(QtObject parent, var properties) Creates and returns an object instance of this component that will have the given \a parent and \a properties. The \a properties argument is optional. @@ -1985,7 +1985,7 @@ QObject *QQmlComponent::createObject(QObject *parent, const QVariantMap &propert } /*! - \qmlmethod object Component::incubateObject(QtObject parent, object properties, enumeration mode) + \qmlmethod var Component::incubateObject(QtObject parent, var properties, enumeration mode) Creates an incubator for an instance of this component. Incubators allow new component instances to be instantiated asynchronously and do not cause freezes in the UI. diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlengine.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlengine.cpp index cee8f8abd0..79f4c7d8c1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlengine.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlengine.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #if QT_CONFIG(qml_network) #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.cpp index 4408f41f09..b6e5ec9ec8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.cpp @@ -34,6 +34,10 @@ static char authority_downloads_documents[] = "com.android.providers.downloads.d static char authority_media_documents[] = "com.android.providers.media.documents"; #endif +#if defined(Q_OS_WASM) +static char weblocalfile_string[] = "weblocalfile"; +#endif + #if QT_DEPRECATED_SINCE(6, 11) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED @@ -512,6 +516,11 @@ bool QQmlFile::isSynchronous(const QUrl &url) return true; #endif +#if defined(Q_OS_WASM) + } else if (scheme.length() == 12 && 0 == scheme.compare(QLatin1String(weblocalfile_string), Qt::CaseInsensitive)) { + return true; +#endif + } else { return false; } @@ -560,6 +569,14 @@ bool QQmlFile::isSynchronous(const QString &url) } #endif +#if defined(Q_OS_WASM) + else if (f == QLatin1Char('w') || f == QLatin1Char('W')) { + return url.length() >= 14 /* weblocalfile:/ */ && + url.startsWith(QLatin1String(weblocalfile_string), Qt::CaseInsensitive) && + url[12] == QLatin1Char(':') && url[13] == QLatin1Char('/'); + } +#endif + return false; } @@ -604,6 +621,12 @@ bool QQmlFile::isLocalFile(const QUrl &url) return hasLocalContentAuthority(url); #endif +#if defined(Q_OS_WASM) + if (scheme.length() == 12 + && scheme.startsWith(QLatin1String(weblocalfile_string), Qt::CaseInsensitive)) + return true; +#endif + return false; } @@ -693,6 +716,11 @@ bool QQmlFile::isLocalFile(const QString &url) case 'C': return hasScheme(url, content_string, strlen(content_string)) && hasLocalContentAuthority(url, strlen(content_string)); +#endif +#if defined(Q_OS_WASM) + case 'w': + case 'W': + return hasScheme(url, weblocalfile_string, strlen(weblocalfile_string)); #endif default: break; @@ -724,6 +752,12 @@ QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) return QString(); } #endif + +#if defined(Q_OS_WASM) + if (url.scheme().compare(QLatin1String("weblocalfile"), Qt::CaseInsensitive) == 0) + return url.toString(); +#endif + return url.toLocalFile(); } @@ -788,6 +822,11 @@ QString QQmlFile::urlToLocalFileOrQrc(const QString& url) return hasLocalContentAuthority(url, strlen(content_string)) ? url : QString(); #endif +#if defined(Q_OS_WASM) + if (hasScheme(url, weblocalfile_string, strlen(weblocalfile_string))) + return url; +#endif + return toLocalFile(url); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.h index 41ed56f5b8..53065e95b1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlfile.h @@ -107,7 +107,7 @@ private: #if QT_DEPRECATED_SINCE(6, 11) QQmlFilePrivate *d; #else - void *d = nullptr; + Q_DECL_UNUSED_MEMBER void *d = nullptr; #endif }; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlimport_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlimport_p.h index 3ece2d1052..260f4bd76e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlimport_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlimport_p.h @@ -20,6 +20,8 @@ #include #include +#include + // // W A R N I N G // ------------- @@ -38,7 +40,6 @@ class QDir; class QQmlImportNamespace; class QQmlTypeLoader; class QQmlTypeLoaderQmldirContent; -class QTypeRevision; class QQmlTypeLoader; const QLoggingCategory &lcQmlImport(); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlincubator.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlincubator.cpp index 4c31d93adb..811e388e29 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlincubator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlincubator.cpp @@ -51,7 +51,10 @@ void QQmlEnginePrivate::incubate( p->changeStatus(QQmlIncubator::Loading); - if (!watcher.hasRecursed()) { + if (!p->q) { + p->clear(); + inProgressCreations--; + } else if (!watcher.hasRecursed()) { QQmlInstantiationInterrupt i; p->incubate(i); } @@ -62,8 +65,12 @@ void QQmlEnginePrivate::incubate( p->vmeGuard.guard(p->creator.data()); p->changeStatus(QQmlIncubator::Loading); - if (incubationController) + if (!p->q) { + p->clear(); + inProgressCreations--; + } else if (incubationController) { incubationController->incubatingObjectCountChanged(incubatorCount); + } } } @@ -340,10 +347,12 @@ void QQmlIncubatorPrivate::incubate(QQmlInstantiationInterrupt &i) if (watcher.hasRecursed()) return; - if (errors.isEmpty()) + if (errors.isEmpty()) { progress = QQmlIncubatorPrivate::Completing; - else + } else { progress = QQmlIncubatorPrivate::Completed; + delete std::exchange(result, nullptr); + } changeStatus(calculateStatus()); @@ -372,6 +381,9 @@ finishIncubate: QExplicitlySharedDataPointer isWaiting = waitingOnMe; clear(); + if (!errors.isEmpty()) + delete std::exchange(result, nullptr); + if (isWaiting) { QRecursionWatcher watcher(isWaiting.data()); changeStatus(calculateStatus()); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlinfo.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlinfo.h index 873b941b8a..64172b7a06 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlinfo.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlinfo.h @@ -22,8 +22,11 @@ Q_QML_EXPORT QQmlInfo qmlInfo(const QObject *me); Q_QML_EXPORT QQmlInfo qmlInfo(const QObject *me, const QQmlError &error); Q_QML_EXPORT QQmlInfo qmlInfo(const QObject *me, const QList &errors); +Q_DECL_COLD_FUNCTION Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me); +Q_DECL_COLD_FUNCTION Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me, const QQmlError &error); +Q_DECL_COLD_FUNCTION Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me, const QList &errors); class QQmlInfoPrivate; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype.cpp index 8a0c029488..7ebe10c5e2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype.cpp @@ -308,10 +308,8 @@ void QQmlMetaType::qmlRemoveModuleRegistration(const QString &uri) if (!data.isValid()) return; // shutdown/deletion race. Not a problem. - if (!data->moduleTypeRegistrationFunctions.contains(uri)) + if (!data->moduleTypeRegistrationFunctions.remove(uri)) qFatal("Cannot remove multiple registrations for %s", qPrintable(uri)); - else - data->moduleTypeRegistrationFunctions.remove(uri); } bool QQmlMetaType::qmlRegisterModuleTypes(const QString &uri) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype_p.h index 67969fde45..62b6cba778 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmetatype_p.h @@ -22,6 +22,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QQmlTypeModule; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmoduleregistration.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmoduleregistration.cpp index 7599b197db..1607e36f73 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmoduleregistration.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlmoduleregistration.cpp @@ -4,7 +4,6 @@ #include #include -#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpluginimporter_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpluginimporter_p.h index d21af4adea..8408836713 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpluginimporter_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpluginimporter_p.h @@ -22,7 +22,7 @@ #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlprivate.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlprivate.h index 868c7a60f6..bf7107bad8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlprivate.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlprivate.h @@ -30,8 +30,11 @@ #include #include #include +#include #include +#if !defined(QT_LEAN_HEADERS) || QT_LEAN_HEADERS < 3 #include +#endif #include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlproperty.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlproperty.cpp index 8344ba056f..14cd619cb1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlproperty.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlproperty.cpp @@ -901,8 +901,11 @@ bool QQmlPropertyPrivate::removeBinding( { Q_ASSERT(o); - auto [target, targetIndex] = findAliasTarget(o, index); - return removeOldBinding(target, targetIndex, flags); + const auto [target, targetPropertyIndex] = findAliasTarget(o, index); + + // If the target property index is invalid, there is no property. The alias targets the "target" + // object itself. You can't have a binding on a bare object so there is nothing to do here. + return !targetPropertyIndex.isValid() || removeOldBinding(target, targetPropertyIndex, flags); } bool QQmlPropertyPrivate::removeBinding( diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache.cpp index bac01a7549..7c00cbf8da 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache.cpp @@ -963,6 +963,7 @@ template static QQmlPropertyData qQmlPropertyCacheCreate(const QMetaObject *metaObject, const T& propertyName) { Q_ASSERT(metaObject); + static_assert(std::is_same_v); QQmlPropertyData rv; @@ -994,7 +995,7 @@ static QQmlPropertyData qQmlPropertyCacheCreate(const QMetaObject *metaObject, c { const QMetaObject *cmo = metaObject; while (cmo) { - int idx = cmo->indexOfProperty(propertyName); + int idx = cmo->indexOfProperty(propertyName.data()); if (idx != -1) { QMetaProperty p = cmo->property(idx); if (p.isScriptable()) { @@ -1018,9 +1019,9 @@ static QQmlPropertyData qQmlPropertyCacheCreate(const QMetaObject *metaObject, c return rv; } -static inline const char *qQmlPropertyCacheToString(QLatin1String string) +static inline QByteArray qQmlPropertyCacheToString(QLatin1String string) { - return string.data(); + return string.toUtf8(); } static inline QByteArray qQmlPropertyCacheToString(QStringView string) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache_p.h index d1e64a259f..dbf2f2ddae 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycache_p.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycachecreator_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycachecreator_p.h index 9f79b3979e..57e2f08eb0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertycachecreator_p.h @@ -27,6 +27,7 @@ #include #include +#include #if QT_CONFIG(regularexpression) #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertydata_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertydata_p.h index 0897d0329b..4c6af2ceea 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertydata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertydata_p.h @@ -18,7 +18,7 @@ #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertytopropertybinding.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertytopropertybinding.cpp index 55e098691e..6c4d91936a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertytopropertybinding.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlpropertytopropertybinding.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -109,6 +110,27 @@ void QQmlPropertyToUnbindablePropertyBinding::update(QQmlPropertyData::WriteFlag captureProperty(sourceMetaObject, property); }); + QV4::ExecutionEngine *v4 = m_binding.engine->handle(); + Q_ASSERT(v4); + + const QMetaType targetMetaType = vtd.isValid() ? vtd.propType() : d->propType(); + const QMetaType valueMetaType = value.metaType(); + + if (valueMetaType != targetMetaType && targetMetaType != QMetaType::fromType()) { + // Coerce the value to the target type using JavaScript semantics. + // Don't use QQmlProperty's internal coercion. It's not the same. + QVariant coerced(targetMetaType); + QV4::Scope scope(v4); + QV4::ScopedValue jsValue( + scope, value.isValid() + ? v4->metaTypeToJS(valueMetaType, value.constData()) + : QV4::Encode::undefined()); + + // If the coercion fails, let writeValueProperty() do its thing after all. + if (QV4::ExecutionEngine::metaTypeFromJS(jsValue, targetMetaType, coerced.data())) + value = std::move(coerced); + } + QQmlPropertyPrivate::writeValueProperty(target, *d, vtd, value, {}, flags); setUpdatingFlag(false); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p.h index c36c1c1e62..8443f4f8c6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p.h @@ -25,7 +25,7 @@ #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p_p.h index 62360a260c..8774bb38cc 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltype_p_p.h @@ -29,6 +29,7 @@ #include #include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler.cpp index 02c49600d7..cdc4e40624 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler.cpp @@ -12,6 +12,8 @@ #include #include +#include + #define COMPILE_EXCEPTION(token, desc) \ { \ recordError((token)->location, desc); \ @@ -829,6 +831,170 @@ void QQmlComponentAndAliasResolver::resolveGeneralizedGroupPro m_propertyCaches->set(binding->value.objectIndex, m_propertyCaches->at(targetObjectIndex)); } +/*! + \internal + + Reorders the alias data within the linked list of \a obj so that + aliases targeting other aliases on the same object come after their + targets. This ensures that dependencies are resolved and appended to + the property cache first, which is necessary because the runtime + assumes that the property cache position matches the alias table index. + + Only considers same-object dependencies (where the alias's target id + resolves to \a objectIndex). Cross-object dependencies are handled by + the multi-pass resolution in resolveAliases(). +*/ +static bool sortAliasDependencies( + QQmlTypeCompiler *compiler, QmlIR::Object *obj, int objectIndex, + const QMap &idToObjectIndex, QQmlError *error) +{ + using AliasArray = QVarLengthArray; + + AliasArray ordered; + ordered.reserve(obj->aliasCount()); + + // if the default property is an alias, we need to later update the default property index + QmlIR::Alias *defaultPropertyAlias = nullptr; + qsizetype aliasCounter = 0; + + + // Collect aliases as nodes in a graph. Non-local ones are already ordered. + AliasArray nodes; + for (QmlIR::Alias *a = obj->firstAlias(); a; ++aliasCounter, a = a->next) { + if (obj->defaultPropertyIsAlias && aliasCounter == obj->indexOfDefaultPropertyOrAlias) { + defaultPropertyAlias = a; + } + const int targetObjIdx = idToObjectIndex.value(a->idIndex(), -1); + if (targetObjIdx == objectIndex) + nodes.append(a); + else + ordered.append(a); + } + + // Nothing to sort here. + if (nodes.isEmpty()) + return true; + + // Collect dependencies as edges between nodes + QVarLengthArray edges(nodes.size(), -1); + for (qsizetype i = 0, end = nodes.size(); i < end; ++i) { + const QStringView propValue = compiler->stringAt(nodes[i]->propertyNameIndex); + const int dotIdx = propValue.indexOf(QLatin1Char('.')); + const QStringView targetName = dotIdx != -1 ? propValue.left(dotIdx) : propValue; + if (targetName.isEmpty()) + continue; + + for (qsizetype j = 0; j < end; ++j) { + if (j != i && compiler->stringAt(nodes[j]->nameIndex()) == targetName) { + edges[i] = j; + break; + } + } + } + + // Simple DFS-based topological sort + for (qsizetype i = 0, end = nodes.size(); i < end; ++i) { + // Skip already inserted nodes + if (nodes[i] == nullptr) + continue; + + // Follow the dependency chain to find the root. + QVarLengthArray chain; + for (qsizetype j = edges[i]; j != -1 && nodes[j]; j = edges[j]) { + if (!chain.contains(j)) { + chain.append(j); + continue; + } + + *error = qQmlCompileError( + nodes[j]->location, + QQmlComponentAndAliasResolverBase::tr("Cyclic alias")); + return false; + } + + // Emit in reverse (dependency first) + for (qsizetype k = chain.size() - 1; k >= 0; --k) + ordered.append(std::exchange(nodes[chain[k]], nullptr)); + ordered.append(std::exchange(nodes[i], nullptr)); + } + + // Apply the ordering in the IR + obj->setFirstAlias(ordered[0]); + for (qsizetype i = 0, end = ordered.size() - 1; i < end; ++i) + ordered[i]->next = ordered[i + 1]; + ordered.last()->next = nullptr; + if (defaultPropertyAlias) { + auto it = std::find(ordered.constBegin(), ordered.constEnd(), defaultPropertyAlias); + obj->indexOfDefaultPropertyOrAlias = std::distance(ordered.constBegin(), it); + } + + return true; +} + +/*! + \internal + + Attempts to resolve a "deep alias" — an alias whose sub-property path + goes through an inline component binding or through another alias. + For example: \c{alias foo: target.groupProp.innerProp} + + Searches the target object's bindings and aliases for \a property, then + looks up \a subProperty on the bound/aliased object's property cache. + + On success, updates \a propIdx with the resolved value-type index and + returns \c true. +*/ +static bool resolveDeepAlias( + QQmlTypeCompiler *compiler, const QmlIR::Object *targetObject, + const QmlIR::Object &component, QStringView property, QStringView subProperty, + QQmlPropertyIndex &propIdx, const QQmlPropertyCacheVector *propertyCaches, + const QMap &idToObjectIndex, + const QSet &resolvedAliases) +{ + for (auto it = targetObject->bindingsBegin(), end = targetObject->bindingsEnd(); + it != end; ++it) { + auto binding = *it; + if (compiler->stringAt(binding.propertyNameIndex) != property) + continue; + const auto &cache = propertyCaches->at(binding.value.objectIndex); + if (!cache) + continue; + QQmlPropertyResolver resolver(cache); + const QQmlPropertyData *pd = resolver.property(subProperty.toString()); + if (!pd) + continue; + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex()); + return true; + } + + for (auto it = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); + it != end; ++it) { + auto innerAlias = *it; + if (compiler->stringAt(innerAlias.nameIndex()) != property) + continue; + // After resolution, idIndex() contains the runtime object id + // (set by setTargetObjectId), not the string table index. + int innerObjectIndex; + if (resolvedAliases.contains(&(*it))) + innerObjectIndex = objectForId(compiler, component, innerAlias.targetObjectId()); + else + innerObjectIndex = idToObjectIndex.value(innerAlias.idIndex(), -1); + if (innerObjectIndex == -1) + continue; + const auto &cache = propertyCaches->at(innerObjectIndex); + if (!cache) + continue; + QQmlPropertyResolver resolver(cache); + const QQmlPropertyData *pd = resolver.property(subProperty.toString()); + if (!pd) + continue; + propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex()); + return true; + } + + return false; +} + template<> typename QQmlComponentAndAliasResolver::AliasResolutionResult QQmlComponentAndAliasResolver::resolveAliasesInObject( @@ -842,10 +1008,16 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject( Q_UNUSED(component); - const QmlIR::Object * const obj = m_compiler->objectAt(objectIndex); + QmlIR::Object *obj = m_compiler->objectAt(objectIndex); if (!obj->aliasCount()) return AllAliasesResolved; + // Reorder aliases so that same-object dependencies come before their + // dependents. This ensures the property cache ordering matches the + // alias table, which the runtime relies on. + if (!sortAliasDependencies(m_compiler, obj, objectIndex, m_idToObjectIndex, error)) + return NoAliasResolved; + int aliasIndex = 0; int numSkippedAliases = 0; bool hasUnresolvedLocalAliases = false; @@ -959,18 +1131,11 @@ QQmlComponentAndAliasResolver::resolveAliasesInObject( // could be a deep alias bool isDeepAlias = subProperty.at(0).isLower(); if (isDeepAlias) { - isDeepAlias = false; - for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { - auto binding = *it; - if (m_compiler->stringAt(binding.propertyNameIndex) == property) { - resolver = QQmlPropertyResolver(m_propertyCaches->at(binding.value.objectIndex)); - const QQmlPropertyData *actualProperty = resolver.property(subProperty.toString()); - if (actualProperty) { - propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); - isDeepAlias = true; - } - } - } + isDeepAlias = resolveDeepAlias( + m_compiler, targetObject, component, + property, subProperty, + propIdx, m_propertyCaches, m_idToObjectIndex, + resolvedAliases); } if (!isDeepAlias) { *error = qQmlCompileError( diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler_p.h index 0c86441cb9..546c6f185a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypecompiler_p.h @@ -30,6 +30,7 @@ class QQmlEnginePrivate; class QQmlError; class QQmlTypeData; class QQmlImports; +class QTypeRevision; namespace QmlIR { struct Document; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata.cpp index ab11f6f502..6d3090e302 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata.cpp @@ -321,6 +321,7 @@ QQmlError QQmlTypeData::createTypeAndPropertyCaches( m_compiledData->typeNameCache = typeNameCache; m_compiledData->resolvedTypes = resolvedTypeCache; m_compiledData->inlineComponentData = m_inlineComponentData; + m_compiledData->qmlType = m_qmlType; QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; @@ -1100,7 +1101,7 @@ QQmlError QQmlTypeData::buildTypeResolutionCaches( } else { ref->setTypePropertyCache(compilationUnit->rootPropertyCache()); } - if (!resolvedType->selfReference && resolvedType->needsCreation) + if (!resolvedType->selfReference) ref->setCompilationUnit(compilationUnit); } else if (qmlType.isInlineComponentType()) { // Inline component diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata_p.h index 6667b301c3..d464b15d83 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypedata_p.h @@ -21,6 +21,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcCycle) diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypeloader_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypeloader_p.h index 1f29d52b86..7ef141950f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypeloader_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypeloader_p.h @@ -27,6 +27,8 @@ #include #include +#include + #include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypemodule_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypemodule_p.h index 54de1f8640..c2961e3407 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypemodule_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmltypemodule_p.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvaluetypewrapper.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvaluetypewrapper.cpp index 4e8187eaf3..12023566cf 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvaluetypewrapper.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvaluetypewrapper.cpp @@ -545,6 +545,8 @@ bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const } }); + Q_ALLOCA_DECLARE(void, rawPtr); + if (d()->isReference()) { QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Walloca-larger-than="); // for size = alignment = 0 @@ -552,7 +554,7 @@ bool QQmlValueTypeWrapper::write(QObject *target, int propertyIndex) const const size_t size = d()->metaType().sizeOf(); const size_t alignment = d()->metaType().alignOf(); size_t space = size + alignment - 1; - Q_ALLOCA_VAR(void, rawPtr, space); + Q_ALLOCA_ASSIGN(void, rawPtr, space); void *alignedPtr = std::align(alignment, size, rawPtr, space); Q_ASSERT(alignedPtr); d()->setGadgetPtr(alignedPtr); diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvmemetaobject.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvmemetaobject.cpp index 606a3cccd1..ee9f6d7f95 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvmemetaobject.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qml/qqmlvmemetaobject.cpp @@ -244,6 +244,8 @@ void QQmlVMEMetaObjectEndpoint::tryConnect() = QQmlMetaType::propertyCacheForType(pd->propType()); void *argv[1] = { &target }; QMetaObject::metacall(target, QMetaObject::ReadProperty, coreIndex, argv); + if (!target) + return; Q_ASSERT(newPropertyCache); pd = newPropertyCache->property(valueTypeIndex); } @@ -282,11 +284,22 @@ QQmlInterceptorMetaObject::~QQmlInterceptorMetaObject() } +static bool propertyIndicesConflict(QQmlPropertyIndex a, QQmlPropertyIndex b) +{ + if (a.coreIndex() != b.coreIndex()) + return false; + + if (!a.hasValueTypeIndex() || !b.hasValueTypeIndex()) + return true; + + return a.valueTypeIndex() == b.valueTypeIndex(); +} + void QQmlInterceptorMetaObject::registerInterceptor(QQmlPropertyIndex index, QQmlPropertyValueInterceptor *interceptor) { for (QQmlPropertyValueInterceptor *vi = interceptors; vi; vi = vi->m_next) { - if (Q_UNLIKELY(vi->m_propertyIndex.coreIndex() == index.coreIndex())) { - qWarning() << "Attempting to set another interceptor on " + if (Q_UNLIKELY(propertyIndicesConflict(vi->m_propertyIndex, index))) { + qWarning() << "Attempting to set another interceptor on" << object->metaObject()->className() << "property" << object->metaObject()->property(index.coreIndex()).name() << "- unsupported"; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmldirparser_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmldirparser_p.h index 91836d08a8..b9ccf9118b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmldirparser_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmldirparser_p.h @@ -119,10 +119,11 @@ public: QTypeRevision version; // invalid version is latest version, unless Flag::Auto Flags flags; - friend bool operator==(const Import &a, const Import &b) + friend bool comparesEqual(const Import &a, const Import &b) noexcept { return a.module == b.module && a.version == b.version && a.flags == b.flags; } + Q_DECLARE_EQUALITY_COMPARABLE(Import) }; QMultiHash components() const { return _components; } diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver.cpp b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver.cpp index ee74796e95..3db8b46a15 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver.cpp @@ -4,6 +4,9 @@ #include "qqmlimportresolver_p.h" +#include +#include + QT_BEGIN_NAMESPACE enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned }; diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver_p.h index bb8ace5f35..66dc239243 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qmldirparser/qqmlimportresolver_p.h @@ -18,9 +18,8 @@ #include -#include -#include -#include +#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/qqmltcobjectcreationhelper_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/qqmltcobjectcreationhelper_p.h index 2f9786cd88..1d1f8b1788 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/qqmltcobjectcreationhelper_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/qqmltcobjectcreationhelper_p.h @@ -18,7 +18,6 @@ #include #include -#include #include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h b/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h index 35a2a06590..48353f94c5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qml/qmltc/supportlibrary/qqmlcpptypehelpers_p.h @@ -23,7 +23,8 @@ */ template using passByConstRefOrValue = - std::conditional_t<((sizeof(T) > 3 * sizeof(void *)) || !std::is_trivial_v), const T &, + std::conditional_t<((sizeof(T) > 3 * sizeof(void *)) || !(std::is_trivially_copyable_v && std::is_trivially_default_constructible_v)), + const T &, T>; #endif // QQMLCPPTYPEHELPERS_P_H diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/CMakeLists.txt index 7d4304c8a2..ac8588cac3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/CMakeLists.txt @@ -18,7 +18,7 @@ qt_internal_add_module(QmlCompiler qqmljscompiler.cpp qqmljscompiler_p.h qqmljscompilerstats.cpp qqmljscompilerstats_p.h qqmljscompilerstatsreporter.cpp qqmljscompilerstatsreporter_p.h - qqmljscontextualtypes_p.h + qqmljscontextualtypes_p.h qqmljscontextualtypes.cpp qqmljsfunctioninitializer.cpp qqmljsfunctioninitializer_p.h qqmljsimporter.cpp qqmljsimporter_p.h qqmljsimportvisitor.cpp qqmljsimportvisitor_p.h diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc index 289f2a0a1f..ae62e2eef5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/qtqmlcompiler-index.qdoc @@ -35,9 +35,9 @@ holds the passes and can be called to analyze an element and its children. A pass is a check for a certain rule or condition evaluated on elements or properties. If the condition is met, the pass can warn the user of an - indentified issue in the code and maybe even suggest a fix. It is called a + identified issue in the code and maybe even suggest a fix. It is called a pass because the analysis performed on elements and properties happens by - running a collection of passes on them in succesion. Each pass should be + running a collection of passes on them in succession. Each pass should be responsible for identifying one specific issue only. Combining a set of passes can perform more complex analysis and, together, form a plugin. Element passes are defined by two main components, namely \c shouldRun() diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc index 78320984da..dc48319457 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/doc/src/qtqmlcompiler-module.qdoc @@ -3,7 +3,7 @@ /*! \module QtQmlCompiler - \modulestate Technology Preview + \preliminary \title Qt Qml Compiler C++ Classes \qtcmakepackage QmlCompiler diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qdeferredpointer_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qdeferredpointer_p.h index fedd859f64..ffaf172eb4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qdeferredpointer_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qdeferredpointer_p.h @@ -28,6 +28,9 @@ class QDeferredSharedPointer; template class QDeferredWeakPointer; +template +void resetFactoryImpl(QDeferredSharedPointer&, F&&); + template class QDeferredFactory { @@ -156,14 +159,10 @@ public: return (m_factory && m_factory->isValid()) ? m_factory.data() : nullptr; } - void resetFactory(const Factory& newFactory) - { - m_data.reset(); - *m_factory = newFactory; - } - private: friend class QDeferredWeakPointer; + template + friend void resetFactoryImpl(QDeferredSharedPointer&, F&&); void lazyLoad() const { diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsannotation.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsannotation.cpp index 76fc275be6..3a4be520af 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsannotation.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsannotation.cpp @@ -6,18 +6,16 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + bool QQmlJSAnnotation::isDeprecation() const { return name == QStringLiteral("Deprecated"); } QQQmlJSDeprecation QQmlJSAnnotation::deprecation() const { Q_ASSERT(isDeprecation()); QQQmlJSDeprecation deprecation; - if (bindings.contains(QStringLiteral("reason"))) { - - auto reason = bindings[QStringLiteral("reason")]; - - if (std::holds_alternative(reason)) { - deprecation.reason = std::get(reason); - } + if (const auto it = bindings.find(u"reason"_s); it != bindings.end()) { + if (auto v = get_if(&it.value())) + deprecation.reason = *v; } return deprecation; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscodegenerator.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscodegenerator.cpp index caca5a2eed..0666d9ca1d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscodegenerator.cpp @@ -4286,14 +4286,14 @@ QString QQmlJSCodeGenerator::convertStored( if (from == jsPrimitiveType) { - if (to == m_typeResolver->realType()) - return variable + u".toDouble()"_s; if (to == boolType) return variable + u".toBoolean()"_s; if (to == m_typeResolver->int64Type() || to == m_typeResolver->uint64Type()) return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable); if (m_typeResolver->isIntegral(to)) return u"%1(%2.toInteger())"_s.arg(to->internalName(), variable); + if (m_typeResolver->isNumeric(to)) + return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable); if (to == m_typeResolver->stringType()) return variable + u".toString()"_s; if (to == jsValueType) @@ -4435,8 +4435,18 @@ QString QQmlJSCodeGenerator::convertStored( } else if (const auto ctor = m_typeResolver->selectConstructor(to, from, &isExtension); ctor.isValid()) { const auto argumentTypes = ctor.parameters(); - return (isExtension ? to->extensionType().scope->internalName() : to->internalName()) - + u"("_s + convertStored(from, argumentTypes[0].type(), variable) + u")"_s; + const QString argument = convertStored(from, argumentTypes[0].type(), variable); + if (isExtension) { + // We typically use private inheritance for the foreign/extension trick. + // Therefore we need to jump through some hoops to extract the actual value here. + return u"[](auto &&arg) { " + "%1 wrapper(std::forward(arg)); " + "return %2(reinterpret_cast<%2 &>(wrapper)); " + "}(%3)"_s.arg( + to->extensionType().scope->internalName(), to->internalName(), argument); + } + + return u"%1(%2)"_s.arg(to->internalName(), argument); } if (to == m_typeResolver->stringType() diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes.cpp new file mode 100644 index 0000000000..0b57f2d8f5 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// Qt-Security score:significant + +#include "qqmljscontextualtypes_p.h" +#include "qqmljsutils_p.h" + +QT_BEGIN_NAMESPACE + +namespace QQmlJS { + +void ContextualTypes::setType(const QString &name, const ContextualType &type) +{ + if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(type.scope); + !fileSelector.isEmpty()) { + setFileSelectedType(fileSelector, name, type); + return; + } + auto insertName = [this, &name, &type]() { + if (!name.startsWith(u'$')) { + if (!m_names.contains(type.scope, name)) + m_names.insert(type.scope, name); + } + }; + + const auto it = m_types.find(name); + if (it == m_types.end()) { + m_types.insert(name, type); + insertName(); + return; + } + + if (it->m_precedence < type.m_precedence) + return; + // remove the old name from m_names + m_names.remove(it->scope, name); + *it = type; + insertName(); +} + +void ContextualTypes::setFileSelectedType(const QString &fileSelector, const QString &name, + const ContextualType &type) +{ + auto insertName = [this, &name, &type]() { + if (!name.startsWith(u'$')) { + if (!m_names.contains(type.scope, name)) + m_names.insert(type.scope, name); + } + }; + + auto it = m_fileSelectedTypes.find(name); + if (it == m_fileSelectedTypes.end()) { + insertName(); + m_fileSelectedTypes.insert(name, { fileSelector, type }); + return; + } + + if (it->type.m_precedence < type.m_precedence) + return; + + m_names.remove(type.scope, name); + *it = { fileSelector, type }; + insertName(); +} + +FileSelectorInfo ContextualTypes::fileSelectorInfoFor(const QQmlJSScope::ConstPtr &scope) const +{ + FileSelectorInfo result; + for (auto [it, end] = m_names.equal_range(scope); it != end; ++it) { + if (auto mainTypeIt = m_types.find(*it); mainTypeIt != m_types.end()) + result.mainType = mainTypeIt->scope; + + for (auto [it2, end2] = m_fileSelectedTypes.equal_range(*it); it2 != end2; ++it2) { + result.fileSelectedTypes.append(*it2); + } + } + return result; +} +} // namespace QQmlJS + +QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes_p.h index 9b0eed78fe..19ffcf4d08 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljscontextualtypes_p.h @@ -19,9 +19,44 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QQmlJS { + +// note: like QQmlImportInstance::Precedence, except that inline components are 0 and +// "Precedence::Highest" is 1 instead of 0. +enum PrecedenceValues : quint8 { + InlineComponent = 0, + Default = 1, + ImplicitImport = std::numeric_limits::max() >> 1 +}; + +struct ContextualType : public ImportedScope +{ + ContextualType() : m_precedence(std::numeric_limits::max()) { } + ContextualType(const ImportedScope &type, quint8 precedence) + : ImportedScope(type), m_precedence(precedence) + { + } + ContextualType(const QQmlJSScope::ConstPtr &type, QTypeRevision revision, quint8 precedence) + : ImportedScope({ type, revision }), m_precedence(precedence) + { + } + quint8 m_precedence; +}; +struct FileSelectedType +{ + QString fileSelector; + ContextualType type; +}; +struct FileSelectorInfo +{ + QQmlJSScope::ConstPtr mainType; + QList fileSelectedTypes; +}; + /*! \internal * Maps type names to types and the compile context of the types. The context can be * INTERNAL (for c++ and synthetic jsrootgen types) or QML (for qml types). @@ -32,7 +67,7 @@ struct ContextualTypes ContextualTypes( CompileContext context, - const QHash> &types, + const QHash &types, const QMultiHash &names, const QQmlJSScope::ConstPtr &arrayType) : m_types(types) @@ -46,15 +81,12 @@ struct ContextualTypes bool hasType(const QString &name) const { return m_types.contains(name); } - ImportedScope type(const QString &name) const { return m_types[name]; } + ContextualType type(const QString &name) const { return m_types[name]; } QString name(const QQmlJSScope::ConstPtr &type) const { return m_names[type]; } - void setType(const QString &name, const ImportedScope &type) - { - if (!name.startsWith(u'$')) - m_names.insert(type.scope, name); - m_types.insert(name, type); - } + void setType(const QString &name, const ContextualType &type); + void setFileSelectedType(const QString &fileSelector, const QString &name, + const ContextualType &type); void clearType(const QString &name) { auto &scope = m_types[name].scope; @@ -70,21 +102,26 @@ struct ContextualTypes return it != m_types.constEnd() && it->scope.isNull(); } - void addTypes(ContextualTypes &&types) - { - Q_ASSERT(types.m_context == m_context); - insertNames(types); - m_types.insert(std::move(types.m_types)); - } - void addTypes(const ContextualTypes &types) { Q_ASSERT(types.m_context == m_context); - insertNames(types); - m_types.insert(types.m_types); + for (auto it = types.m_types.cbegin(), end = types.m_types.cend(); it != end; ++it) { + setType(it.key(), it.value()); + } + for (auto it = types.m_fileSelectedTypes.cbegin(), end = types.m_fileSelectedTypes.cend(); + it != end; ++it) { + setFileSelectedType(it->fileSelector, it.key(), it->type); + } } - const QHash> &types() const { return m_types; } + const QHash &types() const { return m_types; } + auto fileSelectionsEqualRange(const QString &name) const + { + return m_fileSelectedTypes.equal_range(name); + } + bool hasFileSelectors() const { return m_fileSelectedTypes.size() > 0; } + Q_QMLCOMPILER_EXPORT FileSelectorInfo + fileSelectorInfoFor(const QQmlJSScope::ConstPtr &scope) const; const auto &names() const { return m_names; } void clearTypes() @@ -94,16 +131,8 @@ struct ContextualTypes } private: - void insertNames(const ContextualTypes &types) { - for (auto it = types.m_types.constBegin(), end = types.m_types.constEnd(); - it != end; ++it) { - const QString &name = it.key(); - if (!name.startsWith(u'$')) - m_names.insert(it->scope, name); - } - } - - QHash> m_types; + QHash m_types; + QMultiHash m_fileSelectedTypes; QMultiHash m_names; CompileContext m_context; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter.cpp index 1df7f67269..9a47b6b4aa 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter.cpp @@ -176,7 +176,7 @@ QQmlJSImporter::QQmlJSImporter(const QStringList &importPaths, QQmlJSResourceFil m_importVisitor([](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, const ImportVisitorPrerequisites &p) { auto visitor = std::unique_ptr(new QQmlJSImportVisitor( - p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles)); + self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles)); QQmlJS::AST::Node::accept(rootNode, visitor.get()); }) { @@ -379,14 +379,15 @@ QQmlJSImporter::Import QQmlJSImporter::readDirectory(const QString &directory) return import; } -void QQmlJSImporter::importDependencies( - const QQmlJSImporter::Import &import, QQmlJSImporter::AvailableTypes *types, - const QString &prefix, QTypeRevision version, bool isDependency) +void QQmlJSImporter::importDependencies(const QQmlJSImporter::Import &import, quint8 precedence, + QQmlJSImporter::AvailableTypes *types, + const QString &prefix, QTypeRevision version, + bool isDependency) { // Import the dependencies with an invalid prefix. The prefix will never be matched by actual // QML code but the C++ types will be visible. for (auto const &dependency : std::as_const(import.dependencies)) - importHelper(dependency.module, types, QString(), dependency.version, true); + importHelper(dependency.module, types, precedence + 1, QString(), dependency.version, true); bool hasOptionalImports = false; for (auto const &import : std::as_const(import.imports)) { @@ -400,7 +401,7 @@ void QQmlJSImporter::importDependencies( continue; } - importHelper(import.module, types, isDependency ? QString() : prefix, + importHelper(import.module, types, precedence + 1, isDependency ? QString() : prefix, (import.flags & QQmlDirParser::Import::Auto) ? version : import.version, isDependency); } @@ -427,155 +428,184 @@ static bool isVersionAllowed(const QQmlJSScope::Export &exportEntry, || exportVersion.minorVersion() <= importVersion.minorVersion(); } -/* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about commonconstructs. - We might want to improve it in the future if it causes issues -*/ -static bool fileSelectedScopesAreCompatibleHeuristic(const QQmlJSScope::ConstPtr &scope1, const QQmlJSScope::ConstPtr &scope2) { - for (const auto &[propertyName, prop]: scope1->properties().asKeyValueRange()) - if (!scope2->hasProperty(propertyName)) - return false; - for (const auto &[methodName, method]: scope1->methods().asKeyValueRange()) - if (!scope2->hasMethod(methodName)) - return false; - return true; +void QQmlJSImporter::insertAliases(const QQmlJS::ContextualType &type, + QQmlJSImporter::AvailableTypes *types) +{ + const QStringList cppAliases = aliases(type.scope); + for (const QString &alias : cppAliases) + types->cppNames.setType(alias, type); +}; + +void QQmlJSImporter::insertExport(const QQmlJS::ContextualType &type, + const QQmlJS::Export &valExport, const QString &qmlName, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types) const +{ + if (m_flags.testFlag(TolerateFileSelectors)) { + if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(type.scope); + !fileSelector.isEmpty()) { + types->qmlNames.setFileSelectedType(fileSelector, qmlName, type); + return; + } + } + types->qmlNames.setType(qmlName, type); + (*seenExports)[qmlName].append(valExport); } -void QQmlJSImporter::processImport( - const QQmlJS::Import &importDescription, const QQmlJSImporter::Import &import, - QQmlJSImporter::AvailableTypes *types) +QQmlJSScope::Export +QQmlJSImporter::resolveConflictingExports(const QQmlJS::Import &importDescription, + const QQmlJSExportedScope &val, quint8 precedence, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types) { - // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++ - // names with $internal$. This is to avoid clashes between them. - // In the list of C++ types we insert types that don't have a C++ name as their - // QML name prefixed with $anonymous$. - const QString anonPrefix = QStringLiteral("$anonymous$"); - const QString internalPrefix = QStringLiteral("$internal$"); - const QString modulePrefix = QStringLiteral("$module$"); - QHash> seenExports; + QQmlJSScope::Export bestExport; - const auto insertAliases = [&](const QQmlJSScope::ConstPtr &scope, QTypeRevision revision) { - const QStringList cppAliases = aliases(scope); - for (const QString &alias : cppAliases) - types->cppNames.setType(alias, { scope, revision }); - }; + for (const auto &valExport : val.exports) { + const QString qmlName = prefixedName(importDescription.prefix(), valExport.type()); + if (!isVersionAllowed(valExport, importDescription)) + continue; - const auto insertExports = [&](const QQmlJSExportedScope &val, const QString &cppName) { - QQmlJSScope::Export bestExport; + // Even if the QML name is overridden by some other type, we still want + // to insert the C++ type, with the highest revision available. + if (!bestExport.isValid() || valExport.version() > bestExport.version()) + bestExport = valExport; - // Resolve conflicting qmlNames within an import - for (const auto &valExport : val.exports) { - const QString qmlName = prefixedName(importDescription.prefix(), valExport.type()); - if (!isVersionAllowed(valExport, importDescription)) - continue; + auto insertExportWithConflict = [&](const QQmlJSImportedScope &conflicting) { + // The same set of exports can declare the same name multiple times for different + // versions. That's the common thing and we would just continue here when we hit + // it again after having inserted successfully once. + // However, it can also declare *different* names. Then we need to do the whole + // thing again. + if (conflicting.scope == val.scope && conflicting.revision == valExport.version()) + return; - // Even if the QML name is overridden by some other type, we still want - // to insert the C++ type, with the highest revision available. - if (!bestExport.isValid() || valExport.version() > bestExport.version()) - bestExport = valExport; + const SeenVersion seenVersion = computeSeenVersion( + importDescription, seenExports->value(qmlName), valExport.version()); - const auto it = types->qmlNames.types().find(qmlName); - if (it != types->qmlNames.types().end()) { + insertExportWithConflictingVersion(val, precedence, qmlName, valExport, + conflicting.scope, seenExports, types, seenVersion); + }; - // The same set of exports can declare the same name multiple times for different - // versions. That's the common thing and we would just continue here when we hit - // it again after having inserted successfully once. - // However, it can also declare *different* names. Then we need to do the whole - // thing again. - if (it->scope == val.scope && it->revision == valExport.version()) - continue; - - const auto existingExports = seenExports.value(qmlName); - enum { LowerVersion, SameVersion, HigherVersion } seenVersion = LowerVersion; - for (const QQmlJSScope::Export &entry : existingExports) { - if (!isVersionAllowed(entry, importDescription)) - continue; - - if (valExport.version() < entry.version()) { - seenVersion = HigherVersion; - break; - } - - if (seenVersion == LowerVersion && valExport.version() == entry.version()) - seenVersion = SameVersion; - } - - switch (seenVersion) { - case LowerVersion: - break; - case SameVersion: { - if (m_flags & QQmlJSImporterFlag::TolerateFileSelectors) { - auto isFileSelected = [](const QQmlJSScope::ConstPtr &scope) -> bool - { - return scope->filePath().contains(u"+"); - }; - auto warnAboutFileSelector = [&](const QString &path) { - types->warnings.append({ - QStringLiteral("Type %1 is ambiguous due to file selector usage, ignoring %2.") - .arg(qmlName, path), - QtInfoMsg, - QQmlJS::SourceLocation() - }); - }; - if (it->scope) { - if (isFileSelected(val.scope)) { - // new entry is file selected, skip if it looks compatible - if (fileSelectedScopesAreCompatibleHeuristic(it->scope, val.scope)) { - warnAboutFileSelector(val.scope->filePath()); - continue; - } - } else if (isFileSelected(it->scope)) { - // the first scope we saw is file selected. If they are compatible - // we update to the new one without file selector - if (fileSelectedScopesAreCompatibleHeuristic(it->scope, val.scope)) { - warnAboutFileSelector(it->scope->filePath()); - break; - } - } - } - } - types->warnings.append({ - QStringLiteral("Ambiguous type detected. " - "%1 %2.%3 is defined multiple times.") - .arg(qmlName) - .arg(valExport.version().majorVersion()) - .arg(valExport.version().minorVersion()), - QtCriticalMsg, - QQmlJS::SourceLocation() - }); - - // Invalidate the type. We don't know which one to use. - types->qmlNames.clearType(qmlName); - continue; - } - case HigherVersion: - continue; - } - } - - types->qmlNames.setType(qmlName, { val.scope, valExport.version() }); - seenExports[qmlName].append(valExport); + if (const auto it = types->qmlNames.types().find(qmlName); + it != types->qmlNames.types().end()) { + insertExportWithConflict(*it); + continue; } - const QTypeRevision bestRevision = bestExport.isValid() - ? bestExport.revision() - : QTypeRevision::zero(); - types->cppNames.setType(cppName, { val.scope, bestRevision }); + const auto [it, end] = types->qmlNames.fileSelectionsEqualRange(qmlName); + if (it == end) { + insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName, + seenExports, types); + continue; + } + insertExportWithConflict(it->type); + } + return bestExport; +} - insertAliases(val.scope, bestRevision); +QQmlJSImporter::SeenVersion +QQmlJSImporter::computeSeenVersion(const QQmlJS::Import &importDescription, + const QList &existingExports, + QTypeRevision valExportVersion) const +{ + QQmlJSImporter::SeenVersion seenVersion = LowerVersion; + for (const QQmlJSScope::Export &entry : existingExports) { + if (!isVersionAllowed(entry, importDescription)) + continue; - const QTypeRevision bestVersion = bestExport.isValid() - ? bestExport.version() - : QTypeRevision::zero(); - types->qmlNames.setType(prefixedName(internalPrefix, cppName), { val.scope, bestVersion }); + if (valExportVersion < entry.version()) { + seenVersion = HigherVersion; + break; + } + + if (seenVersion == LowerVersion && valExportVersion == entry.version()) + seenVersion = SameVersion; + } + return seenVersion; +} + +void QQmlJSImporter::insertExportWithConflictingVersion( + const QQmlJSExportedScope &val, quint8 precedence, const QString &qmlName, + const QQmlJSScope::Export &valExport, const QQmlJSScope::ConstPtr &scope, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types, SeenVersion seenVersion) const +{ + auto onDuplicateImport = [&]() { + types->warnings.append({ QStringLiteral("Ambiguous type detected. " + "%1 %2.%3 is defined multiple times.") + .arg(qmlName) + .arg(valExport.version().majorVersion()) + .arg(valExport.version().minorVersion()), + QtCriticalMsg, QQmlJS::SourceLocation() }); + + // Invalidate the type. We don't know which one to use. + types->qmlNames.clearType(qmlName); }; + switch (seenVersion) { + case LowerVersion: + insertExport({ val.scope, valExport.version(), precedence }, valExport, qmlName, + seenExports, types); + return; + case SameVersion: { + if (!m_flags.testAnyFlag(QQmlJSImporterFlag::TolerateFileSelectors) || !scope) { + onDuplicateImport(); + return; + } + if (const QString fileSelector = QQmlJSUtils::fileSelectorFor(val.scope); + !fileSelector.isEmpty()) { + types->qmlNames.setFileSelectedType(fileSelector, qmlName, + { val.scope, valExport.version(), precedence }); + return; + } + if (!QQmlJSUtils::fileSelectorFor(scope).isEmpty()) { + // the first scope we saw is file selected, update to the new one without file selector + types->qmlNames.setType(qmlName, { val.scope, valExport.version(), precedence }); + (*seenExports)[qmlName].append(valExport); + return; + } + onDuplicateImport(); + return; + } + case HigherVersion: + return; + } +} + +void QQmlJSImporter::insertExports(const QQmlJS::Import &importDescription, + const QQmlJSExportedScope &val, const QString &cppName, + quint8 precedence, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types) +{ + const QQmlJSScope::Export bestExport = + resolveConflictingExports(importDescription, val, precedence, seenExports, types); + const QTypeRevision bestRevision = + bestExport.isValid() ? bestExport.revision() : QTypeRevision::zero(); + const QQmlJS::ContextualType contextualType{ val.scope, bestRevision, precedence }; + types->cppNames.setType(cppName, contextualType); + + insertAliases(contextualType, types); + + const QTypeRevision bestVersion = + bestExport.isValid() ? bestExport.version() : QTypeRevision::zero(); + types->qmlNames.setType(prefixedName(internalPrefix, cppName), + { val.scope, bestVersion, precedence }); +}; + +void QQmlJSImporter::processImport(const QQmlJS::Import &importDescription, + const QQmlJSImporter::Import &import, quint8 precedence, + QQmlJSImporter::AvailableTypes *types) +{ + QHash> seenExports; // Empty type means "this is the prefix" if (!importDescription.prefix().isEmpty()) - types->qmlNames.setType(importDescription.prefix(), {}); + types->qmlNames.setType(importDescription.prefix(), { {}, precedence }); if (!importDescription.isDependency()) { // Add a marker to show that this module has been imported - types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()), {}); + types->qmlNames.setType(prefixedName(modulePrefix, importDescription.name()), + { {}, precedence }); if (import.isStaticModule) types->staticModules << import.name; @@ -589,7 +619,8 @@ void QQmlJSImporter::processImport( for (auto it = import.scripts.begin(); it != import.scripts.end(); ++it) { // You cannot have a script without an export Q_ASSERT(!it->exports.isEmpty()); - insertExports(*it, prefixedName(anonPrefix, internalName(it->scope))); + insertExports(importDescription, *it, prefixedName(anonPrefix, internalName(it->scope)), + precedence, &seenExports, types); } // add objects @@ -599,13 +630,13 @@ void QQmlJSImporter::processImport( : internalName(val.scope); if (val.exports.isEmpty()) { - // Insert an unresolvable dummy name - types->qmlNames.setType( - prefixedName(internalPrefix, cppName), { val.scope, QTypeRevision() }); - types->cppNames.setType(cppName, { val.scope, QTypeRevision() }); - insertAliases(val.scope, QTypeRevision()); + const QQmlJS::ContextualType unresolvableDummyName{ val.scope, QTypeRevision(), + precedence }; + types->qmlNames.setType(prefixedName(internalPrefix, cppName), unresolvableDummyName); + types->cppNames.setType(cppName, unresolvableDummyName); + insertAliases(unresolvableDummyName, types); } else { - insertExports(val, cppName); + insertExports(importDescription, val, cppName, precedence, &seenExports, types); } } @@ -652,7 +683,7 @@ void QQmlJSImporter::processImport( // ignore the scope currently analyzed by QQmlJSImportVisitor, as its only populated // after importing the implicit directory. - if (val.scope->baseTypeName() == "$InProcess$"_L1) + if (val.scope->baseTypeName() == s_inProcessMarker) continue; // Composite types use QML names, and we should have resolved those already. @@ -689,7 +720,7 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::importHardCodedBuiltins() const auto type = builtins.qmlNames.type(hardcoded); Q_ASSERT(type.scope); - result.setType(hardcoded, type); + result.setType(hardcoded, { type, QQmlJS::PrecedenceValues::Default }); } return ImportedTypes(std::move(result), {}); @@ -703,7 +734,8 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper() AvailableTypes builtins(QQmlJS::ContextualTypes(QQmlJS::ContextualTypes::INTERNAL, {}, {}, {})); - importHelper(u"QML"_s, &builtins, QString(), QTypeRevision::fromVersion(1, 0)); + importHelper(u"QML"_s, &builtins, QQmlJS::PrecedenceValues::Default, QString(), + QTypeRevision::fromVersion(1, 0)); QQmlJSScope::ConstPtr arrayType = builtins.cppNames.type(u"Array"_s).scope; Q_ASSERT(arrayType); @@ -765,14 +797,14 @@ QList QQmlJSImporter::importQmldirs(const QStringList return warnings; } -QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module, +QQmlJSImporter::ImportedTypes QQmlJSImporter::importModule(const QString &module, quint8 precedence, const QString &prefix, QTypeRevision version, QStringList *staticModuleList) { const AvailableTypes builtins = builtinImportHelper(); AvailableTypes result(builtins.cppNames); - if (!importHelper(module, &result, prefix, version)) { + if (!importHelper(module, &result, precedence, prefix, version)) { result.warnings.append({ QStringLiteral("Failed to import %1. Are your import paths set up properly?") .arg(module), @@ -793,7 +825,7 @@ QQmlJSImporter::ImportedTypes QQmlJSImporter::builtinInternalNames() return ImportedTypes(std::move(builtins.cppNames), std::move(builtins.warnings)); } -bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, +bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, quint8 precedence, const QString &prefix, QTypeRevision version, bool isDependency, bool isFile) { @@ -842,8 +874,8 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, Q_ASSERT(m_seenQmldirFiles.contains(*it)); const QQmlJSImporter::Import import = m_seenQmldirFiles.value(*it); - importDependencies(import, cacheTypes.get(), prefix, version, isDependency); - processImport(cacheKey, import, cacheTypes.get()); + importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency); + processImport(cacheKey, import, precedence, cacheTypes.get()); const bool typesFromCache = getTypesFromCache(); Q_ASSERT(typesFromCache); @@ -855,8 +887,8 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, const auto import = readDirectory(module); m_seenQmldirFiles.insert(module, import); m_seenImports.insert(importId, module); - importDependencies(import, cacheTypes.get(), prefix, version, isDependency); - processImport(cacheKey, import, cacheTypes.get()); + importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency); + processImport(cacheKey, import, precedence, cacheTypes.get()); // Try to load a qmldir below, on top of the directory import. modulePaths.append(module); @@ -892,8 +924,8 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, if (it != m_seenQmldirFiles.constEnd()) { const QQmlJSImporter::Import import = *it; m_seenImports.insert(importId, qmldirPath); - importDependencies(import, cacheTypes.get(), prefix, version, isDependency); - processImport(cacheKey, import, cacheTypes.get()); + importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency); + processImport(cacheKey, import, precedence, cacheTypes.get()); const bool typesFromCache = getTypesFromCache(); Q_ASSERT(typesFromCache); @@ -906,10 +938,10 @@ bool QQmlJSImporter::importHelper(const QString &module, AvailableTypes *types, setQualifiedNamesOn(import); m_seenQmldirFiles.insert(qmldirPath, import); m_seenImports.insert(importId, qmldirPath); - importDependencies(import, cacheTypes.get(), prefix, version, isDependency); + importDependencies(import, precedence, cacheTypes.get(), prefix, version, isDependency); // Potentially merges with the result of readDirectory() above. - processImport(cacheKey, import, cacheTypes.get()); + processImport(cacheKey, import, precedence, cacheTypes.get()); const bool typesFromCache = getTypesFromCache(); Q_ASSERT(typesFromCache); @@ -942,26 +974,7 @@ QQmlJSScope::Ptr QQmlJSImporter::localFile2QQmlJSScope(const QString &filePath) sourceFolderFile, { QQmlJSScope::create(), QSharedPointer>(new QDeferredFactory( - this, sourceFolderFile)) }); -} - -/*! -\internal -Add scopes manually created and QQmlJSImportVisited to QQmlJSImporter. -This allows theses scopes to not get loaded twice during linting, for example. - -Returns false if the importer contains a scope different than \a scope for the same -QQmlJSScope::filePath. -*/ -bool QQmlJSImporter::registerScope(const QQmlJSScope::Ptr &scope) -{ - Q_ASSERT(!scope.factory()); - - QQmlJSScope::Ptr &existing = m_importedFiles[scope->filePath()]; - if (existing) - return existing == scope; - existing = scope; - return true; + this, {}, sourceFolderFile, QString(), false)) }); } QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file) @@ -969,13 +982,13 @@ QQmlJSScope::Ptr QQmlJSImporter::importFile(const QString &file) return localFile2QQmlJSScope(file); } -QQmlJSImporter::ImportedTypes QQmlJSImporter::importDirectory( - const QString &directory, const QString &prefix) +QQmlJSImporter::ImportedTypes +QQmlJSImporter::importDirectory(const QString &directory, quint8 precedence, const QString &prefix) { const AvailableTypes builtins = builtinImportHelper(); QQmlJSImporter::AvailableTypes types(QQmlJS::ContextualTypes( QQmlJS::ContextualTypes::INTERNAL, {}, {}, builtins.cppNames.arrayType())); - importHelper(directory, &types, prefix, QTypeRevision(), false, true); + importHelper(directory, &types, precedence, prefix, QTypeRevision(), false, true); return ImportedTypes(std::move(types.qmlNames), std::move(types.warnings)); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter_p.h index cbcc1cc5ff..84b4055818 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimporter_p.h @@ -23,6 +23,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -72,7 +74,17 @@ class QQmlJSImportVisitor; class QQmlJSLogger; class Q_QMLCOMPILER_EXPORT QQmlJSImporter { + // In the list of QML types we prefix unresolvable QML names with $anonymous$, and C++ + // names with $internal$. This is to avoid clashes between them. + // In the list of C++ types we insert types that don't have a C++ name as their + // QML name prefixed with $anonymous$. + static inline constexpr QLatin1String anonPrefix = QLatin1String("$anonymous$"); + static inline constexpr QLatin1String internalPrefix = QLatin1String("$internal$"); + static inline constexpr QLatin1String modulePrefix = QLatin1String("$module$"); + public: + static inline constexpr QLatin1String s_inProcessMarker = QLatin1String("$InProcess$"); + struct ImportedTypes { ImportedTypes(QQmlJS::ContextualTypes &&types, QList &&warnings) : m_types(std::move(types)), m_warnings(std::move(warnings)) @@ -101,15 +113,13 @@ public: return m_types.type(name); } QString name(const QQmlJSScope::ConstPtr &type) const { return m_types.name(type); } - void setType(const QString &name, const QQmlJS::ImportedScope &type) + void setType(const QString &name, const QQmlJS::ContextualType &type) { m_types.setType(name, type); } bool isNullType(const QString &name) const { return m_types.isNullType(name); } - const QHash> &types() const - { - return m_types.types(); - } + const QHash &types() const { return m_types.types(); } + const auto &names() const { return m_types.names(); } void add(ImportedTypes &&other) { @@ -139,14 +149,15 @@ public: ImportedTypes importHardCodedBuiltins(); QList importQmldirs(const QStringList &qmltypesFiles); - bool registerScope(const QQmlJSScope::Ptr &scope); QQmlJSScope::Ptr importFile(const QString &file); - ImportedTypes importDirectory(const QString &directory, const QString &prefix = QString()); + ImportedTypes importDirectory(const QString &directory, quint8 precedence, + const QString &prefix = QString()); // ### qmltc needs this. once re-written, we no longer need to expose this QHash importedFiles() const { return m_importedFiles; } - ImportedTypes importModule(const QString &module, const QString &prefix = QString(), + ImportedTypes importModule(const QString &module, quint8 precedence, + const QString &prefix = QString(), QTypeRevision version = QTypeRevision(), QStringList *staticModuleList = nullptr); @@ -203,6 +214,8 @@ public: const ImportVisitorPrerequisites &prerequisites)>; void setImportVisitor(ImportVisitor visitor) { m_importVisitor = visitor; } + void setFlags(const QQmlJSImporterFlags &flags) { m_flags = flags; } + QQmlJSImporterFlags flags() const { return m_flags; } private: friend class QDeferredFactory; @@ -246,14 +259,40 @@ private: }; AvailableTypes builtinImportHelper(); - bool importHelper(const QString &module, AvailableTypes *types, + bool importHelper(const QString &module, AvailableTypes *types, quint8 precedence, const QString &prefix = QString(), QTypeRevision version = QTypeRevision(), bool isDependency = false, bool isFile = false); - void processImport( - const QQmlJS::Import &importDescription, const Import &import, AvailableTypes *types); - void importDependencies( - const Import &import, AvailableTypes *types, const QString &prefix = QString(), - QTypeRevision version = QTypeRevision(), bool isDependency = false); + void processImport(const QQmlJS::Import &importDescription, const Import &import, + quint8 precedence, AvailableTypes *types); + static void insertAliases(const QQmlJS::ContextualType &type, + QQmlJSImporter::AvailableTypes *types); + void insertExport(const QQmlJS::ContextualType &type, const QQmlJS::Export &valExport, + const QString &qmlName, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types) const; + QQmlJSScope::Export + resolveConflictingExports(const QQmlJS::Import &importDescription, + const QQmlJSExportedScope &val, quint8 precedence, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types); + enum SeenVersion { LowerVersion, SameVersion, HigherVersion }; + void insertExportWithConflictingVersion(const QQmlJSExportedScope &val, quint8 precedence, + const QString &qmlName, + const QQmlJSScope::Export &valExport, + const QQmlJSScope::ConstPtr &scope, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types, + SeenVersion seenVersion) const; + SeenVersion computeSeenVersion(const QQmlJS::Import &importDescription, + const QList &existingExports, + QTypeRevision valExportVersion) const; + void insertExports(const QQmlJS::Import &importDescription, const QQmlJSExportedScope &val, + const QString &cppName, quint8 precedence, + QHash> *seenExports, + QQmlJSImporter::AvailableTypes *types); + void importDependencies(const Import &import, quint8 precedence, AvailableTypes *types, + const QString &prefix = QString(), + QTypeRevision version = QTypeRevision(), bool isDependency = false); QQmlDirParser createQmldirParserForFile(const QString &filename, Import *import); void readQmltypes(const QString &filename, Import *result); Import readQmldir(const QString &dirname); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor.cpp index 750f0c2b43..5b9171771d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -23,6 +23,8 @@ #include "qqmljsloggingutils.h" #include "qqmlsaconstants.h" +#include + #include #include #include @@ -39,8 +41,6 @@ static const QLatin1StringView wasNotFound static const QLatin1StringView didYouAddAllImports = "Did you add all imports and dependencies?"_L1; -Q_STATIC_LOGGING_CATEGORY(lcImportVisitor, "qt.qml.importVisitor", QtWarningMsg); - /*! \internal Returns if assigning \a assignedType to \a property would require an @@ -156,48 +156,26 @@ QString buildName(const Node *node) return result; } -/*! - \internal - Make sure that the importer does not recreate the target scope when trying to import it via - implicit directory import. -*/ -void QQmlJSImportVisitor::registerTargetIntoImporter(const QQmlJSScope::Ptr &target) -{ - target->setScopeType(QQmlSA::ScopeType::QMLScope); - target->setBaseTypeName("$InProcess$"_L1); - target->setFilePath(m_logger->filePath()); - target->setIsComposite(true); - if (!m_importer->registerScope(target)) { - qCDebug(lcImportVisitor) - << "Couldn't register scope into importer: scope will be created multiple times."; - } -} - -static void prepareTargetForVisit(const QQmlJSScope::Ptr &target) -{ - // rootScopeIsValid() assumes target to be a scope that only contains an internal name and a - // moduleName - target->resetForReparse(); -} - -QQmlJSImportVisitor::QQmlJSImportVisitor( - const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger, - const QString &implicitImportDirectory, const QStringList &qmldirFiles) +QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, + const QString &implicitImportDirectory, + const QStringList &qmldirFiles) : m_implicitImportDirectory(implicitImportDirectory), m_qmldirFiles(qmldirFiles), - m_exportedRootScope(target), + m_exportedRootScope(QQmlJSScope::resetForReparse(importer->importFile(logger->filePath()))), m_importer(importer), m_logger(logger), - m_rootScopeImports( - QQmlJS::ContextualTypes( - QQmlJS::ContextualTypes::QML, {}, {}, - importer->builtinInternalNames().contextualTypes().arrayType()), - {}) + m_rootScopeImports(QQmlJS::ContextualTypes( + QQmlJS::ContextualTypes::QML, { }, { }, + importer->builtinInternalNames().contextualTypes().arrayType()), + { }) { Q_ASSERT(logger); // must be valid + Q_ASSERT(importer); // must be valid - prepareTargetForVisit(target); - registerTargetIntoImporter(target); + m_exportedRootScope->setScopeType(QQmlSA::ScopeType::QMLScope); + m_exportedRootScope->setBaseTypeName(QQmlJSImporter::s_inProcessMarker); + m_exportedRootScope->setFilePath(m_logger->filePath()); + m_exportedRootScope->setIsComposite(true); /* FIXME: we create a "local global object" – this prevents any modification of the actual global object; @@ -518,7 +496,8 @@ void QQmlJSImportVisitor::importBaseModules() // Pulling in the modules and neighboring qml files of the qmltypes we're trying to lint is not // something we need to do. if (!m_logger->filePath().endsWith(u".qmltypes"_s)) { - m_rootScopeImports.add(m_importer->importDirectory(m_implicitImportDirectory)); + m_rootScopeImports.add(m_importer->importDirectory( + m_implicitImportDirectory, QQmlJS::PrecedenceValues::ImplicitImport)); // Import all possible resource directories the file may belong to. // This is somewhat fuzzy, but if you're mapping the same file to multiple resource @@ -530,7 +509,8 @@ void QQmlJSImportVisitor::importBaseModules() const qsizetype lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash == -1) continue; - m_rootScopeImports.add(m_importer->importDirectory(path.first(lastSlash))); + m_rootScopeImports.add(m_importer->importDirectory( + path.first(lastSlash), QQmlJS::PrecedenceValues::ImplicitImport)); } } } @@ -544,7 +524,9 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiProgram *) // if the current file is a QML file, make it available, too if (auto elementName = QFileInfo(m_logger->filePath()).baseName(); !elementName.isEmpty() && elementName[0].isUpper()) { - m_rootScopeImports.setType(elementName, { m_exportedRootScope, QTypeRevision {} }); + m_rootScopeImports.setType(elementName, + { m_exportedRootScope, QTypeRevision{ }, + QQmlJS::PrecedenceValues::ImplicitImport }); } return true; @@ -555,6 +537,7 @@ void QQmlJSImportVisitor::endVisit(UiProgram *) for (const auto &scope : std::as_const(m_objectBindingScopes)) { breakInheritanceCycles(scope); checkDeprecation(scope); + checkForComponentTypeWithProperties(scope); } for (const auto &scope : std::as_const(m_objectDefinitionScopes)) { @@ -562,12 +545,14 @@ void QQmlJSImportVisitor::endVisit(UiProgram *) continue; // We're going to check this one below. breakInheritanceCycles(scope); checkDeprecation(scope); + checkForComponentTypeWithProperties(scope); } const auto &keys = m_pendingDefaultProperties.keys(); for (const auto &scope : keys) { breakInheritanceCycles(scope); checkDeprecation(scope); + checkForComponentTypeWithProperties(scope); } resolveAliases(); @@ -723,6 +708,9 @@ void QQmlJSImportVisitor::processDefaultProperties() if (checkCustomParser(parentScope)) continue; + if (!checkTypeResolved(parentScope)) + continue; + /* consider: * * QtObject { // <- parentScope @@ -750,7 +738,7 @@ void QQmlJSImportVisitor::processDefaultProperties() } } - if (!isComponent) { + if (!isComponent && checkTypeResolved(parentScope)) { m_logger->log(QStringLiteral("Cannot assign to non-existent default property"), qmlMissingProperty, it.value().constFirst()->sourceLocation()); } @@ -822,7 +810,7 @@ void QQmlJSImportVisitor::processPropertyTypes() if (const auto propertyType = QQmlJSScope::findType( property.typeName(), m_rootScopeImports.contextualTypes()).scope) { - property.setType(propertyType); + property.setType(property.isList() ? propertyType->listType() : propertyType); type.scope->addOwnProperty(property); } else { QString msg = property.typeName() + ' '_L1 + wasNotFound + ' '_L1 + didYouAddAllImports; @@ -1124,11 +1112,11 @@ void QQmlJSImportVisitor::checkRequiredProperties() } const auto compType = m_rootScopeImports.type(u"Component"_s).scope; - const auto isInComponent = [&](const QQmlJSScope::ConstPtr &requiredScope) { - for (auto s = requiredScope; s; s = s->parentScope()) { - if (s->isWrappedInImplicitComponent() || s->baseType() == compType) - return true; - } + const auto isComponentRoot = [&](const QQmlJSScope::ConstPtr &requiredScope) { + if (requiredScope->isWrappedInImplicitComponent()) + return true; + if (const auto s = requiredScope->parentScope(); s && s->baseType() == compType) + return true; return false; }; @@ -1266,8 +1254,10 @@ void QQmlJSImportVisitor::checkRequiredProperties() QQmlJSScope::ConstPtr prevRequiredScope; for (const QQmlJSScope::ConstPtr &requiredScope : std::as_const(scopesToSearch)) { - if (isInComponent(requiredScope)) - continue; + // Stop at component boundaries. We don't want to report the same problem + // multiple times. + if (isComponentRoot(requiredScope)) + break; if (!scopeRequiresProperty(requiredScope, propName, descendant)) { prevRequiredScope = requiredScope; @@ -1317,7 +1307,8 @@ void QQmlJSImportVisitor::processPropertyBindings() } } - warnMissingPropertyForBinding(name, location, fixSuggestion); + if (checkTypeResolved(scope)) + warnMissingPropertyForBinding(name, location, fixSuggestion); continue; } @@ -1552,7 +1543,7 @@ void QQmlJSImportVisitor::breakInheritanceCycles(const QQmlJSScope::Ptr &origina inheritenceCycle.append(scopes.first()->baseTypeName()); const QString message = QStringLiteral("%1 is part of an inheritance cycle: %2") - .arg(scope->internalName(), inheritenceCycle); + .arg(originalScope->baseTypeName(), inheritenceCycle); m_logger->log(message, qmlInheritanceCycle, scope->sourceLocation()); originalScope->clearBaseType(); originalScope->setBaseTypeError(message); @@ -1631,6 +1622,28 @@ void QQmlJSImportVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr sc } } +void QQmlJSImportVisitor::checkForComponentTypeWithProperties(const QQmlJSScope::ConstPtr &scope) +{ + const QQmlJSScope::ConstPtr base = scope->baseType(); + if (!base) + return; + + // If the base type is composite itself, we ignore it being a QQmlCompoonent and + // assume you actually mean its contents (and produce a deprecation warning). + // We can ignore this case here. + if (base->isComposite()) + return; + + if (base->internalName() != "QQmlComponent"_L1) + return; + + const auto ownProperties = scope->ownProperties(); + for (const auto &property : ownProperties) { + m_logger->log("Component objects cannot declare new properties."_L1, + qmlSyntax, property.sourceLocation()); + } +} + bool QQmlJSImportVisitor::checkCustomParser(const QQmlJSScope::ConstPtr &scope) { return scope->isInCustomParserParent(); @@ -1840,7 +1853,8 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition) m_currentScope->setIsInlineComponent(true); m_currentScope->setInlineComponentName(name); m_currentScope->setOwnModuleName(m_exportedRootScope->moduleName()); - m_rootScopeImports.setType(name, { m_currentScope, revision }); + m_rootScopeImports.setType( + name, { m_currentScope, revision, QQmlJS::PrecedenceValues::InlineComponent }); m_nextIsInlineComponent = false; } @@ -2607,12 +2621,10 @@ void QQmlJSImportVisitor::endVisit(UiScriptBinding *) bool QQmlJSImportVisitor::visit(UiArrayBinding *arrayBinding) { + createAttachedAndGroupedScopes(arrayBinding->qualifiedId); enterEnvironment(QQmlSA::ScopeType::QMLScope, buildName(arrayBinding->qualifiedId), arrayBinding->firstSourceLocation()); m_currentScope->setIsArrayScope(true); - - // TODO: support group/attached properties - return true; } @@ -2624,15 +2636,24 @@ void QQmlJSImportVisitor::endVisit(UiArrayBinding *arrayBinding) // other expressions involving lists (e.g. `var p: [1,2,3]`) are considered // to be script bindings const auto children = m_currentScope->childScopes(); - const auto propertyName = QQmlJSUtils::getScopeName(m_currentScope, QQmlSA::ScopeType::QMLScope); leaveEnvironment(); + const int scopesEnteredCounter = openAttachedAndGroupedScopes(arrayBinding->qualifiedId); + auto guard = qScopeGuard([this, scopesEnteredCounter]() { + for (int i = 0; i < scopesEnteredCounter; ++i) + leaveEnvironment(); + }); + if (checkCustomParser(m_currentScope)) { // These warnings do not apply for custom parsers and their children and need to be handled // on a case by case basis return; } + auto group = arrayBinding->qualifiedId; + for (; group->next; group = group->next) { } + const QString propertyName = group->name.toString(); + qsizetype i = 0; for (auto element = arrayBinding->members; element; element = element->next, ++i) { const auto &type = children[i]; @@ -2698,13 +2719,14 @@ QList QQmlJSImportVisitor::importFromHost( if (fileInfo.isFile()) { const auto scope = m_importer->importFile(path); const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix; - m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() }); + m_rootScopeImports.setType(actualPrefix, + { scope, QTypeRevision(), QQmlJS::PrecedenceValues::Default }); addImportWithLocation(actualPrefix, location, false); return {}; } if (fileInfo.isDir()) { - auto scopes = m_importer->importDirectory(path, prefix); + auto scopes = m_importer->importDirectory(path, QQmlJS::PrecedenceValues::Default, prefix); const auto types = scopes.types(); const auto warnings = scopes.warnings(); m_rootScopeImports.add(std::move(scopes)); @@ -2735,12 +2757,13 @@ QList QQmlJSImportVisitor::importFromQrc( const auto scope = m_importer->importFile(entry.filePath); const QString actualPrefix = prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix; - m_rootScopeImports.setType(actualPrefix, { scope, QTypeRevision() }); + m_rootScopeImports.setType(actualPrefix, + { scope, QTypeRevision(), QQmlJS::PrecedenceValues::Default }); addImportWithLocation(actualPrefix, location, false); return {}; } - auto scopes = m_importer->importDirectory(path, prefix); + auto scopes = m_importer->importDirectory(path, QQmlJS::PrecedenceValues::Default, prefix); const auto types = scopes.types(); const auto warnings = scopes.warnings(); m_rootScopeImports.add(std::move(scopes)); @@ -2797,8 +2820,8 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import) QStringList staticModulesProvided; auto imported = m_importer->importModule( - path, prefix, import->version ? import->version->version : QTypeRevision(), - &staticModulesProvided); + path, QQmlJS::PrecedenceValues::Default, prefix, + import->version ? import->version->version : QTypeRevision(), &staticModulesProvided); const auto types = imported.types(); const auto warnings = imported.warnings(); m_rootScopeImports.add(std::move(imported)); @@ -2882,6 +2905,10 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma) m_scopesById.setValueTypesAreAddressable(true); } else if (value == u"Inaddressable") { m_scopesById.setValueTypesAreAddressable(false); + } else if (value == u"Assertable") { + m_scopesById.setValueTypesAreAssertable(true); + } else if (value == u"Inassertable") { + m_scopesById.setValueTypesAreAssertable(false); } else { m_logger->log(u"Unknown argument \"%1\" to pragma ValueTypeBehavior"_s.arg(value), qmlSyntax, pragma->firstSourceLocation()); @@ -3008,28 +3035,18 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::FormalParameterList *fpl) return true; } -bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) +void QQmlJSImportVisitor::createAttachedAndGroupedScopes(UiQualifiedId *propertyName) { - // ... __styleData: QtObject {...} - - Q_ASSERT(uiob->qualifiedTypeNameId); - bool needsResolution = false; int scopesEnteredCounter = 0; - - const QString typeName = buildName(uiob->qualifiedTypeNameId); - if (typeName.front().isLower() && typeName.contains(u'.')) { - logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger); - } - QString prefix; - for (auto group = uiob->qualifiedId; group->next; group = group->next) { + for (auto group = propertyName; group->next; group = group->next) { const QString idName = group->name.toString(); if (idName.isEmpty()) break; - if (group == uiob->qualifiedId && isImportPrefix(idName)) { + if (group == propertyName && isImportPrefix(idName)) { prefix = idName + u'.'; continue; } @@ -3058,6 +3075,20 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) QQmlJSScope::resolveTypes( m_currentScope, m_rootScopeImports.contextualTypes(), &m_usedTypes); } +} + +bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) +{ + // ... __styleData: QtObject {...} + + Q_ASSERT(uiob->qualifiedTypeNameId); + + const QString typeName = buildName(uiob->qualifiedTypeNameId); + if (typeName.front().isLower() && typeName.contains(u'.')) { + logLowerCaseImport(typeName, uiob->qualifiedTypeNameId->identifierToken, m_logger); + } + + createAttachedAndGroupedScopes(uiob->qualifiedId); enterEnvironment(QQmlSA::ScopeType::QMLScope, typeName, uiob->qualifiedTypeNameId->identifierToken); @@ -3067,24 +3098,18 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) return true; } -void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) +int QQmlJSImportVisitor::openAttachedAndGroupedScopes(UiQualifiedId *propertyName) { - QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), &m_usedTypes); - // must be mutable, as we might mark it as implicitly wrapped in a component - const QQmlJSScope::Ptr childScope = m_currentScope; - leaveEnvironment(); - - auto group = uiob->qualifiedId; - int scopesEnteredCounter = 0; - QString prefix; + int scopesEnteredCounter = 0; + auto group = propertyName; for (; group->next; group = group->next) { const QString idName = group->name.toString(); if (idName.isEmpty()) break; - if (group == uiob->qualifiedId && isImportPrefix(idName)) { + if (group == propertyName && isImportPrefix(idName)) { prefix = idName + u'.'; continue; } @@ -3099,10 +3124,23 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) prefix.clear(); } + return scopesEnteredCounter; +} + +void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) +{ + QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports.contextualTypes(), &m_usedTypes); + // must be mutable, as we might mark it as implicitly wrapped in a component + const QQmlJSScope::Ptr childScope = m_currentScope; + leaveEnvironment(); + + const int scopesEnteredCounter = openAttachedAndGroupedScopes(uiob->qualifiedId); // on ending the visit to UiObjectBinding, set the property type to the // just-visited one if the property exists and this type is valid + auto group = uiob->qualifiedId; + for (; group->next; group = group->next) { } const QString propertyName = group->name.toString(); if (m_currentScope->isNameDeferred(propertyName)) { diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor_p.h index 645261d24a..5616346901 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsimportvisitor_p.h @@ -43,16 +43,9 @@ struct QQmlJSResourceFileMapper; class Q_QMLCOMPILER_EXPORT QQmlJSImportVisitor : public QQmlJS::AST::Visitor { public: - QQmlJSImportVisitor(const QQmlJSScope::Ptr &target, - QQmlJSImporter *importer, QQmlJSLogger *logger, - const QString &implicitImportDirectory, - const QStringList &qmldirFiles = QStringList()); QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, - const QStringList &qmldirFiles = QStringList()) - : QQmlJSImportVisitor(QQmlJSScope::create(), importer, logger, - implicitImportDirectory, qmldirFiles) - {} + const QStringList &qmldirFiles = QStringList()); ~QQmlJSImportVisitor(); using QQmlJS::AST::Visitor::endVisit; @@ -67,6 +60,7 @@ public: QQmlJSImporter::ImportedTypes imports() const { return m_rootScopeImports; } QQmlJSScopesById addressableScopes() const { return m_scopesById; } + QDuplicateTracker *knownUnresolvedTypes() { return &m_unresolvedTypes; } QHash signalHandlers() const { return m_signalHandlers; @@ -161,6 +155,8 @@ protected: virtual bool checkCustomParser(const QQmlJSScope::ConstPtr &scope); void setScopeName(QQmlJSScope::Ptr &scope, QQmlJSScope::ScopeType type, const QString &name); + void createAttachedAndGroupedScopes(QQmlJS::AST::UiQualifiedId *propertyName); + int openAttachedAndGroupedScopes(QQmlJS::AST::UiQualifiedId *propertyName); QString m_implicitImportDirectory; QStringList m_qmldirFiles; @@ -307,6 +303,7 @@ protected: void breakInheritanceCycles(const QQmlJSScope::Ptr &scope); void checkDeprecation(const QQmlJSScope::ConstPtr &scope); void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope); + void checkForComponentTypeWithProperties(const QQmlJSScope::ConstPtr &scope); bool rootScopeIsValid() const { return m_exportedRootScope->sourceLocation().isValid(); } enum class BindingExpressionParseResult { Invalid, Script, Literal, Translation }; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslinter.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslinter.cpp index ea149e14f5..77e35ea5f1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslinter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslinter.cpp @@ -681,6 +681,7 @@ QQmlJSLinter::lintFileImpl(const QString &filename, const QString *fileContents, contextPropertiesFor(filename, mapper ? &*mapper : nullptr, heuristicContextProperties) }; codegen.setTypeResolver(std::move(typeResolver)); + codegen.setKnownUnresolvedTypes(v.knownUnresolvedTypes()); using PassManagerPtr = std::unique_ptrsetCode(u""_s); m_logger->setSilent(silent || json); - const QQmlJSImporter::ImportedTypes types = m_importer.importModule(module); + const QQmlJSImporter::ImportedTypes types = + m_importer.importModule(module, QQmlJS::PrecedenceValues::Default); QList importWarnings = m_importer.takeGlobalWarnings() + types.warnings(); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen.cpp index fd1f61fe9d..2794e58f43 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen.cpp @@ -99,11 +99,11 @@ void QQmlJSLinterCodegen::analyzeFunction(const QV4::Compiler::Context *context, QQmlJSBasicBlocks(context, m_unitGenerator, &m_typeResolver, m_logger) .run(function, ValidateBasicBlocks, dummy); - blocksAndAnnotations = - QQmlJSTypePropagator(m_unitGenerator, &m_typeResolver, m_logger, - blocksAndAnnotations.basicBlocks, blocksAndAnnotations.annotations, - m_passManager, m_contextPropertyInfo) - .run(function); + QQmlJSTypePropagator lintTypePropgator( + m_unitGenerator, &m_typeResolver, m_logger, blocksAndAnnotations.basicBlocks, + blocksAndAnnotations.annotations, m_passManager, m_contextPropertyInfo); + lintTypePropgator.setKnownUnresolvedTypes(m_knownUnresolvedTypes); + blocksAndAnnotations = lintTypePropgator.run(function); if (m_logger->isCategoryIgnored(qmlCompiler)) return; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen_p.h index 2736cff0d4..5bf4c9953a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintercodegen_p.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,10 @@ public: { m_typeResolver = std::move(typeResolver); } + void setKnownUnresolvedTypes(QDuplicateTracker *tracker) + { + m_knownUnresolvedTypes = tracker; + } QQmlJSTypeResolver *typeResolver() { return &m_typeResolver; } @@ -70,6 +75,7 @@ private: void analyzeFunction(const QV4::Compiler::Context *context, QQmlJSCompilePass::Function *function); ContextPropertyInfo m_contextPropertyInfo; + QDuplicateTracker *m_knownUnresolvedTypes = nullptr; }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor.cpp index b8812eaf2c..7ee5370a4b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor.cpp @@ -546,6 +546,12 @@ void LinterVisitor::handleLiteralBinding(const QQmlJSMetaPropertyBinding &bindin } } +void LinterVisitor::endVisit(UiProgram *ast) +{ + QQmlJSImportVisitor::endVisit(ast); + checkFileSelections(); +} + static constexpr QLatin1String s_method = "method"_L1; static constexpr QLatin1String s_signal = "signal"_L1; static constexpr QLatin1String s_property = "property"_L1; @@ -685,6 +691,63 @@ bool LinterVisitor::visit(FunctionDeclaration *fdecl) return QQmlJSImportVisitor::visit(fdecl); } +/* This is a _rough_ heuristic; only meant for qmllint to avoid warnings about common constructs. + We might want to improve it in the future if it causes issues +*/ +static bool compatibilityHeuristicForFileSelector(const QQmlJSScope::ConstPtr &scope1, + const QQmlJSScope::ConstPtr &scope2) +{ + for (const auto &[propertyName, prop] : scope1->properties().asKeyValueRange()) + if (!scope2->hasProperty(propertyName)) + return false; + for (const auto &[methodName, method] : scope1->methods().asKeyValueRange()) + if (!scope2->hasMethod(methodName)) + return false; + return true; +} + +// heuristic to check file selected files for "compability" to the unselected file. +void LinterVisitor::checkFileSelections() +{ + const QQmlJS::FileSelectorInfo info = + m_rootScopeImports.contextualTypes().fileSelectorInfoFor(m_exportedRootScope); + + if (info.fileSelectedTypes.isEmpty() || info.mainType.isNull()) + return; + + const QString name = m_rootScopeImports.name(m_exportedRootScope); + + if (info.mainType == m_exportedRootScope) { + // current has fileselectors -> check all fileselectors for compatiblity + for (const auto &fileSelected : info.fileSelectedTypes) { + if (compatibilityHeuristicForFileSelector(m_exportedRootScope, + fileSelected.type.scope)) { + m_logger->log( + "Type %1 is ambiguous due to file selector usage, ignoring %2."_L1.arg( + name, fileSelected.type.scope->filePath()), + qmlImportFileSelector, m_exportedRootScope->sourceLocation()); + continue; + } + m_logger->log("Type %1 has a potentially incompatible file-selected variant %2."_L1.arg( + name, fileSelected.type.scope->filePath()), + qmlImport, m_exportedRootScope->sourceLocation()); + } + return; + } + + // current is fileselected -> only check against "main" type for compatibility + if (compatibilityHeuristicForFileSelector(info.mainType, m_exportedRootScope)) { + m_logger->log( + "File-selected type %1 is ambiguous due to file selector usage, this file will be ignored in favour of %2."_L1 + .arg(name, info.mainType->filePath()), + qmlImportFileSelector, m_exportedRootScope->sourceLocation()); + return; + } + m_logger->log("File-selected type %1 is potentially incompatible with %2."_L1.arg( + name, info.mainType->filePath()), + qmlImport, m_exportedRootScope->sourceLocation()); +} + } // namespace QQmlJS QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor_p.h index 210fb01207..5952d9ad35 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljslintervisitor_p.h @@ -19,6 +19,8 @@ #include +#include + QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -59,6 +61,8 @@ protected: bool visit(QQmlJS::AST::FunctionExpression *fexpr) override; bool visit(QQmlJS::AST::UiPublicMember *publicMember) override; + void endVisit(QQmlJS::AST::UiProgram *ast) override; + private: struct SeenImport { @@ -101,6 +105,7 @@ private: const QQmlJS::AST::UiPublicMember *associatedPropertyDefinition = nullptr) override; void handleLiteralBinding(const QQmlJSMetaPropertyBinding &binding, const AST::UiPublicMember *associatedPropertyDefinition) override; + void checkFileSelections(); }; } // namespace QQmlJS diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope.cpp index 35d0b1aa9d..ad8f61e9a9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope.cpp @@ -57,6 +57,30 @@ QQmlJSScope::Ptr QQmlJSScope::clone(const ConstPtr &origin) return cloned; } +/*! +\internal +Prepares the scope to be used by QQmlJSImportVisitor: we don't want to have "left-overs" from a +potential previous import visitor run, so remove all information except for the information set +by the qqmljsimporter (internalName, moduleName and isSingleton). + */ +QQmlJSScope::Ptr QQmlJSScope::resetForReparse(Ptr &&scope) +{ + auto *factory = scope.factory(); + if (!factory) { + const QString moduleName = scope->moduleName(); + *scope = QQmlJSScope{ scope->internalName() }; + scope->setOwnModuleName(moduleName); + return std::move(scope); + } + + const QString moduleName = factory->moduleName(); + const QString internalName = factory->internalName(); + *scope.factory() = QQmlJSScope::ConstPtr::Factory{ }; + scope->setOwnModuleName(moduleName); + scope->setInternalName(internalName); + return std::move(scope); +} + /*! \internal Return all the JavaScript identifiers defined in the current scope. @@ -715,7 +739,8 @@ void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::C listType->setFilePath(self->filePath()); } - const QQmlJSImportedScope element = {self, QTypeRevision()}; + const QQmlJS::ContextualType element = { self, QTypeRevision(), + QQmlJS::PrecedenceValues::Default }; const QQmlJSImportedScope array = {arrayType, QTypeRevision()}; QQmlJS::ContextualTypes contextualTypes( QQmlJS::ContextualTypes::INTERNAL, @@ -1185,17 +1210,19 @@ bool QQmlJSScope::Export::isValid() const return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); } -QDeferredFactory::QDeferredFactory(QQmlJSImporter *importer, const QString &filePath, - const TypeReader &typeReader) - : m_filePath(filePath), - m_importer(importer), +QDeferredFactory::QDeferredFactory(QQmlJSImporter *importer, const TypeReader &typeReader, + const QString &filePath, const QString &moduleName, bool isSingleton) + : m_importer(importer), m_typeReader(typeReader ? typeReader : [](QQmlJSImporter *importer, const QString &filePath, const QSharedPointer &scopeToPopulate) { QQmlJSTypeReader defaultTypeReader(importer, filePath); defaultTypeReader(scopeToPopulate); return defaultTypeReader.errors(); - }) + }), + m_filePath(filePath), + m_moduleName(moduleName), + m_isSingleton(isSingleton) { } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope_p.h index 7417a9b110..ba89995e84 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscope_p.h @@ -29,9 +29,11 @@ #include #include #include -#include + #include "qqmlsaconstants.h" +#include + #include QT_BEGIN_NAMESPACE @@ -153,6 +155,7 @@ public: static QQmlJSScope::Ptr create() { return QSharedPointer(new QQmlJSScope); } static QQmlJSScope::Ptr clone(const QQmlJSScope::ConstPtr &origin); + static QQmlJSScope::Ptr resetForReparse(QQmlJSScope::Ptr &&scope); static void cloneInto(QQmlJSScope::Ptr &origin, const QQmlJSScope::Ptr &target) @@ -160,13 +163,6 @@ public: *target = std::move(*clone(origin)); } - //! \internal This should probably restricted, only needed in prepareTargetForVisit - void resetForReparse() { - const QString moduleName = this->moduleName(); - *this = QQmlJSScope { this->internalName() }; - setOwnModuleName(moduleName); - } - static QQmlJSScope::ConstPtr findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope); QQmlJSScope::Ptr parentScope(); @@ -654,8 +650,8 @@ public: const QSharedPointer &scopeToPopulate)>; QDeferredFactory() = default; - QDeferredFactory(QQmlJSImporter *importer, const QString &filePath, - const TypeReader &typeReader = {}); + QDeferredFactory(QQmlJSImporter *importer, const TypeReader &typeReader, + const QString &filePath, const QString &moduleName, bool isSingleton); bool isValid() const { @@ -670,6 +666,8 @@ public: QString filePath() const { return m_filePath; } QQmlJSImporter* importer() const { return m_importer; } + QString moduleName() const { return m_moduleName; } + bool isSingleton() const { return m_isSingleton; } void setIsSingleton(bool isSingleton) { @@ -687,11 +685,11 @@ private: // Should only be called when lazy-loading the type in a deferred pointer. void populate(const QSharedPointer &scope) const; - QString m_filePath; QQmlJSImporter *m_importer = nullptr; - bool m_isSingleton = false; - QString m_moduleName; TypeReader m_typeReader; + QString m_filePath; + QString m_moduleName; + bool m_isSingleton = false; }; using QQmlJSExportedScope = QQmlJSScope::ExportedScope; @@ -712,6 +710,28 @@ constexpr inline bool isFunctionScope(ScopeType type) } +template +static void resetFactory(QDeferredSharedPointer &pointer, + QQmlJSImporter *importer, + const typename QDeferredFactory>::TypeReader &typeReader, + const QString &filePath) +{ + const auto &factory = pointer.factory(); + QDeferredFactory> newFactory(importer, + typeReader, filePath, + factory ? factory->moduleName() : QString(), + factory ? factory->isSingleton() : false); + return resetFactoryImpl(pointer, std::move(newFactory)); +} + +template +void resetFactoryImpl(QDeferredSharedPointer &pointer, U&& factory) +{ + pointer.m_data.reset(); + *pointer.m_factory = std::forward(factory); +} + + QT_END_NAMESPACE #endif // QQMLJSSCOPE_P_H diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscopesbyid_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscopesbyid_p.h index 2f17f0c95d..b28dd8fb99 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscopesbyid_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsscopesbyid_p.h @@ -78,6 +78,9 @@ public: void setValueTypesAreAddressable(bool addressable) { m_valueTypesAreAddressable = addressable; } bool valueTypesAreAddressable() const { return m_valueTypesAreAddressable; } + void setValueTypesAreAssertable(bool assertable) { m_valueTypesAreAssertable = assertable; } + bool valueTypesAreAssertable() const { return m_valueTypesAreAssertable; } + /*! \internal Find the possible IDs for \a scope as seen by \a referrer. There can be at most one @@ -315,6 +318,7 @@ private: bool m_componentsAreBound = false; bool m_signaturesAreEnforced = true; bool m_valueTypesAreAddressable = false; + bool m_valueTypesAreAssertable = false; }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader.cpp index 7cef6f183e..1ba42a5a21 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader.cpp @@ -10,6 +10,7 @@ #include #include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader_p.h index 5913f60d07..7401bfa7dc 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypedescriptionreader_p.h @@ -26,6 +26,8 @@ QT_BEGIN_NAMESPACE +class QTypeRevision; + class Q_QMLCOMPILER_EXPORT QQmlJSTypeDescriptionReader { Q_DECLARE_TR_FUNCTIONS(QQmlJSTypeDescriptionReader) diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator.cpp index 18fea6e059..16210af08e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator.cpp @@ -343,15 +343,12 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM } fixString += handler.isMultiline ? u") "_s : u") => "_s; - - suggestion = QQmlJSFixSuggestion { - name + u" is accessible in this scope because you are handling a signal" - " at %1:%2. Use a function instead.\n"_s - .arg(id.location.startLine) - .arg(id.location.startColumn), - fixLocation, - fixString - }; + suggestion = + QQmlJSFixSuggestion{ u"\"%1\" is ambiguous. " + "Use a function instead: %2%3"_s.arg( + name, fixString, + handler.isMultiline ? "{ ... }"_L1 : "..."_L1), + fixLocation, fixString }; suggestion->setAutoApplicable(); } break; @@ -372,8 +369,9 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM continue; if (it->objectType() == qmlScope) { suggestion = QQmlJSFixSuggestion { - name + " is implicitly injected into this delegate." - " Add a required property instead."_L1, + "'%1' is implicitly injected into this delegate. " + "Add a required property '%1' to the delegate instead."_L1 + .arg(name), qmlScope->sourceLocation() }; }; @@ -387,7 +385,8 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) { if (scope->hasProperty(name)) { QQmlJSScopesById::MostLikelyCallback id; - m_function->addressableScopes.possibleIds(scope, qmlScope, Default, id); + m_function->addressableScopes.possibleIds(scope, qmlScope, + QQmlJSScopesByIdOption::Default, id); QQmlJS::SourceLocation fixLocation = location; fixLocation.length = 0; @@ -691,7 +690,7 @@ void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex) return; } - if (!type.isWritable()) { + if (!type.isWritable() && !type.isList()) { addError(u"Can't assign to read-only property %1"_s.arg(name)); m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty, @@ -891,6 +890,20 @@ bool QQmlJSTypePropagator::handleImportNamespaceLookup(const QString &propertyNa return false; } +bool QQmlJSTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type) +{ + if (type->isFullyResolved() || type->isScript()) + return true; + + if (!m_knownUnresolvedTypes || !m_knownUnresolvedTypes->hasSeen(type)) { + m_logger->log(QStringLiteral("Type %1 is used but it is not resolved") + .arg(QQmlJSUtils::getScopeName(type, type->scopeType())), + qmlUnresolvedType, currentSourceLocation()); + } + + return false; +} + void QQmlJSTypePropagator::handleLookupError(const QString &propertyName) { const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn(); @@ -946,8 +959,10 @@ void QQmlJSTypePropagator::handleLookupError(const QString &propertyName) } } - m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName), - qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion); + if (checkTypeResolved(baseType)) { + m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName), + qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion); + } } void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex) @@ -1395,9 +1410,11 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar fixSuggestion = suggestion; } - m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg( - propertyName, callBase.containedTypeName()), - qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion); + if (baseType->isFullyResolved() || baseType->isScript()) { + m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg( + propertyName, callBase.containedTypeName()), + qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion); + } return; } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator_p.h index 8099c3584c..7129360fe6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstypepropagator_p.h @@ -15,6 +15,7 @@ // // We mean it. +#include #include #include #include @@ -184,6 +185,10 @@ struct Q_QMLCOMPILER_EXPORT QQmlJSTypePropagator : public QQmlJSCompilePass Verdict startInstruction(QV4::Moth::Instr::Type instr) override; void endInstruction(QV4::Moth::Instr::Type instr) override; + void setKnownUnresolvedTypes(QDuplicateTracker *knownUnresolvedTypes) + { + m_knownUnresolvedTypes = knownUnresolvedTypes; + } private: struct ExpectedRegisterState @@ -305,6 +310,7 @@ private: m_state.instructionHasError = true; } void warnAboutTypeCoercion(int lhs); + bool checkTypeResolved(const QQmlJSScope::ConstPtr &type); QQmlJSRegisterContent m_returnType; QQmlSA::PassManager *m_passManager = nullptr; @@ -315,6 +321,7 @@ private: InstructionAnnotations m_prevStateAnnotations; PassState m_state; ContextPropertyInfo m_contextPropertyInfo; + QDuplicateTracker *m_knownUnresolvedTypes = nullptr; }; QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver.cpp index 8de55bd070..909f88dbfb 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver.cpp @@ -13,6 +13,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -1508,9 +1509,11 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( if (from == m_variantListType) return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; - // We can convert anything that fits into QJSPrimitiveValue - if (canConvertFromTo(from, m_jsPrimitiveType) && canConvertFromTo(m_jsPrimitiveType, to)) + // We can convert anything that fits natively into QJSPrimitiveValue + if (canPrimitivelyConvertFromTo(from, m_jsPrimitiveType) + && canPrimitivelyConvertFromTo(m_jsPrimitiveType, to)) { return true; + } if (areEquivalentLists(from, to)) return true; @@ -1607,7 +1610,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType( return true; } } - + // don't look up enums on ids... + if (type.variant() == QQmlJSRegisterContent::ObjectById) + return false; return checkEnums(resultScope, name, &result); }; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver_p.h index cc4660b30c..ec6bda3445 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljstyperesolver_p.h @@ -109,7 +109,7 @@ public: return m_imports.hasType(name) && !m_imports.type(name).scope; } - const QHash> &importedTypes() const + const QHash &importedTypes() const { return m_imports.types(); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils.cpp index 2aa09b1834..631733f726 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils.cpp @@ -386,5 +386,18 @@ QString QQmlJSUtils::getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScop return scope->baseTypeName(); } +QString QQmlJSUtils::fileSelectorFor(const QQmlJSScope::ConstPtr &scope) +{ + if (!scope) + return { }; + const auto factory = scope.factory(); + const QString filePath = factory ? factory->filePath() : scope->filePath(); + QString dir = QFileInfo(filePath).dir().dirName(); + if (dir.startsWith("+"_L1)) { + dir.slice(1); + return dir; + } + return { }; +} QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils_p.h index f4abab874d..cab1e15602 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmljsutils_p.h @@ -403,6 +403,7 @@ struct Q_QMLCOMPILER_EXPORT QQmlJSUtils const QString &pathInBuildFolder); static QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ScopeType type); + static QString fileSelectorFor(const QQmlJSScope::ConstPtr &scope1); }; bool Q_QMLCOMPILER_EXPORT canStrictlyCompareWithVar( diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.cpp index 0da46f32ad..3ce7e3d891 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.cpp @@ -1215,8 +1215,8 @@ bool Element::operatorEqualsImpl(const Element &lhs, const Element &rhs) } /*! - \fn friend inline qsizetype Element::qHash(const Element &key, qsizetype seed) noexcept - Returns the hash for \a key using \a seed to seed the calculation. + \fn size_t Element::qHash(const Element &key, size_t seed) + \qhash{Element} */ qsizetype Element::qHashImpl(const Element &key, qsizetype seed) noexcept @@ -1338,7 +1338,8 @@ Element GenericPass::resolveType(QAnyStringView moduleName, QAnyStringView typeN { Q_D(const GenericPass); QQmlJSImporter *typeImporter = PassManagerPrivate::visitor(*d->m_manager)->importer(); - const auto module = typeImporter->importModule(moduleName.toString()); + const auto module = + typeImporter->importModule(moduleName.toString(), QQmlJS::PrecedenceValues::Default); const auto scope = module.type(typeName.toString()).scope; return QQmlJSScope::createQQmlSAElement(scope); } @@ -1394,7 +1395,7 @@ Element GenericPass::resolveIdToElement(QAnyStringView id, const Element &contex Q_D(const GenericPass); QQmlJSScopesById::MostLikelyCallback result; PassManagerPrivate::visitor(*d->m_manager)->addressableScopes().possibleScopes( - id.toString(), QQmlJSScope::scope(context), Default, result); + id.toString(), QQmlJSScope::scope(context), QQmlJSScopesByIdOption::Default, result); return QQmlJSScope::createQQmlSAElement(result.result); } @@ -1406,7 +1407,7 @@ QString GenericPass::resolveElementToId(const Element &element, const Element &c Q_D(const GenericPass); QQmlJSScopesById::MostLikelyCallback result; PassManagerPrivate::visitor(*d->m_manager)->addressableScopes().possibleIds( - QQmlJSScope::scope(element), QQmlJSScope::scope(context), Default, result); + QQmlJSScope::scope(element), QQmlJSScope::scope(context), QQmlJSScopesByIdOption::Default, result); return result.result; } @@ -1525,7 +1526,8 @@ bool PassManagerPrivate::registerPropertyPass(std::shared_ptr pass QString name; if (!moduleName.isEmpty() && !typeName.isEmpty()) { auto typeImporter = m_visitor->importer(); - auto module = typeImporter->importModule(moduleName.toString()); + auto module = typeImporter->importModule(moduleName.toString(), + QQmlJS::PrecedenceValues::Default); auto element = QQmlJSScope::createQQmlSAElement(module.type(typeName.toString()).scope); if (element.isNull()) diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.h b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.h index d3662db982..bb84973fc4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlcompiler/qqmlsa.h @@ -257,13 +257,19 @@ public: } friend inline bool operator!=(const Element &lhs, const Element &rhs) { return !(lhs == rhs); } - friend inline qsizetype qHash(const Element &key, qsizetype seed = 0) noexcept + friend size_t qHash(const Element &key, size_t seed = 0) noexcept { - return qHashImpl(key, seed); +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + // this used the 1-to-2-arg adapter before: + return Element::qHashImpl(key, 0) ^ seed; +#else + return Element::qHashImpl(key, seed); +#endif } private: static bool operatorEqualsImpl(const Element &, const Element &); + // ### Qt 6.12: change member to size_t static qsizetype qHashImpl(const Element &key, qsizetype seed) noexcept; static constexpr qsizetype sizeofElement = 2 * sizeof(QSharedPointer); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventlocation_p.h b/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventlocation_p.h index 3f75ce4359..7e27b77d89 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventlocation_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventlocation_p.h @@ -50,6 +50,9 @@ private: friend QDataStream &operator>>(QDataStream &stream, QQmlProfilerEventLocation &location); friend QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEventLocation &location); + friend size_t qHash(const QQmlProfilerEventLocation &key, size_t seed = 0) noexcept + { return qHashMulti(seed, key.m_filename, key.m_line, key.m_column); } + QString m_filename; int m_line; int m_column; @@ -69,14 +72,6 @@ inline bool operator!=(const QQmlProfilerEventLocation &location1, return !(location1 == location2); } -inline size_t qHash(const QQmlProfilerEventLocation &location) -{ - return qHash(location.filename()) - ^ ((location.line() & 0xfff) // 12 bits of line number - | ((location.column() << 16) & 0xff0000)); // 8 bits of column - -} - QDataStream &operator>>(QDataStream &stream, QQmlProfilerEventLocation &location); QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEventLocation &location); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventtype_p.h b/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventtype_p.h index 4b7335f4f8..339af48152 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventtype_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmldebug/qqmlprofilereventtype_p.h @@ -51,6 +51,9 @@ private: friend QDataStream &operator>>(QDataStream &stream, QQmlProfilerEventType &type); friend QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEventType &type); + friend size_t qHash(const QQmlProfilerEventType &key, size_t seed = 0) noexcept + { return qHashMulti(seed, key.m_location, key.m_message, key.m_rangeType, key.m_detailType); } + QString m_displayName; QString m_data; QQmlProfilerEventLocation m_location; @@ -62,14 +65,6 @@ private: QDataStream &operator>>(QDataStream &stream, QQmlProfilerEventType &type); QDataStream &operator<<(QDataStream &stream, const QQmlProfilerEventType &type); -inline size_t qHash(const QQmlProfilerEventType &type) -{ - return qHash(type.location()) - ^ (((type.message() << 12) & 0xf000) // 4 bits message - | ((type.rangeType() << 24) & 0xf000000) // 4 bits rangeType - | ((static_cast(type.detailType()) << 28) & 0xf0000000)); // 4 bits detailType -} - inline bool operator==(const QQmlProfilerEventType &type1, const QQmlProfilerEventType &type2) { return type1.message() == type2.message() && type1.rangeType() == type2.rangeType() diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator.cpp index babc7a8c08..c088b5e364 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator.cpp @@ -786,6 +786,7 @@ bool QQmlDomAstCreatorBase::visit(AST::FunctionDeclaration *fDef) } m.access = MethodInfo::Public; m.methodType = MethodInfo::Method; + m.isGenerator = fDef->isGenerator; SourceLocation bodyLoc = fDef->body ? combineLocations(fDef->body) : combineLocations(fDef->lbraceToken, fDef->rbraceToken); @@ -3276,17 +3277,14 @@ static QStringList qmldirFilesFrom(MutableDomItem &qmlFile) return {}; } -QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, - MutableDomItem &qmlFile, +QQmlDomAstCreatorWithQQmlJSScope::QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger, QQmlJSImporter *importer) - : m_root(current), - m_logger(logger), + : m_logger(logger), m_importer(importer), m_implicitImportDirectory(QQmlJSImportVisitor::implicitImportDirectory( m_logger->filePath(), m_importer->resourceFileMapper())), - m_scopeCreator(m_root, m_importer, m_logger, m_implicitImportDirectory, - qmldirFilesFrom(qmlFile)), + m_scopeCreator(m_importer, m_logger, m_implicitImportDirectory, qmldirFilesFrom(qmlFile)), m_domCreator(qmlFile) { } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator_p.h b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator_p.h index 9ac570d3d4..1610b93996 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomastcreator_p.h @@ -622,8 +622,8 @@ class QQmlDomAstCreator final : public QQmlDomAstCreatorBase class QQmlDomAstCreatorWithQQmlJSScope : public AST::Visitor { public: - QQmlDomAstCreatorWithQQmlJSScope(const QQmlJSScope::Ptr ¤t, MutableDomItem &qmlFile, - QQmlJSLogger *logger, QQmlJSImporter *importer); + QQmlDomAstCreatorWithQQmlJSScope(MutableDomItem &qmlFile, QQmlJSLogger *logger, + QQmlJSImporter *importer); #define X(name) \ bool visit(AST::name *) override; \ @@ -747,7 +747,6 @@ private: m_scopeCreator.endVisit(t); } - QQmlJSScope::Ptr m_root; QQmlJSLogger *m_logger = nullptr; QQmlJSImporter *m_importer = nullptr; QString m_implicitImportDirectory; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter.cpp index 017cef80b5..bc41cced7b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter.cpp @@ -126,6 +126,9 @@ bool FormatPartialStatus::tryInsideExpression(bool alsoExpression) case QQmlJSGrammar::T_FUNCTION: newState = StateType::FunctionStart; break; + case QQmlJSGrammar::T_ARROW: + newState = StateType::LambdaStart; + break; case QQmlJSGrammar::T_QUESTION: newState = StateType::TernaryOp; break; @@ -624,6 +627,16 @@ void FormatPartialStatus::handleTokens() } break; + case StateType::LambdaStart: + switch (kind) { + case QQmlJSGrammar::T_LBRACE: + turnInto(StateType::JsblockOpen); + break; + default: + turnInto(StateType::Expression); + } + break; + case StateType::TypeAnnotation: switch (kind) { case QQmlJSGrammar::T_IDENTIFIER: @@ -1293,6 +1306,7 @@ void FormatPartialStatus::defaultOnEnter(StateType newState, int *indentDepth, } break; + case StateType::LambdaStart: case StateType::FunctionStart: // align to the beginning of the line *savedIndentDepth = *indentDepth = column(tokenAt(0).begin()); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter_p.h b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter_p.h index 8d1d995a02..a6ae904a19 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcodeformatter_p.h @@ -71,6 +71,7 @@ public: SignalMaybeArglist, // after identifier SignalArglistOpen, // after '(' + LambdaStart, // after '=>' FunctionStart, // after 'function' FunctionArglistOpen, // after '(' starting function argument list FunctionArglistClosed, // after ')' in argument list, expecting '{' diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcomments.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcomments.cpp index f188eeeff8..e838fe2bc8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcomments.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomcomments.cpp @@ -394,6 +394,27 @@ public: return true; } + bool visit(ArrayPattern *array) override + { + addSourceLocations(array, array->lbracketToken); + addSourceLocations(array, array->commaToken); + addSourceLocations(array, array->rbracketToken); + return true; + } + + bool visit(ObjectPattern *object) override + { + addSourceLocations(object, object->lbraceToken); + addSourceLocations(object, object->rbraceToken); + return true; + } + + bool visit(Elision *elision) override + { + addSourceLocations(elision, elision->commaToken); + return true; + } + bool visit(CaseClause *caseClause) override { // special case: case clauses can have comments attached to their `:` token @@ -437,6 +458,7 @@ public: bool visit(FunctionExpression *fExpr) override { addSourceLocations(fExpr, fExpr->identifierToken); + addSourceLocations(fExpr, fExpr->starToken); addSourceLocations(fExpr, fExpr->lparenToken); addSourceLocations(fExpr, fExpr->rparenToken); addSourceLocations(fExpr, fExpr->lbraceToken); @@ -490,6 +512,27 @@ public: return true; } + bool visit(TryStatement *tryStatement) override + { + addSourceLocations(tryStatement, tryStatement->tryToken); + return true; + } + + bool visit(Catch *catchStatement) override + { + addSourceLocations(catchStatement, catchStatement->catchToken); + addSourceLocations(catchStatement, catchStatement->lparenToken); + addSourceLocations(catchStatement, catchStatement->identifierToken); + addSourceLocations(catchStatement, catchStatement->rparenToken); + return true; + } + + bool visit(Finally *finallyStatement) override + { + addSourceLocations(finallyStatement, finallyStatement->finallyToken); + return true; + } + QMap starts; QMap ends; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements.cpp index 70be5397b0..bfcb499360 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements.cpp @@ -1963,7 +1963,10 @@ void MethodInfo::writeOut(const DomItem &self, OutWriter &ow) const if (methodType == MethodType::Signal) { ow.writeRegion(fLoc, SignalKeywordRegion).ensureSpace(); } else { - ow.writeRegion(fLoc, FunctionKeywordRegion).ensureSpace(); + ow.writeRegion(fLoc, FunctionKeywordRegion); + if (isGenerator) + ow.writeRegion(fLoc, StarTokenRegion); + ow.ensureSpace(); } ow.writeRegion(fLoc, IdentifierRegion, name); writeOutArguments(self, ow); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements_p.h b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements_p.h index 063f468903..15d4ba74db 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomelements_p.h @@ -739,6 +739,7 @@ public: std::shared_ptr body; std::shared_ptr returnType; bool isConstructor = false; + bool isGenerator = false; private: void writeOutArguments(const DomItem &self, OutWriter &ow) const; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomexternalitems.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomexternalitems.cpp index 2f80cb994a..f563319ed1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomexternalitems.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomexternalitems.cpp @@ -497,6 +497,7 @@ DomItem QmlFile::field(const DomItem &self, QStringView name) const void QmlFile::addError(const DomItem &self, ErrorMessage &&msg) { + m_isValid = false; self.containingObject().addError(std::move(msg)); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomreformatter.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomreformatter.cpp index 1e7da6a63e..b76d613c74 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomreformatter.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomreformatter.cpp @@ -132,33 +132,33 @@ bool ScriptFormatter::visit(RegExpLiteral *ast) bool ScriptFormatter::visit(ArrayPattern *ast) { - out(ast->lbracketToken); + outWithComments(ast->lbracketToken, ast); int baseIndent = lw.increaseIndent(1); if (ast->elements) { accept(ast->elements); - out(ast->commaToken); + outWithComments(ast->commaToken, ast); auto lastElement = lastListElement(ast->elements); if (lastElement->element && cast(lastElement->element->initializer)) { ensureNewline(); } } else { - out(ast->commaToken); + outWithComments(ast->commaToken, ast); } lw.decreaseIndent(1, baseIndent); - out(ast->rbracketToken); + outWithComments(ast->rbracketToken, ast); return false; } bool ScriptFormatter::visit(ObjectPattern *ast) { - out(ast->lbraceToken); + outWithComments(ast->lbraceToken, ast); ++expressionDepth; if (ast->properties) { lnAcceptIndented(ast->properties); ensureNewline(); } --expressionDepth; - out(ast->rbraceToken); + outWithComments(ast->rbraceToken, ast); return false; } @@ -172,10 +172,6 @@ bool ScriptFormatter::visit(PatternElementList *ast) if (it->elision) accept(it->elision); - if (it->elision && it->element) { - outWithComments(it->elision->commaToken, it); - ensureSpaceIfNoComment(); - } if (it->element) accept(it->element); if (it->next) { @@ -813,7 +809,7 @@ bool ScriptFormatter::visit(LabelledStatement *ast) bool ScriptFormatter::visit(TryStatement *ast) { - out("try"); // ast->tryToken + outWithComments(ast->tryToken, ast); ensureSpaceIfNoComment(); accept(ast->statement); if (ast->catchExpression) { @@ -829,11 +825,11 @@ bool ScriptFormatter::visit(TryStatement *ast) bool ScriptFormatter::visit(Catch *ast) { - out(ast->catchToken); + outWithComments(ast->catchToken, ast); ensureSpaceIfNoComment(); - out(ast->lparenToken); - out(ast->identifierToken); - out(")"); // ast->rparenToken + outWithComments(ast->lparenToken, ast); + outWithComments(ast->identifierToken, ast); + outWithComments(ast->rparenToken, ast); ensureSpaceIfNoComment(); accept(ast->statement); return false; @@ -841,7 +837,7 @@ bool ScriptFormatter::visit(Catch *ast) bool ScriptFormatter::visit(Finally *ast) { - out("finally"); // ast->finallyToken + outWithComments(ast->finallyToken, ast); ensureSpaceIfNoComment(); accept(ast->statement); return false; @@ -855,13 +851,10 @@ bool ScriptFormatter::visit(FunctionDeclaration *ast) bool ScriptFormatter::visit(FunctionExpression *ast) { if (!ast->isArrowFunction) { - if (ast->isGenerator) { - out("function*"); - ensureSpaceIfNoComment(); - } else { - out("function"); - ensureSpaceIfNoComment(); - } + outWithComments(ast->functionToken, ast); + if (ast->isGenerator) + outWithComments(ast->starToken, ast); + ensureSpaceIfNoComment(); outWithComments(ast->identifierToken, ast); } @@ -904,8 +897,8 @@ bool ScriptFormatter::visit(FunctionExpression *ast) bool ScriptFormatter::visit(Elision *ast) { for (Elision *it = ast; it; it = it->next) { - if (it->next) { - out(","); // ast->commaToken + if (ast->commaToken.isValid()) { + outWithComments(ast->commaToken, ast); ensureSpaceIfNoComment(); } } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomtop.cpp b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomtop.cpp index 263b3ff007..839ab3b462 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomtop.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmldom/qqmldomtop.cpp @@ -1938,11 +1938,9 @@ void DomEnvironment::addQmlFile(const std::shared_ptr &file, AddOption semanticAnalysis().m_importer->importFile(file->canonicalFilePath()); // force reset the outdated qqmljsscope in case it was already populated - QDeferredFactory newFactory(semanticAnalysis().m_importer.get(), - file->canonicalFilePath(), - TypeReader{ weak_from_this(), m_loadPaths }); file->setHandleForPopulation(handle); - handle.resetFactory(std::move(newFactory)); + resetFactory(handle, semanticAnalysis().m_importer.get(), + TypeReader{ weak_from_this(), m_loadPaths }, file->canonicalFilePath()); } } @@ -2112,10 +2110,8 @@ bool DomEnvironment::commitToBase( if (!oldFactory) continue; - const QDeferredFactory newFactory( - oldFactory->importer(), oldFactory->filePath(), - TypeReader{ newBaseForPopulation, m_loadPaths }); - handle.resetFactory(newFactory); + resetFactory(handle, oldFactory->importer(), + TypeReader{ newBaseForPopulation, m_loadPaths }, oldFactory->filePath()); } return true; } @@ -2297,9 +2293,8 @@ void DomEnvironment::populateFromQmlFile(MutableDomItem &&qmlFile) if (m_domCreationOption == DomCreationOption::Extended) { SemanticAnalysis analysis = semanticAnalysis(); - auto scope = analysis.m_importer->importFile(qmlFile.canonicalFilePath()); - auto v = std::make_unique( - scope, qmlFile, logger.get(), analysis.m_importer.get()); + auto v = std::make_unique(qmlFile, logger.get(), + analysis.m_importer.get()); v->enableLoadFileLazily(true); v->enableScriptExpressions(true); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlformat/qqmlformatoptions.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlformat/qqmlformatoptions.cpp index d71cda3046..d0baa58422 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlformat/qqmlformatoptions.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlformat/qqmlformatoptions.cpp @@ -231,8 +231,9 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & "width"_L1, "-1"_L1); parser.addOption(columnWidthOption); parser.addOption(QCommandLineOption({ "n"_L1, "normalize"_L1 }, - QStringLiteral("Reorders the attributes of the objects " - "according to the QML Coding Guidelines."))); + QStringLiteral("Reorders and sorts the attributes of the objects " + "according to the QML Coding Guidelines. " + "Incompatible with --group-attributes-together."))); parser.addOption(QCommandLineOption( @@ -242,17 +243,17 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & parser.addOption(QCommandLineOption( QStringList() << "objects-spacing"_L1, - QStringLiteral("Ensure spaces between objects (only works with normalize option)."))); + QStringLiteral("Ensure spaces between objects (only works with normalize or group-attributes-together option)."))); parser.addOption(QCommandLineOption( QStringList() << "functions-spacing"_L1, - QStringLiteral("Ensure spaces between functions (only works with normalize option)."))); + QStringLiteral("Ensure spaces between functions (only works with normalize or group-attributes-together option)."))); parser.addOption( QCommandLineOption(QStringList() << "group-attributes-together"_L1, - QStringLiteral("Reorders and groups the attributes of the objects " + QStringLiteral("Reorders but does not sort the attributes of the objects " "according to the QML Coding Guidelines. " - "Implies --normalize."))); + "Incompatible with --normalize."))); parser.addOption( QCommandLineOption({ "S"_L1, "sort-imports"_L1 }, @@ -262,7 +263,7 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & parser.addOption(QCommandLineOption( QStringList() << "single-line-empty-objects"_L1, - QStringLiteral("Write empty objects on a single line (only works with normalize option)."))); + QStringLiteral("Write empty objects on a single line (only works with normalize or group-attributes-together option)."))); QCommandLineOption semicolonRuleOption( QStringList() << "semicolon-rule"_L1, @@ -364,6 +365,8 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & // Needs to be set after normalize since it can set NormalizeOrder to true. if (parser.isSet("group-attributes-together"_L1)) { options.mark(Settings::GroupAttributesTogether); + // group attributes together implies Normalize, so don't let (default) settings change the normalization option. + options.mark(Settings::NormalizeOrder); options.setGroupAttributesTogether(true); } if (parser.isSet("sort-imports"_L1)) { diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmlcodemodelmanager.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmlcodemodelmanager.cpp index 3f9e89cbb3..f1cfdb984c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmlcodemodelmanager.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmlcodemodelmanager.cpp @@ -62,13 +62,14 @@ QQmlCodeModelManager::QQmlCodeModelManager(QObject *parent, QQmlToolingSharedSet void QQmlCodeModelManager::onBuildFinished(const QByteArray &url) { auto it = findWorkspace(url); - if (it == m_workspaces.end()) + // note: fallback workspaces have no build folder information + if (it == fallbackWorkspace() || it == m_workspaces.end()) return; // refresh information obtained from build folder, like .qmlls.build.ini or resource files const QStringList buildPaths = it->codeModel->buildPaths(); m_buildInformation.loadSettingsFrom(buildPaths, ForceUpdate); - setBuildPathsOn(&*it, buildPaths, DontAppendPathsFromFallback); + setBuildPathsOn(&*it, buildPaths, AppendPathsFromFallback); } void QQmlCodeModelManager::prepareForShutdown() diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmldiffer.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmldiffer.cpp index 667fccff1a..40ee7997e5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmldiffer.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmldiffer.cpp @@ -16,6 +16,9 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/ #include +#include +#include + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllshelputils.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllshelputils.cpp index 53c461ce93..4ac9e239ac 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllshelputils.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllshelputils.cpp @@ -10,6 +10,9 @@ #include #include #include + +#include + #include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllsutils.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllsutils.cpp index 163ea485d5..7ed646c17d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllsutils.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlls/qqmllsutils.cpp @@ -1138,7 +1138,7 @@ propertyFromReferrerScope(const QQmlJSScope::ConstPtr &referrerScope, const QStr switch (options) { case ResolveOwnerType: return ExpressionType{ propertyName, - findDefiningScopeForProperty(current, propertyName), + findDefiningScopeForProperty(current, resolved->name), resolved->type }; case ResolveActualTypeForFieldMemberExpression: return ExpressionType{ propertyName, property.type(), resolved->type }; @@ -1491,6 +1491,15 @@ resolveSignalHandlerParameterType(const DomItem ¶meterDefinition, const QStr } } +/*! +\internal +resolve an identifier in the same order the QML engine would, that is: +* first check for local JS variables (`let x = ...`) +* then check for ids +* then check for properties +* then check for methods +* then check for properties and methods of the global object + */ static std::optional resolveIdentifierExpressionType(const DomItem &item, ResolveOptions options) { @@ -1522,10 +1531,6 @@ static std::optional resolveIdentifierExpressionType(const DomIt if (!referrerScope) return {}; - // check if its a method - if (auto scope = methodFromReferrerScope(referrerScope, name, options)) - return scope; - const auto qmlFile = item.containingFile().ownerAs(); if (!qmlFile) return {}; @@ -1534,6 +1539,12 @@ static std::optional resolveIdentifierExpressionType(const DomIt if (!resolver) return {}; + // check if its an id + if (const QQmlJSScope::ConstPtr fromId = + resolver->typeForId(referrerScope, name, AssumeComponentsAreBound)) { + return ExpressionType{ name, fromId, QmlObjectIdIdentifier }; + } + // check if its found as a property binding if (const auto scope = propertyBindingFromReferrerScope( referrerScope, name, options, resolver.get())) { @@ -1544,18 +1555,18 @@ static std::optional resolveIdentifierExpressionType(const DomIt if (const auto scope = propertyFromReferrerScope(referrerScope, name, options)) return *scope; + // check if its a method + if (auto scope = methodFromReferrerScope(referrerScope, name, options)) + return scope; + if (resolver->seenModuleQualifiers().contains(name)) return ExpressionType{ name, {}, QualifiedModuleIdentifier }; if (const auto scope = resolveTypeName(resolver, name, item, options)) return scope; - // check if its an id - if (const QQmlJSScope::ConstPtr fromId - = resolver->typeForId(referrerScope, name, AssumeComponentsAreBound)) { - return ExpressionType{ name, fromId, QmlObjectIdIdentifier }; - } - + // the global object doesn't have properties and methods with the same name, therefore the + // order in which we resolve methods and properties on the global object doesn't matter const QQmlJSScope::ConstPtr jsGlobal = resolver->jsGlobalObject(); // check if its a JS global method if (auto scope = methodFromReferrerScope(jsGlobal, name, options)) { @@ -1995,13 +2006,17 @@ static std::optional createCppTypeLocation(const QQmlJSScope::ConstPtr const QStringList &headerLocations, const QQmlJS::SourceLocation &location) { + const TextPosition endPosition{ static_cast(location.startLine) + 1, 1 }; + const QFileInfo filePathInfo(type->filePath()); + if (filePathInfo.isAbsolute() && filePathInfo.exists()) + return Location{ type->filePath(), location, endPosition }; + const QString filePath = findFilePathFromFileName(headerLocations, type->filePath(), {}); if (filePath.isEmpty()) { qCWarning(QQmlLSUtilsLog) << "Couldn't find the C++ file '%1'."_L1.arg(type->filePath()); return {}; } - const TextPosition endPosition{ static_cast(location.startLine) + 1, 1 }; return Location{ filePath, location, endPosition }; } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmeta/types/qqmlbind.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmeta/types/qqmlbind.cpp index a311ea3003..740c7318a1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmeta/types/qqmlbind.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmeta/types/qqmlbind.cpp @@ -424,13 +424,13 @@ void QQmlBindPrivate::validate(QQmlBind *q) const \qml // produces warning: "Unable to assign [undefined] to double value" - value: if (mouse.pressed) mouse.mouseX + targetProperty: if (mouse.pressed) mouse.mouseX \endqml The Binding type can prevent this warning. \qml - Binding on value { + Binding on targetProperty { when: mouse.pressed value: mouse.mouseX } @@ -439,6 +439,44 @@ void QQmlBindPrivate::validate(QQmlBind *q) const The Binding type restores any previously set direct bindings on the property. + However, the binding to \c{Binding}'s \l{value} property is still evaluated + unconditionally, even when \c{when} is \c{false}. + + This might cause warnings when the Binding is for example used to guard + against a value being \c{null}: + + \qml + Binding on targetProperty { + when: root.object !== null + // produces warning: "TypeError: Cannot read property 'someProperty' of null" + value: root.object.someProperty + } + \endqml + + There are to ways to avoid this issue: + \list + \li If the target has an \c id, you can use the syntax described in + \l{Multiple targets in one Binding}, which also causes the + binding expression to only run when the \c{Binding} is enabled: + \qml + Binding { + when: root.object !== null + root.targetProperty: root.object.someProperty + } + \endqml + + \li You can repeat the check in the binding and return early: + \qml + Binding on targetProperty { + when: root.object !== null + value: { + if (root.object === null) + return + return root.object.someProperty + } + } + \endqml + \endlist \section1 Multiple targets in one Binding You can specify multiple bindings to the same object in one Binding element: diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/CMakeLists.txt index 4cda96c2ed..2d6eee50c9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/CMakeLists.txt @@ -18,7 +18,6 @@ qt_internal_add_qml_module(QmlModels qqmlmodelsmodule.cpp qqmlmodelsmodule_p.h qtqmlmodelsglobal.h qtqmlmodelsglobal_p.h DEFINES - QT_NO_FOREACH QT_NO_INTEGER_EVENT_COORDINATES QT_NO_URL_CAST_FROM_STRING LIBRARIES diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/doc/qtqmlmodels.qdocconf b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/doc/qtqmlmodels.qdocconf index acce91f5b1..c0ad1c9bb7 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/doc/qtqmlmodels.qdocconf +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/doc/qtqmlmodels.qdocconf @@ -38,7 +38,10 @@ depends += qtcore qtqml qtquick qtdoc qtqmlworkerscript qtquickcontrols ../../labs/models exampledirs += .. \ - snippets + snippets \ + ../../../tests/auto/qml/qqmltreemodel/data \ + ../../../tests/manual/tableview/tablemodel/json + imagedirs += images diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmladaptormodel_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmladaptormodel_p.h index 2c177d5ddd..39ef121fd5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmladaptormodel_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmladaptormodel_p.h @@ -26,6 +26,8 @@ #include #include +#include + QT_REQUIRE_CONFIG(qml_delegate_model); QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlchangeset_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlchangeset_p.h index da1124f483..c8255eab99 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlchangeset_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlchangeset_p.h @@ -21,6 +21,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class Q_QMLMODELS_EXPORT QQmlChangeSet @@ -34,6 +36,10 @@ public: MoveKey(int moveId, int offset) : moveId(moveId), offset(offset) {} int moveId = -1; int offset = 0; + + private: + friend size_t qHash(MoveKey key, size_t seed = 0) noexcept + { return qHashMulti(seed, key.moveId, key.offset); } }; // The storrage for Change (below). This struct is trivial, which it has to be in order to store @@ -117,7 +123,6 @@ private: Q_DECLARE_TYPEINFO(QQmlChangeSet::Change, Q_PRIMITIVE_TYPE); Q_DECLARE_TYPEINFO(QQmlChangeSet::MoveKey, Q_PRIMITIVE_TYPE); -inline size_t qHash(const QQmlChangeSet::MoveKey &key) { return qHash(std::make_pair(key.moveId, key.offset)); } inline bool operator ==(const QQmlChangeSet::MoveKey &l, const QQmlChangeSet::MoveKey &r) { return l.moveId == r.moveId && l.offset == r.offset; } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel.cpp index a03318578c..52d9db5da0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel.cpp @@ -3128,7 +3128,7 @@ void QQmlDelegateModelGroup::setDefaultInclude(bool include) } /*! - \qmlmethod object QtQml.Models::DelegateModelGroup::get(int index) + \qmlmethod var QtQml.Models::DelegateModelGroup::get(int index) Returns a javascript object describing the item at \a index in the group. diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel_p_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel_p_p.h index 105c124f05..100c860b49 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldelegatemodel_p_p.h @@ -197,6 +197,8 @@ public: int modelIndex() const { return m_index; } bool hasValidModelIndex() const { return m_index >= 0; } virtual void setModelIndex(int idx, int newRow, int newColumn, bool alwaysEmit = false); + void setPersistentModelIndex(const QPersistentModelIndex &index) { m_persistentIndex = index; } + QPersistentModelIndex persistentModelIndex() const { return m_persistentIndex; } bool usesStructuredModelData() const { return m_useStructuredModelData; } @@ -322,6 +324,7 @@ private: quint16 m_scriptRef = 0; bool m_useStructuredModelData = true; + QPersistentModelIndex m_persistentIndex; }; namespace QV4 { diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldmlistaccessordata_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldmlistaccessordata_p.h index faae84735e..7f98a88af5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldmlistaccessordata_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmldmlistaccessordata_p.h @@ -156,7 +156,8 @@ public: return QString::fromUtf8(string); else if constexpr (std::is_same_v) return QString::fromUtf8(string); - Q_UNREACHABLE_RETURN(QString()); + else + static_assert(QtPrivate::type_dependent_false::value, "unsupported string type"); } template @@ -165,10 +166,11 @@ public: if constexpr (std::is_same_v) return string.toUtf8(); else if constexpr (std::is_same_v) - return string; + return string.nullTerminated(); else if constexpr (std::is_same_v) return QByteArray::fromRawData(string, qstrlen(string)); - Q_UNREACHABLE_RETURN(QByteArray()); + else + static_assert(QtPrivate::type_dependent_false::value, "unsupported string type"); } template @@ -183,10 +185,10 @@ public: const QMetaType::TypeFlags typeFlags = type.flags(); if (typeFlags & QMetaType::PointerToQObject) - return row->value()->property(toUtf8(role)); + return row->value()->property(toUtf8(role).constData()); if (const QMetaObject *metaObject = metaObjectFromType(type)) { - const int propertyIndex = metaObject->indexOfProperty(toUtf8(role)); + const int propertyIndex = metaObject->indexOfProperty(toUtf8(role).constData()); if (propertyIndex >= 0) return metaObject->property(propertyIndex).readOnGadget(row->constData()); } @@ -202,7 +204,7 @@ public: return; } - createProperty(toUtf8(string), nullptr); + createProperty(toUtf8(string).constData(), nullptr); } void createMissingProperties(const QVariant *row) @@ -235,9 +237,9 @@ public: } else if (type == QMetaType::fromType()) { static_cast(row->data())->insert(toQString(role), value); } else if (type.flags() & QMetaType::PointerToQObject) { - row->value()->setProperty(toUtf8(role), value); + row->value()->setProperty(toUtf8(role).constData(), value); } else if (const QMetaObject *metaObject = metaObjectFromType(type)) { - const int propertyIndex = metaObject->indexOfProperty(toUtf8(role)); + const int propertyIndex = metaObject->indexOfProperty(toUtf8(role).constData()); if (propertyIndex >= 0) metaObject->property(propertyIndex).writeOnGadget(row->data(), value); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel.cpp index fbd00b1ac8..5c42cdf01d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel.cpp @@ -1959,6 +1959,7 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index) \div {class="float-right"} \inlineimage listmodel.png + {List showing Apple, Orange, and Banana with prices} \enddiv \snippet qml/listmodel/listmodel.qml 0 @@ -1983,6 +1984,7 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index) \div {class="float-right"} \inlineimage listmodel-nested.png + {List showing fruits with nested attribute data} \enddiv \snippet qml/listmodel/listmodel-nested.qml delegate @@ -2092,7 +2094,7 @@ QQmlListModel::~QQmlListModel() m_agent->modelDestroyed(); } - if (m_mainThread && m_agent) + if (m_agent && m_ownAgent) m_agent->release(); m_listModel = nullptr; @@ -2110,9 +2112,6 @@ QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner) model->m_agent = newOwner->m_agent; model->m_dynamicRoles = newOwner->m_dynamicRoles; - if (model->m_mainThread && model->m_agent) - model->m_agent->addref(); - QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner)); return model; @@ -2258,6 +2257,7 @@ QQmlListModelWorkerAgent *QQmlListModel::agent() return m_agent; m_agent = new QQmlListModelWorkerAgent(this); + m_ownAgent = true; return m_agent; } @@ -2663,7 +2663,7 @@ void QQmlListModel::append(QQmlV4FunctionPtr args) } /*! - \qmlmethod object ListModel::get(int index) + \qmlmethod var ListModel::get(int index) Returns the item at \a index in the list model. This allows the item data to be accessed or modified from JavaScript: diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel_p.h index 50a78af346..b768f47431 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmllistmodel_p.h @@ -110,6 +110,7 @@ private: QQmlRefPointer m_compilationUnit; bool m_mainThread; bool m_primary; + bool m_ownAgent = false; bool m_dynamicRoles; diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel.cpp index b5d5c4f242..31826ae4b5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel.cpp @@ -256,6 +256,7 @@ quint64 QQmlObjectModelPrivate::_q_createJSWrapper(QQmlV4ExecutionEnginePtr engi \endcode \image objectmodel.png + {Three colored rectangles stacked in a ListView} \sa {Qt Quick Examples - Views} */ @@ -353,7 +354,7 @@ QQmlObjectModelAttached *QQmlObjectModel::qmlAttachedProperties(QObject *obj) } /*! - \qmlmethod object QtQml.Models::ObjectModel::get(int index) + \qmlmethod QtObject QtQml.Models::ObjectModel::get(int index) \since 5.6 Returns the item at \a index in the model. This allows the item diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel_p.h index c6d50559c8..aacf30f722 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmlobjectmodel_p.h @@ -69,6 +69,7 @@ Q_SIGNALS: void modelUpdated(const QQmlChangeSet &changeSet, bool reset); void createdItem(int index, QObject *object); void initItem(int index, QObject *object); + void updateItemProperties(int index, QObject *object, bool init); void destroyingItem(QObject *object); Q_REVISION(2, 15) void itemPooled(int index, QObject *object); Q_REVISION(2, 15) void itemReused(int index, QObject *object); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel.cpp index a37a8ea452..dc73a17b78 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel.cpp @@ -11,6 +11,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE const char* kModelItemTag = "_tableinstancemodel_modelItem"; @@ -72,6 +74,11 @@ QQmlTableInstanceModel::~QQmlTableInstanceModel() deleteAllFinishedIncubationTasks(); qDeleteAll(m_modelItems); drainReusableItemsPool(0); + + // We don't expect there to be any released items at this point. + // The view must explicitly call commitReleasedItems() after each + // rebuild of the viewport, and long before the model is destructed. + Q_ASSERT(m_releasedItems.isEmpty()); } QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index) @@ -91,50 +98,102 @@ QQmlComponent *QQmlTableInstanceModel::resolveDelegate(int index) return m_delegate; } -QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int index) +QQmlDelegateModelItem *QQmlTableInstanceModel::resolveModelItem(int flatIndex, const QModelIndex &modelIndex) { + Q_ASSERT(flatIndex >= 0 && flatIndex < m_adaptorModel.count()); + // Check if an item for the given index is already loaded and ready - QQmlDelegateModelItem *modelItem = m_modelItems.value(index, nullptr); - if (modelItem) + if (QQmlDelegateModelItem *modelItem = m_modelItems.value(flatIndex, nullptr)) return modelItem; - QQmlComponent *delegate = resolveDelegate(index); + QQmlComponent *delegate = resolveDelegate(flatIndex); if (!delegate) return nullptr; - // Check if the pool contains an item that can be reused - modelItem = m_reusableItemsPool.takeItem(delegate, index); - if (modelItem) { - reuseItem(modelItem, index); - m_modelItems.insert(index, modelItem); + // Check if the temporary release cache contains the requested item + if (modelIndex.isValid()) { + const auto it = std::find_if(m_releasedItems.cbegin(), m_releasedItems.cend(), + [&modelIndex](const QQmlDelegateModelItem *item) { + return item->persistentModelIndex() == modelIndex; + }); + + if (it != m_releasedItems.cend()) { + QQmlDelegateModelItem *modelItem = *it; + // Note: a rebuild can change which delegate a DelegateChooser assigns to a cell, so the + // released item may no longer use the right delegate, even if it has the correct index. + if (modelItem->delegate() == delegate) { + m_releasedItems.erase(it); + restoreFromReleasedItemsCache(modelItem, flatIndex); + return modelItem; + } + } + } + + // Check if the pool contains an item with the same delegate that can be reused + if (QQmlDelegateModelItem *modelItem = m_reusableItemsPool.takeItem(delegate, flatIndex)) { + m_modelItems.insert(flatIndex, modelItem); + reuseItem(modelItem, flatIndex); return modelItem; } // Create a new item from scratch - modelItem = m_adaptorModel.createItem(m_metaType.data(), index); - if (modelItem) { + if (QQmlDelegateModelItem *modelItem = m_adaptorModel.createItem(m_metaType.data(), flatIndex)) { modelItem->setDelegate(delegate); - m_modelItems.insert(index, modelItem); + m_modelItems.insert(flatIndex, modelItem); return modelItem; } - qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << index; + qWarning() << Q_FUNC_INFO << "failed creating a model item for index: " << flatIndex << modelIndex; return nullptr; } +void QQmlTableInstanceModel::commitReleasedItems() +{ + // Transfer all released items from the temporary cache to the reuse pool. From + // now on, they can only be reused based on delegate type and not model index. + for (auto *item : std::as_const(m_releasedItems)) { + item->setPersistentModelIndex(QModelIndex()); + if (m_reusableItemsPool.insertItem(item)) + emit itemPooled(item->modelIndex(), item->object()); + else + destroyModelItem(item, Deferred); + } + m_releasedItems.clear(); +} + QObject *QQmlTableInstanceModel::object(int index, QQmlIncubator::IncubationMode incubationMode) { Q_ASSERT(m_delegate); - Q_ASSERT(index >= 0 && index < m_adaptorModel.count()); - QQmlDelegateModelItem *modelItem = resolveModelItem(index); + QQmlDelegateModelItem *modelItem = resolveModelItem(index, QModelIndex()); if (!modelItem) return nullptr; - // The model item has already been incubated. So - // just bump the ref-count and return it. - if (modelItem->object()) + // Return the incubated object, or start an async incubation task and return nullptr for now + return incubateModelItemIfNeeded(modelItem, incubationMode); +} + +QObject *QQmlTableInstanceModel::object(const QModelIndex &modelIndex, QQmlIncubator::IncubationMode incubationMode) +{ + Q_ASSERT(m_delegate); + Q_ASSERT(m_adaptorModel.adaptsAim()); + + const int flatIndex = m_adaptorModel.indexAt(modelIndex.row(), modelIndex.column()); + QQmlDelegateModelItem *modelItem = resolveModelItem(flatIndex, modelIndex); + if (!modelItem) + return nullptr; + + // Return the incubated object, or start an async incubation task and return nullptr for now + return incubateModelItemIfNeeded(modelItem, incubationMode); +} + +QObject *QQmlTableInstanceModel::incubateModelItemIfNeeded(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode) +{ + if (modelItem->object()) { + // The model item has already been incubated. So + // just bump the ref-count and return it. return modelItem->referenceObjectWeak(); + } // The object is not ready, and needs to be incubated incubateModelItem(modelItem, incubationMode); @@ -184,8 +243,15 @@ QQmlInstanceModel::ReleaseFlags QQmlTableInstanceModel::release(QObject *object, // The item is not referenced by anyone m_modelItems.remove(modelItem->modelIndex()); - if (reusable == Reusable && m_reusableItemsPool.insertItem(modelItem)) { - emit itemPooled(modelItem->modelIndex(), modelItem->object()); + if (reusable == Reusable) { + // Stage the item in a temporary cache rather than moving it directly to the reuse pool. + // The view calls commitReleasedItems() once the rebuild is done, which transfers the cache + // to the pool. Keeping a temporary cache lets resolveModelItem() match a staged item by + // persistent model index first, during viewport builds, and hand back the exact same + // delegate item that was showing that cell. That way we can avoid the otherwise full + // reuse of an item, which includes emitting pooled signals etc. This also preserves + // ongoing visual state such as animations. + m_releasedItems.append(modelItem); return QQmlInstanceModel::Pooled; } @@ -253,6 +319,21 @@ void QQmlTableInstanceModel::drainReusableItemsPool(int maxPoolTime) }); } +void QQmlTableInstanceModel::restoreFromReleasedItemsCache(QQmlDelegateModelItem *item, int newFlatIndex) +{ + // Row/column shifts elsewhere in the table may have changed this item's + // position, so update its index before handing it back from the released + // items cache. Also notify the view so it can refresh any index-dependent + // required properties (such as 'expanded' and 'hasChildren'). + m_modelItems.insert(newFlatIndex, item); + const bool alwaysEmit = true; + const bool init = false; + const int row = m_adaptorModel.rowAt(newFlatIndex); + const int column = m_adaptorModel.columnAt(newFlatIndex); + item->setModelIndex(newFlatIndex, row, column, alwaysEmit); + emit updateItemProperties(newFlatIndex, item->object(), init); +} + void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModelIndex) { // Update the context properties index, row and column on @@ -264,6 +345,8 @@ void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModel const int newRow = m_adaptorModel.rowAt(newModelIndex); const int newColumn = m_adaptorModel.columnAt(newModelIndex); item->setModelIndex(newModelIndex, newRow, newColumn, alwaysEmit); + if (auto *qaim = abstractItemModel()) + item->setPersistentModelIndex(qaim->index(newRow, newColumn)); // Notify the application that all 'dynamic'/role-based context data has // changed as well (their getter function will use the updated index). @@ -273,6 +356,8 @@ void QQmlTableInstanceModel::reuseItem(QQmlDelegateModelItem *item, int newModel // Inform the view that the item is recycled. This will typically result // in the view updating its own attached delegate item properties. + const bool init = false; + emit updateItemProperties(newModelIndex, item->object(), init); emit itemReused(newModelIndex, item->object()); } @@ -347,6 +432,13 @@ void QQmlTableInstanceModel::incubatorStatusChanged(QQmlTableInstanceModelIncuba QObject *object = modelItem->object(); Q_ASSERT(object); + if (auto *qaim = abstractItemModel()) { + // Optimization: store a persistent model index so that resolveModelItem(QModelIndex) + // can match this item by index after a rebuild, even if its flat index has shifted + // due to row or column insertions/removals. + modelItem->setPersistentModelIndex(qaim->index(modelItem->modelRow(), modelItem->modelColumn())); + } + // Tag the incubated object with the model item for easy retrieval upon release etc. object->setProperty(kModelItemTag, QVariant::fromValue(modelItem)); @@ -538,7 +630,11 @@ void QQmlTableInstanceModelIncubationTask::setInitialState(QObject *object) initializeRequiredProperties( modelItemToIncubate, object, tableInstanceModel->delegateModelAccess()); modelItemToIncubate->setObject(object); - emit tableInstanceModel->initItem(modelItemToIncubate->modelIndex(), object); + + const bool init = true; + const int flatIndex = modelItemToIncubate->modelIndex(); + emit tableInstanceModel->initItem(flatIndex, object); + emit tableInstanceModel->updateItemProperties(flatIndex, object, init); if (!QQmlIncubatorPrivate::get(this)->requiredProperties()->empty()) modelItemToIncubate->destroyObjectLater(); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel_p.h b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel_p.h index 75d0f6ff3e..23b6ef1597 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltableinstancemodel_p.h @@ -27,6 +27,7 @@ QT_BEGIN_NAMESPACE class QQmlTableInstanceModel; class QQmlAbstractDelegateComponent; +class QTypeRevision; class QQmlTableInstanceModelIncubationTask : public QQDMIncubationTask { @@ -85,6 +86,10 @@ public: const QAbstractItemModel *abstractItemModel() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; + QObject *object(const QModelIndex &index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested); + QObject *incubateModelItemIfNeeded(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode); + void restoreFromReleasedItemsCache(QQmlDelegateModelItem *item, int newFlatIndex); + void commitReleasedItems(); ReleaseFlags release(QObject *object, ReusableFlag reusable = NotReusable) override; void dispose(QObject *object); void cancel(int) override; @@ -122,13 +127,14 @@ private: QHash m_modelItems; QQmlReusableDelegateModelItemsPool m_reusableItemsPool; + QList m_releasedItems; QList m_finishedIncubationTasks; void incubateModelItem(QQmlDelegateModelItem *modelItem, QQmlIncubator::IncubationMode incubationMode); void incubatorStatusChanged(QQmlTableInstanceModelIncubationTask *dmIncubationTask, QQmlIncubator::Status status); void deleteIncubationTaskLater(QQmlIncubator *incubationTask); void deleteAllFinishedIncubationTasks(); - QQmlDelegateModelItem *resolveModelItem(int index); + QQmlDelegateModelItem *resolveModelItem(int flatIndex, const QModelIndex &modelIndex); void destroyModelItem(QQmlDelegateModelItem *modelItem, DestructionMode mode); void dataChangedCallback(const QModelIndex &begin, const QModelIndex &end, const QList &roles); diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltreemodeltotablemodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltreemodeltotablemodel.cpp index 7d34caf23d..f8ad611fb7 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltreemodeltotablemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/qqmltreemodeltotablemodel.cpp @@ -70,7 +70,7 @@ void QQmlTreeModelToTableModel::setModel(QAbstractItemModel *arg) { if (m_model != arg) { if (m_model) { - for (const auto &c : m_connections) + for (auto &c : m_connections) QObject::disconnect(c); m_connections.fill({}); } @@ -134,9 +134,7 @@ QModelIndex QQmlTreeModelToTableModel::parent(const QModelIndex &child) const QHash QQmlTreeModelToTableModel::roleNames() const { - if (!m_model) - return QHash(); - return m_model->roleNames(); + return m_model ? m_model->roleNames() : QAbstractItemModel::roleNames(); } int QQmlTreeModelToTableModel::rowCount(const QModelIndex &) const diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp index e0a1f3d946..d797a03c6f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp @@ -46,17 +46,17 @@ Q_LOGGING_CATEGORY (lcSortFilterProxyModel, "qt.qml.sortfilterproxymodel") id: sfpm model: processModel sorters: [ - RoleSorter: { + RoleSorter { roleName: "user" priority: 0 }, - RoleSorter: { + RoleSorter { roleName: "pid" priority: 1 } ] filters: [ - FunctionFilter: { + FunctionFilter { component RoleData: QtObject { property qreal cpuUsage } function filter(data: RoleData) : bool { return (data.cpuUsage > 90) @@ -332,7 +332,7 @@ void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) beginResetModel(); if (d->model) { - for (const QMetaObject::Connection &connection : std::as_const(d->m_sourceConnections)) + for (QMetaObject::Connection &connection : d->m_sourceConnections) disconnect(connection); } diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltest/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/qmltest/CMakeLists.txt index fa20b51db2..908dd98a96 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltest/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/qmltest/CMakeLists.txt @@ -34,7 +34,6 @@ qt_internal_add_qml_module(QuickTest TestSchedule.qml testlogger.js DEFINES - QT_NO_FOREACH QT_NO_URL_CAST_FROM_STRING QT_BUILD_QMLTEST_LIB LIBRARIES diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltest/TestCase.qml b/local/recipes/qt/qtdeclarative/source/src/qmltest/TestCase.qml index 76364fe4d4..b2353549f0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltest/TestCase.qml +++ b/local/recipes/qt/qtdeclarative/source/src/qmltest/TestCase.qml @@ -141,9 +141,9 @@ import "testlogger.js" as TestLogger \section1 Simulating Keyboard and Mouse Events The keyPress(), keyRelease(), and keyClick() methods can be used - to simulate keyboard events within unit tests. The events are + to simulate keyboard events within unit tests. The events are delivered to the currently focused QML item. You can pass either - a Qt.Key enum value or a latin1 char (string of length one) + a Qt.Key enum value or a latin1 char (string of length one). \code Rectangle { @@ -699,7 +699,7 @@ Item { /*! \since 5.9 - \qmlmethod object TestCase::createTemporaryQmlObject(string qml, object parent, string filePath) + \qmlmethod QtObject TestCase::createTemporaryQmlObject(string qml, QtObject parent, string filePath) This function dynamically creates a QML object from the given \a qml string with the specified \a parent. The returned object will be @@ -745,7 +745,7 @@ Item { /*! \since 5.9 - \qmlmethod object TestCase::createTemporaryObject(Component component, object parent, object properties) + \qmlmethod QtObject TestCase::createTemporaryObject(Component component, QtObject parent, var properties) This function dynamically creates a QML object from the given \a component with the specified optional \a parent and \a properties. @@ -1017,7 +1017,7 @@ Item { } /*! - \qmlmethod object TestCase::grabImage(item) + \qmlmethod QtObject TestCase::grabImage(Item item) Returns a snapshot image object of the given \a item. @@ -1393,12 +1393,16 @@ Item { /*! \qmlmethod TestCase::keyPress(key, modifiers = Qt.NoModifier, delay = -1) - Simulates pressing a \a key with optional \a modifiers on the currently - focused item. If \a delay is larger than 0, the test will wait for - \a delay milliseconds. + Simulates pressing a \a key with optional \a modifiers on the + currently focused item. If \a delay is larger than 0, the test + will wait for \a delay milliseconds. - The event will be sent to the TestCase window or, in case of multiple windows, - to the current active window. See \l QGuiApplication::focusWindow() for more details. + For more information about the accepted \a key formats, see + \l {Simulating Keyboard and Mouse Events}. + + The event will be sent to the TestCase window or, in case of + multiple windows, to the current active window. See + \l QGuiApplication::focusWindow() for more details. \b{Note:} At some point you should release the key using keyRelease(). @@ -1421,12 +1425,16 @@ Item { /*! \qmlmethod TestCase::keyRelease(key, modifiers = Qt.NoModifier, delay = -1) - Simulates releasing a \a key with optional \a modifiers on the currently - focused item. If \a delay is larger than 0, the test will wait for - \a delay milliseconds. + Simulates releasing a \a key with optional \a modifiers on the + currently focused item. If \a delay is larger than 0, the test + will wait for \a delay milliseconds. - The event will be sent to the TestCase window or, in case of multiple windows, - to the current active window. See \l QGuiApplication::focusWindow() for more details. + For more information about the accepted \a key formats, see + \l {Simulating Keyboard and Mouse Events}. + + The event will be sent to the TestCase window or, in case of + multiple windows, to the current active window. See + \l QGuiApplication::focusWindow() for more details. \sa keyPress(), keyClick() */ @@ -1447,12 +1455,16 @@ Item { /*! \qmlmethod TestCase::keyClick(key, modifiers = Qt.NoModifier, delay = -1) - Simulates clicking of \a key with optional \a modifiers on the currently - focused item. If \a delay is larger than 0, the test will wait for - \a delay milliseconds. + Simulates clicking of \a key with optional \a modifiers on the + currently focused item. If \a delay is larger than 0, the test + will wait for \a delay milliseconds. - The event will be sent to the TestCase window or, in case of multiple windows, - to the current active window. See \l QGuiApplication::focusWindow() for more details. + For more information about the accepted \a key formats, see + \l {Simulating Keyboard and Mouse Events}. + + The event will be sent to the TestCase window or, in case of + multiple windows, to the current active window. See + \l QGuiApplication::focusWindow() for more details. \sa keyPress(), keyRelease() */ diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/jsroot_metatypes.json b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/jsroot_metatypes.json index 662fc36b89..d35034be83 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/jsroot_metatypes.json +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/jsroot_metatypes.json @@ -2317,79 +2317,17 @@ "value": "anonymous" } ], - "className": "URLPrototype", + "className": "URLSearchParams", "methods": [ - { - "access": "public", - "arguments": [ - ], - "isJavaScriptFunction": true, - "name": "toString" - }, - { - "access": "public", - "arguments": [ - ], - "isJavaScriptFunction": true, - "name": "toJSON" - } ], "object": true, "properties": [ - { - "member": "fakeMember", - "name": "hash" - }, - { - "member": "fakeMember", - "name": "host" - }, - { - "member": "fakeMember", - "name": "hostname" - }, - { - "member": "fakeMember", - "name": "href" - }, - { - "member": "fakeMember", - "name": "origin" - }, - { - "member": "fakeMember", - "name": "password" - }, - { - "member": "fakeMember", - "name": "pathname" - }, - { - "member": "fakeMember", - "name": "port" - }, - { - "member": "fakeMember", - "name": "protocol" - }, - { - "member": "fakeMember", - "name": "search" - }, - { - "member": "fakeMember", - "name": "searchParams" - }, - { - "member": "fakeMember", - "name": "username" - } ], - "qualifiedClassName": "URLPrototype", + "qualifiedClassName": "URLSearchParams", "superClasses": [ { "access": "public", - "name": "ObjectPrototype" + "name": "URLPrototypeSearchParamsPrototype" } ] }, @@ -2400,29 +2338,17 @@ "value": "anonymous" } ], - "className": "URL", + "className": "URLPrototypeSearchParamsPrototype", "methods": [ - ], - "object": true, - "properties": [ - ], - "qualifiedClassName": "URL", - "superClasses": [ { "access": "public", - "name": "URLPrototype" - } - ] - }, - { - "classInfos": [ - { - "name": "QML.Element", - "value": "anonymous" - } - ], - "className": "URLSearchParamsPrototype", - "methods": [ + "arguments": [ + ], + "isConstructor": true, + "isJavaScriptFunction": true, + "name": "constructor", + "returnType": "URLSearchParams" + }, { "access": "public", "arguments": [ @@ -2511,7 +2437,7 @@ "object": true, "properties": [ ], - "qualifiedClassName": "URLSearchParamsPrototype", + "qualifiedClassName": "URLPrototypeSearchParamsPrototype", "superClasses": [ { "access": "public", @@ -2526,17 +2452,142 @@ "value": "anonymous" } ], - "className": "URLSearchParams", + "className": "URLPrototypeSearchParams", "methods": [ ], "object": true, "properties": [ ], - "qualifiedClassName": "URLSearchParams", + "qualifiedClassName": "URLPrototypeSearchParams", "superClasses": [ { "access": "public", - "name": "URLSearchParamsPrototype" + "name": "URLPrototypeSearchParamsPrototype" + } + ] + }, + { + "classInfos": [ + { + "name": "QML.Element", + "value": "anonymous" + } + ], + "className": "URLPrototype", + "methods": [ + { + "access": "public", + "arguments": [ + ], + "isConstructor": true, + "isJavaScriptFunction": true, + "name": "constructor", + "returnType": "URL" + }, + { + "access": "public", + "arguments": [ + ], + "isJavaScriptFunction": true, + "name": "toString" + }, + { + "access": "public", + "arguments": [ + ], + "isJavaScriptFunction": true, + "name": "toJSON" + } + ], + "object": true, + "properties": [ + { + "member": "fakeMember", + "name": "hash", + "type": "string" + }, + { + "member": "fakeMember", + "name": "host", + "type": "string" + }, + { + "member": "fakeMember", + "name": "hostname", + "type": "string" + }, + { + "member": "fakeMember", + "name": "href", + "type": "string" + }, + { + "member": "fakeMember", + "name": "origin", + "type": "string" + }, + { + "member": "fakeMember", + "name": "password", + "type": "string" + }, + { + "member": "fakeMember", + "name": "pathname", + "type": "string" + }, + { + "member": "fakeMember", + "name": "port", + "type": "string" + }, + { + "member": "fakeMember", + "name": "protocol", + "type": "string" + }, + { + "member": "fakeMember", + "name": "search", + "type": "string" + }, + { + "member": "fakeMember", + "name": "searchParams", + "type": "URLPrototypeSearchParams" + }, + { + "member": "fakeMember", + "name": "username", + "type": "string" + } + ], + "qualifiedClassName": "URLPrototype", + "superClasses": [ + { + "access": "public", + "name": "ObjectPrototype" + } + ] + }, + { + "classInfos": [ + { + "name": "QML.Element", + "value": "anonymous" + } + ], + "className": "URL", + "methods": [ + ], + "object": true, + "properties": [ + ], + "qualifiedClassName": "URL", + "superClasses": [ + { + "access": "public", + "name": "URLPrototype" } ] }, @@ -5095,6 +5146,10 @@ "name": "HighContrast", "type": "number" }, + { + "name": "ReducedMotion", + "type": "number" + }, { "name": "NoButton", "type": "number" @@ -9003,6 +9058,10 @@ "name": "ImhLatinOnly", "type": "number" }, + { + "name": "ImhDecimaNumbersOnly", + "type": "number" + }, { "name": "ImhExclusiveInputMask", "type": "number" @@ -9191,6 +9250,10 @@ "name": "RangeModelDataRole", "type": "number" }, + { + "name": "RangeModelAdapterRole", + "type": "number" + }, { "name": "UserRole", "type": "number" @@ -9598,6 +9661,31 @@ ], "isJavaScriptFunction": true, "name": "shift" + }, + { + "access": "public", + "arguments": [ + { + } + ], + "isJavaScriptFunction": true, + "name": "unshift" + }, + { + "access": "public", + "arguments": [ + { + } + ], + "isJavaScriptFunction": true, + "name": "push" + }, + { + "access": "public", + "arguments": [ + ], + "isJavaScriptFunction": true, + "name": "pop" } ], "object": true, diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrar_p.h b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrar_p.h index b39ca47254..4238e9eaf5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrar_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrar_p.h @@ -17,7 +17,7 @@ // #include -#include +#include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrarutils_p.h b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrarutils_p.h index a4971dead4..6e20afa332 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrarutils_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltyperegistrarutils_p.h @@ -18,7 +18,7 @@ #include "qmetatypesjsonprocessor_p.h" -#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypesclassdescription_p.h b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypesclassdescription_p.h index 7207b2c943..fe80c14b04 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypesclassdescription_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypesclassdescription_p.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator.cpp b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator.cpp index ac50cef23c..bc208d35cc 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include diff --git a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator_p.h b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator_p.h index 272d252418..2f1c36dbec 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/qmltyperegistrar/qqmltypescreator_p.h @@ -21,6 +21,7 @@ #include #include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/qmlworkerscript/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/qmlworkerscript/CMakeLists.txt index cb1643c6f5..8f2b8049c6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/qmlworkerscript/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/qmlworkerscript/CMakeLists.txt @@ -25,7 +25,6 @@ qt_internal_add_qml_module(QmlWorkerScript ENABLE_JIT_CONSTANT_BLINDING=0 ENABLE_LLINT=0 JS_EXPORT_PRIVATE= # special case - QT_NO_FOREACH QT_NO_INTEGER_EVENT_COORDINATES QT_NO_URL_CAST_FROM_STRING WTFInvokeCrashHook=qmlWTFInvokeCrashHook diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/quick/CMakeLists.txt index a4ecfdaa3e..561ca6d545 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/quick/CMakeLists.txt @@ -229,7 +229,6 @@ qt_internal_add_qml_module(Quick util/qquickvalidator.cpp # expl. spec. of 'QMetaTypeId' after inst. quick_qmltyperegistrations.cpp # qquickvalidator_p.h: error: explicit specialization of 'QMetaTypeId' after instantiation DEFINES - QT_NO_FOREACH QT_NO_INTEGER_EVENT_COORDINATES QT_NO_URL_CAST_FROM_STRING INCLUDE_DIRECTORIES diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/doc/src/qmltypereference.qdoc b/local/recipes/qt/qtdeclarative/source/src/quick/doc/src/qmltypereference.qdoc index 85905c0dd8..42d5781bdf 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/doc/src/qmltypereference.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/quick/doc/src/qmltypereference.qdoc @@ -189,38 +189,6 @@ available when you import \c QtQuick. Properties of type \c font default to the application's default \l {Application::font} {font}. - The most commonly used properties are: - - \list - \li \l string \c font.family - \li \l bool \c font.bold - \li \l bool \c font.italic - \li \l bool \c font.underline - \li \l real \c font.pointSize - \li \l int \c font.pixelSize - \endlist - - If both \c pointSize and a \c pixelSize are specified, \c pixelSize will be used. - - The following properties are also available: - - \list - \li \l {QML Enumerations}{enumeration} \c font.weight - \li \l bool \c font.overline - \li \l bool \c font.strikeout - \li \l {QML Enumerations}{enumeration} \c font.capitalization - \li \l real \c font.letterSpacing - \li \l real \c font.wordSpacing - \li \l bool \c font.kerning - \li \l bool \c font.preferShaping - \li \l {QML Enumerations}{enumeration} \c font.hintingPreference - \li \c object \l [QML] {QtQuick::Text::}{font.features} - \li \l string \c font.styleName - \li \c object \l [QML] {QtQuick::Text::}{font.variableAxes} - \li \l bool \c font.contextFontMerging - \li \l bool \c font.preferTypoLineMetrics - \endlist - Example: \snippet qml/font.qml text @@ -236,38 +204,371 @@ available when you import \c QtQuick. This value type is provided by the \l[QML]{QtQuick} import. - Numerical values for font weights follow the CSS specification, where a - weight of 100 is extremely light, and 900 is extremely bold. - The following values are supported: + \sa {QML Value Types} +*/ + +/*! + \qmlproperty string font::family + + The family name of the font. + + //! [qml-font-family] + 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. + //! [qml-font-family] +*/ + +/*! + \qmlproperty string font::styleName + \since 5.6 + + The style name of the font. + + //! [qml-font-style-name] + The style name is case insensitive. If set, the font will be matched against style name instead + of the font properties \l {font::weight}{font.weight}, \l {font::bold}{font.bold}, + and \l {font::italic}{font.italic}. + //! [qml-font-style-name] + +*/ + +/*! + \qmlproperty bool font::bold + + Whether the font weight is bold. +*/ + +/*! + \qmlproperty int font::weight + + //! [qml-font-weight] + The requested weight of the font. The weight requested must be an integer + between 1 and 1000, or one of the predefined values: \value Font.Thin 100 \value Font.ExtraLight 200 \value Font.Light 300 - \value Font.Normal 400 + \value Font.Normal 400 (default) \value Font.Medium 500 \value Font.DemiBold 600 \value Font.Bold 700 \value Font.ExtraBold 800 \value Font.Black 900 - Capitalization supports the following values: + \qml + Text { text: "Hello"; font.weight: Font.DemiBold } + \endqml + //! [qml-font-weight] +*/ - \value Font.MixedCase No capitalization change is applied. - \value Font.AllUppercase Alters the text to be rendered in all uppercase type. - \value Font.AllLowercase Alters the text to be rendered in all lowercase type. - \value Font.SmallCaps Alters the text to be rendered in small-caps type. - \value Font.Capitalize Alters the text to be rendered with the first character of each word as an uppercase character. +/*! + \qmlproperty bool font::italic + + Whether the font has an italic style. +*/ + +/*! + \qmlproperty bool font::underline + + Whether the text is underlined. +*/ + +/*! + \qmlproperty bool font::strikeout + + Whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real font::pointSize + + The font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int font::pixelSize + + The font size in pixels. + + Makes the font device dependent. + Use \c pointSize to set the size of the font in a device independent manner. +*/ + +/*! + \qmlproperty real font::letterSpacing + + The letter spacing for the font. + + //! [qml-font-letter-spacing] + Letter spacing changes the default spacing between individual letters in + the font. A positive value increases the letter spacing by the corresponding + pixels; a negative value decreases the spacing. + //! [qml-font-letter-spacing] +*/ + +/*! + \qmlproperty real font::wordSpacing + + The word spacing for the font. + + //! [qml-font-word-spacing] + Word spacing changes the default spacing between individual words. A + positive value increases the word spacing by a corresponding amount of + pixels, while a negative value decreases the inter-word spacing accordingly. + //! [qml-font-word-spacing] +*/ + +/*! + \qmlproperty enumeration font::capitalization + + The capitalization for a text. + + //! [qml-font-capitalization] + \value Font.MixedCase the normal case: no capitalization change is applied + \value Font.AllUppercase alters the text to be rendered in all uppercase type + \value Font.AllLowercase alters the text to be rendered in all lowercase type + \value Font.SmallCaps alters the text to be rendered in small-caps type + \value Font.Capitalize alters the text to be rendered with the first character of + each word as an uppercase character + + \qml + Text { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml + //! [qml-font-capitalization] +*/ + +/*! + \qmlproperty enumeration font::hintingPreference + \since 5.8 + + The preferred hinting on the text. + + //! [qml-font-hinting-preference] + This is a hint to the underlying text rendering system + to use a certain level of hinting, and has varying support across platforms. See the table in + the documentation for QFont::HintingPreference for more details. + + \note This property only has an effect when used together with render type \l [QML]{Text::renderType}{Text.NativeRendering}. - Setting the hinting preference only has an effect when using the "NativeRendering" render type. - The property supports the following values: \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. + \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, 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. - \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and vertical directions. + 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 + of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's + layout will be scalable to higher density devices (such as printers) without impacting + details such as line breaks. + \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and + vertical directions. The text will be altered to optimize legibility on the target + device, but since the metrics will depend on the target size of the text, the positions + of glyphs, line breaks, and other typographical detail will not scale, meaning that a + text layout may look different on devices with different pixel densities. - \sa {QML Value Types} + \qml + Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } + \endqml + //! [qml-font-hinting-preference] +*/ + +/*! + \qmlproperty bool font::kerning + \since 5.10 + + //! [qml-font-kerning] + Enables or disables the kerning OpenType feature when shaping the text. + Disabling this may improve performance when creating or changing the text, + at the expense of some cosmetic features. The default value is \c true. + + \qml + Text { text: "OATS FLAVOUR WAY"; font.kerning: false } + \endqml + //! [qml-font-kerning] +*/ + +/*! + \qmlproperty bool font::preferShaping + \since 5.10 + + //! [qml-font-prefer-shaping] + 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 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. + + The default value is \c true. + + \qml + Text { text: "Some text"; font.preferShaping: false } + \endqml + //! [qml-font-prefer-shaping] +*/ + +/*! + \qmlproperty object font::variableAxes + \since 6.7 + + //! [qml-font-variable-axes] + Applies floating point values to variable axes in variable fonts. + + Variable fonts provide a way to store multiple variations (with different weights, widths + or styles) in the same font file. The variations are given as floating point values for + a pre-defined set of parameters, called "variable axes". Specific instances are typically + given names by the font designer, and, in Qt, these can be selected using setStyleName() + just like traditional sub-families. + + In some cases, it is also useful to provide arbitrary values for the different axes. For + instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these. + You could then manually request this by supplying a custom value for the "wght" axis in the + font. + + \qml + Text { + text: "Foobar" + font.family: "MyVariableFont" + font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 } + } + \endqml + + If the "wght" axis is supported by the font and the given value is within its defined range, + a font corresponding to the weight 550.0 will be provided. + + There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width), + "ital" (italic) and "opsz" (optical size). They each have individual ranges defined in the font + itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas + "ital" can span from 0 to 1 (from not italic to fully italic). + + A font may also choose to define custom axes; the only limitation is that the name has to + meet the requirements for a QFont::Tag (sequence of four latin-1 characters.) + + By default, no variable axes are set. + + \note On Windows, variable axes are not supported if the optional GDI font backend is in use. + + \sa QFont::setVariableAxis() + //! [qml-font-variable-axes] +*/ + +/*! + \qmlproperty object font::features + \since 6.6 + +//! [qml-font-features] + Applies integer values to specific OpenType features when shaping the text based on the contents. This provides advanced access to the font shaping process, and can be used + to support font features that are otherwise not covered in the API. + + + The font features are represented by a map from four-letter tags to integer values. This integer + value passed along with the tag in most cases represents a boolean value: A zero value means the + feature is disabled, and a non-zero value means it is enabled. For certain font features, + however, it may have other interpretations. For example, when applied to the \c salt feature, the + value is an index that specifies the stylistic alternative to use. + + For example, the \c frac font feature will convert diagonal fractions separated with a slash + (such as \c 1/2) with a different representation. Typically this will involve baking the full + fraction into a single character width (such as \c ½). + + If a font supports the \c frac feature, then it can be enabled in the shaper as in the following + code: + + \qml + Text { + text: "One divided by two is 1/2" + font.family: "MyFractionFont" + font.features: { "frac": 1 } + } + \endqml + + Multiple features can be assigned values in the same mapping. For instance, + if you would like to also disable kerning for the font, you can explicitly + disable this as follows: + + \qml + Text { + text: "One divided by two is 1/2" + font.family: "MyFractionFont" + font.features: { "frac": 1, "kern": 0 } + } + \endqml + + You can also collect the font properties in an object: + + \qml + Text { + text: "One divided by two is 1/2" + font: { + family: "MyFractionFont" + features: { "frac": 1, "kern": 0 } + } + } + \endqml + + \note By default, Qt will enable and disable certain font features based on other font + properties. In particular, the \c kern feature will be enabled/disabled depending on the + \l {font::kerning} property. In addition, all ligature features (\c liga, \c clig, + + \c dlig, \c hlig) will be disabled if a \l font.letterSpacing is set, but only for writing + systems where the use of ligature is cosmetic. For writing systems where ligatures are required, + the features will remain in their default state. The values set using \c font.features will + override the default behavior. If, for instance, \c{"kern"} is set to 1, then kerning will + always be enabled, regardless of whether the \l {font::kerning} property is set to false. Similarly, + + if it is set to \c 0, it will always be disabled. + + \sa QFont::setFeature() +//! [qml-font-features] +*/ + +/*! + \qmlproperty bool font::contextFontMerging + \since 6.8 + +//! [qml-font-context-font-merging] + If the selected font does not contain a certain character, Qt automatically chooses a + similar-looking fallback font that contains the character. By default this is done on a + character-by-character basis. + + This means that in certain uncommon cases, many different fonts may be used to represent one + string of text even if it's in the same script. Setting \c contextFontMerging to true will try + finding the fallback font that matches the largest subset of the input string instead. This + will be more expensive for strings where missing glyphs occur, but may give more consistent + results. By default, \c contextFontMerging is \c{false}. + + \sa QFont::StyleStrategy +//! [qml-font-context-font-merging] +*/ + +/*! + \qmlproperty bool font::preferTypoLineMetrics + \since 6.8 + +//! [qml-font-prefer-typo-line-metrics] For compatibility reasons, OpenType fonts contain two + competing sets of the vertical line metrics that provide the \l{QFontMetricsF::ascent()}{ascent}, + \l{QFontMetricsF::descent()}{descent} and \l{QFontMetricsF::leading()}{leading} of the font. These + are often referred to as the + \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} (Windows) + metrics and the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo} + (typographical) metrics. While the specification recommends using the \c typo metrics for line + spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} flag is set in + the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection} + field of the font. For backwards-compatibility reasons, this is also the case for Qt applications. + This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to indicate that the \c{typo} + metrics are valid, nor for fonts where the \c{win} metrics and \c{typo} metrics match up. However, + for certain fonts the \c{win} metrics may be larger than the preferable line spacing and the + \c{USE_TYPO_METRICS} flag may be unset by mistake. For such fonts, setting + \c{font.preferTypoLineMetrics} may give superior results. + + By default, \c preferTypoLineMetrics is \c{false}. + + \sa QFont::StyleStrategy +//! [qml-font-prefer-typo-line-metrics] */ /*! diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquickpointerhandler.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquickpointerhandler.cpp index e7f233ef36..0f2de36d83 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquickpointerhandler.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquickpointerhandler.cpp @@ -225,8 +225,21 @@ void QQuickPointerHandler::resetCursorShape() d->cursorSet = false; if (auto *parent = parentItem()) { QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent); - itemPriv->hasCursorHandler = false; - itemPriv->setHasCursorInChild(itemPriv->hasCursor); + // Only clear hasCursorHandler if no other handler on this item still has cursorShape set. + // Multiple handlers with cursorShape are valid (e.g. two HoverHandlers for different devices). + bool otherHandlerHasCursor = false; + if (itemPriv->hasPointerHandlers()) { + for (QQuickPointerHandler *h : itemPriv->extra->pointerHandlers) { + if (h != this && h->isCursorShapeExplicitlySet()) { + otherHandlerHasCursor = true; + break; + } + } + } + if (!otherHandlerHasCursor) { + itemPriv->hasCursorHandler = false; + itemPriv->setHasCursorInChild(itemPriv->hasCursor); + } } emit cursorShapeChanged(); } @@ -365,18 +378,21 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv (existingItemGrabber->keepTouchGrab() && QQuickDeliveryAgentPrivate::isTouchEvent(event)))) { allowed = false; // If the handler wants to steal the exclusive grab from an Item, the Item can usually veto - // by having its keepMouseGrab flag set. But an exception is if that Item is a parent that - // normally filters events (such as a Flickable): it needs to be possible for e.g. a - // DragHandler to operate on an Item inside a Flickable. Flickable is aggressive about - // grabbing on press (for fear of missing updates), but DragHandler uses a passive grab - // at first and then expects to be able to steal the grab later on. It cannot respect - // Flickable's wishes in that case, because then it would never have a chance. - if (existingItemGrabber->keepMouseGrab() && - existingItemGrabber->filtersChildMouseEvents() && existingItemGrabber->isAncestorOf(parentItem())) { + // by having its keepMouseGrab or keepTouchGrab flag set. But if the handler wants to + // steal from an ancestor Item (e.g. a Flickable), allow it: it needs to be able to function + // despite the ancestor's keep-grab flags. Flickable is aggressive about grabbing on press + // (for fear of missing updates), but handlers inside it use passive grabs first and expect + // to steal later. Note: we check isAncestorOf rather than filtersChildMouseEvents() because + // the ancestor may temporarily disable filtering (e.g. TableView disables it during resize drag setup) + // while still holding an exclusive grab that would otherwise block the handler. + if (existingItemGrabber->isAncestorOf(parentItem())) { Q_ASSERT(da); - if (isTouchMouse && point.id() == da->touchMouseId) { - qCDebug(lcPointerHandlerGrab) << this << "steals touchpoint" << point.id() - << "despite parent touch-mouse grabber with keepMouseGrab=true" << existingItemGrabber; + if ((existingItemGrabber->keepMouseGrab() && + (QQuickDeliveryAgentPrivate::isMouseEvent(event) || (isTouchMouse && point.id() == da->touchMouseId))) || + (existingItemGrabber->keepTouchGrab() && QQuickDeliveryAgentPrivate::isTouchEvent(event))) { + qCDebug(lcPointerHandlerGrab) << this << "steals from ancestor" + << existingItemGrabber << "despite keep-grab flags" + << existingItemGrabber->keepMouseGrab() << existingItemGrabber->keepTouchGrab(); allowed = true; } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquicktaphandler.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquicktaphandler.cpp index eef590a597..8f3ddbad59 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquicktaphandler.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/handlers/qquicktaphandler.cpp @@ -141,7 +141,9 @@ void QQuickTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point QQuickSinglePointHandler::handleEventPoint(event, point); // If TapHandler only needs a passive grab, it should not block other items and handlers from reacting. - // If the point is accepted, QQuickItemPrivate::localizedTouchEvent() would skip it. + // For touch, if the point is accepted, QQuickItemPrivate::localizedTouchEvent() would skip it. + // For mouse, keeping accepted=true causes deliverPressOrReleaseEvent() to set handlersOnly=true, + // which prevents sibling items with MouseArea from also receiving the press (QTBUG-145896). if (isTouch && m_gesturePolicy == DragThreshold) point.setAccepted(false); } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcanvasitem.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcanvasitem.cpp index ae8e6e5804..1f8a3847b7 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcanvasitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcanvasitem.cpp @@ -793,7 +793,7 @@ QSGTextureProvider *QQuickCanvasItem::textureProvider() const } /*! - \qmlmethod object QtQuick::Canvas::getContext(string contextId, ... args) + \qmlmethod Context2D QtQuick::Canvas::getContext(string contextId, ... args) Returns a drawing context, or \c null if no context is available. diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcontext2d.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcontext2d.cpp index 5a37082d58..1fecd74f93 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcontext2d.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/context2d/qquickcontext2d.cpp @@ -980,7 +980,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_get_canvas(const QV4::Func } /*! - \qmlmethod object QtQuick::Context2D::restore() + \qmlmethod Context2D QtQuick::Context2D::restore() Pops the top state on the stack, restoring the context to that state. \sa save() @@ -996,7 +996,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_restore(const QV4::Functio } /*! - \qmlmethod object QtQuick::Context2D::reset() + \qmlmethod Context2D QtQuick::Context2D::reset() Resets the context state and properties to the default values. */ QV4::ReturnedValue QQuickJSContext2DPrototype::method_reset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) @@ -1011,7 +1011,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_reset(const QV4::FunctionO } /*! - \qmlmethod object QtQuick::Context2D::save() + \qmlmethod Context2D QtQuick::Context2D::save() Pushes the current state onto the state stack. Before changing any state attributes, you should save the current state @@ -1053,7 +1053,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_save(const QV4::FunctionOb // transformations /*! - \qmlmethod object QtQuick::Context2D::rotate(real angle) + \qmlmethod Context2D QtQuick::Context2D::rotate(real angle) Rotate the canvas around the current origin by \a angle in radians and clockwise direction. \code @@ -1081,7 +1081,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_rotate(const QV4::Function } /*! - \qmlmethod object QtQuick::Context2D::scale(real x, real y) + \qmlmethod Context2D QtQuick::Context2D::scale(real x, real y) Increases or decreases the size of each unit in the canvas grid by multiplying the scale factors to the current tranform matrix. @@ -1111,7 +1111,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_scale(const QV4::FunctionO } /*! - \qmlmethod object QtQuick::Context2D::setTransform(real a, real b, real c, real d, real e, real f) + \qmlmethod Context2D QtQuick::Context2D::setTransform(real a, real b, real c, real d, real e, real f) Changes the transformation matrix to the matrix given by the arguments as described below. @@ -1166,7 +1166,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_setTransform(const QV4::Fu } /*! - \qmlmethod object QtQuick::Context2D::transform(real a, real b, real c, real d, real e, real f) + \qmlmethod Context2D QtQuick::Context2D::transform(real a, real b, real c, real d, real e, real f) This method is very similar to setTransform(), but instead of replacing the old transform matrix, this method applies the given tranform matrix @@ -1198,7 +1198,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_transform(const QV4::Funct } /*! - \qmlmethod object QtQuick::Context2D::translate(real x, real y) + \qmlmethod Context2D QtQuick::Context2D::translate(real x, real y) Translates the origin of the canvas by a horizontal distance of \a x, and a vertical distance of \a y, in coordinate space units. @@ -1220,7 +1220,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_translate(const QV4::Funct /*! - \qmlmethod object QtQuick::Context2D::resetTransform() + \qmlmethod Context2D QtQuick::Context2D::resetTransform() Reset the transformation matrix to the default value (equivalent to calling setTransform(\c 1, \c 0, \c 0, \c 1, \c 0, \c 0)). @@ -1241,7 +1241,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_resetTransform(const QV4:: /*! - \qmlmethod object QtQuick::Context2D::shear(real sh, real sv) + \qmlmethod Context2D QtQuick::Context2D::shear(real sh, real sv) Shears the transformation matrix by \a sh in the horizontal direction and \a sv in the vertical direction. @@ -1600,7 +1600,7 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_strokeStyle(const QV4::Function } /*! - \qmlmethod object QtQuick::Context2D::createLinearGradient(real x0, real y0, real x1, real y1) + \qmlmethod CanvasGradient QtQuick::Context2D::createLinearGradient(real x0, real y0, real x1, real y1) Returns a CanvasGradient object that represents a linear gradient that transitions the color along a line between the start point (\a x0, \a y0) and the end point (\a x1, \a y1). @@ -1648,7 +1648,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_createLinearGradient(const } /*! - \qmlmethod object QtQuick::Context2D::createRadialGradient(real x0, real y0, real r0, real x1, real y1, real r1) + \qmlmethod CanvasGradient QtQuick::Context2D::createRadialGradient(real x0, real y0, real r0, real x1, real y1, real r1) Returns a CanvasGradient object that represents a radial gradient that paints along the cone given by the start circle with origin (\a x0, \a y0) @@ -1703,7 +1703,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_createRadialGradient(const } /*! - \qmlmethod object QtQuick::Context2D::createConicalGradient(real x, real y, real angle) + \qmlmethod CanvasGradient QtQuick::Context2D::createConicalGradient(real x, real y, real angle) Returns a CanvasGradient object that represents a conical gradient that interpolates colors counter-clockwise around a center point (\a x, \a y) @@ -2301,7 +2301,7 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_path(const QV4::FunctionObject //rects /*! - \qmlmethod object QtQuick::Context2D::clearRect(real x, real y, real w, real h) + \qmlmethod Context2D QtQuick::Context2D::clearRect(real x, real y, real w, real h) Clears all pixels on the canvas in the rectangle specified by (\a x, \a y, \a w, \a h) to transparent black. @@ -2323,7 +2323,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_clearRect(const QV4::Funct } /*! - \qmlmethod object QtQuick::Context2D::fillRect(real x, real y, real w, real h) + \qmlmethod Context2D QtQuick::Context2D::fillRect(real x, real y, real w, real h) Paints a rectangular area specified by (\a x, \a y, \a w, \a h) using fillStyle. @@ -2342,7 +2342,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillRect(const QV4::Functi } /*! - \qmlmethod object QtQuick::Context2D::strokeRect(real x, real y, real w, real h) + \qmlmethod Context2D QtQuick::Context2D::strokeRect(real x, real y, real w, real h) Strokes the path of the rectangle specified by (\a x, \a y, \a w, \a h) using strokeStyle, lineWidth, lineJoin, and (if appropriate) miterLimit attributes. @@ -2364,7 +2364,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeRect(const QV4::Func // Complex shapes (paths) API /*! - \qmlmethod object QtQuick::Context2D::arc(real x, real y, real radius, + \qmlmethod Context2D QtQuick::Context2D::arc(real x, real y, real radius, real startAngle, real endAngle, bool anticlockwise) Adds an arc to the current subpath that lies on the circumference of the @@ -2413,7 +2413,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_arc(const QV4::FunctionObj } /*! - \qmlmethod object QtQuick::Context2D::arcTo(real x1, real y1, real x2, + \qmlmethod Context2D QtQuick::Context2D::arcTo(real x1, real y1, real x2, real y2, real radius) Adds an arc with the given control points and radius to the current subpath, @@ -2459,7 +2459,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_arcTo(const QV4::FunctionO } /*! - \qmlmethod object QtQuick::Context2D::beginPath() + \qmlmethod Context2D QtQuick::Context2D::beginPath() Resets the current path to a new path. */ @@ -2476,7 +2476,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_beginPath(const QV4::Funct } /*! - \qmlmethod object QtQuick::Context2D::bezierCurveTo(real cp1x, real cp1y, real cp2x, real cp2y, real x, real y) + \qmlmethod Context2D QtQuick::Context2D::bezierCurveTo(real cp1x, real cp1y, real cp2x, real cp2y, real x, real y) Adds a cubic bezier curve between the current position and the given endPoint using the control points specified by (\a {cp1x}, \a {cp1y}), and (\a {cp2x}, \a {cp2y}). @@ -2520,7 +2520,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_bezierCurveTo(const QV4::F } /*! - \qmlmethod object QtQuick::Context2D::clip() + \qmlmethod Context2D QtQuick::Context2D::clip() Creates the clipping region from the current path. Any parts of the shape outside the clipping path are not displayed. @@ -2554,7 +2554,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_clip(const QV4::FunctionOb } /*! - \qmlmethod object QtQuick::Context2D::closePath() + \qmlmethod Context2D QtQuick::Context2D::closePath() Closes the current subpath by drawing a line to the beginning of the subpath, automatically starting a new path. The current point of the new path is the previous subpath's first point. @@ -2572,7 +2572,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_closePath(const QV4::Funct } /*! - \qmlmethod object QtQuick::Context2D::fill() + \qmlmethod Context2D QtQuick::Context2D::fill() Fills the subpaths with the current fill style. @@ -2590,7 +2590,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_fill(const QV4::FunctionOb } /*! - \qmlmethod object QtQuick::Context2D::lineTo(real x, real y) + \qmlmethod Context2D QtQuick::Context2D::lineTo(real x, real y) Draws a line from the current position to the point at (\a x, \a y). */ @@ -2614,7 +2614,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_lineTo(const QV4::Function } /*! - \qmlmethod object QtQuick::Context2D::moveTo(real x, real y) + \qmlmethod Context2D QtQuick::Context2D::moveTo(real x, real y) Creates a new subpath with a point at (\a x, \a y). */ @@ -2637,7 +2637,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_moveTo(const QV4::Function } /*! - \qmlmethod object QtQuick::Context2D::quadraticCurveTo(real cpx, real cpy, real x, real y) + \qmlmethod Context2D QtQuick::Context2D::quadraticCurveTo(real cpx, real cpy, real x, real y) Adds a quadratic bezier curve between the current point and the endpoint (\a x, \a y) with the control point specified by (\a cpx, \a cpy). @@ -2666,7 +2666,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_quadraticCurveTo(const QV4 } /*! - \qmlmethod object QtQuick::Context2D::rect(real x, real y, real w, real h) + \qmlmethod Context2D QtQuick::Context2D::rect(real x, real y, real w, real h) Adds a rectangle at position (\a x, \a y), with the given width \a w and height \a h, as a closed subpath. @@ -2684,7 +2684,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_rect(const QV4::FunctionOb } /*! - \qmlmethod object QtQuick::Context2D::roundedRect(real x, real y, real w, real h, real xRadius, real yRadius) + \qmlmethod Context2D QtQuick::Context2D::roundedRect(real x, real y, real w, real h, real xRadius, real yRadius) Adds a rounded-corner rectangle, specified by (\a x, \a y, \a w, \a h), to the path. The \a xRadius and \a yRadius arguments specify the radius of the @@ -2708,7 +2708,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_roundedRect(const QV4::Fun } /*! - \qmlmethod object QtQuick::Context2D::ellipse(real x, real y, real w, real h) + \qmlmethod Context2D QtQuick::Context2D::ellipse(real x, real y, real w, real h) Creates an ellipse within the bounding rectangle defined by its top-left corner at (\a x, \a y), width \a w and height \a h, and adds it to the @@ -2731,7 +2731,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_ellipse(const QV4::Functio } /*! - \qmlmethod object QtQuick::Context2D::text(string text, real x, real y) + \qmlmethod Context2D QtQuick::Context2D::text(string text, real x, real y) Adds the given \a text to the path as a set of closed subpaths created from the current context font supplied. @@ -2758,7 +2758,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_text(const QV4::FunctionOb } /*! - \qmlmethod object QtQuick::Context2D::stroke() + \qmlmethod Context2D QtQuick::Context2D::stroke() Strokes the subpaths with the current stroke style. @@ -2776,7 +2776,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_stroke(const QV4::Function } /*! - \qmlmethod object QtQuick::Context2D::isPointInPath(real x, real y) + \qmlmethod bool QtQuick::Context2D::isPointInPath(real x, real y) Returns \c true if the point (\a x, \a y) is in the current path. @@ -2995,7 +2995,7 @@ QV4::ReturnedValue QQuickJSContext2D::method_set_textBaseline(const QV4::Functio } /*! - \qmlmethod object QtQuick::Context2D::fillText(text, x, y) + \qmlmethod Context2D QtQuick::Context2D::fillText(text, x, y) Fills the specified \a text at the given position (\a x, \a y). @@ -3022,7 +3022,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillText(const QV4::Functi RETURN_RESULT(*thisObject); } /*! - \qmlmethod object QtQuick::Context2D::strokeText(text, x, y) + \qmlmethod Context2D QtQuick::Context2D::strokeText(text, x, y) Strokes the given \a text at a position specified by (\a x, \a y). @@ -3044,7 +3044,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeText(const QV4::Func } /*! - \qmlmethod object QtQuick::Context2D::measureText(text) + \qmlmethod var QtQuick::Context2D::measureText(text) Returns an object with a \c width property, whose value is equivalent to calling QFontMetrics::horizontalAdvance() with the given \a text in the @@ -3293,7 +3293,7 @@ QV4::ReturnedValue QQuickJSContext2DImageData::method_get_height(const QV4::Func } /*! - \qmlproperty object QtQuick::CanvasImageData::data + \qmlproperty CanvasPixelArray QtQuick::CanvasImageData::data Holds the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255. */ QV4::ReturnedValue QQuickJSContext2DImageData::method_get_data(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int) @@ -3496,7 +3496,7 @@ QV4::ReturnedValue QQuickJSContext2DPrototype::method_getImageData(const QV4::Fu } /*! - \qmlmethod object QtQuick::Context2D::putImageData(CanvasImageData imageData, real dx, real dy, real dirtyX, real dirtyY, real dirtyWidth, real dirtyHeight) + \qmlmethod void QtQuick::Context2D::putImageData(CanvasImageData imageData, real dx, real dy, real dirtyX, real dirtyY, real dirtyWidth, real dirtyHeight) Paints the data from the given \a imageData object onto the canvas at (\a dx, \a dy). diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable.cpp index 46d4b264e4..198a4d19ab 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable.cpp @@ -242,7 +242,7 @@ QQuickFlickablePrivate::QQuickFlickablePrivate() , hData(this, &QQuickFlickablePrivate::setViewportX) , vData(this, &QQuickFlickablePrivate::setViewportY) , hMoved(false), vMoved(false) - , stealMouse(false), pressed(false) + , stealGrab(false), pressed(false) , scrollingPhase(false), interactive(true), calcVelocity(false) , pixelAligned(false) , syncDrag(false) @@ -1127,7 +1127,9 @@ void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event) if (interactive && timeline.isActive() && ((qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity && !hData.fixingUp && !hData.inOvershoot) || (qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity && !vData.fixingUp && !vData.inOvershoot))) { - stealMouse = true; // If we've been flicked then steal the click. + // If flicked and still moving, prepare to handle the gesture: the user is probably + // trying to speed up or slow down, not click some child item. + stealGrab = true; int flickTime = timeline.time(); if (flickTime > 600) { // too long between flicks - cancel boost @@ -1141,12 +1143,15 @@ void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event) flickBoost = qMax(1.0, flickBoost - 0.5); } } else { - stealMouse = false; + stealGrab = false; hData.continuousFlickVelocity = 0; vData.continuousFlickVelocity = 0; flickBoost = 1.0; } - q->setKeepMouseGrab(stealMouse); + if (event->isSinglePointEvent()) + q->setKeepMouseGrab(stealGrab); + else + q->setKeepTouchGrab(stealGrab); maybeBeginDrag(computeCurrentTime(event), event->points().first().position(), event->isSinglePointEvent() ? static_cast(event)->buttons() @@ -1211,10 +1216,20 @@ void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventTyp bool stealY = false; bool stealX = false; - if (eventType == QEvent::MouseMove) { - stealX = stealY = stealMouse; - } else if (eventType == QEvent::Wheel) { + bool isTouchEvent = false; + switch (eventType) { + case QEvent::MouseMove: + stealX = stealY = stealGrab; + break; + case QEvent::Wheel: stealX = stealY = scrollingPhase; + break; + case QEvent::TouchUpdate: + stealX = stealY = stealGrab; + isTouchEvent = true; + break; + default: + break; } bool prevHMoved = hMoved; @@ -1286,7 +1301,7 @@ void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventTyp } } } - if (!rejectY && stealMouse && dy != vData.previousDragDelta) { + if (!rejectY && stealGrab && dy != vData.previousDragDelta) { clearTimeline(); vData.move.setValue(newY); vMoved = true; @@ -1359,7 +1374,7 @@ void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventTyp } } } - if (!rejectX && stealMouse && dx != hData.previousDragDelta) { + if (!rejectX && stealGrab && dx != hData.previousDragDelta) { clearTimeline(); hData.move.setValue(newX); hMoved = true; @@ -1376,10 +1391,14 @@ void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventTyp hData.previousDragDelta = dx; } - stealMouse = stealX || stealY; - if (stealMouse) { - if ((stealX && keepX) || (stealY && keepY)) - q->setKeepMouseGrab(true); + stealGrab = stealX || stealY; + if (stealGrab) { + if ((stealX && keepX) || (stealY && keepY)) { + if (isTouchEvent) + q->setKeepTouchGrab(true); + else + q->setKeepMouseGrab(true); + } clearDelayedPress(); } @@ -1446,8 +1465,9 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) { Q_Q(QQuickFlickable); - stealMouse = false; + stealGrab = false; q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); pressed = false; // if we drag then pause before release we should not cause a flick. @@ -1592,7 +1612,7 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) } // And the event has been consumed - d->stealMouse = false; + d->stealGrab = false; d->pressed = false; return; } @@ -1650,7 +1670,7 @@ void QQuickFlickable::touchEvent(QTouchEvent *event) } // And the event has been consumed - d->stealMouse = false; + d->stealGrab = false; d->pressed = false; return; } @@ -1709,7 +1729,7 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) d->draggingEnding(); returnToBounds(); d->lastPosTime = -1; - d->stealMouse = false; + d->stealGrab = false; if (!d->velocityTimeline.isActive() && !d->timeline.isActive()) movementEnding(true, true); return; @@ -2955,8 +2975,9 @@ void QQuickFlickablePrivate::cancelInteraction() clearDelayedPress(); pressed = false; draggingEnding(); - stealMouse = false; + stealGrab = false; q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); fixupX(); fixupY(); if (!isViewMoving()) @@ -2994,32 +3015,24 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); // If a touch event contains a new press point, don't steal right away: watch the movements for a while if (isTouch && static_cast(event)->touchPointStates().testFlag(QEventPoint::State::Pressed)) - d->stealMouse = false; - // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child - if (event->pointCount() > 1) { - qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver; - d->stealMouse = false; - return false; - } else { - qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; - } - + d->stealGrab = false; const auto &firstPoint = event->points().first(); if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) { - // We have an exclusive grab (since we're e.g dragging), but at the same time, we have + // We have an exclusive grab (since we're e.g. dragging), but at the same time, we have // a child with a passive grab (which is why this filter is being called). And because // of that, we end up getting the same pointer events twice; First in our own event // handlers (because of the grab), then once more in here, since we filter the child. - // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more - // from below), we mark the event as filtered, and simply return. - event->setAccepted(true); - return true; + // To avoid processing the event twice (e.g. avoid calling handleReleaseEvent once more + // from below), return early. But return false (not true) so that passive-grab handlers + // (TapHandler, DragHandler) inside this Flickable can still receive the event to update + // their state (e.g. setPressed(false) when drag threshold is exceeded). + return false; } QPointF localPos = mapFromScene(firstPoint.scenePosition()); bool receiverDisabled = receiver && !receiver->isEnabled(); - bool stealThisEvent = d->stealMouse; + bool stealThisEvent = d->stealGrab; bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); bool receiverRelinquishGrab = false; @@ -3050,12 +3063,12 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev d->handlePressEvent(localizedEvent.data()); d->captureDelayedPress(receiver, event); // never grab the pointing device on press during filtering: do it later, during a move - d->stealMouse = false; + d->stealGrab = false; stealThisEvent = false; break; case QEventPoint::State::Released: d->handleReleaseEvent(localizedEvent.data()); - stealThisEvent = d->stealMouse; + stealThisEvent = d->stealGrab; break; case QEventPoint::State::Stationary: case QEventPoint::State::Unknown: @@ -3095,7 +3108,7 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev // mouse released, or another item has claimed the grab d->lastPosTime = -1; d->clearDelayedPress(); - d->stealMouse = false; + d->stealGrab = false; d->pressed = false; } return false; @@ -3430,13 +3443,13 @@ void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding) // emit moving signals bool wasMoving = isMoving(); if (hMovementEnding && d->hData.moving - && (!d->pressed && !d->stealMouse)) { + && (!d->pressed && !d->stealGrab)) { d->hData.moving = false; d->hMoved = false; emit movingHorizontallyChanged(); } if (vMovementEnding && d->vData.moving - && (!d->pressed && !d->stealMouse)) { + && (!d->pressed && !d->stealGrab)) { d->vData.moving = false; d->vMoved = false; emit movingVerticallyChanged(); diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p.h index 770f9f8758..36fccb23a6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p.h @@ -314,6 +314,8 @@ private: friend class QQuickFlickableReboundTransition; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickFlickable::PositionMode) + QT_END_NAMESPACE #endif // QQUICKFLICKABLE_P_H diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p_p.h index a245b0a252..a3edffb1df 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickflickable_p_p.h @@ -187,7 +187,7 @@ public: QQuickTimeLine timeline; bool hMoved : 1; bool vMoved : 1; - bool stealMouse : 1; + bool stealGrab : 1; bool pressed : 1; bool scrollingPhase : 1; bool interactive : 1; diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitem.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitem.cpp index aa6c0df1fc..6b511990c3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitem.cpp @@ -2392,6 +2392,8 @@ QQuickItem::~QQuickItem() #if QT_CONFIG(accessibility) if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) QAccessibleCache::instance()->sendObjectDestroyedEvent(this); + + d->isAccessible = false; #endif if (d->windowRefCount > 1) @@ -2662,9 +2664,9 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo } current = parent; } else if (hasChildren) { - if (!wrap) { + if (!wrap && !isTabFence) { qCDebug(lcFocus) << "QQuickItemPrivate::nextPrevItemInTabFocusChain:" - << "Focus chain about to wrap but wrapping was set to false." + << "Focus chain about to wrap but we're outside a tab fence and wrapping was set to false." << "Returning."; return nullptr; } @@ -8137,7 +8139,7 @@ void QQuickItem::setFocus(bool focus, Qt::FocusReason reason) while (scope && !scope->isFocusScope() && scope->parentItem()) scope = scope->parentItem(); - if (d->focus == focus && (!focus || !scope || QQuickItemPrivate::get(scope)->subFocusItem == this)) + if (d->focus == focus && d->activeFocus == focus && (!focus || !scope || QQuickItemPrivate::get(scope)->subFocusItem == this)) return; bool notifyListeners = false; @@ -8447,7 +8449,7 @@ void QQuickItemPrivate::setHasCursorInChild(bool hc) // if we're asked to turn it off (because of an unsetcursor call, or a node // removal) then we should make sure it's really ok to turn it off. if (!hc && subtreeCursorEnabled) { - if (hasCursor) + if (hasCursor || hasCursorHandler) return; // nope! sorry, I have a cursor myself for (QQuickItem *otherChild : std::as_const(childItems)) { QQuickItemPrivate *otherChildPrivate = QQuickItemPrivate::get(otherChild); @@ -8999,7 +9001,7 @@ void QQuickItem::setContainmentMask(QObject *mask) if (!extraDataExists) d->extra.value(); // ensure extra exists if (mask) { - int methodIndex = mask->metaObject()->indexOfMethod(QByteArrayLiteral("contains(QPointF)")); + int methodIndex = mask->metaObject()->indexOfMethod("contains(QPointF)"); if (methodIndex < 0) { qmlWarning(this) << QStringLiteral("QQuickItem: Object set as mask does not have an invokable contains method, ignoring it."); return; @@ -10294,13 +10296,15 @@ QQuickItemPrivate::ExtraData::ExtraData() #if QT_CONFIG(accessibility) QAccessible::Role QQuickItemPrivate::effectiveAccessibleRole() const { - Q_Q(const QQuickItem); - auto *attached = qmlAttachedPropertiesObject(q, false); auto role = QAccessible::NoRole; - if (auto *accessibleAttached = qobject_cast(attached)) - role = accessibleAttached->role(); - if (role == QAccessible::NoRole) - role = accessibleRole(); + if (!inDestructor) { // we might get called back while emitting QAccessible::ObjectDestroy + Q_Q(const QQuickItem); + auto *attached = qmlAttachedPropertiesObject(q, false); + if (auto *accessibleAttached = qobject_cast(attached)) + role = accessibleAttached->role(); + if (role == QAccessible::NoRole) + role = accessibleRole(); + } return role; } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitemgrabresult.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitemgrabresult.cpp index 878776aa8b..880c4ebb0c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitemgrabresult.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickitemgrabresult.cpp @@ -20,6 +20,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -47,8 +48,10 @@ public: delete cacheEntry; } + // Should be called under a locked mutex! bool hasCallback() const { return qmlEngine && callback.isCallable(); } + // Should be called under a locked mutex! void ensureImageInCache() const { if (url.isEmpty() && !image.isNull()) { url.setScheme(QQuickPixmap::itemGrabberScheme); @@ -61,6 +64,8 @@ public: static QQuickItemGrabResult *create(QQuickItem *item, const QSize &size); + mutable QBasicMutex mutex; + QImage image; mutable QUrl url; @@ -75,6 +80,9 @@ public: QSizeF itemSize; QSize textureSize; qreal devicePixelRatio; + + QMetaObject::Connection setupConnection; + QMetaObject::Connection renderConnection; }; /*! @@ -171,6 +179,7 @@ bool QQuickItemGrabResult::saveToFile(const QString &fileName) const Q_D(const QQuickItemGrabResult); if (fileName.startsWith(QLatin1String("file:/"))) return saveToFile(QUrl(fileName)); + QMutexLocker locker(&d->mutex); return d->image.save(fileName); } @@ -188,6 +197,7 @@ bool QQuickItemGrabResult::saveToFile(const QUrl &filePath) const qWarning() << "saveToFile can only save to a file on the local filesystem"; return false; } + QMutexLocker locker(&d->mutex); return d->image.save(filePath.toLocalFile()); } @@ -207,6 +217,7 @@ bool QQuickItemGrabResult::saveToFile(const QString &fileName) QUrl QQuickItemGrabResult::url() const { Q_D(const QQuickItemGrabResult); + QMutexLocker locker(&d->mutex); d->ensureImageInCache(); return d->url; } @@ -214,6 +225,7 @@ QUrl QQuickItemGrabResult::url() const QImage QQuickItemGrabResult::image() const { Q_D(const QQuickItemGrabResult); + QMutexLocker locker(&d->mutex); return d->image; } @@ -224,9 +236,17 @@ bool QQuickItemGrabResult::event(QEvent *e) { Q_D(QQuickItemGrabResult); if (e->type() == Event_Grab_Completed) { - if (d->hasCallback()) { - // JS callback - d->callback.call(QJSValueList() << d->qmlEngine->newQObject(this)); + // We cannot hold the mutex while emitting the signal or calling the + // callback, because they can immediately call various getters. + QJSValue callback; + QQmlEngine *engine = nullptr; + if (QMutexLocker locker(&d->mutex); d->hasCallback()) { + callback = d->callback; + engine = d->qmlEngine; + } + if (engine) { + // JS callback + callback.call(QJSValueList() << engine->newQObject(this)); QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); } else { Q_EMIT ready(); @@ -236,12 +256,17 @@ bool QQuickItemGrabResult::event(QEvent *e) return QObject::event(e); } +/*! + \internal + This method is called from QSGRenderThread +*/ void QQuickItemGrabResult::setup() { Q_D(QQuickItemGrabResult); + QMutexLocker locker(&d->mutex); if (!d->item) { - disconnect(d->window.data(), &QQuickWindow::beforeSynchronizing, this, &QQuickItemGrabResult::setup); - disconnect(d->window.data(), &QQuickWindow::afterRendering, this, &QQuickItemGrabResult::render); + disconnect(d->setupConnection); + disconnect(d->renderConnection); QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed)); return; } @@ -254,9 +279,14 @@ void QQuickItemGrabResult::setup() d->itemSize = QSizeF(d->item->width(), d->item->height()); } +/*! + \internal + This method is called from QSGRenderThread +*/ void QQuickItemGrabResult::render() { Q_D(QQuickItemGrabResult); + QMutexLocker locker(&d->mutex); if (!d->texture) return; @@ -287,8 +317,8 @@ void QQuickItemGrabResult::render() delete d->texture; d->texture = nullptr; - disconnect(d->window.data(), &QQuickWindow::beforeSynchronizing, this, &QQuickItemGrabResult::setup); - disconnect(d->window.data(), &QQuickWindow::afterRendering, this, &QQuickItemGrabResult::render); + disconnect(d->setupConnection); + disconnect(d->renderConnection); QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed)); } @@ -319,6 +349,8 @@ QQuickItemGrabResult *QQuickItemGrabResultPrivate::create(QQuickItem *item, cons QQuickItemGrabResult *result = new QQuickItemGrabResult(); QQuickItemGrabResultPrivate *d = result->d_func(); + // no need to lock here, since we didn't do the connection yet, so it cannot be + // accessed from multiple threads d->item = item; d->window = item->window(); d->textureSize = size; @@ -355,10 +387,24 @@ QSharedPointer QQuickItem::grabToImage(const QSize &target if (!result) return QSharedPointer(); - connect(window(), &QQuickWindow::beforeSynchronizing, result, &QQuickItemGrabResult::setup, Qt::DirectConnection); - connect(window(), &QQuickWindow::afterRendering, result, &QQuickItemGrabResult::render, Qt::DirectConnection); + QQuickItemGrabResultPrivate *d = result->d_func(); + auto sharedResult = QSharedPointer(result); + result->setParent(nullptr); // the smart pointer now manages the lifetime - return QSharedPointer(result); + auto weakResult = QWeakPointer(sharedResult); + QMutexLocker locker(&d->mutex); + d->setupConnection = connect(window(), &QQuickWindow::beforeSynchronizing, + result, [weakResult] { + if (auto strong = weakResult.toStrongRef(); strong) + strong->setup(); + }, Qt::DirectConnection); + d->renderConnection = connect(window(), &QQuickWindow::afterRendering, + result, [weakResult] { + if (auto strong = weakResult.toStrongRef(); strong) + strong->render(); + }, Qt::DirectConnection); + + return sharedResult; } /*! @@ -426,12 +472,17 @@ bool QQuickItem::grabToImage(const QJSValue &callback, const QSize &targetSize) if (!result) return false; - connect(window(), &QQuickWindow::beforeSynchronizing, result, &QQuickItemGrabResult::setup, Qt::DirectConnection); - connect(window(), &QQuickWindow::afterRendering, result, &QQuickItemGrabResult::render, Qt::DirectConnection); - QQuickItemGrabResultPrivate *d = result->d_func(); + + QMutexLocker locker(&d->mutex); d->qmlEngine = engine; d->callback = callback; + + d->setupConnection = connect(window(), &QQuickWindow::beforeSynchronizing, + result, &QQuickItemGrabResult::setup, Qt::DirectConnection); + d->renderConnection = connect(window(), &QQuickWindow::afterRendering, + result, &QQuickItemGrabResult::render, Qt::DirectConnection); + return true; } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicklistview.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicklistview.cpp index e972006249..38a44dd658 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicklistview.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicklistview.cpp @@ -2481,6 +2481,26 @@ QQuickListView::~QQuickListView() Note that the position of the current item may only be approximate until it becomes visible in the view. + + As \c currentItem needs to work with any delegate, its type is \l{Item}. + Often, a \c ListView is however used with exactly one type of delegate. + In that case, casting \c currentItem to the delegate's type can help + tooling and lead to more efficient code: + + \qml + component Message : Item { + required property string sender + required property string text + } + ListView { + id: messageView + delegate: Message {} + model: messageModel + } + Button { + text: "Reply to %1".arg((messageView.currentItem) as Message).sender + } + \endqml */ /*! diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickloader.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickloader.cpp index e23243ce00..111e68eb5d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickloader.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickloader.cpp @@ -488,7 +488,7 @@ QUrl QQuickLoader::setSourceUrlHelper(const QUrl &unresolvedUrl) } /*! - \qmlmethod object QtQuick::Loader::setSource(url source, object properties) + \qmlmethod void QtQuick::Loader::setSource(url source, var properties) Creates an object instance of the given \a source component that will have the given \a properties. The \a properties argument is optional. The instance diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickmousearea.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickmousearea.cpp index 139f8899f3..98c3f0e8e3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickmousearea.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickmousearea.cpp @@ -1083,7 +1083,7 @@ void QQuickMouseArea::itemChange(ItemChange change, const ItemChangeData &value) // that this QQuickMouseArea item was one of the hovered items. if (dap->hoveredLeafItemFound) { for (auto hoverItem : dap->hoverItems) { - if (hoverItem.first == this) { + if (hoverItem.item == this) { // Found a match so update the hover state. d->lastScenePos = scenePos; d->lastPos = mapFromScene(d->lastScenePos); diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickshadereffect.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickshadereffect.cpp index 9c969e05a3..e4e9648ea4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickshadereffect.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickshadereffect.cpp @@ -1212,11 +1212,7 @@ void QQuickShaderEffectPrivate::disconnectSignals(Shader shaderType) if (source) { if (q->window()) QQuickItemPrivate::get(source)->derefWindow(); - auto it = m_destroyedConnections.constFind(source); - if (it != m_destroyedConnections.constEnd()) { - QObject::disconnect(*it); - m_destroyedConnections.erase(it); - } + QObject::disconnect(m_destroyedConnections.take(source)); } } } @@ -1449,8 +1445,8 @@ void QQuickShaderEffectPrivate::updateShaderVars(Shader shaderType) // disconnect instead. if (!m_destroyedConnections.contains(source)) m_destroyedConnections.insert(source, - QObject::connect(source, &QObject::destroyed, source, - [this](QObject *obj) { sourceDestroyed(obj); })); + connect(source, &QObject::destroyed, + this, &QQuickShaderEffectPrivate::sourceDestroyed)); } } } @@ -1513,13 +1509,8 @@ void QQuickShaderEffectPrivate::propertyChanged(int mappedId) // If the same source has been attached to two separate // textures/samplers, then changing one of them would trigger both // to be disconnected. So check first. - if (sourceIsUnique(source, type, idx)) { - auto it = m_destroyedConnections.constFind(source); - if (it != m_destroyedConnections.constEnd()) { - QObject::disconnect(*it); - m_destroyedConnections.erase(it); - } - } + if (sourceIsUnique(source, type, idx)) + QObject::disconnect(m_destroyedConnections.take(source)); } source = qobject_cast(qvariant_cast(vd.value)); @@ -1532,8 +1523,8 @@ void QQuickShaderEffectPrivate::propertyChanged(int mappedId) QQuickItemPrivate::get(source)->refWindow(q->window()); if (!m_destroyedConnections.contains(source)) m_destroyedConnections.insert(source, - QObject::connect(source, &QObject::destroyed, - source, [this](QObject *obj) { sourceDestroyed(obj); })); + connect(source, &QObject::destroyed, + this, &QQuickShaderEffectPrivate::sourceDestroyed)); } m_dirty |= QSGShaderEffectNode::DirtyShaderTexture; diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview.cpp index e1011ce90c..106a60ebb7 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview.cpp @@ -5,20 +5,23 @@ #include "qquicktableview_p.h" #include "qquicktableview_p_p.h" -#include #include #include +#include +#include +#include +#include +#include #include #include -#include -#include -#include #include #include #include #include +#include + /*! \qmltype TableView \inqmlmodule QtQuick @@ -1974,54 +1977,63 @@ QPoint QQuickTableViewPrivate::clampedCellAtPos(const QPointF &pos) const void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection) { + if (oldSelection == newSelection) + return; + const QAbstractItemModel *qaim = selectionModel->model(); const QRect oldRect = oldSelection.normalized(); const QRect newRect = newSelection.normalized(); + const auto &columnMapping = syncView ? syncView->d_func()->horizontalLogicalIndices + : horizontalLogicalIndices; + const auto &rowMapping = syncView ? syncView->d_func()->verticalLogicalIndices + : verticalLogicalIndices; + const bool hasMapping = !columnMapping.empty() || !rowMapping.empty(); + QItemSelection select; QItemSelection deselect; + const auto mergeInto = + [this, qaim, hasMapping](QItemSelection &selection, + const QModelIndex &startIndex, const QModelIndex &endIndex) + { + if (hasMapping) { + for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), + logicalColumnIndex(modelIndex.column())); + selection.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); + } + } else { + selection.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + } + }; + // Select cells inside the new selection rect { const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x()); const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width()); - for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { - const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); - select.append(QItemSelection(logicalModelIndex, logicalModelIndex)); - } + mergeInto(select, startIndex, endIndex); } // Unselect cells in the new minus old rects if (oldRect.x() < newRect.x()) { const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x()); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1); - for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { - const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); - deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); - } + mergeInto(deselect, startIndex, endIndex); } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) { const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width()); - for (auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { - const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); - deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); - } + mergeInto(deselect, startIndex, endIndex); } if (oldRect.y() < newRect.y()) { const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x()); const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width()); - for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { - const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); - deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); - } + mergeInto(deselect, startIndex, endIndex); } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) { const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x()); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width()); - for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { - const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); - deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); - } + mergeInto(deselect, startIndex, endIndex); } if (selectionFlag == QItemSelectionModel::Select) { @@ -2814,11 +2826,20 @@ FxTableItem *QQuickTableViewPrivate::createFxTableItem(const QPoint &cell, QQmlI Q_Q(QQuickTableView); bool ownItem = false; + QObject* object = nullptr; + const QAbstractItemModel *aim = model->abstractItemModel(); + const int modelRow = isTransposed ? logicalColumnIndex(cell.y()) : logicalRowIndex(cell.y()); + const int modelColumn = isTransposed ? logicalRowIndex(cell.x()) : logicalColumnIndex(cell.x()); + const int modelIndex = modelIndexAtCell(QPoint(modelColumn, modelRow)); - int modelIndex = modelIndexAtCell(isTransposed ? QPoint(logicalRowIndex(cell.x()), logicalColumnIndex(cell.y())) : - QPoint(logicalColumnIndex(cell.x()), logicalRowIndex(cell.y()))); + if (tableModel && aim) { + // Prefer loading via QModelIndex so that QQmlTableInstanceModel can also + // match recently released items by model index, not just by delegate type. + object = tableModel->object(aim->index(modelRow, modelColumn), incubationMode); + } else { + object = model->object(modelIndex, incubationMode); + } - QObject* object = model->object(modelIndex, incubationMode); if (!object) { if (model->incubationStatus(modelIndex) == QQmlIncubator::Loading) { // Item is incubating. Return nullptr for now, and let the table call this @@ -2928,6 +2949,8 @@ void QQuickTableViewPrivate::unloadItem(const QPoint &cell) const int modelIndex = modelIndexAtCell(cell); Q_TABLEVIEW_ASSERT(loadedItems.contains(modelIndex), modelIndex << cell); releaseItem(loadedItems.take(modelIndex), reusableFlag); + if (tableModel) + tableModel->commitReleasedItems(); } bool QQuickTableViewPrivate::canLoadTableEdge(Qt::Edge tableEdge, const QRectF fillRect) const @@ -3669,6 +3692,10 @@ void QQuickTableViewPrivate::processRebuildTable() updateEditItem(); updateCurrentRowAndColumn(); + // Move released items that was not reused during the rebuild to the reuse pool + if (tableModel) + tableModel->commitReleasedItems(); + emit q->layoutChanged(); qCDebug(lcTableViewDelegateLifecycle()) << "current table:" << tableLayoutToString(); @@ -4445,9 +4472,25 @@ void QQuickTableViewPrivate::itemCreatedCallback(int modelIndex, QObject*) updatePolish(); } -void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object) +void QQuickTableViewPrivate::updateItemProperties(int flatIndex, QObject *object, bool init) { Q_Q(QQuickTableView); + const QPoint cell = cellAtModelIndex(flatIndex); + const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); + const bool current = currentInSelectionModel(visualCell); + const bool selected = selectedInSelectionModel(visualCell); + + setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), flatIndex, object, init); + setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), flatIndex, object, init); + setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), flatIndex, object, init); + setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), flatIndex, object, init); + setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), flatIndex, object, init); +} + +void QQuickTableViewPrivate::initItemCallback(int flatIndex, QObject *object) +{ + Q_Q(QQuickTableView); + Q_UNUSED(flatIndex); auto item = qobject_cast(object); if (!item) @@ -4458,17 +4501,6 @@ void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object) if (auto attached = getAttachedObject(item)) attached->setView(q); - - const QPoint cell = cellAtModelIndex(modelIndex); - const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); - const bool current = currentInSelectionModel(visualCell); - const bool selected = selectedInSelectionModel(visualCell); - - setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, item, true); - setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true); - setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true); - setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), modelIndex, item, true); - setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, item, true); } void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object) @@ -4479,20 +4511,9 @@ void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object) emit attached->pooled(); } -void QQuickTableViewPrivate::itemReusedCallback(int modelIndex, QObject *object) +void QQuickTableViewPrivate::itemReusedCallback(int flatIndex, QObject *object) { - Q_Q(QQuickTableView); - - const QPoint cell = cellAtModelIndex(modelIndex); - const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); - const bool current = currentInSelectionModel(visualCell); - const bool selected = selectedInSelectionModel(visualCell); - - setRequiredProperty(kRequiredProperty_tableView, QVariant::fromValue(q), modelIndex, object, false); - setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false); - setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false); - // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing - setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, object, false); + Q_UNUSED(flatIndex); if (auto item = qobject_cast(object)) QQuickItemPrivate::get(item)->setCulled(false); @@ -4732,8 +4753,9 @@ void QQuickTableViewPrivate::connectToModel() QObjectPrivate::connect(model, &QQmlInstanceModel::createdItem, this, &QQuickTableViewPrivate::itemCreatedCallback); QObjectPrivate::connect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback); - QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); - QObjectPrivate::connect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + QObjectPrivate::connect(model, &QQmlInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); + QObjectPrivate::connect(model, &QQmlInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + QObjectPrivate::connect(model, &QQmlInstanceModel::updateItemProperties, this, &QQuickTableViewPrivate::updateItemProperties); // Connect atYEndChanged to a function that fetches data if more is available QObjectPrivate::connect(q, &QQuickTableView::atYEndChanged, this, &QQuickTableViewPrivate::fetchMoreData); @@ -4752,6 +4774,7 @@ void QQuickTableViewPrivate::connectToModel() connect(aim, &QAbstractItemModel::columnsRemoved, this, &QQuickTableViewPrivate::columnsRemovedCallback); connect(aim, &QAbstractItemModel::modelReset, this, &QQuickTableViewPrivate::modelResetCallback); connect(aim, &QAbstractItemModel::layoutChanged, this, &QQuickTableViewPrivate::layoutChangedCallback); + connect(aim, &QAbstractItemModel::dataChanged, this, &QQuickTableViewPrivate::dataChangedCallback); } else { QObjectPrivate::connect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated); } @@ -4769,8 +4792,9 @@ void QQuickTableViewPrivate::disconnectFromModel() QObjectPrivate::disconnect(model, &QQmlInstanceModel::createdItem, this, &QQuickTableViewPrivate::itemCreatedCallback); QObjectPrivate::disconnect(model, &QQmlInstanceModel::initItem, this, &QQuickTableViewPrivate::initItemCallback); - QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); - QObjectPrivate::disconnect(model, &QQmlTableInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + QObjectPrivate::disconnect(model, &QQmlInstanceModel::itemPooled, this, &QQuickTableViewPrivate::itemPooledCallback); + QObjectPrivate::disconnect(model, &QQmlInstanceModel::itemReused, this, &QQuickTableViewPrivate::itemReusedCallback); + QObjectPrivate::disconnect(model, &QQmlInstanceModel::updateItemProperties, this, &QQuickTableViewPrivate::updateItemProperties); QObjectPrivate::disconnect(q, &QQuickTableView::atYEndChanged, this, &QQuickTableViewPrivate::fetchMoreData); @@ -4783,6 +4807,7 @@ void QQuickTableViewPrivate::disconnectFromModel() disconnect(aim, &QAbstractItemModel::columnsRemoved, this, &QQuickTableViewPrivate::columnsRemovedCallback); disconnect(aim, &QAbstractItemModel::modelReset, this, &QQuickTableViewPrivate::modelResetCallback); disconnect(aim, &QAbstractItemModel::layoutChanged, this, &QQuickTableViewPrivate::layoutChangedCallback); + disconnect(aim, &QAbstractItemModel::dataChanged, this, &QQuickTableViewPrivate::dataChangedCallback); } else { QObjectPrivate::disconnect(model, &QQmlInstanceModel::modelUpdated, this, &QQuickTableViewPrivate::modelUpdated); } @@ -4892,6 +4917,27 @@ void QQuickTableViewPrivate::modelResetCallback() scheduleRebuildTable(RebuildOption::All); } +void QQuickTableViewPrivate::dataChangedCallback(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) +{ + const auto *chooser = qobject_cast(assignedDelegate); + if (!chooser) + return; + + if (loadedItems.isEmpty() + || topLeft.column() > rightColumn() || bottomRight.column() < leftColumn() + || topLeft.row() > bottomRow() || bottomRight.row() < topRow()) { + return; + } + + if (!roles.empty()) { + const int roleIndex = topLeft.model()->roleNames().key(chooser->role().toUtf8()); + if (!roles.contains(roleIndex)) + return; + } + + scheduleRebuildTable(RebuildOption::ViewportOnly); +} + void QQuickTableViewPrivate::positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect) { Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); @@ -6304,7 +6350,7 @@ void QQuickTableView::moveRow(int source, int destination) d->moveSection(source, destination, Qt::Vertical); } -void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientations orientation) +void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientation orientation) { Q_Q(QQuickTableView); @@ -6320,53 +6366,45 @@ void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orient if (m_sectionState != SectionState::Moving) { m_sectionState = SectionState::Moving; - if (syncView) + if (syncView) { syncView->d_func()->moveSection(source, destination, orientation); - else { + } else { // Initialize the visual and logical index mapping initializeIndexMapping(); // Set current index mapping according to moving rows or columns - SectionData *visualIndex = nullptr; - SectionData *logicalIndex = nullptr; + auto &visualIndices = visualIndicesForOrientation(orientation); + auto &logicalIndices = logicalIndicesForOrientation(orientation); - if (orientation == Qt::Horizontal) { - visualIndex = visualIndices[0].data(); - logicalIndex = logicalIndices[0].data(); - } else if (orientation == Qt::Vertical) { - visualIndex = visualIndices[1].data(); - logicalIndex = logicalIndices[1].data(); - } - - const int logical = logicalIndex[source].index; + const int logical = logicalIndices.at(source).index; int visual = source; if (destination > source) { while (visual < destination) { - SectionData &visualData = visualIndex[logicalIndex[visual + 1].index]; - SectionData &logicalData = logicalIndex[visual]; + SectionData &visualData = visualIndices[logicalIndices[visual + 1].index]; + SectionData &logicalData = logicalIndices[visual]; visualData.prevIndex = visualData.index; visualData.index = visual; logicalData.prevIndex = logicalData.index; - logicalData.index = logicalIndex[visual + 1].index; + logicalData.index = logicalIndices[visual + 1].index; ++visual; } } else { while (visual > destination) { - SectionData &visualData = visualIndex[logicalIndex[visual - 1].index]; - SectionData &logicalData = logicalIndex[visual]; + SectionData &visualData = visualIndices[logicalIndices[visual - 1].index]; + SectionData &logicalData = logicalIndices[visual]; visualData.prevIndex = visualData.index; visualData.index = visual; logicalData.prevIndex = logicalData.index; - logicalData.index = logicalIndex[visual - 1].index; + logicalData.index = logicalIndices[visual - 1].index; --visual; } } - visualIndex[logical].prevIndex = visualIndex[logical].index; - visualIndex[logical].index = destination; - logicalIndex[destination].prevIndex = logicalIndex[destination].index; - logicalIndex[destination].index = logical; + visualIndices[logical].prevIndex = visualIndices[logical].index; + visualIndices[logical].index = destination; + logicalIndices[destination].prevIndex = logicalIndices[destination].index; + logicalIndices[destination].index = logical; // Trigger section move for horizontal and vertical child views // Used in a case where moveSection() triggered for table view @@ -6386,10 +6424,13 @@ void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orient // Emit section moved signal for the sections moved in the view const int startIndex = (source > destination) ? destination : source; const int endIndex = (source > destination) ? source : destination; - const int mapIndex = static_cast(orientation) - 1; + const auto &logicalDataIndices = syncView + ? syncView->d_func()->logicalIndicesForOrientation(orientation) + : logicalIndicesForOrientation(orientation); + const auto &visualDataIndices = syncView + ? syncView->d_func()->visualIndicesForOrientation(orientation) + : visualIndicesForOrientation(orientation); for (int index = startIndex; index <= endIndex; index++) { - const SectionData *logicalDataIndices = (syncView ? syncView->d_func()->logicalIndices[mapIndex].constData() : logicalIndices[mapIndex].constData()); - const SectionData *visualDataIndices = syncView ? syncView->d_func()->visualIndices[mapIndex].constData() : visualIndices[mapIndex].constData(); const int prevLogicalIndex = logicalDataIndices[index].prevIndex; if (orientation == Qt::Horizontal) emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index); @@ -6411,27 +6452,30 @@ void QQuickTableView::clearRowReordering() d->clearSection(Qt::Vertical); } -void QQuickTableViewPrivate::clearSection(Qt::Orientations orientation) +void QQuickTableViewPrivate::clearSection(Qt::Orientation orientation) { Q_Q(QQuickTableView); - const int mapIndex = static_cast(orientation) - 1; - const QList oldLogicalIndices = syncView ? syncView->d_func()->logicalIndices[mapIndex] : logicalIndices[mapIndex]; - const QList oldVisualIndices = syncView ? syncView->d_func()->visualIndices[mapIndex] : visualIndices[mapIndex];; + const auto &oldLogicalIndices = syncView + ? syncView->d_func()->logicalIndicesForOrientation(orientation) + : logicalIndicesForOrientation(orientation); + const auto &oldVisualIndices = syncView + ? syncView->d_func()->visualIndicesForOrientation(orientation) + : visualIndicesForOrientation(orientation); - if (syncView) + if (syncView) { syncView->d_func()->clearSection(orientation); - else { + } else { // Clear the index mapping and rebuild the table - logicalIndices[mapIndex].clear(); - visualIndices[mapIndex].clear(); + logicalIndicesForOrientation(orientation).clear(); + visualIndicesForOrientation(orientation).clear(); scheduleRebuildTable(RebuildOption::ViewportOnly); } // Emit section moved signal for the sections moved in the view - for (int index = 0; index < oldLogicalIndices.size(); index++) { - const SectionData *logicalDataIndices = oldLogicalIndices.constData(); - const SectionData *visualDataIndices = oldVisualIndices.constData(); + for (int index = 0; index < int(oldLogicalIndices.size()); index++) { + const auto &logicalDataIndices = oldLogicalIndices; + const auto &visualDataIndices = oldVisualIndices; if (logicalDataIndices[index].index != index) { const int currentIndex = logicalDataIndices[index].index; if (orientation == Qt::Horizontal) @@ -6810,7 +6854,7 @@ void QQuickTableView::edit(const QModelIndex &index) d->editModel->useImportVersion(d->resolveImportVersion()); QObject::connect(d->editModel, &QQmlInstanceModel::initItem, this, [this, d] (int serializedModelIndex, QObject *object) { - // initItemCallback will call setRequiredProperty for each required property in the + // updateItemProperties() will call setRequiredProperty for each required property in the // delegate, both for this class, but also also for any subclasses. setRequiredProperty // is currently dependent of the QQmlTableInstanceModel that was used to create the object // in order to initialize required properties, so we need to set the editItem variable @@ -6821,7 +6865,8 @@ void QQuickTableView::edit(const QModelIndex &index) if (!d->editItem) return; // Initialize required properties - d->initItemCallback(serializedModelIndex, object); + const bool init = true; + d->updateItemProperties(serializedModelIndex, object, init); const auto cellItem = itemAtCell(cellAtIndex(d->editIndex)); Q_ASSERT(cellItem); d->editItem->setParentItem(cellItem); @@ -7593,58 +7638,58 @@ void QQuickTableViewPrivate::initializeIndexMapping() visualIndex[index].index = logicalIndex[index].index = index; }; - if (visualIndices[0].size() != tableSize.width() - || logicalIndices[0].size() != tableSize.width()) - initIndices(visualIndices[0], logicalIndices[0], tableSize.width()); + if (horizontalVisualIndices.size() != size_t(tableSize.width()) + || horizontalLogicalIndices.size() != size_t(tableSize.width())) + initIndices(horizontalVisualIndices, horizontalLogicalIndices, tableSize.width()); - if (visualIndices[1].size() != tableSize.height() - || logicalIndices[1].size() != tableSize.height()) - initIndices(visualIndices[1], logicalIndices[1], tableSize.height()); + if (verticalVisualIndices.size() != size_t(tableSize.height()) + || verticalLogicalIndices.size() != size_t(tableSize.height())) + initIndices(verticalVisualIndices, verticalLogicalIndices, tableSize.height()); } void QQuickTableViewPrivate::clearIndexMapping() { - logicalIndices[0].clear(); - visualIndices[0].clear(); + horizontalLogicalIndices.clear(); + horizontalVisualIndices.clear(); - logicalIndices[1].clear(); - visualIndices[1].clear(); + verticalLogicalIndices.clear(); + verticalVisualIndices.clear(); } int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const { if (syncView) return syncView->d_func()->logicalRowIndex(visualIndex); - if (logicalIndices[1].isEmpty() || visualIndex < 0) + if (verticalLogicalIndices.empty() || visualIndex < 0) return visualIndex; - return logicalIndices[1].constData()[visualIndex].index; + return verticalLogicalIndices.at(visualIndex).index; } int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const { if (syncView) return syncView->d_func()->logicalColumnIndex(visualIndex); - if (logicalIndices[0].isEmpty() || visualIndex < 0) + if (horizontalLogicalIndices.empty() || visualIndex < 0) return visualIndex; - return logicalIndices[0].constData()[visualIndex].index; + return horizontalLogicalIndices.at(visualIndex).index; } int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const { if (syncView) return syncView->d_func()->visualRowIndex(logicalIndex); - if (visualIndices[1].isEmpty() || logicalIndex < 0) + if (verticalVisualIndices.empty() || logicalIndex < 0) return logicalIndex; - return visualIndices[1].constData()[logicalIndex].index; + return verticalVisualIndices.at(logicalIndex).index; } int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const { if (syncView) return syncView->d_func()->visualColumnIndex(logicalIndex); - if (visualIndices[0].isEmpty() || logicalIndex < 0) + if (horizontalVisualIndices.empty() || logicalIndex < 0) return logicalIndex; - return visualIndices[0].constData()[logicalIndex].index; + return horizontalVisualIndices.at(logicalIndex).index; } int QQuickTableViewPrivate::getEditCellIndex(const QModelIndex &index) const diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p.h index b6c17720d9..592fedf005 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p.h @@ -179,10 +179,10 @@ public: void setDelegateModelAccess(QQmlDelegateModel::DelegateModelAccess delegateModelAccess); Q_INVOKABLE void forceLayout(); - Q_INVOKABLE void positionViewAtCell(const QPoint &cell, QQuickTableView::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); - Q_INVOKABLE void positionViewAtIndex(const QModelIndex &index, QQuickTableView::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); - Q_INVOKABLE void positionViewAtRow(int row, QQuickTableView::PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF()); - Q_INVOKABLE void positionViewAtColumn(int column, QQuickTableView::PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF()); + Q_INVOKABLE void positionViewAtCell(const QPoint &cell, QQuickFlickable::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); + Q_INVOKABLE void positionViewAtIndex(const QModelIndex &index, QQuickFlickable::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); + Q_INVOKABLE void positionViewAtRow(int row, QQuickFlickable::PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF()); + Q_INVOKABLE void positionViewAtColumn(int column, QQuickFlickable::PositionMode mode, qreal offset = 0, const QRectF &subRect = QRectF()); Q_INVOKABLE QQuickItem *itemAtCell(const QPoint &cell) const; Q_REVISION(6, 4) Q_INVOKABLE QPoint cellAtPosition(const QPointF &position, bool includeSpacing = false) const; @@ -226,7 +226,7 @@ public: QT_DEPRECATED_VERSION_X_6_5("Use itemAtIndex(index(row, column)) instead") Q_INVOKABLE QQuickItem *itemAtCell(int column, int row) const; QT_DEPRECATED_VERSION_X_6_5("Use positionViewAtIndex(index(row, column)) instead") - Q_INVOKABLE void positionViewAtCell(int column, int row, QQuickTableView::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); + Q_INVOKABLE void positionViewAtCell(int column, int row, QQuickFlickable::PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); #endif Q_REVISION(6, 8) Q_INVOKABLE void moveColumn(int source, int destination); @@ -330,7 +330,6 @@ private: friend class QQuickTableViewPrivate; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickTableView::PositionMode) Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickTableView::EditTriggers) QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p_p.h index 0a558081d9..4c7b135722 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktableview_p_p.h @@ -40,6 +40,8 @@ #include #endif +#include + QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcTableViewDelegateLifecycle) @@ -51,6 +53,7 @@ static const int kEdgeIndexAtEnd = -3; class FxTableItem; class QQuickTableSectionSizeProviderPrivate; +class QTypeRevision; /*! \internal * TableView uses QQuickTableViewHoverHandler to track where the pointer is @@ -478,8 +481,19 @@ public: int prevIndex = -1; }; - QList visualIndices[Qt::Vertical]; - QList logicalIndices[Qt::Vertical]; + std::vector horizontalVisualIndices; + std::vector verticalVisualIndices; + std::vector horizontalLogicalIndices; + std::vector verticalLogicalIndices; + + std::vector &visualIndicesForOrientation(Qt::Orientation orientation) + { + return orientation == Qt::Horizontal ? horizontalVisualIndices : verticalVisualIndices; + } + std::vector &logicalIndicesForOrientation(Qt::Orientation orientation) + { + return orientation == Qt::Horizontal ? horizontalLogicalIndices : verticalLogicalIndices; + } SectionState m_sectionState = SectionState::Idle; @@ -595,6 +609,7 @@ public: virtual void itemPooledCallback(int modelIndex, QObject *object); virtual void itemReusedCallback(int modelIndex, QObject *object); virtual void modelUpdated(const QQmlChangeSet &changeSet, bool reset); + virtual void updateItemProperties(int flatIndex, QObject *object, bool init); virtual void syncWithPendingChanges(); virtual void syncDelegate(); @@ -618,6 +633,7 @@ public: void columnsRemovedCallback(const QModelIndex &parent, int begin, int end); void layoutChangedCallback(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); void modelResetCallback(); + void dataChangedCallback(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles); bool compareModel(const QVariant& model1, const QVariant& model2) const; void positionViewAtRow(int row, Qt::Alignment alignment, qreal offset, const QRectF subRect = QRectF()); @@ -689,10 +705,10 @@ public: inline void setActivePointerHandler(QQuickTableViewPointerHandler *handler) { activePtrHandler = handler; } inline QQuickTableViewPointerHandler* activePointerHandler() const { return activePtrHandler; } // Row/Column reordering - void moveSection(int source , int destination, Qt::Orientations orientation); + void moveSection(int source , int destination, Qt::Orientation orientation); void initializeIndexMapping(); void clearIndexMapping(); - void clearSection(Qt::Orientations orientation); + void clearSection(Qt::Orientation orientation); virtual int logicalRowIndex(const int visualIndex) const; virtual int logicalColumnIndex(const int visualIndex) const; virtual int visualRowIndex(const int logicalIndex) const; diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktext.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktext.cpp index f2ef15bbbe..5a65f170c4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktext.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktext.cpp @@ -472,6 +472,10 @@ void QQuickTextPrivate::updateSize() signalSizeChange(previousSize); lineCount = 1; emit q->lineCountChanged(); + if (truncated) { + truncated = false; + emit q->truncatedChanged(); + } updateType = UpdatePaintNode; q->update(); return; @@ -976,12 +980,19 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (eos != -1) // There's an abbreviated string available break; - const QTextLine nextLine = layout.createLine(); + QTextLine nextLine = layout.createLine(); elideText = wrappedLine ? elidedText(line.width(), line, &nextLine) : elidedText(line.width(), line); elideStart = line.textStart(); // elideEnd isn't required for right eliding. + + // nextLine starts beyond maximumLineCount and is only + // needed for the elide calculation above. Finalize it + // at zero width so endLayout() won't give it QFIXED_MAX + // width, which would inflate maximumWidth(). + if (nextLine.isValid()) + nextLine.setLineWidth(0); } else { br = unelidedRect; height = naturalHeight; @@ -1020,9 +1031,10 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) : layoutText.size(); if (eol < layoutText.size() && layoutText.at(eol) != QChar::LineSeparator) line = layout.createLine(); - for (; line.isValid() && unwrappedLineCount <= maxLineCount; ++unwrappedLineCount) + for (; line.isValid() && unwrappedLineCount < maxLineCount; ++unwrappedLineCount) line = layout.createLine(); } + layout.endLayout(); const qreal naturalWidth = layout.maximumWidth(); @@ -1584,10 +1596,7 @@ QQuickText::~QQuickText() Sets the family name of the font. - 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. + \include qmltypereference.qdoc qml-font-family */ /*! @@ -1596,8 +1605,7 @@ QQuickText::~QQuickText() Sets the style name of the font. - The style name is case insensitive. If set, the font will be matched against style name instead - of the font properties \l font.weight, \l font.bold and \l font.italic. + \include qmltypereference.qdoc qml-font-style-name */ /*! @@ -1609,22 +1617,7 @@ QQuickText::~QQuickText() /*! \qmlproperty int QtQuick::Text::font.weight - The requested weight of the font. The weight requested must be an integer - between 1 and 1000, or one of the predefined values: - - \value Font.Thin 100 - \value Font.ExtraLight 200 - \value Font.Light 300 - \value Font.Normal 400 (default) - \value Font.Medium 500 - \value Font.DemiBold 600 - \value Font.Bold 700 - \value Font.ExtraBold 800 - \value Font.Black 900 - - \qml - Text { text: "Hello"; font.weight: Font.DemiBold } - \endqml + \include qmltypereference.qdoc qml-font-weight */ /*! @@ -1665,8 +1658,7 @@ QQuickText::~QQuickText() Sets the letter spacing for the font. - Letter spacing changes the default spacing between individual letters in the font. - A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. + \include qmltypereference.qdoc qml-font-letter-spacing */ /*! @@ -1674,9 +1666,7 @@ QQuickText::~QQuickText() Sets the word spacing for the font. - Word spacing changes the default spacing between individual words. - A positive value increases the word spacing by a corresponding amount of pixels, - while a negative value decreases the inter-word spacing accordingly. + \include qmltypereference.qdoc qml-font-word-spacing */ /*! @@ -1684,122 +1674,37 @@ QQuickText::~QQuickText() Sets the capitalization for the text. - \value Font.MixedCase the normal case: no capitalization change is applied - \value Font.AllUppercase alters the text to be rendered in all uppercase type - \value Font.AllLowercase alters the text to be rendered in all lowercase type - \value Font.SmallCaps alters the text to be rendered in small-caps type - \value Font.Capitalize alters the text to be rendered with the first character of - each word as an uppercase character - - \qml - Text { text: "Hello"; font.capitalization: Font.AllLowercase } - \endqml + \include qmltypereference.qdoc qml-font-capitalization */ /*! \qmlproperty enumeration QtQuick::Text::font.hintingPreference \since 5.8 - Sets the preferred hinting on the text. This is a hint to the underlying text rendering system - to use a certain level of hinting, and has varying support across platforms. See the table in - the documentation for QFont::HintingPreference for more details. + Sets the preferred hinting on the text. - \note This property only has an effect when used together with render type Text.NativeRendering. - - \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, 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 - of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's - layout will be scalable to higher density devices (such as printers) without impacting - details such as line breaks. - \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and - vertical directions. The text will be altered to optimize legibility on the target - device, but since the metrics will depend on the target size of the text, the positions - of glyphs, line breaks, and other typographical detail will not scale, meaning that a - text layout may look different on devices with different pixel densities. - - \qml - Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } - \endqml + \include qmltypereference.qdoc qml-font-hinting-preference */ /*! \qmlproperty bool QtQuick::Text::font.kerning \since 5.10 - Enables or disables the kerning OpenType feature when shaping the text. Disabling this may - improve performance when creating or changing the text, at the expense of some cosmetic - features. The default value is true. - - \qml - Text { text: "OATS FLAVOUR WAY"; font.kerning: false } - \endqml + \include qmltypereference.qdoc qml-font-kerning */ /*! \qmlproperty bool QtQuick::Text::font.preferShaping \since 5.10 - 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 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. - - The default value is true. - - \qml - Text { text: "Some text"; font.preferShaping: false } - \endqml + \include qmltypereference.qdoc qml-font-prefer-shaping */ /*! \qmlproperty object QtQuick::Text::font.variableAxes \since 6.7 -//! [qml-font-variable-axes] - Applies floating point values to variable axes in variable fonts. - - Variable fonts provide a way to store multiple variations (with different weights, widths - or styles) in the same font file. The variations are given as floating point values for - a pre-defined set of parameters, called "variable axes". Specific instances are typically - given names by the font designer, and, in Qt, these can be selected using setStyleName() - just like traditional sub-families. - - In some cases, it is also useful to provide arbitrary values for the different axes. For - instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these. - You could then manually request this by supplying a custom value for the "wght" axis in the - font. - - \qml - Text { - text: "Foobar" - font.family: "MyVariableFont" - font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 } - } - \endqml - - If the "wght" axis is supported by the font and the given value is within its defined range, - a font corresponding to the weight 550.0 will be provided. - - There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width), - "ital" (italic) and "opsz" (optical size). They each have indivdual ranges defined in the font - itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas - "ital" can span from 0 to 1 (from not italic to fully italic). - - A font may also choose to define custom axes; the only limitation is that the name has to - meet the requirements for a QFont::Tag (sequence of four latin-1 characters.) - - By default, no variable axes are set. - - \note On Windows, variable axes are not supported if the optional GDI font backend is in use. - - \sa QFont::setVariableAxis() -//! [qml-font-variable-axes] + \include qmltypereference.qdoc qml-font-variable-axes */ @@ -1807,113 +1712,21 @@ QQuickText::~QQuickText() \qmlproperty object QtQuick::Text::font.features \since 6.6 -//! [qml-font-features] - Applies integer values to specific OpenType features when shaping the text based on the contents - in \a features. This provides advanced access to the font shaping process, and can be used - to support font features that are otherwise not covered in the API. - - The font features are represented by a map from four-letter tags to integer values. This integer - value passed along with the tag in most cases represents a boolean value: A zero value means the - feature is disabled, and a non-zero value means it is enabled. For certain font features, - however, it may have other interpretations. For example, when applied to the \c salt feature, the - value is an index that specifies the stylistic alternative to use. - - For example, the \c frac font feature will convert diagonal fractions separated with a slash - (such as \c 1/2) with a different representation. Typically this will involve baking the full - fraction into a single character width (such as \c ½). - - If a font supports the \c frac feature, then it can be enabled in the shaper as in the following - code: - - \qml - Text { - text: "One divided by two is 1/2" - font.family: "MyFractionFont" - font.features: { "frac": 1 } - } - \endqml - - Multiple features can be assigned values in the same mapping. For instance, - if you would like to also disable kerning for the font, you can explicitly - disable this as follows: - - \qml - Text { - text: "One divided by two is 1/2" - font.family: "MyFractionFont" - font.features: { "frac": 1, "kern": 0 } - } - \endqml - - You can also collect the font properties in an object: - - \qml - Text { - text: "One divided by two is 1/2" - font: { - family: "MyFractionFont" - features: { "frac": 1, "kern": 0 } - } - } - \endqml - - \note By default, Qt will enable and disable certain font features based on other font - properties. In particular, the \c kern feature will be enabled/disabled depending on the - \l font.kerning property of the QFont. In addition, all ligature features (\c liga, \c clig, - \c dlig, \c hlig) will be disabled if a \l font.letterSpacing is set, but only for writing - systems where the use of ligature is cosmetic. For writing systems where ligatures are required, - the features will remain in their default state. The values set using \c font.features will - override the default behavior. If, for instance, \c{"kern"} is set to 1, then kerning will - always be enabled, regardless of whether the \l font.kerning property is set to false. Similarly, - if it is set to \c 0, it will always be disabled. - - \sa QFont::setFeature() -//! [qml-font-features] + \include qmltypereference.qdoc qml-font-features */ /*! \qmlproperty bool QtQuick::Text::font.contextFontMerging \since 6.8 -//! [qml-font-context-font-merging] - If the selected font does not contain a certain character, Qt automatically chooses a - similar-looking fallback font that contains the character. By default this is done on a - character-by-character basis. - - This means that in certain uncommon cases, many different fonts may be used to represent one - string of text even if it's in the same script. Setting \c contextFontMerging to true will try - finding the fallback font that matches the largest subset of the input string instead. This - will be more expensive for strings where missing glyphs occur, but may give more consistent - results. By default, \c contextFontMerging is \c{false}. - - \sa QFont::StyleStrategy -//! [qml-font-context-font-merging] + \include qmltypereference.qdoc qml-font-context-font-merging */ /*! \qmlproperty bool QtQuick::Text::font.preferTypoLineMetrics \since 6.8 -//! [qml-font-prefer-typo-line-metrics] For compatibility reasons, OpenType fonts contain two - competing sets of the vertical line metrics that provide the \l{QFontMetricsF::ascent()}{ascent}, - \l{QFontMetricsF::descent()}{descent} and \l{QFontMetricsF::leading()}{leading} of the font. These - are often referred to as the - \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} (Windows) - metrics and the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo} - (typographical) metrics. While the specification recommends using the \c typo metrics for line - spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} flag is set in - the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection} - field of the font. For backwards-compatibility reasons, this is also the case for Qt applications. - This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to indicate that the \c{typo} - metrics are valid, nor for fonts where the \c{win} metrics and \c{typo} metrics match up. However, - for certain fonts the \c{win} metrics may be larger than the preferable line spacing and the - \c{USE_TYPO_METRICS} flag may be unset by mistake. For such fonts, setting - \c{font.preferTypoLineMetrics} may give superior results. - - By default, \c preferTypoLineMetrics is \c{false}. - - \sa QFont::StyleStrategy -//! [qml-font-prefer-typo-line-metrics] + \include qmltypereference.qdoc qml-font-prefer-typo-line-metrics */ @@ -3136,6 +2949,7 @@ QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const translatedMousePos.rx() -= q->leftPadding(); translatedMousePos.ry() -= q->topPadding() + QQuickTextUtil::alignedY(layedOutTextRect.height() + lineHeightOffset(), availableHeight(), vAlign); if (styledText) { + translatedMousePos.rx() -= QQuickTextUtil::alignedX(lineWidth, availableWidth(), q->effectiveHAlign()); QString link = anchorAt(&layout, translatedMousePos); if (link.isEmpty() && elideLayout) link = anchorAt(elideLayout.get(), translatedMousePos); diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextedit.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextedit.cpp index d47acd165c..614395ae38 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextedit.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextedit.cpp @@ -187,9 +187,7 @@ QString QQuickTextEdit::text() const Sets the family name of the font. - The family name is case insensitive and may optionally include a foundry name, e.g. "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. + \include qmltypereference.qdoc qml-font-family */ /*! @@ -212,22 +210,7 @@ QString QQuickTextEdit::text() const /*! \qmlproperty int QtQuick::TextEdit::font.weight - The requested weight of the font. The weight requested must be an integer - between 1 and 1000, or one of the predefined values: - - \value Font.Thin 100 - \value Font.ExtraLight 200 - \value Font.Light 300 - \value Font.Normal 400 (default) - \value Font.Medium 500 - \value Font.DemiBold 600 - \value Font.Bold 700 - \value Font.ExtraBold 800 - \value Font.Black 900 - - \qml - TextEdit { text: "Hello"; font.weight: Font.DemiBold } - \endqml + \include qmltypereference.qdoc qml-font-weight */ /*! @@ -269,8 +252,7 @@ QString QQuickTextEdit::text() const Sets the letter spacing for the font. - Letter spacing changes the default spacing between individual letters in the font. - A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. + \include qmltypereference.qdoc qml-font-letter-spacing */ /*! @@ -278,9 +260,7 @@ QString QQuickTextEdit::text() const Sets the word spacing for the font. - Word spacing changes the default spacing between individual words. - A positive value increases the word spacing by a corresponding amount of pixels, - while a negative value decreases the inter-word spacing accordingly. + \include qmltypereference.qdoc qml-font-word-spacing */ /*! @@ -304,89 +284,51 @@ QString QQuickTextEdit::text() const \qmlproperty enumeration QtQuick::TextEdit::font.hintingPreference \since 5.8 - Sets the preferred hinting on the text. This is a hint to the underlying text rendering system - to use a certain level of hinting, and has varying support across platforms. See the table in - the documentation for QFont::HintingPreference for more details. + Sets the preferred hinting on the text. - \note This property only has an effect when used together with render type TextEdit.NativeRendering. - - \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. - \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 - of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's - layout will be scalable to higher density devices (such as printers) without impacting - details such as line breaks. - \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and - vertical directions. The text will be altered to optimize legibility on the target - device, but since the metrics will depend on the target size of the text, the positions - of glyphs, line breaks, and other typographical detail will not scale, meaning that a - text layout may look different on devices with different pixel densities. - - \qml - TextEdit { text: "Hello"; renderType: TextEdit.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } - \endqml + \include qmltypereference.qdoc qml-font-hinting-preference */ /*! \qmlproperty bool QtQuick::TextEdit::font.kerning \since 5.10 - Enables or disables the kerning OpenType feature when shaping the text. Disabling this may - improve performance when creating or changing the text, at the expense of some cosmetic - features. The default value is true. - - \qml - TextEdit { text: "OATS FLAVOUR WAY"; kerning: font.false } - \endqml + \include qmltypereference.qdoc qml-font-kerning */ /*! \qmlproperty bool QtQuick::TextEdit::font.preferShaping \since 5.10 - 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 - 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. - - The default value is true. - - \qml - TextEdit { text: "Some text"; font.preferShaping: false } - \endqml + \include qmltypereference.qdoc qml-font-prefer-shaping */ /*! \qmlproperty object QtQuick::TextEdit::font.variableAxes \since 6.7 - \include qquicktext.cpp qml-font-variable-axes + \include qmltypereference.qdoc qml-font-variable-axes */ /*! \qmlproperty object QtQuick::TextEdit::font.features \since 6.6 - \include qquicktext.cpp qml-font-features + \include qmltypereference.qdoc qml-font-features */ /*! \qmlproperty bool QtQuick::TextEdit::font.contextFontMerging \since 6.8 - \include qquicktext.cpp qml-font-context-font-merging + \include qmltypereference.qdoc qml-font-context-font-merging */ /*! \qmlproperty bool QtQuick::TextEdit::font.preferTypoLineMetrics \since 6.8 - \include qquicktext.cpp qml-font-prefer-typo-line-metrics + \include qmltypereference.qdoc qml-font-prefer-typo-line-metrics */ /*! diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextinput.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextinput.cpp index 9552018074..b72bb2235b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextinput.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextinput.cpp @@ -231,9 +231,7 @@ QString QQuickTextInputPrivate::realText() const Sets the family name of the font. - The family name is case insensitive and may optionally include a foundry name, e.g. "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. + \include qmltypereference.qdoc qml-font-family */ /*! @@ -255,22 +253,7 @@ QString QQuickTextInputPrivate::realText() const /*! \qmlproperty int QtQuick::TextInput::font.weight - The requested weight of the font. The weight requested must be an integer - between 1 and 1000, or one of the predefined values: - - \value Font.Thin 100 - \value Font.ExtraLight 200 - \value Font.Light 300 - \value Font.Normal 400 (default) - \value Font.Medium 500 - \value Font.DemiBold 600 - \value Font.Bold 700 - \value Font.ExtraBold 800 - \value Font.Black 900 - - \qml - TextInput { text: "Hello"; font.weight: Font.DemiBold } - \endqml + \include qmltypereference.qdoc qml-font-weight */ /*! @@ -311,8 +294,7 @@ QString QQuickTextInputPrivate::realText() const Sets the letter spacing for the font. - Letter spacing changes the default spacing between individual letters in the font. - A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. + \include qmltypereference.qdoc qml-font-letter-spacing */ /*! @@ -320,9 +302,7 @@ QString QQuickTextInputPrivate::realText() const Sets the word spacing for the font. - Word spacing changes the default spacing between individual words. - A positive value increases the word spacing by a corresponding amount of pixels, - while a negative value decreases the inter-word spacing accordingly. + \include qmltypereference.qdoc qml-font-word-spacing */ /*! @@ -330,105 +310,58 @@ QString QQuickTextInputPrivate::realText() const Sets the capitalization for the text. - \value Font.MixedCase the normal case: no capitalization change is applied - \value Font.AllUppercase alters the text to be rendered in all uppercase type - \value Font.AllLowercase alters the text to be rendered in all lowercase type - \value Font.SmallCaps alters the text to be rendered in small-caps type - \value Font.Capitalize alters the text to be rendered with the first character of - each word as an uppercase character - - \qml - TextInput { text: "Hello"; font.capitalization: Font.AllLowercase } - \endqml + \include qmltypereference.qdoc qml-font-capitalization */ /*! \qmlproperty enumeration QtQuick::TextInput::font.hintingPreference \since 5.8 - Sets the preferred hinting on the text. This is a hint to the underlying text rendering system - to use a certain level of hinting, and has varying support across platforms. See the table in - the documentation for QFont::HintingPreference for more details. + Sets the preferred hinting on the text. - \note This property only has an effect when used together with render type TextInput.NativeRendering. - - \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. - \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 - of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's - layout will be scalable to higher density devices (such as printers) without impacting - details such as line breaks. - \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and - vertical directions. The text will be altered to optimize legibility on the target - device, but since the metrics will depend on the target size of the text, the positions - of glyphs, line breaks, and other typographical detail will not scale, meaning that a - text layout may look different on devices with different pixel densities. - - \qml - TextInput { text: "Hello"; renderType: TextInput.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } - \endqml + \include qmltypereference.qdoc qml-font-hinting-preference */ /*! \qmlproperty bool QtQuick::TextInput::font.kerning \since 5.10 - Enables or disables the kerning OpenType feature when shaping the text. Disabling this may - improve performance when creating or changing the text, at the expense of some cosmetic - features. The default value is true. - - \qml - TextInput { text: "OATS FLAVOUR WAY"; font.kerning: false } - \endqml + \include qmltypereference.qdoc qml-font-kerning */ /*! \qmlproperty bool QtQuick::TextInput::font.preferShaping \since 5.10 - 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 - 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. - - The default value is true. - - \qml - TextInput { text: "Some text"; font.preferShaping: false } - \endqml + \include qmltypereference.qdoc qml-font-prefer-shaping */ /*! \qmlproperty object QtQuick::TextInput::font.variableAxes \since 6.7 - \include qquicktext.cpp qml-font-variable-axes + \include qmltypereference.qdoc qml-font-variable-axes */ /*! \qmlproperty object QtQuick::TextInput::font.features \since 6.6 - \include qquicktext.cpp qml-font-features + \include qmltypereference.qdoc qml-font-features */ /*! \qmlproperty bool QtQuick::TextInput::font.contextFontMerging \since 6.8 - \include qquicktext.cpp qml-font-context-font-merging + \include qmltypereference.qdoc qml-font-context-font-merging */ /*! \qmlproperty bool QtQuick::TextInput::font.preferTypoLineMetrics \since 6.8 - \include qquicktext.cpp qml-font-prefer-typo-line-metrics + \include qmltypereference.qdoc qml-font-prefer-typo-line-metrics */ QFont QQuickTextInput::font() const { @@ -1329,8 +1262,15 @@ void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo) Flags that alter behaviour are: \value Qt.ImhHiddenText Characters should be hidden, as is typically used when entering passwords. + + Note that Android IMEs deliberately only + allow Latin/ASCII character input for password fields. \value Qt.ImhSensitiveData Typed text should not be stored by the active input method in any persistent storage like predictive user dictionary. + + Note that the Qt Framework treats this as a visible password + and in this case Android IMEs deliberately only + allow Latin/ASCII character input. \value Qt.ImhNoAutoUppercase The input method should not try to automatically switch to upper case when a sentence ends. \value Qt.ImhPreferNumbers Numbers are preferred (but not required). diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextnodeengine.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextnodeengine.cpp index 0f64bf2b1c..415bf7652e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextnodeengine.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktextnodeengine.cpp @@ -465,7 +465,11 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF case QTextCharFormat::AlignBottom: ascent = size.height() - line.descent(); break; - case QTextCharFormat::AlignBaseline: + case QTextCharFormat::AlignBaseline: { + QFontMetricsF m(format.font()); + ascent = size.height() - m.descent(); + break; + } default: ascent = size.height(); } @@ -841,7 +845,11 @@ void QQuickTextNodeEngine::addToSceneGraph(QSGInternalTextNode *parentNode, const std::pair &range = node->ranges.at(i); int rangeLength = range.second - range.first + 1; - if (previousNode != nullptr) { + // Only check overlap with nodes from the same text position. + // Nodes from different positions (e.g. list bullets from a + // separate QTextLayout) have coincidental range overlaps that + // don't represent actual glyph coverage. + if (previousNode != nullptr && previousNode->position == node->position) { for (int j = 0; j < previousNode->ranges.size(); ++j) { const std::pair &otherRange = previousNode->ranges.at(j); @@ -855,7 +863,8 @@ void QQuickTextNodeEngine::addToSceneGraph(QSGInternalTextNode *parentNode, } } - if (nextNode != nullptr && rangeLength > 0) { + if (nextNode != nullptr && nextNode->position == node->position + && rangeLength > 0) { for (int j = 0; j < nextNode->ranges.size(); ++j) { const std::pair &otherRange = nextNode->ranges.at(j); diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview.cpp index d53ffc1353..91e212de74 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview.cpp @@ -327,18 +327,6 @@ void QQuickTreeViewPrivate::setModelImpl(const QVariant &newModel) emit q->modelChanged(); } -void QQuickTreeViewPrivate::initItemCallback(int serializedModelIndex, QObject *object) -{ - updateRequiredProperties(serializedModelIndex, object, true); - QQuickTableViewPrivate::initItemCallback(serializedModelIndex, object); -} - -void QQuickTreeViewPrivate::itemReusedCallback(int serializedModelIndex, QObject *object) -{ - updateRequiredProperties(serializedModelIndex, object, false); - QQuickTableViewPrivate::itemReusedCallback(serializedModelIndex, object); -} - void QQuickTreeViewPrivate::dataChangedCallback( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { @@ -352,24 +340,28 @@ void QQuickTreeViewPrivate::dataChangedCallback( if (!item) continue; - const int serializedModelIndex = modelIndexAtCell(QPoint(column, row)); - updateRequiredProperties(serializedModelIndex, item, false); + const int flatIndex = column * m_treeModelToTableModel.rowCount() + row; + updateItemProperties(flatIndex, item, false); } } } -void QQuickTreeViewPrivate::updateRequiredProperties(int serializedModelIndex, QObject *object, bool init) +void QQuickTreeViewPrivate::updateItemProperties(int flatIndex, QObject *object, bool init) { Q_Q(QQuickTreeView); - const QPoint cell = cellAtModelIndex(serializedModelIndex); - const int row = cell.y(); - const int column = cell.x(); + QQuickTableViewPrivate::updateItemProperties(flatIndex, object, init); - setRequiredProperty("treeView", QVariant::fromValue(q), serializedModelIndex, object, init); - setRequiredProperty("isTreeNode", column == kTreeColumn, serializedModelIndex, object, init); - setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), serializedModelIndex, object, init); - setRequiredProperty("expanded", q->isExpanded(row), serializedModelIndex, object, init); - setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), serializedModelIndex, object, init); + // Use the model's row count rather than cellAtModelIndex(), since a pending rebuild + // can leave the table size in TableView stale relative to m_treeModelToTableModel. + const int availableRows = m_treeModelToTableModel.rowCount(); + const int column = flatIndex / availableRows; + const int row = flatIndex % availableRows; + + setRequiredProperty("treeView", QVariant::fromValue(q), flatIndex, object, init); + setRequiredProperty("isTreeNode", column == kTreeColumn, flatIndex, object, init); + setRequiredProperty("hasChildren", m_treeModelToTableModel.hasChildren(row), flatIndex, object, init); + setRequiredProperty("expanded", q->isExpanded(row), flatIndex, object, init); + setRequiredProperty("depth", m_treeModelToTableModel.depthAtRow(row), flatIndex, object, init); } void QQuickTreeViewPrivate::updateSelection(const QRect &oldSelection, const QRect &newSelection) diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview_p_p.h index 88d563adb1..f7ecf0ea0a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquicktreeview_p_p.h @@ -36,13 +36,11 @@ public: QVariant modelImpl() const override; void setModelImpl(const QVariant &newModel) override; - void initItemCallback(int serializedModelIndex, QObject *object) override; - void itemReusedCallback(int serializedModelIndex, QObject *object) override; void dataChangedCallback(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles); - void updateRequiredProperties(int serializedModelIndex, QObject *object, bool init); + void updateItemProperties(int flatIndex, QObject *object, bool init) override; void updateSelection(const QRect &oldSelection, const QRect &newSelection) override; public: diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickwindow.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickwindow.cpp index eae5bd3ac1..a8bbb722d4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickwindow.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/items/qquickwindow.cpp @@ -1263,7 +1263,7 @@ QQuickWindow::~QQuickWindow() // loaded into memory. QQuickPixmap::purgeCache(); - for (const QMetaObject::Connection &connection : d->connections) + for (QMetaObject::Connection &connection : d->connections) disconnect(connection); } @@ -1613,6 +1613,20 @@ bool QQuickWindow::event(QEvent *event) d->maybeSynthesizeContextMenuEvent(static_cast(pe)); } +#if QT_CONFIG(tabletevent) + // QGuiApplication::processTabletEvent() uses forwardToPopup() to send + // tablet events to popup windows so they can handle them or block + // further delivery. Prevent fall-through to any window behind a popup + // by accepting any tablet event that landed outside the popup's bounds. + // QTabletEvent.accepted is false by default (unlike mouse events), so + // we need to stop delivery by accepting explicitly. + if (type() == Qt::Popup && QQuickDeliveryAgentPrivate::isTabletEvent(pe) && + !QRect(QPoint(), size()).contains(pe->points().first().scenePosition().toPoint())) { + pe->accept(); + return true; + } +#endif + if (ret) return true; } else if (event->isInputEvent()) { diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/platform/android/jar/src/org/qtproject/qt/android/QtQuickView.java b/local/recipes/qt/qtdeclarative/source/src/quick/platform/android/jar/src/org/qtproject/qt/android/QtQuickView.java index eefde3e57a..5922b96446 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/platform/android/jar/src/org/qtproject/qt/android/QtQuickView.java +++ b/local/recipes/qt/qtdeclarative/source/src/quick/platform/android/jar/src/org/qtproject/qt/android/QtQuickView.java @@ -21,11 +21,7 @@ import java.security.InvalidParameterException; * It brings the power of Qt Quick into your Android app, making it possible to use various Qt Quick * APIs, in Android Java or Kotlin apps. * - * Known limitations: - *
    - *
  • Only CMake is supported, not qmake. - *
  • Only one QtQuickView can be added to your app, adding multiple outcomes unknown. - *
+ * Known limitation: Only CMake is supported, not qmake. * @see Qt QQuickView **/ public class QtQuickView extends QtView { diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrenderloop.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrenderloop.cpp index 319c98ef93..e2a2d458f1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrenderloop.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrenderloop.cpp @@ -384,13 +384,15 @@ void QSGGuiThreadRenderLoop::teardownGraphics() { for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) { if (it->rhi) { - QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown(); + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key()); + wd->cleanupNodesOnShutdown(); if (it->rc) it->rc->invalidate(); releaseSwapchain(it.key()); if (it->ownRhi) QSGRhiSupport::instance()->destroyRhi(it->rhi, {}); it->rhi = nullptr; + wd->rhi = nullptr; } } } @@ -403,7 +405,8 @@ void QSGGuiThreadRenderLoop::handleDeviceLoss() if (!it->rhi || !it->rhi->isDeviceLost()) continue; - QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown(); + QQuickWindowPrivate *wd = QQuickWindowPrivate::get(it.key()); + wd->cleanupNodesOnShutdown(); if (it->rc) it->rc->invalidate(); @@ -414,6 +417,7 @@ void QSGGuiThreadRenderLoop::handleDeviceLoss() if (it->ownRhi) QSGRhiSupport::instance()->destroyRhi(it->rhi, {}); it->rhi = nullptr; + wd->rhi = nullptr; } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrhilayer.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrhilayer.cpp index 01678872bc..3d9d9e08f4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrhilayer.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgrhilayer.cpp @@ -234,6 +234,11 @@ void QSGRhiLayer::clearMainTexture() if (tempRt->create()) { m_context->currentFrameCommandBuffer()->beginPass(tempRt.get(), Qt::transparent, { 1.0f, 0 }); m_context->currentFrameCommandBuffer()->endPass(); + // Keep the objects alive until a later frame, because the current frame + // is only going to be submitted (endFrame) by the scenegraph at a + // later point. + tempRt.release()->deleteLater(); + tempRp.release()->deleteLater(); } else { qWarning("Failed to clear layer main texture in recursive mode"); } diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgthreadedrenderloop.cpp index c8f9909242..5b279fee1a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -584,6 +584,7 @@ void QSGRenderThread::teardownGraphics() if (ownRhi) QSGRhiSupport::instance()->destroyRhi(rhi, {}); rhi = nullptr; + wd->rhi = nullptr; } void QSGRenderThread::handleDeviceLoss() diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickanimation.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickanimation.cpp index 5a7877274d..5bfb3f86d2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickanimation.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickanimation.cpp @@ -2295,169 +2295,169 @@ void QQuickPropertyAnimation::setTo(const QVariant &t) \row \li \c Easing.Linear \li Easing curve for a linear (t) function: velocity is constant. - \li \inlineimage qeasingcurve-linear.png + \li \inlineimage qeasingcurve-linear.png {Linear easing curve} \row \li \c Easing.InQuad \li Easing curve for a quadratic (t^2) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inquad.png + \li \inlineimage qeasingcurve-inquad.png {InQuad easing curve} \row \li \c Easing.OutQuad \li Easing curve for a quadratic (t^2) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outquad.png + \li \inlineimage qeasingcurve-outquad.png {OutQuad easing curve} \row \li \c Easing.InOutQuad \li Easing curve for a quadratic (t^2) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutquad.png + \li \inlineimage qeasingcurve-inoutquad.png {InOutQuad easing curve} \row \li \c Easing.OutInQuad \li Easing curve for a quadratic (t^2) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinquad.png + \li \inlineimage qeasingcurve-outinquad.png {OutInQuad easing curve} \row \li \c Easing.InCubic \li Easing curve for a cubic (t^3) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-incubic.png + \li \inlineimage qeasingcurve-incubic.png {InCubic easing curve} \row \li \c Easing.OutCubic \li Easing curve for a cubic (t^3) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outcubic.png + \li \inlineimage qeasingcurve-outcubic.png {OutCubic easing curve} \row \li \c Easing.InOutCubic \li Easing curve for a cubic (t^3) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutcubic.png + \li \inlineimage qeasingcurve-inoutcubic.png {InOutCubic easing curve} \row \li \c Easing.OutInCubic \li Easing curve for a cubic (t^3) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outincubic.png + \li \inlineimage qeasingcurve-outincubic.png {OutInCubic easing curve} \row \li \c Easing.InQuart \li Easing curve for a quartic (t^4) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inquart.png + \li \inlineimage qeasingcurve-inquart.png {InQuart easing curve} \row \li \c Easing.OutQuart \li Easing curve for a quartic (t^4) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outquart.png + \li \inlineimage qeasingcurve-outquart.png {OutQuart easing curve} \row \li \c Easing.InOutQuart \li Easing curve for a quartic (t^4) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutquart.png + \li \inlineimage qeasingcurve-inoutquart.png {InOutQuart easing curve} \row \li \c Easing.OutInQuart \li Easing curve for a quartic (t^4) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinquart.png + \li \inlineimage qeasingcurve-outinquart.png {OutInQuart easing curve} \row \li \c Easing.InQuint \li Easing curve for a quintic (t^5) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inquint.png + \li \inlineimage qeasingcurve-inquint.png {InQuint easing curve} \row \li \c Easing.OutQuint \li Easing curve for a quintic (t^5) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outquint.png + \li \inlineimage qeasingcurve-outquint.png {OutQuint easing curve} \row \li \c Easing.InOutQuint \li Easing curve for a quintic (t^5) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutquint.png + \li \inlineimage qeasingcurve-inoutquint.png {InOutQuint easing curve} \row \li \c Easing.OutInQuint \li Easing curve for a quintic (t^5) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinquint.png + \li \inlineimage qeasingcurve-outinquint.png {OutInQuint easing curve} \row \li \c Easing.InSine \li Easing curve for a sinusoidal (sin(t)) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-insine.png + \li \inlineimage qeasingcurve-insine.png {InSine easing curve} \row \li \c Easing.OutSine \li Easing curve for a sinusoidal (sin(t)) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outsine.png + \li \inlineimage qeasingcurve-outsine.png {OutSine easing curve} \row \li \c Easing.InOutSine \li Easing curve for a sinusoidal (sin(t)) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutsine.png + \li \inlineimage qeasingcurve-inoutsine.png {InOutSine easing curve} \row \li \c Easing.OutInSine \li Easing curve for a sinusoidal (sin(t)) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinsine.png + \li \inlineimage qeasingcurve-outinsine.png {OutInSine easing curve} \row \li \c Easing.InExpo \li Easing curve for an exponential (2^t) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inexpo.png + \li \inlineimage qeasingcurve-inexpo.png {InExpo easing curve} \row \li \c Easing.OutExpo \li Easing curve for an exponential (2^t) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outexpo.png + \li \inlineimage qeasingcurve-outexpo.png {OutExpo easing curve} \row \li \c Easing.InOutExpo \li Easing curve for an exponential (2^t) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutexpo.png + \li \inlineimage qeasingcurve-inoutexpo.png {InOutExpo easing curve} \row \li \c Easing.OutInExpo \li Easing curve for an exponential (2^t) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinexpo.png + \li \inlineimage qeasingcurve-outinexpo.png {OutInExpo easing curve} \row \li \c Easing.InCirc \li Easing curve for a circular (sqrt(1-t^2)) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-incirc.png + \li \inlineimage qeasingcurve-incirc.png {InCirc easing curve} \row \li \c Easing.OutCirc \li Easing curve for a circular (sqrt(1-t^2)) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outcirc.png + \li \inlineimage qeasingcurve-outcirc.png {OutCirc easing curve} \row \li \c Easing.InOutCirc \li Easing curve for a circular (sqrt(1-t^2)) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutcirc.png + \li \inlineimage qeasingcurve-inoutcirc.png {InOutCirc easing curve} \row \li \c Easing.OutInCirc \li Easing curve for a circular (sqrt(1-t^2)) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outincirc.png + \li \inlineimage qeasingcurve-outincirc.png {OutInCirc easing curve} \row \li \c Easing.InElastic \li Easing curve for an elastic (exponentially decaying sine wave) function: accelerating from zero velocity. \br The peak amplitude can be set with the \e amplitude parameter, and the period of decay by the \e period parameter. - \li \inlineimage qeasingcurve-inelastic.png + \li \inlineimage qeasingcurve-inelastic.png {InElastic easing curve} \row \li \c Easing.OutElastic \li Easing curve for an elastic (exponentially decaying sine wave) function: decelerating to zero velocity. \br The peak amplitude can be set with the \e amplitude parameter, and the period of decay by the \e period parameter. - \li \inlineimage qeasingcurve-outelastic.png + \li \inlineimage qeasingcurve-outelastic.png {OutElastic easing curve} \row \li \c Easing.InOutElastic \li Easing curve for an elastic (exponentially decaying sine wave) function: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutelastic.png + \li \inlineimage qeasingcurve-inoutelastic.png {InOutElastic easing curve} \row \li \c Easing.OutInElastic \li Easing curve for an elastic (exponentially decaying sine wave) function: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinelastic.png + \li \inlineimage qeasingcurve-outinelastic.png {OutInElastic easing curve} \row \li \c Easing.InBack \li Easing curve for a back (overshooting cubic function: (s+1)*t^3 - s*t^2) easing in: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inback.png + \li \inlineimage qeasingcurve-inback.png {InBack easing curve} \row \li \c Easing.OutBack \li Easing curve for a back (overshooting cubic function: (s+1)*t^3 - s*t^2) easing out: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outback.png + \li \inlineimage qeasingcurve-outback.png {OutBack easing curve} \row \li \c Easing.InOutBack \li Easing curve for a back (overshooting cubic function: (s+1)*t^3 - s*t^2) easing in/out: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutback.png + \li \inlineimage qeasingcurve-inoutback.png {InOutBack easing curve} \row \li \c Easing.OutInBack \li Easing curve for a back (overshooting cubic easing: (s+1)*t^3 - s*t^2) easing out/in: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinback.png + \li \inlineimage qeasingcurve-outinback.png {OutInBack easing curve} \row \li \c Easing.InBounce \li Easing curve for a bounce (exponentially decaying parabolic bounce) function: accelerating from zero velocity. - \li \inlineimage qeasingcurve-inbounce.png + \li \inlineimage qeasingcurve-inbounce.png {InBounce easing curve} \row \li \c Easing.OutBounce \li Easing curve for a bounce (exponentially decaying parabolic bounce) function: decelerating to zero velocity. - \li \inlineimage qeasingcurve-outbounce.png + \li \inlineimage qeasingcurve-outbounce.png {OutBounce easing curve} \row \li \c Easing.InOutBounce \li Easing curve for a bounce (exponentially decaying parabolic bounce) function easing in/out: acceleration until halfway, then deceleration. - \li \inlineimage qeasingcurve-inoutbounce.png + \li \inlineimage qeasingcurve-inoutbounce.png {InOutBounce easing curve} \row \li \c Easing.OutInBounce \li Easing curve for a bounce (exponentially decaying parabolic bounce) function easing out/in: deceleration until halfway, then acceleration. - \li \inlineimage qeasingcurve-outinbounce.png + \li \inlineimage qeasingcurve-outinbounce.png {OutInBounce easing curve} \row \li \c Easing.BezierSpline \li Custom easing curve defined by the easing.bezierCurve property. diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent.cpp index fea93f6f89..0a974d6b08 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent.cpp @@ -21,6 +21,7 @@ #include +#include #include QT_BEGIN_NAMESPACE @@ -56,6 +57,22 @@ static bool allowSyntheticRightClick() return allowRightClick != 0; } +static QQuickDeliveryAgentPrivate::HoverItems::iterator findHoverStateByItem(QQuickDeliveryAgentPrivate::HoverItems &hoverItems, QQuickItem *item) +{ + return std::find_if(hoverItems.begin(), hoverItems.end(), + [item](const QQuickDeliveryAgentPrivate::HoverItemState &hoverState) { + return hoverState.item == item; + }); +} + +static QQuickDeliveryAgentPrivate::HoverItems::const_iterator findHoverStateByItem(const QQuickDeliveryAgentPrivate::HoverItems &hoverItems, const QQuickItem *item) +{ + return std::find_if(hoverItems.cbegin(), hoverItems.cend(), + [item](const QQuickDeliveryAgentPrivate::HoverItemState &hoverState) { + return hoverState.item == item; + }); +} + void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent) { Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); @@ -684,17 +701,17 @@ bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp) // while we don't modify hoveritems directly in the loop, the delivery of the event // is expected to reset the stored ID for each cleared item, and items might also - // be removed from the map in response to event delivery. + // be removed from the list in response to event delivery. // So we don't want to iterate over a const version of hoverItems here (it would be // misleading), but still use const_iterators to avoid premature detach and constant // ref-count-checks. for (auto it = hoverItems.cbegin(); it != hoverItems.cend(); ++it) { - if (const auto &item = it.key()) { + if (QQuickItem *item = it->item) { deliverHoverEventToItem(item, item->mapFromScene(lastPos), lastPos, lastPos, globalPos, modifiers, timestamp, HoverChange::Clear); Q_ASSERT(([this, item]{ - const auto &it2 = std::as_const(hoverItems).find(item); - return it2 == hoverItems.cend() || it2.value() == 0; + const auto it2 = findHoverStateByItem(std::as_const(hoverItems), item); + return it2 == hoverItems.cend() || it2->hoverId == 0; }())); } } @@ -1260,8 +1277,8 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); const bool isHovering = item->contains(localPos); - const auto hoverItemIterator = hoverItems.find(item); - const bool wasHovering = hoverItemIterator != hoverItems.end() && hoverItemIterator.value() != 0; + auto hoverItemIterator = findHoverStateByItem(hoverItems, item); + const bool wasHovering = hoverItemIterator != hoverItems.end() && hoverItemIterator->hoverId != 0; qCDebug(lcHoverTrace) << "item:" << item << "scene pos:" << scenePos << "localPos:" << localPos << "wasHovering:" << wasHovering << "isHovering:" << isHovering; @@ -1278,9 +1295,9 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( // line towards the root from now on. hoveredLeafItemFound = true; if (hoverItemIterator != hoverItems.end()) - hoverItemIterator.value() = currentHoverId; + hoverItemIterator->hoverId = currentHoverId; else - hoverItems[item] = currentHoverId; + hoverItems.append({item, currentHoverId}); if (wasHovering) accepted = sendHoverEvent(QEvent::HoverMove, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp); @@ -1288,7 +1305,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( accepted = sendHoverEvent(QEvent::HoverEnter, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp); } else if (wasHovering) { // A leave should never stop propagation - hoverItemIterator.value() = 0; + hoverItemIterator->hoverId = 0; sendHoverEvent(QEvent::HoverLeave, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp); } @@ -1329,10 +1346,11 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( // Mark the whole item as updated, even if only the handler is // actually in a hovered state (because of HoverHandler.margins) hoveredLeafItemFound = true; + hoverItemIterator = findHoverStateByItem(hoverItems, item); if (hoverItemIterator != hoverItems.end()) - hoverItemIterator.value() = currentHoverId; + hoverItemIterator->hoverId = currentHoverId; else - hoverItems[item] = currentHoverId; + hoverItems.append({item, currentHoverId}); if (hh->isBlocking()) { qCDebug(lcHoverTrace) << "skipping rest of hover delivery due to blocking" << hh; accepted = true; @@ -2330,7 +2348,10 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) bool res = deliverHoverEventToItem(item, item->mapFromScene(point.scenePosition()), point.scenePosition(), point.sceneLastPosition(), point.globalPosition(), event->modifiers(), event->timestamp(), HoverChange::Set); // if the event was accepted, then the item's ID must be valid - Q_ASSERT(!res || hoverItems.value(item)); + Q_ASSERT(([this, item = item.get(), res]{ + const auto it2 = findHoverStateByItem(std::as_const(hoverItems), item); + return !res || it2->hoverId != 0; + }())); } } } @@ -2861,9 +2882,29 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve if (event->isAccepted()) { for (auto point : filteringParentTouchEvent.points()) { const QQuickItem *exclusiveGrabber = qobject_cast(event->exclusiveGrabber(point)); - if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab()) + // Transfer the grab to the filtering parent unless the current exclusive grabber has + // keepTouchGrab set AND the filtering parent is not an ancestor of that grabber. + // If filteringParent is an ancestor of exclusiveGrabber (e.g. PinchArea wrapping a Flickable), + // allow the transfer: presumably the user intended the parent to intercept multi-touch gestures. + // But only if the grabber itself accepts touch events — meaning it set keepTouchGrab + // on its own behalf (like Flickable). If the grabber doesn't accept touch events + // (like a passive QQuickText), keepTouchGrab was set by the filtering parent's + // childMouseEventFilter to maintain the filter-based event routing pattern + // (e.g. SplitView sets a handle child as grabber to keep receiving filter calls). + const bool grabberInsideFilteringParent = exclusiveGrabber && + exclusiveGrabber->acceptTouchEvents() && + filteringParent->isAncestorOf(const_cast(exclusiveGrabber)); + if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab() || grabberInsideFilteringParent) event->setExclusiveGrabber(point, filteringParent); } + // QPointerEvent::setAccepted(true) marks all individual QEventPoints as accepted, + // which causes localizedTouchEvent() to skip them (line 9577: if (p.isAccepted()) continue). + // This would prevent any ancestor filtering parent (e.g. PinchArea wrapping this Flickable) + // from seeing the points in the recursive sendFilteredPointerEventImpl() call below. + // Reset per-point accepted state so ancestor filters can still see all relevant points. + // The overall event->isAccepted() remains true to stop non-filter item delivery. + for (int i = 0; i < event->pointCount(); ++i) + event->point(i).setAccepted(false); } } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) && !filteringParent->acceptTouchEvents()) { diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent_p_p.h index bd25ef06e5..516dc9d338 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickdeliveryagent_p_p.h @@ -84,7 +84,12 @@ public: #endif QQuickItem *lastUngrabbed = nullptr; QStack eventsInDelivery; - QFlatMap, uint> hoverItems; + struct HoverItemState { + QPointer item; + uint hoverId = 0; + }; + using HoverItems = QList; + HoverItems hoverItems; QList hasFiltered; // during event delivery to a single receiver, the filtering parents for which childMouseEventFilter was already called QList skipDelivery; // during delivery of one event to all receivers, Items to which we know delivery is no longer necessary @@ -217,6 +222,7 @@ public: QList contextMenuTargets(QQuickItem *item, const QContextMenuEvent *event) const; void deliverContextMenuEvent(QContextMenuEvent *event); }; +Q_DECLARE_TYPEINFO(QQuickDeliveryAgentPrivate::HoverItemState, Q_RELOCATABLE_TYPE); Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickDeliveryAgentPrivate::FocusOptions) diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickforeignutils_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickforeignutils_p.h index 512e434f9e..492eabc72b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickforeignutils_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickforeignutils_p.h @@ -7,6 +7,7 @@ #include +#include #include #include #if QT_CONFIG(im) @@ -38,6 +39,14 @@ QT_BEGIN_NAMESPACE +struct QAccessibilityHintsForeign +{ + Q_GADGET + QML_FOREIGN(QAccessibilityHints) + QML_ANONYMOUS + QML_ADDED_IN_VERSION(6, 10) +}; + struct QStyleHintsForeign { Q_GADGET diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickinputmethod.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickinputmethod.cpp index 5879a2187e..98b9b4f6e8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickinputmethod.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickinputmethod.cpp @@ -9,7 +9,7 @@ QT_BEGIN_NAMESPACE /*! - \qmltype InputMethod + \qmlsingletontype InputMethod \inqmlmodule QtQuick \brief Provides access to \l QInputMethod for QML applications. @@ -17,6 +17,145 @@ QT_BEGIN_NAMESPACE The InputMethod singleton allows access to application's \l QInputMethod object and all its properties and slots. See the \l QInputMethod documentation for further details. + + \since 6.4 +*/ + +/*! + \qmlproperty rect InputMethod::cursorRectangle + + \brief Input item's cursor rectangle in window coordinates. + + The cursor rectangle is often used by various text editing controls + like text prediction popups for following the text being typed. +*/ + +/*! + \qmlproperty rect InputMethod::anchorRectangle + + \brief Input item's anchor rectangle in window coordinates. + + The anchor rectangle is often used by various text editing controls + like text prediction popups for following the text selection. +*/ + +/*! + \qmlproperty rect InputMethod::keyboardRectangle + \brief Virtual keyboard's geometry in window coordinates. + + This might be an empty rectangle if it is not possible to know the geometry + of the keyboard. This is the case for a floating keyboard on android. +*/ + +/*! + \qmlproperty rect InputMethod::inputItemClipRectangle + \brief Input item's clipped rectangle in window coordinates. + + The clipped input rectangle is often used by various input methods to determine + how much screen real estate is available for the input method (e.g. Virtual Keyboard). +*/ + +/*! + \qmlproperty bool InputMethod::visible + \brief Virtual keyboard's visibility on the screen + + The value of this property remains false for devices + with no virtual keyboards. + + \sa show(), hide() +*/ + +/*! + \qmlproperty bool InputMethod::animating + \brief True when the virtual keyboard is being opened or closed. + + The value of this property is false when keyboard is fully open or closed. + When \c animating is \c true and \c visibility is \c true keyboard + is being opened. When \c animating is \c true and \c visibility is + false keyboard is being closed. +*/ + +/*! + \qmlproperty Locale InputMethod::locale + \brief Current input locale. +*/ + +/*! + \qmlproperty enumeration InputMethod::inputDirection + \brief Current input direction. + + Possible values: + + \value Qt.LeftToRight (default) Items are laid out from left to right. + \value Qt.RightToLeft Items are laid out from right to left. + */ + +/*! + \qmlmethod void InputMethod::commit() + + Commits the word that the user is currently composing to the editor. The function is + mostly needed by the input methods with text prediction features and by the + methods where the script used for typing characters is different from the + script that actually gets appended to the editor. Any kind of action that + interrupts the text composing needs to flush the composing state by calling the + commit() function, for example when the cursor is moved elsewhere. +*/ + +/*! + \qmlmethod void InputMethod::hide() + + Requests the virtual keyboard to close. + + Normally applications should not need to call this function, + as the keyboard should close automatically when the text editor loses + focus, for example when the parent view is closed. +*/ + +/*! + \qmlmethod void InputMethod::invokeAction(var a, int cursorPosition) + + Called by the input item when the word currently being composed is tapped by + the user, as indicated by the action \a a and the given \a cursorPosition. + Input methods often use this information to offer more word suggestions to the user. + + \sa QInputMethod::Action +*/ + +/*! + \qmlmethod void InputMethod::reset() + + Resets the input method state. For example, a text editor normally calls + this method before inserting a text to make widget ready to accept a text. + + Input method resets automatically when the focused editor changes. +*/ + +/*! + \qmlmethod void InputMethod::show() + + Requests the virtual keyboard to open. If the platform + doesn't provide a virtual keyboard the visibility + remains false. + + Normally applications should not need to call this + function, as the keyboard should open automatically when + the text editor gains focus. +*/ + +/*! + \qmlmethod void InputMethod::update(enumeration queries) + + Called by the input item to inform the platform input methods when there have been + state changes in the editor's input method query attributes. When calling the function + \a queries parameter has to be used to tell what has changed. An input method + can use this to make queries for attributes it is interested in with QInputMethodQueryEvent. + + In particular calling update() whenever the cursor position changes is important as + that often causes other query attributes, like surrounding text and text selection, + to change as well. The attributes that often change together with cursor position + have been grouped in Qt::ImQueryInput value for convenience. + + \sa Qt::InputMethodQueries */ QQuickInputMethod::QQuickInputMethod(QObject *parent) : QObject(parent) diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickpath.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickpath.cpp index ec46d9d1fe..f3050ed6f6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickpath.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickpath.cpp @@ -248,6 +248,8 @@ bool QQuickPath::hasStartY() const bool QQuickPath::isClosed() const { Q_D(const QQuickPath); + if (d->_pathElements.isEmpty()) + return false; return d->closed; } @@ -3173,9 +3175,7 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &) Sets the family name of the font. - The family name is case insensitive and may optionally include a foundry name, e.g. "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. + \include qmltypereference.qdoc qml-font-family */ /*! @@ -3253,8 +3253,7 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &) Sets the letter spacing for the font. - Letter spacing changes the default spacing between individual letters in the font. - A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. + \include qmltypereference.qdoc qml-font-letter-spacing */ /*! @@ -3262,9 +3261,7 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &) Sets the word spacing for the font. - Word spacing changes the default spacing between individual words. - A positive value increases the word spacing by a corresponding amount of pixels, - while a negative value decreases the inter-word spacing accordingly. + \include qmltypereference.qdoc qml-font-word-spacing */ /*! @@ -3272,72 +3269,47 @@ void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &) Sets the capitalization for the text. - \value Font.MixedCase no capitalization change is applied - \value Font.AllUppercase alters the text to be rendered in all uppercase type - \value Font.AllLowercase alters the text to be rendered in all lowercase type - \value Font.SmallCaps alters the text to be rendered in small-caps type - \value Font.Capitalize alters the text to be rendered with the first character of - each word as an uppercase character - - \qml - PathText { text: "Hello"; font.capitalization: Font.AllLowercase } - \endqml + \include qmltypereference.qdoc qml-font-capitalization */ /*! \qmlproperty bool QtQuick::PathText::font.kerning - Enables or disables the kerning OpenType feature when shaping the text. Disabling this may - improve performance when creating or changing the text, at the expense of some cosmetic - features. The default value is true. - - \qml - PathText { text: "OATS FLAVOUR WAY"; font.kerning: false } - \endqml + \include qmltypereference.qdoc qml-font-kerning */ /*! \qmlproperty bool QtQuick::PathText::font.preferShaping - 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 - 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. - - The default value is true. - - \qml - PathText { text: "Some text"; font.preferShaping: false } - \endqml + \include qmltypereference.qdoc qml-font-prefer-shaping */ /*! \qmlproperty object QtQuick::PathText::font.variableAxes \since 6.7 - \include qquicktext.cpp qml-font-variable-axes + \include qmltypereference.qdoc qml-font-variable-axes */ /*! \qmlproperty object QtQuick::PathText::font.features \since 6.6 - \include qquicktext.cpp qml-font-features + \include qmltypereference.qdoc qml-font-features */ /*! \qmlproperty bool QtQuick::PathText::font.contextFontMerging \since 6.8 - \include qquicktext.cpp qml-font-context-font-merging + \include qmltypereference.qdoc qml-font-context-font-merging */ /*! \qmlproperty bool QtQuick::PathText::font.preferTypoLineMetrics \since 6.8 - \include qquicktext.cpp qml-font-prefer-typo-line-metrics + \include qmltypereference.qdoc qml-font-prefer-typo-line-metrics */ void QQuickPathText::updatePath() const { diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes.cpp b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes.cpp index 524c0426d1..fcadf2ad4a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes.cpp @@ -1389,7 +1389,7 @@ QQuickColorSpaceEnums::NamedColorSpace QQuickColorSpaceValueType::namedColorSpac void QQuickColorSpaceValueType::setNamedColorSpace( QQuickColorSpaceEnums::NamedColorSpace namedColorSpace) { - *this = { (QColorSpace::NamedColorSpace)namedColorSpace }; + *static_cast(this) = { (QColorSpace::NamedColorSpace)namedColorSpace }; } QQuickColorSpaceEnums::Primaries QQuickColorSpaceValueType::primaries() const noexcept diff --git a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes_p.h b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes_p.h index c3e40a8491..92327806d1 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quick/util/qquickvaluetypes_p.h @@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICK_EXPORT QQuickColorValueType : public QColor +class Q_QUICK_EXPORT QQuickColorValueType : private QColor { Q_PROPERTY(qreal r READ r WRITE setR FINAL) Q_PROPERTY(qreal g READ g WRITE setG FINAL) @@ -87,7 +87,7 @@ public: void setHslLightness(qreal); }; -class Q_QUICK_EXPORT QQuickVector2DValueType : public QVector2D +class Q_QUICK_EXPORT QQuickVector2DValueType : private QVector2D { Q_PROPERTY(qreal x READ x WRITE setX FINAL) Q_PROPERTY(qreal y READ y WRITE setY FINAL) @@ -123,7 +123,7 @@ public: Q_INVOKABLE bool fuzzyEquals(const QVector2D &vec) const; }; -class Q_QUICK_EXPORT QQuickVector3DValueType : public QVector3D +class Q_QUICK_EXPORT QQuickVector3DValueType : private QVector3D { Q_PROPERTY(qreal x READ x WRITE setX FINAL) Q_PROPERTY(qreal y READ y WRITE setY FINAL) @@ -164,7 +164,7 @@ public: Q_INVOKABLE bool fuzzyEquals(const QVector3D &vec) const; }; -class Q_QUICK_EXPORT QQuickVector4DValueType : public QVector4D +class Q_QUICK_EXPORT QQuickVector4DValueType : private QVector4D { Q_PROPERTY(qreal x READ x WRITE setX FINAL) Q_PROPERTY(qreal y READ y WRITE setY FINAL) @@ -207,7 +207,7 @@ public: Q_INVOKABLE bool fuzzyEquals(const QVector4D &vec) const; }; -class Q_QUICK_EXPORT QQuickQuaternionValueType : public QQuaternion +class Q_QUICK_EXPORT QQuickQuaternionValueType : private QQuaternion { Q_PROPERTY(qreal scalar READ scalar WRITE setScalar FINAL) Q_PROPERTY(qreal x READ x WRITE setX FINAL) @@ -255,7 +255,7 @@ public: Q_INVOKABLE bool fuzzyEquals(const QQuaternion &q) const; }; -class Q_QUICK_EXPORT QQuickMatrix4x4ValueType : public QMatrix4x4 +class Q_QUICK_EXPORT QQuickMatrix4x4ValueType : private QMatrix4x4 { Q_PROPERTY(qreal m11 READ m11 WRITE setM11 FINAL) Q_PROPERTY(qreal m12 READ m12 WRITE setM12 FINAL) @@ -413,7 +413,7 @@ enum Style { Q_ENUM_NS(Style) }; -class Q_QUICK_EXPORT QQuickFontValueType : public QFont +class Q_QUICK_EXPORT QQuickFontValueType : private QFont { Q_GADGET @@ -547,7 +547,7 @@ enum class TransferFunction { Q_ENUM_NS(TransferFunction) } -class Q_QUICK_EXPORT QQuickColorSpaceValueType : public QColorSpace +class Q_QUICK_EXPORT QQuickColorSpaceValueType : private QColorSpace { Q_GADGET diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/CMakeLists.txt index 1a5160a405..578e0b683d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/CMakeLists.txt @@ -5,6 +5,36 @@ ## QuickControls2 Module: ##################################################################### +# Need to evaluate QuickControls features early. We can't wait until +# qt_internal_add_qml_module() is executed because we need feature evaluation +# to determine the optional imports. +qt_feature_evaluate_features("${CMAKE_CURRENT_SOURCE_DIR}/configure.cmake") + +if(QT_FEATURE_quickcontrols2_fusion) + list(APPEND optional_imports "QtQuick.Controls.Fusion/auto") +endif() +if(QT_FEATURE_quickcontrols2_imagine) + list(APPEND optional_imports "QtQuick.Controls.Imagine/auto") +endif() +if(QT_FEATURE_quickcontrols2_material) + list(APPEND optional_imports "QtQuick.Controls.Material/auto") +endif() +if(QT_FEATURE_quickcontrols2_universal) + list(APPEND optional_imports "QtQuick.Controls.Universal/auto") +endif() +if(QT_FEATURE_quickcontrols2_fluentwinui3) + list(APPEND optional_imports "QtQuick.Controls.FluentWinUI3/auto") +endif() +if(QT_FEATURE_quickcontrols2_macos) + list(APPEND optional_imports "QtQuick.Controls.macOS/auto") +endif() +if(QT_FEATURE_quickcontrols2_ios) + list(APPEND optional_imports "QtQuick.Controls.iOS/auto") +endif() +if(QT_FEATURE_quickcontrols2_windows) + list(APPEND optional_imports "QtQuick.Controls.Windows/auto") +endif() + qt_internal_add_qml_module(QuickControls2 URI "QtQuick.Controls" VERSION "${PROJECT_VERSION}" @@ -13,14 +43,7 @@ qt_internal_add_qml_module(QuickControls2 CLASS_NAME QtQuickControls2Plugin PLUGIN_TARGET qtquickcontrols2plugin OPTIONAL_IMPORTS - QtQuick.Controls.Fusion/auto - QtQuick.Controls.Material/auto - QtQuick.Controls.Imagine/auto - QtQuick.Controls.Universal/auto - QtQuick.Controls.FluentWinUI3/auto - QtQuick.Controls.Windows/auto - QtQuick.Controls.macOS/auto - QtQuick.Controls.iOS/auto + ${optional_imports} DEFAULT_IMPORTS QtQuick.Controls.Basic/auto NO_PLUGIN_OPTIONAL diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/ComboBox.qml index 25a1937501..3c216f2af3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/ComboBox.qml @@ -44,6 +44,7 @@ T.ComboBox { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding leftPadding: !control.mirrored ? 12 : control.editable && activeFocus ? 3 : 1 rightPadding: control.mirrored ? 12 : control.editable && activeFocus ? 3 : 1 topPadding: 6 - control.padding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/MenuBarItem.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/MenuBarItem.qml index fbcb111b9d..f96ceee1e5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/MenuBarItem.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/MenuBarItem.qml @@ -22,7 +22,6 @@ T.MenuBarItem { icon.width: 24 icon.height: 24 - icon.color: control.palette.buttonText contentItem: IconLabel { spacing: control.spacing @@ -33,7 +32,8 @@ T.MenuBarItem { icon: control.icon text: control.text font: control.font - color: control.palette.buttonText + defaultIconColor: control.palette.buttonText + color: defaultIconColor } background: Rectangle { diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/SearchField.qml index 79966851b9..91772a6192 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/basic/SearchField.qml @@ -38,9 +38,10 @@ T.SearchField { searchIndicator.indicator: Rectangle { implicitWidth: 28 implicitHeight: 28 + height: control.height - (background.border.width * 2) x: !control.mirrored ? 3 : control.width - width - 3 - y: control.topPadding + (control.availableHeight - height) / 2 + y: background.border.width color: control.palette.button ColorImage { @@ -56,9 +57,10 @@ T.SearchField { clearIndicator.indicator: Rectangle { implicitWidth: 28 implicitHeight: 28 + height: control.height - (background.border.width * 2) x: control.mirrored ? 3 : control.width - width - 3 - y: control.topPadding + (control.availableHeight - height) / 2 + y: background.border.width visible: control.text.length > 0 color: control.palette.button @@ -73,6 +75,7 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding leftPadding: control.searchIndicator.indicator && !control.mirrored ? 6 : 0 rightPadding: control.clearIndicator.indicator && !control.mirrored ? 6 : 0 topPadding: 6 - control.padding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc index c0d34e3f9e..4a8b7adc12 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/doc/src/qtquickcontrols-customize.qdoc @@ -551,6 +551,12 @@ are not suitable for customizing. \include customizing-native-styles.qdocinc + \note When modifying the size of controls, the actual content size will + typically remain the same. For example, some styles may make their Button + fill the bounds of the control, but horizontally and/or vertically center + the check indicator and text of a CheckBox. If the default resizing + behaviour is not desired, override the respective delegate(s). + \section2 Customizing ApplicationWindow ApplicationWindow consists of one visual item: diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/BusyIndicator.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/BusyIndicator.qml index 1d59ea1474..f6261cd74e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/BusyIndicator.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/BusyIndicator.qml @@ -22,6 +22,7 @@ T.BusyIndicator { property Shape ring: Shape { parent: control.contentItem + visible: control.running x: (parent.width - width) / 2 y: (parent.height - height) / 2 implicitWidth: parent.implicitWidth diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/ComboBox.qml index 1f10b8044f..da293e723f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/ComboBox.qml @@ -68,13 +68,16 @@ T.ComboBox { contentItem: T.TextField { text: control.editable ? control.editText : control.displayText + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + topPadding: control.__config.label_contentItem.topPadding || 0 leftPadding: control.__config.label_contentItem.leftPadding || 0 rightPadding: control.__config.label_contentItem.rightPadding || 0 bottomPadding: control.__config.label_contentItem.bottomPadding || 0 - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding) + topInset: -control.__config.label_contentItem.topInset || 0 + bottomInset: -control.__config.label_contentItem.bottomInset || 0 enabled: control.editable autoScroll: control.editable diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/DelayButton.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/DelayButton.qml index cfc154500e..bc08e4cde5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/DelayButton.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/DelayButton.qml @@ -76,7 +76,7 @@ T.DelayButton { text: control.text font: control.font - color: control.icon.color + color: control.__buttonText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight @@ -90,7 +90,7 @@ T.DelayButton { text: control.text font: control.font - color: control.icon.color + color: control.__buttonText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/SearchField.qml index f0cd13f269..324706294b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/SearchField.qml @@ -72,10 +72,11 @@ T.SearchField { imageConfig: indicatorConfig.indicator_up_background x: !control.mirrored ? control.width - width - control.spacing : control.spacing - y: control.topPadding + (control.availableHeight - height) / 2 + y: control.topPadding implicitWidth: 32 implicitHeight: 24 + height: control.availableHeight ColorImage { x: (parent.width - width) / 2 @@ -103,9 +104,12 @@ T.SearchField { x: (!searchIndicator.indicator || !searchIndicator.indicator.visible) ? (!control.mirrored ? control.width - width - control.spacing : control.spacing) : (!control.mirrored ? control.width - width - (control.spacing * 2) - searchIndicator.indicator.width : searchIndicator.indicator.width + (control.spacing * 2)) - y: control.topPadding + (control.availableHeight - height) / 2 + y: control.topPadding + implicitWidth: 32 implicitHeight: 24 + height: control.availableHeight + visible: control.text.length > 0 ColorImage { @@ -121,13 +125,16 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + leftPadding: control.__config.label_contentItem.leftPadding rightPadding: control.__config.label_contentItem.rightPadding topPadding: control.__config.label_contentItem.topPadding bottomPadding: control.__config.label_contentItem.bottomPadding - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding) + topInset: -control.__config.label_contentItem.topInset || 0 + bottomInset: -control.__config.label_contentItem.bottomInset || 0 text: control.text diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/TabButton.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/TabButton.qml index f391a1c728..ee1f59f94c 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/TabButton.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/TabButton.qml @@ -49,7 +49,7 @@ T.TabButton { font: control.font icon: control.icon defaultIconColor: control.down ? pressedText : control.hovered ? hoveredText : control.palette.buttonText - color: control.icon.color + color: defaultIconColor readonly property color pressedText: Application.styleHints.colorScheme === Qt.Light ? Qt.rgba(control.palette.buttonText.r, control.palette.buttonText.g, control.palette.buttonText.b, 0.447) diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/impl/qquickfluentwinui3focusframe.cpp b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/impl/qquickfluentwinui3focusframe.cpp index 60ca7c49c2..92286c7064 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/impl/qquickfluentwinui3focusframe.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fluentwinui3/impl/qquickfluentwinui3focusframe.cpp @@ -24,8 +24,7 @@ QQuickFluentWinUI3FocusFrame::QQuickFluentWinUI3FocusFrame() connect(qGuiApp, &QGuiApplication::focusObjectChanged, this, [this](QObject *focusObject){ if (QQuickControl *control = qobject_cast(focusObject); control && (control->focusReason() == Qt::FocusReason::TabFocusReason - || control->focusReason() == Qt::FocusReason::BacktabFocusReason - || control->focusReason() == Qt::FocusReason::OtherFocusReason)) { + || control->focusReason() == Qt::FocusReason::BacktabFocusReason)) { moveToItem(control); } else { moveToItem(nullptr); diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/ComboBox.qml index ab67217d88..0be937977e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/ComboBox.qml @@ -44,6 +44,8 @@ T.ComboBox { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding + topPadding: 4 leftPadding: 4 - control.padding rightPadding: 4 - control.padding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/MenuBarItem.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/MenuBarItem.qml index 19d79d21ba..0c345da6c2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/MenuBarItem.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/MenuBarItem.qml @@ -32,7 +32,8 @@ T.MenuBarItem { icon: control.icon text: control.text font: control.font - color: control.down || control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text + defaultIconColor: control.down || control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text + color: defaultIconColor } background: Rectangle { diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/SearchField.qml index eaa4a2e9f3..05d485ddad 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/fusion/SearchField.qml @@ -10,7 +10,6 @@ import QtQuick.Controls.impl import QtQuick.Controls.Fusion import QtQuick.Controls.Fusion.impl - T.SearchField { id: control @@ -38,13 +37,20 @@ T.SearchField { required property int index } - searchIndicator.indicator: Rectangle { - implicitWidth: 20 + searchIndicator.indicator: PaddedRectangle { + implicitWidth: 22 implicitHeight: 20 + height: control.height - 2 - x: !control.mirrored ? 2 : control.width - width - 2 - y: control.topPadding + (control.availableHeight - height) / 2 - color: control.palette.base + x: !control.mirrored ? 1 : control.width - width - 1 + y: 1 + + radius: 1.7 + clip: true + topPadding: -2 + leftPadding: -2 + + color: control.searchIndicator.pressed ? Fusion.buttonColor(control.palette, false, true, true) : "transparent" ColorImage { x: (parent.width - width) / 2 @@ -57,14 +63,21 @@ T.SearchField { } } - clearIndicator.indicator: Rectangle { - implicitWidth: 20 + clearIndicator.indicator: PaddedRectangle { + implicitWidth: 22 implicitHeight: 20 + height: control.height - 2 - x: control.mirrored ? 2 : control.width - width - 2 - y: control.topPadding + (control.availableHeight - height) / 2 + x: control.mirrored ? 1 : control.width - width - 1 + y: 1 visible: control.text.length > 0 - color: control.palette.base + + radius: 1.7 + clip: true + topPadding: -2 + leftPadding: -2 + + color: control.clearIndicator.pressed ? Fusion.buttonColor(control.palette, false, true, true) : "transparent" ColorImage { x: (parent.width - width) / 2 @@ -78,6 +91,8 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding + leftPadding: !control.mirrored ? 6 : 0 rightPadding: !control.mirrored ? 6 : 0 @@ -118,6 +133,60 @@ T.SearchField { height: 1 color: Fusion.topShadow } + + Rectangle { + x: !control.mirrored ? 1 : parent.width - width - 1 + y: 1 + width: control.searchIndicator.indicator && control.searchIndicator.indicator.visible + ? control.searchIndicator.indicator.width + 1 : 0 + height: parent.height - 2 + radius: 2 + + gradient: Gradient { + GradientStop { + position: 0 + color: Fusion.gradientStart(Fusion.buttonColor(control.palette, control.visualFocus, false, control.searchIndicator.hovered)) + } + GradientStop { + position: 1 + color: Fusion.gradientStop(Fusion.buttonColor(control.palette, control.visualFocus, false, control.searchIndicator.hovered)) + } + } + + Rectangle { + x: !control.mirrored ? parent.width - 1 : 0 + width: 1 + height: parent.height + color: Fusion.outline(control.palette) + } + } + + Rectangle { + x: control.mirrored ? 1 : parent.width - width - 1 + y: 1 + width: control.clearIndicator.indicator && control.clearIndicator.indicator.visible + ? control.clearIndicator.indicator.width + 1 : 0 + height: parent.height - 2 + radius: 2 + + gradient: Gradient { + GradientStop { + position: 0 + color: Fusion.gradientStart(Fusion.buttonColor(control.palette, control.visualFocus, false, control.clearIndicator.hovered)) + } + GradientStop { + position: 1 + color: Fusion.gradientStop(Fusion.buttonColor(control.palette, control.visualFocus, false, control.clearIndicator.hovered)) + } + } + + Rectangle { + x: control.mirrored ? parent.width - 1 : 0 + width: 1 + height: parent.height + color: Fusion.outline(control.palette) + } + } } popup: T.Popup { diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/ComboBox.qml index be83369892..3af6063fd8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/ComboBox.qml @@ -58,11 +58,17 @@ T.ComboBox { } contentItem: T.TextField { + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + topPadding: control.background ? control.background.topPadding : 0 leftPadding: control.background ? control.background.leftPadding : 0 rightPadding: control.background ? control.background.rightPadding : 0 bottomPadding: control.background ? control.background.bottomPadding : 0 + topInset: background ? -background.topInset || 0 : 0 + bottomInset: background ? -background.bottomInset || 0 : 0 + text: control.editable ? control.editText : control.displayText enabled: control.editable diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/SearchField.qml index 4348b72bce..fa88d1852a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/imagine/SearchField.qml @@ -84,6 +84,7 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding z: 2 text: control.text diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/ComboBox.qml index 1d94aef325..ba87107081 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/ComboBox.qml @@ -85,8 +85,8 @@ T.ComboBox { contentItem: T.TextField { implicitWidth: control.__popupButtonStyle ? Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) : 0 - implicitHeight: control.__popupButtonStyle ? Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding) : 0 + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) padding: control.__popupButtonStyle ? 0 : 6 text: control.editable ? control.editText : control.displayText enabled: control.editable diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/SearchField.qml index b7e4f3393a..f91c6a73e3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/ios/SearchField.qml @@ -81,6 +81,8 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding + leftPadding: control.searchIndicator.indicator && !control.mirrored ? 12 : 6 rightPadding: control.clearIndicator.indicator && !control.mirrored ? 12 : 6 topPadding: 6 - control.padding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/ComboBox.qml index 01ecbe0e2e..a74561996f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/ComboBox.qml @@ -45,6 +45,7 @@ T.ComboBox { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding leftPadding: Material.textFieldHorizontalPadding topPadding: Material.textFieldVerticalPadding bottomPadding: Material.textFieldVerticalPadding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/SearchField.qml index b3eb281c29..a343ba254b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/material/SearchField.qml @@ -39,8 +39,8 @@ T.SearchField { searchIndicator.indicator: Item { x: !control.mirrored ? 10 : control.width - width - 10 - y: control.topPadding + (control.availableHeight - height) / 2 - height: control.height + y: control.topPadding + height: control.availableHeight width: height / 2 ColorImage { @@ -54,8 +54,8 @@ T.SearchField { clearIndicator.indicator: Item { x: control.mirrored ? 10 : control.width - width - 10 - y: control.topPadding + (control.availableHeight - height) / 2 - height: control.height + y: control.topPadding + height: control.availableHeight width: height / 2 visible: control.text.length > 0 @@ -69,11 +69,17 @@ T.SearchField { } contentItem: T.TextField { + // implicitWidth: Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding + implicitHeight: contentHeight + topPadding + bottomPadding + leftPadding: Material.textFieldHorizontalPadding rightPadding: Material.textFieldHorizontalPadding topPadding: Material.textFieldVerticalPadding bottomPadding: Material.textFieldVerticalPadding + // If we're clipped, set topInset to half the height of the placeholder text to avoid it being clipped. + topInset: clip ? placeholder.height / 2 : 0 + text: control.text color: control.enabled ? control.Material.foreground : control.Material.hintTextColor diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyle.cpp b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyle.cpp index 84a6073e3b..379f10a429 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyle.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyle.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyleplugin.cpp b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyleplugin.cpp index 84ef1f585a..df3225b111 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyleplugin.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/qquickstyleplugin.cpp @@ -15,6 +15,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcStylePlugin, "qt.quick.controls.styleplugin") diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/ComboBox.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/ComboBox.qml index 4f327175dd..a12e2033d2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/ComboBox.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/ComboBox.qml @@ -55,6 +55,8 @@ T.ComboBox { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding + leftPadding: control.mirrored ? 1 : 12 rightPadding: control.mirrored ? 10 : 1 topPadding: 5 - control.topPadding diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/SearchField.qml b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/SearchField.qml index 6579b41984..b09098ed7a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/SearchField.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrols/universal/SearchField.qml @@ -43,9 +43,10 @@ T.SearchField { searchIndicator.indicator: Item { x: !control.mirrored ? control.padding : control.width - width - control.padding - y: control.topPadding + (control.availableHeight - height) / 2 + y: control.topPadding implicitWidth: 28 implicitHeight: 28 + height: control.availableHeight Rectangle { width: parent.width @@ -70,9 +71,10 @@ T.SearchField { clearIndicator.indicator: Item { x: control.mirrored ? control.padding : control.width - width - control.padding - y: control.topPadding + (control.availableHeight - height) / 2 + y: control.topPadding implicitWidth: 28 implicitHeight: 28 + height: control.availableHeight visible: control.text.length > 0 Rectangle { @@ -97,6 +99,8 @@ T.SearchField { } contentItem: T.TextField { + implicitHeight: contentHeight + topPadding + bottomPadding + leftPadding: !control.mirrored ? 6 : 0 rightPadding: !control.mirrored ? 6 : 0 diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrolsimpl/qquickitemgroup.cpp b/local/recipes/qt/qtdeclarative/source/src/quickcontrolsimpl/qquickitemgroup.cpp index 121072c1b9..3bd3b8287b 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrolsimpl/qquickitemgroup.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrolsimpl/qquickitemgroup.cpp @@ -68,13 +68,12 @@ void QQuickItemGroup::itemChange(ItemChange change, const ItemChangeData &data) void QQuickItemGroup::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { - QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry); - if (newGeometry.size() != oldGeometry.size()) { const auto children = childItems(); for (QQuickItem *child : children) child->setSize(newGeometry.size()); } + QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry); } void QQuickItemGroup::itemImplicitWidthChanged(QQuickItem *) diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/CMakeLists.txt index 18da2d3e3b..b15299bc9d 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/CMakeLists.txt @@ -19,6 +19,7 @@ qt_internal_add_module(QuickControlsTestUtilsPrivate Qt::QmlPrivate Qt::Quick Qt::QuickControls2 + Qt::QuickControls2Private Qt::QuickPrivate Qt::QuickTemplates2 Qt::QuickTemplates2Private diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils.cpp b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils.cpp index 941da0f64f..a452ddda10 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils.cpp @@ -3,9 +3,11 @@ #include "controlstestutils_p.h" +#include #include #include #include +#include #include #include #include @@ -51,7 +53,7 @@ bool QQuickControlsTestUtils::QQuickStyleHelper::updateStyle(const QString &styl } void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, - const QString &sourcePath, const QString &targetPath, const QStringList &skipList, + const QString &styleName, const QString &targetPath, const QStringList &skipList, QQuickControlsTestUtils::ForEachCallback callback) { // We cannot use QQmlComponent to load QML files directly from the source tree. @@ -65,25 +67,25 @@ void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString & // the engine's import path. This way we can use QQmlComponent to load each QML file // for benchmarking. - const QFileInfoList entries = QDir(qqc2ImportPath + QLatin1Char('/') + sourcePath).entryInfoList( + const QFileInfoList entries = QDir(qqc2ImportPath + QLatin1Char('/') + styleName.toLower()).entryInfoList( QStringList(QStringLiteral("*.qml")), QDir::Files); for (const QFileInfo &entry : entries) { QString name = entry.baseName(); if (!skipList.contains(name)) { const auto importPathList = engine->importPathList(); for (const QString &importPath : importPathList) { - QString name = entry.dir().dirName() + QLatin1Char('/') + entry.fileName(); + const QString relativePath = entry.dir().dirName() + QLatin1Char('/') + entry.fileName(); QString filePath = importPath + QLatin1Char('/') + targetPath + QLatin1Char('/') + entry.fileName(); if (filePath.startsWith(QLatin1Char(':'))) filePath.prepend(QStringLiteral("qrc")); if (QFile::exists(filePath)) { - callback(name, QUrl::fromLocalFile(filePath)); + callback(styleName, name, relativePath, QUrl::fromLocalFile(filePath)); break; } else { QUrl url(filePath); filePath = QQmlFile::urlToLocalFileOrQrc(filePath); if (!filePath.isEmpty() && QFile::exists(filePath)) { - callback(name, url); + callback(styleName, name, relativePath, url); break; } } @@ -93,13 +95,15 @@ void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString & } void QQuickControlsTestUtils::addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, - const QString &sourcePath, const QString &targetPath, const QStringList &skipList) + const QString &styleName, const QString &targetPath, const QStringList &skipList) { - forEachControl(engine, qqc2ImportPath, sourcePath, targetPath, skipList, [&](const QString &relativePath, const QUrl &absoluteUrl) { + forEachControl(engine, qqc2ImportPath, styleName, targetPath, skipList, [&]( + const QString &, const QString &, const QString &relativePath, const QUrl &absoluteUrl) { QTest::newRow(qPrintable(relativePath)) << absoluteUrl; }); } + bool QQuickControlsTestUtils::verifyButtonClickable(QQuickAbstractButton *button) { if (!button->window()) { @@ -228,11 +232,105 @@ Q_INVOKABLE QQmlComponent *QQuickControlsTestUtils::ComponentCreator::createComp return component.release(); } +QQuickControlsTestUtils::StyleInfo *QQuickControlsTestUtils::StyleInfo::create(QQmlEngine *, QJSEngine *) +{ + return StyleInfo::instance(); +} + +QQuickControlsTestUtils::StyleInfo *QQuickControlsTestUtils::StyleInfo::instance() +{ + static std::unique_ptr instance(new StyleInfo); + // If this API was only used from QML, we could use the default JavaScriptOwnership + // and let the engine take ownership of us. However, it's possible to use this API + // only from C++, which means we need to take ownership just in case. + QJSEngine::setObjectOwnership(instance.get(), QJSEngine::CppOwnership); + return instance.get(); +} + +void QQuickControlsTestUtils::StyleInfo::initialize(const QString &controlsImportPath) +{ +#if defined(Q_OS_ANDROID) + qWarning() << "StyleInfo is not supported when cross-compiling: QTBUG-100191"; + return; +#endif + + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData(QString::fromLatin1( + "import QtQuick.Templates; Control { }").toUtf8(), QUrl()); + + const QStringList qmlTypeNames = QQmlMetaType::qmlTypeNames(); + + // Collect the files from each style in the source tree. + QDirIterator it(controlsImportPath, QStringList() << QLatin1String("*.qml") << QLatin1String("*.js"), + QDir::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QFileInfo info = it.fileInfo(); + if (qmlTypeNames.contains(QLatin1String("QtQuick.Templates/") + info.baseName())) { + const auto dirName = info.dir().dirName(); + const auto typeName = info.fileName(); + m_sourceQmlFiles.append({dirName, typeName, dirName + u"/" + typeName, info.filePath() }); + } + } + + // Gather the list of styles. + QStringList builtInStyles = QQuickStylePrivate::builtInStyles(); + // TODO: add native styles: QTBUG-87108. Originally this list was hard-coded + // and didn't include them. + const QStringList nativeStyles = { QLatin1String("macOS"), QLatin1String("Windows") }; + builtInStyles.removeIf([&nativeStyles](const QString &styleName){ + return nativeStyles.contains(styleName); + }); + + QList> styleRelativePaths; + for (const auto &styleName : std::as_const(builtInStyles)) { + // E.g. { "Basic", "QtQuick/Controls/Basic" } + styleRelativePaths.append(std::make_pair(styleName, QLatin1String("QtQuick/Controls/") + styleName)); + } + + // Then, collect the files from each installed style directory. + for (const auto &stylePathPair : styleRelativePaths) { + forEachControl(&engine, controlsImportPath, stylePathPair.first, stylePathPair.second, QStringList(), + [&](const QString &styleName, const QString &typeName, const QString &relativePath, + const QUrl &absoluteUrl) { + m_installedQmlFiles.append({ styleName, typeName, relativePath, absoluteUrl.toLocalFile() }); + }); + } + + std::sort(m_sourceQmlFiles.begin(), m_sourceQmlFiles.end()); + std::sort(m_installedQmlFiles.begin(), m_installedQmlFiles.end()); +} + QString QQuickControlsTestUtils::StyleInfo::styleName() const { return QQuickStyle::name(); } +void QQuickControlsTestUtils::StyleInfo::warnIfNotInitialized() const +{ + if (m_sourceQmlFiles.isEmpty()) + qWarning() << "StyleInfo hasn't been initialized"; +} + +bool QQuickControlsTestUtils::StyleInfo::QmlFileData::operator<( + const QQuickControlsTestUtils::StyleInfo::QmlFileData &rhs) const +{ + return relativePath < rhs.relativePath; +} + +QList QQuickControlsTestUtils::StyleInfo::sourceQmlFiles() const +{ + warnIfNotInitialized(); + return m_sourceQmlFiles; +} + +QList QQuickControlsTestUtils::StyleInfo::installedQmlFiles() const +{ + warnIfNotInitialized(); + return m_installedQmlFiles; +} + /*! It's recommended to use try-finally (see tst_monthgrid.qml for an example) or init/initTestCase and cleanup/cleanupTestCase if setting environment diff --git a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils_p.h b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils_p.h index 47f5bf4e5d..211c7ff79f 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quickcontrolstestutils/controlstestutils_p.h @@ -48,12 +48,13 @@ namespace QQuickControlsTestUtils QScopedPointer engine; }; - typedef std::function ForEachCallback; + typedef std::function ForEachCallback; - void forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &sourcePath, + void forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &styleName, const QString &targetPath, const QStringList &skipList, ForEachCallback callback); - void addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &sourcePath, - const QString &targetPath, const QStringList &skipList = QStringList()); + void addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &styleName, + const QString &targetPath, const QStringList &skipList = QStringList()); [[nodiscard]] bool verifyButtonClickable(QQuickAbstractButton *button); [[nodiscard]] bool clickButton(QQuickAbstractButton *button); @@ -80,7 +81,34 @@ namespace QQuickControlsTestUtils QML_SINGLETON public: + // Used by C++. + void initialize(const QString &controlsImportPath); + static StyleInfo *instance(); + + // Needed for QML integration. + static StyleInfo *create(QQmlEngine *, QJSEngine *); + QString styleName() const; + + struct QmlFileData { + bool operator<(const QmlFileData &rhs) const; + + QString styleName; + QString typeName; + QString relativePath; + QString absolutePath; + }; + + QList sourceQmlFiles() const; + QList installedQmlFiles() const; + + private: + StyleInfo() = default; + + void warnIfNotInitialized() const; + + QList m_sourceQmlFiles; + QList m_installedQmlFiles; }; class MockPlatformTheme : public QPlatformTheme diff --git a/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qml/FolderBreadcrumbBar.qml b/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qml/FolderBreadcrumbBar.qml index aa524cdcc8..b75c05bca9 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qml/FolderBreadcrumbBar.qml +++ b/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qml/FolderBreadcrumbBar.qml @@ -58,8 +58,10 @@ DialogsQuickImpl.FolderBreadcrumbBar { x: control.leftPadding y: control.topPadding icon.source: "qrc:/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/up-icon-round.png" + icon.color: control.palette.buttonText icon.width: 16 icon.height: 16 + height: control.contentItem.height width: height focusPolicy: Qt.TabFocus } diff --git a/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qquicksidebar.cpp b/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qquicksidebar.cpp index ce8ae582e2..9e77ed53eb 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qquicksidebar.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickdialogs/quickdialogsquickimpl/qquicksidebar.cpp @@ -41,7 +41,7 @@ QQuickSideBar::QQuickSideBar(QQuickItem *parent) Q_D(QQuickSideBar); d->folderPaths = s_defaultPaths; - QObject::connect(this, &QQuickContainer::currentIndexChanged, [d](){ + QObject::connect(this, &QQuickContainer::currentIndexChanged, this, [d] { d->currentButtonClickedUrl.clear(); }); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquickflexboxlayoutengine.cpp b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquickflexboxlayoutengine.cpp index 1741b5cc6c..d6d2451459 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquickflexboxlayoutengine.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquickflexboxlayoutengine.cpp @@ -97,8 +97,9 @@ void QQuickFlexboxLayoutEngine::collectItemSizeHints(QQuickFlexboxLayoutItem *fl if (info->isFillWidthSet() || info->isFillHeightSet()) { // Set stretch to child item both width and height if (auto *parentLayoutItem = qobject_cast(m_flexboxParentItem->quickItem())) { - if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row || - parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) { + const bool horz = parentLayoutItem->direction() == QQuickFlexboxLayout::Row || + parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse; + if (horz) { // If the Layout.fillHeight not been set, the preferred height // will be set as height if (!info->fillHeight()) @@ -124,17 +125,17 @@ void QQuickFlexboxLayoutEngine::collectItemSizeHints(QQuickFlexboxLayoutItem *fl // direction) flexItem->setItemShrinkAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f); } - } - // If the Layout.fillHeight not been set, the preferred height will be - // set as height in the previous condition. Otherwise (for - // Layout.fillHeight been set as true), make flex item to AlignStretch. - // Thus it can also grow vertically. - // Note: The same applies for Layout.fillWidth to grow horizontally. - if ((qt_is_nan(flexItem->size().width()) && info->fillWidth()) || - (qt_is_nan(flexItem->size().height()) && info->fillHeight())) { - flexItem->setItemStretchAlongCrossSection(); - } else { - flexItem->inheritItemStretchAlongCrossSection(); + // If the Layout.fillHeight not been set, the preferred height will be + // set as height in the previous condition. Otherwise (for + // Layout.fillHeight been set as true), make flex item to AlignStretch. + // Thus it can also grow vertically. + // Note: The same applies for Layout.fillWidth to grow horizontally. + if ((horz && (qt_is_nan(flexItem->size().height()) && info->fillHeight())) + || (!horz && (qt_is_nan(flexItem->size().width()) && info->fillWidth()))) { + flexItem->setItemStretchAlongCrossSection(); + } else { + flexItem->inheritItemStretchAlongCrossSection(); + } } } } @@ -159,7 +160,17 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const minS = QSizeF(0,0); prefS = QSizeF(0,0); - maxS = QSizeF(std::numeric_limits::infinity(), std::numeric_limits::infinity()); + maxS = QSizeF(0,0); + + qreal flexColumnGap = 0; + qreal flexRowGap = 0; + bool wrap = false; + auto *qFlexLayout = qobject_cast(m_flexboxParentItem->quickItem()); + if (qFlexLayout) { + flexColumnGap = qFlexLayout->columnGap(); + flexRowGap = qFlexLayout->rowGap(); + wrap = qFlexLayout->wrap() != QQuickFlexboxLayout::NoWrap; + } const int count = itemCount(); for (int i = 0; i < count; ++i) { @@ -171,15 +182,15 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // meant the flex child item has a const width or height but // want to stretch vertically or horizontally if (flexLayoutItem->isItemStreched()) { - if (auto *parentLayoutItem = qobject_cast(m_flexboxParentItem->quickItem())) { + if (qFlexLayout) { // Reset the size of the child item if the parent sets // its property 'align-item' to strecth // Note: The child item can also override the parent // align-item property through align-self // (this is FlexboxLayout.alignItem for quick items) flexLayoutItem->resetSize(); - if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row || - parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) { + if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || + qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { flexLayoutItem->setWidth(hints.pref().width()); } else { flexLayoutItem->setHeight(hints.pref().height()); @@ -214,10 +225,27 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Parent pref, min, max width/height: // Sum of the pref, min and max width/height of // the child items - if (auto *qFlexLayout = qobject_cast(m_flexboxParentItem->quickItem())) { - if (qFlexLayout->wrap() == QQuickFlexboxLayout::NoWrap) { - if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || - qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { + if (qFlexLayout) { + const bool last = (i == count - 1); + if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || + qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { + if (wrap) { + // Minimum size + minS.setWidth(qMax(minS.width(), hints.min().width())); + minS.setHeight(qMax(minS.height(), hints.min().height())); + // Preferred size. Same as no-wrap preferred size + prefS.setWidth(prefS.width() + hints.pref().width()); + prefS.setHeight(qMax(prefS.height(), hints.pref().height())); + // Maximum size + maxS.setWidth(maxS.width() + hints.max().width()); + maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + prefS.rwidth() += flexColumnGap; + maxS.rwidth() += flexColumnGap; + maxS.rheight() += flexRowGap; + } + } else { // Minimum size minS.setWidth(minS.width() + hints.min().width()); minS.setHeight(qMax(minS.height(), hints.min().height())); @@ -227,8 +255,33 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Maximum size maxS.setWidth(maxS.width() + hints.max().width()); maxS.setHeight(qMax(maxS.height(), hints.max().height())); - } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column || - qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) { + + if (!last) { + minS.rwidth() += flexColumnGap; + prefS.rwidth() += flexColumnGap; + maxS.rwidth() += flexColumnGap; + } + } + + } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column || + qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) { + if (wrap) { + // Minimum size + minS.setWidth(qMax(minS.width(), hints.min().width())); + minS.setHeight(qMax(minS.height(), hints.min().height())); + // Preferred size. Same as no-wrap preferred size + prefS.setWidth(qMax(prefS.width(), hints.pref().width())); + prefS.setHeight(prefS.height() + hints.pref().height()); + // Maximum size + maxS.setWidth(maxS.width() + hints.max().width()); + maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + prefS.rheight() += flexRowGap; + maxS.rwidth() += flexColumnGap; + maxS.rheight() += flexRowGap; + } + } else { // Minimum size minS.setWidth(qMax(minS.width(), hints.min().width())); minS.setHeight(minS.height() + hints.min().height()); @@ -238,12 +291,13 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Maximum size maxS.setWidth(qMax(maxS.width(), hints.max().width())); maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + minS.rheight() += flexRowGap; + prefS.rheight() += flexRowGap; + maxS.rheight() += flexRowGap; + } } - } else if (qFlexLayout->wrap() == QQuickFlexboxLayout::Wrap || - qFlexLayout->wrap() == QQuickFlexboxLayout::WrapReverse) { - minS += hints.min(); - prefS += hints.pref(); - maxS += hints.max(); } } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklayoutitemproxy.cpp b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklayoutitemproxy.cpp index fd5167aa4c..a4dfe686b6 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklayoutitemproxy.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklayoutitemproxy.cpp @@ -64,6 +64,8 @@ QT_BEGIN_NAMESPACE \div {class="float-right"} \inlineimage simpleProxy.png + {Narrow window with stacked layout and wide window with + side-by-side layout} \enddiv The LayoutItemProxy can also be used without layouts, e.g. by anchoring it @@ -288,12 +290,14 @@ void QQuickLayoutItemProxy::setTarget(QQuickItem *newTarget) // proxy.implicitWidth: target.implicitWidth auto fnBindImplW = [newTarget, this](){ this->setImplicitWidth(newTarget->implicitWidth()); }; fnBindImplW(); - connect(newTarget, &QQuickItem::implicitWidthChanged, fnBindImplW); + connect(newTarget, &QQuickItem::implicitWidthChanged, + this, std::move(fnBindImplW)); // proxy.implicitHeight: target.implicitHeight auto fnBindImplH = [newTarget, this](){ this->setImplicitHeight(newTarget->implicitHeight()); }; fnBindImplH(); - connect(newTarget, &QQuickItem::implicitHeightChanged, fnBindImplH); + connect(newTarget, &QQuickItem::implicitHeightChanged, + this, std::move(fnBindImplH)); } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklinearlayout.cpp b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklinearlayout.cpp index 830ae11025..d8d7a0db10 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklinearlayout.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicklayouts/qquicklinearlayout.cpp @@ -29,7 +29,7 @@ \input layout.qdocinc attached-properties \endlist - \image rowlayout.png + \image rowlayout.png {Two rectangles side by side showing their dimensions} \code RowLayout { @@ -89,7 +89,7 @@ \input layout.qdocinc attached-properties \endlist - \image columnlayout.png + \image columnlayout.png {Three rectangles stacked vertically: red, green, blue} \code ColumnLayout{ @@ -154,7 +154,7 @@ columns the layout can have, before the auto-positioning wraps back to the beginning of the next row. The \l columns property is only used when \l flow is \c GridLayout.LeftToRight. - \image gridlayout.png + \image gridlayout.png {Text arranged in grid: Three, words, in, a, row} \code GridLayout { diff --git a/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/qstyle/windows/qquickwindowsxpstyle_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/qstyle/windows/qquickwindowsxpstyle_p_p.h index 3be49be330..81199d67a4 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/qstyle/windows/qquickwindowsxpstyle_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/qstyle/windows/qquickwindowsxpstyle_p_p.h @@ -19,6 +19,8 @@ //#include #include "qquickwindowsxpstyle_p.h" #include "qquickwindowsstyle_p_p.h" + +#include #include #include @@ -117,10 +119,11 @@ struct ThemeMapKey { : theme(data.theme), partId(data.partId), stateId(data.stateId), noBorder(data.noBorder), noContent(data.noContent) {} -}; +private: + friend size_t qHash(const ThemeMapKey &key, size_t seed = 0) noexcept + { return qHashMulti(seed, key.theme, key.partId, key.stateId); } -inline size_t qHash(const ThemeMapKey &key) -{ return key.theme ^ key.partId ^ key.stateId; } +}; inline bool operator==(const ThemeMapKey &k1, const ThemeMapKey &k2) { diff --git a/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/util/qquickstyleconstants.mm b/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/util/qquickstyleconstants.mm index fb2b00e8c6..874b27ec02 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/util/qquickstyleconstants.mm +++ b/local/recipes/qt/qtdeclarative/source/src/quicknativestyle/util/qquickstyleconstants.mm @@ -16,7 +16,7 @@ using namespace Qt::StringLiterals; QQuickStyleConstants::QQuickStyleConstants() { - connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, [=]{ + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] { emit secondarySystemFillColorChanged(); emit tertiarySystemFillColorChanged(); }); diff --git a/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickellipseshape.cpp b/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickellipseshape.cpp index 40f25608a0..ee322a61b5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickellipseshape.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickellipseshape.cpp @@ -577,7 +577,7 @@ void QQuickEllipseShapePrivate::updatePath() \snippet ellipseshape.qml ellipseShape - \image path-ellipseshape.png + \image path-ellipseshape.png {Ellipse with a pie slice cut out} */ QQuickEllipseShape::QQuickEllipseShape(QQuickItem *parent) : QQuickShape(*(new QQuickEllipseShapePrivate), parent) diff --git a/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickrectangleshape.cpp b/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickrectangleshape.cpp index ee8aa98bb9..067318f240 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickrectangleshape.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickshapes/designhelpers/qquickrectangleshape.cpp @@ -416,7 +416,7 @@ void QQuickRectangleShapePrivate::calculateIndependentRadii() \snippet rectangleshape-bevel.qml rectangleShape - \image pathrectangle-bevel.png + \image pathrectangle-bevel.png {Green rectangle with beveled corners} */ QQuickRectangleShape::QQuickRectangleShape(QQuickItem *parent) diff --git a/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshape.cpp b/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshape.cpp index 64404181cd..74a849d203 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshape.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshape.cpp @@ -152,7 +152,7 @@ QQuickShapeStrokeFillParams::QQuickShapeStrokeFillParams() Once associated with a Shape, here is the output with a joinStyleIndex of 2 (ShapePath.RoundJoin): - \image visualpath-code-example.png + \image visualpath-code-example.png {Black angled line with rounded corner} \sa {Qt Quick Examples - Shapes}, {Weather Forecast Example}, Shape */ @@ -172,7 +172,7 @@ QQuickShapePath::QQuickShapePath(QObject *parent) // to the difference in semantics and the need to act (see dirty flag // below) differently on QQuickPath-related changes. - connect(this, &QQuickPath::changed, [this]() { + connect(this, &QQuickPath::changed, this, [this]() { Q_D(QQuickShapePath); d->dirty |= QQuickShapePathPrivate::DirtyPath; emit shapePathChanged(); @@ -240,7 +240,7 @@ void QQuickShapePath::setStrokeWidth(qreal w) } /*! \since 6.11 - \qmlproperty real QtQuick.Shapes::ShapePath::cosmeticStroke + \qmlproperty bool QtQuick.Shapes::ShapePath::cosmeticStroke This property holds whether the stroke width remains constant despite rendering scale. @@ -826,6 +826,7 @@ void QQuickShapeTrim::setOffset(qreal t) \endqml \image pathitem-code-example.png + {Triangle with rainbow gradient fill and red dashed stroke} Like \l Item, Shape also allows any visual or non-visual objects to be declared as children. ShapePath objects are handled specially. This is @@ -1846,6 +1847,7 @@ void QQuickShapeLinearGradient::setY2(qreal v) \endcode \image shape-radial-gradient.png + {Ellipse with radial gradient from white center to cyan edge} Extended radial gradients, where a separate focal circle is specified, are also supported. diff --git a/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshapecurverenderer.cpp b/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshapecurverenderer.cpp index 7382e1512b..36b3a57b28 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshapecurverenderer.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickshapes/qquickshapecurverenderer.cpp @@ -638,6 +638,7 @@ void QQuickShapeCurveRenderer::processPath(PathData *pathData) pathData->strokeNodes = addTriangulatingStrokerNodes(strokePath, pen); else pathData->strokeNodes = addCurveStrokeNodes(strokePath, pen); + dirtyFlags |= UniformsDirty; } } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickaction.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickaction.cpp index f5f0c4d71a..8f94d97792 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickaction.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickaction.cpp @@ -471,10 +471,11 @@ void QQuickAction::setCheckable(bool checkable) /*! \qmlproperty keysequence QtQuick.Controls::Action::shortcut - This property holds the action's shortcut. The key sequence can be set - to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, - or it can be described with a string containing a sequence of up to four - key presses that are needed to trigger the shortcut. + This property holds the action's shortcut. The key sequence can be set to + one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, (for + example, \c StandardKey.Copy), or it can be described with a string + containing a sequence of up to four key presses that are needed to trigger + the shortcut. \code Action { diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickcombobox.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickcombobox.cpp index 2fb9fd7c21..a35e62c7de 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickcombobox.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickcombobox.cpp @@ -8,6 +8,7 @@ #include "qquickabstractbutton_p_p.h" #include "qquickpopup_p_p.h" #include "qquickdeferredexecute_p_p.h" +#include "qquickpopupwindow_p_p.h" #include #include @@ -1486,6 +1487,9 @@ void QQuickComboBox::setPopup(QQuickPopup *popup) QQuickComboBoxPrivate::hideOldPopup(d->popup); } if (popup) { +#if QT_CONFIG(wayland) + QQuickPopupPrivate::get(popup)->extendedWindowType = QNativeInterface::Private::QWaylandWindow::ComboBox; +#endif QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicklabel_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicklabel_p_p.h index a0a9052586..92eddadad8 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicklabel_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicklabel_p_p.h @@ -101,7 +101,6 @@ public: QLazilyAllocated extra; bool resizingBackground = false; - QPalette resolvedPalette; QQuickDeferredPointer background; }; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenu.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenu.cpp index 420c7ced82..6214074cab 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenu.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenu.cpp @@ -287,6 +287,9 @@ public: QQuickMenuPrivate::QQuickMenuPrivate() { cascade = shouldCascade(); +#if QT_CONFIG(wayland) + extendedWindowType = QNativeInterface::Private::QWaylandWindow::Menu; +#endif } void QQuickMenuPrivate::init() @@ -2200,7 +2203,7 @@ void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) if (oldItem) { QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); - QQuickItemPrivate::get(newItem)->removeItemChangeListener(d, QQuickItemPrivate::Destroyed); + QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Destroyed); QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); } if (newItem) { diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenuitem.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenuitem.cpp index 8ddb5d7cf3..2ed79dad63 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenuitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickmenuitem.cpp @@ -124,6 +124,9 @@ void QQuickMenuItemPrivate::setSubMenu(QQuickMenu *newSubMenu) QObjectPrivate::connect(newSubMenu, &QQuickPopup::enabledChanged, this, &QQuickMenuItemPrivate::updateEnabled); q->setText(newSubMenu->title()); q->setIcon(newSubMenu->icon()); +#if QT_CONFIG(wayland) + QQuickMenuPrivate::get(newSubMenu)->extendedWindowType = QNativeInterface::Private::QWaylandWindow::SubMenu; +#endif } subMenu = newSubMenu; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay.cpp index eee057bad3..f570545018 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include QT_BEGIN_NAMESPACE @@ -73,6 +74,78 @@ void QQuickOverlayPrivate::itemRotationChanged(QQuickItem *) updateGeometry(); } +// Wheel and tablet events are not subject to modal popup blocking in delivery agent, +// so modal popup blocking must be handled explicitly here. +// Returns true if event is consumed by a modal popup blocking its top-most target. +#if QT_CONFIG(tabletevent) || QT_CONFIG(wheelevent) +bool QQuickOverlayPrivate::eatEventIfBlockedByModal(QPointerEvent *event) +{ + Q_Q(QQuickOverlay); + const QList targetItems = deliveryAgentPrivate()->pointerTargets( + window->contentItem(), event, event->point(0), false, false); + if (targetItems.isEmpty()) + return false; + + QQuickItem *const topItem = targetItems.first(); + QQuickItem *const dimmerItem = q->property("_q_dimmerItem").value(); + // Find the QQuickPopupItem that contains topItem, if any. + QQuickItem *item = qobject_cast(topItem) ? topItem : nullptr; + + if (!item) { + item = topItem; + while ((item = item->parentItem())) { + if (qobject_cast(item)) + break; + } + } + + // topItem may be a QQuickShaderEffectSource created for a popup's drop shadow layer. + // It is a direct child of the overlay (not of the popup item), so the parent walk above + // misses it. Resolve it to the popup item it renders so the loop below handles it correctly. + if (!item && dimmerItem != topItem && q->isAncestorOf(topItem)) { + if (auto *shaderEffect = qobject_cast(topItem)) { + if (auto *pi = qobject_cast(shaderEffect->sourceItem())) + item = pi; + } + if (!item) + return false; + } + + // Iterate popups front-to-back. Block the event if topItem is not inside a popup + // that sits above the first modal popup in stacking order. + for (const auto &popup : stackingOrderPopups()) { + const QQuickItem *popupItem = popup->popupItem(); + if (!popupItem) + continue; + if (popupItem == item) { + if (topItem != item && popup->isModal() + && !item->contains(item->mapFromScene(event->point(0).scenePosition()))) { + // topItem is the popup's shader effect source (drop shadow), not the popup item + // itself — the event landed in the shadow area outside the popup's bounds. + // Eat it for modal popups to prevent it reaching items behind the shadow. + event->accept(); + return true; + } + // The event landed inside the popup. Tablet events must be filtered here + // because, unlike wheel and mouse events, no standard item + // accepts tablet events — so the delivery agent would otherwise continue + // past the popup to a TapHandler behind it. + // Intentionally not calling event->accept() so Qt synthesizes a mouse event, + // which the overlay's mouse press handler delivers to popup content normally. +#if QT_CONFIG(tabletevent) + if (QQuickDeliveryAgentPrivate::isTabletEvent(event)) + return true; +#endif + break; + } + // topItem is outside this popup — let the popup decide whether to block. + if (popup->overlayEvent(topItem, event)) + return true; + } + return false; +} +#endif + bool QQuickOverlayPrivate::startDrag(QEvent *event, const QPointF &pos) { Q_Q(QQuickOverlay); @@ -425,8 +498,7 @@ void QQuickOverlay::touchEvent(QTouchEvent *event) } #endif -#if QT_CONFIG(wheelevent) -void QQuickOverlay::wheelEvent(QWheelEvent *event) +void QQuickOverlay::handleWheelAndDnDEvents(QEvent *event) { Q_D(QQuickOverlay); if (d->mouseGrabberPopup) { @@ -441,6 +513,34 @@ void QQuickOverlay::wheelEvent(QWheelEvent *event) } event->ignore(); } + +#if QT_CONFIG(wheelevent) +void QQuickOverlay::wheelEvent(QWheelEvent *event) +{ + handleWheelAndDnDEvents(event); +} +#endif + +#if QT_CONFIG(quick_draganddrop) +void QQuickOverlay::dragEnterEvent(QDragEnterEvent *event) +{ + handleWheelAndDnDEvents(event); +} + +void QQuickOverlay::dragMoveEvent(QDragMoveEvent *event) +{ + handleWheelAndDnDEvents(event); +} + +void QQuickOverlay::dragLeaveEvent(QDragLeaveEvent *event) +{ + handleWheelAndDnDEvents(event); +} + +void QQuickOverlay::dropEvent(QDropEvent *event) +{ + handleWheelAndDnDEvents(event); +} #endif /*! @@ -531,6 +631,45 @@ bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) if (!isVisible() || object != d->window) return false; +#if QT_CONFIG(wheelevent) || QT_CONFIG(quick_draganddrop) + auto targetItemsForEvent = [&](QEvent *){ +#if QT_CONFIG(wheelevent) + if (event->type() == QEvent::Wheel) { + QWheelEvent *we = static_cast(event); + return d->deliveryAgentPrivate()->pointerTargets( + d->window->contentItem(), we, we->point(0), false, false); + } +#endif +#if QT_CONFIG(quick_draganddrop) + bool isDnDEvent = false; + switch (event->type()) { + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::Drop: + isDnDEvent = true; + break; + default: + break; + } + if (isDnDEvent) { + QDropEvent *de = static_cast(event); + auto position = mapFromScene(de->position()); + return d->deliveryAgentPrivate()->eventTargets( + d->window->contentItem(), de, -1, position, de->position(), + [](QQuickItem *item, const QEvent *) -> std::optional { + // Only consider actual drop targets; still recurse into + // non-accepting items so their children can be found. + if (!item->flags().testFlag(QQuickItem::ItemAcceptsDrops)) + return false; + return std::nullopt; // use default containment check + }); + } +#endif + // Not needed so far + Q_UNREACHABLE_RETURN(QList {}); + }; +#endif + switch (event->type()) { #if QT_CONFIG(quicktemplates2_multitouch) case QEvent::TouchBegin: @@ -612,47 +751,48 @@ bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) d->handleRelease(d->window->contentItem(), event, nullptr); break; } -#if QT_CONFIG(wheelevent) - case QEvent::Wheel: { - // If the top item in the drawing-order is blocked by a modal popup, then - // eat the event. There is no scenario where the top most item is blocked - // by a popup, but an item further down in the drawing order is not. - QWheelEvent *we = static_cast(event); - const QList targetItems = d->deliveryAgentPrivate()->pointerTargets( - d->window->contentItem(), we, we->point(0), false, false); +#if QT_CONFIG(quick_draganddrop) + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::Drop: { + // Find which item the drag is targeting and block if a modal popup is in front. + const QList targetItems = targetItemsForEvent(event); if (targetItems.isEmpty()) break; - - QQuickItem * const dimmerItem = property("_q_dimmerItem").value(); - QQuickItem * const topItem = targetItems.first(); - - QQuickItem *item = topItem; - while ((item = item->parentItem())) { - if (qobject_cast(item)) - break; + QQuickItem *const dimmerItem = property("_q_dimmerItem").value(); + QQuickItem *const topItem = targetItems.first(); + QQuickItem *item = qobject_cast(topItem) ? topItem : nullptr; + if (!item) { + item = topItem; + while ((item = item->parentItem())) { + if (qobject_cast(item)) + break; + } } - if (!item && dimmerItem != topItem && isAncestorOf(topItem)) break; - - const auto popups = d->stackingOrderPopups(); - // Eat the event if receiver topItem is not a child of a popup before - // the first modal popup. - for (const auto &popup : popups) { + for (const auto &popup : d->stackingOrderPopups()) { const QQuickItem *popupItem = popup->popupItem(); if (!popupItem) continue; - // if current popup item matches with any popup in stack, deliver the event if (popupItem == item) break; - // if the popup doesn't contain the item but is modal, eat the event - if (popup->overlayEvent(topItem, we)) + if (popup->overlayEvent(topItem, event)) return true; } break; } #endif - +#if QT_CONFIG(wheelevent) + case QEvent::Wheel: + return d->eatEventIfBlockedByModal(static_cast(event)); +#endif +#if QT_CONFIG(tabletevent) + case QEvent::TabletMove: + case QEvent::TabletPress: + case QEvent::TabletRelease: + return d->eatEventIfBlockedByModal(static_cast(event)); +#endif default: break; } diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p.h index 103a5f6f9b..3ebe047478 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p.h @@ -69,6 +69,13 @@ protected: #if QT_CONFIG(wheelevent) void wheelEvent(QWheelEvent *event) override; #endif +#if QT_CONFIG(quick_draganddrop) + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; +#endif + void handleWheelAndDnDEvents(QEvent *event); bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p_p.h index 1c0b8a1eba..cc75fca5b0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickoverlay_p_p.h @@ -37,7 +37,9 @@ public: { return overlay->d_func(); } - +#if QT_CONFIG(tabletevent) || QT_CONFIG(wheelevent) + bool eatEventIfBlockedByModal(QPointerEvent *event); +#endif bool startDrag(QEvent *event, const QPointF &pos); bool handlePress(QQuickItem *source, QEvent *event, QQuickPopup *target); bool handleMove(QQuickItem *source, QEvent *event, QQuickPopup *target); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup.cpp index 825ff2ae6d..30ab3db1a7 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup.cpp @@ -670,6 +670,13 @@ bool QQuickPopupPrivate::handleHoverEvent(QQuickItem *item, QHoverEvent *event) QMarginsF QQuickPopupPrivate::windowInsets() const { + // Don't honor negative insets on wayland. If we do, + // it would impact the popup window's geometry. + // We assume that the compositor will draw window decorations. +#if QT_CONFIG(wayland) + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) + return {0, 0, 0, 0}; +#endif Q_Q(const QQuickPopup); // If the popup has negative insets, it means that its background is pushed // outside the bounds of the popup. This is fine when the popup is an item in the @@ -691,8 +698,8 @@ QMarginsF QQuickPopupPrivate::windowInsets() const return { q->leftInset() < 0 ? -q->leftInset() : 0, - q->rightInset() < 0 ? -q->rightInset() : 0, q->topInset() < 0 ? -q->topInset() : 0, + q->rightInset() < 0 ? -q->rightInset() : 0, q->bottomInset() < 0 ? -q->bottomInset() : 0 }; } @@ -2308,7 +2315,8 @@ void QQuickPopup::setContentItem(QQuickItem *item) d->popupItem->setContentItem(item); if (d->complete) { QQuickItem *newContentItem = d->popupItem->d_func()->contentItem.data(); - connect(newContentItem, &QQuickItem::childrenChanged, this, &QQuickPopup::contentChildrenChanged); + if (newContentItem) + connect(newContentItem, &QQuickItem::childrenChanged, this, &QQuickPopup::contentChildrenChanged); if (oldContentItem != newContentItem) emit contentChildrenChanged(); } @@ -3247,7 +3255,7 @@ static QQuickItem *findRootOfOverlaySubtree(QQuickItem *source, const QQuickOver /*! \internal - Called whenever the window receives a Wheel/Hover/Mouse/Touch event, + Called whenever the window receives a Wheel/Hover/Mouse/Touch/Tablet event, and has an active popup (with popupType: Popup.Item) in its scene. The purpose is to close popups when the press/release event happened outside of it, @@ -3279,7 +3287,23 @@ bool QQuickPopup::overlayEvent(QQuickItem *item, QEvent *event) if (d->modal) event->accept(); return d->modal; - + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::DragLeave: + case QEvent::Drop: + // ignore() so that the OS sees Qt::IgnoreAction and renders the "not allowed" cursor + // within the drag image. accept() would only change the window cursor after the drag ends: too late. + if (d->modal) + event->ignore(); + return d->modal; +#if QT_CONFIG(tabletevent) + case QEvent::TabletPress: + case QEvent::TabletMove: + case QEvent::TabletRelease: + if (d->modal) + event->accept(); + return d->modal; +#endif #if QT_CONFIG(quicktemplates2_multitouch) case QEvent::TouchBegin: case QEvent::TouchUpdate: @@ -3321,6 +3345,28 @@ void QQuickPopup::wheelEvent(QWheelEvent *event) } #endif +#if QT_CONFIG(quick_draganddrop) +void QQuickPopup::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + +void QQuickPopup::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); +} + +void QQuickPopup::dropEvent(QDropEvent *event) +{ + event->accept(); +} + +void QQuickPopup::dragEnterEvent(QDragEnterEvent *event) +{ + event->ignore(); // OS shows "forbidden" cursor as part of drag image +} +#endif + void QQuickPopup::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) { Q_UNUSED(newItem); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p.h index 8579f51de7..c0f53e3784 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p.h @@ -421,6 +421,12 @@ protected: #if QT_CONFIG(wheelevent) virtual void wheelEvent(QWheelEvent *event); #endif +#if QT_CONFIG(quick_draganddrop) + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dragMoveEvent(QDragMoveEvent *event); + virtual void dragLeaveEvent(QDragLeaveEvent *event); + virtual void dropEvent(QDropEvent *event); +#endif virtual void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem); virtual void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p_p.h index b0e3bd084b..a8997e33a3 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopup_p_p.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -207,6 +208,10 @@ public: QQuickPopup::PopupType popupType = QQuickPopup::Item; Qt::WindowModality popupWndModality = Qt::NonModal; Qt::WindowFlags windowFlags = Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint; +#if QT_CONFIG(wayland) + // The extended window type is used by the wayland compositor to support things like flipping of menus and combobox popups + QNativeInterface::Private::QWaylandWindow::WindowType extendedWindowType = QNativeInterface::Private::QWaylandWindow::Default; +#endif friend class QQuickPopupTransitionManager; }; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem.cpp index 4ec2218dd8..e22ba1eb36 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem.cpp @@ -235,6 +235,31 @@ void QQuickPopupItem::wheelEvent(QWheelEvent *event) } #endif +#if QT_CONFIG(quick_draganddrop) +void QQuickPopupItem::dragEnterEvent(QDragEnterEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->dragEnterEvent(event); +} + +void QQuickPopupItem::dragMoveEvent(QDragMoveEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->dragMoveEvent(event); +} +void QQuickPopupItem::dragLeaveEvent(QDragLeaveEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->dragLeaveEvent(event); +} +void QQuickPopupItem::dropEvent(QDropEvent *event) +{ + Q_D(QQuickPopupItem); + d->popup->dropEvent(event); +} + +#endif + void QQuickPopupItem::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) { Q_D(QQuickPopupItem); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem_p_p.h index dc3dc18447..e236fbceaa 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupitem_p_p.h @@ -51,6 +51,12 @@ protected: #if QT_CONFIG(wheelevent) void wheelEvent(QWheelEvent *event) override; #endif +#if QT_CONFIG(quick_draganddrop) + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; +#endif void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; void contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) override; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopuppositioner.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopuppositioner.cpp index df464efb06..74dc4728c5 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopuppositioner.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopuppositioner.cpp @@ -288,7 +288,6 @@ void QQuickPopupPositioner::reposition() void QQuickPopupPositioner::repositionPopupWindow() { auto *p = QQuickPopupPrivate::get(popup()); - QQuickPopupItem *popupItem = static_cast(m_popup->popupItem()); QPointF requestedPos(p->x, p->y); // Shift the window position a bit back, so that the top-left of the @@ -303,6 +302,12 @@ void QQuickPopupPositioner::repositionPopupWindow() return; } + // Wayland does server side repositioning. + // All other platforms handle the repositioning on the client side. + if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) + return; + + QQuickPopupItem *popupItem = static_cast(m_popup->popupItem()); const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr; const QQuickOverlay *centerInOverlay = qobject_cast(centerInParent); bool skipFittingStep = false; @@ -322,9 +327,7 @@ void QQuickPopupPositioner::repositionPopupWindow() : p->parentItem->mapToGlobal(windowPos.x(), windowPos.y()); QRectF rect = { globalCoords.x(), globalCoords.y(), popupItem->width(), popupItem->height() }; - // QTBUG-99618: On wayland, we can't use QWindow::mapToGlobal(), and should use a xdg_positioner instead. - static bool isWayland = QGuiApplication::platformName().startsWith(QLatin1String("wayland")); - if (!skipFittingStep && !isWayland) { + if (!skipFittingStep) { const QScreen *screenAtPopupPosition = QGuiApplication::screenAt(globalCoords.toPoint()); const QScreen *screen = screenAtPopupPosition ? screenAtPopupPosition : QGuiApplication::primaryScreen(); const QRectF bounds = screen->availableGeometry().toRectF(); diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow.cpp index 5b48895c08..bfe802b5c0 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow.cpp @@ -158,6 +158,12 @@ void QQuickPopupWindowPrivate::setVisible(bool visible) s_grabbedWindow = nullptr; } +#if QT_CONFIG(wayland) + // The parent control geoemtry is used by the wayland compositor when flipping menus and comboboxes. + if (auto waylandWindow = dynamic_cast(platformWindow); waylandWindow && visible) + waylandWindow->setParentControlGeometry(q->parentControlGeometry()); +#endif + QQuickWindowQmlImplPrivate::setVisible(visible); // Similar logic to grabForPopup(QWidget *popup) @@ -325,6 +331,15 @@ bool QQuickPopupWindowPrivate::filterPopupSpecialCases(QEvent *event) bool QQuickPopupWindow::event(QEvent *e) { Q_D(QQuickPopupWindow); +#if QT_CONFIG(wayland) + if (e->type() == QEvent::PlatformSurface && static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { + if (auto *waylandWindow = dynamic_cast(handle())) { + waylandWindow->setExtendedWindowType(QQuickPopupPrivate::get(d->m_popup)->extendedWindowType); + waylandWindow->setParentControlGeometry(parentControlGeometry()); + } + } +#endif + if (d->filterPopupSpecialCases(e)) return true; @@ -409,17 +424,46 @@ void QQuickPopupWindow::handlePopupPositionChangeFromWindowSystem(const QPoint & void QQuickPopupWindow::implicitWidthChanged() { - Q_D(const QQuickPopupWindow); - if (auto popup = d->m_popup) - setWidth(popup->implicitWidth()); + Q_D(QQuickPopupWindow); + if (auto popup = d->m_popup) { + auto *popupPrivate = QQuickPopupPrivate::get(popup); + // Include window insets so that it works for styles like FluentWinUI3 + // that have insets. + // Use qCeil to avoid sub-pixel truncation that causes text wrapping + // (see QTBUG-130683). This matches the initial sizing logic in + // QQuickPopupPrivate::adjustPopupItemParentAndWindow(). + const QMarginsF insets = popupPrivate->windowInsets(); + setWidth(qCeil(popup->implicitWidth() + insets.left() + insets.right())); + d->m_popupItem->setWidth(popup->implicitWidth()); + popupPrivate->reposition(); + } } void QQuickPopupWindow::implicitHeightChanged() { - Q_D(const QQuickPopupWindow); - if (auto popup = d->m_popup) - setHeight(popup->implicitHeight()); + Q_D(QQuickPopupWindow); + if (auto popup = d->m_popup) { + auto *popupPrivate = QQuickPopupPrivate::get(popup); + const QMarginsF insets = popupPrivate->windowInsets(); + setHeight(qCeil(popup->implicitHeight() + insets.top() + insets.bottom())); + d->m_popupItem->setHeight(popup->implicitHeight()); + popupPrivate->reposition(); + } } +#if QT_CONFIG(wayland) +QRect QQuickPopupWindow::parentControlGeometry() const +{ + const QQuickItem *parent = popup()->parentItem(); + // Menus have an overlap property that we want to honor, + // as long as it's not wider than half the width of the parent item. + const qreal overlap = popup()->property("overlap").toReal(); + QRectF parentItemBoundingRect = parent->boundingRect(); + const QPointF parentItemMappedPosition = parent->mapToScene(parentItemBoundingRect.topLeft()); + return { qFloor(parentItemMappedPosition.x() + overlap), qFloor(parentItemMappedPosition.y()), + qCeil(qMax(qAbs(parentItemBoundingRect.width() - overlap * 2), parentItemBoundingRect.width() / 4)), qCeil(parentItemBoundingRect.height()) }; +} +#endif + QT_END_NAMESPACE diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow_p_p.h index 5c7908a159..6a4e3cc50a 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickpopupwindow_p_p.h @@ -47,7 +47,9 @@ private: void handlePopupPositionChangeFromWindowSystem(const QPoint &pos); void implicitWidthChanged(); void implicitHeightChanged(); - +#if QT_CONFIG(wayland) + QRect parentControlGeometry() const; +#endif Q_DISABLE_COPY(QQuickPopupWindow) Q_DECLARE_PRIVATE(QQuickPopupWindow) }; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickscrollview.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickscrollview.cpp index 29055633c7..f576eceeed 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickscrollview.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickscrollview.cpp @@ -138,6 +138,7 @@ public: static void contentChildren_clear(QQmlListProperty *prop); void itemImplicitWidthChanged(QQuickItem *item) override; + void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; void updateScrollBarWidth(); void updateScrollBarHeight(); @@ -240,10 +241,12 @@ void QQuickScrollViewPrivate::disconnectScrollBarSignals(QQuickScrollBarAttached if (scrollBar->vertical) { QObjectPrivate::disconnect(scrollBar->vertical, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); QObjectPrivate::disconnect(scrollBar->vertical, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + QQuickItemPrivate::get(scrollBar->vertical)->removeItemChangeListener(this, QQuickItemPrivate::Geometry); } if (scrollBar->horizontal) { QObjectPrivate::disconnect(scrollBar->horizontal, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); QObjectPrivate::disconnect(scrollBar->horizontal, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + QQuickItemPrivate::get(scrollBar->horizontal)->removeItemChangeListener(this, QQuickItemPrivate::Geometry); } } @@ -290,10 +293,12 @@ bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, ContentItemFla if (scrollBar->vertical) { QObjectPrivate::connect(scrollBar->vertical, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); QObjectPrivate::connect(scrollBar->vertical, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + QQuickItemPrivate::get(scrollBar->vertical)->addItemChangeListener(this, QQuickItemPrivate::Geometry); } if (scrollBar->horizontal) { QObjectPrivate::connect(scrollBar->horizontal, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); QObjectPrivate::connect(scrollBar->horizontal, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + QQuickItemPrivate::get(scrollBar->horizontal)->addItemChangeListener(this, QQuickItemPrivate::Geometry); } } @@ -491,6 +496,15 @@ void QQuickScrollViewPrivate::itemImplicitWidthChanged(QQuickItem *item) QQuickPanePrivate::itemImplicitWidthChanged(item); } +void QQuickScrollViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) +{ + QQuickPanePrivate::itemGeometryChanged(item, change, diff); + if (change.widthChange() && item == verticalScrollBar()) + updateScrollBarWidth(); + else if (change.heightChange() && item == horizontalScrollBar()) + updateScrollBarHeight(); +} + QQuickScrollView::QQuickScrollView(QQuickItem *parent) : QQuickPane(*(new QQuickScrollViewPrivate), parent) { diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicksearchfield.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicksearchfield.cpp index 17ab854fdb..126ff89211 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicksearchfield.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicksearchfield.cpp @@ -410,14 +410,19 @@ void QQuickSearchFieldPrivate::createdItem(int index, QObject *object) void QQuickSearchFieldPrivate::suggestionCountChanged() { Q_Q(QQuickSearchField); - if (q->suggestionCount() == 0) - setCurrentItemAtIndex(-1, NoActivate); + + if (q->suggestionCount() == 0) { + setCurrentIndex(-1); + setHighlightedIndex(-1, NoHighlight); + emit q->suggestionCountChanged(); + return; + } + // If the suggestionModel has been updated and the current text matches an item in // the model, update currentIndex and highlightedIndex to the index of that item. if (!text.isEmpty()) { for (int idx = 0; idx < q->suggestionCount(); ++idx) { - QString t = textAt(idx); - if (t == text) { + if (textAt(idx) == text) { setCurrentItemAtIndex(idx, NoActivate); updateHighlightedIndex(); break; @@ -588,8 +593,10 @@ void QQuickSearchFieldPrivate::updateText() void QQuickSearchFieldPrivate::updateDisplayText() { Q_Q(QQuickSearchField); - const QString currentText = textAt(currentIndex); + if (currentIndex < 0) + return; + const QString currentText = textAt(currentIndex); if (text != currentText) q->setText(currentText); } @@ -1021,6 +1028,9 @@ void QQuickSearchField::setPopup(QQuickPopup *popup) } if (popup) { +#if QT_CONFIG(wayland) + QQuickPopupPrivate::get(popup)->extendedWindowType = QNativeInterface::Private::QWaylandWindow::ComboBox; +#endif QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, @@ -1090,9 +1100,10 @@ bool QQuickSearchField::eventFilter(QObject *object, QEvent *event) switch (event->type()) { case QEvent::MouseButtonRelease: { QQuickTextInput *input = qobject_cast(d->contentItem); - if (input->hasFocus()) { - if (!d->text.isEmpty() && !d->isPopupVisible() && (d->delegateModel && d->delegateModel->count() > 0)) - d->showPopup(); + if (input->hasFocus() && !d->text.isEmpty() && !d->isPopupVisible() + && (d->delegateModel && d->delegateModel->count() > 0) + && static_cast(event)->button() == Qt::LeftButton) { + d->showPopup(); } break; } @@ -1107,6 +1118,7 @@ bool QQuickSearchField::eventFilter(QObject *object, QEvent *event) default: break; } + return QQuickControl::eventFilter(object, event); } @@ -1115,10 +1127,21 @@ void QQuickSearchField::focusInEvent(QFocusEvent *event) Q_D(QQuickSearchField); QQuickControl::focusInEvent(event); - if ((event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason - || event->reason() == Qt::ShortcutFocusReason) - && d->contentItem) - d->contentItem->forceActiveFocus(event->reason()); + if (!d->contentItem) + return; + + const auto reason = event->reason(); + switch (reason) { + case Qt::TabFocusReason: + case Qt::BacktabFocusReason: + case Qt::ShortcutFocusReason: + case Qt::OtherFocusReason: + if (reason != Qt::OtherFocusReason || !d->isPopupVisible()) + d->contentItem->forceActiveFocus(reason); + break; + default: + break; + } } void QQuickSearchField::focusOutEvent(QFocusEvent *event) @@ -1169,16 +1192,18 @@ void QQuickSearchField::keyPressEvent(QKeyEvent *event) Q_D(QQuickSearchField); const auto key = event->key(); + const bool hasModel = !d->suggestionModel.isNull(); + const bool hasText = !d->text.isEmpty(); - if (!d->suggestionModel.isNull() && !d->text.isEmpty()) { - switch (key) { + switch (key) { case Qt::Key_Escape: case Qt::Key_Back: if (d->isPopupVisible()) { d->hidePopup(false); event->accept(); - } else { + } else if (hasText) { setText(QString()); + event->accept(); } break; case Qt::Key_Return: @@ -1190,27 +1215,32 @@ void QQuickSearchField::keyPressEvent(QKeyEvent *event) event->accept(); break; case Qt::Key_Up: - d->decreaseCurrentIndex(); - event->accept(); + if (hasModel && hasText) { + d->decreaseCurrentIndex(); + event->accept(); + } break; case Qt::Key_Down: - d->increaseCurrentIndex(); - event->accept(); + if (hasModel && hasText) { + d->increaseCurrentIndex(); + event->accept(); + } break; case Qt::Key_Home: - if (d->isPopupVisible()) + if (d->isPopupVisible()) { d->setHighlightedIndex(0, Highlight); - event->accept(); + event->accept(); + } break; case Qt::Key_End: - if (d->isPopupVisible()) + if (d->isPopupVisible()) { d->setHighlightedIndex(suggestionCount() - 1, Highlight); - event->accept(); + event->accept(); + } break; default: QQuickControl::keyPressEvent(event); break; - } } } diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickspinbox.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickspinbox.cpp index 8883ac7814..e22e6c9c06 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickspinbox.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickspinbox.cpp @@ -37,6 +37,8 @@ QT_BEGIN_NAMESPACE \snippet qtquickcontrols-spinbox.qml 1 + For floating point values, use \l DoubleSpinBox. + \section2 Custom Values \image qtquickcontrols-spinbox-textual.png @@ -53,7 +55,8 @@ QT_BEGIN_NAMESPACE \snippet qtquickcontrols-spinbox-prefix.qml 1 - \sa Tumbler, {Customizing SpinBox}, {Focus Management in Qt Quick Controls} + \sa Tumbler, {Customizing SpinBox}, {Focus Management in Qt Quick Controls}, + {DoubleSpinBox} */ /*! diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickstackelement.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickstackelement.cpp index c7eb62fec7..ccbb4f9782 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickstackelement.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquickstackelement.cpp @@ -161,6 +161,9 @@ QQuickStackElement *QQuickStackElement::fromStackViewArg( bool QQuickStackElement::load(QV4::ExecutionEngine *v4, QQuickStackView *parent) { setView(parent); + if (init) + return item; + if (!item) { ownItem = true; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktextarea_p_p.h b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktextarea_p_p.h index 48fc1c59b9..64cd7b11ce 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktextarea_p_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktextarea_p_p.h @@ -117,12 +117,10 @@ public: qreal rightInset = 0; qreal bottomInset = 0; QFont requestedFont; - QPalette requestedPalette; }; QLazilyAllocated extra; bool resizingBackground = false; - QPalette resolvedPalette; QQuickDeferredPointer background; QString placeholder; QColor placeholderColor; diff --git a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktooltip.cpp b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktooltip.cpp index e6a1328b41..112115c77e 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktooltip.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quicktemplates/qquicktooltip.cpp @@ -118,6 +118,9 @@ public: QQuickToolTipPrivate::QQuickToolTipPrivate() { windowFlags = Qt::ToolTip; +#if QT_CONFIG(wayland) + extendedWindowType = QNativeInterface::Private::QWaylandWindow::ToolTip; +#endif } void QQuickToolTipPrivate::startDelay() diff --git a/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/generator/qquickqmlgenerator.cpp index ad46727ed8..acbc254e30 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/generator/qquickqmlgenerator.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/generator/qquickqmlgenerator.cpp @@ -19,6 +19,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static QString sanitizeString(const QString &input) { QString s = input; @@ -849,6 +851,8 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte stream() << "capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle); stream() << "joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle); stream() << "miterLimit: " << info.strokeStyle.miterLimit; + if (info.strokeStyle.cosmetic) + stream() << "cosmeticStroke: true"; if (info.strokeStyle.dashArray.length() != 0) { stream() << "strokeStyle: " << "ShapePath.DashLine"; stream() << "dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray); @@ -1676,13 +1680,13 @@ qsizetype QQuickQmlGenerator::generateFilterStep(const FilterNodeInfo &info, const int maxNodeCount = 8; // Find all nodes for this merge - QList > inputs; + QList > inputs; for (; stepIndex < info.steps.size(); ++stepIndex) { const FilterNodeInfo::FilterStep &nodeStep = info.steps.at(stepIndex); if (nodeStep.filterType != FilterNodeInfo::Type::MergeNode) break; - inputs.append(qMakePair(nodeStep.input1, nodeStep.namedInput1)); + inputs.emplace_back(nodeStep.input1, nodeStep.namedInput1); } if (inputs.size() > maxNodeCount) { @@ -1709,7 +1713,7 @@ qsizetype QQuickQmlGenerator::generateFilterStep(const FilterNodeInfo &info, for (int i = 0; i < maxNodeCount; ++i) { auto input = i < inputs.size() ? inputs.at(i) - : qMakePair(FilterNodeInfo::FilterInput::None, QStringLiteral("null")); + : std::pair(FilterNodeInfo::FilterInput::None, u"null"_s); QString inputId = input.first != FilterNodeInfo::FilterInput::SourceColor ? input.second diff --git a/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h b/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h index 11f2b53c4d..265f507b08 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h +++ b/local/recipes/qt/qtdeclarative/source/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h @@ -25,8 +25,8 @@ class Q_QUICKVECTORIMAGEHELPERS_EXPORT QQuickColorOpacityAnimation : public QQui Q_OBJECT Q_DECLARE_PRIVATE(QQuickPropertyAnimation) - Q_PROPERTY(qreal from READ from WRITE setFrom) - Q_PROPERTY(qreal to READ to WRITE setTo) + Q_PROPERTY(qreal from READ from WRITE setFrom OVERRIDE) + Q_PROPERTY(qreal to READ to WRITE setTo OVERRIDE) QML_NAMED_ELEMENT(ColorOpacityAnimation) public: diff --git a/local/recipes/qt/qtdeclarative/source/src/quickwidgets/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/src/quickwidgets/CMakeLists.txt index f3575c84fc..a1aff7e247 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickwidgets/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/src/quickwidgets/CMakeLists.txt @@ -12,7 +12,6 @@ qt_internal_add_module(QuickWidgets qquickwidget.cpp qquickwidget.h qquickwidget_p.h qtquickwidgetsglobal.h DEFINES - QT_NO_FOREACH QT_NO_INTEGER_EVENT_COORDINATES QT_NO_URL_CAST_FROM_STRING LIBRARIES diff --git a/local/recipes/qt/qtdeclarative/source/src/quickwidgets/qquickwidget.cpp b/local/recipes/qt/qtdeclarative/source/src/quickwidgets/qquickwidget.cpp index 3968fdf381..a4fa9a54d2 100644 --- a/local/recipes/qt/qtdeclarative/source/src/quickwidgets/qquickwidget.cpp +++ b/local/recipes/qt/qtdeclarative/source/src/quickwidgets/qquickwidget.cpp @@ -1344,9 +1344,7 @@ void QQuickWidget::continueExecute() if (d->source.isEmpty()) d->source = d->component->url(); - d->setRootObject(obj.get()); - if (d->root) - Q_UNUSED(obj.release()); + d->setRootObject(obj.release()); emit statusChanged(status()); } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/data/disabledparent.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/data/disabledparent.qml new file mode 100644 index 0000000000..e067d1a43e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/data/disabledparent.qml @@ -0,0 +1,35 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + +Rectangle { + color: "black" + width: 320 + height: 320 + + // Parent item with enabled: false (disables user interaction, + // but should NOT prevent particle animation) + Item { + enabled: false + anchors.fill: parent + + ParticleSystem { + id: sys + objectName: "system" + anchors.fill: parent + + ItemParticle { + delegate: Image { source: "../../shared/star.png" } + } + + Emitter { + //0,0 position + size: 32 + emitRate: 1000 + lifeSpan: 500 + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp index 079ccd4323..b8e27ee092 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp @@ -23,6 +23,7 @@ private slots: void test_takeGive(); void test_noCrashOnReset(); void test_noLeakWhenDeleted(); + void test_disabledParent(); }; void tst_qquickitemparticle::initTestCase() @@ -135,6 +136,27 @@ void tst_qquickitemparticle::test_noLeakWhenDeleted() delete view; } +void tst_qquickitemparticle::test_disabledParent() +{ + // QTBUG-136580: ItemParticle should work even when contained in + // a parent item with enabled: false. The enabled property controls + // user interaction, not rendering or particle animation. + QQuickView* view = createView(testFileUrl("disabledparent.qml"), 600); + QQuickParticleSystem* system = view->rootObject()->findChild("system"); + ensureAnimTime(600, system->m_animation); + + QVERIFY(extremelyFuzzyCompare(system->groupData[0]->size(), 500, 10)); + int delegateCount = 0; + for (QQuickParticleData *d : std::as_const(system->groupData[0]->data)) { + if (d->t == -1) + continue; + if (d->delegate) + delegateCount++; + } + QVERIFY2(delegateCount > 0, "ItemParticle delegates should be created even with disabled parent"); + delete view; +} + QTEST_MAIN(tst_qquickitemparticle); #include "tst_qquickitemparticle.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 11bc62e1f9..f3608eddb2 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -5690,9 +5690,12 @@ JSON.stringify([], v3); void tst_QJSEngine::typedArraySet() { QJSEngine engine; + // The length need only be large enough that offset + length overflows + // a uint32; keep it small so the two Int8Array allocations fit on + // memory-constrained targets such as the QNX QEMU CI. const auto value = engine.evaluate( "(function() {" - " var length = 0xfffffe0;" + " var length = 0x1000;" " var offset = 0xfffffff0;" " var e1;" " var e2;" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 02f9da5427..da5ffdfb58 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -8,7 +8,7 @@ set(cpp_sources ambiguous.h birthdayparty.cpp birthdayparty.h collector.h - convertQJSPrimitiveValueToIntegral.h + convertQJSPrimitiveValueToNumeric.h cppbaseclass.h cppobj.h detachedreferences.h @@ -147,8 +147,9 @@ set(qml_files conversions.qml conversions2.qml convertPrimitiveToVar.qml - convertQJSPrimitiveValueToIntegral.qml + convertQJSPrimitiveValueToNumeric.qml convertToOriginalReadAcumulatorForUnaryOperators.qml + ctorArgumentConversion.qml curlygrouped.qml cycleHead.qml dateConstruction.qml @@ -238,6 +239,7 @@ set(qml_files listAsArgument.qml listConversion.qml listIndices.qml + listOfInlineComponent.qml listOfInvisible.qml listPropertyAsModel.qml listToString.qml diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h deleted file mode 100644 index 459dd62374..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -#ifndef CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H -#define CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H - -#include -#include - -class Moo485 : public QObject -{ - Q_OBJECT - QML_ELEMENT - - Q_PROPERTY(int uid READ uid CONSTANT FINAL) - -public: - explicit Moo485(QObject *parent = nullptr) : QObject(parent) { } - int uid() const { return 4711; } -}; - -class Foo485 : public QObject -{ - Q_OBJECT - QML_ELEMENT - - Q_PROPERTY(quint16 uid MEMBER m_uid FINAL) - -public: - explicit Foo485(QObject *parent = nullptr) : QObject(parent) { } - quint16 m_uid = 0; -}; - -#endif // CONVERTQJSPRIMITIVEVALUETOINTEGRAL_H diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml deleted file mode 100644 index 6a15d6f775..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToIntegral.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick -import TestTypes - - -Item { - id: root - - property Moo485 moo - - readonly property Foo485 foo: Foo485 { - uid: root.moo.uid ?? 0xFFFF - } -} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.h new file mode 100644 index 0000000000..a197484750 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.h @@ -0,0 +1,37 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef CONVERTQJSPRIMITIVEVALUETONUMERIC_H +#define CONVERTQJSPRIMITIVEVALUETONUMERIC_H + +#include +#include + +class Moo485 : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(quint8 u8 MEMBER u8 FINAL) + Q_PROPERTY(qint8 i8 MEMBER i8 FINAL) + Q_PROPERTY(quint16 u16 MEMBER u16 FINAL) + Q_PROPERTY(qint16 i16 MEMBER i16 FINAL) + Q_PROPERTY(quint32 u32 MEMBER u32 FINAL) + Q_PROPERTY(qint32 i32 MEMBER i32 FINAL) + Q_PROPERTY(float f MEMBER f FINAL) + Q_PROPERTY(qreal r MEMBER r FINAL) + Q_PROPERTY(double d MEMBER d FINAL) + +public: + quint8 u8; + qint8 i8; + quint16 u16; + qint16 i16; + quint32 u32; + qint32 i32; + float f; + qreal r; + double d; +}; + +#endif // CONVERTQJSPRIMITIVEVALUETONUMERIC_H diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.qml new file mode 100644 index 0000000000..d1a90ebc7e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/convertQJSPrimitiveValueToNumeric.qml @@ -0,0 +1,18 @@ +pragma Strict + +import QtQuick +import TestTypes + +Moo485 { + // 1 + undefined -> QJSPrimitiveValue + u8: 1 + undefined + i8: 1 + undefined + u16: 1 + undefined + i16: 1 + undefined + u32: 1 + undefined + i32: 1 + undefined + // 64 bit integers are not considered primitive + f: 1 + undefined + r: 1 + undefined + d: 1 + undefined +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/ctorArgumentConversion.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/ctorArgumentConversion.qml new file mode 100644 index 0000000000..14ecc528f8 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/ctorArgumentConversion.qml @@ -0,0 +1,8 @@ +pragma Strict +import QtQml + +QtObject { + // double can be JS-coerced to int via QJSPrimitiveValue + // We should not mistakenly use the copy ctor here. + property foreignWithLength w: { return 4.2 } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/listOfInlineComponent.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/listOfInlineComponent.qml new file mode 100644 index 0000000000..1f70ee9e0b --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/listOfInlineComponent.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + id: root + + // NOTE: No crash if: + // - We use list instead of list, OR + // - We set NO_CACHEGEN in qt_add_qml_module(), OR + // - We use Windows instead of macOS, OR + // - We swap the two lines below + property list qmlTypeList + component QMLType: QtObject {} + + property var model: root.qmlTypeList +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/withlength.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/withlength.h index 6d51533622..1ab85cfb25 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/withlength.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/data/withlength.h @@ -59,4 +59,36 @@ private: InnerWithLength v; }; +struct LocalWithLength +{ + Q_GADGET + Q_PROPERTY(int length READ length CONSTANT) +public: + + LocalWithLength() = default; + LocalWithLength(int length) : m_length(length) {} + + int length() const { return m_length; } + +private: + int m_length = 20; + +}; + +struct ForeignWithLength: private LocalWithLength +{ + Q_GADGET + QML_VALUE_TYPE(foreignWithLength) + QML_FOREIGN(LocalWithLength) + QML_EXTENDED(ForeignWithLength) + QML_CONSTRUCTIBLE_VALUE + +public: + Q_INVOKABLE ForeignWithLength() = default; + Q_INVOKABLE ForeignWithLength(int length) : LocalWithLength(length) {} + Q_INVOKABLE ForeignWithLength(const LocalWithLength &withLength) + : LocalWithLength(withLength) + {} +}; + #endif // WITHLENGTH_H diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 927d5db9d2..a9ad8f8dfa 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -95,10 +95,11 @@ private slots: void conversionInDeadCode(); void conversions(); void convertPrimitiveToVar(); - void convertQJSPrimitiveValueToIntegral(); + void convertQJSPrimitiveValueToNumeric(); void convertToOriginalReadAcumulatorForUnaryOperators(); void cppMethodListReturnType(); void cppValueTypeList(); + void ctorArgumentConversion(); void dateConstruction(); void dateConversions(); void deadContext_data(); @@ -195,6 +196,7 @@ private slots: void listIndices(); void listLength(); void listOfInvisible(); + void listOfInlineComponent(); void listPropertyAsModel(); void listToString(); void lotsOfRegisters(); @@ -1620,10 +1622,10 @@ void tst_QmlCppCodegen::convertPrimitiveToVar() QCOMPARE(o->property("offsetValue").toInt(), 41); } -void tst_QmlCppCodegen::convertQJSPrimitiveValueToIntegral() +void tst_QmlCppCodegen::convertQJSPrimitiveValueToNumeric() { QQmlEngine engine; - QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/convertQJSPrimitiveValueToIntegral.qml"_s)); + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/convertQJSPrimitiveValueToNumeric.qml"_s)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); @@ -1664,6 +1666,16 @@ void tst_QmlCppCodegen::cppValueTypeList() QCOMPARE(object->property("b").toDouble(), 13.5); } +void tst_QmlCppCodegen::ctorArgumentConversion() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/ctorArgumentConversion.qml"_s)); + std::unique_ptr object(component.create()); + QVERIFY2(object, component.errorString().toUtf8().constData()); + LocalWithLength w = object->property("w").value(); + QCOMPARE(w.length(), 4); +} + void tst_QmlCppCodegen::dateConstruction() { QQmlEngine engine; @@ -3835,6 +3847,19 @@ void tst_QmlCppCodegen::listOfInvisible() QCOMPARE(object->property("width").toDouble(), 27.0); } +void tst_QmlCppCodegen::listOfInlineComponent() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listOfInlineComponent.qml"_s)); + QVERIFY2(component.isReady(), component.errorString().toUtf8()); + QScopedPointer object(component.create()); + QVERIFY(!object.isNull()); + const QVariant model = object->property("model"); + QCOMPARE(model.metaType(), QMetaType::fromType()); + QQmlListReference ref = model.value(); + QCOMPARE(ref.count(), 0); +} + void tst_QmlCppCodegen::listPropertyAsModel() { QQmlEngine engine; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index e05c41fdbf..518f212f77 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -18,6 +18,66 @@ #include #include +// C++ type that provides attached properties. Used by +// compositeAttachedPropertyChecksum to reproduce the real-world +// scenario where a composite QML type extending this C++ type is +// referenced as an attached-property type. +class TestAttachedProperties : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) +public: + explicit TestAttachedProperties(QObject *parent = nullptr) : QObject(parent) {} + int value() const { return m_value; } + void setValue(int v) + { + if (m_value != v) { + m_value = v; + emit valueChanged(); + } + } +signals: + void valueChanged(); +private: + int m_value = 0; +}; + +class TestAttachable : public QObject +{ + Q_OBJECT + QML_ATTACHED(TestAttachedProperties) +public: + explicit TestAttachable(QObject *parent = nullptr) : QObject(parent) {} + static TestAttachedProperties *qmlAttachedProperties(QObject *obj) + { + return new TestAttachedProperties(obj); + } +}; + +class TestAttachableB : public QObject +{ + Q_OBJECT + QML_ATTACHED(TestAttachedProperties) +public: + explicit TestAttachableB(QObject *parent = nullptr) : QObject(parent) {} + static TestAttachedProperties *qmlAttachedProperties(QObject *obj) + { + return new TestAttachedProperties(obj); + } +}; + +class TestAttachableC : public QObject +{ + Q_OBJECT + QML_ATTACHED(TestAttachedProperties) +public: + explicit TestAttachableC(QObject *parent = nullptr) : QObject(parent) {} + static TestAttachedProperties *qmlAttachedProperties(QObject *obj) + { + return new TestAttachedProperties(obj); + } +}; + class tst_qmldiskcache: public QObject { Q_OBJECT @@ -46,6 +106,12 @@ private slots: void inlineComponentDoesNotCauseConstantInvalidation_data(); void inlineComponentDoesNotCauseConstantInvalidation(); + void selfReferencingSignalParameter(); + void compositeAttachedPropertyChecksum(); + void compositeAttachedCycleDirectAB(); + void compositeAttachedCycleIndirectABC(); + + void selfReferenceDoesNotInvalidateCache(); private: QDir m_qmlCacheDirectory; @@ -1364,6 +1430,279 @@ void tst_qmldiskcache::inlineComponentDoesNotCauseConstantInvalidation() QVERIFY(data1 != data2); } +void tst_qmldiskcache::selfReferencingSignalParameter() +{ + QQmlEngine engine; + + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + // Write a QML file that uses itself as a signal parameter type + const QString testFilePath = tempDir.path() + "/SelfReference.qml"; + { + QFile f(testFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml\n" + "QtObject {\n" + " property SelfReference self\n" + " signal blah(selfParam: SelfReference)\n" + " function returnSelf() : SelfReference { return this; }\n" + "}")); + } + + const QString mainFilePath = tempDir.path() + "/main.qml"; + { + QFile f(mainFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml\nSelfReference {}")); + } + + // First load: compiles and creates cache + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(mainFilePath)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + + const QString cacheFilePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( + QUrl::fromLocalFile(testFilePath)); + QVERIFY(QFile::exists(cacheFilePath)); + QDateTime initialCacheTimeStamp = QFileInfo(cacheFilePath).lastModified(); + + engine.clearComponentCache(); + waitForFileSystem(); + + // Second load: should use the cache without invalidation + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(mainFilePath)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + + { + QVERIFY(QFile::exists(cacheFilePath)); + QDateTime newCacheTimeStamp = QFileInfo(cacheFilePath).lastModified(); + QCOMPARE(newCacheTimeStamp, initialCacheTimeStamp); + } +} + +void tst_qmldiskcache::compositeAttachedPropertyChecksum() +{ + // Verifies that using attached properties via a composite type + // derived from an attaching C++ type produces a valid dependency + // checksum so the .qmlc cache is not needlessly invalidated: + // + // 1. TestAttachable is a C++ type with QML_ATTACHED. + // 2. CompositeAttachable.qml extends TestAttachable (composite type). + // 3. test.qml uses "CompositeAttachable.value: 42" (attached property). + // + // Previously, the composite type's ResolvedTypeReference had no + // compilation unit (needsCreation was false), causing addToHash() + // to fail and the dependency checksum to be all-zeros. On reload, + // verifyChecksum() would fail and the cache would be rewritten + // every time. + + qmlRegisterType("TestModule", 1, 0, "TestAttachable"); + + QQmlEngine engine; + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + const QString compositeFilePath = + testCompiler.tempDir.path() + "/CompositeAttachable.qml"; + + { + QFile f(compositeFilePath); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\nTestAttachable {}\n"); + } + + const QByteArray mainContents = + "import QtQml 2.0\n" + "QtObject {\n" + " CompositeAttachable.value: 42\n" + "}\n"; + + testCompiler.clearCache(); + QVERIFY2(testCompiler.writeTestFile(mainContents), + qPrintable(testCompiler.lastErrorString)); + + // First load: compiles from source and creates .qmlc cache. + QVERIFY2(testCompiler.loadTestFile(), + qPrintable(testCompiler.lastErrorString)); + + // Second load: must use the cached .qmlc without rewriting it. + // This fails without the fix because the dependency checksum + // would mismatch (empty vs all-zeros). + const QDateTime cacheModified = + QFileInfo(testCompiler.cacheFilePath).lastModified(); + + QVERIFY2(testCompiler.loadTestFile(), + qPrintable(testCompiler.lastErrorString)); + + QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), + cacheModified); +} + +void tst_qmldiskcache::compositeAttachedCycleDirectAB() +{ + // Direct cycle: two composite types, each derived from a different + // attaching C++ base, each using the other's attached properties. + // + // CompositeA.qml extends TestAttachable and uses "CompositeB.value: 1" + // CompositeB.qml extends TestAttachableB and uses "CompositeA.value: 2" + // + // The type loader must detect the circular dependency (A waits on B, + // B waits on A) and report an error. + + qmlRegisterType("TestModule", 1, 0, "TestAttachable"); + qmlRegisterType("TestModule", 1, 0, "TestAttachableB"); + + QQmlEngine engine; + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + { + QFile f(testCompiler.tempDir.path() + "/CompositeA.qml"); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\n" + "TestAttachable {\n" + " CompositeB.value: 1\n" + "}\n"); + } + { + QFile f(testCompiler.tempDir.path() + "/CompositeB.qml"); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\n" + "TestAttachableB {\n" + " CompositeA.value: 2\n" + "}\n"); + } + + QSKIP("Loading dependency cycles does not fail cleanly on 6.11"); + + // Load CompositeA — it depends on CompositeB and vice versa. + CleanlyLoadingComponent component( + &engine, testCompiler.tempDir.path() + "/CompositeA.qml"); + QVERIFY(component.isError()); + QVERIFY2(component.errorString().contains(QLatin1String("Cyclic dependency")), + qPrintable(component.errorString())); +} + +void tst_qmldiskcache::compositeAttachedCycleIndirectABC() +{ + // Indirect cycle through an intermediate type: + // + // CompositeA.qml extends TestAttachable and uses "CompositeB.value: 1" + // CompositeB.qml extends TestAttachableB and uses "CompositeC.value: 2" + // CompositeC.qml extends TestAttachableC and uses "CompositeA.value: 3" + // + // The type loader currently only detects direct A↔B cycles in + // addDependency(). Indirect cycles (A→B→C→A) are not detected + // and cause all three types to wait on each other indefinitely. + // This test verifies that the cycle is detected; until the type + // loader gains transitive cycle detection this test is expected + // to fail. + + QSKIP("Type loader does not detect indirect dependency cycles yet (only direct A<->B)."); + + qmlRegisterType("TestModule", 1, 0, "TestAttachable"); + qmlRegisterType("TestModule", 1, 0, "TestAttachableB"); + qmlRegisterType("TestModule", 1, 0, "TestAttachableC"); + + QQmlEngine engine; + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + { + QFile f(testCompiler.tempDir.path() + "/CompositeA.qml"); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\n" + "TestAttachable {\n" + " CompositeB.value: 1\n" + "}\n"); + } + { + QFile f(testCompiler.tempDir.path() + "/CompositeB.qml"); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\n" + "TestAttachableB {\n" + " CompositeC.value: 2\n" + "}\n"); + } + { + QFile f(testCompiler.tempDir.path() + "/CompositeC.qml"); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write("import TestModule 1.0\n" + "TestAttachableC {\n" + " CompositeA.value: 3\n" + "}\n"); + } + + // Load CompositeA — it depends on B, B on C, C on A. + CleanlyLoadingComponent component( + &engine, testCompiler.tempDir.path() + "/CompositeA.qml"); + QVERIFY(component.isError()); + QVERIFY2(component.errorString().contains(QLatin1String("Cyclic dependency")), + qPrintable(component.errorString())); +} + +// Verify that a self-referencing QML type (a type with a property of its own +// type) can successfully reuse its disk cache. A self-reference produces a +// ResolvedTypeReference with neither a valid QQmlType nor a compilationUnit, +// which causes the dependency hash computation to fail and stores an all-zero +// dependencyMD5Checksum. On reload the hash computation fails again, returning +// an empty QByteArray that doesn't match the 16-byte all-zero field, so the +// cache is always rejected and regenerated. +void tst_qmldiskcache::selfReferenceDoesNotInvalidateCache() +{ + QQmlEngine engine; + + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + const QString testFilePath = tempDir.path() + "/SelfRef.qml"; + { + QFile f(testFilePath); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.write(QByteArrayLiteral("import QtQml 2.0\n" + "QtObject {\n" + " property SelfRef child: null\n" + "}")); + } + + // First load: compiles from source and writes .qmlc + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + + const QString cacheFilePath = QV4::CompiledData::CompilationUnit::localCacheFilePath( + QUrl::fromLocalFile(testFilePath)); + QVERIFY(QFile::exists(cacheFilePath)); + QDateTime initialCacheTimeStamp = QFileInfo(cacheFilePath).lastModified(); + engine.clearComponentCache(); + waitForFileSystem(); + + // Second load: should reuse the cached .qmlc without regenerating it. + { + CleanlyLoadingComponent component(&engine, QUrl::fromLocalFile(testFilePath)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer obj(component.create()); + QVERIFY(!obj.isNull()); + } + + { + QVERIFY(QFile::exists(cacheFilePath)); + QDateTime newCacheTimeStamp = QFileInfo(cacheFilePath).lastModified(); + QCOMPARE(newCacheTimeStamp, initialCacheTimeStamp); + } +} + QTEST_MAIN(tst_qmldiskcache) #include "tst_qmldiskcache.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.formatted.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.formatted.qml new file mode 100644 index 0000000000..46a0119a01 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.formatted.qml @@ -0,0 +1,10 @@ +import QtQuick + +Item { + a: [ /*1*/ , /*2*/ ] + o: ({ /*3*/ }) + e: [ /*1 + */, , , + + /*2*/] +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.qml new file mode 100644 index 0000000000..5000ae399e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnArrayAndObjectPatterns.qml @@ -0,0 +1,12 @@ + + +import QtQuick + +Item { + a: [ /*1*/ , /*2*/ ] + o: ({ /*3*/ }) + e: [ /*1 + */, , , + + /*2*/] +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.formatted.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.formatted.qml new file mode 100644 index 0000000000..72e2f81ce8 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.formatted.qml @@ -0,0 +1,21 @@ +import QtQuick + +Item { + function // 1 + * // 2 + f // 3 + ( // 4 + ) // 5 + { // 6 + } // 7 + + Component.onCompleted: { + function // 1 + * // 2 + g // 3 + ( // 4 + ) // 5 + { // 6 + } // 7 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.qml new file mode 100644 index 0000000000..762e9a73b0 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnGenerator.qml @@ -0,0 +1,23 @@ + + +import QtQuick + +Item { + function // 1 + * // 2 + f // 3 + ( // 4 + ) // 5 + { // 6 + } // 7 + + Component.onCompleted: { + function // 1 + * // 2 + g // 3 + ( // 4 + ) // 5 + { // 6 + } // 7 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.formatted.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.formatted.qml new file mode 100644 index 0000000000..ba5d529b9f --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.formatted.qml @@ -0,0 +1,18 @@ +import QtQuick + +Item { + function f() { + try // 1 + { // 2 + } // 3 + catch // 4 + ( // 5 + e // 6 + ) // 7 + { // 8 + } // 9 + finally // 10 + { // 11 + } // 12 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.qml new file mode 100644 index 0000000000..2107eca7e0 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/data/commentsOnTryCatchFinally.qml @@ -0,0 +1,20 @@ + + +import QtQuick + +Item { + function f() { + try // 1 + { // 2 + } // 3 + catch // 4 + ( // 5 + e // 6 + ) // 7 + { // 8 + } // 9 + finally // 10 + { // 11 + } // 12 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 06819049c8..268a91346b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,9 @@ private Q_SLOTS: void normalizedId_data(); void normalizedId(); + void reject_data(); + void reject(); + private: QString formatInMemory(const QString &fileToFormat, bool *didSucceed = nullptr, LineWriterOptions options = LineWriterOptions(), @@ -280,12 +284,13 @@ QString TestQmlformat::formatInMemoryImpl(bool *didSucceed, LineWriterOptions op checks = largeChecks; QTextStream res(&resultStr); - LineWriter lw([&res](QStringView s) { res << s; }, QLatin1String("*testStream*"), options); + auto lw = QQmlJS::Dom::createLineWriter([&res](QStringView s) { res << s; }, + QLatin1String("*testStream*"), options); DomItem qmlFile = tFile.field(Fields::currentItem); - OutWriter ow(getFileItemOwner(qmlFile), lw); + OutWriter ow(getFileItemOwner(qmlFile), *lw); ow.indentNextlines = true; writtenOut = qmlFile.writeOutForFile(ow, checks); - lw.eof(); + lw->eof(); res.flush(); } if (didSucceed) @@ -410,6 +415,13 @@ void TestQmlformat::qml_data() << "commentInEnum.formatted.qml"; QTest::newRow("commentInQmlObject") << "commentInQmlObject.qml" << "commentInQmlObject.formatted.qml"; + QTest::newRow("commentsOnArrayAndObjectPatterns") + << "commentsOnArrayAndObjectPatterns.qml" + << "commentsOnArrayAndObjectPatterns.formatted.qml"; + QTest::newRow("commentsOnGenerator") << "commentsOnGenerator.qml" + << "commentsOnGenerator.formatted.qml"; + QTest::newRow("commentsOnTryCatchFinally") << "commentsOnTryCatchFinally.qml" + << "commentsOnTryCatchFinally.formatted.qml"; } void TestQmlformat::qml() { @@ -436,26 +448,48 @@ void TestQmlformat::qmlSnippet_data() { QTest::addColumn("unformatted"); QTest::addColumn("expectedResult"); + QTest::addColumn("opts"); + + LineWriterOptions defaultOptions ; + defaultOptions.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; + // the expected snippets use unix newlines, also on windows + defaultOptions.lineEndings = LineWriterOptions::LineEndings::Unix; + + { + LineWriterOptions maxColumnWidth = defaultOptions; + maxColumnWidth.maxLineLength = 80; + QTest::addRow("LambdaMaxColumnWidth") + << uR"(Item { Component.onCompleted: () => { console.info("a"); A.b.c = rs => { let x = 3; return x; }; }})"_s + << uR"(Item { + Component.onCompleted: () => { + console.info("a"); + A.b.c = rs => { + let x = 3; + return x; + }; + } +} +)"_s + << maxColumnWidth; + } QTest::addRow("QmlObjectComment") << u"EIUMAPQTE.ResourceMapHistoryControls { // qmllint disable required\n}"_s - << u"EIUMAPQTE.ResourceMapHistoryControls { // qmllint disable required\n}\n"_s; + << u"EIUMAPQTE.ResourceMapHistoryControls { // qmllint disable required\n}\n"_s + << defaultOptions; } void TestQmlformat::qmlSnippet() { QFETCH(QString, unformatted); QFETCH(QString, expectedResult); + QFETCH(LineWriterOptions, opts); constexpr QLatin1String snippetTemplate = R"(import QtQuick %1)"_L1; bool wasSuccessful; - LineWriterOptions opts; - opts.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; - // the expected snippets use unix newlines, also on windows - opts.lineEndings = LineWriterOptions::LineEndings::Unix; QString output = formatSnippetInMemory(snippetTemplate.arg(unformatted), &wasSuccessful, opts); QVERIFY(wasSuccessful && !output.isEmpty()); QCOMPARE(output, snippetTemplate.arg(expectedResult)); @@ -593,5 +627,38 @@ void TestQmlformat::normalizedId() QCOMPARE(output, exp); } +void TestQmlformat::reject_data() +{ + QTest::addColumn("snippet"); + + QTest::addRow("var-in-object") << "var x = 3;"; +} + +void TestQmlformat::reject() +{ + QFETCH(QString, snippet); + + const QString code = snippet.startsWith("import"_L1) || snippet.startsWith("pragam"_L1) + ? snippet + : R"(import QtQuick +Item { +%1 +})"_L1.arg(snippet); + + bool wasSuccessful = true; + + auto env = DomEnvironment::create( + QStringList(), // as we load no dependencies we do not need any paths + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + + const QString formatted = formatInMemoryImpl( + &wasSuccessful, LineWriterOptions{}, WriteOutCheck::None, WriteOutCheck::None, + std::move(env), FileToLoad::fromMemory(env, testFile("file.qml"), code)); + + QVERIFY(!wasSuccessful); + QCOMPARE(formatted, ""_L1); +} + QTEST_MAIN(TestQmlformat) #include "tst_qmlformat.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat_cli.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat_cli.cpp index 1edc220df2..d0b2be1bf1 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat_cli.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmlformat/tst_qmlformat_cli.cpp @@ -205,7 +205,7 @@ void TestQmlformatCli::testFormat_data() QTest::newRow("normalize + keep attributes order") << "normalizedGroupAttributesTogether.qml" << "normalizedGroupAttributesTogether.formatted.qml" - << QStringList{ "-n", "--group-attributes-together" } << RunOption::OnCopy; + << QStringList{ "--group-attributes-together" } << RunOption::OnCopy; QTest::newRow("indentEquals2") << "threeFunctionsOneLine.js" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/AttachedProperties/attachedProperty.qmltypes b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/AttachedProperties/attachedProperty.qmltypes index 54acc48fd4..29284550bd 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/AttachedProperties/attachedProperty.qmltypes +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/AttachedProperties/attachedProperty.qmltypes @@ -1,4 +1,4 @@ -import QtQuick.tooling 1.2 +import QtQuick.tooling 1.2 Module { Component { file: "attachedtype.h" @@ -16,6 +16,15 @@ Module { prototype: "QObject" Property { name: "count"; type: "int"; read: "getCount"; write: "setCount" } Property { name: "str"; type: "QString"; read: "getStr"; write: "setStr" } + Property { name: "mylist"; type: "QString"; read: "getStr"; write: "setStr" } + Property { + name: "myList" + type: "QObject" + isList: true + read: "myList" + write: "setMyList" + notify: "myListChanged" + } Property { name: "object"; type: "QObject"; isPointer: true; read: "getObject"; write: "setObject" } } Component { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/Connections.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/Connections.qml new file mode 100644 index 0000000000..463c1e2fc0 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/Connections.qml @@ -0,0 +1,12 @@ +import QtQuick + +Rectangle { + property string hello + + id: root + + Connections { + target: root + function onHelloChanged(argument) {} + } +} \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector2/main.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector2/main.qml index a51322225c..6925e3637b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector2/main.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector2/main.qml @@ -1,5 +1,5 @@ import "." - +import QtQuick Item { ToolBar {} Broken { asdf: 42; } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/App.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/App.qml new file mode 100644 index 0000000000..1b792a0678 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/App.qml @@ -0,0 +1,4 @@ +import QML + +QtObject { +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Bad.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Bad.qml new file mode 100644 index 0000000000..bf5b7e5150 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Bad.qml @@ -0,0 +1,5 @@ +import QML + +QtObject { + // incompatible: no property helloWorld defined here! +} \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Circle.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Circle.qml new file mode 100644 index 0000000000..70847b726a --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/Circle.qml @@ -0,0 +1,7 @@ +import QML + +QtObject { + id: root + + required property bool isFocused +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/SubCircle.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/SubCircle.qml new file mode 100644 index 0000000000..53c91b99f7 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/SubCircle.qml @@ -0,0 +1,6 @@ +import QML + +Circle { + property int sub + property int anotherProperty +} \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/qmldir b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/qmldir new file mode 100644 index 0000000000..49cb6dc76d --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/+Material/qmldir @@ -0,0 +1 @@ +prefer :/qt/qml/FileSelector3/ \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/.qmllint.ini b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/.qmllint.ini new file mode 100644 index 0000000000..98e290443e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/.qmllint.ini @@ -0,0 +1,2 @@ +[Warnings] +ImportFileSelector=warning \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/App.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/App.qml new file mode 100644 index 0000000000..1b792a0678 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/App.qml @@ -0,0 +1,4 @@ +import QML + +QtObject { +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Bad.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Bad.qml new file mode 100644 index 0000000000..8f9b3c84da --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Bad.qml @@ -0,0 +1,5 @@ +import QML + +QtObject { + required property int helloWorld +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Circle.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Circle.qml new file mode 100644 index 0000000000..2149437780 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/Circle.qml @@ -0,0 +1,5 @@ +import QML + +QtObject { + required property bool isFocused +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/SubCircle.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/SubCircle.qml new file mode 100644 index 0000000000..30c1465097 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/SubCircle.qml @@ -0,0 +1,5 @@ +import QML + +Circle { + required property int sub +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/qmldir b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/qmldir new file mode 100644 index 0000000000..025a76043b --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/qmldir @@ -0,0 +1,10 @@ +module FileSelector3 +App 254.0 +Material/App.qml +Bad 254.0 +Material/Bad.qml +Circle 254.0 +Material/Circle.qml +SubCircle 254.0 +Material/SubCircle.qml +App 254.0 App.qml +Bad 254.0 Bad.qml +Circle 254.0 Circle.qml +SubCircle 254.0 SubCircle.qml + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/resources.qrc b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/resources.qrc new file mode 100644 index 0000000000..0938169611 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/FileSelector3/resources.qrc @@ -0,0 +1,14 @@ + + + App.qml + Bad.qml + Circle.qml + +Material/App.qml + +Material/Bad.qml + +Material/Circle.qml + qmldir + +Material/qmldir + SubCircle.qml + +Material/SubCircle.qml + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/MenuBar.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/MenuBar.qml new file mode 100644 index 0000000000..4fa4c81a97 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/MenuBar.qml @@ -0,0 +1,12 @@ +import QtQuick + +Rectangle { + readonly property int currentIndex: lv.currentIndex + readonly property int custWidth: lv.highlightMoveDuration + anchors.fill: parent + color: "red" + ListView { + id: lv + highlightMoveDuration: 500 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/qmldir b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/qmldir new file mode 100644 index 0000000000..0e132f6e1b --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/ModuleWithMenuBar/qmldir @@ -0,0 +1,3 @@ +module ModuleWithMenuBar +MenuBar 1.0 MenuBar.qml + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asCast.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asCast.qml new file mode 100644 index 0000000000..c6201aa6f8 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asCast.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2021 The Qt Company Ltd. + +pragma Strict +pragma ValueTypeBehavior: Assertable + +import QtQuick +import QtQml as QQ + +Item { + QtObject { id: object } + Item { id: item } + Rectangle { id: rectangle } + + property string string: "a" + property url url: "http://example.com" + property date date: new Date(1996, 2, 3) + + property QtObject objectAsObject: object as QtObject + property QtObject objectAsItem: object as Item + property QtObject objectAsRectangle: object as Rectangle + + property QtObject itemAsObject: item as QtObject + property QtObject itemAsItem: item as Item + property QtObject itemAsRectangle: item as Rectangle + + property QtObject rectangleAsObject: rectangle as QtObject + property QtObject rectangleAsItem: rectangle as Item + property QtObject rectangleAsRectangle: rectangle as Rectangle + + property QtObject nullAsObject: null as QtObject + property QtObject nullAsItem: null as Item + property QtObject nullAsRectangle: null as Rectangle + + property QtObject undefinedAsObject: undefined as QtObject + property QtObject undefinedAsItem: undefined as Item + property QtObject undefinedAsRectangle: undefined as Rectangle + + property var stringAsString: string as QQ.string + property var urlAsUrl: url as QQ.url + property var dateAsDate: date as QQ.date +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asValueTypeGood.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asValueTypeGood.qml new file mode 100644 index 0000000000..c5441ca09d --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/asValueTypeGood.qml @@ -0,0 +1,32 @@ +pragma ValueTypeBehavior: Assertable +import QtQml as Q + +Q.QtObject { + property var a + property rect b: a as Q.rect + property bool c: a instanceof Q.rect + property bool d: ({x: 10, y: 20}) instanceof Q.point + property var e: ({x: 10, y: 20}) as Q.point + property var g: "green" as Q.string + + property var i: { + let p = new Q.point; + p.x = 10 + p.y = 20 + return p + } + + property var j: 4.0 as Q.int + property var k: (4.5 / 1.5) as Q.int + property var l: 5 as Q.double + property var m: "something" as Q.var + property var n: 1 as Q.bool + property var o: Infinity as Q.int + + property var p: b as Q.size; + property var q: this as Q.size; + property var r: ({}) as Q.size; + property var s: 11 as Q.size; + property var t: Q.Component as Q.size; + property var u: Q.Qt as Q.size; +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/componentDefinitionInnerRequiredProperty.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/componentDefinitionInnerRequiredProperty.qml new file mode 100644 index 0000000000..9ea517a6f8 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/componentDefinitionInnerRequiredProperty.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + Component { + id: mycomp + + Item { + Rectangle { + // Inner required properties cannot be set so this + // should produce an error + required property bool bar + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/goodAttachedProperty.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/goodAttachedProperty.qml index ec9ba079f5..b4aec4d5c0 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/goodAttachedProperty.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/goodAttachedProperty.qml @@ -1,8 +1,9 @@ -import QtQml +import QtQml import AttachedProperties 1.0 QtObject { TestType.object: Component { QtObject {} } TestType.count: 42 + 2 + TestType.myList: [ QtObject {} ] Component.onCompleted: { TestType.count = 42; // TestType here is an attached type, not this object's instance of it. diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/unknownPropertyDuplicateBinding.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/unknownPropertyDuplicateBinding.qml index 0cfdf5ec34..56dce2d5d7 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/unknownPropertyDuplicateBinding.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/data/unknownPropertyDuplicateBinding.qml @@ -1,7 +1,7 @@ -import QtQuick +import QtQuick Item { - inner.states: [ + inner.states: [ // qmllint disable unqualified unresolved-type State {name: "foo"}, // qmllint disable missing-property State {name: "bar"} // qmllint disable missing-property ] diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/tst_qmllint.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/tst_qmllint.cpp index cb4faba4b4..e83edff3b1 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -41,6 +41,18 @@ public: QtMsgType severity = QtWarningMsg; }; + struct FixMessage + { + QString text; + QString replacement; + quint32 line = 0, column = 0; + + FixMessage(const QString &text, const QString &replacement, quint32 line = 0, + quint32 column = 0) + : text(text), replacement(replacement), line(line), column(column) + {} + }; + struct Result { enum Flag { @@ -60,7 +72,7 @@ public: QList expectedMessages = {}; QList badMessages = {}; - QList expectedReplacements = {}; + QList expectedReplacements = {}; Flags flags = {}; @@ -187,10 +199,6 @@ private Q_SLOTS: private: enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum ContainOption { StringNotContained, StringContained }; - enum ReplacementOption { - NoReplacementSearch, - DoReplacementSearch, - }; enum LintType { LintFile, LintModule }; @@ -253,8 +261,9 @@ private: void searchWarnings(const QJsonArray &warnings, const QString &string, QtMsgType type = QtWarningMsg, quint32 line = 0, quint32 column = 0, - ContainOption shouldContain = StringContained, - ReplacementOption searchReplacements = NoReplacementSearch); + ContainOption shouldContain = StringContained); + void searchReplacements(const QJsonArray &warnings, const QString &substring, + const QString &replacementSubString, quint32 line, quint32 column); template @@ -334,35 +343,39 @@ void TestQmllint::testUnqualified_data() 33 } // builtin property }, {}, - { { Message { u"root."_s, 9, 16 } }, { Message { u"root."_s, 13, 33 } } } - }; + { { { u"unqualified is a member of a parent element.\n "_s + u"You can qualify the access with its id to avoid this warning."_s, + u"root."_s, 9, 16 }, + { u"x is a member of a parent element.\n "_s + u"You can qualify the access with its id to avoid this warning."_s, + u"root."_s, 13, 33 } } } }; // access injected name from signal QTest::newRow("SignalHandler") << QStringLiteral("SignalHandler.qml") - << Result { { - Message { QStringLiteral("Unqualified access"), 5, 21 }, - Message { QStringLiteral("Unqualified access"), 10, 21 }, - Message { QStringLiteral("Unqualified access"), 8, 29 }, - Message { QStringLiteral("Unqualified access"), 12, 34 }, - }, - {}, - { - Message { QStringLiteral("function(mouse)"), 4, 22 }, - Message { QStringLiteral("function(mouse)"), 9, 24 }, - Message { QStringLiteral("(mouse) => "), 8, 16 }, - Message { QStringLiteral("(mouse) => "), 12, 21 }, - } }; + << Result{ { { "Unqualified access"_L1, 5, 21 }, + { "Unqualified access"_L1, 10, 21 }, + { "Unqualified access"_L1, 8, 29 }, + { "Unqualified access"_L1, 12, 34 } }, + { }, + { { "\"mouse\" is ambiguous. Use a function instead: function(mouse) { ... }"_L1, + "function(mouse) "_L1, 4, 22 }, + { "\"mouse\" is ambiguous. Use a function instead: function(mouse) { ... }"_L1, + "function(mouse) "_L1, 9, 24 }, + { "\"mouse\" is ambiguous. Use a function instead: (mouse) => ..."_L1, + "(mouse) => "_L1, 8, 16 }, + { "\"mouse\" is ambiguous. Use a function instead: (mouse) => ..."_L1, + "(mouse) => "_L1, 12, 21 } } }; // access catch identifier outside catch block QTest::newRow("CatchStatement") << QStringLiteral("CatchStatement.qml") << Result { { Message { QStringLiteral("Unqualified access"), 6, 21 } } }; QTest::newRow("NonSpuriousParent") << QStringLiteral("nonSpuriousParentWarning.qml") - << Result { { - Message { QStringLiteral("Unqualified access"), 6, 25 }, - }, - {}, - { { Message { u"."_s, 6, 25 } } } }; + << Result{ { { "Unqualified access"_L1, 6, 25 } }, + { }, + { { { u"parent is a member of a parent element.\n "_s + u"You can qualify the access with its id to avoid this warning."_s, + u"."_s, 6, 25 } } } }; QTest::newRow("crashConnections") << QStringLiteral("crashConnections.qml") @@ -372,11 +385,13 @@ void TestQmllint::testUnqualified_data() << QStringLiteral("delegateContextProperties.qml") << Result { { Message { QStringLiteral("Unqualified access"), 6, 14 }, Message { QStringLiteral("Unqualified access"), 7, 15 }, - Message { QStringLiteral("model is implicitly injected into this " - "delegate. Add a required property instead.") }, + Message { QStringLiteral("'model' is implicitly injected into this " + "delegate. Add a required property 'model' to " + "the delegate instead.") }, Message { - QStringLiteral("index is implicitly injected into this delegate. " - "Add a required property instead.") } } }; + QStringLiteral("'index' is implicitly injected into this " + "delegate. Add a required property 'index' to the " + "delegate instead.") } } }; QTest::newRow("storeSloppy") << QStringLiteral("UnqualifiedInStoreSloppy.qml") << Result{ { Message{ QStringLiteral("Unqualified access"), 9, 26} } }; @@ -542,232 +557,335 @@ void TestQmllint::dirtyQmlCode_data() { QTest::addColumn("filename"); QTest::addColumn("result"); + QTest::addColumn("options"); + + const CallQmllintOptions defaultOptions; QTest::newRow("2Interceptors") << QStringLiteral("2interceptors.qml") - << Result{ { { "Duplicate interceptor on property \"x\""_L1 } } }; + << Result{ { { "Duplicate interceptor on property \"x\""_L1 } } } << defaultOptions; QTest::newRow("2ValueSources") << QStringLiteral("2valueSources.qml") - << Result{ { { "Duplicate value source on property \"x\""_L1 } } }; + << Result{ { { "Duplicate value source on property \"x\""_L1 } } } << defaultOptions; QTest::newRow("AssignToReadOnlyProperty") << QStringLiteral("assignToReadOnlyProperty.qml") - << Result{ { { "Cannot assign to read-only property activeFocus"_L1 } } }; + << Result{ { { "Cannot assign to read-only property activeFocus"_L1 } } } + << defaultOptions; QTest::newRow("AssignToReadOnlyProperty2") << QStringLiteral("assignToReadOnlyProperty2.qml") - << Result{ { { "Cannot assign to read-only property activeFocus"_L1 } } }; + << Result{ { { "Cannot assign to read-only property activeFocus"_L1 } } } + << defaultOptions; QTest::newRow("AutomatchedSignalHandler") << QStringLiteral("AutomatchedSignalHandler.qml") - << Result{ { { "Unqualified access"_L1, 12, 36 } } }; + << Result{ { { "Unqualified access"_L1, 12, 36 } } } << defaultOptions; QTest::newRow("AutomatchedSignalHandler2") << QStringLiteral("AutomatchedSignalHandler.qml") << Result{ { { "Implicitly defining \"onClicked\" as signal handler in Connections " "is deprecated. Create a function instead: \"function onClicked() " - "{ ... }\""_L1 } } }; + "{ ... }\""_L1 } } } + << defaultOptions; QTest::newRow("BadAttached") << QStringLiteral("badAttached.qml") - << Result{ { { "unknown attached property scope WrongAttached."_L1 } } }; - QTest::newRow("BadBinding") - << QStringLiteral("badBinding.qml") - << Result{ { { "Could not find property \"doesNotExist\"."_L1 } } }; + << Result{ { { "unknown attached property scope WrongAttached."_L1 } } } + << defaultOptions; + QTest::newRow("BadBinding") << QStringLiteral("badBinding.qml") + << Result{ { { "Could not find property \"doesNotExist\"."_L1 } } } + << defaultOptions; QTest::newRow("BadLiteralBinding") << QStringLiteral("badLiteralBinding.qml") - << Result{ { { "Cannot assign literal of type string to int"_L1 } } }; + << Result{ { { "Cannot assign literal of type string to int"_L1 } } } << defaultOptions; QTest::newRow("BadLiteralBindingDate") << QStringLiteral("badLiteralBindingDate.qml") - << Result{ { { "Cannot assign binding of type QString to QDateTime"_L1 } } }; + << Result{ { { "Cannot assign binding of type QString to QDateTime"_L1 } } } + << defaultOptions; QTest::newRow("BadModulePrefix") << QStringLiteral("badModulePrefix.qml") - << Result{ { { "Cannot access singleton as a property of an object"_L1 } } }; + << Result{ { { "Cannot access singleton as a property of an object"_L1 } } } + << defaultOptions; QTest::newRow("BadModulePrefix2") << QStringLiteral("badModulePrefix2.qml") << Result{ { { "Cannot use non-QObject type QRectF to access prefixed import"_L1 } }, { { "Type not found in namespace"_L1 }, - { "Member \"BirthdayParty\" not found on type \"QRectF\""_L1 } } }; + { "Member \"BirthdayParty\" not found on type \"QRectF\""_L1 } } } + << defaultOptions; QTest::newRow("BadPropertyType") << QStringLiteral("badPropertyType.qml") << Result{ { { "No type found for property \"bad\". This may be due to a missing "_L1 - "import statement or incomplete qmltypes files."_L1 } } }; + "import statement or incomplete qmltypes files."_L1 } } } + << defaultOptions; QTest::newRow("BadScriptBindingOnAttachedSignalHandler") << QStringLiteral("badScriptBinding.attachedSignalHandler.qml") - << Result{ { { "no matching signal found for handler \"onBogusSignal\""_L1, 3, 10 } } }; + << Result{ { { "no matching signal found for handler \"onBogusSignal\""_L1, 3, 10 } } } + << defaultOptions; QTest::newRow("BadScriptBindingOnAttachedType") << QStringLiteral("badScriptBinding.attached.qml") - << Result{ { { "Could not find property \"bogusProperty\"."_L1, 5, 12 } } }; + << Result{ { { "Could not find property \"bogusProperty\"."_L1, 5, 12 } } } + << defaultOptions; QTest::newRow("BadScriptBindingOnGroup") << QStringLiteral("badScriptBinding.group.qml") - << Result{ { { "Could not find property \"bogusProperty\"."_L1, 3, 10 } } }; + << Result{ { { "Could not find property \"bogusProperty\"."_L1, 3, 10 } } } + << defaultOptions; QTest::newRow("CoerceToVoid") << QStringLiteral("coercetovoid.qml") - << Result{ { { "Function without return type annotation returns double"_L1 } } }; + << Result{ { { "Function without return type annotation returns double"_L1 } } } + << defaultOptions; QTest::newRow("DefaultPropertyLookupInUnknownType") << QStringLiteral("unknownParentDefaultPropertyCheck.qml") - << Result{ { { "Alien was not found. Did you add all imports and dependencies?"_L1 } } }; + << Result{ { { "Alien was not found. Did you add all imports and dependencies?"_L1 } } } + << defaultOptions; QTest::newRow("DefaultPropertyWithWrongType(string)") << QStringLiteral("defaultPropertyWithWrongType.qml") << Result{ { { "Cannot assign to default property of incompatible type"_L1 } }, - { { "Cannot assign to non-existent default property"_L1 } } }; + { { "Cannot assign to non-existent default property"_L1 } } } + << defaultOptions; QTest::newRow("Deprecation (Property binding, no reason)") << QStringLiteral("deprecatedPropertyBinding.qml") - << Result{ { { "Binding on deprecated property \"deprecated\""_L1 } } }; + << Result{ { { "Binding on deprecated property \"deprecated\""_L1 } } } + << defaultOptions; QTest::newRow("Deprecation (Property binding, with reason)") << QStringLiteral("deprecatedPropertyBindingReason.qml") - << Result{ { { "Binding on deprecated property \"deprecatedReason\" (Reason: Test)"_L1 } } }; + << Result{ { { "Binding on deprecated property \"deprecatedReason\" (Reason: Test)"_L1 } } } + << defaultOptions; QTest::newRow("Deprecation (Property, no reason)") << QStringLiteral("deprecatedProperty.qml") - << Result{ { { "Property \"deprecated\" is deprecated"_L1 } } }; + << Result{ { { "Property \"deprecated\" is deprecated"_L1 } } } << defaultOptions; QTest::newRow("Deprecation (Property, with reason)") << QStringLiteral("deprecatedPropertyReason.qml") - << Result{ { { "Property \"deprecated\" is deprecated (Reason: Test)"_L1 } } }; + << Result{ { { "Property \"deprecated\" is deprecated (Reason: Test)"_L1 } } } + << defaultOptions; QTest::newRow("Deprecation (Type, no reason)") << QStringLiteral("deprecatedType.qml") - << Result{ { { "Type \"TypeDeprecated\" is deprecated"_L1 } } }; + << Result{ { { "Type \"TypeDeprecated\" is deprecated"_L1 } } } << defaultOptions; QTest::newRow("Deprecation (Type, with reason)") << QStringLiteral("deprecatedTypeReason.qml") - << Result{ { { "Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)"_L1 } } }; + << Result{ { { "Type \"TypeDeprecatedReason\" is deprecated (Reason: Test)"_L1 } } } + << defaultOptions; QTest::newRow("DoubleAssignToDefaultProperty") << QStringLiteral("defaultPropertyWithDoubleAssignment.qml") - << Result{ { { "Cannot assign multiple objects to a default non-list property"_L1 } } }; + << Result{ { { "Cannot assign multiple objects to a default non-list property"_L1 } } } + << defaultOptions; + CallQmllintOptions withFileSelectorResourceFile = defaultOptions; + withFileSelectorResourceFile.resources.append(testFile("FileSelector3/resources.qrc")); + QTest::newRow("fileSelectorIncompatibleType") + << QStringLiteral("FileSelector3/Bad.qml") + << Result{ { + { "Type Bad has a potentially incompatible file-selected variant %1."_L1 + .arg(testFile("FileSelector3/+Material/Bad.qml")), + 3, 1 }, + }, + { + { "Ambiguous"_L1 }, + } } + .withFlags(Result::UseSettings) + << withFileSelectorResourceFile; + QTest::newRow("fileSelectorIncompatibleType2") + << QStringLiteral("FileSelector3/SubCircle.qml") + << Result{ { + { "Type SubCircle is ambiguous due to file selector usage, ignoring %1."_L1 + .arg(testFile("FileSelector3/+Material/SubCircle.qml")), + 3, 1 }, + }, + { + { "potentially incompatible"_L1 }, + } } + .withFlags(Result::UseSettings) + << withFileSelectorResourceFile; + QTest::newRow("fileSelectorIncompatibleFileSelectedType") + << QStringLiteral("FileSelector3/+Material/Bad.qml") + << Result{ { + { "File-selected type Bad is potentially incompatible with %1."_L1.arg( + testFile("FileSelector3/Bad.qml")), + 3, 1 }, + } } + << withFileSelectorResourceFile; + QTest::newRow("fileSelectorIncompatibleFileSelectedType2") + << QStringLiteral("FileSelector3/+Material/SubCircle.qml") + << Result{ { + { "File-selected type SubCircle is ambiguous due to file selector usage, this file will be ignored in favour of %1."_L1 + .arg(testFile("FileSelector3/SubCircle.qml")), + 3, 1 }, + }, + { + { "potentially incompatible"_L1 }, + } } + .withFlags(Result::UseSettings) + << withFileSelectorResourceFile; + QTest::newRow(("ImportModuleWithFileSelector")) + << QStringLiteral("FileSelector/main.qml") << Result::cleanWithSettings() + << defaultOptions; + QTest::newRow(("ImportFileSelector")) - << QStringLiteral("FileSelector/main.qml") - << Result{ - { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1.arg( - testFile("FileSelector/+Material/ToolBar.qml")), - 1, 1, QtMsgType::QtInfoMsg } } - }.withFlags(Result::Flags(Result::UseSettings | Result::ExitsNormally)); - QTest::newRow(("ImportFileSelector2")) - << QStringLiteral("FileSelector2/main.qml") - << Result{ - { - { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 - .arg(testFile("FileSelector2/+Material/ToolBar.qml")), - 1, 1, QtMsgType::QtInfoMsg }, - { "Ambiguous type detected. Broken 1.0 is defined multiple times."_L1, 1, - 1, QtMsgType::QtWarningMsg }, - }, - { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1.arg( - testFile("FileSelector2/+Material/ToolBar.qml")), - 1, 1, QtMsgType::QtWarningMsg } } - }.withFlags(Result::Flags(Result::UseSettings)); + << QStringLiteral("FileSelector/ToolBar.qml") + << Result{ { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 + .arg(testFile("FileSelector/+Material/ToolBar.qml")), + 3, 1, QtInfoMsg } } } + .withFlags(Result::Flags(Result::UseSettings | Result::ExitsNormally)) + << defaultOptions; + QTest::newRow(("ImportFileSelector2ToolBar")) + << QStringLiteral("FileSelector2/ToolBar.qml") + << Result{ { + { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 + .arg(testFile("FileSelector2/+Material/ToolBar.qml")), + 3, 1, QtMsgType::QtInfoMsg }, + }, + { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 + .arg(testFile("FileSelector2/+Material/ToolBar.qml")), + 3, 1, QtMsgType::QtWarningMsg }, + { "Item was not found."_L1 }, + { "Broken"_L1 } } } + .withFlags(Result::Flags(Result::UseSettings | Result::ExitsNormally)) + << defaultOptions; + QTest::newRow(("ImportFileSelector2Broken")) + << QStringLiteral("FileSelector2/ToolBar.qml") + << Result{ { + { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 + .arg(testFile("FileSelector2/+Material/ToolBar.qml")), + 3, 1, QtMsgType::QtInfoMsg }, + }, + { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1 + .arg(testFile("FileSelector2/+Material/ToolBar.qml")), + 3, 1, QtMsgType::QtWarningMsg }, + { "ToolBar"_L1 } } } + .withFlags(Result::Flags(Result::UseSettings | Result::ExitsNormally)) + << defaultOptions; QTest::newRow("InvalidImport") << QStringLiteral("invalidImport.qml") - << Result{ { { "Failed to import FooBar. Are your import paths set up properly?"_L1, - 2, 1 } } }; + << Result{ { { "Failed to import FooBar. Are your import paths set up properly?"_L1, 2, + 1 } } } + << defaultOptions; QTest::newRow("Invalid_id_blockstatement") << QStringLiteral("invalidId2.qml") - << Result{ { { "id must be followed by an identifier"_L1 } } }; + << Result{ { { "id must be followed by an identifier"_L1 } } } << defaultOptions; QTest::newRow("Invalid_id_expression") - << QStringLiteral("invalidId1.qml") - << Result{ { { "Failed to parse id"_L1 } } }; + << QStringLiteral("invalidId1.qml") << Result{ { { "Failed to parse id"_L1 } } } + << defaultOptions; QTest::newRow("Invalid_syntax_JS") - << QStringLiteral("failure1.js") << Result{ { { "Expected token `;'"_L1, 4, 12 } } }; + << QStringLiteral("failure1.js") << Result{ { { "Expected token `;'"_L1, 4, 12 } } } + << defaultOptions; QTest::newRow("Invalid_syntax_QML") - << QStringLiteral("failure1.qml") << Result{ { { "Expected token `:'"_L1, 4, 8 } } }; + << QStringLiteral("failure1.qml") << Result{ { { "Expected token `:'"_L1, 4, 8 } } } + << defaultOptions; QTest::newRow("IsNotAnEntryOfEnum") << QStringLiteral("IsNotAnEntryOfEnum.qml") - << Result{ { { "Member \"Mode\" not found on type \"Item\""_L1, 12, 29 }, + << Result{ { { "Member \"Mode\" not found on type \"IsNotAnEntryOfEnum\""_L1, 12, 29 }, { "\"Hour\" is not an entry of enum \"Mode\"."_L1, 13, 62 } }, - {}, - { { "Hours"_L1 } } }; + { }, + { { "Did you mean \"mode\"?"_L1, "mode"_L1, 12, 29 }, + { "Did you mean \"Hours\"?"_L1, "Hours"_L1, 13, 62 } } } + << defaultOptions; QTest::newRow("MemberNotFound") << QStringLiteral("memberNotFound.qml") - << Result{ { { "Member \"foo\" not found on type \"QtObject\""_L1, 6, 31 } } }; + << Result{ { { "Member \"foo\" not found on type \"memberNotFound\""_L1, 6, 31 } } } + << defaultOptions; QTest::newRow("MissingDefaultProperty") << QStringLiteral("defaultPropertyWithoutKeyword.qml") - << Result{ { { "Cannot assign to non-existent default property"_L1 } } }; + << Result{ { { "Cannot assign to non-existent default property"_L1 } } } + << defaultOptions; QTest::newRow("MissingDefaultPropertyDefinedInTheSameType") << QStringLiteral("defaultPropertyWithinTheSameType.qml") - << Result{ { { "Cannot assign to non-existent default property"_L1 } } }; + << Result{ { { "Cannot assign to non-existent default property"_L1 } } } + << defaultOptions; QTest::newRow("MultiDefaultPropertyWithWrongType") << QStringLiteral("multiDefaultPropertyWithWrongType.qml") << Result{ { { "Cannot assign to default property of incompatible type"_L1 } }, - { { "Cannot assign to non-existent default property"_L1 } } }; + { { "Cannot assign to non-existent default property"_L1 } } } + << defaultOptions; QTest::newRow("NonExistentListProperty") << QStringLiteral("nonExistentListProperty.qml") - << Result{ { { "Could not find property \"objs\"."_L1 } } }; + << Result{ { { "Could not find property \"objs\"."_L1 } } } << defaultOptions; QTest::newRow("OnAssignment") << QStringLiteral("onAssignment.qml") - << Result{ { { "Member \"loops\" not found on type \"bool\""_L1 } } }; - QTest::newRow("PropertyAliasCycles") - << QStringLiteral("settings/propertyAliasCycle/file.qml") - << Result::cleanWithSettings(); + << Result{ { { "Member \"loops\" not found on type \"bool\""_L1 } } } << defaultOptions; + QTest::newRow("PropertyAliasCycles") << QStringLiteral("settings/propertyAliasCycle/file.qml") + << Result::cleanWithSettings() << defaultOptions; // make sure that warnings are triggered without settings: QTest::newRow("PropertyAliasCycles2") << QStringLiteral("settings/propertyAliasCycle/file.qml") << Result{ { { "\"cycle1\" is part of an alias cycle"_L1 }, - { "\"cycle1\" is part of an alias cycle"_L1 } } }; + { "\"cycle1\" is part of an alias cycle"_L1 } } } + << defaultOptions; QTest::newRow("QtQuick.Window 2.0") << QStringLiteral("qtquickWindow20.qml") - << Result{ { { "Member \"window\" not found on type \"QQuickWindow\""_L1 } } }; + << Result{ { { "Member \"window\" not found on type \"QQuickWindow\""_L1 } } } + << defaultOptions; QTest::newRow("SignalParameterMismatch") << QStringLiteral("namedSignalParameters.qml") << Result{ { { "Parameter 1 to signal handler for \"onSig\" is called \"argarg\". " "The signal has a parameter of the same name in position 2."_L1 } }, - { { "onSig2"_L1 } } }; + { { "onSig2"_L1 } } } + << defaultOptions; QTest::newRow("StoreNameMethod") << QStringLiteral("storeNameMethod.qml") - << Result{ { { "Cannot assign to method foo"_L1 } } }; + << Result{ { { "Cannot assign to method foo"_L1 } } } << defaultOptions; QTest::newRow("TooManySignalParameters") << QStringLiteral("tooManySignalParameters.qml") << Result{ { { "Signal handler for \"onSig\" has more formal parameters than the " - "signal it handles."_L1 } } }; + "signal it handles."_L1 } } } + << defaultOptions; QTest::newRow("TypePropertAccess") - << QStringLiteral("typePropertyAccess.qml") - << Result{}; + << QStringLiteral("typePropertyAccess.qml") << Result{ } << defaultOptions; QTest::newRow("UnknownJavascriptMethd") << QStringLiteral("unknownJavascriptMethod.qml") - << Result{ { { "Member \"foo2\" not found on type \"Methods\""_L1, 5, 25 } } }; - QTest::newRow("Unused Import (prefix)") - << QStringLiteral("unused_prefix.qml") - << Result{ { { "Unused import"_L1, 1, 1, QtInfoMsg } }, - {}, - {}, - Result::ExitsNormally }; - QTest::newRow("Unused Import (simple)") - << QStringLiteral("unused_simple.qml") - << Result{ { { "Unused import"_L1, 1, 1, QtInfoMsg } }, - {}, - {}, - Result::ExitsNormally }; + << Result{ { { "Member \"foo2\" not found on type \"Methods\""_L1, 5, 25 } } } + << defaultOptions; + QTest::newRow("Unused Import (prefix)") << QStringLiteral("unused_prefix.qml") + << Result{ { { "Unused import"_L1, 1, 1, QtInfoMsg } }, + { }, + { }, + Result::ExitsNormally } + << defaultOptions; + QTest::newRow("Unused Import (simple)") << QStringLiteral("unused_simple.qml") + << Result{ { { "Unused import"_L1, 1, 1, QtInfoMsg } }, + { }, + { }, + Result::ExitsNormally } + << defaultOptions; QTest::newRow("ValueSource+2Interceptors") << QStringLiteral("valueSourceBetween2interceptors.qml") - << Result{ { { "Duplicate interceptor on property \"x\""_L1 } } }; + << Result{ { { "Duplicate interceptor on property \"x\""_L1 } } } << defaultOptions; QTest::newRow("ValueSource+ListValue") << QStringLiteral("valueSource_listValue.qml") - << Result{ { { "Cannot combine value source and binding on property \"objs\""_L1 } } }; + << Result{ { { "Cannot combine value source and binding on property \"objs\""_L1 } } } + << defaultOptions; QTest::newRow("ValueSource+Value") << QStringLiteral("valueSource_Value.qml") - << Result{ { { "Cannot combine value source and binding on property \"obj\""_L1 } } }; + << Result{ { { "Cannot combine value source and binding on property \"obj\""_L1 } } } + << defaultOptions; QTest::newRow("VariableUsedBeforeDeclaration") << QStringLiteral("useBeforeDeclaration.qml") - << Result{ { { "Identifier 'argq' is used here before its declaration"_L1, 5, 9 }, - { "Note: declaration of 'argq' here"_L1, 6, 13 }, } }; + << Result{ { + { "Identifier 'argq' is used here before its declaration"_L1, 5, 9 }, + { "Note: declaration of 'argq' here"_L1, 6, 13 }, + } } + << defaultOptions; QTest::newRow("WithStatement") << QStringLiteral("WithStatement.qml") - << Result{ { { "with statements are strongly discouraged"_L1 } } }; - QTest::newRow("aliasCycle1") - << QStringLiteral("aliasCycle.qml") - << Result{ { { "Alias \"b\" is part of an alias cycle"_L1, 6, 5 } } }; - QTest::newRow("aliasCycle2") - << QStringLiteral("aliasCycle.qml") - << Result{ { { "Alias \"a\" is part of an alias cycle"_L1, 5, 5 } } }; + << Result{ { { "with statements are strongly discouraged"_L1 } } } << defaultOptions; + QTest::newRow("aliasCycle1") << QStringLiteral( + "aliasCycle.qml") << Result{ { { "Alias \"b\" is part of an alias cycle"_L1, 6, 5 } } } + << defaultOptions; + QTest::newRow("aliasCycle2") << QStringLiteral( + "aliasCycle.qml") << Result{ { { "Alias \"a\" is part of an alias cycle"_L1, 5, 5 } } } + << defaultOptions; QTest::newRow("anchors3") << QStringLiteral("anchors3.qml") - << Result{ { { "Cannot assign binding of type QQuickItem to QQuickAnchorLine"_L1 } } }; + << Result{ { { "Cannot assign binding of type QQuickItem to QQuickAnchorLine"_L1 } } } + << defaultOptions; QTest::newRow("annotatedDefaultParameter") << QStringLiteral("annotatedDefaultParameter.qml") - << Result{ { { "Type annotations on default parameters are not supported"_L1 } } }; + << Result{ { { "Type annotations on default parameters are not supported"_L1 } } } + << defaultOptions; QTest::newRow("assignNonExistingTypeToVarProp") << QStringLiteral("assignNonExistingTypeToVarProp.qml") << Result{ { { "NonExistingType was not found. Did you add all imports and " - "dependencies?"_L1 } } }; + "dependencies?"_L1 } } } + << defaultOptions; // should succeed, but it does not: - QTest::newRow("attachedPropertyAccess") - << QStringLiteral("goodAttachedPropertyAccess.qml") - << Result::clean(); + QTest::newRow("attachedPropertyAccess") << QStringLiteral("goodAttachedPropertyAccess.qml") + << Result::clean() << defaultOptions; // should succeed, but it does not: - QTest::newRow("attachedPropertyNested") - << QStringLiteral("goodAttachedPropertyNested.qml") - << Result::clean(); + QTest::newRow("attachedPropertyNested") << QStringLiteral("goodAttachedPropertyNested.qml") + << Result::clean() << defaultOptions; QTest::newRow("autoFixConnectionsBinding") << QStringLiteral("autofix/ConnectionsHandler.qml") << Result{ { { "Implicitly defining \"onWidthChanged\" as signal handler in " @@ -775,93 +893,111 @@ void TestQmllint::dirtyQmlCode_data() "onWidthChanged() { ... }\"."_L1 }, { "Implicitly defining \"onColorChanged\" as signal handler in " "Connections is deprecated. Create a function instead: \"function " - "onColorChanged(collie) { ... }\"."_L1 } }, - { }, - { { "function onWidthChanged() { console.log(\"new width:\", width) }"_L1 }, - { "function onColorChanged(col) { console.log(\"new color:\", col) }"_L1 }, }, }; + "onColorChanged(collie) { ... }\"."_L1 } } } + << defaultOptions; QTest::newRow("bad constant number to string") << QStringLiteral("numberToStringProperty.qml") - << Result{ { { "Cannot assign literal of type double to QString"_L1 } } }; + << Result{ { { "Cannot assign literal of type double to QString"_L1 } } } + << defaultOptions; QTest::newRow("bad string binding (QT_TR_NOOP)") << QStringLiteral("bad_QT_TR_NOOP.qml") - << Result{ { { "Cannot assign literal of type string to int"_L1 } } }; + << Result{ { { "Cannot assign literal of type string to int"_L1 } } } << defaultOptions; QTest::newRow("bad template literal (simple)") << QStringLiteral("badTemplateStringSimple.qml") - << Result{ { { "Cannot assign literal of type string to int"_L1 } } }; + << Result{ { { "Cannot assign literal of type string to int"_L1 } } } << defaultOptions; QTest::newRow("bad tranlsation binding (qsTr)") - << QStringLiteral("bad_qsTr.qml") - << Result{}; + << QStringLiteral("bad_qsTr.qml") << Result{ } << defaultOptions; QTest::newRow("bad unary minus to string") << QStringLiteral("unaryMinusToStringProperty.qml") - << Result{ { { "Cannot assign literal of type double to QString"_L1 } } }; - QTest::newRow("badAlias") - << QStringLiteral("badAlias.qml") - << Result{ { { "Cannot resolve alias \"wrong\""_L1, 4, 5 } } }; + << Result{ { { "Cannot assign literal of type double to QString"_L1 } } } + << defaultOptions; + QTest::newRow("badAlias") << QStringLiteral("badAlias.qml") + << Result{ { { "Cannot resolve alias \"wrong\""_L1, 4, 5 } } } + << defaultOptions; QTest::newRow("badAliasExpression") << QStringLiteral("badAliasExpression.qml") << Result{ { { "Invalid alias expression. Only IDs and field member expressions can "_L1 - "be aliased"_L1, 5, 26 } } }; + "be aliased"_L1, + 5, 26 } } } + << defaultOptions; QTest::newRow("badAliasNotAnExpression") << QStringLiteral("badAliasNotAnExpression.qml") << Result{ { { "Invalid alias expression. Only IDs and field member expressions can "_L1 - "be aliased"_L1, 4, 30 } } }; + "be aliased"_L1, + 4, 30 } } } + << defaultOptions; QTest::newRow("badAliasObject") << QStringLiteral("badAliasObject.qml") - << Result{ { { "Member \"wrongwrongwrong\" not found on type \"QtObject\""_L1, 8, 40 } } }; + << Result{ { { "Member \"wrongwrongwrong\" not found on type \"QtObject\""_L1, 8, + 40 } } } + << defaultOptions; QTest::newRow("badAliasProperty1") << QStringLiteral("badAliasProperty.qml") - << Result{ { { "Cannot resolve alias \"wrong\""_L1, 5, 5 } } }; + << Result{ { { "Cannot resolve alias \"wrong\""_L1, 5, 5 } } } << defaultOptions; QTest::newRow("badAttachedProperty") << QStringLiteral("badAttachedProperty.qml") - << Result{ { { "Member \"progress\" not found on type \"TestTypeAttached\""_L1 } } }; + << Result{ { { "Member \"progress\" not found on type \"TestTypeAttached\""_L1 } } } + << defaultOptions; QTest::newRow("badAttachedPropertyNested") << QStringLiteral("badAttachedPropertyNested.qml") << Result{ { { "Member \"progress\" not found on type \"QObject\""_L1, 12, 41 } }, - { { "Member \"progress\" not found on type \"QObject\""_L1, 6, 37 } } }; + { { "Member \"progress\" not found on type \"QObject\""_L1, 6, 37 } } } + << defaultOptions; QTest::newRow("badAttachedPropertyTypeQtObject") << QStringLiteral("badAttachedPropertyTypeQtObject.qml") - << Result{ { { "Cannot assign object of type QtObject to int"_L1 } } }; + << Result{ { { "Cannot assign object of type QtObject to int"_L1 } } } + << defaultOptions; QTest::newRow("badAttachedPropertyTypeString") << QStringLiteral("badAttachedPropertyTypeString.qml") - << Result{ { { "Cannot assign literal of type string to int"_L1 } } }; + << Result{ { { "Cannot assign literal of type string to int"_L1 } } } << defaultOptions; QTest::newRow("badEnumFromQtQml") << QStringLiteral("badEnumFromQtQml.qml") - << Result{ { { "Member \"Linear123\" not found on type \"QQmlEasing\""_L1, - 4, 30 } } }; + << Result{ { { "Member \"Linear123\" not found on type \"QQmlEasing\""_L1, 4, 30 } } } + << defaultOptions; QTest::newRow("badGeneralizedGroup1") << QStringLiteral("badGeneralizedGroup1.qml") - << Result{ { { "Could not find property \"aaaa\"."_L1 } } }; + << Result{ { { "Could not find property \"aaaa\"."_L1 } } } << defaultOptions; QTest::newRow("badGeneralizedGroup2") << QStringLiteral("badGeneralizedGroup2.qml") - << Result{ { { "unknown grouped property scope aself"_L1 } } }; - QTest::newRow("badParent") - << QStringLiteral("badParent.qml") - << Result{ { { "Member \"rrr\" not found on type \"Item\""_L1, 5, 34 } } }; + << Result{ { { "unknown grouped property scope aself"_L1 } } } << defaultOptions; + QTest::newRow("badParent") << QStringLiteral("badParent.qml") + << Result{ { { "Member \"rrr\" not found on type \"badParent\""_L1, + 5, 34 } } } + << defaultOptions; QTest::newRow("badQmldirImportAndDepend") << QStringLiteral("qmldirImportAndDepend/bad.qml") - << Result{ { { "Item was not found. Did you add all imports and dependencies?"_L1, - 3, 1 } } }; - QTest::newRow("badScript") - << QStringLiteral("badScript.qml") - << Result{ { { "Member \"stuff\" not found on type \"Empty\""_L1, 5, 21 } } }; + << Result{ { { "Item was not found. Did you add all imports and dependencies?"_L1, 3, + 1 } } } + << defaultOptions; + QTest::newRow("badScript") << QStringLiteral("badScript.qml") + << Result{ { { "Member \"stuff\" not found on type \"Empty\""_L1, 5, + 21 } } } + << defaultOptions; QTest::newRow("badScriptOnAttachedProperty") << QStringLiteral("badScript.attached.qml") - << Result{ { { "Unqualified access"_L1, 3, 26 } } }; + << Result{ { { "Unqualified access"_L1, 3, 26 } } } << defaultOptions; QTest::newRow("badTypeAssertion") << QStringLiteral("badTypeAssertion.qml") - << Result{ { { "Member \"rrr\" not found on type \"QQuickItem\""_L1, 5, 39 } } }; + << Result{ { { "Member \"rrr\" not found on type \"QQuickItem\""_L1, 5, 39 } } } + << defaultOptions; QTest::newRow("badlyBoundComponents") << QStringLiteral("badlyBoundComponents.qml") - << Result{ { { "Unqualified access"_L1, 18, 36 } } }; + << Result{ { { "Unqualified access"_L1, 18, 36 } } } << defaultOptions; QTest::newRow("brokenNamespace") << QStringLiteral("brokenNamespace.qml") - << Result{ { { "Type not found in namespace"_L1, 4, 19 } } }; + << Result{ { { "Type not found in namespace"_L1, 4, 19 } } } << defaultOptions; QTest::newRow("cachedDependency") << QStringLiteral("cachedDependency.qml") << Result{ { { "Unused import"_L1, 1, 1, QtInfoMsg } }, { { "Cannot assign binding of type QQuickItem to QObject"_L1 } }, - {}, - Result::ExitsNormally }; + { }, + Result::ExitsNormally } + << defaultOptions; + QTest::newRow("componentDefinitionInnerRequiredProperty") + << u"componentDefinitionInnerRequiredProperty.qml"_s + << Result{ { { "Component is missing required property bar from Rectangle"_L1, 11, + 13 } } } + << defaultOptions; QTest::newRow("connectionsBinding") << QStringLiteral("autofix/ConnectionsHandler.qml") << Result{ { { "Implicitly defining \"onWidthChanged\" as signal handler in " @@ -869,67 +1005,82 @@ void TestQmllint::dirtyQmlCode_data() "onWidthChanged() { ... }\"."_L1 }, { "Implicitly defining \"onColorChanged\" as signal handler in " "Connections is deprecated. Create a function instead: " - "\"function onColorChanged(collie) { ... }\"."_L1 } } }; + "\"function onColorChanged(collie) { ... }\"."_L1 } } } + << defaultOptions; QTest::newRow("cppPropertyChangeHandlers-no-property") << QStringLiteral("badCppPropertyChangeHandlers3.qml") - << Result{ { { "no matching signal found for handler \"onXChanged\""_L1 } } }; + << Result{ { { "no matching signal found for handler \"onXChanged\""_L1 } } } + << defaultOptions; QTest::newRow("cppPropertyChangeHandlers-not-a-signal") << QStringLiteral("badCppPropertyChangeHandlers4.qml") - << Result{ { { "no matching signal found for handler \"onWannabeSignal\""_L1 } } }; + << Result{ { { "no matching signal found for handler \"onWannabeSignal\""_L1 } } } + << defaultOptions; QTest::newRow("cppPropertyChangeHandlers-wrong-parameters-size-bindable") << QStringLiteral("badCppPropertyChangeHandlers1.qml") << Result{ { { "Signal handler for \"onAChanged\" has more formal parameters than " - "the signal it handles"_L1 } } }; + "the signal it handles"_L1 } } } + << defaultOptions; QTest::newRow("cppPropertyChangeHandlers-wrong-parameters-size-notify") << QStringLiteral("badCppPropertyChangeHandlers2.qml") << Result{ { { "Signal handler for \"onBChanged\" has more formal parameters than " - "the signal it handles"_L1 } } }; + "the signal it handles"_L1 } } } + << defaultOptions; QTest::newRow("cycle in import") << QStringLiteral("cycleHead.qml") - << Result{ { { "MenuItem is part of an inheritance cycle: MenuItem -> MenuItem"_L1 } } }; + << Result{ { { "MenuItem is part of an inheritance cycle: MenuItem -> MenuItem"_L1 } } } + << defaultOptions; QTest::newRow("deprecatedFunction") << QStringLiteral("deprecatedFunction.qml") << Result{ { { "Method \"deprecated(foobar)\" is deprecated (Reason: No particular " - "reason.)"_L1 } } }; + "reason.)"_L1 } } } + << defaultOptions; QTest::newRow("deprecatedFunctionInherited") << QStringLiteral("deprecatedFunctionInherited.qml") << Result{ { { "Method \"deprecatedInherited(c, d)\" is deprecated (Reason: This " - "deprecation should be visible!)"_L1 } } }; + "deprecation should be visible!)"_L1 } } } + << defaultOptions; QTest::newRow("didYouMean(binding)") << QStringLiteral("didYouMeanBinding.qml") << Result{ { { "Could not find property \"witdh\"."_L1 } }, - {}, - { { "width"_L1 } } }; + { }, + { { "Did you mean \"width\"?"_L1, "width"_L1 } } } + << defaultOptions; QTest::newRow("didYouMean(component)") << QStringLiteral("didYouMeanComponent.qml") << Result{ { { "Itym was not found. Did you add all imports and dependencies?"_L1 }, - {}, - { { "Item"_L1 } } } }; + { }, + { { "Item"_L1 } } } } + << defaultOptions; QTest::newRow("didYouMean(enum)") << QStringLiteral("didYouMeanEnum.qml") << Result{ { { "Member \"Readx\" not found on type \"QQuickImage\""_L1 }, - {}, - { { "Ready"_L1 } } } }; + { }, + { { "Ready"_L1 } } } } + << defaultOptions; QTest::newRow("didYouMean(property)") << QStringLiteral("didYouMeanProperty.qml") << Result{ { { "Member \"hoight\" not found on type \"Rectangle\""_L1 }, - {}, - { { "height"_L1 } } } }; + { }, + { { "height"_L1 } } } } + << defaultOptions; QTest::newRow("didYouMean(propertyCall)") << QStringLiteral("didYouMeanPropertyCall.qml") << Result{ { { "Member \"lgg\" not found on type \"Console\""_L1 }, - {}, - { { "log"_L1 } } } }; + { }, + { { "log"_L1 } } } } + << defaultOptions; QTest::newRow("didYouMean(unqualified)") << QStringLiteral("didYouMeanUnqualified.qml") << Result{ { { "Unqualified access"_L1 } }, - {}, - { { "height"_L1 } } }; + { }, + { { "Did you mean \"height\"?"_L1, "height"_L1 } } } + << defaultOptions; QTest::newRow("didYouMean(unqualifiedCall)") << QStringLiteral("didYouMeanUnqualifiedCall.qml") << Result{ { { "Unqualified access"_L1 } }, - {}, - { { "func"_L1 } } }; + { }, + { { "Did you mean \"func\""_L1, "func"_L1 } } } + << defaultOptions; QTest::newRow("duplicateImportsDirty") << QStringLiteral("duplicateImportsDirty.qml") << Result{ { { "Duplicate import 'QtQml'"_L1, 2, 8 }, @@ -940,198 +1091,245 @@ void TestQmllint::dirtyQmlCode_data() { "Note: previous import 'QtQuick.Controls' here"_L1, 8, 8 }, { "Duplicate import '..'"_L1, 12, 8 }, { "Note: previous import '..' here"_L1, 11, 8 } }, - { { "Duplicate import 'QtQml'"_L1, 3, 8 } } }; + { { "Duplicate import 'QtQml'"_L1, 3, 8 } } } + << defaultOptions; QTest::newRow("duplicated id") << QStringLiteral("duplicateId.qml") << Result{ { { "Found a duplicated id. id root was first declared "_L1, 0, 0, - QtCriticalMsg } } }; + QtCriticalMsg } } } + << defaultOptions; QTest::newRow("duplicatedPropertyName") << QStringLiteral("duplicatedPropertyName.qml") << Result{ { { "Duplicated property name \"cat\", \"cat\" is already a property."_L1, 5, - 21 } } }; + 21 } } } + << defaultOptions; QTest::newRow("duplicatedSignalName") << QStringLiteral("duplicatedPropertyName.qml") << Result{ { { "Duplicated signal name \"clicked\", \"clicked\" is already a signal"_L1, - 8, 12 } } }; + 8, 12 } } } + << defaultOptions; QTest::newRow("enumInvalid") << QStringLiteral("enumInvalid.qml") - << Result{ { { "Member \"red\" not found on type \"QtObject\""_L1, 5, 25 }, - { "Member \"red\" not found on type \"QtObject\""_L1, 6, 25 }, - { "Member \"S2\" not found on type \"EnumTesterScoped\""_L1, 8, 38 }, }, + << Result{ { + { "Member \"red\" not found on type \"QtObject\""_L1, 5, 25 }, + { "Member \"red\" not found on type \"QtObject\""_L1, 6, 25 }, + { "Member \"S2\" not found on type \"EnumTesterScoped\""_L1, 8, 38 }, + }, { }, - { { "Did you mean \"U2\"?"_L1, 8 } } }; + { { "Did you mean \"U2\"?"_L1, "U2"_L1, 8, 38 } } } + << defaultOptions; QTest::newRow("enumsAreNotTypes_functionAnnotations") << QStringLiteral("EnumsAreNotTypes_functionAnnotations.qml") << Result{ { { "QML enumerations are not types. Use underlying type " - "(int or double) instead."_L1, 5, 17 }, + "(int or double) instead."_L1, + 5, 17 }, { "QML enumerations are not types. Use underlying type " - "(int or double) instead."_L1, 6, 9 } } }; + "(int or double) instead."_L1, + 6, 9 } } } + << defaultOptions; QTest::newRow("id_in_value_type") << QStringLiteral("idInValueType.qml") - << Result{ { { "id declarations are only allowed in objects"_L1 } } }; + << Result{ { { "id declarations are only allowed in objects"_L1 } } } << defaultOptions; QTest::newRow("inaccessibleId") << QStringLiteral("inaccessibleId.qml") - << Result{ { { "Member \"objectName\" not found on type \"int\""_L1 } } }; + << Result{ { { "Member \"objectName\" not found on type \"int\""_L1 } } } + << defaultOptions; QTest::newRow("inaccessibleId2") << QStringLiteral("inaccessibleId2.qml") - << Result{ { { "Member \"objectName\" not found on type \"int\""_L1 } } }; + << Result{ { { "Member \"objectName\" not found on type \"int\""_L1 } } } + << defaultOptions; QTest::newRow("incompleteQmltypes") << QStringLiteral("incompleteQmltypes.qml") - << Result{ { { "Type \"QPalette\" of property \"palette\" not found"_L1, 5, 26 } } }; + << Result{ { { "Type \"QPalette\" of property \"palette\" not found"_L1, 5, 26 } } } + << defaultOptions; QTest::newRow("incompleteQmltypes2") << QStringLiteral("incompleteQmltypes2.qml") - << Result{ { { "Member \"weDontKnowIt\" not found on type \"CustomPalette\""_L1, - 5, 35 } } }; + << Result{ { { "Type CustomPalette is used but it is not resolved"_L1, 5, 35 } } } + << defaultOptions; QTest::newRow("incompleteQmltypes3") << QStringLiteral("incompleteQmltypes3.qml") - << Result{ { { "Type \"QPalette\" of property \"palette\" not found"_L1, 5, 21 } } }; + << Result{ { { "Type \"QPalette\" of property \"palette\" not found"_L1, 5, 21 } } } + << defaultOptions; QTest::newRow("inheritanceCycle") << QStringLiteral("Cycle1.qml") - << Result{ { { "Cycle1 is part of an inheritance cycle: Cycle2 -> Cycle3 -> " - "Cycle1 -> Cycle2"_L1, - 2, 1 } } }; + << Result{ { { "Cycle1 is part of an inheritance cycle: Cycle1 -> Cycle2 -> " + "Cycle3 -> Cycle1"_L1, + 2, 1 } } } + << defaultOptions; QTest::newRow("inlineComponentNoComponent") << QStringLiteral("inlineComponentNoComponent.qml") - << Result{ { { "Inline component declaration must be followed by a typename"_L1, - 3, 2 } } }; + << Result{ { { "Inline component declaration must be followed by a typename"_L1, 3, + 2 } } } + << defaultOptions; QTest::newRow("inlineComponentSearchInfiniteLoop") << QStringLiteral("InlineComponentSearchInfiniteLoop_Main.qml") << Result{ { { "InlineComponentSearchInfiniteLoop_Other.a was not found. " - "Did you add all imports and dependencies?"_L1, 5, 5 } } }; + "Did you add all imports and dependencies?"_L1, + 5, 5 } } } + << defaultOptions; QTest::newRow("invalidAliasTarget1") << QStringLiteral("invalidAliasTarget.qml") - << Result{ { { "Invalid alias expression - an initializer is needed."_L1, 6, 18 } } }; + << Result{ { { "Invalid alias expression - an initializer is needed."_L1, 6, 18 } } } + << defaultOptions; QTest::newRow("invalidAliasTarget2") << QStringLiteral("invalidAliasTarget.qml") << Result{ { { "Invalid alias expression. Only IDs and field member expressions can " "be aliased"_L1, - 7, 30 } } }; + 7, 30 } } } + << defaultOptions; QTest::newRow("invalidAliasTarget3") << QStringLiteral("invalidAliasTarget.qml") << Result{ { { "Invalid alias expression. Only IDs and field member expressions can be " "aliased"_L1, - 9, 34 } } }; + 9, 34 } } } + << defaultOptions; QTest::newRow("invalidInterceptor") << QStringLiteral("invalidInterceptor.qml") - << Result{ { { "On-binding for property \"angle\" has wrong type \"Item\""_L1 } } }; + << Result{ { { "On-binding for property \"angle\" has wrong type \"Item\""_L1 } } } + << defaultOptions; QTest::newRow("javascriptMethodsInModule") << QStringLiteral("javascriptMethodsInModuleBad.qml") - << Result{ { { "Member \"unknownFunc\" not found on type \"Foo\""_L1, 5, 21 } } }; + << Result{ { { "Member \"unknownFunc\" not found on type \"Foo\""_L1, 5, 21 } } } + << defaultOptions; QTest::newRow("jsVarDeclarationsWriteConst") << QStringLiteral("jsVarDeclarationsWriteConst.qml") - << Result{ { { "Cannot assign to read-only property constProp"_L1 } } }; - QTest::newRow("lintInnerFunctionsToo") - << QStringLiteral("lintInnerFunctionsToo.qml") - << Result{ { { "Unqualified access"_L1, 5, 17 }, - { "Unqualified access"_L1, 6, 23 }, - { "Unqualified access"_L1, 7, 25 }, - { "Unqualified access"_L1, 8, 26 }, - { "Unqualified access"_L1, 11, 17 }, - { "Unqualified access"_L1, 16, 30 }, - { "Unqualified access"_L1, 17, 38 }, - { "Unqualified access"_L1, 18, 35 }, - { "Unqualified access"_L1, 19, 51 } } }; + << Result{ { { "Cannot assign to read-only property constProp"_L1 } } } + << defaultOptions; + QTest::newRow("lintInnerFunctionsToo") << QStringLiteral("lintInnerFunctionsToo.qml") + << Result{ { { "Unqualified access"_L1, 5, 17 }, + { "Unqualified access"_L1, 6, 23 }, + { "Unqualified access"_L1, 7, 25 }, + { "Unqualified access"_L1, 8, 26 }, + { "Unqualified access"_L1, 11, 17 }, + { "Unqualified access"_L1, 16, 30 }, + { "Unqualified access"_L1, 17, 38 }, + { "Unqualified access"_L1, 18, 35 }, + { "Unqualified access"_L1, 19, 51 } } } + << defaultOptions; QTest::newRow("lowerCaseQualifiedImport") << QStringLiteral("lowerCaseQualifiedImport.qml") << Result{ { { "Import qualifier 'test' must start with a capital letter."_L1 }, { "Namespace 'test' of 'test.Rectangle' must start with an upper case " - "letter."_L1 } } }; + "letter."_L1 } } } + << defaultOptions; QTest::newRow("lowerCaseQualifiedImport2") << QStringLiteral("lowerCaseQualifiedImport2.qml") << Result{ { { "Import qualifier 'test' must start with a capital letter."_L1 }, { "Namespace 'test' of 'test.Item' must start with an upper case letter."_L1 }, { "Namespace 'test' of 'test.Rectangle' must start with an upper case letter."_L1 }, { "Namespace 'test' of 'test.color' must start with an upper case letter."_L1 }, - { "Namespace 'test' of 'test.Grid' must start with an upper case letter."_L1 } } }; + { "Namespace 'test' of 'test.Grid' must start with an upper case letter."_L1 } } } + << defaultOptions; QTest::newRow("missingComponentBehaviorBound") << QStringLiteral("missingComponentBehaviorBound.qml") - << Result{ { { "Unqualified access"_L1, 8, 31 } }, - {}, + << Result{ { { "Unqualified access"_L1, 8, 31 } }, + { }, { { "Set \"pragma ComponentBehavior: Bound\" in order to use IDs from " "outer components in nested components."_L1, - 0, 0, QtInfoMsg } }, - Result::AutoFixable }; + "pragma ComponentBehavior: Bound\n"_L1, 0, 0 } }, + Result::AutoFixable } + << defaultOptions; QTest::newRow("missingQmltypes") << QStringLiteral("missingQmltypes.qml") - << Result{ { { "QML types file does not exist"_L1 } } }; + << Result{ { { "QML types file does not exist"_L1 } } } << defaultOptions; QTest::newRow("missingRequiredAlias") << QStringLiteral("missingRequiredAlias.qml") - << Result{ { { "Component is missing required property foo from Item"_L1 } } }; + << Result{ { { "Component is missing required property foo from Item"_L1 } } } + << defaultOptions; QTest::newRow("missingRequiredOnObjectDefinitionBinding") << QStringLiteral("missingRequiredPropertyOnObjectDefinitionBinding.qml") - << Result{ { { uR"(Component is missing required property i from QtObject)"_s, 4, 26 } } }; + << Result{ { { uR"(Component is missing required property i from QtObject)"_s, 4, + 26 } } } + << defaultOptions; QTest::newRow("missingSingletonPragma") << QStringLiteral("missingSingletonPragma.qml") << Result{ { { "Type MissingPragma declared as singleton in qmldir but missing pragma " - "Singleton"_L1 } } }; + "Singleton"_L1 } } } + << defaultOptions; QTest::newRow("missingSingletonQmldir") << QStringLiteral("missingSingletonQmldir.qml") << Result{ { { "Type MissingQmldirSingleton not declared as singleton in qmldir but " - "using pragma Singleton"_L1 } } }; + "using pragma Singleton"_L1 } } } + << defaultOptions; QTest::addRow("multifix") << QStringLiteral("multifix.qml") - << Result{ { { "Unqualified access"_L1, 7, 19, QtWarningMsg }, + << Result{ { { "Unqualified access"_L1, 7, 19, QtWarningMsg }, { "Unqualified access"_L1, 11, 19, QtWarningMsg } }, - {}, - { { "pragma ComponentBehavior: Bound\n"_L1, 1, 1 } }, - { Result::AutoFixable } }; + { }, + { { "Set \"pragma ComponentBehavior: Bound\" in order to use IDs from " + "outer components in nested components."_L1, + "pragma ComponentBehavior: Bound\n"_L1, 1, 1 } }, + { Result::AutoFixable } } + << defaultOptions; QTest::newRow("multilineString") << QStringLiteral("multilineString.qml") - << Result{ { { "String contains unescaped line terminator which is deprecated."_L1, - 0, 0, QtInfoMsg } }, - {}, - { { "`Foo\nmultiline\\`\nstring`"_L1, 4, 32 }, - { "`another\\`\npart\nof it`"_L1, 6, 11 }, - { R"(` + << Result{ { { "String contains unescaped line terminator which is deprecated."_L1, 0, + 0, QtInfoMsg } }, + { }, + { { "Use a template literal instead."_L1, "`Foo\nmultiline\\`\nstring`"_L1, + 4, 32 }, + { "Use a template literal instead."_L1, "`another\\`\npart\nof it`"_L1, 6, + 11 }, + { "Use a template literal instead."_L1, + R"(` quote: " \\" \\\\" ticks: \` \` \\\` \\\` singleTicks: ' \' \\' \\\' -expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, 10, 28 }, - { R"(` +expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, + 10, 28 }, + { "Use a template literal instead."_L1, + R"(` quote: " \" \\" \\\" ticks: \` \` \\\` \\\` singleTicks: ' \\' \\\\' -expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, 16, 27 } }, - { Result::ExitsNormally, Result::AutoFixable } }; +expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, + 16, 27 } }, + { Result::ExitsNormally, Result::AutoFixable } } + << defaultOptions; // The warning should show up only once even though // we have to run the type propagator multiple times. - QTest::newRow("multiplePasses") - << testFile("multiplePasses.qml") - << Result{ { { "Unqualified access"_L1 } } }; - QTest::newRow("nanchors1") - << QStringLiteral("nanchors1.qml") - << Result{ { { "unknown grouped property scope nanchors."_L1 } } }; - QTest::newRow("nanchors2") - << QStringLiteral("nanchors2.qml") - << Result{ { { "unknown grouped property scope nanchors."_L1 } } }; - QTest::newRow("nanchors3") - << QStringLiteral("nanchors3.qml") - << Result{ { { "unknown grouped property scope nanchors."_L1 } } }; + QTest::newRow("multiplePasses") << testFile("multiplePasses.qml") + << Result{ { { "Unqualified access"_L1 } } } << defaultOptions; + QTest::newRow("nanchors1") << QStringLiteral("nanchors1.qml") + << Result{ { { "unknown grouped property scope nanchors."_L1 } } } + << defaultOptions; + QTest::newRow("nanchors2") << QStringLiteral("nanchors2.qml") + << Result{ { { "unknown grouped property scope nanchors."_L1 } } } + << defaultOptions; + QTest::newRow("nanchors3") << QStringLiteral("nanchors3.qml") + << Result{ { { "unknown grouped property scope nanchors."_L1 } } } + << defaultOptions; QTest::newRow("nestedInlineComponents") << QStringLiteral("nestedInlineComponents.qml") - << Result{ { { "Nested inline components are not supported"_L1 } } }; + << Result{ { { "Nested inline components are not supported"_L1 } } } << defaultOptions; QTest::newRow("nonNullStored") << QStringLiteral("nonNullStored.qml") - << Result{ { { "Member \"objectName\" not found on type \"Foozle\""_L1 } }, - { { "Unqualified access"_L1 } } }; + << Result{ { { "Type Foozle is used but it is not resolved"_L1 } }, + { { "Unqualified access"_L1 } } } + << defaultOptions; QTest::newRow("notQmlRootMethods") << QStringLiteral("notQmlRootMethods.qml") - << Result{ { { "Member \"deleteLater\" not found on type \"QtObject\""_L1 }, - { "Member \"destroyed\" not found on type \"QtObject\""_L1 } } }; + << Result{ { { "Member \"deleteLater\" not found on type \"notQmlRootMethods\""_L1 }, + { "Member \"destroyed\" not found on type \"notQmlRootMethods\""_L1 } } } + << defaultOptions; QTest::newRow("nullBinding") << QStringLiteral("nullBinding.qml") - << Result{ { { "Cannot assign literal of type null to double"_L1 } } }; + << Result{ { { "Cannot assign literal of type null to double"_L1 } } } + << defaultOptions; QTest::newRow("parentIsComponent") << QStringLiteral("parentIsComponent.qml") - << Result{ { { "Member \"progress\" not found on type \"QQuickItem\""_L1, 7, 39 } } }; + << Result{ { { "Member \"progress\" not found on type \"QQuickItem\""_L1, 7, 39 } } } + << defaultOptions; QTest::newRow("redundantOptionalChainingEnums") << QStringLiteral("RedundantOptionalChainingEnums.qml") << Result{ { { "Redundant optional chaining for enum lookup"_L1, 5, 54 }, - { "Redundant optional chaining for enum lookup"_L1, 6, 26 } } }; + { "Redundant optional chaining for enum lookup"_L1, 6, 26 } } } + << defaultOptions; QTest::newRow("segFault (bad)") << QStringLiteral("SegFault.bad.qml") - << Result{ { { "Member \"foobar\" not found on type \"QQuickScreenAttached\""_L1 } } }; + << Result{ { { "Member \"foobar\" not found on type \"QQuickScreenAttached\""_L1 } } } + << defaultOptions; { const auto msgGen = [](const QString &name, quint32 line, quint32 col) { return Message{ "Reading non-constant and non-notifiable property %1. Binding might "_L1 @@ -1142,55 +1340,72 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, 16, 27 } }, << Result{ { msgGen("cppStale"_L1, 10, 24), msgGen("cppReadonly"_L1, 11, 24) }, { msgGen("cppConstant"_L1, 14, 24), msgGen("cppNotifiable"_L1, 15, 24), msgGen("cppConstantNotifiable"_L1, 16, 24), msgGen("i"_L1, 17, 24), - msgGen("ro"_L1, 18, 24) } }; + msgGen("ro"_L1, 18, 24) } } + << defaultOptions; } - QTest::newRow("string as id") - << QStringLiteral("stringAsId.qml") - << Result{ { { "ids do not need quotation marks"_L1 } } }; + QTest::newRow("string as id") << QStringLiteral("stringAsId.qml") + << Result{ { { "ids do not need quotation marks"_L1 } } } + << defaultOptions; QTest::newRow("stringIdUsedInWarning") << QStringLiteral("stringIdUsedInWarning.qml") - << Result{ { { "i is a member of a parent element"_L1, } }, - {}, - { { "stringy."_L1 } } }; - QTest::newRow("unboundComponents") - << QStringLiteral("unboundComponents.qml") - << Result{ { { "Unqualified access"_L1, 10, 25 }, - { "Unqualified access"_L1, 14, 33 } } }; + << Result{ { { + "i is a member of a parent element"_L1, + } }, + { }, + { { "i is a member of a parent element.\n " + "You can qualify the access with its id to avoid this warning."_L1, + "stringy."_L1 } } } + << defaultOptions; + QTest::newRow("unboundComponents") << QStringLiteral("unboundComponents.qml") + << Result{ { { "Unqualified access"_L1, 10, 25 }, + { "Unqualified access"_L1, 14, 33 } } } + << defaultOptions; QTest::newRow("unknownTypeCustomParser") << QStringLiteral("unknownTypeCustomParser.qml") - << Result{ { { "TypeDoesNotExist was not found."_L1 } } }; + << Result{ { { "TypeDoesNotExist was not found."_L1 } } } << defaultOptions; QTest::newRow("unresolvedArrayBinding") << QStringLiteral("unresolvedArrayBinding.qml") - << Result{ { { "Declaring an object which is not a Qml object as a list member."_L1 } } }; + << Result{ { { "Declaring an object which is not a Qml object as a list member."_L1 } } } + << defaultOptions; QTest::newRow("unresolvedAttachedType") << QStringLiteral("unresolvedAttachedType.qml") << Result{ { { "unknown attached property scope UnresolvedAttachedType."_L1 } }, - { { "Could not find property \"property\"."_L1 } } }; + { { "Could not find property \"property\"."_L1 } } } + << defaultOptions; QTest::newRow("unresolvedType") << QStringLiteral("unresolvedType.qml") << Result{ { { "UnresolvedType was not found. Did you add all imports and dependencies"_L1 } }, - { { "incompatible type"_L1 } } }; + { { "incompatible type"_L1 } } } + << defaultOptions; // We want to see the warning about the missing type only once. QTest::newRow("unresolvedType2") << QStringLiteral("unresolvedType2.qml") << Result{ { { "QQC2.Label was not found. Did you add all imports and dependencies?"_L1 } }, { { "'QQC2.Label' is used but it is not resolved"_L1 }, - { "Type QQC2.Label is used but it is not resolved"_L1 } } }; + { "Type QQC2.Label is used but it is not resolved"_L1 } } } + << defaultOptions; QTest::newRow("unresolvedTypeAnnotation") << QStringLiteral("unresolvedTypeAnnotations.qml") - << Result{{ - { uR"("A" was not found for the type of parameter "a" in method "f".)"_s, 4, 17 }, - { uR"("B" was not found for the type of parameter "b" in method "f".)"_s, 4, 23 }, - { uR"("R" was not found for the return type of method "g".)"_s, 5, 18 }, - { uR"("C" was not found for the type of parameter "c" in method "h".)"_s, 6, 17 }, - { uR"("R" was not found for the return type of method "h".)"_s, 6, 22 }, - { uR"("D" was not found for the type of parameter "d" in method "i".)"_s, 7, 17 }, - { uR"("G" was not found for the type of parameter "g" in method "i".)"_s, 7, 26 }, - }}; + << Result{ { + { uR"("A" was not found for the type of parameter "a" in method "f".)"_s, 4, + 17 }, + { uR"("B" was not found for the type of parameter "b" in method "f".)"_s, 4, + 23 }, + { uR"("R" was not found for the return type of method "g".)"_s, 5, 18 }, + { uR"("C" was not found for the type of parameter "c" in method "h".)"_s, 6, + 17 }, + { uR"("R" was not found for the return type of method "h".)"_s, 6, 22 }, + { uR"("D" was not found for the type of parameter "d" in method "i".)"_s, 7, + 17 }, + { uR"("G" was not found for the type of parameter "g" in method "i".)"_s, 7, + 26 }, + } } + << defaultOptions; QTest::newRow("jsdeclInQmlScope") << QStringLiteral("jsdeclInQmlScope.qml") - << Result{ { { "JavaScript declarations are not allowed in QML elements"_L1 , 4, 13 } } }; + << Result{ { { "JavaScript declarations are not allowed in QML elements"_L1, 4, 13 } } } + << defaultOptions; QTest::newRow("contextPropertiesFromUser") << u"ContextProperties/qml/MyUserContextProperties.qml"_s @@ -1216,13 +1431,14 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)"_L1, 16, 27 } }, { Message{ "Unqualified access"_L1 }, }, - }; + } << defaultOptions; } void TestQmllint::dirtyQmlCode() { QFETCH(QString, filename); QFETCH(Result, result); + QFETCH(CallQmllintOptions, options); QEXPECT_FAIL("attachedPropertyAccess", "We cannot discern between types and instances", Abort); QEXPECT_FAIL("attachedPropertyNested", "We cannot discern between types and instances", Abort); @@ -1236,7 +1452,6 @@ void TestQmllint::dirtyQmlCode() QEXPECT_FAIL("BadTranslationMixWithMacros", "Translation macros are currently invisible to QQmlJSTypePropagator", Abort); - CallQmllintOptions options; options.readSettings = result.flags.testFlag(Result::UseSettings); const QJsonArray warnings = callQmllint(filename, options, fromResultFlags(result.flags)); @@ -1256,24 +1471,23 @@ void TestQmllint::dirtyQmlCode() QEXPECT_FAIL("badAttachedPropertyNested", "We cannot discern between types and instances", Abort); }, - [] { - QEXPECT_FAIL("autoFixConnectionsBinding", - "We can't autofix the code without the Dom.", Abort); - }); + [] {}); } static void addLocationOffsetTo(TestQmllint::Result *result, qsizetype lineOffset, qsizetype columnOffset = 0) { - for (auto *messages : - { &result->expectedMessages, &result->badMessages, &result->expectedReplacements }) { + auto processMessages = [lineOffset, columnOffset](auto messages) { for (auto &message : *messages) { if (message.line != 0) message.line += lineOffset; if (message.column != 0) message.column += columnOffset; } - } + }; + processMessages(&result->expectedMessages); + processMessages(&result->badMessages); + processMessages(&result->expectedReplacements); } void TestQmllint::dirtyQmlSnippet_data() @@ -1317,7 +1531,7 @@ void TestQmllint::dirtyQmlSnippet_data() << u"property color myColor: \"lbue\""_s << Result{ { { "Invalid color \"lbue\""_L1, 1, 25 } }, {}, - { { "Did you mean \"blue\"?", 1, 25 } } } + { { "Did you mean \"blue\"?", "blue"_L1, 1, 25 } } } << defaultOptions; QTest::newRow("componentExactlyOneChild1") << u"Component { Item {} Item {} }"_s @@ -1527,6 +1741,21 @@ void TestQmllint::dirtyQmlSnippet_data() 1, 11 } } } << defaultOptions; + QTest::newRow("missingTypeAccessMethod") + << u"MyThing { id: bad } Component.onCompleted: console.log(bad.asdf())"_s + << Result{ { { "MyThing was not found"_L1 } }, { { "asdf"_L1 } } } << defaultOptions; + QTest::newRow("missingTypeAccessProperty") + << u"MyThing { id: bad } Component.onCompleted: console.log(bad.asdf)"_s + << Result{ { { "MyThing was not found"_L1 } }, { { "asdf"_L1 } } } << defaultOptions; + QTest::newRow("missingTypeBinding") + << u"MyThing { asdf: 123 }"_s + << Result{ { { "MyThing was not found"_L1 } }, { { "asdf"_L1 } } } << defaultOptions; + QTest::newRow("missingTypeDefaultBinding") + << u"MyThing { Item{} }"_s + << Result{ { { "MyThing was not found"_L1 } }, { { "default"_L1 } } } << defaultOptions; + QTest::newRow("missingTypeObjectBinding") + << u"MyThing { asdf: Item{} }"_s + << Result{ { { "MyThing was not found"_L1 } }, { { "asdf"_L1 } } } << defaultOptions; QTest::newRow("nonRootEnum1") << u"Item { enum E { A, B, C } }"_s << Result{ { { "Enum declared outside the root element. It won't be accessible."_L1, @@ -1549,6 +1778,10 @@ void TestQmllint::dirtyQmlSnippet_data() { "Prefer more specific type string over var", 3, 1 }, { "Prefer more specific type bool over var", 4, 1 } } } << defaultOptions; + QTest::newRow("reservedIdentifier") + << u"property int interface"_s + << Result { { {"Reserved keyword \"interface\" cannot be used as a QML identifier.", 1, 14} } } + << defaultOptions; QTest::newRow("requiredInInlineComponent") << u"Item { component Foo: Item { required property var bla; } } Foo {}"_s << Result{ { { "Component is missing required property bla from Foo"_L1, 1, 61 } } } @@ -1622,6 +1855,10 @@ void TestQmllint::dirtyQmlSnippet_data() u"}"_s << Result{ { { "Component is missing required property r1 from Base"_L1, 6, 1 } } } << defaultOptions; + QTest::newRow("requiredInComponent") + << u"Item { Component { id: comp; required property var bla; Item {} } }"_s + << Result{ { { "Component objects cannot declare new properties."_L1, 1, 30 } } } + << defaultOptions; QTest::newRow("testSnippet") << u"property int qwer: \"Hello\""_s << Result{ { { "Cannot assign literal of type string to int"_L1 } } } @@ -1701,16 +1938,30 @@ void TestQmllint::cleanQmlSnippet_data() << u"function f(a:int, b: int): bool { return a == b; }"_s << defaultOptions; QTest::newRow("equality-with-coercion2") << u"function f(a, b) { return a == null && b == undefined; }"_s << defaultOptions; + QTest::newRow("listAssignment") << u"import TestTypes\n" + "BirthdayParty {\n" + " function f() {\n" + " guests = [];\n" + " }\n" + "}"_s << defaultOptions; QTest::newRow("lowerCaseId") << u"id: root"_s << defaultOptions; + QTest::newRow("importPrecedence") << uR"(import QtQuick + import ModuleWithMenuBar + import QtQuick.Controls + + Item { + width: comp1.custWidth + MenuBar { + id: comp1 + } + })"_s + << defaultOptions; QTest::newRow("preferNonVarProperties_nonReadOnly") << u"property var i: 1 \n"_s u"property var r: 1.0 \n"_s u"property var s: \"s\" \n"_s u"property var b: true \n"_s << defaultOptions; - QTest::newRow("requiredInComponent") - << u"Item { Component { id: comp; required property var bla; Item {} } }"_s - << defaultOptions; QTest::newRow("requiredInComponent2") << u"Item { Component { id: comp; Item { required property var bla } } }"_s << defaultOptions; @@ -1778,6 +2029,29 @@ void TestQmllint::cleanQmlSnippet_data() QTest::newRow("ambiguity-enum-and-chained-attached-property") << u"import EnumList\nFlexboxLayout { direction: FlexboxLayout.Row; }"_s << defaultOptions; + QTest::newRow("ambiguity-enum-and-attached-property") + << u"import QtQuick\n" + "Item {\n" + " id: myItem\n" + " enum MyEnum { Component }\n" + " Item { property var myP: myItem.Component.completed() }\n" + "}"_s + << defaultOptions; + QTest::newRow("ambiguity-enum-and-attached-property2") + << u"import QtQuick\n" + "Item {\n" + " id: myItem\n" + " enum Component { SomeValue }\n" + " Item { property var myP: myItem.Component.completed() }\n" + "}"_s + << defaultOptions; + QTest::newRow("ambiguity-enum-and-attached-property-sanity") + << u"import QtQuick\n" + "Item {\n" + " id: myItem\n" + " Item { property var myP: myItem.Component.completed() }\n" + "}"_s + << defaultOptions; } void TestQmllint::cleanQmlSnippet() @@ -2283,200 +2557,279 @@ void TestQmllint::contextPropertiesFromHeuristicLint() void TestQmllint::cleanQmlCode_data() { QTest::addColumn("filename"); + QTest::addColumn("options"); - QTest::newRow("2Behavior") << QStringLiteral("2behavior.qml"); - QTest::newRow("Accessible") << QStringLiteral("accessible.qml"); - QTest::newRow("AddressableValue") << QStringLiteral("addressableValue.qml"); - QTest::newRow("AttachedProps") << QStringLiteral("AttachedProps.qml"); - QTest::newRow("AttachedType") << QStringLiteral("AttachedType.qml"); - QTest::newRow("BindingBeforeDeclaration") << QStringLiteral("bindingBeforeDeclaration.qml"); - QTest::newRow("BindingTypeMismatch") << QStringLiteral("bindingTypeMismatch.qml"); - QTest::newRow("BindingTypeMismatchFunction") << QStringLiteral("bindingTypeMismatchFunction.qml"); + const CallQmllintOptions defaultOptions; + + QTest::newRow("2Behavior") << QStringLiteral("2behavior.qml") << defaultOptions; + QTest::newRow("Accessible") << QStringLiteral("accessible.qml") << defaultOptions; + QTest::newRow("AddressableValue") << QStringLiteral("addressableValue.qml") << defaultOptions; + QTest::newRow("AttachedProps") << QStringLiteral("AttachedProps.qml") << defaultOptions; + QTest::newRow("AttachedType") << QStringLiteral("AttachedType.qml") << defaultOptions; + QTest::newRow("BindingBeforeDeclaration") + << QStringLiteral("bindingBeforeDeclaration.qml") << defaultOptions; + QTest::newRow("BindingTypeMismatch") + << QStringLiteral("bindingTypeMismatch.qml") << defaultOptions; + QTest::newRow("BindingTypeMismatchFunction") + << QStringLiteral("bindingTypeMismatchFunction.qml") << defaultOptions; QTest::newRow("BindingsOnGroupAndAttachedProperties") - << QStringLiteral("goodBindingsOnGroupAndAttached.qml"); + << QStringLiteral("goodBindingsOnGroupAndAttached.qml") << defaultOptions; QTest::newRow("CustomParserSignalHandler") - << QStringLiteral("customParserSignalHandler.qml"); + << QStringLiteral("customParserSignalHandler.qml") << defaultOptions; QTest::newRow("CustomParserUnqualifiedAccess") - << QStringLiteral("customParserUnqualifiedAccess.qml"); - QTest::newRow("duplicateImportsClean") << QStringLiteral("duplicateImportsClean.qml"); - QTest::newRow("EnumAccess1") << QStringLiteral("EnumAccess1.qml"); - QTest::newRow("EnumAccess2") << QStringLiteral("EnumAccess2.qml"); - QTest::newRow("EnumAccessCpp") << QStringLiteral("EnumAccessCpp.qml"); - QTest::newRow("GoodModulePrefix") << QStringLiteral("goodModulePrefix.qml"); - QTest::newRow("groupedPropertyBinding") << QStringLiteral("groupedPropertyBinding.qml"); - QTest::newRow("ID overrides property") << QStringLiteral("accessibleId.qml"); - QTest::newRow("ImportDirectoryQmldir") << QStringLiteral("Things/LintDirectly.qml"); - QTest::newRow("ImportQMLModule") << QStringLiteral("importQMLModule.qml"); - QTest::newRow("InlineComponent") << QStringLiteral("inlineComponent.qml"); - QTest::newRow("InlineComponentWithComponents") << QStringLiteral("inlineComponentWithComponents.qml"); - QTest::newRow("InlineComponentsChained") << QStringLiteral("inlineComponentsChained.qml"); - QTest::newRow("JS_with_pragma_and_import") << QStringLiteral("QTBUG-45916.js"); - QTest::newRow("ListProperty") << QStringLiteral("ListProperty.qml"); - QTest::newRow("ParentEnum") << QStringLiteral("parentEnum.qml"); - QTest::newRow("QEventPoint") << QStringLiteral("qEventPoint.qml"); - QTest::newRow("QML_importing_JS") << QStringLiteral("importing_js.qml"); - QTest::newRow("QObject.hasOwnProperty") << QStringLiteral("qobjectHasOwnProperty.qml"); - QTest::newRow("QQmlEasingEnums::Type") << QStringLiteral("animationEasing.qml"); - QTest::newRow("QQmlScriptString") << QStringLiteral("scriptstring.qml"); - QTest::newRow("QVariant") << QStringLiteral("qvariant.qml"); - QTest::newRow("QtQuick.Window 2.1") << QStringLiteral("qtquickWindow21.qml"); - QTest::newRow("ScriptInTemplate") << QStringLiteral("scriptInTemplate.qml"); - QTest::newRow("Signals") << QStringLiteral("Signal.qml"); - QTest::newRow("Simple_QML") << QStringLiteral("Simple.qml"); - QTest::newRow("StringToDateTime") << QStringLiteral("stringToDateTime.qml"); - QTest::newRow("Unused imports (multi)") << QStringLiteral("unused_multi.qml"); - QTest::newRow("Unused static module") << QStringLiteral("unused_static.qml"); + << QStringLiteral("customParserUnqualifiedAccess.qml") << defaultOptions; + QTest::newRow("duplicateImportsClean") + << QStringLiteral("duplicateImportsClean.qml") << defaultOptions; + QTest::newRow("EnumAccess1") << QStringLiteral("EnumAccess1.qml") << defaultOptions; + QTest::newRow("EnumAccess2") << QStringLiteral("EnumAccess2.qml") << defaultOptions; + QTest::newRow("EnumAccessCpp") << QStringLiteral("EnumAccessCpp.qml") << defaultOptions; + QTest::newRow("GoodModulePrefix") << QStringLiteral("goodModulePrefix.qml") << defaultOptions; + QTest::newRow("groupedPropertyBinding") + << QStringLiteral("groupedPropertyBinding.qml") << defaultOptions; + QTest::newRow("ID overrides property") << QStringLiteral("accessibleId.qml") << defaultOptions; + QTest::newRow("ImportDirectoryQmldir") + << QStringLiteral("Things/LintDirectly.qml") << defaultOptions; + QTest::newRow("ImportPrecedence") << QStringLiteral("Connections.qml") << defaultOptions; + QTest::newRow("ImportQMLModule") << QStringLiteral("importQMLModule.qml") << defaultOptions; + QTest::newRow("InlineComponent") << QStringLiteral("inlineComponent.qml") << defaultOptions; + QTest::newRow("InlineComponentWithComponents") + << QStringLiteral("inlineComponentWithComponents.qml") << defaultOptions; + QTest::newRow("InlineComponentsChained") + << QStringLiteral("inlineComponentsChained.qml") << defaultOptions; + QTest::newRow("JS_with_pragma_and_import") + << QStringLiteral("QTBUG-45916.js") << defaultOptions; + QTest::newRow("ListProperty") << QStringLiteral("ListProperty.qml") << defaultOptions; + QTest::newRow("ParentEnum") << QStringLiteral("parentEnum.qml") << defaultOptions; + QTest::newRow("QEventPoint") << QStringLiteral("qEventPoint.qml") << defaultOptions; + QTest::newRow("QML_importing_JS") << QStringLiteral("importing_js.qml") << defaultOptions; + QTest::newRow("QObject.hasOwnProperty") + << QStringLiteral("qobjectHasOwnProperty.qml") << defaultOptions; + QTest::newRow("QQmlEasingEnums::Type") + << QStringLiteral("animationEasing.qml") << defaultOptions; + QTest::newRow("QQmlScriptString") << QStringLiteral("scriptstring.qml") << defaultOptions; + QTest::newRow("QVariant") << QStringLiteral("qvariant.qml") << defaultOptions; + QTest::newRow("QtQuick.Window 2.1") << QStringLiteral("qtquickWindow21.qml") << defaultOptions; + QTest::newRow("ScriptInTemplate") << QStringLiteral("scriptInTemplate.qml") << defaultOptions; + QTest::newRow("Signals") << QStringLiteral("Signal.qml") << defaultOptions; + QTest::newRow("Simple_QML") << QStringLiteral("Simple.qml") << defaultOptions; + QTest::newRow("StringToDateTime") << QStringLiteral("stringToDateTime.qml") << defaultOptions; + QTest::newRow("Unused imports (multi)") << QStringLiteral("unused_multi.qml") << defaultOptions; + QTest::newRow("Unused static module") << QStringLiteral("unused_static.qml") << defaultOptions; QTest::newRow("Unversioned change signal without arguments") - << QStringLiteral("unversionChangedSignalSansArguments.qml"); - QTest::newRow("Used imports") << QStringLiteral("used.qml"); - QTest::newRow("ValidLiterals") << QStringLiteral("validLiterals.qml"); - QTest::addRow("ValidTranslations") << u"translations/qsTranslateTranslation.qml"_s; - QTest::addRow("ValidTranslations2") << u"translations/qsTrTranslation.qml"_s; - QTest::addRow("ValidTranslations3") << u"translations/qsTrIdTranslation.qml"_s; - QTest::addRow("ValidTranslations4") << u"translations/Good.qml"_s; - QTest::newRow("VariantMapGetPropertyLookup") << QStringLiteral("variantMapLookup.qml"); - QTest::newRow("WriteListProperty") << QStringLiteral("writeListProperty.qml"); - QTest::newRow("aliasGroup") << QStringLiteral("aliasGroup.qml"); - QTest::newRow("aliasToList") << QStringLiteral("aliasToList.qml"); + << QStringLiteral("unversionChangedSignalSansArguments.qml") << defaultOptions; + QTest::newRow("Used imports") << QStringLiteral("used.qml") << defaultOptions; + QTest::newRow("ValidLiterals") << QStringLiteral("validLiterals.qml") << defaultOptions; + QTest::addRow("ValidTranslations") + << u"translations/qsTranslateTranslation.qml"_s << defaultOptions; + QTest::addRow("ValidTranslations2") << u"translations/qsTrTranslation.qml"_s << defaultOptions; + QTest::addRow("ValidTranslations3") + << u"translations/qsTrIdTranslation.qml"_s << defaultOptions; + QTest::addRow("ValidTranslations4") << u"translations/Good.qml"_s << defaultOptions; + QTest::newRow("VariantMapGetPropertyLookup") + << QStringLiteral("variantMapLookup.qml") << defaultOptions; + QTest::newRow("WriteListProperty") << QStringLiteral("writeListProperty.qml") << defaultOptions; + QTest::newRow("aliasGroup") << QStringLiteral("aliasGroup.qml") << defaultOptions; + QTest::newRow("aliasToList") << QStringLiteral("aliasToList.qml") << defaultOptions; QTest::newRow("aliasToRequiredProperty") - << QStringLiteral("aliasToRequiredPropertyIsNotRequiredItself.qml"); - QTest::newRow("anchors1") << QStringLiteral("anchors1.qml"); - QTest::newRow("anchors2") << QStringLiteral("anchors2.qml"); - QTest::newRow("attached") << QStringLiteral("attached.qml"); - QTest::newRow("attachedImportUse") << QStringLiteral("attachedImportUse.qml"); + << QStringLiteral("aliasToRequiredPropertyIsNotRequiredItself.qml") << defaultOptions; + QTest::newRow("anchors1") << QStringLiteral("anchors1.qml") << defaultOptions; + QTest::newRow("anchors2") << QStringLiteral("anchors2.qml") << defaultOptions; + QTest::newRow("asCast") << QStringLiteral("asCast.qml") << defaultOptions; + QTest::newRow("asValueTypeGood") << QStringLiteral("asValueTypeGood.qml") << defaultOptions; + QTest::newRow("attached") << QStringLiteral("attached.qml") << defaultOptions; + QTest::newRow("attachedImportUse") << QStringLiteral("attachedImportUse.qml") << defaultOptions; QTest::newRow("attachedPropertyAssignments") - << QStringLiteral("attachedPropertyAssignments.qml"); - QTest::newRow("attachedTypeIndirect") << QStringLiteral("attachedTypeIndirect.qml"); - QTest::newRow("boundComponents") << QStringLiteral("boundComponents.qml"); - QTest::newRow("bytearray") << QStringLiteral("bytearray.qml"); - QTest::newRow("callBase") << QStringLiteral("callBase.qml"); - QTest::newRow("callLater") << QStringLiteral("callLater.qml"); - QTest::newRow("catchIdentifier") << QStringLiteral("catchIdentifierNoWarning.qml"); - QTest::newRow("compositeSingleton") << QStringLiteral("compositesingleton.qml"); - QTest::newRow("confusingImport") << QStringLiteral("Dialog.qml"); - QTest::newRow("connectionNoParent") << QStringLiteral("connectionNoParent.qml"); // QTBUG-97600 - QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml"); - QTest::newRow("constructorProperty") << QStringLiteral("constructorProperty.qml"); + << QStringLiteral("attachedPropertyAssignments.qml") << defaultOptions; + QTest::newRow("attachedTypeIndirect") + << QStringLiteral("attachedTypeIndirect.qml") << defaultOptions; + QTest::newRow("boundComponents") << QStringLiteral("boundComponents.qml") << defaultOptions; + QTest::newRow("bytearray") << QStringLiteral("bytearray.qml") << defaultOptions; + QTest::newRow("callBase") << QStringLiteral("callBase.qml") << defaultOptions; + QTest::newRow("callLater") << QStringLiteral("callLater.qml") << defaultOptions; + QTest::newRow("catchIdentifier") + << QStringLiteral("catchIdentifierNoWarning.qml") << defaultOptions; + QTest::newRow("compositeSingleton") + << QStringLiteral("compositesingleton.qml") << defaultOptions; + QTest::newRow("confusingImport") << QStringLiteral("Dialog.qml") << defaultOptions; + QTest::newRow("connectionNoParent") + << QStringLiteral("connectionNoParent.qml") << defaultOptions; // QTBUG-97600 + QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml") << defaultOptions; + QTest::newRow("constructorProperty") + << QStringLiteral("constructorProperty.qml") << defaultOptions; QTest::newRow("cppPropertyChangeHandlers") - << QStringLiteral("goodCppPropertyChangeHandlers.qml"); - QTest::newRow("customParser") << QStringLiteral("customParser.qml"); - QTest::newRow("customParser.recursive") << QStringLiteral("customParser.recursive.qml"); - QTest::addRow("deceptiveLayout") << u"deceptiveLayout.qml"_s; - QTest::newRow("declared property of JS object") << QStringLiteral("bareQt.qml"); - QTest::newRow("defaultImport") << QStringLiteral("defaultImport.qml"); - QTest::newRow("defaultProperty") << QStringLiteral("defaultProperty.qml"); - QTest::newRow("defaultPropertyComponent") << QStringLiteral("defaultPropertyComponent.qml"); - QTest::newRow("defaultPropertyComponent2") << QStringLiteral("defaultPropertyComponent.2.qml"); - QTest::newRow("defaultPropertyList") << QStringLiteral("defaultPropertyList.qml"); - QTest::newRow("defaultPropertyListModel") << QStringLiteral("defaultPropertyListModel.qml"); - QTest::newRow("defaultPropertyVar") << QStringLiteral("defaultPropertyVar.qml"); - QTest::newRow("dependsOnDuplicateType") << QStringLiteral("dependsOnDuplicateType.qml"); - QTest::newRow("deprecatedFunctionOverride") << QStringLiteral("deprecatedFunctionOverride.qml"); - QTest::newRow("dontCheckJSTypes") << QStringLiteral("dontCheckJSTypes.qml"); - QTest::newRow("dontConfuseMemberPrintWithGlobalPrint") << QStringLiteral("findMemberPrint.qml"); - QTest::newRow("duplicateQmldirImport") << QStringLiteral("qmldirImport/duplicate.qml"); - QTest::newRow("enumFromQtQml") << QStringLiteral("enumFromQtQml.qml"); - QTest::newRow("enumList") << QStringLiteral("enumListTest.qml"); - QTest::newRow("enumProperty") << QStringLiteral("enumProperty.qml"); - QTest::newRow("enumsOfScrollBar") << QStringLiteral("enumsOfScrollBar.qml"); - QTest::newRow("esmodule") << QStringLiteral("esmodule.mjs"); - QTest::newRow("externalEnumProperty") << QStringLiteral("externalEnumProperty.qml"); - QTest::newRow("fileSelectorDuplciateImport") << QStringLiteral("FileSelector/main.qml"); - QTest::newRow("forLoop") << QStringLiteral("forLoop.qml"); - QTest::newRow("goodAlias") << QStringLiteral("goodAlias.qml"); - QTest::newRow("goodAliasObject") << QStringLiteral("goodAliasObject.qml"); - QTest::newRow("goodAttachedProperty") << QStringLiteral("goodAttachedProperty.qml"); - QTest::newRow("goodGeneralizedGroup") << QStringLiteral("goodGeneralizedGroup.qml"); - QTest::newRow("goodParent") << QStringLiteral("goodParent.qml"); - QTest::newRow("goodTypeAssertion") << QStringLiteral("goodTypeAssertion.qml"); - QTest::newRow("grouped scope failure") << QStringLiteral("groupedScope.qml"); - QTest::newRow("groupedAttachedLayout") << QStringLiteral("groupedAttachedLayout.qml"); + << QStringLiteral("goodCppPropertyChangeHandlers.qml") << defaultOptions; + QTest::newRow("customParser") << QStringLiteral("customParser.qml") << defaultOptions; + QTest::newRow("customParser.recursive") + << QStringLiteral("customParser.recursive.qml") << defaultOptions; + QTest::addRow("deceptiveLayout") << u"deceptiveLayout.qml"_s << defaultOptions; + QTest::newRow("declared property of JS object") + << QStringLiteral("bareQt.qml") << defaultOptions; + QTest::newRow("defaultImport") << QStringLiteral("defaultImport.qml") << defaultOptions; + QTest::newRow("defaultProperty") << QStringLiteral("defaultProperty.qml") << defaultOptions; + QTest::newRow("defaultPropertyComponent") + << QStringLiteral("defaultPropertyComponent.qml") << defaultOptions; + QTest::newRow("defaultPropertyComponent2") + << QStringLiteral("defaultPropertyComponent.2.qml") << defaultOptions; + QTest::newRow("defaultPropertyList") + << QStringLiteral("defaultPropertyList.qml") << defaultOptions; + QTest::newRow("defaultPropertyListModel") + << QStringLiteral("defaultPropertyListModel.qml") << defaultOptions; + QTest::newRow("defaultPropertyVar") + << QStringLiteral("defaultPropertyVar.qml") << defaultOptions; + QTest::newRow("dependsOnDuplicateType") + << QStringLiteral("dependsOnDuplicateType.qml") << defaultOptions; + QTest::newRow("deprecatedFunctionOverride") + << QStringLiteral("deprecatedFunctionOverride.qml") << defaultOptions; + QTest::newRow("dontCheckJSTypes") << QStringLiteral("dontCheckJSTypes.qml") << defaultOptions; + QTest::newRow("dontConfuseMemberPrintWithGlobalPrint") + << QStringLiteral("findMemberPrint.qml") << defaultOptions; + QTest::newRow("duplicateQmldirImport") + << QStringLiteral("qmldirImport/duplicate.qml") << defaultOptions; + QTest::newRow("enumFromQtQml") << QStringLiteral("enumFromQtQml.qml") << defaultOptions; + QTest::newRow("enumList") << QStringLiteral("enumListTest.qml") << defaultOptions; + QTest::newRow("enumProperty") << QStringLiteral("enumProperty.qml") << defaultOptions; + QTest::newRow("enumsOfScrollBar") << QStringLiteral("enumsOfScrollBar.qml") << defaultOptions; + QTest::newRow("esmodule") << QStringLiteral("esmodule.mjs") << defaultOptions; + QTest::newRow("externalEnumProperty") + << QStringLiteral("externalEnumProperty.qml") << defaultOptions; + QTest::newRow("fileSelectorDuplciateImport") + << QStringLiteral("FileSelector/main.qml") << defaultOptions; + + CallQmllintOptions withResourceFiles = defaultOptions; + withResourceFiles.resources.append(testFile("FileSelector3/resources.qrc")); + QTest::newRow("fileSelectorCmopatibleType") + << QStringLiteral("FileSelector3/App.qml") << withResourceFiles; + QTest::newRow("fileSelectorCompatibleFileSelectedType") + << QStringLiteral("FileSelector3/+Material/App.qml") << withResourceFiles; + + QTest::newRow("forLoop") << QStringLiteral("forLoop.qml") << defaultOptions; + QTest::newRow("goodAlias") << QStringLiteral("goodAlias.qml") << defaultOptions; + QTest::newRow("goodAliasObject") << QStringLiteral("goodAliasObject.qml") << defaultOptions; + QTest::newRow("goodAttachedProperty") + << QStringLiteral("goodAttachedProperty.qml") << defaultOptions; + QTest::newRow("goodGeneralizedGroup") + << QStringLiteral("goodGeneralizedGroup.qml") << defaultOptions; + QTest::newRow("goodParent") << QStringLiteral("goodParent.qml") << defaultOptions; + QTest::newRow("goodTypeAssertion") << QStringLiteral("goodTypeAssertion.qml") << defaultOptions; + QTest::newRow("grouped scope failure") << QStringLiteral("groupedScope.qml") << defaultOptions; + QTest::newRow("groupedAttachedLayout") + << QStringLiteral("groupedAttachedLayout.qml") << defaultOptions; QTest::newRow("groupedProperty (valueSource+interceptor)") - << QStringLiteral("groupedProperty_valueSource_interceptor.qml"); - QTest::newRow("groupedPropertyAssignments") << QStringLiteral("groupedPropertyAssignments.qml"); - QTest::newRow("ignoreWarnings") << QStringLiteral("ignoreWarnings.qml"); - QTest::newRow("importWithPrefix") << QStringLiteral("ImportWithPrefix.qml"); - QTest::newRow("initReadonly") << QStringLiteral("initReadonly.qml"); - QTest::newRow("interceptor") << QStringLiteral("interceptor.qml"); - QTest::newRow("interceptor+valueSource") << QStringLiteral("interceptor_valueSource.qml"); - QTest::newRow("nonConflictingDuplicateBinding.qml") << QStringLiteral("nonConflictingDuplicateBinding.qml"); - QTest::newRow("itemviewattached") << QStringLiteral("itemViewAttached.qml"); + << QStringLiteral("groupedProperty_valueSource_interceptor.qml") << defaultOptions; + QTest::newRow("groupedPropertyAssignments") + << QStringLiteral("groupedPropertyAssignments.qml") << defaultOptions; + QTest::newRow("ignoreWarnings") << QStringLiteral("ignoreWarnings.qml") << defaultOptions; + QTest::newRow("importWithPrefix") << QStringLiteral("ImportWithPrefix.qml") << defaultOptions; + QTest::newRow("initReadonly") << QStringLiteral("initReadonly.qml") << defaultOptions; + QTest::newRow("interceptor") << QStringLiteral("interceptor.qml") << defaultOptions; + QTest::newRow("interceptor+valueSource") + << QStringLiteral("interceptor_valueSource.qml") << defaultOptions; + QTest::newRow("nonConflictingDuplicateBinding.qml") + << QStringLiteral("nonConflictingDuplicateBinding.qml") << defaultOptions; + QTest::newRow("itemviewattached") << QStringLiteral("itemViewAttached.qml") << defaultOptions; QTest::newRow("javascriptMethodsInModule") - << QStringLiteral("javascriptMethodsInModuleGood.qml"); - QTest::newRow("jsLibrary") << QStringLiteral("jsLibrary.qml"); - QTest::newRow("jsVarDeclarations") << QStringLiteral("jsVarDeclarations.qml"); - QTest::newRow("jsmoduleimport") << QStringLiteral("jsmoduleimport.qml"); - QTest::newRow("jsonArrayIsRecognized") << QStringLiteral("jsonArrayIsRecognized.qml"); - QTest::newRow("jsonObjectIsRecognized") << QStringLiteral("jsonObjectIsRecognized.qml"); - QTest::newRow("layouts depends quick") << QStringLiteral("layouts.qml"); - QTest::newRow("listPropertyMethods") << QStringLiteral("listPropertyMethods.qml"); - QTest::newRow("locale") << QStringLiteral("locale.qml"); - QTest::newRow("matchByName") << QStringLiteral("matchByName.qml"); - QTest::newRow("methodInScope") << QStringLiteral("MethodInScope.qml"); - QTest::newRow("methodsInJavascript") << QStringLiteral("javascriptMethods.qml"); - QTest::newRow("multiDefaultProperty") << QStringLiteral("multiDefaultPropertyOk.qml"); - QTest::newRow("multiExtension") << QStringLiteral("multiExtension.qml"); - QTest::newRow("multilineStringEscaped") << QStringLiteral("multilineStringEscaped.qml"); - QTest::newRow("noMissingNotify") << QStringLiteral("noMissingNotify.qml"); - QTest::newRow("noWarningBindableOnly") << QStringLiteral("noWarningBindableOnly.qml"); - QTest::newRow("nullBindingFunction") << QStringLiteral("nullBindingFunction.qml"); - QTest::newRow("objectArray") << QStringLiteral("objectArray.qml"); - QTest::newRow("objectBindingOnVarProperty") << QStringLiteral("objectBoundToVar.qml"); - QTest::newRow("on binding in grouped property") << QStringLiteral("onBindingInGroupedProperty.qml"); - QTest::newRow("onlyMajorVersion") << QStringLiteral("onlyMajorVersion.qml"); - QTest::newRow("optionalChainingCall") << QStringLiteral("optionalChainingCall.qml"); + << QStringLiteral("javascriptMethodsInModuleGood.qml") << defaultOptions; + QTest::newRow("jsLibrary") << QStringLiteral("jsLibrary.qml") << defaultOptions; + QTest::newRow("jsVarDeclarations") << QStringLiteral("jsVarDeclarations.qml") << defaultOptions; + QTest::newRow("jsmoduleimport") << QStringLiteral("jsmoduleimport.qml") << defaultOptions; + QTest::newRow("jsonArrayIsRecognized") + << QStringLiteral("jsonArrayIsRecognized.qml") << defaultOptions; + QTest::newRow("jsonObjectIsRecognized") + << QStringLiteral("jsonObjectIsRecognized.qml") << defaultOptions; + QTest::newRow("layouts depends quick") << QStringLiteral("layouts.qml") << defaultOptions; + QTest::newRow("listPropertyMethods") + << QStringLiteral("listPropertyMethods.qml") << defaultOptions; + QTest::newRow("locale") << QStringLiteral("locale.qml") << defaultOptions; + QTest::newRow("matchByName") << QStringLiteral("matchByName.qml") << defaultOptions; + QTest::newRow("methodInScope") << QStringLiteral("MethodInScope.qml") << defaultOptions; + QTest::newRow("methodsInJavascript") + << QStringLiteral("javascriptMethods.qml") << defaultOptions; + QTest::newRow("multiDefaultProperty") + << QStringLiteral("multiDefaultPropertyOk.qml") << defaultOptions; + QTest::newRow("multiExtension") << QStringLiteral("multiExtension.qml") << defaultOptions; + QTest::newRow("multilineStringEscaped") + << QStringLiteral("multilineStringEscaped.qml") << defaultOptions; + QTest::newRow("noMissingNotify") << QStringLiteral("noMissingNotify.qml") << defaultOptions; + QTest::newRow("noWarningBindableOnly") + << QStringLiteral("noWarningBindableOnly.qml") << defaultOptions; + QTest::newRow("nullBindingFunction") + << QStringLiteral("nullBindingFunction.qml") << defaultOptions; + QTest::newRow("objectArray") << QStringLiteral("objectArray.qml") << defaultOptions; + QTest::newRow("objectBindingOnVarProperty") + << QStringLiteral("objectBoundToVar.qml") << defaultOptions; + QTest::newRow("on binding in grouped property") + << QStringLiteral("onBindingInGroupedProperty.qml") << defaultOptions; + QTest::newRow("onlyMajorVersion") << QStringLiteral("onlyMajorVersion.qml") << defaultOptions; + QTest::newRow("optionalChainingCall") + << QStringLiteral("optionalChainingCall.qml") << defaultOptions; #ifdef HAS_QC_BASIC - QTest::newRow("overlay") << QStringLiteral("overlayFromControls.qml"); + QTest::newRow("overlay") << QStringLiteral("overlayFromControls.qml") << defaultOptions; #endif - QTest::newRow("overridescript") << QStringLiteral("overridescript.qml"); - QTest::newRow("prefixedAttachedProperty") << QStringLiteral("prefixedAttachedProperty.qml"); - QTest::newRow("propertyBindingValue") << QStringLiteral("propertyBindingValue.qml"); - QTest::newRow("propertyDelegate") << QStringLiteral("propertyDelegate.qml"); - QTest::newRow("propertyMapUsage") << QStringLiteral("propertyMapUsage.qml"); - QTest::newRow("propertyOverride") << QStringLiteral("propertyOverride.qml"); - QTest::newRow("propertyWithOn") << QStringLiteral("switcher.qml"); - QTest::newRow("qjsroot") << QStringLiteral("qjsroot.qml"); - QTest::newRow("qmlRootMethods") << QStringLiteral("qmlRootMethods.qml"); - QTest::newRow("qmldirAndQmltypes") << QStringLiteral("qmldirAndQmltypes.qml"); - QTest::newRow("qmldirImportAndDepend") << QStringLiteral("qmldirImportAndDepend/good.qml"); - QTest::newRow("qmodelIndex") << QStringLiteral("qmodelIndex.qml"); - QTest::newRow("qtquickdialog") << QStringLiteral("qtquickdialog.qml"); - QTest::newRow("qualifiedAttached") << QStringLiteral("Drawer.qml"); - QTest::addRow("regExp") << u"regExp.qml"_s; - QTest::newRow("requiredPropertyInGroupedPropertyScope") << QStringLiteral("requiredPropertyInGroupedPropertyScope.qml"); - QTest::newRow("requiredPropertySetViaOnBinding") << QStringLiteral("requiredPropertySetViaOnBinding.qml"); - QTest::newRow("RequiredPropertyBaseWithAlias") << QStringLiteral("RequiredPropertyBaseWithAlias.qml"); - QTest::newRow("requiredWithRootLevelAlias") << QStringLiteral("RequiredWithRootLevelAlias.qml"); - QTest::newRow("required_property_in_Component") << QStringLiteral("requiredPropertyInComponent.qml"); - QTest::newRow("retrieveFunction") << QStringLiteral("retrieveFunction.qml"); - QTest::newRow("scopedAndUnscopedEnums") << QStringLiteral("enumValid.qml"); - QTest::newRow("segFault") << QStringLiteral("SegFault.qml"); - QTest::newRow("selfReferential") << QStringLiteral("SelfReferential.qml"); - QTest::newRow("setRequiredTroughAlias") << QStringLiteral("setRequiredPropertyThroughAlias.qml"); + QTest::newRow("overridescript") << QStringLiteral("overridescript.qml") << defaultOptions; + QTest::newRow("prefixedAttachedProperty") + << QStringLiteral("prefixedAttachedProperty.qml") << defaultOptions; + QTest::newRow("propertyBindingValue") + << QStringLiteral("propertyBindingValue.qml") << defaultOptions; + QTest::newRow("propertyDelegate") << QStringLiteral("propertyDelegate.qml") << defaultOptions; + QTest::newRow("propertyMapUsage") << QStringLiteral("propertyMapUsage.qml") << defaultOptions; + QTest::newRow("propertyOverride") << QStringLiteral("propertyOverride.qml") << defaultOptions; + QTest::newRow("propertyWithOn") << QStringLiteral("switcher.qml") << defaultOptions; + QTest::newRow("qjsroot") << QStringLiteral("qjsroot.qml") << defaultOptions; + QTest::newRow("qmlRootMethods") << QStringLiteral("qmlRootMethods.qml") << defaultOptions; + QTest::newRow("qmldirAndQmltypes") << QStringLiteral("qmldirAndQmltypes.qml") << defaultOptions; + QTest::newRow("qmldirImportAndDepend") + << QStringLiteral("qmldirImportAndDepend/good.qml") << defaultOptions; + QTest::newRow("qmodelIndex") << QStringLiteral("qmodelIndex.qml") << defaultOptions; + QTest::newRow("qtquickdialog") << QStringLiteral("qtquickdialog.qml") << defaultOptions; + QTest::newRow("qualifiedAttached") << QStringLiteral("Drawer.qml") << defaultOptions; + QTest::addRow("regExp") << u"regExp.qml"_s << defaultOptions; + QTest::newRow("requiredPropertyInGroupedPropertyScope") + << QStringLiteral("requiredPropertyInGroupedPropertyScope.qml") << defaultOptions; + QTest::newRow("requiredPropertySetViaOnBinding") + << QStringLiteral("requiredPropertySetViaOnBinding.qml") << defaultOptions; + QTest::newRow("RequiredPropertyBaseWithAlias") + << QStringLiteral("RequiredPropertyBaseWithAlias.qml") << defaultOptions; + QTest::newRow("requiredWithRootLevelAlias") + << QStringLiteral("RequiredWithRootLevelAlias.qml") << defaultOptions; + QTest::newRow("required_property_in_Component") + << QStringLiteral("requiredPropertyInComponent.qml") << defaultOptions; + QTest::newRow("retrieveFunction") << QStringLiteral("retrieveFunction.qml") << defaultOptions; + QTest::newRow("scopedAndUnscopedEnums") << QStringLiteral("enumValid.qml") << defaultOptions; + QTest::newRow("segFault") << QStringLiteral("SegFault.qml") << defaultOptions; + QTest::newRow("selfReferential") << QStringLiteral("SelfReferential.qml") << defaultOptions; + QTest::newRow("setRequiredTroughAlias") + << QStringLiteral("setRequiredPropertyThroughAlias.qml") << defaultOptions; QTest::newRow("setRequiredTroughAliasOfAlias") - << QStringLiteral("setRequiredPropertyThroughAliasOfAlias.qml"); - QTest::newRow("shapes") << QStringLiteral("shapes.qml"); - QTest::newRow("singleElementAssignedToList") << QStringLiteral("singleElementAssignedToList.qml"); - QTest::newRow("stringLength") << QStringLiteral("stringLength.qml"); - QTest::newRow("stringLength2") << QStringLiteral("stringLength2.qml"); - QTest::newRow("stringLength3") << QStringLiteral("stringLength3.qml"); - QTest::newRow("stringToByteArray") << QStringLiteral("stringToByteArray.qml"); - QTest::newRow("template literal (substitution)") << QStringLiteral("templateStringSubstitution.qml"); - QTest::newRow("thisObject") << QStringLiteral("thisObject.qml"); - QTest::newRow("uiQml") << QStringLiteral("FormUser.qml"); - QTest::newRow("unexportedCppBase") << QStringLiteral("unexportedCppBase.qml"); - QTest::newRow("unknownBuiltinFont") << QStringLiteral("ButtonLoader.qml"); - QTest::newRow("unknownPropertyDuplicateBinding.qml") << QStringLiteral("unknownPropertyDuplicateBinding.qml"); - QTest::newRow("unnotifiableReadOutsideBinding") << QStringLiteral("unnotifiableReadOutsideBinding.qml"); - QTest::newRow("v4SequenceMethods") << QStringLiteral("v4SequenceMethods.qml"); - QTest::newRow("valueSource") << QStringLiteral("valueSource.qml"); - QTest::newRow("var") << QStringLiteral("var.qml"); + << QStringLiteral("setRequiredPropertyThroughAliasOfAlias.qml") << defaultOptions; + QTest::newRow("shapes") << QStringLiteral("shapes.qml") << defaultOptions; + QTest::newRow("singleElementAssignedToList") + << QStringLiteral("singleElementAssignedToList.qml") << defaultOptions; + QTest::newRow("stringLength") << QStringLiteral("stringLength.qml") << defaultOptions; + QTest::newRow("stringLength2") << QStringLiteral("stringLength2.qml") << defaultOptions; + QTest::newRow("stringLength3") << QStringLiteral("stringLength3.qml") << defaultOptions; + QTest::newRow("stringToByteArray") << QStringLiteral("stringToByteArray.qml") << defaultOptions; + QTest::newRow("template literal (substitution)") + << QStringLiteral("templateStringSubstitution.qml") << defaultOptions; + QTest::newRow("thisObject") << QStringLiteral("thisObject.qml") << defaultOptions; + QTest::newRow("uiQml") << QStringLiteral("FormUser.qml") << defaultOptions; + QTest::newRow("unexportedCppBase") << QStringLiteral("unexportedCppBase.qml") << defaultOptions; + QTest::newRow("unknownBuiltinFont") << QStringLiteral("ButtonLoader.qml") << defaultOptions; + QTest::newRow("unknownPropertyDuplicateBinding.qml") + << QStringLiteral("unknownPropertyDuplicateBinding.qml") << defaultOptions; + QTest::newRow("unnotifiableReadOutsideBinding") + << QStringLiteral("unnotifiableReadOutsideBinding.qml") << defaultOptions; + QTest::newRow("v4SequenceMethods") << QStringLiteral("v4SequenceMethods.qml") << defaultOptions; + QTest::newRow("valueSource") << QStringLiteral("valueSource.qml") << defaultOptions; + QTest::newRow("var") << QStringLiteral("var.qml") << defaultOptions; } void TestQmllint::cleanQmlCode() { QFETCH(QString, filename); - runTest(filename, Result::clean()); + QFETCH(CallQmllintOptions, options); + + const QJsonArray warnings = callQmllint(filename, options); + checkResult(warnings, Result::clean()); } void TestQmllint::compilerWarnings_data() @@ -2534,8 +2887,7 @@ void TestQmllint::compilerWarnings_data() QTest::newRow("returnTypeAnnotation-component") << QStringLiteral("returnTypeAnnotation_component.qml") << Result{ { { "Could not compile function comp: function without return type " - "annotation returns (component in" }, - { "returnTypeAnnotation_component.qml)::c with type Comp. " + "annotation returns returnTypeAnnotation_component::c with type Comp. " "This may prevent proper compilation to Cpp." } } } << true; QTest::newRow("returnTypeAnnotation-enum") @@ -2547,15 +2899,14 @@ void TestQmllint::compilerWarnings_data() QTest::newRow("returnTypeAnnotation-method") << QStringLiteral("returnTypeAnnotation_method.qml") << Result{ { { "Could not compile function method: function without return type " - "annotation returns (component in " }, // Don't check the build folder path - { "returnTypeAnnotation_method.qml)::f(...). This may " + "annotation returns returnTypeAnnotation_method::f(...). This may " "prevent proper compilation to Cpp." } } } << true; QTest::newRow("returnTypeAnnotation-property") << QStringLiteral("returnTypeAnnotation_property.qml") << Result{ { { "Could not compile function prop: function without return type " - "annotation returns (component in " }, // Don't check the build folder path - { "returnTypeAnnotation_property.qml)::i with type int. This may prevent " + "annotation returns returnTypeAnnotation_property::i with type int. " + "This may prevent " "proper compilation to Cpp." } } } << true; QTest::newRow("returnTypeAnnotation-type") @@ -2940,10 +3291,10 @@ void TestQmllint::checkResult(const QJsonArray &warnings, const Result &result, searchWarnings(warnings, msg.text, msg.severity, msg.line, msg.column, StringNotContained); } - for (const Message &replacement : result.expectedReplacements) { + for (const FixMessage &replacement : result.expectedReplacements) { onReplacementFailures(); - searchWarnings(warnings, replacement.text, replacement.severity, replacement.line, - replacement.column, StringContained, DoReplacementSearch); + searchReplacements(warnings, replacement.text, replacement.replacement, replacement.line, + replacement.column); } // check for duplicates @@ -2960,9 +3311,63 @@ void TestQmllint::checkResult(const QJsonArray &warnings, const Result &result, QVERIFY2(firstDuplicate == sortedWarnings.constEnd(), "Found duplicate warnings!"); } +static bool warningsContainReplacement(const QJsonArray &warnings, const QString &substring, + const QString &replacementSubString, quint32 line, + quint32 column) +{ + for (const QJsonValueConstRef warningJson : warnings) { + for (const QJsonValueConstRef fix : warningJson[u"suggestions"].toArray()) { + QString replacement = fix[u"replacement"].toString(); +#ifdef Q_OS_WIN + // Replacements can contain native line endings + // but we need them to be uniform in order for them to conform to our test data + replacement = replacement.replace(u"\r\n"_s, u"\n"_s); +#endif + if (replacement != replacementSubString) + continue; + + if (!fix[u"message"].toString().contains(substring)) + continue; + + const quint32 fixLine = fix[u"line"].toInt(); + const quint32 fixColumn = fix[u"column"].toInt(); + if (line != 0 || column != 0) { + if (fixLine != line || fixColumn != column) { + continue; + } + } + return true; + } + } + return false; +} + +void TestQmllint::searchReplacements(const QJsonArray &warnings, const QString &substring, + const QString &replacementSubString, quint32 line, quint32 column) +{ + const bool contains = + warningsContainReplacement(warnings, substring, replacementSubString, line, column); + + const auto toDescription = [](const QJsonArray &warnings, const QString &substring, + quint32 line, quint32 column) { + const auto indentedJson = + QString::fromUtf8(QJsonDocument(warnings).toJson(QJsonDocument::Indented)); + QString msg = QStringLiteral("qmllint output:\n%1\nIt must contain replacement '%2'") + .arg(indentedJson, substring); + if (line != 0 || column != 0) + msg += u" (%1:%2)"_s.arg(line).arg(column); + + return msg; + }; + + if (!contains) + qWarning().noquote() << toDescription(warnings, substring, line, column); + QVERIFY(contains); +} + void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &substring, QtMsgType type, quint32 line, quint32 column, - ContainOption shouldContain, ReplacementOption searchReplacements) + ContainOption shouldContain) { bool contains = false; @@ -2989,27 +3394,6 @@ void TestQmllint::searchWarnings(const QJsonArray &warnings, const QString &subs contains = true; break; } - - if (searchReplacements == DoReplacementSearch) { - QString replacement = fix[u"replacement"].toString(); -#ifdef Q_OS_WIN - // Replacements can contain native line endings - // but we need them to be uniform in order for them to conform to our test data - replacement = replacement.replace(u"\r\n"_s, u"\n"_s); -#endif - - if (replacement.contains(substring)) { - quint32 fixLine = fix[u"line"].toInt(); - quint32 fixColumn = fix[u"column"].toInt(); - if (line != 0 || column != 0) { - if (fixLine != line || fixColumn != column) { - continue; - } - } - contains = true; - break; - } - } } } @@ -3143,7 +3527,7 @@ void TestQmllint::attachedPropertyReuse() "Using attached type MyStyle already initialized in a parent scope"_L1, 10, 16 } }, {}, - { Message{ "Reference it by id instead"_L1, 10, 16 } }, + { { "Reference it by id instead"_L1, "control."_L1, 10, 16 } }, Result::AutoFixable }, {}, {}, {}, UseDefaultImports, &categories); runTest("pluginQuick_multipleAttachedPropertyReuse.qml", @@ -3222,6 +3606,19 @@ void TestQmllint::lintModule_data() testFile("hidden/moduleWithQrc_raw_qml_0.qrc") }) << Result::clean(); + QTest::newRow(("ImportFileSelector")) + << QStringLiteral("FileSelector") << QStringList() << QStringList() + << Result{ + { { "Ambiguous type detected. ToolBar 1.0 is defined multiple times."_L1 } } + }.withFlags(Result::Flags(Result::UseSettings)); + QTest::newRow(("ImportFileSelector2")) + << QStringLiteral("FileSelector2") << QStringList() << QStringList() + << Result{ + { { "Ambiguous type detected. ToolBar 1.0 is defined multiple times."_L1 }, + { "Ambiguous type detected. Broken 1.0 is defined multiple times."_L1 } }, + { { "Type ToolBar is ambiguous due to file selector usage, ignoring %1"_L1.arg( + testFile("FileSelector2/+Material/ToolBar.qml")) } } + }.withFlags(Result::Flags(Result::UseSettings)); } void TestQmllint::lintModule() @@ -3292,17 +3689,24 @@ void TestQmllint::valueTypesFromString() u"Construction from string is deprecated. Use structured value type construction instead for type \"QMatrix4x4\""_s }, }, { /*bad messages */ }, - { - Message{ u"({ width: 30, height: 50 })"_s }, - Message{ u"({ x: 10, y: 20, width: 30, height: 50 })"_s }, - Message{ u"({ x: 30, y: 50 })"_s }, - Message{ u"({ x: 1, y: 2 })"_s }, - Message{ u"({ x: 1, y: 2 })"_s }, - Message{ u"({ x: 1, y: 2, z: 3 })"_s }, - Message{ u"({ x: 1, y: 2, z: 3, w: 4 })"_s }, - Message{ u"({ scalar: 1, x: 2, y: 3, z: 4 })"_s }, - Message{ - u"({ m11: 1, m12: 2, m13: 3, m14: 4, m21: 5, m22: 6, m23: 7, m24: 8, m31: 9, m32: 10, m33: 11, m34: 12, m41: 13, m42: 14, m43: 15, m44: 16 })"_s }, + { { u"Replace string by structured value construction"_s, + u"({ width: 30, height: 50 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 10, y: 20, width: 30, height: 50 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 30, y: 50 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 1, y: 2 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 1, y: 2 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 1, y: 2, z: 3 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ x: 1, y: 2, z: 3, w: 4 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ scalar: 1, x: 2, y: 3, z: 4 })"_s }, + { u"Replace string by structured value construction"_s, + u"({ m11: 1, m12: 2, m13: 3, m14: 4, m21: 5, m22: 6, m23: 7, m24: 8, m31: 9, m32: 10, m33: 11, m34: 12, m41: 13, m42: 14, m43: 15, m44: 16 })"_s }, } }); } @@ -4143,9 +4547,9 @@ void TestQmllint::crashes() checkResult( warnings, - Result{ { Message{ - u"FooBar was not found. Did you add all imports and dependencies?"_s }, - Message{ u"Cannot assign to non-existent default property"_s } } }); + Result{ { + Message{ u"FooBar was not found. Did you add all imports and dependencies?"_s }, + } }); } QTEST_GUILESS_MAIN(TestQmllint) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/CMakeLists.txt index b16307afef..8b8fca6f14 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/CMakeLists.txt @@ -146,6 +146,8 @@ set(qml_sources attachedNamespacedProperty.qml newLineTranslation.qml + + typedMethodCallOnIdLookup.qml ) set(js_sources diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/typedMethodCallOnIdLookup.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/typedMethodCallOnIdLookup.qml new file mode 100644 index 0000000000..8e6249b929 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/QmltcTests/typedMethodCallOnIdLookup.qml @@ -0,0 +1,17 @@ +pragma Strict +import QtQml + +QtObject { + id: self + + property string hello: "Hello" + property string result + + function greet(prefix: string, suffix: string) : string { + return prefix + self.hello + suffix + } + + Component.onCompleted: { + self.result = self.greet("[", "]") + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.cpp index ce3d25973a..cfa3bf861c 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -105,6 +105,7 @@ #include "newlinetranslation.h" #include "mymatryoshkaitems.h" +#include "typedmethodcallonidlookup.h" // Qt: #include @@ -220,6 +221,7 @@ void tst_qmltc::initTestCase() QUrl(u"qrc:/qt/qml/QmltcTests/valueTypeListProperty.qml"_s), QUrl(u"qrc:/qt/qml/QmltcTests/appendToQQmlListProperty.qml"_s), QUrl(u"qrc:/qt/qml/QmltcTests/requiredProperties.qml"_s), + QUrl(u"qrc:/qt/qml/QmltcTests/typedMethodCallOnIdLookup.qml"_s), }; QQmlEngine e; @@ -928,9 +930,8 @@ void tst_qmltc::requiredPropertiesInitialization() &e, { aliasToInnerThatWillBeMarkedRequired, - // QTBUG-131777 - //aliasToPropertyThatShadows, - //aliasToRequiredInner, + aliasToPropertyThatShadows, + aliasToRequiredInner, &inheritedRequiredProperty, nonRequiredInheritedPropertyThatWillBeMarkedRequired, objectList, @@ -943,9 +944,7 @@ void tst_qmltc::requiredPropertiesInitialization() ); QCOMPARE(created.aliasToInnerThatWillBeMarkedRequired(), aliasToInnerThatWillBeMarkedRequired); - QEXPECT_FAIL("", "QTBUG-131777", Continue); QCOMPARE(created.aliasToPropertyThatShadows(), aliasToPropertyThatShadows); - QEXPECT_FAIL("", "QTBUG-131777", Continue); QCOMPARE(created.aliasToRequiredInner(), aliasToRequiredInner); QCOMPARE(created.getInheritedRequiredProperty(), &inheritedRequiredProperty); QCOMPARE(created.getNonRequiredInheritedPropertyThatWillBeMarkedRequired(), nonRequiredInheritedPropertyThatWillBeMarkedRequired); @@ -3481,4 +3480,11 @@ void tst_qmltc::nestedWithId() QVERIFY(!qobject_cast(inner)); } +void tst_qmltc::typedMethodCallOnIdLookup() +{ + QQmlEngine e; + PREPEND_NAMESPACE(typedMethodCallOnIdLookup) created(&e); + QCOMPARE(created.property("result").toString(), u"[Hello]"_s); +} + QTEST_MAIN(tst_qmltc) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.h index 8708292d6c..fedeeeba5b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc/tst_qmltc.h @@ -119,4 +119,6 @@ private slots: void newLineTranslation(); void nestedWithId(); + + void typedMethodCallOnIdLookup(); }; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp index fa6f0499fe..8bc12d5053 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp @@ -334,11 +334,10 @@ void tst_qmltc_qprocess::unboundRequiredPropertyInInlineComponent() void tst_qmltc_qprocess::componentDefinitionInnerRequiredProperty() { { - QEXPECT_FAIL("", "QTBUG-131777", Continue); - QFAIL("QTBUG-131777"); const auto errors = runQmltc(u"componentDefinitionInnerRequiredProperty.qml"_s, false); QVERIFY(errors.contains( - u"componentDefinitionInnerRequiredProperty.qml:11:13: Component is missing required property bar from here [required]" + u"componentDefinitionInnerRequiredProperty.qml:11:13: " + "Component is missing required property bar from Rectangle [required]" )); } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 66725e3298..286bbcb9b0 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -514,13 +514,17 @@ void tst_QQmlDelegateModel::typedModelData() for (int i = 0; i < 4; ++i) { if (i == 0) { - for (int j = 0; j < 3; ++j) { + for (int j = 0; j < 2; ++j) { QTest::ignoreMessage( QtWarningMsg, "Could not find any constructor for value type QQmlPointFValueType " "to call with value QVariant(double, 11)"); } + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlPointFValueType " + "to call with value 11"); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + ":62:9: Unable to assign double to QPointF")); @@ -650,8 +654,6 @@ void tst_QQmlDelegateModel::nestedRequired() qPrintable(url.toString() + QLatin1String(":13:9: Required property control was not initialized"))); QObject *delegate5 = delegateModel3->object(0); - - QEXPECT_FAIL("", "object should not be created with required property unset", Continue); QVERIFY(!delegate5); } @@ -695,13 +697,27 @@ void tst_QQmlDelegateModel::overriddenModelData() QObject *delegate = delegateModel->object(0); QVERIFY(delegate); - if (i == 1 || i == 2) { - // You can actually not override if the model is a QObject or a JavaScript array. - // Someone is certainly relying on this. - // We need to find a migration mechanism to fix it. - QCOMPARE(delegate->objectName(), QLatin1String(" 0 0 e 0")); - } else { + // You can actually not override if the model is a QObject or a JavaScript array. + // Someone is certainly relying on this. + // We need to find a migration mechanism to fix it. + switch (i) { + case 1: { + static const QRegularExpression expectation( + "\\[object V4ReferenceObject\\] 0 0 " + "QQmlDMListAccessorData\\(0x[0-9a-f]+\\) e 0"); + QVERIFY(expectation.match(delegate->objectName()).hasMatch()); + break; + } + case 2: { + static const QRegularExpression expectation( + "QObject_QML_[0-9]+\\(0x[0-9a-f]+\\) 0 0 " + "QQmlDMObjectData\\(0x[0-9a-f]+\\) e 0"); + QVERIFY(expectation.match(delegate->objectName()).hasMatch()); + break; + } + default: QCOMPARE(delegate->objectName(), QLatin1String("a b c d e f")); + break; } } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/ownModuleName.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/HelloModule/ownModuleName.qml similarity index 100% rename from local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/ownModuleName.qml rename to local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/HelloModule/ownModuleName.qml diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/HelloModule/qmldir b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/HelloModule/qmldir new file mode 100644 index 0000000000..6cbbdc5725 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/HelloModule/qmldir @@ -0,0 +1,2 @@ +module HelloModule +ownModuleName 254.0 ownModuleName.qml \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/componentWrappedObjects.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/componentWrappedObjects.qml index 9483949e02..006fba4f1e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/componentWrappedObjects.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/data/componentWrappedObjects.qml @@ -1,22 +1,22 @@ import QtQuick import Qt.labs.qmlmodels Item { - // Note: use properties instead of ids to uniquely identify types - property Component nonWrapped1: Component { property int nonWrapped1; Rectangle {} } - property Component nonWrapped2: ComponentType { property int nonWrapped2 } - property Component nonWrapped3: DelegateChooser { property int nonWrapped3 } + // Note: use objectName instead of ids to uniquely identify types + property Component nonWrapped1: Component { objectName: "nonWrapped1"; Rectangle {} } + property Component nonWrapped2: ComponentType { objectName: "nonWrapped2" } + property Component nonWrapped3: DelegateChooser { objectName: "nonWrapped3" } - property Component wrapped: Text { property int wrapped } + property Component wrapped: Text { objectName: "wrapped" } component MyInlineComponent: Item { - property Component wrapped: Text { property int wrapped } + property Component wrapped: Text { objectName: "wrapped" } TableView { - delegate: Text { property int wrapped2 } + delegate: Text { objectName: "wrapped2" } } TableView { - delegate: ComponentType { property int wrapped3 } + delegate: ComponentType { objectName: "wrapped3" } } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp index ad4c425a6a..29c7b3f575 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp @@ -73,9 +73,8 @@ class tst_qqmljsscope : public QQmlDataTest logger.setCategoryIgnored(qmlShadow, true); logger.setCategoryIgnored(qmlPropertyOverride, true); logger.setSilent(expectErrorsOrWarnings); - QQmlJSScope::Ptr target = QQmlJSScope::create(); - target->setOwnModuleName(u"HelloModule"_s); - QQmlJSImportVisitor visitor(target, &m_importer, &logger, dataDirectory()); + QQmlJSScope::Ptr target = m_importer.importFile(logger.filePath()); + QQmlJSImportVisitor visitor(&m_importer, &logger, QDir::cleanPath(resolvedFile + "/.."_L1)); QQmlJSTypeResolver typeResolver { &m_importer }; typeResolver.init(&visitor, document->program); if (!expectErrorsOrWarnings) { @@ -248,7 +247,7 @@ void tst_qqmljsscope::allTypesAvailable() }; QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr }; - const auto imported = importer.importModule(u"QtQml"_s); + const auto imported = importer.importModule(u"QtQml"_s, QQmlJS::PrecedenceValues::Default); QCOMPARE(imported.contextualTypes().context(), QQmlJS::ContextualTypes::QML); const auto types = imported.types(); QVERIFY(types.contains(u"$internal$.QObject"_s)); @@ -301,7 +300,15 @@ void tst_qqmljsscope::componentWrappedObjects() const auto isGoodType = [](const QQmlJSScope::ConstPtr &type, const QString &propertyName, bool isWrapped) { - return type->hasOwnProperty(propertyName) + const auto objectNameBindings = type->ownPropertyBindings(u"objectName"_s); + if (std::distance(objectNameBindings.first, objectNameBindings.second) != 1) + return false; + + const QQmlJSMetaPropertyBinding objectNameBinding = *objectNameBindings.first; + if (objectNameBinding.bindingType() != QQmlSA::BindingType::StringLiteral) + return false; + + return objectNameBinding.stringValue() == propertyName && type->isWrappedInImplicitComponent() == isWrapped; }; @@ -783,7 +790,7 @@ void tst_qqmljsscope::hasOwnEnumerationKeys() void tst_qqmljsscope::ownModuleName() { const QString moduleName = u"HelloModule"_s; - QQmlJSScope::ConstPtr root = run(u"ownModuleName.qml"_s); + QQmlJSScope::ConstPtr root = run(u"HelloModule/ownModuleName.qml"_s); QVERIFY(root); QCOMPARE(root->moduleName(), moduleName); QCOMPARE(root->ownModuleName(), moduleName); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyButton.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyButton.qml new file mode 100644 index 0000000000..dba23831c4 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyButton.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + property QtObject action + property alias text: labelWithOptionMenu.label.property + property MyLabel l: MyLabel { + id: labelWithOptionMenu + label.property: action ? action.objectName : "" + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyLabel.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyLabel.qml new file mode 100644 index 0000000000..d1581103f9 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/MyLabel.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + property alias label: labelText + property Binding l: Binding { + id: labelText + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasCrash.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasCrash.qml new file mode 100644 index 0000000000..9027c27627 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasCrash.qml @@ -0,0 +1,8 @@ +import QtQml + +QtObject { + objectName: a.text + property MyButton a: MyButton { + text: "boom" + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasToPropertyOfAlias.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasToPropertyOfAlias.qml new file mode 100644 index 0000000000..f82e1a3bc0 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasToPropertyOfAlias.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + id: root + + property alias bar: root.foo.objectName + property alias foo: fooId + + foo {} + + property QtObject o: QtObject { + id: fooId + objectName: "bar" + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty.qml new file mode 100644 index 0000000000..11808997bd --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty.qml @@ -0,0 +1,16 @@ +import QtQuick + +Foo { + + component Foo: Item { + id: root + + property alias label: root.text + default property alias foo: it.data + + Item { + id: it + } + property string text: "should not change" + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty2.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty2.qml new file mode 100644 index 0000000000..d8cab343ca --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/aliasesAndDefaultProperty2.qml @@ -0,0 +1,14 @@ +import QtQuick + +BasicTableView { +component BasicTableView : Item { + id: root + default property alias __columns: root.data + readonly property alias __listView: listView + ListView { + id: listView + } +} + +Item {} +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.errors.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.errors.txt new file mode 100644 index 0000000000..0dbc91f9fe --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.errors.txt @@ -0,0 +1 @@ +5:5:Cyclic alias diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.qml new file mode 100644 index 0000000000..564dbb1ef2 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/cyclicSameObjectAlias.qml @@ -0,0 +1,6 @@ +import QtQml 2.2 +QtObject { + id: root + property alias a: root.b + property alias b: root.a +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/deepAliasWithPropertyObject.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/deepAliasWithPropertyObject.qml new file mode 100644 index 0000000000..06c0c2491b --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/data/deepAliasWithPropertyObject.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + id: root + + component Inner : QtObject { + property int value: 42 + } + + property alias deepValue: root.innerObj.value + property Inner innerObj: Inner {} + property QtObject other: QtObject { + property alias proxy: root.deepValue + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index f0e275aa67..331583686e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -455,6 +455,7 @@ private slots: void objectInQmlListAndGc(); void asCastToInlineComponent(); void deepAliasOnICOrReadonly(); + void deepAliasWithPropertyObject(); void optionalChainCallOnNullProperty(); @@ -522,6 +523,9 @@ private slots: void argumentsUsageInBindings(); void aliasToLargeRevision(); + void aliasToPropertyOfAlias(); + void aliasesAndDefaultProperty(); + void removeBindingFromAliasToObject(); void propertyCycle(); void urlWithFragment(); @@ -857,6 +861,7 @@ void tst_qqmllanguage::errors_data() QTest::newRow("assignComponentToWrongType") << "assignComponentToWrongType.qml" << "assignComponentToWrongType.errors.txt" << false; QTest::newRow("cyclicAlias") << "cyclicAlias.qml" << "cyclicAlias.errors.txt" << false; + QTest::newRow("cyclicSameObjectAlias") << "cyclicSameObjectAlias.qml" << "cyclicSameObjectAlias.errors.txt" << false; QTest::newRow("fuzzed.1") << "fuzzed.1.qml" << "fuzzed.1.errors.txt" << false; QTest::newRow("fuzzed.2") << "fuzzed.2.qml" << "fuzzed.2.errors.txt" << false; @@ -2972,66 +2977,75 @@ void tst_qqmllanguage::dontDoubleCallClassBegin() void tst_qqmllanguage::reservedWords_data() { QTest::addColumn("word"); + QTest::addColumn("isECMAKeyword"); - QTest::newRow("abstract") << QByteArray("abstract"); - QTest::newRow("as") << QByteArray("as"); - QTest::newRow("break") << QByteArray("break"); - QTest::newRow("case") << QByteArray("case"); - QTest::newRow("catch") << QByteArray("catch"); - QTest::newRow("class") << QByteArray("class"); - QTest::newRow("continue") << QByteArray("continue"); - QTest::newRow("const") << QByteArray("const"); - QTest::newRow("debugger") << QByteArray("debugger"); - QTest::newRow("default") << QByteArray("default"); - QTest::newRow("delete") << QByteArray("delete"); - QTest::newRow("do") << QByteArray("do"); - QTest::newRow("else") << QByteArray("else"); - QTest::newRow("enum") << QByteArray("enum"); - QTest::newRow("export") << QByteArray("export"); - QTest::newRow("extends") << QByteArray("extends"); - QTest::newRow("false") << QByteArray("false"); - QTest::newRow("finally") << QByteArray("finally"); - QTest::newRow("for") << QByteArray("for"); - QTest::newRow("function") << QByteArray("function"); - QTest::newRow("if") << QByteArray("if"); - QTest::newRow("implements") << QByteArray("implements"); - QTest::newRow("import") << QByteArray("import"); - QTest::newRow("pragma") << QByteArray("pragma"); - QTest::newRow("in") << QByteArray("in"); - QTest::newRow("instanceof") << QByteArray("instanceof"); - QTest::newRow("interface") << QByteArray("interface"); - QTest::newRow("native") << QByteArray("native"); - QTest::newRow("new") << QByteArray("new"); - QTest::newRow("null") << QByteArray("null"); - QTest::newRow("package") << QByteArray("package"); - QTest::newRow("private") << QByteArray("private"); - QTest::newRow("protected") << QByteArray("protected"); - QTest::newRow("public") << QByteArray("public"); - QTest::newRow("return") << QByteArray("return"); - QTest::newRow("static") << QByteArray("static"); - QTest::newRow("super") << QByteArray("super"); - QTest::newRow("switch") << QByteArray("switch"); - QTest::newRow("synchronized") << QByteArray("synchronized"); - QTest::newRow("this") << QByteArray("this"); - QTest::newRow("throw") << QByteArray("throw"); - QTest::newRow("throws") << QByteArray("throws"); - QTest::newRow("transient") << QByteArray("transient"); - QTest::newRow("true") << QByteArray("true"); - QTest::newRow("try") << QByteArray("try"); - QTest::newRow("typeof") << QByteArray("typeof"); - QTest::newRow("var") << QByteArray("var"); - QTest::newRow("void") << QByteArray("void"); - QTest::newRow("volatile") << QByteArray("volatile"); - QTest::newRow("while") << QByteArray("while"); - QTest::newRow("with") << QByteArray("with"); + // ECMA keywords + QTest::newRow("break") << QByteArray("break") << true; + QTest::newRow("do") << QByteArray("do") << true; + QTest::newRow("in") << QByteArray("in") << true; + QTest::newRow("typeof") << QByteArray("typeof") << true; + QTest::newRow("case") << QByteArray("case") << true; + QTest::newRow("else") << QByteArray("else") << true; + QTest::newRow("void") << QByteArray("void") << true; + QTest::newRow("instanceof") << QByteArray("instanceof") << true; + QTest::newRow("var") << QByteArray("var") << true; + QTest::newRow("catch") << QByteArray("catch") << true; + QTest::newRow("export") << QByteArray("export") << true; + QTest::newRow("new") << QByteArray("new") << true; + QTest::newRow("class") << QByteArray("class") << true; + QTest::newRow("extends") << QByteArray("extends") << true; + QTest::newRow("return") << QByteArray("return") << true; + QTest::newRow("while") << QByteArray("while") << true; + QTest::newRow("const") << QByteArray("const") << true; + QTest::newRow("finally") << QByteArray("finally") << true; + QTest::newRow("super") << QByteArray("super") << true; + QTest::newRow("with") << QByteArray("with") << true; + QTest::newRow("continue") << QByteArray("continue") << true; + QTest::newRow("for") << QByteArray("for") << true; + QTest::newRow("switch") << QByteArray("switch") << true; + QTest::newRow("debugger") << QByteArray("debugger") << true; + QTest::newRow("function") << QByteArray("function") << true; + QTest::newRow("this") << QByteArray("this") << true; + QTest::newRow("default") << QByteArray("default") << true; + QTest::newRow("if") << QByteArray("if") << true; + QTest::newRow("throw") << QByteArray("throw") << true; + QTest::newRow("delete") << QByteArray("delete") << true; + QTest::newRow("import") << QByteArray("import") << true; + QTest::newRow("try") << QByteArray("try") << true; + + // bool and null literals + + QTest::newRow("false") << QByteArray("false") << true; + QTest::newRow("null") << QByteArray("null") << true; + QTest::newRow("true") << QByteArray("true") << true; + + QTest::newRow("abstract") << QByteArray("abstract") << false; + QTest::newRow("as") << QByteArray("as") << false; + QTest::newRow("enum") << QByteArray("enum") << false; + QTest::newRow("implements") << QByteArray("implements") << false; + QTest::newRow("pragma") << QByteArray("pragma") << false; + QTest::newRow("interface") << QByteArray("interface") << false; + QTest::newRow("native") << QByteArray("native") << false; + QTest::newRow("package") << QByteArray("package") << false; + QTest::newRow("private") << QByteArray("private") << false; + QTest::newRow("protected") << QByteArray("protected") << false; + QTest::newRow("public") << QByteArray("public") << false; + QTest::newRow("static") << QByteArray("static") << false; + QTest::newRow("synchronized") << QByteArray("synchronized") << false; + QTest::newRow("throws") << QByteArray("throws") << false; + QTest::newRow("transient") << QByteArray("transient") << false; + QTest::newRow("volatile") << QByteArray("volatile") << false; } void tst_qqmllanguage::reservedWords() { QFETCH(QByteArray, word); + QFETCH(bool, isECMAKeyword); QQmlComponent component(&engine); component.setData("import QtQuick 2.0\nQtObject { property string " + word + " }", QUrl()); - QCOMPARE(component.errorString(), QLatin1String(":2:28: Expected token `identifier'\n")); + QString errorMessage = ":2:28: %1 keyword \"%2\" cannot be used as a QML identifier.\n"_L1 + .arg(isECMAKeyword ? "ECMAScript"_L1 : "Reserved"_L1, QString::fromLatin1(word)); + QCOMPARE(component.errorString(), errorMessage); } // Check that first child of qml is of given type. Empty type insists on error. @@ -8918,6 +8932,16 @@ void tst_qqmllanguage::deepAliasOnICOrReadonly() "Invalid property assignment: \"readonlyRectX\" is a read-only property"))); } +void tst_qqmllanguage::deepAliasWithPropertyObject() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("deepAliasWithPropertyObject.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("deepValue").toInt(), 42); +} + void tst_qqmllanguage::optionalChainCallOnNullProperty() { QTest::failOnWarning(QRegularExpression(".*Cannot call method 'destroy' of null.*")); @@ -9908,6 +9932,51 @@ void tst_qqmllanguage::aliasToLargeRevision() QCOMPARE(o->property("dd"), 16); } +void tst_qqmllanguage::aliasToPropertyOfAlias() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("aliasToPropertyOfAlias.qml")); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("bar").toString(), "bar"_L1); +} + +void tst_qqmllanguage::aliasesAndDefaultProperty() +{ + // This test is meant to capture the case where the reordering of aliases + // in the IR necessiates an update of the default property index + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("aliasesAndDefaultProperty.qml")); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("label"), "should not change"_L1); + + // similar test, should not complain about assigning read-only property + c.loadUrl(testFileUrl("aliasesAndDefaultProperty2.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + o.reset(c.create()); + QVERIFY(!o.isNull()); +} + +void tst_qqmllanguage::removeBindingFromAliasToObject() +{ + QQmlEngine engine; + + QQmlComponent c(&engine, testFileUrl("aliasCrash.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + std::unique_ptr o(c.create()); + QVERIFY(o); + + QCOMPARE(o->objectName(), "boom"_L1); +} + void tst_qqmllanguage::urlWithFragment() { QQmlEngine engine; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/CMakeLists.txt index cdde2b0f3b..1e79ea2cd4 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/CMakeLists.txt @@ -46,3 +46,7 @@ qt_internal_extend_target(tst_qqmllistmodel CONDITION NOT ANDROID AND NOT IOS DEFINES QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) + +if (TARGET QOffscreenIntegrationPlugin) + add_dependencies(tst_qqmllistmodel QOffscreenIntegrationPlugin) +endif() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.js b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.js new file mode 100644 index 0000000000..69981a39e2 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.js @@ -0,0 +1,13 @@ +WorkerScript.onMessage = function(msg) { + console.log("worker modifying & syncing model") + msg.rowListModel.clear(); + msg.rowListModel.sync(); + + for (let i = 0; i < msg.a.length; i++) { + var row = msg.a[i]; + msg.rowListModel.append({ rowNum: i, keys: row }); + msg.rowListModel.sync(); + } + + WorkerScript.sendMessage({}) +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.qml new file mode 100644 index 0000000000..ee8a8d0f60 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/data/properWorkerAgentLifecycle.qml @@ -0,0 +1,54 @@ +import QtQuick + +Window { + id: root + width: 100 + height: 100 + visible: true + + property int sent: 0 + property int received: 0 + + WorkerScript { + id: keyboardWorker + source: "properWorkerAgentLifecycle.js" + onMessage: _ => ++received + } + + function messageWorker() { + ++sent + keyboardWorker.sendMessage({ + a: [ [ { label: "0" }, ], [ { label: "A" }, ]], + rowListModel: rowListModel + }); + } + + Timer { + running: root.sent < 4 + repeat: true + interval: 50 + onTriggered: messageWorker() + } + + ListModel { + id: rowListModel + } + + Item { + width: parent.width + + ListView { + anchors.fill: parent + model: rowListModel + delegate: + Row { + id: row + required property QtObject modelData + Repeater { + id: keyboardRowRepeater + model: row.modelData.keys + } + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index 3f98bb98ef..33fb1ae088 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -53,6 +53,13 @@ class tst_qqmllistmodel : public QQmlDataTest { Q_OBJECT public: + static void initMain() + { + // Some tests may require a window to fully emulate the behavior of certain things like a + // ListView. Use the offscreen platform to avoid showing windows which might cause issues. + qputenv("QT_QPA_PLATFORM", "offscreen"); + } + tst_qqmllistmodel() : QQmlDataTest(QT_QMLTEST_DATADIR) { @@ -126,6 +133,7 @@ private slots: void valuesOfInnerList(); void arrayLikes(); void functionInNested(); + void properWorkerAgentLifecycle(); }; bool tst_qqmllistmodel::compareVariantList(const QVariantList &testList, QVariant object) @@ -2211,6 +2219,23 @@ void tst_qqmllistmodel::functionInNested() QVERIFY(!o.isNull()); } +void tst_qqmllistmodel::properWorkerAgentLifecycle() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("properWorkerAgentLifecycle.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "worker modifying & syncing model"); + QTest::ignoreMessage(QtDebugMsg, "worker modifying & syncing model"); + QTest::ignoreMessage(QtDebugMsg, "worker modifying & syncing model"); + QTest::ignoreMessage(QtDebugMsg, "worker modifying & syncing model"); + + QScopedPointer o(component.create()); + QVERIFY(!o.isNull()); + + QTRY_COMPARE(o->property("received"), 4); +} + QTEST_MAIN(tst_qqmllistmodel) #include "tst_qqmllistmodel.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index d6427988e2..f660fd701c 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -3,6 +3,7 @@ #include "interfaces.h" #include +#include #include #include #include @@ -10,6 +11,8 @@ #include #include #include +#include +#include #include #include #if QT_CONFIG(regularexpression) @@ -152,6 +155,52 @@ private: }; +class VariantSourceObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) +public: + explicit VariantSourceObject(QObject *parent = nullptr) : QObject(parent) {} + + QVariant value() const { return m_value; } + void setValue(const QVariant &v) + { + if (m_value != v) { + m_value = v; + emit valueChanged(); + } + } + +signals: + void valueChanged(); + +private: + QVariant m_value; +}; + +class StringTargetObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged) +public: + explicit StringTargetObject(QObject *parent = nullptr) : QObject(parent) {} + + QString value() const { return m_value; } + void setValue(const QString &v) + { + if (m_value != v) { + m_value = v; + emit valueChanged(); + } + } + +signals: + void valueChanged(); + +private: + QString m_value; +}; + class tst_qqmlproperty : public QQmlDataTest { Q_OBJECT @@ -236,6 +285,8 @@ private slots: void convertToWriteTargetType_data(); void convertToWriteTargetType(); + void writeInvalidVariantToTypedProperty(); + private: QQmlEngine engine; }; @@ -2779,6 +2830,39 @@ void tst_qqmlproperty::convertToWriteTargetType() QCOMPARE(result, targetValue); } +// QTBUG-145016: verify that property-to-property bindings perform JavaScript coercion +// when the source value is an invalid QVariant (representing "undefined"). +void tst_qqmlproperty::writeInvalidVariantToTypedProperty() +{ + VariantSourceObject source; + StringTargetObject target; + + QQmlProperty sourceProp(&source, u"value"_s); + QQmlProperty targetProp(&target, u"value"_s); + + QVERIFY(sourceProp.isValid()); + QVERIFY(targetProp.isValid()); + + QQmlAnyBinding binding = QQmlPropertyToPropertyBinding::create(&engine, sourceProp, targetProp); + binding.installOn(targetProp); + + // Write a valid string - should propagate + source.setValue(QVariant(QStringLiteral("Foo"))); + QCOMPARE(target.value(), u"Foo"_s); + + // Write an invalid QVariant (represents "undefined" in JS). + // The binding must still update the target property so that subsequent + // valid writes trigger the change signal. + source.setValue(QVariant()); + QCOMPARE(target.value(), u"undefined"_s); + + // Write a valid string again - must trigger a change + QSignalSpy spy(&target, &StringTargetObject::valueChanged); + source.setValue(QVariant(QStringLiteral("Foo"))); + QCOMPARE(target.value(), u"Foo"_s); + QCOMPARE(spy.size(), 1); +} + QTEST_MAIN(tst_qqmlproperty) #include "tst_qqmlproperty.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp index 69c9e51f73..f3ebeda23e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp @@ -1131,11 +1131,11 @@ void tst_qqmlpropertycache::append_propertyAttr() QMetaObjectBuilder moBuilder; const QString propName = "p"; moBuilder.addProperty(propName.toUtf8(), "int", -1); - const auto *mo = moBuilder.toMetaObject(); + std::unique_ptr mo(moBuilder.toMetaObject(), &free); if (!warningPattern.isEmpty()) QTest::ignoreMessage(level, QRegularExpression(warningPattern.toLatin1())); - cache->update(mo); + cache->update(mo.get()); // no matter what the property is added QCOMPARE(cache->propertyCount(), mo->propertyCount()); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/TreeData.js b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/TreeData.js new file mode 100644 index 0000000000..9a0e562a9f --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/TreeData.js @@ -0,0 +1,36 @@ +//![file] +let folders = [ + { + "checked": false, + "size": "—", + "type": "folder", + "name": "Documents", + "lastModified": "2025-07-01", + "rows": [ + { + "checked": true, + "size": "24 KB", + "type": "file", + "name": "Resume.pdf", + "lastModified": "2025-06-20" + } + ] + }, + { + "checked": false, + "size": "—", + "type": "folder", + "name": "Pictures", + "lastModified": "2025-05-30", + "rows": [ + { + "checked": true, + "size": "3.5 MB", + "type": "file", + "name": "Vacation.jpg", + "lastModified": "2025-05-15" + } + ] + } +] +//![file] diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/empty.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/empty.qml index ea3d7e3223..311ed71dc1 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/empty.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/empty.qml @@ -97,6 +97,22 @@ Item { testModel.rows = ({ foo: 1 }) // should fail - type is JSObject } + function setInvalidRowsArray() { + testModel.rows = [1, 2, 3] // "wrong kind of array" + } + + function appendInvalidNumber() { // should fail - type is int + testModel.appendRow(42); + } + + function appendInvalidString() { // should fail - type is string + testModel.appendRow("Hello world"); + } + + function appendInvalidArray() { // should fail - type is array, at the moment we are expecting objects + testModel.appendRow([1, 2, 3]); + } + TreeModel { id: treeModel objectName: "testModel" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/setRowsViaJSON.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/setRowsViaJSON.qml new file mode 100644 index 0000000000..403bbbd18e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/data/setRowsViaJSON.qml @@ -0,0 +1,46 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +//![file] +import QtQuick +import Qt.labs.qmlmodels + +//![import] +import "TreeData.js" as JsonData +//![import] + +Item { + id: root + width: 200 + height: 200 + + property alias testModel: treeModel + property alias treeView: treeView + +//![assignment] + Component.onCompleted: treeView.model.rows = JsonData.folders +//![assignment] + +//![model] + TreeModel { + id: treeModel + objectName: "testModel" + + TableModelColumn { display: "checked" } + TableModelColumn { display: "size" } + TableModelColumn { display: "type" } + TableModelColumn { display: "name" } + TableModelColumn { display: "lastModified" } + } +//![model] + + TreeView { + id: treeView + anchors.fill: parent + model: testModel + delegate: Text { + text: model.display + } + } +} +//![file] diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/tst_qqmltreemodel.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/tst_qqmltreemodel.cpp index a65ae5dbe7..a3ded7d2e7 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/tst_qqmltreemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qml/qqmltreemodel/tst_qqmltreemodel.cpp @@ -36,6 +36,7 @@ private slots: void setRowsForEmptyModel(); void setRowsOnNonEmptyModel(); void setRowsRejectsNonArray(); + void setRowsFromJSON(); void setRow(); void setData(); }; @@ -998,6 +999,88 @@ void tst_QQmlTreeModel::setRowsRejectsNonArray() QCOMPARE(model->columnCount(), 5); QCOMPARE(columnCountSpy.size(), 0); QCOMPARE(rowsChangedSpy.size(), rowsChangedSignalEmissions); + + // try to set an array that does not contain key-value pairs + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setInvalidRowsArray")); + // setRows is not returning early + QCOMPARE(model->treeSize(), 0); + QCOMPARE(model->columnCount(), 5); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowsChangedSpy.size(), rowsChangedSignalEmissions); + + // try to append to an invalid row - int + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".* could not find any node at the specified index")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendInvalidNumber")); + // appendRow returns early - nothing changes + QCOMPARE(model->treeSize(), 0); + QCOMPARE(model->columnCount(), 5); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowsChangedSpy.size(), rowsChangedSignalEmissions); + + // try to append to an invalid row - string + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".* could not find any node at the specified index")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendInvalidString")); + // appendRow returns early - nothing changes + QCOMPARE(model->treeSize(), 0); + QCOMPARE(model->columnCount(), 5); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowsChangedSpy.size(), rowsChangedSignalEmissions); + + // try to append to an invalid row - array + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".* could not find any node at the specified index")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*does not contain.*")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendInvalidArray")); + // appendRow returns early - nothing changes + QCOMPARE(model->treeSize(), 0); + QCOMPARE(model->columnCount(), 5); + QCOMPARE(columnCountSpy.size(), 0); + QCOMPARE(rowsChangedSpy.size(), rowsChangedSignalEmissions); +} + +void tst_QQmlTreeModel::setRowsFromJSON() +{ + QQuickView view; + QVERIFY(QQuickTest::showView(view, testFileUrl("setRowsViaJSON.qml"))); + + auto *model = view.rootObject()->property("testModel").value(); + QVERIFY(model); + + // The tree has been initialized from JSON + QCOMPARE(model->treeSize(), 4); + QCOMPARE(model->columnCount(), 5); + + const QHash roleNames = model->roleNames(); + QCOMPARE(roleNames.size(), 1); + QVERIFY(roleNames.values().contains("display")); + + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toBool(), false); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toString(), QStringLiteral("\u2014")); + QCOMPARE(model->data(model->index(0, 2, QModelIndex()), roleNames.key("display")).toString(), u"folder"_s); + QCOMPARE(model->data(model->index(0, 3, QModelIndex()), roleNames.key("display")).toString(), u"Documents"_s); + QCOMPARE(model->data(model->index(0, 4, QModelIndex()), roleNames.key("display")).toString(), u"2025-07-01"_s); + + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toBool(), false); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toString(), QStringLiteral("\u2014")); + QCOMPARE(model->data(model->index(1, 2, QModelIndex()), roleNames.key("display")).toString(), u"folder"_s); + QCOMPARE(model->data(model->index(1, 3, QModelIndex()), roleNames.key("display")).toString(), u"Pictures"_s); + QCOMPARE(model->data(model->index(1, 4, QModelIndex()), roleNames.key("display")).toString(), u"2025-05-30"_s); + + QCOMPARE(model->data(model->index({0,0}, 0), roleNames.key("display")).toBool(), true); + QCOMPARE(model->data(model->index({0,0}, 1), roleNames.key("display")).toString(), u"24 KB"_s); + QCOMPARE(model->data(model->index({0,0}, 2), roleNames.key("display")).toString(), u"file"_s); + QCOMPARE(model->data(model->index({0,0}, 3), roleNames.key("display")).toString(), u"Resume.pdf"_s); + QCOMPARE(model->data(model->index({0,0}, 4), roleNames.key("display")).toString(), u"2025-06-20"_s); + + QCOMPARE(model->data(model->index({1,0}, 0), roleNames.key("display")).toBool(), true); + QCOMPARE(model->data(model->index({1,0}, 1), roleNames.key("display")).toString(), u"3.5 MB"_s); + QCOMPARE(model->data(model->index({1,0}, 2), roleNames.key("display")).toString(), u"file"_s); + QCOMPARE(model->data(model->index({1,0}, 3), roleNames.key("display")).toString(), u"Vacation.jpg"_s); + QCOMPARE(model->data(model->index({1,0}, 4), roleNames.key("display")).toString(), u"2025-05-15"_s); } void tst_QQmlTreeModel::setRow() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/domitem/broken.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/domitem/broken.qml new file mode 100644 index 0000000000..f8228612de --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/domitem/broken.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + var x = 3; +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml index 5058d08ae3..e1fe51e21a 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctions.qml @@ -3,4 +3,6 @@ Item { arrow2: (x) => x + x arrow3: (x) => x > 3 ? x * x : x arrow4: (x) => { return x > 3 ? x * x : x; } + arrow5: () => { return x > 3 ? x * x : x; } + Component.onCompleted: () => { console.log("asdf"); } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml index 4b0bbabd3d..0e69864d2d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domdata/reformatter/arrowFunctionsReformatted.qml @@ -5,4 +5,11 @@ Item { arrow4: x => { return x > 3 ? x * x : x; } + arrow5: () => { + return x > 3 ? x * x : x; + } + + Component.onCompleted: () => { + console.log("asdf"); + } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domitem/tst_qmldomitem.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domitem/tst_qmldomitem.h index 8308ab6242..1bfbe54d36 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domitem/tst_qmldomitem.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/domitem/tst_qmldomitem.h @@ -633,7 +633,7 @@ private slots: qDebug() << "testDeepCopy.diffs:" << diffs; QVERIFY(diffs.isEmpty()); DomItem env(envPtr); - DomItem univFile = env.universe().path(f.canonicalPath()); + DomItem univFile = env.path(f.canonicalPath()); MutableDomItem univFileCopy = univFile.makeCopy(); QStringList univFileDiffs = domCompareStrList(univFile, univFileCopy, FieldFilter::compareFilter()); @@ -4741,6 +4741,12 @@ private slots: } } + void invalidFile() + { + auto dom = parse(baseDir + u"/broken.qml"_s, qmltypeDirs); + QCOMPARE(dom.field(Fields::isValid).value().toBool(true), false); + } + private: QString baseDir; QStringList qmltypeDirs; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/reformatter/tst_reformatter.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/reformatter/tst_reformatter.h index 7c3f8a8783..8604f0e241 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/reformatter/tst_reformatter.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmldom/reformatter/tst_reformatter.h @@ -130,6 +130,9 @@ private slots: QTest::newRow("linebreakBinaryOp") << QStringLiteral(u"linebreakBinaryOp.qml") << QStringLiteral(u"linebreakBinaryOpReformattedWidth60.qml") << lwOptions; + QTest::newRow("linebreakArrowFunctions") + << QStringLiteral(u"arrowFunctions.qml") + << QStringLiteral(u"arrowFunctionsReformatted.qml") << lwOptions; } QTest::newRow("nonInitializedPropertyInComponent") << "nonInitializedPropertyInComponent.qml" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/modules/tst_qmlls_modules.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/modules/tst_qmlls_modules.cpp index 1ad2596bb7..7ba146eb28 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/modules/tst_qmlls_modules.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/modules/tst_qmlls_modules.cpp @@ -1454,8 +1454,6 @@ void tst_qmlls_modules::warnings_data() "your project? [import]"_L1); missingWorkspaceB.warnings.append( "MyItem was not found. Did you add all imports and dependencies?: Did you mean \"Item\"? [import]"_L1); - missingWorkspaceB.warnings.append( - "Could not find property \"fromImportPathB\". [missing-property]"_L1); QTest::addRow("MissingWorkspaceB") << u"workspaces/twoWorkspaces/WorkspaceB/UseImportPathB.qml"_s << missingWorkspaceB; @@ -1485,8 +1483,6 @@ void tst_qmlls_modules::warnings_data() "your project? [import]"_L1); nestedWorkspaces.warnings.append( "MyItem was not found. Did you add all imports and dependencies?: Did you mean \"Item\"? [import]"_L1); - nestedWorkspaces.warnings.append( - "Could not find property \"fromImportPathA\". [missing-property]"_L1); // use import paths from twoWorkspaces and not from WorkspaceA, expect warnings QTest::addRow("NestedWorkspaces2") diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp index 910c24a3ab..bba19b7008 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/qqmlcodemodel/tst_qmlls_qqmlcodemodel.cpp @@ -939,13 +939,17 @@ void tst_qmlls_qqmlcodemodel::reloadQmllsBuildIniAfterBuild() { QmlLsp::QQmlCodeModelManager manager; const QByteArray rootUrl{ testFileUrl("rootA").toEncoded() }; + const QByteArray fallbackUrl; + + static constexpr QLatin1String fallbackImportPath = "/path/to/importPath"_L1; + manager.setImportPaths({ fallbackImportPath }); QTemporaryDir temporaryDir; QVERIFY(temporaryDir.isValid()); manager.addRootUrls({ rootUrl }); manager.setBuildPathsForRootUrl(rootUrl, { temporaryDir.path() }); - QCOMPARE(manager.importPathsForUrl(rootUrl), QStringList{ temporaryDir.path() }); + QCOMPARE(manager.importPathsForUrl(rootUrl), QStringList{ fallbackImportPath }); QDir dir(temporaryDir.path()); dir.mkdir(".qt"_L1); @@ -960,7 +964,10 @@ void tst_qmlls_qqmlcodemodel::reloadQmllsBuildIniAfterBuild() manager.onBuildFinished(rootUrl); - QCOMPARE(manager.importPathsForUrl(rootUrl), QStringList{ testFile("test"_L1) }); + const QStringList expectedImportPathsForRoot = + QStringList{ testFile("test"_L1), fallbackImportPath }; + QCOMPARE(manager.importPathsForUrl(rootUrl), expectedImportPathsForRoot); + QCOMPARE(manager.importPathsForUrl(fallbackUrl), QStringList{ fallbackImportPath }); { QFile file(temporaryDir.filePath(".qt/.qmlls.build.ini"_L1)); @@ -972,7 +979,10 @@ void tst_qmlls_qqmlcodemodel::reloadQmllsBuildIniAfterBuild() manager.onBuildFinished(rootUrl); - QCOMPARE(manager.importPathsForUrl(rootUrl), QStringList{ testFile("test2"_L1) }); + const QStringList expectedImportPathsForRoot2 = + QStringList{ testFile("test2"_L1), fallbackImportPath }; + QCOMPARE(manager.importPathsForUrl(rootUrl), expectedImportPathsForRoot2); + QCOMPARE(manager.importPathsForUrl(fallbackUrl), QStringList{ fallbackImportPath }); } QTEST_MAIN(tst_qmlls_qqmlcodemodel) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes index 7f06e11a4f..1f2d2ce758 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/ModuleWithComponent/plugin.qmltypes @@ -75,4 +75,22 @@ Module { lineNumber: 38 } } + Component { + file: "mycomponentfromnoncppheader.java" + lineNumber: 42 + name: "MyComponentFromNonCpp" + accessSemantics: "reference" + prototype: "QObject" + exports: ["ModuleWithComponent/MyComponentFromNonCpp 254.0"] + Property { + name: "myProperty" + type: "int" + read: "myProperty" + write: "setMyProperty" + notify: "myPropertyChanged" + lineNumber: 12 + index: 0 + } + Signal { name: "myPropertyChanged" } + } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/completions/idMethodMixup.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/completions/idMethodMixup.qml new file mode 100644 index 0000000000..b741ca0159 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/completions/idMethodMixup.qml @@ -0,0 +1,12 @@ +import QtQuick + +Item { + Item { + id: hello + property int myProperty + } + + function hello() { + hello. + } +} \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/SomeIncludeFolder/mycomponentfromnoncppheader.java b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/SomeIncludeFolder/mycomponentfromnoncppheader.java new file mode 100644 index 0000000000..098c57e3fa --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/SomeIncludeFolder/mycomponentfromnoncppheader.java @@ -0,0 +1 @@ +// note: just needs to exist for qmlls to find it \ No newline at end of file diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml index d344a97e15..44923ccb12 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/findDefinition/UseMyCppComponent.qml @@ -13,4 +13,5 @@ MyComponentFromCpp { enum EnumFromQml { HelloEnumFromQml, ByeEnumFromQml } property int someEnum2: UseMyCppComponent.ByeEnumFromQml property int someScopedEnum2: UseMyCppComponent.EnumFromQml.ByeEnumFromQml + property var notFromCpp: MyComponentFromNonCpp {} } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/MySingleton.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/MySingleton.qml new file mode 100644 index 0000000000..9cb98ac02f --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/MySingleton.qml @@ -0,0 +1,7 @@ +pragma Singleton + +import QtQuick + +QtObject { + readonly property color windowColor: "black" +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/misc.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/misc.qml index 908344f9ff..04d404670e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/misc.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/misc.qml @@ -11,4 +11,6 @@ Item { const aa = Array.prototype.push(1) const KK = Math.random(); } + + color: MySingleton.windowColor } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/qmldir b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/qmldir new file mode 100644 index 0000000000..924041ed72 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/highlights/qmldir @@ -0,0 +1,3 @@ +module A +singleton MySingleton 1.0 MySingleton.qml + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/resolveExpressionType/identifiers.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/resolveExpressionType/identifiers.qml new file mode 100644 index 0000000000..1e87cbefc3 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/data/resolveExpressionType/identifiers.qml @@ -0,0 +1,41 @@ +import QtQuick + +Window { + component WithId: Item { + Item { id: colliding } + + property int colliding + function colliding() {} + + function f() { + let colliding = 3; + return colliding; // should be the local JS variable + } + function g() { + return colliding; // should be the id + } + } + component WithoutId: Item { + property int colliding + function colliding() {} + + function h() { + return colliding; // should be the property + } + } + component WithoutId2: Item { + function colliding() {} + property int colliding + + function h() { + return colliding; // should be the property + } + } + component WithoutIdAndWithoutProperty: Item { + function colliding() {} + + function h() { + return colliding; // should be the method + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp index 773f57ba7d..572f19177b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_highlighting.cpp @@ -737,6 +737,9 @@ void tst_qmlls_highlighting::highlights_data() QTest::addRow("globalVarMath") << fileItem << HighlightToken(QQmlJS::SourceLocation(337, 4, 12, 20), QmlHighlightKind::JsGlobalVar, QmlHighlightModifier::None); + QTest::addRow("singleton") << fileItem + << HighlightToken(QQmlJS::SourceLocation(370, 11, 15, 12), + QmlHighlightKind::QmlType, QmlHighlightModifier::None); } { // property chains const auto filePath = m_highlightingDataDir + "/propertyChains.qml"; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.cpp index b607e63ac3..e20ab344bc 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.cpp @@ -1979,6 +1979,10 @@ void tst_qmlls_utils::findDefinitionFromLocation_data() QTest::addRow("componentFromCpp") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 3 << 1 << componentFromCppHeaderPath << 42 << 1 << strlen("") << QStringList{ testFile("findDefinition"_L1) }; + QTest::addRow("componentFromJava") + << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 16 << 44 + << testFile("findDefinition/SomeIncludeFolder/mycomponentfromnoncppheader.java"_L1) + << 42 << 1 << strlen("") << QStringList{ testFile("findDefinition"_L1) }; QTest::addRow("propertyFromCpp") << testFile("findDefinition/UseMyCppComponent.qml"_L1) << 4 << 6 << componentFromCppHeaderPath << 12 << 1 << strlen("") << QStringList{ testFile("findDefinition"_L1) }; @@ -2101,6 +2105,47 @@ void tst_qmlls_utils::findDefinitionFromLocation() QCOMPARE(definition->sourceLocation().length, quint32(expectedLength)); } +void tst_qmlls_utils::findDefinitionFromAbsoluteLocation() +{ + QTemporaryDir build; + QVERIFY(build.isValid()); + + QDir(build.path()).mkdir("ModuleWithComponent"); + QFile::copy(testFile("ModuleWithComponent/qmldir"), + build.filePath("ModuleWithComponent/qmldir")); + + QFile headerFile(build.filePath("ModuleWithComponent/mycomponentfromnoncppheader.java")); + QVERIFY(headerFile.open(QFile::WriteOnly | QFile::Text)); + + QFile qmltypes(testFile("ModuleWithComponent/plugin.qmltypes")); + QVERIFY(qmltypes.open(QFile::ReadOnly | QFile::Text)); + QByteArray qmltypesContent = qmltypes.readAll(); + + const QByteArray expectedFilePath = headerFile.fileName().toUtf8(); + qmltypesContent.replace("mycomponentfromnoncppheader.java", expectedFilePath); + + QFile newQmltypes(build.filePath("ModuleWithComponent/plugin.qmltypes")); + QVERIFY(newQmltypes.open(QFile::WriteOnly | QFile::Text)); + newQmltypes.write(qmltypesContent); + + auto [env, file] = createEnvironmentAndLoadFile( + testFile("findDefinition/UseMyCppComponent.qml"_L1), { build.path() }); + + auto locations = QQmlLSUtils::itemsFromTextLocation( + file.field(QQmlJS::Dom::Fields::currentItem), 15, 43); + + QCOMPARE(locations.size(), 1); + + auto definition = QQmlLSUtils::findDefinitionOf(locations.front().domItem, { build.path() }); + QVERIFY(definition); + + QCOMPARE(definition->filename(), expectedFilePath); + + QCOMPARE(definition->sourceLocation().startLine, quint32(42)); + QCOMPARE(definition->sourceLocation().startColumn, quint32(1)); + QCOMPARE(definition->sourceLocation().length, quint32(0)); +} + void tst_qmlls_utils::resolveExpressionType_data() { QTest::addColumn("filePath"); @@ -2380,6 +2425,21 @@ void tst_qmlls_utils::resolveExpressionType_data() << file << 26 << 18 << ResolveOwnerType << myHeader << noLine << SignalHandlerIdentifier << u"onMySomeTypeChanged"_s; } + { + const QString file = testFile(u"resolveExpressionType/identifiers.qml"_s); + QTest::addRow("identifierJSVariableFirst") + << file << 12 << 26 << ResolveActualTypeForFieldMemberExpression << noFile << noLine + << JavaScriptIdentifier << u"colliding"_s; + QTest::addRow("identifierIdSecond") + << file << 15 << 22 << ResolveActualTypeForFieldMemberExpression << file << 5 + << QmlObjectIdIdentifier << u"colliding"_s; + QTest::addRow("identifierPropertyThird") << file << 23 << 22 << ResolveOwnerType << file + << 18 << PropertyIdentifier << u"colliding"_s; + QTest::addRow("identifierPropertyThird2") << file << 31 << 22 << ResolveOwnerType << file + << 26 << PropertyIdentifier << u"colliding"_s; + QTest::addRow("identifierMethodFourth") << file << 38 << 22 << ResolveOwnerType << file + << 34 << MethodIdentifier << u"colliding"_s; + } } void tst_qmlls_utils::resolveExpressionType() @@ -4540,6 +4600,10 @@ void tst_qmlls_utils::completions_data() QTest::newRow("attachedSignalHandler") << testFile("completions/attachedSignalHandler.qml") << 5 << 9 << ExpectedCompletions{ { "event", CompletionItemKind::Variable } } << QStringList{}; + QTest::newRow("idMethodMixup") + << testFile("completions/idMethodMixup.qml") << 10 << 15 + << ExpectedCompletions{ { "myProperty"_L1, CompletionItemKind::Property } } + << QStringList{ }; } void tst_qmlls_utils::completions() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.h b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.h index bf446f1120..c261e114c3 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qmlls/utils/tst_qmlls_utils.h @@ -57,6 +57,8 @@ private slots: void findDefinitionFromLocation_data(); void findDefinitionFromLocation(); + void findDefinitionFromAbsoluteLocation(); + void findLocationOfItem_data(); void findLocationOfItem(); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/build.gradle b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/build.gradle index 0c130095c2..5d4de20c3a 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/build.gradle +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/build.gradle @@ -1,15 +1,12 @@ plugins { - id 'com.android.application' version '8.3.0' - id 'org.qtproject.qt.gradleplugin' version '1.2' + id 'com.android.application' version '9.0.0' + id 'org.qtproject.qt.gradleplugin' version '1.4' } -def home = System.properties['user.home'] -def ndkPath = System.getenv('ANDROID_NDK_ROOT') QtBuild { - qtPath = file(home + '/work/install') + qtPath = file(qtAndroidDir).parentFile.parentFile.parentFile projectPath = file('qml/') - qtKitDir = file(home + '/work/install/target') - extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + ndkPath] + extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + System.getenv('ANDROID_NDK_ROOT')] } android { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/gradle/wrapper/gradle-wrapper.properties b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d4081da476..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/qml/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/qml/CMakeLists.txt index ef71d9492c..8807daa401 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/qml/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/basic/android/qml/CMakeLists.txt @@ -13,7 +13,7 @@ qt_standard_project_setup() qt_add_executable(tst_qtquickview_basic_qml tst_qtquickview.cpp) -set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_JAVA_CLASS TRUE) +set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_ANDROID_JAVA_CLASS TRUE) qt_add_qml_module(tst_qtquickview_basic_qml URI TestViewModule VERSION 1.0 diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/build.gradle b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/build.gradle index 68715eda69..3781f0cb4d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/build.gradle +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/build.gradle @@ -1,15 +1,12 @@ plugins { - id 'com.android.application' version '8.3.0' - id 'org.qtproject.qt.gradleplugin' version '1.2' + id 'com.android.application' version '9.0.0' + id 'org.qtproject.qt.gradleplugin' version '1.4' } -def home = System.properties['user.home'] -def ndkPath = System.getenv('ANDROID_NDK_ROOT') QtBuild { - qtPath = file(home + '/work/install') + qtPath = file(qtAndroidDir).parentFile.parentFile.parentFile projectPath = file('qml/') - qtKitDir = file(home + '/work/install/target') - extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + ndkPath] + extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + System.getenv('ANDROID_NDK_ROOT')] } android { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/gradle/wrapper/gradle-wrapper.properties b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d4081da476..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/qml/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/qml/CMakeLists.txt index ff76e65f33..7db2790b28 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/qml/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/functions/android/qml/CMakeLists.txt @@ -13,7 +13,7 @@ qt_standard_project_setup() qt_add_executable(tst_qtquickview_functions_qml tst_qtquickview.cpp) -set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_JAVA_CLASS TRUE) +set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_ANDROID_JAVA_CLASS TRUE) qt_add_qml_module(tst_qtquickview_functions_qml URI TestViewModule VERSION 1.0 diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/build.gradle b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/build.gradle index 0c130095c2..5d4de20c3a 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/build.gradle +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/build.gradle @@ -1,15 +1,12 @@ plugins { - id 'com.android.application' version '8.3.0' - id 'org.qtproject.qt.gradleplugin' version '1.2' + id 'com.android.application' version '9.0.0' + id 'org.qtproject.qt.gradleplugin' version '1.4' } -def home = System.properties['user.home'] -def ndkPath = System.getenv('ANDROID_NDK_ROOT') QtBuild { - qtPath = file(home + '/work/install') + qtPath = file(qtAndroidDir).parentFile.parentFile.parentFile projectPath = file('qml/') - qtKitDir = file(home + '/work/install/target') - extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + ndkPath] + extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + System.getenv('ANDROID_NDK_ROOT')] } android { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/gradle/wrapper/gradle-wrapper.properties b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d4081da476..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/qml/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/qml/CMakeLists.txt index 87fa0af5d3..55d5244263 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/qml/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/signallistener/android/qml/CMakeLists.txt @@ -13,7 +13,7 @@ qt_standard_project_setup() qt_add_executable(tst_qtquickview_signallistener_qml tst_qtquickview.cpp) -set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_JAVA_CLASS TRUE) +set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_ANDROID_JAVA_CLASS TRUE) qt_add_qml_module(tst_qtquickview_signallistener_qml URI TestViewModule VERSION 1.0 diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/build.gradle b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/build.gradle index 0c130095c2..5d4de20c3a 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/build.gradle +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/build.gradle @@ -1,15 +1,12 @@ plugins { - id 'com.android.application' version '8.3.0' - id 'org.qtproject.qt.gradleplugin' version '1.2' + id 'com.android.application' version '9.0.0' + id 'org.qtproject.qt.gradleplugin' version '1.4' } -def home = System.properties['user.home'] -def ndkPath = System.getenv('ANDROID_NDK_ROOT') QtBuild { - qtPath = file(home + '/work/install') + qtPath = file(qtAndroidDir).parentFile.parentFile.parentFile projectPath = file('qml/') - qtKitDir = file(home + '/work/install/target') - extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + ndkPath] + extraCMakeArguments = ['-DANDROID_NDK_ROOT=' + System.getenv('ANDROID_NDK_ROOT')] } android { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/gradle/wrapper/gradle-wrapper.properties b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d4081da476..0000000000 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/qml/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/qml/CMakeLists.txt index fd5ec46f72..972a964198 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/qml/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/qtquickview/statuslistener/android/qml/CMakeLists.txt @@ -13,7 +13,7 @@ qt_standard_project_setup() qt_add_executable(tst_qtquickview_statuslistener_qml tst_qtquickview.cpp) -set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_JAVA_CLASS TRUE) +set_source_files_properties(TestView.qml PROPERTIES QT_QML_GENERATE_ANDROID_JAVA_CLASS TRUE) set_source_files_properties(Invalid.qml PROPERTIES QT_QML_SKIP_QMLDIR_ENTRY TRUE QT_QML_SKIP_QMLLINT TRUE diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/CMakeLists.txt index c7362800ca..a95e84a5e2 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/CMakeLists.txt @@ -1,113 +1,110 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from quick.pro. - -add_subdirectory(geometry) -add_subdirectory(nodes) -add_subdirectory(qquickpixmapcache) -add_subdirectory(qquicktransform) - # Limit set of tests to run for static Qt builds. if(QT_BUILD_MINIMAL_STATIC_TESTS) return() endif() + add_subdirectory(drawingmodes) +add_subdirectory(geometry) +add_subdirectory(nodes) +add_subdirectory(qquickpixmapcache) +add_subdirectory(qquicktransform) if(QT_FEATURE_private_tests) add_subdirectory(doc) add_subdirectory(examples) add_subdirectory(nokeywords) - add_subdirectory(propertyrequirements) - add_subdirectory(qquickanimations) - add_subdirectory(qquickapplication) - add_subdirectory(qquickbehaviors) - if(QT_FEATURE_qml_labs) - add_subdirectory(qquickboundaryrule) - endif() - add_subdirectory(qquickfontloader) - add_subdirectory(qquickfontloader_static) - add_subdirectory(qquickfontmetrics) - add_subdirectory(qquickimageprovider) - add_subdirectory(qquicklayouts) - add_subdirectory(qquickpath) - add_subdirectory(qquicksmoothedanimation) - add_subdirectory(qquickspringanimation) - add_subdirectory(qquickanimationcontroller) - add_subdirectory(qquickstyledtext) - add_subdirectory(qquickstates) - add_subdirectory(qquicksystempalette) - add_subdirectory(qquicktimeline) add_subdirectory(pointerhandlers) + add_subdirectory(propertyrequirements) if(QT_FEATURE_accessibility) add_subdirectory(qquickaccessible) endif() add_subdirectory(qquickanchors) add_subdirectory(qquickanimatedimage) + add_subdirectory(qquickanimatedsprite) + add_subdirectory(qquickanimationcontroller) + add_subdirectory(qquickanimations) add_subdirectory(qquickanimators) - add_subdirectory(qquickdynamicpropertyanimation) + add_subdirectory(qquickapplication) + add_subdirectory(qquickbehaviors) add_subdirectory(qquickborderimage) - add_subdirectory(qquickwindow) - add_subdirectory(qquickwindowcontainer) + if(QT_FEATURE_qml_labs) + add_subdirectory(qquickboundaryrule) + endif() + add_subdirectory(qquickcanvasitem) + add_subdirectory(qquickcolorgroup) add_subdirectory(qquickdeliveryagent) + if(QT_FEATURE_quick_designer) + add_subdirectory(qquickdesignersupport) + endif() add_subdirectory(qquickdrag) add_subdirectory(qquickdragattached) add_subdirectory(qquickdroparea) + add_subdirectory(qquickdynamicpropertyanimation) add_subdirectory(qquickflickable) add_subdirectory(qquickflipable) add_subdirectory(qquickfocusscope) + add_subdirectory(qquickfontloader) + add_subdirectory(qquickfontloader_static) + add_subdirectory(qquickfontmetrics) + if(QT_FEATURE_opengl) + add_subdirectory(qquickframebufferobject) + endif() add_subdirectory(qquickgraphicsinfo) add_subdirectory(qquickgridview) add_subdirectory(qquickimage) + add_subdirectory(qquickimageprovider) add_subdirectory(qquickitem) add_subdirectory(qquickitem2) add_subdirectory(qquickitemlayer) + add_subdirectory(qquicklayouts) add_subdirectory(qquicklistview) add_subdirectory(qquicklistview2) - if(QT_FEATURE_qml_labs) - add_subdirectory(qquicktableview) - endif() - add_subdirectory(qquicktreeview) add_subdirectory(qquickloader) add_subdirectory(qquickmousearea) add_subdirectory(qquickmultipointtoucharea) add_subdirectory(qquickpainteditem) - add_subdirectory(qquicksafearea) + add_subdirectory(qquickpalette) + add_subdirectory(qquickpath) add_subdirectory(qquickpathview) add_subdirectory(qquickpincharea) add_subdirectory(qquickpositioners) add_subdirectory(qquickrectangle) + add_subdirectory(qquickrendercontrol) add_subdirectory(qquickrepeater) + add_subdirectory(qquickrhiitem) + add_subdirectory(qquicksafearea) + add_subdirectory(qquickscreen) + add_subdirectory(qquickshadereffect) add_subdirectory(qquickshortcut) + add_subdirectory(qquicksmoothedanimation) + add_subdirectory(qquickspringanimation) + add_subdirectory(qquickspritesequence) + add_subdirectory(qquickstates) + add_subdirectory(qquickstyledtext) + add_subdirectory(qquicksystempalette) + if(QT_FEATURE_qml_labs) + add_subdirectory(qquicktableview) + endif() add_subdirectory(qquicktext) add_subdirectory(qquicktextdocument) add_subdirectory(qquicktextedit) add_subdirectory(qquicktextinput) - add_subdirectory(qquickvisualdatamodel) + add_subdirectory(qquicktimeline) + add_subdirectory(qquicktreeview) + add_subdirectory(qquickvaluetypes) add_subdirectory(qquickview) add_subdirectory(qquickview_extra) - add_subdirectory(qquickcanvasitem) - if(QT_FEATURE_quick_designer) - add_subdirectory(qquickdesignersupport) - endif() - add_subdirectory(qquickscreen) - add_subdirectory(touchmouse) - add_subdirectory(scenegraph) -if(QT_FEATURE_qml_labs) - add_subdirectory(sharedimage) -endif() - add_subdirectory(qquickcolorgroup) - add_subdirectory(qquickpalette) - add_subdirectory(qquickrendercontrol) - add_subdirectory(qquickshadereffect) - add_subdirectory(qquickanimatedsprite) - add_subdirectory(qquickspritesequence) - add_subdirectory(qquickrhiitem) + add_subdirectory(qquickvisualdatamodel) + add_subdirectory(qquickwindow) + add_subdirectory(qquickwindowcontainer) add_subdirectory(rendernode) - - if(QT_FEATURE_opengl) - add_subdirectory(qquickframebufferobject) + add_subdirectory(scenegraph) + if(QT_FEATURE_qml_labs) + add_subdirectory(sharedimage) endif() - add_subdirectory(softwarerenderer) + add_subdirectory(touchmouse) endif() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/draggableFlickableParent.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/draggableFlickableParent.qml new file mode 100644 index 0000000000..c9c5aec759 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/draggableFlickableParent.qml @@ -0,0 +1,27 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +// A DragHandler on the parent Item of a Flickable (ListView). +// When dragging starts within the ListView, the Flickable should scroll, +// and the DragHandler should NOT activate (QTBUG-75074, QTBUG-79238). +Item { + id: root + width: 400 + height: 400 + + DragHandler { } + + ListView { + anchors.fill: parent + model: 200 + delegate: Rectangle { + required property int modelData + width: ListView.view.width + height: 30 + color: modelData % 2 ? "lightgrey" : "grey" + Text { text: parent.modelData } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/tapOnItemAboveFlickable.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/tapOnItemAboveFlickable.qml new file mode 100644 index 0000000000..02de9671dc --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/data/tapOnItemAboveFlickable.qml @@ -0,0 +1,33 @@ +import QtQuick + +Item { +    width: 360 +    height: 360 + Flickable { +        id: flickable +        anchors.top: parent.top +        anchors.left: parent.left +        width: 300 + height: 300 +        contentWidth: rect.width + contentHeight: rect.height +        Rectangle { +            id: rect +            color: "beige" +            width: 300 + height: 650 + Text { + text: "flick with mouse or touch" + anchors.centerIn: parent + } +        } +    } + Rectangle { +        anchors.fill: flickable + color: tapHandler.pressed ? "lightsteelblue" : "aliceblue" + opacity: 0.5 +       TapHandler { + id: tapHandler + } +    } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp index 07e063f9f3..c8e1efb1a7 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/flickableinterop/tst_flickableinterop.cpp @@ -59,6 +59,9 @@ private slots: void pinchHandlerOnFlickable(); void nativeGesturePinchOnFlickableWithParentTapHandler_data(); void nativeGesturePinchOnFlickableWithParentTapHandler(); + void touchDraggableFlickableParent(); + void tapAboveFlickable_data(); + void tapAboveFlickable(); private: void createView(QScopedPointer &window, const char *fileName); @@ -807,7 +810,7 @@ void tst_FlickableInterop::pinchHandlerOnFlickable() QSignalSpy grabChangedSpy(touchscreen.get(), &QPointingDevice::grabChanged); QObject *grabber = nullptr; - connect(touchscreen.get(), &QPointingDevice::grabChanged, this, + connect(touchscreen.get(), &QPointingDevice::grabChanged, &window, [&grabber](QObject *g, QPointingDevice::GrabTransition transition, const QPointerEvent *, const QEventPoint &) { if (transition == QPointingDevice::GrabTransition::GrabExclusive) grabber = g; @@ -914,7 +917,7 @@ void tst_FlickableInterop::nativeGesturePinchOnFlickableWithParentTapHandler() QSignalSpy tapSpy(tapHandler, &QQuickTapHandler::tapped); QObject *grabber = nullptr; - connect(device, &QPointingDevice::grabChanged, this, + connect(device, &QPointingDevice::grabChanged, window.rootObject(), [&grabber](QObject *g, QPointingDevice::GrabTransition transition, const QPointerEvent *, const QEventPoint &) { if (transition == QPointingDevice::GrabTransition::GrabExclusive) grabber = g; @@ -972,6 +975,71 @@ void tst_FlickableInterop::nativeGesturePinchOnFlickableWithParentTapHandler() QCOMPARE(tapActiveSpy.size(), 0); } +void tst_FlickableInterop::touchDraggableFlickableParent() +{ + const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("draggableFlickableParent.qml"))); + QQuickFlickable *flickable = window.rootObject()->findChild(); // actually ListView + QVERIFY(flickable); + QQuickDragHandler *dragHandler = window.rootObject()->findChild(); + QVERIFY(dragHandler); + + // Drag one finger on the Flickable and make sure it flicks, not the DragHandler + QTest::QTouchEventSequence touchSeq = QTest::touchEvent(&window, touchscreen.get()); + QPoint p1(10, 190); + touchSeq.press(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + int beganFlicking = -1; + for (int i = 0; i < 10; ++i) { + p1 += QPoint(dragThreshold, -dragThreshold); + touchSeq.move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (flickable->isMovingVertically() && beganFlicking < 0) + beganFlicking = i; + QCOMPARE(dragHandler->active(), false); + } + QCOMPARE(beganFlicking, 2); + touchSeq.release(1, p1, &window).commit(); +} + + +void tst_FlickableInterop::tapAboveFlickable_data() +{ + QTest::addColumn("device"); + const QPointingDevice *constTouchscreen = touchscreen.get(); + + QTest::newRow("touchscreen") << constTouchscreen; + + // QTBUG-126812: mouse dragging through a sibling TapHandler item does not yet work. + // Touch works because TapHandler (DragThreshold policy) resets point.accepted=false + // for touch, keeping handlersOnly=false so Flickable receives the press. + // For mouse, TapHandler leaves accepted=true → handlersOnly=true → Flickable skipped. + // Maybe it could be fixed if Flickable used an internal passive-grab handler rather than + // relying on mousePressEvent being delivered through the normal item event path. + // QTest::newRow("primary") << QPointingDevice::primaryPointingDevice(); +} + +void tst_FlickableInterop::tapAboveFlickable() +{ + QFETCH(const QPointingDevice*, device); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("tapOnItemAboveFlickable.qml"))); + QQuickFlickable *flickable = window.rootObject()->findChild(); + QVERIFY(flickable); + QSignalSpy draggingChangedSpy(flickable, &QQuickFlickable::draggingChanged); + QQuickTapHandler *tapHandler = window.rootObject()->findChild(); + QVERIFY(tapHandler); + QSignalSpy pressedChangedSpy(tapHandler, &QQuickTapHandler::pressedChanged); + + // Press and drag: TapHandler needs only a passive grab to react; + // but Flickable should grab and be dragged + QQuickTest::pointerFlick(device, &window, 1, {20, 280}, {20, 20}, 100); + QCOMPARE(pressedChangedSpy.count(), 2); + QCOMPARE(draggingChangedSpy.count(), 2); +} + QTEST_MAIN(tst_FlickableInterop) #include "tst_flickableinterop.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickdraghandler/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickdraghandler/BLACKLIST index e555a4bb61..04c9693984 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickdraghandler/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickdraghandler/BLACKLIST @@ -7,3 +7,5 @@ android android [touchPassiveGrabbers] android +[unsuitableEventDuringDrag] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/BLACKLIST index 5866adf1d0..10233bebec 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/BLACKLIST @@ -2,3 +2,5 @@ qnx windows macos +[window] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/cursorShapeAfterDeletion.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/cursorShapeAfterDeletion.qml new file mode 100644 index 0000000000..35a4481175 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/cursorShapeAfterDeletion.qml @@ -0,0 +1,24 @@ +import QtQuick + +Rectangle { + id: root + width: 400 + height: 400 + objectName: "root" + + HoverHandler { + objectName: "rootHoverHandler" + cursorShape: Qt.OpenHandCursor + } + + Rectangle { + id: innerRect + objectName: "innerRect" + anchors.fill: parent + + HoverHandler { + objectName: "innerHoverHandler" + cursorShape: Qt.PointingHandCursor + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/twoHoverHandlersCursorShape.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/twoHoverHandlersCursorShape.qml new file mode 100644 index 0000000000..96f5d5ce48 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/twoHoverHandlersCursorShape.qml @@ -0,0 +1,25 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle { + id: root + width: 400 + height: 400 + objectName: "root" + + // Two HoverHandlers with different cursorShapes on the same item + // (typically, to have different cursors for mouse and stylus) + HoverHandler { + id: handler1 + objectName: "handler1" + cursorShape: Qt.CrossCursor + } + + HoverHandler { + id: handler2 + objectName: "handler2" + cursorShape: Qt.PointingHandCursor + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index 54bdb5abb5..c32e075d13 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -58,6 +58,8 @@ private slots: void effectivelyClips_data(); void effectivelyClips(); void grandChildOutOfBounds(); + void cursorShapeAfterDeletion(); + void twoHandlersCursorShapeReset(); private: void createView(QScopedPointer &window, const char *fileName); @@ -1079,6 +1081,85 @@ void tst_HoverHandler::grandChildOutOfBounds() #endif } +void tst_HoverHandler::cursorShapeAfterDeletion() +{ + // QTBUG-141870: parent HoverHandler's cursorShape must survive child deletion + if (isPlatformWayland()) + QSKIP("Wayland: QCursor::setPos() doesn't work."); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("cursorShapeAfterDeletion.qml"))); + QQuickItem *root = window.rootObject(); + QVERIFY(root); + + QQuickItem *innerRect = root->findChild("innerRect"); + QVERIFY(innerRect); + + // Move mouse in and verify inner handler's cursor wins + QTest::mouseMove(&window, QPoint(200, 200)); +#if QT_CONFIG(cursor) + QTRY_COMPARE(window.cursor().shape(), Qt::PointingHandCursor); +#endif + + // Delete the inner item + delete innerRect; + + // Nudge the mouse to trigger cursor update + QTest::mouseMove(&window, QPoint(201, 201)); + +#if QT_CONFIG(cursor) + // Root handler's cursor must still work + QTRY_COMPARE(window.cursor().shape(), Qt::OpenHandCursor); +#endif +} + +void tst_HoverHandler::twoHandlersCursorShapeReset() +{ + // Resetting one handler's cursorShape must not break the other handler on the same item. + // Two HoverHandlers with cursorShape on the same item is a supported pattern (e.g. to show + // different cursors for mouse vs. stylus via acceptedDevices). When one handler's + // cursorShape binding is cleared, hasCursorHandler on the parent item must stay true as + // long as any other handler on that item still has cursorShape set. + if (isPlatformWayland()) + QSKIP("Wayland: QCursor::setPos() doesn't work."); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("twoHoverHandlersCursorShape.qml"))); + QQuickItem *root = window.rootObject(); + QVERIFY(root); + + QQuickPointerHandler *handler1 = root->findChild("handler1"); + QQuickPointerHandler *handler2 = root->findChild("handler2"); + QVERIFY(handler1); + QVERIFY(handler2); + + QVERIFY(handler1->isCursorShapeExplicitlySet()); + QVERIFY(handler2->isCursorShapeExplicitlySet()); + + // Both handlers have cursorShape set, so hasCursorHandler must be true + QQuickItemPrivate *rootPriv = QQuickItemPrivate::get(root); + QVERIFY(rootPriv->hasCursorHandler); + + // Move the mouse over the item; handler1 was created last, so it wins among same-device handlers. + // (Of course, users should not depend on such an implementation detail: + // usually the only reason to have two handlers setting the cursor is if only one of + // them reacts at any given time to a specific pointing device, modifier or whatever.) + QTest::mouseMove(&window, QPoint(200, 200)); +#if QT_CONFIG(cursor) + QTRY_COMPARE(window.cursor().shape(), Qt::CrossCursor); +#endif + + // Reset handler1's cursorShape: handler2 should take over setting the cursor + handler1->resetCursorShape(); + QVERIFY(!handler1->isCursorShapeExplicitlySet()); + QVERIFY(handler2->isCursorShapeExplicitlySet()); + QVERIFY(rootPriv->hasCursorHandler); + QTest::mouseMove(&window, QPoint(201, 201)); +#if QT_CONFIG(cursor) + QTRY_COMPARE(window.cursor().shape(), Qt::PointingHandCursor); +#endif +} + QTEST_MAIN(tst_HoverHandler) #include "tst_qquickhoverhandler.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquicktaphandler/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquicktaphandler/BLACKLIST index 1559014480..050914019d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquicktaphandler/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquicktaphandler/BLACKLIST @@ -1,3 +1,5 @@ # QTBUG-103072 [gesturePolicyDragWithinBounds] android +[longPress] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickwheelhandler/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickwheelhandler/BLACKLIST index 2949d3371f..726174ebce 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickwheelhandler/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/pointerhandlers/qquickwheelhandler/BLACKLIST @@ -1,3 +1,8 @@ # QTBUG-81993 [singleHandler] macos ci +wayland +[nestedHandler] +wayland +[blocking] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/behaviorOnValueTypeProperties.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/behaviorOnValueTypeProperties.qml new file mode 100644 index 0000000000..9372fc8b2d --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/behaviorOnValueTypeProperties.qml @@ -0,0 +1,30 @@ +import QtQuick + +Rectangle { + id: rect + + Timer { + running: true + interval: 1 + + onTriggered: { + rectRotation.axis.x = .2; + rectRotation.axis.y = .4; + rectRotation.axis.z = .8; + } + } + + + transform: Rotation { + id: rectRotation + axis { + x: 1 + y: 0 + z: 0 + + Behavior on x { NumberAnimation { duration: 100 } } + Behavior on y { NumberAnimation { duration: 100 } } + Behavior on z { NumberAnimation { duration: 100 } } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/duplicated.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/duplicated.qml index 15518a05cf..4a0c87ca7d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/duplicated.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/data/duplicated.qml @@ -28,4 +28,14 @@ Rectangle { x: 200 } } + + transform: Rotation { + id: rectRotation + Behavior on axis { + ScriptAction { script: console.log("toplevel axis") } + } + axis { + Behavior on x { NumberAnimation { duration: 100 } } + } + } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp index 1fd074b4df..2a94b4de4f 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickbehaviors/tst_qquickbehaviors.cpp @@ -1,19 +1,25 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include + #include "bindable.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + class tst_qquickbehaviors : public QQmlDataTest { Q_OBJECT @@ -34,6 +40,7 @@ private slots: void valueType(); void emptyBehavior(); void duplicatedBehavior(); + void behaviorOnValueTypeProperties(); void explicitSelection(); void nonSelectingBehavior(); void reassignedAnimation(); @@ -227,8 +234,14 @@ void tst_qquickbehaviors::emptyBehavior() void tst_qquickbehaviors::duplicatedBehavior() { QTest::failOnWarning(QRegularExpression(".*")); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, - QRegularExpression("Attempting to set another interceptor on.*")); + QTest::ignoreMessage( + QtMsgType::QtWarningMsg, + QRegularExpression( + "Attempting to set another interceptor on [^ ]+ property x - unsupported")); + QTest::ignoreMessage( + QtMsgType::QtWarningMsg, + QRegularExpression( + "Attempting to set another interceptor on [^ ]+ property axis - unsupported")); QQmlEngine engine; QQmlComponent c(&engine, testFileUrl("duplicated.qml")); QScopedPointer rect(qobject_cast(c.create())); @@ -238,6 +251,35 @@ void tst_qquickbehaviors::duplicatedBehavior() QQuickItemPrivate::get(rect.data())->setState("moved"); } +void tst_qquickbehaviors::behaviorOnValueTypeProperties() +{ + QTest::failOnWarning(); + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("behaviorOnValueTypeProperties.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + std::unique_ptr obj(c.create()); + QVERIFY(obj); + + QQuickRectangle *rect = qobject_cast(obj.get()); + QVERIFY(rect); + + QQmlListProperty transformList = rect->transform(); + QCOMPARE(transformList.count(&transformList), 1); + QQuickTransform *transform = transformList.at(&transformList, 0); + QVERIFY(transform); + + QQuickRotation *rotation = qobject_cast(transform); + QVERIFY(rotation); + + QCOMPARE(rotation->axis().x(), 1); + QCOMPARE(rotation->axis().y(), 0); + QCOMPARE(rotation->axis().z(), 0); + + QTRY_COMPARE(rotation->axis().x(), .2f); + QTRY_COMPARE(rotation->axis().y(), .4f); + QTRY_COMPARE(rotation->axis().z(), .8f); +} + void tst_qquickbehaviors::explicitSelection() { QQmlEngine engine; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickfocusscope/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickfocusscope/BLACKLIST index 7a1e82e98d..cc10c6e088 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickfocusscope/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickfocusscope/BLACKLIST @@ -1,3 +1,4 @@ # QTBUG-103086 [canvasFocus] android +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickframebufferobject/tst_qquickframebufferobject.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickframebufferobject/tst_qquickframebufferobject.cpp index 7741401b5a..35305dc500 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickframebufferobject/tst_qquickframebufferobject.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickframebufferobject/tst_qquickframebufferobject.cpp @@ -188,8 +188,7 @@ void tst_QQuickFramebufferObject::testThatStuffWorks() item->setMsaa(msaa); view.show(); - view.requestActivate(); - QVERIFY(QTest::qWaitForWindowExposed(&view)); + QVERIFY(QTest::qWaitForWindowActive(&view)); if (QGuiApplication::platformName() == "offscreen" && view.rendererInterface()->graphicsApi() == QSGRendererInterface::Software) @@ -232,8 +231,7 @@ void tst_QQuickFramebufferObject::testInvalidate() item->setTextureSize(QSize(200, 200)); view.show(); - view.requestActivate(); - QVERIFY(QTest::qWaitForWindowExposed(&view)); + QVERIFY(QTest::qWaitForWindowActive(&view)); if (QGuiApplication::platformName() == "offscreen" && view.rendererInterface()->graphicsApi() == QSGRendererInterface::Software) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index af670e4bba..54b998bef5 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -4091,7 +4091,6 @@ void tst_QQuickGridView::columnCount() QQuickView window; window.setSource(testFileUrl("gridview4.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQuickGridView *view = qobject_cast(window.rootObject()); @@ -6747,7 +6746,6 @@ void tst_QQuickGridView::keyNavigationEnabled() QScopedPointer window(createView()); window->setSource(testFileUrl("keyNavigationEnabled.qml")); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickGridView *gridView = qobject_cast(window->rootObject()); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/BLACKLIST index 6723c98812..f559fc5652 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/BLACKLIST @@ -4,3 +4,7 @@ xcb # QTBUG-100991 [mousePropagationToParent] android +[lastFocusChangeReason] +wayland +[embeddedInWidgetsFocus] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/data/grabImage.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/data/grabImage.qml index 7252900ef5..eedc0600e3 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/data/grabImage.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/data/grabImage.qml @@ -8,6 +8,8 @@ Window { property bool finishedSuccessfuly: false property var itemGrabResult: null + property url grabbedUrl + property var grabbedImage Item { id: child @@ -17,9 +19,11 @@ Window { height: 50 Component.onCompleted: { - const grabbed = child.grabToImage(function(itemGrabResult) { - finishedSuccessfuly = true - root.itemGrabResult = itemGrabResult + const grabbed = child.grabToImage(function(result) { + root.itemGrabResult = result + root.grabbedUrl = result.url + root.grabbedImage = result.image + root.finishedSuccessfuly = true }) } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 55116d485e..7a0bbf2d1b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -11,6 +11,7 @@ #include "private/qquickfocusscope_p.h" #include "private/qquickrectangle_p.h" #include "private/qquickitem_p.h" +#include #include #include #ifdef Q_OS_WIN @@ -2697,6 +2698,8 @@ void tst_qquickitem::grabImage() QTRY_VERIFY(window->property("finishedSuccessfuly").toBool()); QQuickItemGrabResult *result = window->property("itemGrabResult").value(); QVERIFY(result); + QVERIFY(!window->property("grabbedUrl").toUrl().isEmpty()); + QVERIFY(!window->property("grabbedImage").value().isNull()); } void tst_qquickitem::invisibleItemCursorShape() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/data/DisabledRootItemWithTextInput.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/data/DisabledRootItemWithTextInput.qml new file mode 100644 index 0000000000..86c50e91e5 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/data/DisabledRootItemWithTextInput.qml @@ -0,0 +1,12 @@ +import QtQuick + +Window { + id: root + visible: true + contentItem.enabled: false + + TextInput { + text: "text field " + enabled + } +} + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index 4694034b13..e785c48f48 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -150,6 +150,7 @@ private slots: void embeddedInWidgetsFocus(); #endif void gcIntegration(); + void focusWorksAfterEnablingRootItem(); private: QQmlEngine engine; @@ -3915,6 +3916,9 @@ void tst_QQuickItem::grab() QVERIFY(root); QQuickItem *item = root->findChild("myItem"); QVERIFY(item); + + // The actual grabbed item size can be different, depending on DPR. + const auto dpr = view.screen()->devicePixelRatio(); #if QT_CONFIG(opengl) { // Default size (item is 100x100) QSharedPointer result = item->grabToImage(); @@ -3923,7 +3927,9 @@ void tst_QQuickItem::grab() QVERIFY(!result->url().isEmpty()); QImage image = result->image(); QCOMPARE(image.pixel(0, 0), qRgb(255, 0, 0)); - QCOMPARE(image.pixel(99, 99), qRgb(0, 0, 255)); + // Take DPR into account + const QPoint bottomRight = QPoint(99, 99) * dpr; + QCOMPARE(image.pixel(bottomRight), qRgb(0, 0, 255)); } { // Smaller size @@ -3934,9 +3940,25 @@ void tst_QQuickItem::grab() QVERIFY(!result->url().isEmpty()); QImage image = result->image(); QCOMPARE(image.pixel(0, 0), qRgb(255, 0, 0)); - QCOMPARE(image.pixel(49, 49), qRgb(0, 0, 255)); + // Take DPR into account + const QPoint bottomRight = QPoint(49, 49) * dpr; + QCOMPARE(image.pixel(bottomRight), qRgb(0, 0, 255)); } #endif + { + // Make sure that we do not deadlock when calling getters in the + // slot connected to the ready() signal. + QSharedPointer result = item->grabToImage(); + QImage capturedImage; + QUrl capturedUrl; + connect(result.data(), &QQuickItemGrabResult::ready, result.data(), + [result, &capturedImage, &capturedUrl]() { + capturedImage = result->image(); + capturedUrl = result->url(); + }); + QTRY_VERIFY(!capturedImage.isNull()); + QVERIFY(!capturedUrl.isEmpty()); + } } void tst_QQuickItem::isAncestorOf() @@ -4709,6 +4731,39 @@ void tst_QQuickItem::gcIntegration() QVERIFY(!observer); } +void tst_QQuickItem::focusWorksAfterEnablingRootItem() // QTBUG-135624 +{ + QQmlEngine e; + QQmlComponent comp(&e); + comp.loadUrl(testFileUrl("DisabledRootItemWithTextInput.qml")); + QQuickWindow *window = qobject_cast(comp.create()); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowFocused(window)); + + QList childItems = window->contentItem()->childItems(); + QVERIFY(!childItems.isEmpty()); + QQuickTextInput *textInput = qobject_cast(childItems.first()); + QVERIFY(textInput); + + // Should be set to false in the QML file + QCOMPARE(window->contentItem()->isEnabled(), false); + QCOMPARE(textInput->hasFocus(), false); + + // The click won't do anything when the item is effectively disabled + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, textInput->mapToScene(textInput->boundingRect().center()).toPoint()); + QCOMPARE(textInput->hasFocus(), false); + QCOMPARE(window->activeFocusItem(), nullptr); + + // Enable the root item and verify that focus works normally + window->contentItem()->setEnabled(true); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, textInput->mapToScene(textInput->boundingRect().center()).toPoint()); + QTRY_COMPARE(textInput->hasFocus(), true); + QCOMPARE(window->activeFocusItem(), textInput); + textInput->clear(); + QTest::keyClick(window, Qt::Key_A); + QTRY_COMPARE(textInput->text(), "a"); +} + QTEST_MAIN(tst_QQuickItem) #include "tst_qquickitem.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml index d0924651f6..5334f7a977 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml @@ -258,6 +258,9 @@ Item { flexboxLayout.destroy() } + function itemRect(item) { + return [item.x, item.y, item.width, item.height] + } // alignItem affects flexbox layout container cross-axis function test_flexAlignItems() { let flexboxLayout = createTemporaryObject(dynamicFlexboxLayoutComponent, container) @@ -327,6 +330,17 @@ Item { compare(rectItem2.x, 0) compare(rectItem2.y, rectItem1.implicitHeight) + // QTBUG-142021: item with fillX misaligns along the primary axis + flexboxLayout.alignItems = FlexboxLayout.AlignCenter + flexboxLayout.wrap = FlexboxLayout.NoWrap + rectItem1.Layout.fillHeight = true + rectItem2.Layout.preferredWidth = 10 + rectItem2.Layout.preferredHeight = 30 + rectItem2.Layout.fillWidth = true + waitForItemPolished(flexboxLayout) + compare(itemRect(rectItem1), [ 0, 0, 20, 40]) + compare(itemRect(rectItem2), [20, 5, 40, 30]) + rectItem1.destroy() rectItem2.destroy() flexboxLayout.destroy() @@ -793,5 +807,109 @@ Item { compare(rectItem2.width, 20) compare(rectItem2.height, 100) } + + Component { + id: flexboxWithSizeHintsLayoutComponent + FlexboxLayout { + gap: 0 + Rectangle { + Layout.minimumWidth: 1 + Layout.minimumHeight: 1 + implicitWidth: 10 + implicitHeight: 10 + Layout.maximumWidth: 100 + Layout.maximumHeight: 100 + } + Rectangle { + Layout.minimumWidth: 2 + Layout.minimumHeight: 2 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 200 + Layout.maximumHeight: 200 + } + } + } + + function test_sizeHints() { + let flexboxLayout = createTemporaryObject(flexboxWithSizeHintsLayoutComponent, container) + waitForItemPolished(flexboxLayout) + // Test size hints of main axis. Since nothing has Layout.fillWidth: true, no items will flex + // It is therefore considered as a fixed-width layout + expectFailContinue("", "Layout.minimumWidth is 2." + + " FlexboxLayout should take fillWidth/sizePolicy into consideration when normalizing size hints") + compare(flexboxLayout.Layout.minimumWidth, 30) + compare(flexboxLayout.implicitWidth, 30) + expectFailContinue("", "Layout.maximumWidth is ∞." + + " FlexboxLayout should take fillWidth/sizePolicy into consideration when normalizing size hints") + compare(flexboxLayout.Layout.maximumWidth, 30) + + // Make first item flexible + let item0 = flexboxLayout.children[0] + item0.Layout.fillWidth = true + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 3) + compare(flexboxLayout.implicitWidth, 30) + + // The semantic of the implicit value of Layout.maximumWidth is unclear. Currently it is infinite. + // But this is inconsistent with RowLayout: + // RowLayout will have its maximumWidth be the sum of all the childrens *effective* maximum widths (+spacings) + // (effective maximum width is just: (Layout.fillWidth ? Layout.maximumWidth : implicitWidth), but this is + // beside the point here) + + // If the FlexboxLayout have the RowLayout semantic, and it is a child of another layout, the parent layout + // will respect its maximumWidth (so FlexboxLayout.justifyContent will be pointless) + + // However, if the FlexboxLayout is anchored to a parent Item with anchors.fill, the FlexboxLayout can become + // wider than its maximumWidth. In that scenario, FlexboxLayout.justifyContent has a purpose + compare(flexboxLayout.Layout.maximumWidth, 300) + + // restore back to no flexing + item0.Layout.fillWidth = false + waitForItemPolished(flexboxLayout) + + // Test size hints of cross-axis + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitHeight, 20) + // actually semantic of this is unclear as described for Layout.maximumWidth above + compare(flexboxLayout.Layout.maximumHeight, 200) + + // ----- gap: 5 ----- + // Now add a gap, test if size hints are adjusted accordingly + flexboxLayout.gap = 5 + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 8) + compare(flexboxLayout.implicitWidth, 35) + compare(flexboxLayout.Layout.maximumWidth, 305) + + // If the FlexboxLayout is not wrapping, gap shouldn't change the cross-axis size hint + // Test size hints of cross-axis + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitHeight, 20) + // actually semantic of this is unclear as described for Layout.maximumWidth above + compare(flexboxLayout.Layout.maximumHeight, 200) + + + // flex-wrap: wrap + /* + if (wrap) + minimumWidth: width of the largest minimumwidth + minimumHeight: height of the largest minimumHeight + + implicitWidth: width for being close to the golden ratio (+ gaps) + implicitWidth: height for being close to the golden ratio. (+ gaps) + + maximumWidth: sum of all maximumWidths (+ gaps) + maximumHeight: sum of all maximumHeights (+ gaps) + */ + flexboxLayout.wrap = FlexboxLayout.Wrap + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 2) + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitWidth, 30 + 5) + compare(flexboxLayout.implicitHeight, 20) + compare(flexboxLayout.Layout.maximumWidth, 300 + 5) + compare(flexboxLayout.Layout.maximumHeight, 300 + 5) + } } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/BLACKLIST index e463fc0b45..16ca84f801 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/BLACKLIST @@ -7,3 +7,23 @@ macos ci android [contentHeightWithDelayRemove] android +[sectionsDragOutsideBounds] +wayland +[marginsResize] +wayland +[snapToItem] +wayland +[snapOneItemResize_QTBUG_43555] +wayland +[snapOneItem] +wayland +[flickBeyondBounds] +wayland +[flickBothDirections] +wayland +[outsideViewportChangeNotAffectingView] +wayland +[testProxyModelChangedAfterMove] +wayland +[dragOverFloatingHeaderOrFooter] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 1e63bdaf07..2a1eeed467 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -9374,7 +9374,6 @@ void tst_QQuickListView::keyNavigationEnabled() QScopedPointer window(createView()); window->setSource(testFileUrl("keyNavigationEnabled.qml")); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickListView *listView = qobject_cast(window->rootObject()); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview2/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview2/BLACKLIST index 0162bbc852..bc046271a3 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview2/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicklistview2/BLACKLIST @@ -3,3 +3,7 @@ android # QTBUG-104471 [flickDuringFlicking] android # QTBUG-104471 macos ci # QTBUG-105190 +[fetchMore] +wayland +[visibleBoundToCountGreaterThanZero] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index a0a2943a9e..b995d239e6 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -2489,7 +2489,7 @@ void tst_QQuickMouseArea::settingHiddenInPressUngrabs() // QTBUG-74987 QScopedPointer window(qmlobject_cast(c.create())); QVERIFY(window.data()); window->show(); - window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickMouseArea *catArea = window->findChild("cat"); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST index 51142f53e7..ebaaf30da3 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickmultipointtoucharea/BLACKLIST @@ -2,8 +2,10 @@ ubuntu-20.04 ubuntu-22.04 ubuntu-24.04 xcb # QTBUG-129032 +wayland [nested] ubuntu-20.04 ubuntu-22.04 ubuntu-24.04 xcb # QTBUG-129032 +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 7d0a8a3af3..51d432c2d2 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -2322,7 +2322,7 @@ void tst_QQuickPathView::nestedinFlickable() // no further moves after the initial move beyond threshold QTest::mouseRelease(window.data(), Qt::LeftButton, Qt::NoModifier, QPoint(73,219)); - QTRY_COMPARE(movingSpy.size(), 2); + QTRY_COMPARE_WITH_TIMEOUT(movingSpy.size(), 2, 2s); QTRY_COMPARE(moveEndedSpy.size(), 1); QCOMPARE(moveStartedSpy.size(), 1); // Flickable should not handle this diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpixmapcache/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpixmapcache/BLACKLIST index 00d8eb5619..dbe6d20cea 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpixmapcache/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickpixmapcache/BLACKLIST @@ -1,3 +1,6 @@ # QTBUG-118064 [slowDevice] * + +[slowDeviceInterrupted] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp index bf0ca0d9b4..ed3d3bb518 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshadereffect/tst_qquickshadereffect.cpp @@ -172,7 +172,7 @@ void tst_qquickshadereffect::testPropertyMappings() { QQuickView view; view.setSource(QUrl(QStringLiteral("qrc:/data/testProperties.qml"))); - QTRY_VERIFY(view.rootObject()->property("finished").toBool()); + QTRY_VERIFY_WITH_TIMEOUT(view.rootObject()->property("finished").toBool(), 2s); } QTEST_MAIN(tst_qquickshadereffect) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp index a307c40109..a35612e088 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickshortcut/tst_qquickshortcut.cpp @@ -95,7 +95,6 @@ void tst_QQuickShortcut::standardShortcuts() QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QObject *shortcut = window->property("shortcut").value(); @@ -228,7 +227,6 @@ void tst_QQuickShortcut::shortcuts() QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); window->setProperty("shortcuts", shortcuts); @@ -305,7 +303,6 @@ void tst_QQuickShortcut::sequence() QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); window->setProperty("shortcuts", shortcuts); @@ -402,7 +399,6 @@ void tst_QQuickShortcut::context() QVERIFY(activeWindow); activeWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(activeWindow)); - activeWindow->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(activeWindow)); activeWindow->setProperty("shortcuts", activeWindowShortcuts); @@ -472,7 +468,6 @@ void tst_QQuickShortcut::matcher() QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); window->setProperty("shortcuts", QVariantList() << shortcut); @@ -515,7 +510,6 @@ void tst_QQuickShortcut::multiple() QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QObject *shortcut = window->property("shortcut").value(); @@ -586,7 +580,6 @@ void tst_QQuickShortcut::contextChange() QVERIFY(activeWindow); activeWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(activeWindow)); - activeWindow->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(activeWindow)); QTest::keyPress(activeWindow, key, modifiers); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/CMakeLists.txt index 2534730a2a..c71f7d2725 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/CMakeLists.txt @@ -21,6 +21,7 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qquicktableview SOURCES + delegatechoosermodel.h testmodel.h tst_qquicktableview.cpp LIBRARIES diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/checkalwaysemit.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/checkalwaysemit.qml index 463e21342c..232d98fa69 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/checkalwaysemit.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/checkalwaysemit.qml @@ -34,7 +34,7 @@ Item { color: "lightgray" border.width: 1 - property string modelDataFromIndex: tableView.model.dataFromSerializedIndex(index) + property string modelDataFromIndex: tableView.model.dataFromFlatIndex(index) property string modelDataBinding: modelData Text { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/delegatechooser.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/delegatechooser.qml new file mode 100644 index 0000000000..ac59645534 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/data/delegatechooser.qml @@ -0,0 +1,42 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + width: 640 + height: 450 + + property alias tableView: tableView + + TableView { + id: tableView + + anchors.fill: parent + + delegate: DelegateChooser { + role: "choice" + + DelegateChoice { + roleValue: 0 + + delegate: Rectangle { + implicitWidth: 50 + implicitHeight: 50 + color: model.choice0Data + + Component.onCompleted: tableView.model.delegate_created(0) + } + } + + DelegateChoice { + roleValue: 1 + delegate: Text { + text: model.choice1Data + + Component.onCompleted: tableView.model.delegate_created(1) + } + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/delegatechoosermodel.h b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/delegatechoosermodel.h new file mode 100644 index 0000000000..29b794b310 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/delegatechoosermodel.h @@ -0,0 +1,93 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef DELEGATECHOOSERMODEL_H +#define DELEGATECHOOSERMODEL_H + +#include +#include + +#include + +class DelegateChooserModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum Role { + ChoiceRole = Qt::UserRole, + Choice0DataRole, + Choice1DataRole, + }; + + DelegateChooserModel(int rows, int columns, QObject *parent = nullptr) : QAbstractTableModel(parent), m_rows(rows), m_columns(columns) + { + } + + int rowCount(const QModelIndex &parent) const override { return parent.isValid() ? 0 : m_rows; } + int columnCount(const QModelIndex &parent) const override { return parent.isValid() ? 0 : m_columns; } + + QHash roleNames() const override + { + using namespace Qt::StringLiterals; + return { + { Qt::DisplayRole, "display"_ba }, + { ChoiceRole, "choice"_ba }, + { Choice0DataRole, "choice0Data"_ba }, + { Choice1DataRole, "choice1Data"_ba }, + }; + } + + QVariant data(const QModelIndex &index, int role) const override + { + using namespace Qt::StringLiterals; + + QVariant ret; + + if (!index.isValid()) { + return ret; + } + + switch (role) { + case Qt::DisplayRole: + ret = m_choice ? u"Text"_s : u"Rectangle"_s; + break; + case ChoiceRole: + ret = m_choice; + break; + case Choice0DataRole: + ret = QColor{ Qt::red }; + break; + case Choice1DataRole: + ret = u"Text"_s; + break; + } + + return ret; + } + + Q_INVOKABLE void switchChoice() + { + if (m_choice) { + m_choice = 0; + } else { + m_choice = 1; + } + + m_choice_delegates_count.fill(0); + + Q_EMIT dataChanged(index(0, 0), index(m_rows - 1, m_columns - 1), { ChoiceRole }); + } + + Q_INVOKABLE void delegate_created(int choice) { ++m_choice_delegates_count.at(static_cast(choice)); } + + const std::array &choice_delegates_count() const { return m_choice_delegates_count; } + +private: + int m_rows; + int m_columns; + int m_choice{ }; + std::array m_choice_delegates_count{ }; +}; + +#endif diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/testmodel.h b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/testmodel.h index 21ed7e03a5..26179ee4cc 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/testmodel.h +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/testmodel.h @@ -46,9 +46,9 @@ public: ret = 42; break; case Qt::DisplayRole: { - int serializedIndex = index.row() + (index.column() * m_columns); - if (modelData.contains(serializedIndex)) - ret = modelData.value(serializedIndex); + const QPoint cell(index.column(), index.row()); + if (modelData.contains(cell)) + ret = modelData.value(cell); else ret = QStringLiteral("%1").arg(index.row()); } break; @@ -59,11 +59,11 @@ public: return ret; } - Q_INVOKABLE QVariant dataFromSerializedIndex(int index) const + Q_INVOKABLE QVariant dataFromFlatIndex(int flatIndex) const { - if (modelData.contains(index)) - return modelData.value(index); - return QString(); + const int row = flatIndex % m_rows; + const int column = flatIndex / m_rows; + return modelData.value(QPoint(column, row)); } QHash roleNames() const override @@ -86,8 +86,7 @@ public: for (int r = 0; r < span.height(); ++r) { const int changedRow = cell.y() + r; const int changedColumn = cell.x() + c; - const int serializedIndex = changedRow + (changedColumn * m_rows); - modelData.insert(serializedIndex, string); + modelData.insert(QPoint(changedColumn, changedRow), string); } } @@ -103,6 +102,7 @@ public: beginInsertRows(parent, row, row + count - 1); m_rows += count; + shiftModelDataRows(row, count); endInsertRows(); return true; } @@ -114,12 +114,7 @@ public: beginRemoveRows(parent, row, row + count - 1); m_rows -= count; - for (int c = 0; c < m_columns; ++c) { - for (int r = 0; r < count; ++r) { - const int serializedIndex = (row + r) + (c * m_rows); - modelData.remove(serializedIndex); - } - } + shiftModelDataRows(row + count, -count); endRemoveRows(); return true; } @@ -131,6 +126,7 @@ public: beginInsertColumns(parent, column, column + count - 1); m_columns += count; + shiftModelDataColumns(column, count); endInsertColumns(); return true; } @@ -142,6 +138,7 @@ public: beginRemoveColumns(parent, column, column + count - 1); m_columns -= count; + shiftModelDataColumns(column + count, -count); endRemoveColumns(); return true; } @@ -155,11 +152,38 @@ public: void swapRows(int row1, int row2) { layoutAboutToBeChanged(); - Q_ASSERT(modelData.contains(row1)); - Q_ASSERT(modelData.contains(row2)); - const QString tmp = modelData[row1]; - modelData[row1] = modelData[row2]; - modelData[row2] = tmp; + for (int c = 0; c < m_columns; ++c) { + const QPoint cell1(c, row1); + const QPoint cell2(c, row2); + const QString cell1Data = modelData.value(cell1); + + // Do the data swap, but since modelData is not supposed to contain + // cells with empty strings, we need to check for this. + if (modelData.contains(cell2)) + modelData[cell1] = modelData[cell2]; + else + modelData.remove(cell1); + + if (!cell1Data.isEmpty()) + modelData[cell2] = cell1Data; + else + modelData.remove(cell2); + } + + // Since this is a row reorder and not just a data update, we must + // remap any persistent model indexes pointing into the swapped rows. + // This is required by the layoutAboutToBeChanged/layoutChanged contract. + QModelIndexList from, to; + for (const QModelIndex &idx : persistentIndexList()) { + if (idx.row() == row1) { + from << idx; + to << createIndex(row2, idx.column()); + } else if (idx.row() == row2) { + from << idx; + to << createIndex(row1, idx.column()); + } + } + changePersistentIndexList(from, to); layoutChanged(); } @@ -203,11 +227,50 @@ signals: void columnCountChanged(); private: + + void shiftModelDataRows(int firstRow, int delta) + { + // Shift all modelData entries whose row >= firstRow by delta + // (positive delta = insert, negative delta = remove). + QHash updated; + for (auto it = modelData.begin(); it != modelData.end(); ++it) { + const int row = it.key().y(); + if (delta < 0) { + const int removedCount = -delta; + if (row < firstRow && row >= firstRow - removedCount) + continue; // row was removed + } + const int newRow = row < firstRow ? row : row + delta; + updated.insert(QPoint(it.key().x(), newRow), it.value()); + } + modelData = std::move(updated); + } + + void shiftModelDataColumns(int firstColumn, int delta) + { + // Shift all modelData entries whose column >= firstColumn by delta. + // (positive delta = insert, negative delta = remove). + QHash updated; + for (auto it = modelData.begin(); it != modelData.end(); ++it) { + const int column = it.key().x(); + if (delta < 0) { + const int removedCount = -delta; + if (column < firstColumn && column >= firstColumn - removedCount) + continue; // column was removed + } + const int newColumn = column < firstColumn ? column : column + delta; + updated.insert(QPoint(newColumn, it.key().y()), it.value()); + } + modelData = std::move(updated); + } + +private: + int m_rows = 0; int m_columns = 0; bool m_dataCanBeFetched = false; bool m_useCustomRoleNames = false; - QHash modelData; + QHash modelData; Qt::ItemFlags m_flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; }; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index ee74195ec4..226042355d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -23,6 +23,7 @@ #include #include +#include "delegatechoosermodel.h" #include "testmodel.h" #include @@ -160,6 +161,7 @@ private slots: void checkIfDelegatesAreReused_data(); void checkIfDelegatesAreReused(); void checkIfDelegatesAreReusedAsymmetricTableSize(); + void verifyThatOnlyRemovedRowsArePooled(); void checkContextProperties_data(); void checkContextProperties(); void checkContextPropertiesQQmlListProperyModel_data(); @@ -320,6 +322,8 @@ private slots: void checkCellModelIdxAfterReorder(); void checkEditAfterReorder(); void checkSelectionAfterReorder(); + + void delegateChooserDataChange(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -2650,37 +2654,53 @@ void tst_QQuickTableView::checkRowAndColumnChangedButNotIndex() void tst_QQuickTableView::checkThatWeAlwaysEmitChangedUponItemReused() { - // Check that we always emit changes to index when we reuse an item, even - // if it doesn't change. This is needed since the model can have changed - // row or column count while the item was in the pool, which means that - // any data referred to by the index property inside the delegate - // will change too. So we need to refresh any bindings to index. - // QTBUG-79209 + // Verify that index, row and column are always re-emitted when an item is + // reused, even if the flat index value hasn't changed. This matters because + // the model may reset with swapped row and column counts while the item sits + // in the pool: the same flat index then maps to a different (row, column) pair, + // so all bindings that depend on those properties must be refreshed. QTBUG-79209 LOAD_TABLEVIEW("checkalwaysemit.qml"); - TestModel model(1, 1); + TestModel model(1, 2); tableView->setModel(QVariant::fromValue(&model)); - model.setModelData(QPoint(0, 0), QSize(1, 1), "old value"); + + const QPoint modifiedCell{1, 0}; + const QString text = "Lorem Ipsum"_L1; + + model.setModelData(modifiedCell, QSize(1, 1), text); WAIT_UNTIL_POLISHED; - const auto reuseItem = tableViewPrivate->loadedTableItem(QPoint(0, 0))->item; - const auto context = qmlContext(reuseItem.data()); + { + // Check that the context properties of the modified cell are as expected + const auto item = tableViewPrivate->loadedTableItem(modifiedCell)->item; + const auto context = qmlContext(item.data()); + const int flatIndex = modifiedCell.x() * model.rowCount() + modifiedCell.y(); + QCOMPARE(flatIndex, 1); + QCOMPARE(context->contextProperty("index").toInt(), flatIndex); + QCOMPARE(context->contextProperty("row").toInt(), modifiedCell.y()); + QCOMPARE(context->contextProperty("column").toInt(), modifiedCell.x()); + QCOMPARE(context->contextProperty("modelDataFromIndex").toString(), text); + } - // Remove the cell/row that has "old value" as model data, and - // add a new one right after. The new cell will have the same - // index, but with no model data assigned. - // This change will not be detected by items in the pool. But since - // we emit indexChanged when the item is reused, it will be updated then. - model.removeRow(0); - model.insertRow(0); + // Add a new row at the bottom, below the modified cell. This shouldn't affect + // its row and column position, nor the model data fetched from the + // modelDataFromIndex() binding. But since the new row changes row and column + // count, its flat index changes. + model.insertRow(1); WAIT_UNTIL_POLISHED; - QCOMPARE(context->contextProperty("index").toInt(), 0); - QCOMPARE(context->contextProperty("row").toInt(), 0); - QCOMPARE(context->contextProperty("column").toInt(), 0); - QCOMPARE(context->contextProperty("modelDataFromIndex").toString(), ""); + { + const auto item = tableViewPrivate->loadedTableItem(modifiedCell)->item; + const auto context = qmlContext(item.data()); + const int flatIndex = modifiedCell.x() * model.rowCount() + modifiedCell.y(); + QCOMPARE(flatIndex, 2); + QCOMPARE(context->contextProperty("index").toInt(), flatIndex); + QCOMPARE(context->contextProperty("row").toInt(), modifiedCell.y()); + QCOMPARE(context->contextProperty("column").toInt(), modifiedCell.x()); + QCOMPARE(context->contextProperty("modelDataFromIndex").toString(), text); + } } void tst_QQuickTableView::checkChangingModelFromDelegate() @@ -8195,13 +8215,17 @@ void tst_QQuickTableView::typedModelData() for (int i = 0; i < 4; ++i) { if (i == 0) { - for (int j = 0; j < 3; ++j) { + for (int j = 0; j < 2; ++j) { QTest::ignoreMessage( QtWarningMsg, "Could not find any constructor for value type QQmlPointFValueType " "to call with value QVariant(double, 11)"); } + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlPointFValueType " + "to call with value 11"); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + ":60:9: Unable to assign double to QPointF")); @@ -8744,6 +8768,105 @@ void tst_QQuickTableView::checkSelectionAfterReorder() QCOMPARE(selectionModel.hasSelection(), false); } +void tst_QQuickTableView::delegateChooserDataChange() +{ + LOAD_TABLEVIEW("delegatechooser.qml"); + + const int rows = 2; + const int columns = 2; + DelegateChooserModel model{ rows, columns }; + tableView->setModel(QVariant::fromValue(&model)); + + WAIT_UNTIL_POLISHED; + + QCOMPARE(model.choice_delegates_count()[0], rows * columns); + QCOMPARE(model.choice_delegates_count()[1], 0); + + model.switchChoice(); + WAIT_UNTIL_POLISHED; + + QCOMPARE(model.choice_delegates_count()[0], 0); + QCOMPARE(model.choice_delegates_count()[1], rows * columns); + + tableView->setModel({ }); +} + +void tst_QQuickTableView::verifyThatOnlyRemovedRowsArePooled() +{ + // Check that we don't put delegate items above or below a removed or inserted row + // into the reuse pool. Instead those items should be unaffected by the model changes + // (other than changes to the flat index, row and column). + LOAD_TABLEVIEW("plaintableview.qml"); + + TestModel model(3, 2); + tableView->setModel(QVariant::fromValue(&model)); + + WAIT_UNTIL_POLISHED; + + QList> initialItems; + QList pooledObjects; + QList reusedObjects; + + // Create a few helper functions that we can use further down in the test. + // getItems() returns a list of loaded delegate items that corresponds to + // the cells given as argument. + auto getItems = [&](const QList &cells) { + QList> result; + for (const auto &cell : cells) + result.append(tableViewPrivate->loadedTableItem(cell)->item); + return result; + }; + + // Create a function that tracks which objects get pooled or reused, so we + // can verify that none of the unrelated cells are affected by the model changes. + auto verifyInitialItemsNotPooled = [&]() { + for (const auto &item : initialItems) { + QVERIFY(!pooledObjects.contains(item.data())); + QVERIFY(!reusedObjects.contains(item.data())); + } + }; + connect(tableViewPrivate->tableModel, &QQmlInstanceModel::itemPooled, + this, [&](int, QObject *obj) { pooledObjects.append(obj); }); + connect(tableViewPrivate->tableModel, &QQmlInstanceModel::itemReused, + this, [&](int, QObject *obj) { reusedObjects.append(obj); }); + + // Create a function that verifies that index, row and column context properties on + // each initial item reflect the expected values. The row that is below the removed + // row will have those updated. The serialized index uses (col * numRows) + row. + using Props = std::tuple; // {index, row, column} + auto verifyContextProperties = [&](const QList &expected) { + for (int i = 0; i < initialItems.size(); ++i) { + const auto [expIndex, expRow, expColumn] = expected[i]; + const auto ctx = qmlContext(initialItems[i].data()); + QCOMPARE(ctx->contextProperty("index").toInt(), expIndex); + QCOMPARE(ctx->contextProperty("row").toInt(), expRow); + QCOMPARE(ctx->contextProperty("column").toInt(), expColumn); + } + }; + + // Store the items in the rows above and below the row that is about + // to be removed. Those items should never be deleted or reused. + initialItems = getItems({{0,0}, {1,0}, {0,2}, {1,2}}); + + // Remove the row in the center, and verify that the rows + // above and below didn't change delegate items. + model.removeRow(1); + WAIT_UNTIL_POLISHED; + + QCOMPARE(getItems({{0,0}, {1,0}, {0,1}, {1,1}}), initialItems); + verifyContextProperties({{0,0,0}, {2,0,1}, {1,1,0}, {3,1,1}}); + verifyInitialItemsNotPooled(); + + // Insert a row in the center, and then verify that the rows + // above and below didn't change delegate items. + model.insertRow(1); + WAIT_UNTIL_POLISHED; + + QCOMPARE(getItems({{0,0}, {1,0}, {0,2}, {1,2}}), initialItems); + verifyContextProperties({{0,0,0}, {3,0,1}, {2,2,0}, {5,2,1}}); + verifyInitialItemsNotPooled(); +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/BLACKLIST index 0f9aa30cbe..c1b64be9ee 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/BLACKLIST @@ -1,8 +1,3 @@ - -[contentSize] -windows gcc -msvc-2019 -msvc-2022 [hAlignVisual] sles # QTQAINFRA-4127 diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/styledTextLinkInColumnLayout.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/styledTextLinkInColumnLayout.qml new file mode 100644 index 0000000000..c02291ba97 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/styledTextLinkInColumnLayout.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + width: 640 + height: 480 + + Text { + objectName: "styledTextLink" + text: 'this text has a link in it' + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + onLinkHovered: function(link) { hoveredLinkText.text = link } + } + + Text { + objectName: "hoveredLink" + id: hoveredLinkText + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectBaselineAlignment.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectBaselineAlignment.qml new file mode 100644 index 0000000000..f6a43cd9b5 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectBaselineAlignment.qml @@ -0,0 +1,30 @@ +import QtQuick + +Item { + width: 400 + height: 300 + Text { + objectName: "baselineText" + x: 0; y: 0 + width: 400; height: 100 + text: "hello" + textFormat: Text.RichText + font.pixelSize: 30 + } + Text { + objectName: "bottomText" + x: 0; y: 100 + width: 400; height: 100 + text: "hello" + textFormat: Text.RichText + font.pixelSize: 30 + } + Text { + objectName: "topText" + x: 0; y: 200 + width: 400; height: 100 + text: "hello" + textFormat: Text.RichText + font.pixelSize: 30 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectMixedFontAlignment.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectMixedFontAlignment.qml new file mode 100644 index 0000000000..da23473bc6 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/data/textObjectMixedFontAlignment.qml @@ -0,0 +1,22 @@ +import QtQuick + +Item { + width: 400 + height: 200 + Text { + objectName: "baselineText" + x: 0; y: 0 + width: 400; height: 100 + text: "hello" + textFormat: Text.RichText + font.pixelSize: 50 + } + Text { + objectName: "bottomText" + x: 0; y: 100 + width: 400; height: 100 + text: "hello" + textFormat: Text.RichText + font.pixelSize: 50 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/tst_qquicktext.cpp index f25f6b3a18..087674bbad 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -96,10 +96,13 @@ private slots: void linkInteraction_data(); void linkInteraction(); + void styledTextLinkInColumnLayout(); + void implicitSize_data(); void implicitSize(); void implicitSizeChangeRewrap(); void implicitSizeMaxLineCount(); + void implicitWidthAfterWidthChange(); void dependentImplicitSizes(); void contentSize(); void implicitSizeBinding_data(); @@ -171,6 +174,10 @@ private slots: void textObjectRespectsDpr_data(); void textObjectRespectsDpr(); + void clearTruncated(); + + void textObjectBaselineAlignment(); + void textObjectBaselineVsBottomAlignment(); private: QStringList standard; @@ -1080,7 +1087,7 @@ void tst_qquicktext::hAlignImplicitWidth() QQuickView view(testFileUrl("hAlignImplicitWidth.qml")); view.setFlags(view.flags() | Qt::WindowStaysOnTopHint); // Prevent being obscured by other windows. view.show(); - view.requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&view)); QVERIFY(QTest::qWaitForWindowExposed(&view)); QQuickText *text = view.rootObject()->findChild("textItem"); @@ -1580,7 +1587,6 @@ void tst_qquicktext::underline() { QQuickView view(testFileUrl("underline.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); @@ -1593,7 +1599,6 @@ void tst_qquicktext::overline() { QQuickView view(testFileUrl("overline.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); @@ -1606,7 +1611,6 @@ void tst_qquicktext::strikeout() { QQuickView view(testFileUrl("strikeout.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); @@ -2175,6 +2179,35 @@ void tst_qquicktext::linkInteraction() QCOMPARE(textObject->linkAt(-1, -1), QString()); } +// QTBUG-131441: styled text link detection is incorrect when Text +// is in ColumnLayout with Layout.fillWidth and non-left alignment +void tst_qquicktext::styledTextLinkInColumnLayout() +{ + QQuickView view; + QVERIFY(QQuickTest::showView(view, testFileUrl("styledTextLinkInColumnLayout.qml"))); + + QQuickText *textItem = view.rootObject()->findChild("styledTextLink"); + QVERIFY(textItem); + QVERIFY(textItem->width() > textItem->contentWidth()); + + const QString plainText("this text has a link in it"); + const TextMetrics metrics(plainText); + const QSizeF bounds(textItem->width(), textItem->height()); + const int linkCharPos = plainText.indexOf("link") + 2; // middle of "link" + const QRectF linkRect = metrics.characterRectangle(linkCharPos, Qt::AlignHCenter, Qt::AlignTop, bounds); + const QString expectedLink = "http://qt-project.org/test"; + + QCOMPARE(textItem->linkAt(linkRect.center().x(), linkRect.center().y()), expectedLink); + QCOMPARE(textItem->linkAt(textItem->width() - 10, linkRect.center().y()), QString()); + + // Verify that onLinkHovered actually fires by hovering over the link + QQuickText *hoveredLinkText = view.rootObject()->findChild("hoveredLink"); + QVERIFY(hoveredLinkText); + const QPoint linkCenter = textItem->mapToScene(linkRect.center()).toPoint(); + QTest::mouseMove(&view, linkCenter); + QTRY_COMPARE(hoveredLinkText->text(), expectedLink); +} + void tst_qquicktext::baseUrl() { QUrl localUrl("file:///tests/text.qml"); @@ -2455,6 +2488,44 @@ void tst_qquicktext::implicitSizeMaxLineCount() QCOMPARE_EQ(textObject->implicitWidth(), referenceWidth); } +// implicitWidth should not grow after width is constrained and then released +void tst_qquicktext::implicitWidthAfterWidthChange() +{ + // Test that implicitWidth remains stable when the item width is constrained + // and then released, for StyledText with
tags, maximumLineCount, wrap, and elide. + QScopedPointer textObject(new QQuickText); + textObject->setTextFormat(QQuickText::StyledText); + textObject->setText(QStringLiteral("First line
Second line
" + "11111111111111111111111111111111111111111111111111111111111" + "11111111111111111111111111111111111111111")); + textObject->setMaximumLineCount(2); + textObject->setWrapMode(QQuickText::Wrap); + textObject->setElideMode(QQuickText::ElideRight); + textObject->componentComplete(); + + // Get the unconstrained implicitWidth + const qreal originalImplicitWidth = textObject->implicitWidth(); + QVERIFY(originalImplicitWidth > 0); + + // Constrain the width to something narrow enough to trigger wrapping + const qreal narrowWidth = originalImplicitWidth * 0.3; + textObject->setWidth(narrowWidth); + + // The implicitWidth should not change just because we constrained the width + const qreal constrainedImplicitWidth = textObject->implicitWidth(); + QCOMPARE_EQ(constrainedImplicitWidth, originalImplicitWidth); + + // Now widen the width back + textObject->setWidth(originalImplicitWidth * 2); + const qreal widenedImplicitWidth = textObject->implicitWidth(); + QCOMPARE_EQ(widenedImplicitWidth, originalImplicitWidth); + + // Release the width constraint entirely + textObject->resetWidth(); + const qreal afterImplicitWidth = textObject->implicitWidth(); + QCOMPARE_EQ(afterImplicitWidth, originalImplicitWidth); +} + void tst_qquicktext::dependentImplicitSizes() { QQmlComponent component(&engine, testFile("implicitSizes.qml")); @@ -2550,6 +2621,9 @@ void tst_qquicktext::contentSize() QVERIFY(textObject->contentWidth() <= textObject->width()); QVERIFY(textObject->contentHeight() < textObject->height()); QCOMPARE(spySize.size(), 3); + #if (defined(Q_CC_MSVC) && Q_CC_MSVC > 1920) || defined(Q_CC_MINGW) + QEXPECT_FAIL("", "QTBUG-88646", Continue); + #endif QCOMPARE(spyWidth.size(), 3); QCOMPARE(spyHeight.size(), 2); int spyCount = 3; @@ -2568,6 +2642,9 @@ void tst_qquicktext::contentSize() QVERIFY(textObject->contentWidth() > textObject->width()); QVERIFY(textObject->contentHeight() > textObject->height()); QCOMPARE(spySize.size(), ++spyCount); + #if (defined(Q_CC_MSVC) && Q_CC_MSVC > 1920) || defined(Q_CC_MINGW) + QEXPECT_FAIL("", "QTBUG-88646", Continue); + #endif QCOMPARE(spyWidth.size(), spyCount); QCOMPARE(spyHeight.size(), 3); } @@ -3169,7 +3246,6 @@ void tst_qquicktext::lineLaidOutRelayout() QScopedPointer window(createView(testFile("lineLayoutRelayout.qml"))); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickText *myText = window->rootObject()->findChild("myText"); @@ -3201,7 +3277,6 @@ void tst_qquicktext::lineLaidOutFontUpdate() QScopedPointer window(createView(testFile("lineLayoutFontUpdate.qml"))); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); auto *myText = window->rootObject()->findChild("exampleText"); @@ -4613,7 +4688,6 @@ void tst_qquicktext::htmlLists() textObject->setText(text); view->show(); - view->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(view.get())); QCOMPARE(textPrivate->extra->doc->lineCount(), nbLines); @@ -5097,6 +5171,192 @@ void tst_qquicktext::textObjectRespectsDpr() QCOMPARE(customTextObject.drawnRect.size(), customTextObject.intrinsicSize(textDocument, 0, {})); } +class ColoredTextObject : public QObject, public QTextObjectInterface +{ + Q_OBJECT + Q_INTERFACES(QTextObjectInterface) + +public: + using QObject::QObject; + + QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &) override + { + return {20, 20}; + } + + void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *, + int, const QTextFormat &) override + { + painter->fillRect(rect, Qt::red); + } +}; + +static int findFirstRedPixelY(const QImage &img, int startY, int endY) +{ + for (int y = startY; y < endY; ++y) { + for (int x = 0; x < img.width(); ++x) { + const QColor c = img.pixelColor(x, y); + if (c.red() > 200 && c.green() < 50 && c.blue() < 50) + return y; + } + } + return -1; +} + +void tst_qquicktext::textObjectBaselineAlignment() +{ + SKIP_IF_NO_WINDOW_GRAB; + + QScopedPointer window(new QQuickView); + window->setSource(testFileUrl("textObjectBaselineAlignment.qml")); + QTRY_COMPARE(window->status(), QQuickView::Ready); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + auto *baselineText = window->rootObject()->findChild("baselineText"); + auto *bottomText = window->rootObject()->findChild("bottomText"); + auto *topText = window->rootObject()->findChild("topText"); + QVERIFY(baselineText); + QVERIFY(bottomText); + QVERIFY(topText); + + auto insertObject = [](QQuickText *text, QTextCharFormat::VerticalAlignment alignment, + ColoredTextObject *obj) { + auto *textPrivate = QQuickTextPrivate::get(text); + auto *textDocument = textPrivate->extra->doc; + auto *documentLayout = textDocument->documentLayout(); + + documentLayout->registerHandler(QTextCharFormat::UserObject, obj); + + QTextCursor cursor(textDocument); + QTextCharFormat format; + format.setObjectType(QTextCharFormat::UserObject); + format.setVerticalAlignment(alignment); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + + text->invalidate(); + }; + + const qreal dpr = window->devicePixelRatio(); + const int regionHeight = int(100 * dpr); + + ColoredTextObject baselineObj, bottomObj, topObj; + insertObject(baselineText, QTextCharFormat::AlignBaseline, &baselineObj); + insertObject(bottomText, QTextCharFormat::AlignBottom, &bottomObj); + insertObject(topText, QTextCharFormat::AlignTop, &topObj); + + // Wait for all objects to be rendered + QImage grab; + QTRY_VERIFY_WITH_TIMEOUT([&]() { + grab = window->grabWindow(); + return findFirstRedPixelY(grab, 0, regionHeight) >= 0 + && findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) >= 0 + && findFirstRedPixelY(grab, 2 * regionHeight, 3 * regionHeight) >= 0; + }(), 5000); + + // Find Y position of the red object relative to each Text item's region + const int baselineY = findFirstRedPixelY(grab, 0, regionHeight); + const int bottomY = findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) - regionHeight; + const int topY = findFirstRedPixelY(grab, 2 * regionHeight, 3 * regionHeight) - 2 * regionHeight; + + QVERIFY2(baselineY >= 0, "AlignBaseline object not found in grab"); + QVERIFY2(bottomY >= 0, "AlignBottom object not found in grab"); + QVERIFY2(topY >= 0, "AlignTop object not found in grab"); + + // With a single font size, AlignBaseline and AlignBottom produce + // approximately the same position. They may differ by 1px due to + // QFontMetrics (integer) vs QFixed rounding in line descent. + QVERIFY2(qAbs(baselineY - bottomY) <= 1, + qPrintable(QString("AlignBaseline (%1) and AlignBottom (%2) should be within 1px") + .arg(baselineY).arg(bottomY))); + + // Sanity check: AlignTop should differ from AlignBottom/AlignBaseline + QVERIFY2(topY != bottomY, "AlignTop should differ from AlignBottom"); +} + +void tst_qquicktext::textObjectBaselineVsBottomAlignment() +{ + SKIP_IF_NO_WINDOW_GRAB; + + QScopedPointer window(new QQuickView); + window->setSource(testFileUrl("textObjectMixedFontAlignment.qml")); + QTRY_COMPARE(window->status(), QQuickView::Ready); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + auto *baselineText = window->rootObject()->findChild("baselineText"); + auto *bottomText = window->rootObject()->findChild("bottomText"); + QVERIFY(baselineText); + QVERIFY(bottomText); + + // Insert inline objects with a small font (pixelSize 12) into text items + // that use a large font (pixelSize 50). This makes line.descent() (from + // the 50px text) much larger than QFontMetrics(format.font()).descent() + // (from the 12px object format), so AlignBaseline and AlignBottom must + // produce different Y positions. + auto insertObject = [](QQuickText *text, QTextCharFormat::VerticalAlignment alignment, + ColoredTextObject *obj) { + auto *textPrivate = QQuickTextPrivate::get(text); + auto *textDocument = textPrivate->extra->doc; + auto *documentLayout = textDocument->documentLayout(); + + documentLayout->registerHandler(QTextCharFormat::UserObject, obj); + + QTextCursor cursor(textDocument); + QTextCharFormat format; + format.setObjectType(QTextCharFormat::UserObject); + format.setVerticalAlignment(alignment); + QFont f; + f.setPixelSize(12); + format.setFont(f); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + + text->invalidate(); + }; + + const qreal dpr = window->devicePixelRatio(); + const int regionHeight = int(100 * dpr); + + ColoredTextObject baselineObj, bottomObj; + insertObject(baselineText, QTextCharFormat::AlignBaseline, &baselineObj); + insertObject(bottomText, QTextCharFormat::AlignBottom, &bottomObj); + + QImage grab; + QTRY_VERIFY_WITH_TIMEOUT([&]() { + grab = window->grabWindow(); + return findFirstRedPixelY(grab, 0, regionHeight) >= 0 + && findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) >= 0; + }(), 5000); + + const int baselineY = findFirstRedPixelY(grab, 0, regionHeight); + const int bottomY = findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) - regionHeight; + + QVERIFY2(baselineY >= 0, "AlignBaseline object not found in grab"); + QVERIFY2(bottomY >= 0, "AlignBottom object not found in grab"); + + // With mixed font sizes, AlignBaseline (uses font descent) and + // AlignBottom (uses line descent) must differ + QVERIFY2(baselineY != bottomY, + qPrintable(QString("AlignBaseline (%1) and AlignBottom (%2) should differ " + "with mixed font sizes").arg(baselineY).arg(bottomY))); + + // AlignBottom should be lower (larger Y) since line.descent() > font descent + QVERIFY2(bottomY > baselineY, + qPrintable(QString("AlignBottom (%1) should be below AlignBaseline (%2)") + .arg(bottomY).arg(baselineY))); +} + +void tst_qquicktext::clearTruncated() +{ + QQmlComponent component(&engine, testFile("ellipsisText.qml")); + QScopedPointer object(component.create()); + QQuickText *text = qobject_cast(object.data()); + QVERIFY(text); + QVERIFY(text->truncated()); + text->setText(""); + QVERIFY(!text->truncated()); +} + QT_END_NAMESPACE QTEST_MAIN(tst_qquicktext) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/BLACKLIST index 1b9a15349e..2e090ce180 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/BLACKLIST @@ -15,6 +15,7 @@ android android [cursorVisible] android +wayland [transparentSelectionColor] android diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/selectedTextColorOrderedList.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/selectedTextColorOrderedList.qml new file mode 100644 index 0000000000..e43b298bdb --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/selectedTextColorOrderedList.qml @@ -0,0 +1,17 @@ +import QtQuick + +Rectangle { + width: 320 + height: 240 + color: "white" + TextEdit { + id: textEdit + objectName: "textEdit" + anchors.fill: parent + textFormat: TextEdit.RichText + selectedTextColor: "red" + selectionColor: "green" + font.pixelSize: 20 + text: "
  1. abc
  2. def
" + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectBaselineAlignment.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectBaselineAlignment.qml new file mode 100644 index 0000000000..089835d691 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectBaselineAlignment.qml @@ -0,0 +1,30 @@ +import QtQuick + +Item { + width: 400 + height: 300 + TextEdit { + objectName: "baselineText" + x: 0; y: 0 + width: 400; height: 100 + text: "hello" + textFormat: TextEdit.RichText + font.pixelSize: 30 + } + TextEdit { + objectName: "bottomText" + x: 0; y: 100 + width: 400; height: 100 + text: "hello" + textFormat: TextEdit.RichText + font.pixelSize: 30 + } + TextEdit { + objectName: "topText" + x: 0; y: 200 + width: 400; height: 100 + text: "hello" + textFormat: TextEdit.RichText + font.pixelSize: 30 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectMixedFontAlignment.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectMixedFontAlignment.qml new file mode 100644 index 0000000000..5f6d258b8a --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/data/textObjectMixedFontAlignment.qml @@ -0,0 +1,22 @@ +import QtQuick + +Item { + width: 400 + height: 200 + TextEdit { + objectName: "baselineText" + x: 0; y: 0 + width: 400; height: 100 + text: "hello" + textFormat: TextEdit.RichText + font.pixelSize: 50 + } + TextEdit { + objectName: "bottomText" + x: 0; y: 100 + width: 400; height: 100 + text: "hello" + textFormat: TextEdit.RichText + font.pixelSize: 50 + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index c2edad8cc5..e78bb2d9f4 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -247,6 +247,10 @@ private slots: void textObjectRespectsDpr_data(); void textObjectRespectsDpr(); + void textObjectBaselineAlignment(); + void textObjectBaselineVsBottomAlignment(); + void selectedTextColorOrderedList(); + private: void simulateKeys(QWindow *window, const QList &keys); #if QT_CONFIG(shortcut) @@ -7142,6 +7146,212 @@ void tst_qquicktextedit::textObjectRespectsDpr() QCOMPARE(customTextObject.drawnRect.size(), customTextObject.intrinsicSize(textDocument, 0, {})); } +class ColoredTextObject : public QObject, public QTextObjectInterface +{ + Q_OBJECT + Q_INTERFACES(QTextObjectInterface) + +public: + using QObject::QObject; + + QSizeF intrinsicSize(QTextDocument *, int, const QTextFormat &) override + { + return {20, 20}; + } + + void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *, + int, const QTextFormat &) override + { + painter->fillRect(rect, Qt::red); + } +}; + +static int findFirstRedPixelY(const QImage &img, int startY, int endY) +{ + for (int y = startY; y < endY; ++y) { + for (int x = 0; x < img.width(); ++x) { + const QColor c = img.pixelColor(x, y); + if (c.red() > 200 && c.green() < 50 && c.blue() < 50) + return y; + } + } + return -1; +} + +void tst_qquicktextedit::textObjectBaselineAlignment() +{ + SKIP_IF_NO_WINDOW_GRAB; + + QScopedPointer window(new QQuickView); + window->setSource(testFileUrl("textObjectBaselineAlignment.qml")); + QTRY_COMPARE(window->status(), QQuickView::Ready); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + auto *baselineText = window->rootObject()->findChild("baselineText"); + auto *bottomText = window->rootObject()->findChild("bottomText"); + auto *topText = window->rootObject()->findChild("topText"); + QVERIFY(baselineText); + QVERIFY(bottomText); + QVERIFY(topText); + + auto insertObject = [](QQuickTextEdit *textEdit, QTextCharFormat::VerticalAlignment alignment, + ColoredTextObject *obj) { + auto *textDocument = textEdit->textDocument()->textDocument(); + auto *documentLayout = textDocument->documentLayout(); + + documentLayout->registerHandler(QTextCharFormat::UserObject, obj); + + QTextCursor cursor(textDocument); + QTextCharFormat format; + format.setObjectType(QTextCharFormat::UserObject); + format.setVerticalAlignment(alignment); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + + textEdit->invalidate(); + }; + + const qreal dpr = window->devicePixelRatio(); + const int regionHeight = int(100 * dpr); + + ColoredTextObject baselineObj, bottomObj, topObj; + insertObject(baselineText, QTextCharFormat::AlignBaseline, &baselineObj); + insertObject(bottomText, QTextCharFormat::AlignBottom, &bottomObj); + insertObject(topText, QTextCharFormat::AlignTop, &topObj); + + // Wait for all objects to be rendered + QImage grab; + QTRY_VERIFY_WITH_TIMEOUT([&]() { + grab = window->grabWindow(); + return findFirstRedPixelY(grab, 0, regionHeight) >= 0 + && findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) >= 0 + && findFirstRedPixelY(grab, 2 * regionHeight, 3 * regionHeight) >= 0; + }(), 5000); + + // Find Y position of the red object relative to each TextEdit's region + const int baselineY = findFirstRedPixelY(grab, 0, regionHeight); + const int bottomY = findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) - regionHeight; + const int topY = findFirstRedPixelY(grab, 2 * regionHeight, 3 * regionHeight) - 2 * regionHeight; + + QVERIFY2(baselineY >= 0, "AlignBaseline object not found in grab"); + QVERIFY2(bottomY >= 0, "AlignBottom object not found in grab"); + QVERIFY2(topY >= 0, "AlignTop object not found in grab"); + + // With a single font size, AlignBaseline and AlignBottom produce + // approximately the same position. They may differ by 1px due to + // QFontMetrics (integer) vs QFixed rounding in line descent. + QVERIFY2(qAbs(baselineY - bottomY) <= 1, + qPrintable(QString("AlignBaseline (%1) and AlignBottom (%2) should be within 1px") + .arg(baselineY).arg(bottomY))); + + // Sanity check: AlignTop should differ from AlignBottom/AlignBaseline + QVERIFY2(topY != bottomY, "AlignTop should differ from AlignBottom"); +} + +void tst_qquicktextedit::textObjectBaselineVsBottomAlignment() +{ + SKIP_IF_NO_WINDOW_GRAB; + + QScopedPointer window(new QQuickView); + window->setSource(testFileUrl("textObjectMixedFontAlignment.qml")); + QTRY_COMPARE(window->status(), QQuickView::Ready); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + auto *baselineText = window->rootObject()->findChild("baselineText"); + auto *bottomText = window->rootObject()->findChild("bottomText"); + QVERIFY(baselineText); + QVERIFY(bottomText); + + // Insert inline objects with a small font (pixelSize 12) into text items + // that use a large font (pixelSize 50). This makes line.descent() (from + // the 50px text) much larger than QFontMetrics(format.font()).descent() + // (from the 12px object format), so AlignBaseline and AlignBottom must + // produce different Y positions. + auto insertObject = [](QQuickTextEdit *textEdit, QTextCharFormat::VerticalAlignment alignment, + ColoredTextObject *obj) { + auto *textDocument = textEdit->textDocument()->textDocument(); + auto *documentLayout = textDocument->documentLayout(); + + documentLayout->registerHandler(QTextCharFormat::UserObject, obj); + + QTextCursor cursor(textDocument); + QTextCharFormat format; + format.setObjectType(QTextCharFormat::UserObject); + format.setVerticalAlignment(alignment); + QFont f; + f.setPixelSize(12); + format.setFont(f); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + + textEdit->invalidate(); + }; + + const qreal dpr = window->devicePixelRatio(); + const int regionHeight = int(100 * dpr); + + ColoredTextObject baselineObj, bottomObj; + insertObject(baselineText, QTextCharFormat::AlignBaseline, &baselineObj); + insertObject(bottomText, QTextCharFormat::AlignBottom, &bottomObj); + + QImage grab; + QTRY_VERIFY_WITH_TIMEOUT([&]() { + grab = window->grabWindow(); + return findFirstRedPixelY(grab, 0, regionHeight) >= 0 + && findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) >= 0; + }(), 5000); + + const int baselineY = findFirstRedPixelY(grab, 0, regionHeight); + const int bottomY = findFirstRedPixelY(grab, regionHeight, 2 * regionHeight) - regionHeight; + + QVERIFY2(baselineY >= 0, "AlignBaseline object not found in grab"); + QVERIFY2(bottomY >= 0, "AlignBottom object not found in grab"); + + // With mixed font sizes, AlignBaseline (uses font descent) and + // AlignBottom (uses line descent) must differ + QVERIFY2(baselineY != bottomY, + qPrintable(QString("AlignBaseline (%1) and AlignBottom (%2) should differ " + "with mixed font sizes").arg(baselineY).arg(bottomY))); + + // AlignBottom should be lower (larger Y) since line.descent() > font descent + QVERIFY2(bottomY > baselineY, + qPrintable(QString("AlignBottom (%1) should be below AlignBaseline (%2)") + .arg(bottomY).arg(baselineY))); +} + +void tst_qquicktextedit::selectedTextColorOrderedList() +{ + SKIP_IF_NO_WINDOW_GRAB; + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("selectedTextColorOrderedList.qml"))); + QQuickTextEdit *textEdit = window.rootObject()->findChild("textEdit"); + QVERIFY(textEdit); + + // Select "ab" (partial selection within first list item) + textEdit->select(0, 2); + + QImage img = window.grabWindow(); + QCOMPARE(img.isNull(), false); + + // Scan the entire first line of the TextEdit for red pixels. + // The selected text "ab" should be rendered in red (selectedTextColor). + const QRectF cursorRect = textEdit->positionToRectangle(1); + const QPointF offset = textEdit->mapToScene(QPointF(0, 0)); + const int lineY = int(offset.y() + cursorRect.center().y()); + + bool foundRed = false; + for (int x = 0; x < img.width() && !foundRed; ++x) { + QColor c = img.pixelColor(x, lineY); + if (c.red() > 200 && c.green() < 80 && c.blue() < 80) + foundRed = true; + } + QVERIFY2(foundRed, + qPrintable(QString("No red pixels found on line y=%1; " + "selectedTextColor was not applied to selected list text") + .arg(lineY))); +} + QT_END_NAMESPACE QTEST_MAIN(tst_qquicktextedit) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index 8364f6e240..6b1a649d4d 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -1781,7 +1781,6 @@ void tst_qquicktextinput::positionAt() QQuickView window(testFileUrl("positionAt.qml")); QVERIFY(window.rootObject() != nullptr); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQuickTextInput *textinputObject = qobject_cast(window.rootObject()); @@ -1871,7 +1870,6 @@ void tst_qquicktextinput::maxLength() QQuickView window(testFileUrl("maxLength.qml")); QVERIFY(window.rootObject() != nullptr); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQuickTextInput *textinputObject = qobject_cast(window.rootObject()); @@ -2220,7 +2218,6 @@ void tst_qquicktextinput::inputMethods() { QQuickView window(testFileUrl("inputmethods.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); // test input method hints @@ -2342,7 +2339,6 @@ void tst_qquicktextinput::signal_accepted() { QQuickView window(testFileUrl("signal_accepted.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QVERIFY(window.rootObject() != nullptr); @@ -2384,7 +2380,6 @@ void tst_qquicktextinput::signal_editingfinished() { QQuickView window(testFileUrl("signal_editingfinished.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QVERIFY(window.rootObject() != nullptr); @@ -2454,7 +2449,6 @@ void tst_qquicktextinput::signal_textEdited() { QQuickWindow window; window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQuickTextInput *input = new QQuickTextInput(window.contentItem()); @@ -2705,7 +2699,6 @@ void tst_qquicktextinput::copyAndPasteKeySequence() QQuickWindow window; textInput->setParentItem(window.contentItem()); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); // copy and paste @@ -2805,7 +2798,6 @@ void tst_qquicktextinput::middleClickPaste() QQuickView window(testFileUrl("mouseselectionmode_default.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QVERIFY(window.rootObject() != nullptr); @@ -2871,7 +2863,6 @@ void tst_qquicktextinput::cursorDelegate() QFETCH(QUrl, source); QQuickView view(source); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickTextInput *textInputObject = view.rootObject()->findChild("textInputObject"); QVERIFY(textInputObject != nullptr); @@ -2990,7 +2981,6 @@ void tst_qquicktextinput::remoteCursorDelegate() view.rootContext()->setContextProperty("contextDelegate", &component); view.setSource(testFileUrl("cursorTestRemote.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickTextInput *textInputObject = view.rootObject()->findChild("textInputObject"); QVERIFY(textInputObject != nullptr); @@ -3346,7 +3336,6 @@ void tst_qquicktextinput::echoMode() { QQuickView window(testFileUrl("echoMode.qml")); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QVERIFY(window.rootObject() != nullptr); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/CMakeLists.txt new file mode 100644 index 0000000000..3b1a39882e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qquickvaluetypes LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qquickvaluetypes + SOURCES + tst_qquickvaluetypes.cpp + LIBRARIES + Qt::Gui + Qt::Qml + Qt::QuickPrivate +) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/tst_qquickvaluetypes.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/tst_qquickvaluetypes.cpp new file mode 100644 index 0000000000..c0db209072 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickvaluetypes/tst_qquickvaluetypes.cpp @@ -0,0 +1,99 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include + +#include + +#include +#include +#include +#include +#include +#include + +// Exercise the value type transformation that qmlsc does in direct mode +// when converting a constructible value type wrapper (e.g. QQuickColorValueType) +// to its underlying type (e.g. QColor). +template +Underlying valueTypeTransform(Arg &&arg) +{ + return [](auto &&arg) { + ValueType wrapper(std::forward(arg)); + return Underlying(reinterpret_cast(wrapper)); + }(std::forward(arg)); +} + +class tst_qquickvaluetypes : public QObject +{ + Q_OBJECT + +private slots: + void colorFromString(); + void colorFromColor(); + void vector2dFromVector2d(); + void vector3dFromVector3d(); + void vector4dFromVector4d(); + void quaternionFromQuaternion(); + void fontFromFont(); +}; + +void tst_qquickvaluetypes::colorFromString() +{ + const QColor result = + valueTypeTransform(QStringLiteral("#dddddd")); + QCOMPARE(result, QColor("#dddddd")); + + const QColor red = + valueTypeTransform(QStringLiteral("red")); + QCOMPARE(red, QColor(Qt::red)); +} + +void tst_qquickvaluetypes::colorFromColor() +{ + const QColor input(64, 128, 192, 255); + const QColor result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +void tst_qquickvaluetypes::vector2dFromVector2d() +{ + const QVector2D input(1.5f, 2.5f); + const QVector2D result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +void tst_qquickvaluetypes::vector3dFromVector3d() +{ + const QVector3D input(1.0f, 2.0f, 3.0f); + const QVector3D result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +void tst_qquickvaluetypes::vector4dFromVector4d() +{ + const QVector4D input(1.0f, 2.0f, 3.0f, 4.0f); + const QVector4D result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +void tst_qquickvaluetypes::quaternionFromQuaternion() +{ + const QQuaternion input(1.0f, 0.5f, 0.25f, 0.125f); + const QQuaternion result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +void tst_qquickvaluetypes::fontFromFont() +{ + QFont input; + input.setFamily(QStringLiteral("Arial")); + input.setPixelSize(16); + input.setBold(true); + const QFont result = valueTypeTransform(input); + QCOMPARE(result, input); +} + +QTEST_MAIN(tst_qquickvaluetypes) + +#include "tst_qquickvaluetypes.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/BLACKLIST index b1341ca8ec..77739502ad 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/BLACKLIST @@ -5,9 +5,12 @@ android android [requestActivate] android +wayland [contentItemSize] android [attachedProperty] android [visibilityDoesntClobberWindowState] xcb ubuntu-24.04 # QTBUG-129030 +wayland + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index a6a06764f9..a656729c3a 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -2587,7 +2587,6 @@ void tst_qquickwindow::attachedProperty() QQuickView view(testUrl); view.setTitle(QTest::currentTestFunction()); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QVERIFY(view.rootObject()->property("windowActive").toBool()); QCOMPARE(view.rootObject()->property("contentItem").value(), view.contentItem()); @@ -2598,7 +2597,6 @@ void tst_qquickwindow::attachedProperty() QQuickWindow *innerWindow = view.rootObject()->findChild("extraWindow"); QVERIFY(innerWindow); innerWindow->show(); - innerWindow->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(innerWindow)); QQuickText *text = view.rootObject()->findChild("extraWindowText"); @@ -3602,7 +3600,6 @@ void tst_qquickwindow::testChildMouseEventFilter() } window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList(); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/BLACKLIST new file mode 100644 index 0000000000..1aff7bc65c --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/BLACKLIST @@ -0,0 +1,2 @@ +[focusNavigation] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/tst_qquickwindowcontainer.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/tst_qquickwindowcontainer.cpp index 5b00a79f90..7306256c33 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/tst_qquickwindowcontainer.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/qquickwindowcontainer/tst_qquickwindowcontainer.cpp @@ -45,6 +45,8 @@ private slots: void windowComponent(); #endif + void invisibleChildWindowFocus(); + void updateStackingOrderPerformance(); private: @@ -238,6 +240,39 @@ void tst_QQuickWindowContainer::windowComponent() } #endif // TEST_WINDOW_PARENT +void tst_QQuickWindowContainer::invisibleChildWindowFocus() +{ + // QTBUG-141450: Test that an invisible child window doesn't steal focus when + // the stacking order is updated. + QQuickWindow quickWindow; + QWindow childWindow; + QQuickWindowContainer container; + + // Start invisible so childWindow is never shown and never grabs focus. + container.setVisible(false); + container.setParentItem(quickWindow.contentItem()); + container.setContainedWindow(&childWindow); + + quickWindow.show(); + QVERIFY(QQuickTest::qWaitForPolish(&quickWindow)); + // Child is parented but not visible + QCOMPARE(childWindow.parent(), &quickWindow); + QVERIFY(!childWindow.isVisible()); + + if (!QTest::qWaitForWindowActive(&quickWindow)) + QSKIP("Window failed to activate, skipping test"); + + // With childWindow invisible, quickWindow must be the focus window + QTRY_COMPARE(QGuiApplication::focusWindow(), &quickWindow); + + // Trigger a stacking order update. This should not affect which window + // has focus. + auto *windowPrivate = QQuickWindowPrivate::get(&quickWindow); + windowPrivate->updateChildWindowStackingOrder(); + QCoreApplication::processEvents(); + QCOMPARE(QGuiApplication::focusWindow(), &quickWindow); +} + void tst_QQuickWindowContainer::updateStackingOrderPerformance() { QQuickWindow quickWindow; diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/touchmouse/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/touchmouse/BLACKLIST index 460a5111cb..dd0c1f625b 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quick/touchmouse/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quick/touchmouse/BLACKLIST @@ -8,3 +8,5 @@ windows gcc developer-build [touchButtonOnFlickable] windows gcc developer-build # QTBUG-74517 +[mouseOverTouch] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/CMakeLists.txt index 04771fd89c..dd4648754f 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/CMakeLists.txt @@ -59,6 +59,9 @@ if(NOT ANDROID) # QTBUG-100191 add_subdirectory(sanity) add_subdirectory(snippets) endif() +if(QT_FEATURE_quickcontrols2_stylekit) + add_subdirectory(stylekit) +endif() add_subdirectory(styleimports) add_subdirectory(styleimportscompiletimematerial) add_subdirectory(styleimportscompiletimeqmlonly) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/CMakeLists.txt index 0aa8911f4a..275fea2664 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/CMakeLists.txt @@ -40,6 +40,7 @@ qt_internal_add_test(tst_accessibility Qt::QuickTemplates2Private Qt::QuickTest Qt::QuickTestUtilsPrivate + Qt::QuickControlsTestUtilsPrivate Qt::TestPrivate TESTDATA ${test_data} ) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp index 112da838eb..935bd68200 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/accessibility/tst_accessibility.cpp @@ -1,13 +1,17 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include #include #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -19,6 +23,10 @@ #include #endif +Q_LOGGING_CATEGORY(lcNoTransparentText, "qt.quick.controls.tests.accessibility.notransparenttext") + +using namespace QQuickControlsTestUtils; + class tst_accessibility : public QQmlDataTest { Q_OBJECT @@ -27,6 +35,8 @@ public: tst_accessibility(); private slots: + void initTestCase() override; + void a11y_data(); void a11y(); @@ -44,9 +54,19 @@ private slots: void locale(); void defaultButton(); + void noTransparentText(); private: - QQmlEngine engine; + /* + This is a pointer because: + + #1: Using a new engine for each test row is very slow. + #2: qmlClearTypeRegistrations cannot be called while an engine exists. + + Until QTBUG-134198 is implemented, we need to keep the engine around as long as possible + for #1, and destroy (and recreate) it when necessary for #2. + */ + std::unique_ptr engine; }; #if QT_CONFIG(accessibility) @@ -81,10 +101,21 @@ QQuickItem *findItem(QObject *object) } tst_accessibility::tst_accessibility() - : QQmlDataTest(QT_QMLTEST_DATADIR) + : QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::FailOnWarnings) { } +void tst_accessibility::initTestCase() +{ + QQmlDataTest::initTestCase(); +#if !defined(Q_OS_ANDROID) + // StyleInfo is not supported when cross-compiling: QTBUG-100191. + StyleInfo::instance()->initialize(QQC2_IMPORT_PATH); +#endif + engine.reset(new QQmlEngine); +} + + void tst_accessibility::a11y_data() { QTest::addColumn("fileBaseName"); @@ -142,7 +173,7 @@ void tst_accessibility::a11y() QFETCH(QAccessible::Role, role); QFETCH(QString, text); - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("defaults/" + adjustFileBaseName(fileBaseName) + ".qml")); QScopedPointer object(component.create()); @@ -225,10 +256,16 @@ void tst_accessibility::override() { QFETCH(QAccessible::Role, role); + static const QStringList ignoredWarningTypes = { "Dialog", "Drawer", "Menu", "Popup", "ToolTip" }; + if (ignoredWarningTypes.contains(QTest::currentDataTag())) { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*Accessible attached property must be attached to an object deriving from Item or Action")); + } + const QString name = QTest::currentDataTag(); const QString fileBaseName = name.toLower(); - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("override/" + adjustFileBaseName(fileBaseName) + ".qml")); QScopedPointer object(component.create()); @@ -271,7 +308,7 @@ void a11yDescendants(QAccessibleInterface *iface, Predicate pred) void tst_accessibility::ordering() { - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("ordering/page.qml")); QScopedPointer object(component.create()); @@ -298,7 +335,7 @@ void tst_accessibility::actionAccessibility() accessibility->setActive(true); } - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("actionAccessibility/button.qml")); QScopedPointer object(component.create()); @@ -329,7 +366,7 @@ void tst_accessibility::actionAccessibilityImplicitName() accessibility->setActive(true); } - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("actionAccessibility/button2.qml")); QScopedPointer object(component.create()); @@ -356,7 +393,7 @@ void tst_accessibility::sliderTest() accessibility->setActive(true); } - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); component.loadUrl(testFileUrl("item.qml")); QScopedPointer object(component.create()); QVERIFY2(!object.isNull(), qPrintable(component.errorString())); @@ -405,7 +442,7 @@ void tst_accessibility::accessibleName() accessibility->setActive(true); } - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); // verify that accessible name matches the button text if none was set explicitly component.loadUrl(testFileUrl("accessibleName/button.qml")); @@ -444,7 +481,7 @@ void tst_accessibility::locale() accessibility->setActive(true); } - QQmlComponent component(&engine); + QQmlComponent component(engine.get()); // verify that locale is the default locale if none was set explicitly component.loadUrl(testFileUrl("locale/button.qml")); @@ -509,6 +546,209 @@ void tst_accessibility::defaultButton() #endif } +// Not using data rows for this since they are generated at runtime rather than being hard-coded, +// which could mess with test metrics. +void tst_accessibility::noTransparentText() +{ +#if defined(Q_OS_ANDROID) + QSKIP("StyleInfo is not supported when cross-compiling: QTBUG-100191"); +#endif + + // We need to exclude types that QQuickView can't load, or controls that + // we know don't have text. By manually excluding rather than manually including, + // we ensure that new types are automatically tested. If they shouldn't be tested, + // or need to be accounted for in the test, they will cause a failure indicating that. + const QStringList exclusions = { + // Non-visual. + "AbstractButton", + "Action", + "ActionGroup", + "ButtonGroup", + "Calendar", + "CalendarModel", + + // Required properties. + "HorizontalHeaderViewDelegate", + "TableViewDelegate", + "TreeViewDelegate", + "VerticalHeaderViewDelegate", + + // No text property. + "ApplicationWindow", + "BusyIndicator", + "Container", + "Control", + "Dial", + "DialogButtonBox", + "Drawer", + "Frame", + "HorizontalHeaderView", + "VerticalHeaderView", + "Menu", + "MenuBar", + "MenuSeparator", + "Page", + "PageIndicator", + "Pane", + "Popup", + "ProgressBar", + "RangeSlider", + "ScrollBar", + "ScrollIndicator", + "ScrollView", + "SelectionRectangle", + "Slider", + "SplitView", + "StackView", + "SwipeView", + "TabBar", + "ToolBar", + "ToolSeparator", + "Tumbler", + + // Style-specific non-Controls types. + // FluentWinUI3 + "Config", + "StyleImage" + }; + + struct TextPropertyAccessInfo { + // Default: "text" + QString setterName; + // Default: "contentItem" + QString itemExpression; + // Allows overriding the above. + QHash styleSpecificItemExpressions; + }; + + const QHash textPropertyAccess = { + { "ComboBox", { "displayText", {}, {} } }, + { "DayOfWeekRow", { "doNotSet", "contentItem.children[0]", {} } }, + { "DelayButton", { + {}, + "contentItem.children[0]", + { + { "Imagine", "contentItem" }, + { "Material", "contentItem" }, + { "Universal", "contentItem" } + } + }}, + { "Dialog", { "title", "header", {} } }, + { "DoubleSpinBox", { "doNotSet", {}, {} } }, + { "Label", { {}, "this", {} } }, + { "GroupBox", { "title", "label", {} } }, + { "MonthGrid", { "doNotSet", "contentItem.children[0]", {} } }, + { "SpinBox", { "doNotSet", {}, {} } }, + { "TextArea", { {}, "this", {} } }, + { "TextField", { {}, "this", {} } }, + { "WeekNumberColumn", { "doNotSet", "contentItem.children[0]", {} } } + }; + + QList installedQmlFiles = StyleInfo::instance()->installedQmlFiles(); + const auto exclusionRemover = [&exclusions](const StyleInfo::QmlFileData &qmlFileData) { + for (const QString &exclusion : exclusions) { + if (qmlFileData.relativePath.endsWith(exclusion + QLatin1String(".qml"))) + return true; + } + return false; + }; + installedQmlFiles.erase( + std::remove_if(installedQmlFiles.begin(), installedQmlFiles.end(), exclusionRemover), installedQmlFiles.end()); + + qCDebug(lcNoTransparentText) << "Installed QML files after removing exclusions:"; + for (auto it = installedQmlFiles.begin(); it != installedQmlFiles.end(); ++it) { + qCDebug(lcNoTransparentText) << "-" << it->styleName << it->typeName + << it->relativePath << it->absolutePath; + } + + for (auto it = installedQmlFiles.constBegin(); it != installedQmlFiles.constEnd(); ++it) { + qCDebug(lcNoTransparentText) << "Testing" << it->styleName << it->relativePath + << it->absolutePath; + + if (QQuickStyle::name() != it->styleName) { + // Can't have an engine alive when qmlClearTypeRegistrations is called. + auto cleanup = qScopeGuard([this](){ engine.reset(new QQmlEngine); }); + engine.reset(); + qmlClearTypeRegistrations(); + QQuickStyle::setStyle(it->styleName); + } + + // QTBUG-129447 + if (it->styleName == "Imagine" && it->typeName == "Dialog") { + for (int i = 0; i < 3; ++i) + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*QML Dialog: Binding loop detected for property.*")); + } + + // Load the QML for this type. + QQmlComponent component(engine.get()); + const TextPropertyAccessInfo alternativePropertyAcess = textPropertyAccess.value(it->typeName); + const QString setterName = alternativePropertyAcess.setterName.isEmpty() + ? QLatin1String("text") : alternativePropertyAcess.setterName; + // "doNotSet" means we don't need to set anything. + const QString setterBinding = setterName != "doNotSet" ? setterName + ": 'Some text'" : ""; + const QString qml = QString("import QtQuick.Controls.%1; %2 { %3 }") + .arg(it->styleName, it->typeName, setterBinding); + component.setData(qml.toUtf8(), QUrl()); + QScopedPointer root(component.create()); + QVERIFY2(component.isReady(), qPrintable(QString::fromLatin1("Failed to load QML for %1: %2QML:\n%3") + .arg(it->typeName, component.errorString(), qml))); + + // Get the item that we should query for the text and color properties. + QString styleSpecificItemExpression + = alternativePropertyAcess.styleSpecificItemExpressions.value(it->styleName); + if (styleSpecificItemExpression.isEmpty()) { + // No style-specific expression was given; use the generic one. + styleSpecificItemExpression = alternativePropertyAcess.itemExpression; + } + const QString textItemExpression = styleSpecificItemExpression.isEmpty() + ? "contentItem" : styleSpecificItemExpression; + QQuickItem *textItem = nullptr; + // "this" means use the root object. + if (textItemExpression == QLatin1String("this")) { + textItem = qobject_cast(root.get()); + } else { + QQmlExpression itemQmlExpression(qmlContext(root.get()), root.get(), textItemExpression); + const QVariant evaluationResult = itemQmlExpression.evaluate(); + if (!evaluationResult.isValid()) { + QString failureMessage = QString::fromLatin1("itemExpression \"%1\" for %2 %3 is invalid") + .arg(textItemExpression, it->styleName, it->typeName); + if (itemQmlExpression.hasError()) + failureMessage += QLatin1String(": ") + itemQmlExpression.error().toString(); + QFAIL(qPrintable(failureMessage)); + } + textItem = evaluationResult.value(); + } + QVERIFY2(textItem, qPrintable(QString::fromLatin1( + "The item (%1) to use for the text and color properties of %2 %3 is null") + .arg(textItemExpression, it->styleName, it->typeName))); + + // If it has an icon property, set icon.color to "transparent", which should only affect the + // icon color (make it use the original color), not the text color. + if (root->property("icon").isValid()) { + auto *asAbstractButton = qobject_cast(root.get()); + QVERIFY(asAbstractButton); + auto icon = asAbstractButton->icon(); + icon.setColor(Qt::transparent); + asAbstractButton->setIcon(icon); + QCOMPARE(asAbstractButton->icon().color(), Qt::transparent); + } + + // Confirm that the text is not empty and the color is not transparent. + QVERIFY(!textItem->property("text").toString().isEmpty()); + const QVariant colorProperty = textItem->property("color"); + QVERIFY2(colorProperty.isValid(), qPrintable(QString::fromLatin1( + "The item (%1) to use for %2 %3 has no color property") + .arg(textItemExpression, it->styleName, it->typeName))); + const auto color = colorProperty.value(); + QVERIFY2(color.isValid(), qPrintable(QString::fromLatin1( + "color property of the item (%1) to use for %2 %3 is invalid") + .arg(textItemExpression, it->styleName, it->typeName))); + QVERIFY2(color.alpha() != 0, qPrintable(QString::fromLatin1( + "color property of the item (%1) to use for %2 %3 is transparent") + .arg(textItemExpression, it->styleName, it->typeName))); + } +} + QTEST_MAIN(tst_accessibility) #include "tst_accessibility.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/basic/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/basic/BLACKLIST index 187bf6ceb4..dcd895d4d2 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/basic/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/basic/BLACKLIST @@ -61,3 +61,5 @@ android android [SelectionRectangle::test_selectionModeAuto] wayland +[ToolTip::test_makeVisibleWhileExitTransitionRunning] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_combobox.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_combobox.qml index 82e67c890c..ed88b0a35c 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_combobox.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_combobox.qml @@ -2482,4 +2482,26 @@ TestCase { control.delegate = delegateComponent1 verify(delegateComponent2) } + + // QTBUG-144146 - verify background size + Component { + id: comboboxBackgroundResize + ComboBox { + width: 100 + height: 30 + } + } + + function test_backgroundResize() { + if (StyleInfo.styleName !== "FluentWinUI3") + skip("background size problem only exists in FluentWinUI3. " + + "more details in QTBUG-144146") + + let control = createTemporaryObject(comboboxBackgroundResize, testCase) + verify(control) + + verify(control.children.length > 0) + compare(control.background.children[0].width, 100) + compare(control.background.children[0].height, 30) + } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_scrollview.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_scrollview.qml index 2a4fd40a42..0f560925ee 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_scrollview.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/data/tst_scrollview.qml @@ -6,6 +6,7 @@ import QtTest import QtQuick.Controls import QtQuick.Templates as T import Qt.test.controls +import QtQuick.Layouts TestCase { id: testCase @@ -65,12 +66,17 @@ TestCase { Component { id: flickableLabel ScrollView { + width: 200 + height: 200 + visible: true Flickable { + clip: true contentWidth: label.implicitWidth contentHeight: label.implicitHeight Label { id: label text: "ABC" + color: "red" font.pixelSize: 512 } } @@ -225,8 +231,8 @@ TestCase { let label = flickable.contentItem.children[0] compare(label.text, "ABC") - compare(control.implicitWidth, label.implicitWidth) - compare(control.implicitHeight, label.implicitHeight) + compare(control.implicitWidth, label.implicitWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, label.implicitHeight + control.topPadding + control.bottomPadding) compare(control.contentWidth, label.implicitWidth) compare(control.contentHeight, label.implicitHeight) @@ -235,12 +241,12 @@ TestCase { compare(flickable.contentHeight, label.implicitHeight) control.contentWidth = 200 - compare(control.implicitWidth, 200) + compare(control.implicitWidth, 200 + control.leftPadding + control.rightPadding) compare(control.contentWidth, 200) compare(flickable.contentWidth, 200) control.contentHeight = 100 - compare(control.implicitHeight, 100) + compare(control.implicitHeight, 100 + control.topPadding + control.bottomPadding) compare(control.contentHeight, 100) compare(flickable.contentHeight, 100) } @@ -834,4 +840,39 @@ TestCase { verify(scrollview.rightPadding >= scrollview.padding) verify(scrollview.bottomPadding >= scrollview.padding) } + + // Verify that binding contentWidth to availableWidth doesn't cause a binding loop + // (QTBUG-145226). The RowLayout resizes the ScrollView via Layout.fillWidth, which + // was the scenario that triggered the bug in styles where visible scroll bars + // reduce availableWidth (e.g. macOS, Windows, StyleKit). + Component { + id: scrollViewContentWidthBindingComp + RowLayout { + spacing: 20 + anchors.fill: parent + anchors.margins: 20 + ScrollView { + id: scrollViewInLayout + Layout.fillWidth: true + Layout.fillHeight: true + contentWidth: availableWidth + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + Rectangle { + width: 50 + height: 1000 + } + } + } + } + + function test_contentWidthBindingNoLoop() { + let root = createTemporaryObject(scrollViewContentWidthBindingComp, testCase) + verify(root) + let scrollview = root.children[0] + verify(scrollview) + waitForRendering(scrollview) + // If there is a binding loop, it's caught by failOnWarning() + compare(scrollview.contentWidth, scrollview.availableWidth) + } } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST index d213622256..683514c028 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/BLACKLIST @@ -87,4 +87,5 @@ android android [SelectionRectangle::test_selectionModeAuto] wayland - +[ToolTip::test_makeVisibleWhileExitTransitionRunning] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt index 32cd4902c2..1a96363752 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fluentwinui3/CMakeLists.txt @@ -10,7 +10,7 @@ endif() # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) + ${CMAKE_CURRENT_SOURCE_DIR}/../data/*) list(APPEND test_data ${test_data_glob}) set(controls_data ${CMAKE_CURRENT_SOURCE_DIR}/../data) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fusion/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fusion/BLACKLIST index 0adf582889..0e6522d5d7 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fusion/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/fusion/BLACKLIST @@ -63,3 +63,5 @@ android android [SelectionRectangle::test_selectionModeAuto] wayland +[ToolTip::test_makeVisibleWhileExitTransitionRunning] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/imagine/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/imagine/BLACKLIST index e787022583..5f3035a777 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/imagine/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/imagine/BLACKLIST @@ -56,3 +56,5 @@ android [SelectionRectangle::test_selectionModeAuto] wayland +[ToolTip::test_makeVisibleWhileExitTransitionRunning] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/universal/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/universal/BLACKLIST index 3a0e4eaede..875240cc14 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/universal/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/controls/universal/BLACKLIST @@ -56,3 +56,5 @@ android [SelectionRectangle::test_selectionModeAuto] wayland +[ToolTip::test_makeVisibleWhileExitTransitionRunning] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/customization/tst_customization.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/customization/tst_customization.cpp index c5247b9082..7a07568331 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/customization/tst_customization.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/customization/tst_customization.cpp @@ -528,7 +528,6 @@ void tst_customization::comboPopup() QQuickWindow window; window.resize(300, 300); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); QQmlComponent component(engine); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/focus/tst_focus.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/focus/tst_focus.cpp index ce95785582..455f2fa2c2 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/focus/tst_focus.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/focus/tst_focus.cpp @@ -99,7 +99,6 @@ void tst_focus::navigation() view.setSource(testFileUrl(testFile)); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QVERIFY(QGuiApplication::focusWindow() == &view); @@ -235,7 +234,6 @@ void tst_focus::reason() QQuickView view; view.setSource(testFileUrl("focusReason.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickControl *control = view.findChild("control"); @@ -457,7 +455,6 @@ void tst_focus::visualFocus() QQuickView view; view.setSource(testFileUrl("visualFocus.qml")); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickItem *column = view.rootObject(); @@ -514,7 +511,6 @@ void tst_focus::scope() QVERIFY(child); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/palette/tst_palette.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/palette/tst_palette.cpp index a323b02120..ed0f71af3e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/palette/tst_palette.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/palette/tst_palette.cpp @@ -623,7 +623,8 @@ void tst_palette::comboBoxPopupWithThemeDefault_data() QTest::addColumn("style"); QTest::addColumn("expectedComboBoxPopupBackgroundColor"); - QTest::newRow("Basic") << "Basic" << QColor::fromRgb(0xFFFFFF); + const bool isDark = qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark; + QTest::newRow("Basic") << "Basic" << QColor::fromRgb(isDark ? 0x000000 : 0xFFFFFF); // We can't test Fusion because it uses the default application palette, // which is the default-constructed QPalette, so the test would always pass. diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/BLACKLIST index 6017eb957d..5817b101be 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/BLACKLIST @@ -4,3 +4,7 @@ opensuse-leap [textControlsMenuKey] * # QTBUG-143701 + +[textEditingContextMenuUndoRedo] +wayland + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp index 07507f3bcb..42b0c0d1ed 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontextmenu/tst_qquickcontextmenu.cpp @@ -641,7 +641,8 @@ void tst_QQuickContextMenu::textEditingContextMenuUndoRedo() QTRY_VERIFY(!contextMenu->menu()->isVisible()); // Ensure that the control has text if it's a SearchField. - QVERIFY(selectFirstItemIfSearchField(qobject_cast(editor->parentItem()))); + auto *searchField = qobject_cast(editor->parentItem()); + QVERIFY(selectFirstItemIfSearchField(searchField)); QCOMPARE(editor->property("text").toString(), expectedTextComplete); // Modify the text. Undo should then be enabled, but not redo. @@ -651,9 +652,20 @@ void tst_QQuickContextMenu::textEditingContextMenuUndoRedo() QVERIFY(undoMenuItem->isEnabled()); QVERIFY(!redoMenuItem->isEnabled()); + // If it's a SearchField, ensure that the popup is closed. + if (searchField) { + QTest::keyClick(&window, Qt::Key_Escape); + QTRY_VERIFY(!contextMenu->menu()->isVisible()); + } + // Right click on the editor to open the context menu. QTest::mouseClick(&window, Qt::RightButton, Qt::NoModifier, mapCenterToWindow(editor)); QTRY_VERIFY(contextMenu->menu()->isOpened()); + if (searchField) { + // SearchField's popup should close when it loses focus, + // and it should stay closed (QTBUG-144237). + QTRY_VERIFY(!searchField->popup()->isVisible()); + } // Click on the Undo menu item. Redo should then be enabled. QVERIFY(clickMenuItem(undoMenuItem)); @@ -686,7 +698,6 @@ void tst_QQuickContextMenu::textEditingContextMenuCut() QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFileName))); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *editor = window.rootObject()->property("editor").value(); @@ -766,7 +777,6 @@ void tst_QQuickContextMenu::textEditingContextMenuCopy() QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFileName))); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *editor = window.rootObject()->property("editor").value(); @@ -838,7 +848,6 @@ void tst_QQuickContextMenu::textEditingContextMenuPaste() QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFileName))); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *editor = window.rootObject()->property("editor").value(); @@ -903,7 +912,6 @@ void tst_QQuickContextMenu::textEditingContextMenuDelete() QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFileName))); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *editor = window.rootObject()->property("editor").value(); @@ -969,7 +977,6 @@ void tst_QQuickContextMenu::textEditingContextMenuSelectAll() QQuickView window; QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFileName))); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *editor = window.rootObject()->property("editor").value(); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/data/backgroundAlignment.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/data/backgroundAlignment.qml new file mode 100644 index 0000000000..5d777dd146 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/data/backgroundAlignment.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Templates as T + +ApplicationWindow { + width: control.width + height: control.height + visible: true + color: "white" + T.Button { + id: control + implicitWidth: implicitBackgroundWidth + leftInset + rightInset + implicitHeight: implicitBackgroundHeight + topInset + bottomInset + topInset: 6 + bottomInset: 6 + background: Rectangle { + implicitWidth: 200 + implicitHeight: 50 + color: "red" + layer.enabled: true + } + } +} + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp index 5434aa1efa..12993dbf56 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickcontrol/tst_qquickcontrol.cpp @@ -27,6 +27,7 @@ private slots: void fractionalFontSize(); void resizeBackgroundKeepsBindings(); void hoverInMouseArea(); + void backgroundAlignment(); private: QScopedPointer touchDevice; @@ -137,6 +138,19 @@ void tst_QQuickControl::hoverInMouseArea() QVERIFY(textField->isHovered()); } +void tst_QQuickControl::backgroundAlignment() +{ + QQuickApplicationHelper helper(this, QStringLiteral("backgroundAlignment.qml")); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + auto ctxt = qmlContext(window); + QVERIFY(ctxt); + auto image = window->grabWindow(); + // If the top inset is applied, the top 6 pixels will appear white. + QCOMPARE(image.pixelColor(0, 0), QColor(255,255,255)); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickControl) #include "tst_qquickcontrol.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/data/multiple.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/data/multiple.qml index f6e5eb02af..62f6749bdf 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/data/multiple.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/data/multiple.qml @@ -19,7 +19,7 @@ ApplicationWindow { Drawer { id: leftDrawer width: 300 - height: 400 + height: parent.height z: 1 contentItem: Button { @@ -37,7 +37,7 @@ ApplicationWindow { Drawer { id: rightDrawer width: 300 - height: 400 + height: parent.height edge: Qt.RightEdge contentItem: Button { diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp index da282a5ecf..97bd214c0c 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickdrawer/tst_qquickdrawer.cpp @@ -453,34 +453,45 @@ void tst_QQuickDrawer::reposition() QVERIFY(dimmer); QCOMPARE(geometry(dimmer), QRectF(0, 0, window->width(), window->height())); - QTRY_COMPARE(geometry(popupItem), QRectF(0, 0, window->width() / 2., window->height())); + QTRY_COMPARE(geometry(popupItem), QRectF(0, 0, drawer->width(), window->height())); - drawer->setY(100); - QCOMPARE(geometry(dimmer), QRectF(0, 100, window->width(), window->height() - 100)); - QCOMPARE(geometry(popupItem), QRectF(0, 100, window->width() / 2., window->height() - 100)); + const qreal drawerY = 100; + drawer->setY(drawerY); + QCOMPARE(geometry(dimmer), QRectF(0, drawerY, window->width(), window->height() - drawerY)); + QCOMPARE(geometry(popupItem), QRectF(0, drawerY, drawer->width(), window->height() - drawerY)); drawer->setHeight(window->height()); - QCOMPARE(geometry(dimmer), QRectF(0, 100, window->width(), window->height())); - QCOMPARE(geometry(popupItem), QRectF(0, 100, window->width() / 2., window->height())); + QCOMPARE(geometry(dimmer), QRectF(0, drawerY, window->width(), window->height())); + QCOMPARE(geometry(popupItem), QRectF(0, drawerY, drawer->width(), window->height())); drawer->resetHeight(); - QCOMPARE(geometry(dimmer), QRectF(0, 100, window->width(), window->height() - 100)); - QCOMPARE(geometry(popupItem), QRectF(0, 100, window->width() / 2., window->height() - 100)); + QCOMPARE(geometry(dimmer), QRectF(0, drawerY, window->width(), window->height() - drawerY)); + QCOMPARE(geometry(popupItem), QRectF(0, drawerY, drawer->width(), window->height() - drawerY)); + + // The drawer's top is the sum of contentItem()->y() (which accounts for both + // safe margins and the header in ApplicationWindow) plus the drawer->y(). + const qreal drawerTop = window->contentItem()->y() + drawer->y(); + const auto dimmerRect = [&] { + return QRectF(0, drawerTop, window->width(), window->height() - drawerTop); + }; + const auto popupRect = [&](qreal x) { + return QRectF(x, drawerTop, drawer->width(), window->height() - drawerTop); + }; drawer->setParentItem(window->contentItem()); - QCOMPARE(geometry(dimmer), QRectF(0, 150, window->width(), window->height() - 150)); - QCOMPARE(geometry(popupItem), QRectF(0, 150, window->width() / 2., window->height() - 150)); + QCOMPARE(geometry(dimmer), dimmerRect()); + QCOMPARE(geometry(popupItem), popupRect(0)); drawer->setEdge(Qt::RightEdge); - QCOMPARE(geometry(dimmer), QRectF(0, 150, window->width(), window->height() - 150)); - QTRY_COMPARE(geometry(popupItem), QRectF(window->width() - drawer->width(), 150, window->width() / 2., window->height() - 150)); + QCOMPARE(geometry(dimmer), dimmerRect()); + QTRY_COMPARE(geometry(popupItem), popupRect(window->width() - drawer->width())); window->setWidth(window->width() + 100); - QTRY_COMPARE(geometry(dimmer), QRectF(0, 150, window->width(), window->height() - 150)); - QTRY_COMPARE(geometry(popupItem), QRectF(window->width() - drawer->width(), 150, window->width() / 2., window->height() - 150)); + QTRY_COMPARE(geometry(dimmer), dimmerRect()); + QTRY_COMPARE(geometry(popupItem), popupRect(window->width() - drawer->width())); drawer->close(); - QTRY_COMPARE(geometry(popupItem), QRectF(window->width(), 150, window->width() / 2., window->height() - 150)); + QTRY_COMPARE(geometry(popupItem), popupRect(window->width())); QQuickDrawer *drawer2 = window->property("drawer2").value(); QVERIFY(drawer2); @@ -565,10 +576,13 @@ void tst_QQuickDrawer::rotateTouchOpen() // Swipe down from the window's top edge const int hCenter = window->width()/2; const int vCenter = window->height()/2; - QTest::touchEvent(window, touchDevice.data()).press(0, QPoint(hCenter, 0)); - QTest::touchEvent(window, touchDevice.data()).move(0, QPoint(hCenter, vCenter/2)); - QTest::touchEvent(window, touchDevice.data()).move(0, QPoint(hCenter, vCenter)); - QTest::touchEvent(window, touchDevice.data()).release(0, QPoint(hCenter, vCenter)); + const int topSafeMargin = window->safeAreaMargins().top(); + // Extend drag margin to cover the safe area so the press is recognized. + drawer_LR->setDragMargin(qMax(drawer_LR->dragMargin(), static_cast(topSafeMargin + 1))); + QTest::touchEvent(window, touchDevice.data()).press(0, QPoint(hCenter, topSafeMargin)); + QTest::touchEvent(window, touchDevice.data()).move(0, QPoint(hCenter, topSafeMargin + vCenter/2)); + QTest::touchEvent(window, touchDevice.data()).move(0, QPoint(hCenter, topSafeMargin + vCenter)); + QTest::touchEvent(window, touchDevice.data()).release(0, QPoint(hCenter, topSafeMargin + vCenter)); QTRY_COMPARE(drawer_LR->position(), 1.0); // "Left Drawer" is at the window's top edge QTRY_COMPARE(drawer_TB->position(), 0.0); // "Top Drawer" is at the window's right edge diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickiconimage/tst_qquickiconimage.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickiconimage/tst_qquickiconimage.cpp index 91684203f7..b91262d396 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickiconimage/tst_qquickiconimage.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickiconimage/tst_qquickiconimage.cpp @@ -96,7 +96,6 @@ void tst_qquickiconimage::nameBindingSourceSize() QQuickView view(testFileUrl("nameBindingSourceSize.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -172,7 +171,6 @@ void tst_qquickiconimage::sourceBindingNoSizes() view.show(); INIT_DPR(view); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickIconImage *iconImage = qobject_cast(view.rootObject()->childItems().at(0)); @@ -195,7 +193,6 @@ void tst_qquickiconimage::sourceBindingSourceSize() QQuickView view(testFileUrl("sourceBindingSourceSize.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -227,7 +224,6 @@ void tst_qquickiconimage::sourceBindingSourceSizeWidthHeight() QQuickView view(testFileUrl("sourceBindingSourceSizeWidthHeight.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -246,7 +242,6 @@ void tst_qquickiconimage::sourceBindingSourceTooLarge() QQuickView view(testFileUrl("sourceBindingSourceTooLarge.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -284,7 +279,6 @@ void tst_qquickiconimage::alignment() QQuickView view(testFileUrl("alignment.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -359,7 +353,6 @@ void tst_qquickiconimage::svgSourceBindingSourceSize() QQuickView view(testFileUrl("svgSourceBindingSourceSize.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickIconImage *iconImage = qobject_cast(view.rootObject()->childItems().at(0)); @@ -383,7 +376,6 @@ void tst_qquickiconimage::color() QQuickView view(testFileUrl("color.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -419,7 +411,6 @@ void tst_qquickiconimage::changeSourceSize() QQuickView view(testFileUrl("sourceBindingSourceSize.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickIconImage *iconImage = qobject_cast(view.rootObject()->childItems().at(0)); @@ -440,7 +431,6 @@ void tst_qquickiconimage::fileSelectors() view.setSource(testFileUrl("fileSelectors.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); INIT_DPR(view); @@ -482,7 +472,6 @@ void tst_qquickiconimage::imageProvider() view.setSource(testFileUrl("imageProvider.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickIconImage *iconImage = qobject_cast(view.rootObject()->findChild()); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/BLACKLIST index 3c997b70e7..0355e4bcac 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/BLACKLIST @@ -8,3 +8,6 @@ macos [contextMenuKeyboard] wayland + +[resetCurrentIndexUponPopup] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/data/removeContentItem.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/data/removeContentItem.qml new file mode 100644 index 0000000000..a4f56d5726 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/data/removeContentItem.qml @@ -0,0 +1,39 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 600 + height: 400 + + property alias menu: contextMenu + property Item oldContentItem : null + + function removeContentItem() + { + oldContentItem = contextMenu.contentItem + contextMenu.contentItem = null + } + + function restoreContentItem() + { + contextMenu.contentItem = oldContentItem + } + + Item { + width: 100 + height: 100 + anchors.centerIn: parent + Menu { + id: contextMenu + title: "Some Menu" + visible: true + MenuItem { + text: "Action 1" + } + } + } +} + diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp index 8c7506a58e..8dea8e7221 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenu/tst_qquickmenu.cpp @@ -131,6 +131,7 @@ private slots: void dontDeleteDelegates(); void loadMenuAsynchronously(); void visibleTrue(); + void resetContentItem(); private: bool nativeMenuSupported = false; @@ -234,7 +235,6 @@ void tst_QQuickMenu::mouse() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); @@ -361,15 +361,16 @@ void tst_QQuickMenu::pressAndHold() QQuickMenu *menu = window->property("menu").value(); QVERIFY(menu); - QPoint windowCenter = QRect(QPoint(0, 0), window->size()).center(); + const QMargins safeMargins = window->safeAreaMargins(); + QPoint safeTopLeft = QPoint(safeMargins.left() + 1, safeMargins.top() + 1); - QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, windowCenter); + QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, safeTopLeft); QTRY_VERIFY(menu->isVisible()); - QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, windowCenter); + QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, safeTopLeft); QVERIFY(menu->isVisible()); - QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, windowCenter); + QTest::mouseClick(window, Qt::LeftButton, Qt::NoModifier, safeTopLeft); QTRY_VERIFY(!menu->isVisible()); } @@ -397,7 +398,6 @@ void tst_QQuickMenu::contextMenuKeyboard() moveMouseAway(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(QGuiApplication::focusWindow() == window); @@ -624,7 +624,6 @@ void tst_QQuickMenu::disabledMenuItemKeyNavigation() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(QGuiApplication::focusWindow() == window); @@ -689,7 +688,6 @@ void tst_QQuickMenu::mnemonics() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); MnemonicKeySimulator keySim(window); @@ -892,7 +890,6 @@ void tst_QQuickMenu::checkableMnemonics() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); window->setProperty("checkable", checkable); @@ -954,7 +951,6 @@ void tst_QQuickMenu::menuButton() QQuickApplicationWindow *window = helper.appWindow; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QVERIFY(QGuiApplication::focusWindow() == window); @@ -1007,7 +1003,6 @@ void tst_QQuickMenu::menuSeparator() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); @@ -1441,7 +1436,6 @@ void tst_QQuickMenu::actionShortcuts() QVERIFY2(helper.ready, helper.failureMessage()); QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // Try the menu's shortcut. @@ -1777,7 +1771,6 @@ void tst_QQuickMenu::subMenuKeyboard() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); if (mirrored) { @@ -1908,7 +1901,6 @@ void tst_QQuickMenu::subMenuDisabledKeyboard() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); if (mirrored) { @@ -2718,7 +2710,6 @@ void tst_QQuickMenu::giveMenuItemFocusOnButtonPress() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // Press enter on the button to open the menu. @@ -3295,7 +3286,6 @@ void tst_QQuickMenu::effectivePosition() QQuickApplicationWindow *window = helper.appWindow; centerOnScreen(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); @@ -3420,7 +3410,6 @@ void tst_QQuickMenu::resetCurrentIndexUponPopup() centerOnScreen(window); moveMouseAway(window); window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickMenu *menu = window->property("menu").value(); @@ -3451,23 +3440,26 @@ void tst_QQuickMenu::resetCurrentIndexUponPopup() void tst_QQuickMenu::mousePropagationWithinPopup() { + SKIP_IF_NO_WINDOW_ACTIVATION; + QQuickControlsApplicationHelper helper(this, QLatin1String("mousePropagationWithinPopup.qml")); QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; centerOnScreen(window); + moveMouseAway(window); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); QQuickPopup *popup = window->property("popup").value(); QVERIFY(popup); popup->open(); - QTRY_VERIFY(popup->isOpened()); + TRY_VERIFY_POPUP_OPENED(popup); QQuickMenu *nestedMenu = window->property("nestedMenu").value(); QVERIFY(nestedMenu); nestedMenu->open(); - QTRY_VERIFY(nestedMenu->isOpened()); + TRY_VERIFY_POPUP_OPENED(nestedMenu); QQuickMouseArea *mouseArea = window->property("mouseArea").value(); QVERIFY(mouseArea); @@ -3503,7 +3495,6 @@ void tst_QQuickMenu::shortcutInNestedSubMenuAction() QVERIFY2(helper.ready, helper.failureMessage()); QQuickApplicationWindow *window = helper.appWindow; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); // Shouldn't result in an infinite loop. @@ -3561,7 +3552,6 @@ void tst_QQuickMenu::loadMenuAsynchronously() QQuickView window(testFileUrl("loadMenuAsynchronously.qml")); QCOMPARE(window.status(), QQuickView::Ready); window.show(); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *rootItem = window.rootObject(); @@ -3613,6 +3603,26 @@ void tst_QQuickMenu::visibleTrue() menu->close(); } +void tst_QQuickMenu::resetContentItem() +{ + QQuickControlsApplicationHelper helper(this, QLatin1String("removeContentItem.qml")); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickApplicationWindow *window = helper.appWindow; + auto *menu = window->property("menu").value(); + QVERIFY(menu); + QSignalSpy spy(menu, &QQuickMenu::contentItemChanged); + + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + QTRY_VERIFY(menu->isOpened()); + + QMetaObject::invokeMethod(window, "removeContentItem"); + QCOMPARE(spy.count(), 1); + QMetaObject::invokeMethod(window, "restoreContentItem"); + QCOMPARE(spy.count(), 2); + menu->close(); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickMenu) #include "tst_qquickmenu.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenubar/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenubar/BLACKLIST index d409a8fe84..8548b1ab07 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenubar/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickmenubar/BLACKLIST @@ -2,5 +2,8 @@ [keys] android -[hovedAfterClosingWithEscape] +[hoverAfterClosingWithEscape] +wayland + +[closeByClickingOutside] wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickninepatchimage/tst_qquickninepatchimage.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickninepatchimage/tst_qquickninepatchimage.cpp index 640f200227..bdd689f5d3 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickninepatchimage/tst_qquickninepatchimage.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickninepatchimage/tst_qquickninepatchimage.cpp @@ -84,7 +84,6 @@ void tst_qquickninepatchimage::ninePatch() QQuickView view(testFileUrl("ninepatchimage.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickImage *ninePatchImage = qobject_cast(view.rootObject()); @@ -92,7 +91,9 @@ void tst_qquickninepatchimage::ninePatch() ninePatchImage->setSource(testFileUrl("foo.9.png")); ninePatchImage->setSize(size); - const QImage ninePatchImageGrab = grabItemToImage(ninePatchImage).scaled(size * dpr); + QImage ninePatchImageGrab = grabItemToImage(ninePatchImage).scaled(size * dpr); + // grabItemToImage() may have device's DPR as metadata, override it to setGlobalFactor()'s DPR. + ninePatchImageGrab.setDevicePixelRatio(dpr); // Generate an image to compare against the actual 9-patch image. QImage generatedImage(size * dpr, ninePatchImageGrab.format()); @@ -142,7 +143,6 @@ void tst_qquickninepatchimage::padding() QQuickView view(testFileUrl("ninepatchimage.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickImage *ninePatchImage = qobject_cast(view.rootObject()); @@ -184,7 +184,6 @@ void tst_qquickninepatchimage::inset() QQuickView view(testFileUrl("ninepatchimage.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickImage *ninePatchImage = qobject_cast(view.rootObject()); @@ -231,7 +230,6 @@ void tst_qquickninepatchimage::implicitSize() QQuickView view(testFileUrl("ninepatchimage.qml")); QCOMPARE(view.status(), QQuickView::Ready); view.show(); - view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickImage *ninePatchImage = qobject_cast(view.rootObject()); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterExitNonModal.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterExitNonModal.qml index 9412d77368..e90cd3a0dc 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterExitNonModal.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/activeFocusAfterExitNonModal.qml @@ -5,19 +5,29 @@ import QtQuick import QtQuick.Controls ApplicationWindow { - width: 400 + width: 600 height: 400 + title: "activeFocusItem: " + activeFocusItem property alias rootItem: rootItem property alias focusItem: focusItem property alias popup: popup - Item { + Rectangle { id: rootItem + objectName: "rootItem" + width: 32 + height: 32 + color: activeFocus ? "salmon" : "lightgray" } - Item { + Rectangle { id: focusItem + objectName: "focusItem" + y: 32 + width: 32 + height: 32 + color: activeFocus ? "salmon" : "lightgray" focus: true Component.onCompleted: forceActiveFocus() } @@ -27,10 +37,11 @@ ApplicationWindow { focus: true modal: false popupType: Popup.Item + width: 100 + height: 100 contentItem: Rectangle { - width: 100 - height: 100 - color: "lightgray" + objectName: "popupContentItem" + color: activeFocus ? "salmon" : "lightgray" focus: true Component.onCompleted: forceActiveFocus() } diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/popupWindowImplicitSizeChange.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/popupWindowImplicitSizeChange.qml new file mode 100644 index 0000000000..770d14cd9f --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/popupWindowImplicitSizeChange.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 640 + height: 480 + + property alias button: btn + property string toolTipText: "?" + + Button { + id: btn + text: "Hover me" + ToolTip.delay: 0 + ToolTip.visible: hovered && toolTipText.length > 0 + ToolTip.text: toolTipText + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/windowWithInteractiveItemsAndPopup.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/windowWithInteractiveItemsAndPopup.qml new file mode 100644 index 0000000000..99bf593376 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/data/windowWithInteractiveItemsAndPopup.qml @@ -0,0 +1,48 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls + +Window { + width: 320 + height: 240 + Row { + spacing: 6 + Button { + id: popupBtn + text: "show popup" + onClicked: { + myPopup.visible = true + } + } + Button { + objectName: "extra button" + text: "click me" + } + Rectangle { + id: rect + objectName: "tappable rect" + color: th.pressed ? "steelblue" : "#00FF00" + width: 30 + height: 30 + TapHandler { id: th } + } + } + + Popup { + id: myPopup + anchors.centerIn: parent + visible: false + modal: true + popupType: Popup.Native + closePolicy: Popup.NoAutoClose + width: 240 + height: 160 + dim: true + Button { + text: "close" + onClicked: myPopup.close() + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp index 268450fc32..49ea1a5b13 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickpopup/tst_qquickpopup.cpp @@ -44,6 +44,16 @@ using namespace Qt::StringLiterals; using namespace QQuickVisualTestUtils; using namespace QQuickControlsTestUtils; +static bool isPlatformWayland() +{ + return QGuiApplication::platformName().startsWith(QStringLiteral("wayland")); +} + +static bool isPlatformUbuntu() +{ + return qEnvironmentVariable("XDG_SESSION_DESKTOP") == QStringLiteral("ubuntu"); +} + class tst_QQuickPopup : public QQmlDataTest { Q_OBJECT @@ -146,11 +156,21 @@ private slots: void popupWindowWithPaddingFromSafeArea(); void popupWindowPositionerRespectingScreenBounds_data(); void popupWindowPositionerRespectingScreenBounds(); + void popupWindowRepositionOnImplicitSizeChange(); void propagateTouchEvents(); + void blockEventsBehindModal_data(); + void blockEventsBehindModal(); void spacingAndInsetsAreRevaluatedWhenChanged(); + void windowInsetsOrder(); private: QScopedPointer touchScreen = QScopedPointer(QTest::createTouchDevice()); +#if QT_CONFIG(tabletevent) + std::unique_ptr tabletStylusDevice{ + QPointingDevicePrivate::tabletDevice(QInputDevice::DeviceType::Stylus, + QPointingDevice::PointerType::Pen, + QPointingDeviceUniqueId::fromNumericId(1234567890))}; +#endif }; tst_QQuickPopup::tst_QQuickPopup() @@ -1057,6 +1077,7 @@ void tst_QQuickPopup::activeFocusAfterExitNonModal() QTRY_VERIFY_ACTIVE_FOCUS(popup); inner->forceActiveFocus(); popup->close(); + // TODO: Find out why this takes ~5 seconds on macOS: QTBUG-145131 closedSpy.wait(); QTRY_VERIFY(!popup->hasActiveFocus()); QTRY_VERIFY_ACTIVE_FOCUS(inner); @@ -1068,6 +1089,7 @@ void tst_QQuickPopup::activeFocusAfterExitNonModal() popup->open(); QTRY_VERIFY(popup->isVisible()); popup->close(); + // TODO: Find out why this takes ~5 seconds on macOS: QTBUG-145131 closedSpy.wait(); QTRY_VERIFY(!popup->hasActiveFocus()); QTRY_VERIFY_ACTIVE_FOCUS(root); @@ -3666,6 +3688,74 @@ void tst_QQuickPopup::popupWindowPositionerRespectingScreenBounds() popup->close(); } +// QTBUG-142700: Verify that when a popup window's implicit size changes +// (e.g. ToolTip text gets longer), the popup window is repositioned to +// reflect the new size. Previously, reposition() was not called after +// resizing, so the popup appeared in the wrong location. +void tst_QQuickPopup::popupWindowRepositionOnImplicitSizeChange() +{ + if (!arePopupWindowsSupported()) + QSKIP("The platform doesn't support popup windows. Skipping test."); + + QQuickControlsApplicationHelper helper(this, "popupWindowImplicitSizeChange.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + + QQuickWindow *window = helper.window; + centerOnScreen(window); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + auto *button = window->property("button").value(); + QVERIFY(button); + + // Get the ToolTip attached to the button + auto *toolTipAttached = qobject_cast( + qmlAttachedPropertiesObject(button, false)); + QVERIFY(toolTipAttached); + QQuickPopup *toolTip = toolTipAttached->toolTip(); + QVERIFY(toolTip); + + toolTip->setPopupType(QQuickPopup::Window); + + // Set negative insets to simulate a style with drop-shadow (like FluentWinUI3). + toolTip->setLeftInset(-10); + toolTip->setRightInset(-10); + toolTip->setTopInset(-10); + toolTip->setBottomInset(-10); + + // Hover the button to show the tooltip. Start from the bottom of the + // window to ensure a proper hover-enter transition on the button. + const QPointF buttonCenter = button->mapToItem(window->contentItem(), + QPointF(button->width() / 2, button->height() / 2)); + PointLerper lerper(window, QPoint(buttonCenter.x(), window->height() - 1)); + lerper.move(buttonCenter.toPoint()); + QTRY_VERIFY(toolTip->isOpened()); + + auto *popupPrivate = QQuickPopupPrivate::get(toolTip); + TRY_VERIFY_POPUP_OPENED(toolTip); + auto *popupWindow = popupPrivate->popupWindow; + QVERIFY(popupWindow); + QVERIFY(QTest::qWaitForWindowExposed(popupWindow)); + + const int initialY = popupWindow->y(); + + const int initialWidth = popupWindow->width(); + + // Change the text to something much longer, triggering implicit size change. + // Without the fix, the popup window is resized but not repositioned, + // causing the y position to drift. + window->setProperty("toolTipText", "Flinstone, Fred - a much longer tooltip text"); + + // Wait for the popup window to finish resizing before checking position. + QTRY_VERIFY(popupWindow->width() > initialWidth); + + // The tooltip should remain at the same y position. + // Without the fix (missing reposition()), y shifts incorrectly. + QCOMPARE(popupWindow->y(), initialY); +} + void tst_QQuickPopup::propagateTouchEvents() { QQuickApplicationHelper helper(this, "propagateTouchEvents.qml"); @@ -3694,6 +3784,102 @@ void tst_QQuickPopup::propagateTouchEvents() QTRY_VERIFY(!popup->isOpened()); } +void tst_QQuickPopup::blockEventsBehindModal_data() +{ + QTest::addColumn("popupType"); + QTest::addColumn("device"); + + // QTest::newRow("window: primary") // TODO QTBUG-131786 QTBUG-145585 QTBUG-141362 etc. + // << QQuickPopup::Window + // << QPointingDevice::primaryPointingDevice(); + QTest::newRow("item: primary") + << QQuickPopup::Item + << QPointingDevice::primaryPointingDevice(); + QTest::newRow("item: touch") + << QQuickPopup::Item + << static_cast(touchScreen.get()); + QTest::newRow("window: touch") + << QQuickPopup::Window + << static_cast(touchScreen.get()); +#if QT_CONFIG(tabletevent) && !defined(Q_OS_QNX) + QTest::newRow("window: stylus") // QTBUG-135879 + << QQuickPopup::Window + << tabletStylusDevice.get(); + QTest::newRow("item: stylus") + << QQuickPopup::Item + << tabletStylusDevice.get(); +#endif +} + +void tst_QQuickPopup::blockEventsBehindModal() +{ + if (isPlatformUbuntu()) + QSKIP("Ubuntu: sidebar tends to overlay the test window and interfere"); + + QFETCH(QQuickPopup::PopupType, popupType); + QFETCH(const QPointingDevice *, device); + + QQuickApplicationHelper helper(this, "windowWithInteractiveItemsAndPopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + helper.window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *extraButton = window->findChild("extra button"); + QVERIFY(extraButton); + QSignalSpy buttonPressedChangedSpy(extraButton, &QQuickAbstractButton::pressedChanged); + QPoint buttonP = extraButton->mapToScene(extraButton->boundingRect().center()).toPoint(); + + auto *tapHandler = window->findChild(); + QVERIFY(tapHandler); + QSignalSpy thPressedChangedSpy(tapHandler, &QQuickTapHandler::pressedChanged); + QPoint tapHandlerP = tapHandler->parentItem()->mapToScene(tapHandler->parentItem()->boundingRect().center()).toPoint(); + + auto *popup = window->findChild(); + QVERIFY(popup); + popup->setPopupType(popupType); + auto *popupCloseButton = popup->findChild(); + QVERIFY(popupCloseButton); + QSignalSpy closeButtonPressedChangedSpy(popupCloseButton, &QQuickAbstractButton::pressedChanged); + + auto pointerClick = [device](const QPoint &p, QQuickWindow *window) { + QQuickTest::pointerPress(device, window, 1, p); + QQuickTest::pointerRelease(device, window, 1, p); + }; + + // the popup is not shown yet: we can click anything with any device + QCOMPARE(buttonPressedChangedSpy.count(), 0); + QCOMPARE(thPressedChangedSpy.count(), 0); + pointerClick(buttonP, window); + QTRY_COMPARE(buttonPressedChangedSpy.count(), 2); + pointerClick(tapHandlerP, window); + QTRY_COMPARE(thPressedChangedSpy.count(), 2); + + // show the modal popup: then we cannot click anything behind it + popup->open(); + QTRY_VERIFY(popup->isOpened()); // wait for it to fully open + QQuickWindow *windowOfPopup = popupCloseButton->window(); // main window or native popup window + QVERIFY(QTest::qWaitForWindowExposed(windowOfPopup)); + pointerClick(buttonP, window); + QCOMPARE(buttonPressedChangedSpy.count(), 2); + pointerClick(tapHandlerP, window); + QCOMPARE(thPressedChangedSpy.count(), 2); + + // click the close button on the popup + QPoint popupCloseButtonP = popupCloseButton->mapToScene(popupCloseButton->boundingRect().center()).toPoint(); + pointerClick(popupCloseButtonP, windowOfPopup); + QTRY_COMPARE(closeButtonPressedChangedSpy.count(), 2); + + // wait for it to close, then click stuff in the main window again + QTRY_COMPARE(popup->isVisible(), false); + buttonPressedChangedSpy.clear(); + thPressedChangedSpy.clear(); + pointerClick(buttonP, window); + QTRY_COMPARE(buttonPressedChangedSpy.count(), 2); + pointerClick(tapHandlerP, window); + QTRY_COMPARE(thPressedChangedSpy.count(), 2); +} + void tst_QQuickPopup::spacingAndInsetsAreRevaluatedWhenChanged() { QQuickApplicationHelper helper(this, "simplepopup.qml"); @@ -3744,6 +3930,37 @@ void tst_QQuickPopup::spacingAndInsetsAreRevaluatedWhenChanged() QCOMPARE(popup->bottomInset(), initialBottomInset + offset); } +void tst_QQuickPopup::windowInsetsOrder() +{ + if (!arePopupWindowsSupported()) + QSKIP("The platform doesn't support popup windows. Skipping test."); + + QQuickApplicationHelper helper(this, "simplepopup.qml"); + QVERIFY2(helper.ready, helper.failureMessage()); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + auto *popup = window->property("popup").value(); + QVERIFY(popup); + + popup->setPopupType(QQuickPopup::Window); + // Use distinct values per side so any swap is immediately visible. + popup->setLeftInset(-5); + popup->setTopInset(-10); + popup->setRightInset(-15); + popup->setBottomInset(-20); + + popup->open(); + TRY_VERIFY_POPUP_OPENED(popup); + + const QMarginsF insets = QQuickPopupPrivate::get(popup)->windowInsets(); + QCOMPARE(insets.left(), 5); + QCOMPARE(insets.top(), 10); + QCOMPARE(insets.right(), 15); + QCOMPARE(insets.bottom(), 20); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickPopup) #include "tst_qquickpopup.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp index 29f3662169..5659be7aa0 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquicktextarea/tst_qquicktextarea.cpp @@ -215,7 +215,6 @@ void tst_QQuickTextArea::customContextMenuOnRelease() QQuickView window; QVERIFY(QQuickTest::showView(window, qmlUrl)); - window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); auto *ourContextMenu = window.rootObject()->property("ourContextMenu").value(); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickuniversalstyle/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickuniversalstyle/BLACKLIST index 81481e0a12..5d024b2a39 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickuniversalstyle/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/qquickuniversalstyle/BLACKLIST @@ -1,3 +1,2 @@ -[Material::test_comboBox] +[Universal::test_comboBox] wayland - diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/sanity/tst_sanity.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/sanity/tst_sanity.cpp index 88e04d7316..ec889ce03e 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/sanity/tst_sanity.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/sanity/tst_sanity.cpp @@ -43,8 +43,6 @@ private slots: void quickControlsSanityPlugin_data(); private: - QMap sourceQmlFiles; - QMap installedQmlFiles; QStringList m_importPaths; QQmlJSLinter m_linter; @@ -86,44 +84,15 @@ tst_Sanity::tst_Sanity() void tst_Sanity::initTestCase() { QQmlDataTest::initTestCase(); - QQmlEngine engine; - QQmlComponent component(&engine); - component.setData(QString("import QtQuick.Templates 2.%1; Control { }").arg(15).toUtf8(), QUrl()); - - const QStringList qmlTypeNames = QQmlMetaType::qmlTypeNames(); - - // Collect the files from each style in the source tree. - QDirIterator it(QQC2_IMPORT_PATH, QStringList() << "*.qml" << "*.js", QDir::Files, QDirIterator::Subdirectories); - while (it.hasNext()) { - it.next(); - QFileInfo info = it.fileInfo(); - if (qmlTypeNames.contains(QStringLiteral("QtQuick.Templates/") + info.baseName())) - sourceQmlFiles.insert(info.dir().dirName() + "/" + info.fileName(), info.filePath()); - } - - // Then, collect the files from each installed style directory. - const QList> styleRelativePaths = { - { "basic", "QtQuick/Controls/Basic" }, - { "fusion", "QtQuick/Controls/Fusion" }, - { "material", "QtQuick/Controls/Material" }, - { "universal", "QtQuick/Controls/Universal" }, - // TODO: add native styles: QTBUG-87108 - { "ios", "QtQuick/Controls/iOS" } - }; - for (const auto &stylePathPair : styleRelativePaths) { - forEachControl(&engine, QQC2_IMPORT_PATH, stylePathPair.first, stylePathPair.second, QStringList(), - [&](const QString &relativePath, const QUrl &absoluteUrl) { - installedQmlFiles.insert(relativePath, absoluteUrl.toLocalFile()); - }); - } + StyleInfo::instance()->initialize(QQC2_IMPORT_PATH); } void tst_Sanity::jsFiles() { - QMap::const_iterator it; - for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) { - if (QFileInfo(it.value()).suffix() == QStringLiteral("js")) - QFAIL(qPrintable(it.value() + ": JS files are not allowed")); + const QList sourceQmlFiles = StyleInfo::instance()->sourceQmlFiles(); + for (auto it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) { + if (QFileInfo(it->absolutePath).suffix() == QStringLiteral("js")) + QFAIL(qPrintable(it->absolutePath + ": JS files are not allowed")); } } @@ -145,9 +114,9 @@ void tst_Sanity::qmllint_data() QTest::addColumn("control"); QTest::addColumn("filePath"); - QMap::const_iterator it; - for (it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) - QTest::newRow(qPrintable(it.key())) << it.key() << it.value(); + const QList sourceQmlFiles = StyleInfo::instance()->sourceQmlFiles(); + for (auto it = sourceQmlFiles.constBegin(); it != sourceQmlFiles.constEnd(); ++it) + QTest::newRow(qPrintable(it->relativePath)) << it->relativePath << it->absolutePath; } void tst_Sanity::quickControlsSanityPlugin() diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/CMakeLists.txt new file mode 100644 index 0000000000..502597f90e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if (NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_stylekit LANGUAGES C CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +##################################################################### +## tst_stylekit Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/data/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_stylekit + SOURCES + tst_stylekit.cpp + LIBRARIES + Qt::Gui + Qt::Quick + Qt::QuickPrivate + Qt::QuickControls2 + Qt::QuickTemplates2 + Qt::QuickTemplates2Private + Qt::LabsStyleKit + Qt::LabsStyleKitPrivate + Qt::QuickTestUtilsPrivate + TESTDATA ${test_data} +) + +qt_internal_extend_target(tst_stylekit CONDITION ANDROID OR IOS + DEFINES + QT_QMLTEST_DATADIR=":/data" +) + +qt_internal_extend_target(tst_stylekit CONDITION NOT ANDROID AND NOT IOS + DEFINES + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" +) diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/data/styleVariations.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/data/styleVariations.qml new file mode 100644 index 0000000000..fe45030adc --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/data/styleVariations.qml @@ -0,0 +1,86 @@ +import QtQuick +import Qt.labs.StyleKit + +Item { + width: 640 + height: 480 + + property alias insideFrame: insideFrame + property alias outsideFrame: outsideFrame + property alias insideFrame_instanceVariation: insideFrame_instanceVariation + property alias outsideFrame_instanceVariation: outsideFrame_instanceVariation + + StyleKit.style: Style { + // Pin to Light theme so the test results are consistent regardless of the OS theme + themeName: "Light" + + StyleVariation { + id: typeVariation + button.background.border.width: 4 + } + + StyleVariation { + name: "instanceVariation" + button.background.border.width: 10 + button.background.radius: 8 + } + + button { + background.border.width: 2 + background.radius: 4 + } + + frame { + variations: [typeVariation] + } + + light: Theme { + StyleVariation { + name: "instanceVariation" + button.background.border.width: 5 + } + + frame { + variations: [typeVariation] + } + + button { + background.border.width: 3 + } + } + } + + Column { + anchors.fill: parent + anchors.margins: 50 + spacing: 10 + + Button { + id: outsideFrame + text: "outsideFrame" + } + + Button { + id: outsideFrame_instanceVariation + text: "outsideFrame_instanceVariation" + StyleVariation.variations: ["instanceVariation"] + } + + Frame { + Column { + spacing: 10 + anchors.fill: parent + Button { + id: insideFrame + text: "insideFrame" + } + + Button { + id: insideFrame_instanceVariation + text: "insideFrame_instanceVariation" + StyleVariation.variations: ["instanceVariation"] + } + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/tst_stylekit.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/tst_stylekit.cpp new file mode 100644 index 0000000000..511e66dc51 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickcontrols/stylekit/tst_stylekit.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define LOAD_STYLEKIT_FILE(fileName) \ + QScopedPointer view(new QQuickView(nullptr)); \ + view->setSource(testFileUrl(fileName)); \ + view->show(); \ + view->requestActivate(); \ + QVERIFY(QTest::qWaitForWindowActive(view.get())); + +#define GET_CONTROL(NAME) \ + QQuickItem * NAME ## Control = view->rootObject()->property(#NAME).value(); \ + QVERIFY(NAME ## Control); \ + auto * NAME = NAME ## Control ->findChild(); \ + QVERIFY(NAME) + +class tst_StyleKit : public QQmlDataTest +{ + Q_OBJECT +public: + tst_StyleKit(); + +private slots: + void propagation_variations(); +}; + +tst_StyleKit::tst_StyleKit() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ +} + +void tst_StyleKit::propagation_variations() +{ + /* Test that variations respect the documented propagation order. That is, + * anything in a Theme should override anything in a Style. From this follows that + * properties set on a variation in the Style should be shadowed by the same + * properties set anywhere in the Theme (e.g. Theme.control will shadow an instance + * variation in the Style). + * Also, with respect to propagation, it doesn't matter if a type variation is defined + * in the Style or the Theme, what matters is from where it's used. + * If both an instance variation and a type variation are defined in the same Style + * or Theme, with the same properties, the instance variation takes precedence. */ + LOAD_STYLEKIT_FILE("styleVariations.qml") + + GET_CONTROL(insideFrame); + QCOMPARE(insideFrame->background()->border()->width(), 4); + QCOMPARE(insideFrame->background()->radius(), 4); + + GET_CONTROL(outsideFrame); + QCOMPARE(outsideFrame->background()->border()->width(), 3); + QCOMPARE(outsideFrame->background()->radius(), 4); + + GET_CONTROL(insideFrame_instanceVariation); + QCOMPARE(insideFrame_instanceVariation->background()->border()->width(), 5); + QCOMPARE(insideFrame_instanceVariation->background()->radius(), 8); + + GET_CONTROL(outsideFrame_instanceVariation); + QCOMPARE(outsideFrame_instanceVariation->background()->border()->width(), 5); + QCOMPARE(outsideFrame_instanceVariation->background()->radius(), 8); +} + +QTEST_MAIN(tst_StyleKit) + +#include "tst_stylekit.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST b/local/recipes/qt/qtdeclarative/source/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST index 822b92dd19..e72f864da4 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickdialogs/qquickfiledialogimpl/BLACKLIST @@ -5,3 +5,9 @@ # QTBUG-92585 [fileMode:OpenFiles] * + +[itemsDisabledWhenNecessary] +wayland + +[fileMode] +wayland diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quicktest/activefocus/tst_activefocus.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quicktest/activefocus/tst_activefocus.cpp index edbcc34224..5b5efa60df 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quicktest/activefocus/tst_activefocus.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quicktest/activefocus/tst_activefocus.cpp @@ -37,7 +37,6 @@ void tst_ActiveFocus::failureMessage() QQuickWindow *window = helper.window; window->show(); - window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); auto *item = window->property("item").value(); diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/itemWithPopupInside.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/itemWithPopupInside.qml new file mode 100644 index 0000000000..a93bd886a4 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/itemWithPopupInside.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Controls + +Item { + id: root + width: 800 + height: 600 + + Popup { + visible: true + popupType: Popup.Item + Column { + TextField { + objectName: "textField1" + } + TextField { + objectName: "textField2" + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/nonItem.qml b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/nonItem.qml new file mode 100644 index 0000000000..dc4de80bad --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/data/nonItem.qml @@ -0,0 +1,7 @@ +import QtQml + +QtObject { + Component.onCompleted: { + console.log("Completed") + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 5c3b0c5ed6..a0156d85e6 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -169,6 +169,8 @@ private slots: void fromModuleCtor(); void loadFromModule_data(); void loadFromModule(); + void nonItem(); + void focusTabFence(); private: QPointingDevice *device = QTest::createTouchDevice(); @@ -434,8 +436,8 @@ void tst_qquickwidget::readback() QImage img = view->grabFramebuffer(); QVERIFY(!img.isNull()); - QCOMPARE(img.width(), qCeil(view->width() * view->devicePixelRatio())); - QCOMPARE(img.height(), qCeil(view->height() * view->devicePixelRatio())); + // Match QQuickWidget's internal FBO size: size() * devicePixelRatio() via QSize::operator*(qreal). + QCOMPARE(img.size(), view->size() * view->devicePixelRatio()); QRgb pix = img.pixel(5, 5); QCOMPARE(pix, qRgb(255, 0, 0)); @@ -897,7 +899,7 @@ void tst_qquickwidget::focusChain() layout.addWidget(&middleWidget); layout.addWidget(&qqw2); window.show(); - window.windowHandle()->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(&window)); QVERIFY(QTest::qWaitForWindowExposed(&window)); QQuickItem *root1 = qqw1.rootObject(); @@ -1437,6 +1439,49 @@ void tst_qquickwidget::loadFromModule() } } +void tst_qquickwidget::nonItem() +{ + QQuickWidget widget; + QTest::ignoreMessage(QtDebugMsg, "Completed"); + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression( + "QQuickWidget only supports loading of root objects " + "that derive from QQuickItem")); + widget.setSource(testFileUrl("nonItem.qml")); + QCOMPARE(widget.status(), QQuickWidget::Error); + QCOMPARE(widget.rootObject(), nullptr); +} + +void tst_qquickwidget::focusTabFence() +{ + QWidget widget; + auto *quickWidget = new QQuickWidget; + quickWidget->setSource(testFileUrl("itemWithPopupInside.qml")); + quickWidget->setParent(&widget); + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(widget.windowHandle())); + + auto *textField1 = quickWidget->rootObject()->findChild("textField1"); + QVERIFY(textField1); + auto *textField2 = quickWidget->rootObject()->findChild("textField2"); + QVERIFY(textField2); + + widget.setFocus(); + QTRY_VERIFY(widget.hasFocus()); + textField1->forceActiveFocus(); + QTRY_COMPARE(quickWidget->quickWindow()->activeFocusItem(), textField1); + + QTest::keyClick(quickWidget, Qt::Key_Tab); + QTRY_COMPARE(quickWidget->quickWindow()->activeFocusItem(), textField2); + QTest::keyClick(quickWidget, Qt::Key_Tab); + QTRY_COMPARE(quickWidget->quickWindow()->activeFocusItem(), textField1); + QTest::keyClick(quickWidget, Qt::Key_Tab); + QTRY_COMPARE(quickWidget->quickWindow()->activeFocusItem(), textField2); + QTest::keyClick(quickWidget, Qt::Key_Tab); + QTRY_COMPARE(quickWidget->quickWindow()->activeFocusItem(), textField1); +} + QTEST_MAIN(tst_qquickwidget) #include "tst_qquickwidget.moc" diff --git a/local/recipes/qt/qtdeclarative/source/tests/auto/wasm/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/auto/wasm/CMakeLists.txt index 6e4c194410..ffb3074f05 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/auto/wasm/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/auto/wasm/CMakeLists.txt @@ -1,4 +1,6 @@ # Copyright (C) 2025 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -add_subdirectory(selenium) +# Temporarily drop test until it works + +# add_subdirectory(selenium) diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/OrderedPaths.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/OrderedPaths.qml index 688fbfdb59..81e52cada6 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/OrderedPaths.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/OrderedPaths.qml @@ -4,9 +4,11 @@ import QtQuick.Shapes Item { id: root property bool async: false + property alias isAnimating: anim.running property int counter: 0 NumberAnimation { + id: anim target: root property: "counter" duration: 2000 diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order.qml index 14d2a322f9..a5210c89cc 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order.qml @@ -3,8 +3,10 @@ import QtQuick Item { width: 320 height: 480 + property bool suspendGrabbing: paths.isAnimating OrderedPaths { + id: paths anchors.fill: parent async: false } diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order2.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order2.qml index 1887cc527e..269cf0fb80 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order2.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order2.qml @@ -5,9 +5,11 @@ Item { id: root width: 320 height: 480 + property bool suspendGrabbing: anim.running property int counter: 0 NumberAnimation { + id: anim target: root property: "counter" duration: 150 diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order_async.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order_async.qml index 5dc96721b3..7bf4ae6476 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order_async.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_order_async.qml @@ -3,8 +3,10 @@ import QtQuick Item { width: 320 height: 480 + property bool suspendGrabbing: paths.isAnimating OrderedPaths { + id: paths anchors.fill: parent async: true } diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_remove_paths.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_remove_paths.qml index 54ce3d8108..fc25e5642f 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_remove_paths.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_remove_paths.qml @@ -2,8 +2,10 @@ import QtQuick import QtQuick.Shapes Rectangle { + id: topLevel width: 320 height: 480 + property bool suspendGrabbing: anim.running ListModel { id: renderers @@ -11,6 +13,13 @@ Rectangle { ListElement { renderer: Shape.CurveRenderer } } + property int step: 0 + NumberAnimation on step { + id: anim + to: 3 + duration: 500 + } + Row { padding: 10 spacing: 20 @@ -21,11 +30,7 @@ Rectangle { height: 400 color: "lightgray" - property int step: 0 - NumberAnimation on step { - to: 3 - duration: 300 - } + property int step: topLevel.step onStepChanged: { var d; diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_update.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_update.qml index e9d73e762e..7923c629d7 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_update.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shape/shape_update.qml @@ -6,6 +6,7 @@ Item { width: 480 height: 320 property color outlineColor: "blue" + property bool suspendGrabbing: true ListModel { id: renderers @@ -27,19 +28,15 @@ Item { property int offset: 30 property string label: "" - Timer { - interval: 17 - running: true - repeat: true - onTriggered: { - const str = 'Qt Rocks! ABCDEFGHIJKLMNOPQRSTUVWXYZ0123' - if (offset > 0) { - offset = offset - 1; - label = str.substring(offset); - } else { - stop(); - } - } + NumberAnimation on offset { + to: 0 + duration: 1000 + } + onOffsetChanged: { + const str = 'Qt Rocks! ABCDEFGHIJKLMNOPQRSTUVWXYZ0123' + shape.label = str.substring(offset); + if (offset === 0) + topItem.suspendGrabbing = false; } ShapePath { diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_1.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_1.svg new file mode 100644 index 0000000000..9448fcb527 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_2.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_2.svg new file mode 100644 index 0000000000..1d52d2d247 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_capStyle_shapes_2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_cosmetic.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_cosmetic.svg new file mode 100644 index 0000000000..c4c3aff264 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_cosmetic.svg @@ -0,0 +1,33 @@ + + + + + cosmetic 2px, scaled + + + + + 2px, scaled + + + + + 2px, normal + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_dash.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_dash.svg new file mode 100644 index 0000000000..5fdefa4cce --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_dash.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_joinStyle_shapes_1.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_joinStyle_shapes_1.svg new file mode 100644 index 0000000000..1da775a97e --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_joinStyle_shapes_1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_text.svg b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_text.svg new file mode 100644 index 0000000000..bacc04fff4 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/shared/svg/stroking_text.svg @@ -0,0 +1,4 @@ + + pizazz + pizazz + diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/text/textinput_text_baseline_adjustment.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/text/textinput_text_baseline_adjustment.qml new file mode 100644 index 0000000000..43dd5e2429 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/text/textinput_text_baseline_adjustment.qml @@ -0,0 +1,84 @@ +import QtQuick 2.0 + +Item { + width: 400 + height: 400 + + ListModel { + id: model + ListElement { name: "20px, bottom"; vAlign: Text.AlignBottom; size: 20 } + ListElement { name: "20px, top"; vAlign: Text.AlignTop; size: 20 } + ListElement { name: "20px, center"; vAlign: Text.AlignVCenter; size: 20 } + ListElement { name: "30px, bottom"; vAlign: Text.AlignBottom; size: 30 } + ListElement { name: "30px, top"; vAlign: Text.AlignTop; size: 30 } + ListElement { name: "30px, center"; vAlign: Text.AlignVCenter; size: 30 } + } + + Column { + anchors.fill: parent + spacing: 20 + Repeater { + anchors.fill: parent + model: model + Row { + height: 40 + Text { + text: name + ": " + width: 90 + verticalAlignment: Text.AlignBottom + } + + TextInput { + text: "foo😀" + verticalAlignment: vAlign + font.pixelSize: size + font.family: "Consolas" + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + opacity: 0.25 + color: "blue" + anchors.bottom: parent.baseline + } + } + + Text { + text: "foo😀" + verticalAlignment: vAlign + font.pixelSize: size + font.family: "Consolas" + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + opacity: 0.25 + color: "blue" + anchors.bottom: parent.baseline + } + + } + + TextEdit { + textFormat: TextEdit.PlainText + text: "foo😀" + verticalAlignment: vAlign + font.pixelSize: size + font.family: "Consolas" + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + opacity: 0.25 + color: "blue" + anchors.bottom: parent.baseline + } + } + } + } + } + +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/vectorimages/strokes.qml b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/vectorimages/strokes.qml new file mode 100644 index 0000000000..02bbb2658c --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/data/vectorimages/strokes.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.VectorImage + +Rectangle { + id: topLevelItem + width: 820 + height: 820 + + ListModel { + id: files + ListElement { src: "../shared/svg/stroking_capStyle_shapes_1.svg" } + ListElement { src: "../shared/svg/stroking_capStyle_shapes_2.svg" } + ListElement { src: "../shared/svg/stroking_cosmetic.svg" } + ListElement { src: "../shared/svg/stroking_dash.svg" } + ListElement { src: "../shared/svg/stroking_joinStyle_shapes_1.svg" } + ListElement { src: "../shared/svg/stroking_text.svg" } + } + + Grid { + columns: 2 + anchors.fill: parent + Repeater { + model: files + VectorImage { + source: src + preferredRendererType: VectorImage.CurveRenderer + width: 400 + height: implicitHeight * width / implicitWidth + clip: true + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/scenegrabber/main.cpp b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/scenegrabber/main.cpp index 4fd895c113..f98c5742c1 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/scenegrabber/main.cpp +++ b/local/recipes/qt/qtdeclarative/source/tests/baseline/scenegraph/scenegrabber/main.cpp @@ -44,7 +44,7 @@ class GrabbingView : public QQuickView public: GrabbingView(const QString &outputFile, const bool useAppWindow) - : ofile(outputFile), grabNo(0), isGrabbing(false), justShow(outputFile.isEmpty()), preferAppWindow(useAppWindow) + : ofile(outputFile), justShow(outputFile.isEmpty()), preferAppWindow(useAppWindow) { if (justShow) return; @@ -57,7 +57,7 @@ public: if (!preferAppWindow) QObject::connect(this, &QQuickWindow::afterRendering, this, &GrabbingView::startGrabbing, Qt::SingleShotConnection); - int sceneTimeout = qEnvironmentVariableIntegerValue("LANCELOT_SCENE_TIMEOUT").value_or(SCENE_TIMEOUT); + sceneTimeout = qEnvironmentVariableIntegerValue("LANCELOT_SCENE_TIMEOUT").value_or(SCENE_TIMEOUT); QTimer::singleShot(sceneTimeout, this, &GrabbingView::timedOut); runtime.start(); @@ -76,7 +76,7 @@ public: private slots: void startGrabbing() { - GRABBERDEBUG << "Starting grabbing"; + GRABBERDEBUG << "afterRendering received"; grab(); } @@ -88,9 +88,13 @@ private slots: } QScopedValueRollback grabGuard(isGrabbing, true); - if (grabNo > 0 && grabTimer->remainingTime() > grabTimer->interval() / 10) { - GRABBERDEBUG << "Skipping grab, too soon since last"; - return; + if (QQuickItem *rootObject = this->rootObject()) { + QVariant v = rootObject->property("suspendGrabbing"); + if (v.isValid() && v.canConvert() && v.toBool()) { + GRABBERDEBUG << "Skipping grab, item has suspended grabbing"; + grabTimer->start(); + return; + } } grabNo++; @@ -148,9 +152,10 @@ private: QTimer *grabTimer = nullptr; QElapsedTimer runtime; QString ofile; - int grabNo; - bool isGrabbing; - bool justShow; + int sceneTimeout = 0; + int grabNo = 0; + bool isGrabbing = false; + bool justShow = false; QQuickApplicationWindow *appwindow = nullptr; bool preferAppWindow = false; }; diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/manual/CMakeLists.txt index 38195a8dc8..5389388477 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/manual/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/CMakeLists.txt @@ -42,3 +42,4 @@ if(TARGET Qt::QuickVectorImage) endif() endif() add_subdirectory(e2e) +add_subdirectory(overridesemantics) diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/CMakeLists.txt new file mode 100644 index 0000000000..4bf0fa7890 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_manual_overridesemantics LANGUAGES CXX) + find_package(Qt6 REQUIRED COMPONENTS Core Qml) + qt_standard_project_setup(REQUIRES 6.11) +endif() + +qt_internal_add_manual_test(tst_manual_overridesemantics + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Qml +) + +qt_add_qml_module(tst_manual_overridesemantics + URI OverrideSemantics + QML_FILES + QmlPlainBase.qml + QmlVirtualBase.qml + QmlOverrideBase.qml + QmlFinalBase.qml + QmlMethodBase.qml + SOURCES + cpptypes.h +) diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlFinalBase.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlFinalBase.qml new file mode 100644 index 0000000000..a399894203 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlFinalBase.qml @@ -0,0 +1,4 @@ +import QtQml +QtObject { + final property int value: 1 +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlMethodBase.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlMethodBase.qml new file mode 100644 index 0000000000..595c623a69 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlMethodBase.qml @@ -0,0 +1,4 @@ +import QtQml +QtObject { + function value() : int { return 1; } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlOverrideBase.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlOverrideBase.qml new file mode 100644 index 0000000000..529c2c7f18 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlOverrideBase.qml @@ -0,0 +1,8 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +QtObject { + override property int value: 1 +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlPlainBase.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlPlainBase.qml new file mode 100644 index 0000000000..22d063641d --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlPlainBase.qml @@ -0,0 +1,4 @@ +import QtQml +QtObject { + property int value: 1 +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlVirtualBase.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlVirtualBase.qml new file mode 100644 index 0000000000..a27f68b9e4 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/QmlVirtualBase.qml @@ -0,0 +1,4 @@ +import QtQml +QtObject { + virtual property int value: 1 +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/cpptypes.h b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/cpptypes.h new file mode 100644 index 0000000000..68ce57b80b --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/cpptypes.h @@ -0,0 +1,328 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef CPPTYPES_H +#define CPPTYPES_H + +#include +#include + +// ============================================================================ +// C++ BASE TYPES — each exposes "value" returning 1 +// ============================================================================ + +class CppPlainBase : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ value CONSTANT) +public: + using QObject::QObject; + int value() const { return 1; } +}; + +class CppVirtualBase : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ value CONSTANT VIRTUAL) +public: + using QObject::QObject; + int value() const { return 1; } +}; + +class CppFinalBase : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ value CONSTANT FINAL) +public: + using QObject::QObject; + int value() const { return 1; } +}; + +class CppOverrideBase : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ value CONSTANT OVERRIDE) +public: + using QObject::QObject; + int value() const { return 1; } +}; + +class CppMethodBase : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + using QObject::QObject; + Q_INVOKABLE int value() const { return 1; } +}; + +// ============================================================================ +// C++ DERIVED: plain property shadows — each returns 2 +// ============================================================================ + +class CppBare_CppPlain : public CppPlainBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT) +public: + using CppPlainBase::CppPlainBase; + int derivedValue() const { return 2; } +}; + +class CppBare_CppVirtual : public CppVirtualBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT) +public: + using CppVirtualBase::CppVirtualBase; + int derivedValue() const { return 2; } +}; + +class CppBare_CppFinal : public CppFinalBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT) +public: + using CppFinalBase::CppFinalBase; + int derivedValue() const { return 2; } +}; + +class CppBare_CppMethod : public CppMethodBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT) +public: + using CppMethodBase::CppMethodBase; + int derivedValue() const { return 2; } +}; + +class CppBare_CppOverride : public CppOverrideBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT) +public: + using CppOverrideBase::CppOverrideBase; + int derivedValue() const { return 2; } +}; + +// ============================================================================ +// C++ DERIVED: VIRTUAL property — each returns 2 +// ============================================================================ + +class CppVirtual_CppPlain : public CppPlainBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT VIRTUAL) +public: + using CppPlainBase::CppPlainBase; + int derivedValue() const { return 2; } +}; + +class CppVirtual_CppVirtual : public CppVirtualBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT VIRTUAL) +public: + using CppVirtualBase::CppVirtualBase; + int derivedValue() const { return 2; } +}; + +class CppVirtual_CppFinal : public CppFinalBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT VIRTUAL) +public: + using CppFinalBase::CppFinalBase; + int derivedValue() const { return 2; } +}; + +class CppVirtual_CppMethod : public CppMethodBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT VIRTUAL) +public: + using CppMethodBase::CppMethodBase; + int derivedValue() const { return 2; } +}; + +class CppVirtual_CppOverride : public CppOverrideBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT VIRTUAL) +public: + using CppOverrideBase::CppOverrideBase; + int derivedValue() const { return 2; } +}; + +// ============================================================================ +// C++ DERIVED: OVERRIDE property — each returns 2 +// ============================================================================ + +class CppOverride_CppPlain : public CppPlainBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT OVERRIDE) +public: + using CppPlainBase::CppPlainBase; + int derivedValue() const { return 2; } +}; + +class CppOverride_CppVirtual : public CppVirtualBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT OVERRIDE) +public: + using CppVirtualBase::CppVirtualBase; + int derivedValue() const { return 2; } +}; + +class CppOverride_CppFinal : public CppFinalBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT OVERRIDE) +public: + using CppFinalBase::CppFinalBase; + int derivedValue() const { return 2; } +}; + +class CppOverride_CppMethod : public CppMethodBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT OVERRIDE) +public: + using CppMethodBase::CppMethodBase; + int derivedValue() const { return 2; } +}; + +class CppOverride_CppOverride : public CppOverrideBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT OVERRIDE) +public: + using CppOverrideBase::CppOverrideBase; + int derivedValue() const { return 2; } +}; + +// ============================================================================ +// C++ DERIVED: FINAL property — each returns 2 +// ============================================================================ + +class CppFinal_CppPlain : public CppPlainBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT FINAL) +public: + using CppPlainBase::CppPlainBase; + int derivedValue() const { return 2; } +}; + +class CppFinal_CppVirtual : public CppVirtualBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT FINAL) +public: + using CppVirtualBase::CppVirtualBase; + int derivedValue() const { return 2; } +}; + +class CppFinal_CppFinal : public CppFinalBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT FINAL) +public: + using CppFinalBase::CppFinalBase; + int derivedValue() const { return 2; } +}; + +class CppFinal_CppMethod : public CppMethodBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT FINAL) +public: + using CppMethodBase::CppMethodBase; + int derivedValue() const { return 2; } +}; + +class CppFinal_CppOverride : public CppOverrideBase +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(int value READ derivedValue CONSTANT FINAL) +public: + using CppOverrideBase::CppOverrideBase; + int derivedValue() const { return 2; } +}; + +// ============================================================================ +// C++ DERIVED: method shadows property/method — each returns 2 +// ============================================================================ + +class CppMethod_CppPlain : public CppPlainBase +{ + Q_OBJECT + QML_ELEMENT +public: + using CppPlainBase::CppPlainBase; + Q_INVOKABLE int value() const { return 2; } +}; + +class CppMethod_CppVirtual : public CppVirtualBase +{ + Q_OBJECT + QML_ELEMENT +public: + using CppVirtualBase::CppVirtualBase; + Q_INVOKABLE int value() const { return 2; } +}; + +class CppMethod_CppFinal : public CppFinalBase +{ + Q_OBJECT + QML_ELEMENT +public: + using CppFinalBase::CppFinalBase; + Q_INVOKABLE int value() const { return 2; } +}; + +class CppMethod_CppMethod : public CppMethodBase +{ + Q_OBJECT + QML_ELEMENT +public: + using CppMethodBase::CppMethodBase; + Q_INVOKABLE int value() const { return 2; } +}; + +class CppMethod_CppOverride : public CppOverrideBase +{ + Q_OBJECT + QML_ELEMENT +public: + using CppOverrideBase::CppOverrideBase; + Q_INVOKABLE int value() const { return 2; } +}; + +#endif // CPPTYPES_H diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/main.cpp b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/main.cpp new file mode 100644 index 0000000000..e814c907c3 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/overridesemantics/main.cpp @@ -0,0 +1,538 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "cpptypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Qt::StringLiterals; + +// --------------------------------------------------------------------------- +// Message capture +// --------------------------------------------------------------------------- + +struct CapturedMessage +{ + QtMsgType type; + QString category; + QString text; +}; + +static std::vector s_messages; +static QtMessageHandler s_previousHandler = nullptr; + +static void messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) +{ + s_messages.push_back({ type, QString::fromUtf8(ctx.category), msg }); +} + +static void installHandler() +{ + s_messages.clear(); + s_previousHandler = qInstallMessageHandler(messageHandler); +} + +static void restoreHandler() +{ + qInstallMessageHandler(s_previousHandler); +} + +// --------------------------------------------------------------------------- +// Classify captured messages about property cache / override semantics +// --------------------------------------------------------------------------- + +struct RuntimeResult +{ + QString level; // "clean", "debug", "warning", "error", "unknown" + QString message; // full message text +}; + +static RuntimeResult classifyMessages() +{ + if (s_messages.empty()) + return { u"clean"_s, {} }; + + RuntimeResult result{ u"unknown"_s, {} }; + + for (const auto &m : s_messages) { + if (!m.category.startsWith(u"qt.qml.propertyCache"_s)) + continue; + + QString level; + if (m.type == QtDebugMsg) + level = u"debug"_s; + else if (m.type == QtWarningMsg || m.type == QtCriticalMsg) + level = u"warning"_s; + + // Keep the most severe + if (result.level == u"unknown"_s || (result.level == u"debug"_s && level == u"warning"_s)) { + result.level = level; + result.message = m.text; + } + } + + // If there were messages but none matched our category, flag as unexpected + if (result.level == u"unknown"_s) + result.message = u"unexpected: "_s + s_messages.front().text; + + return result; +} + +// --------------------------------------------------------------------------- +// Test-case definition +// --------------------------------------------------------------------------- + +struct TestCase +{ + const char *baseDescription; // e.g. "C++ plain property" + const char *derivedDescription; // e.g. "QML override property" + // For C++ derived types: the QML type name to instantiate + // For QML derived types: empty (qmlSnippet is used instead) + const char *cppTypeName; + // QML body for the derived member (only for QML derived tests) + // e.g. "override property int value: 2" + const char *baseTypeName; // QML type name of the base + const char *qmlMember; // QML code for the derived member + bool derivedIsMethod; // true if derived declares a method, not a property +}; + +// clang-format off +static const TestCase s_testCases[] = { + // ===== C++ over C++, grouped by base ===== + { "C++ plain property", "C++ plain property", "CppBare_CppPlain", nullptr, nullptr, false }, + { "C++ plain property", "C++ VIRTUAL property", "CppVirtual_CppPlain", nullptr, nullptr, false }, + { "C++ plain property", "C++ OVERRIDE property", "CppOverride_CppPlain", nullptr, nullptr, false }, + { "C++ plain property", "C++ FINAL property", "CppFinal_CppPlain", nullptr, nullptr, false }, + { "C++ plain property", "C++ method", "CppMethod_CppPlain", nullptr, nullptr, true }, + + { "C++ VIRTUAL property", "C++ plain property", "CppBare_CppVirtual", nullptr, nullptr, false }, + { "C++ VIRTUAL property", "C++ VIRTUAL property", "CppVirtual_CppVirtual", nullptr, nullptr, false }, + { "C++ VIRTUAL property", "C++ OVERRIDE property", "CppOverride_CppVirtual",nullptr, nullptr, false }, + { "C++ VIRTUAL property", "C++ FINAL property", "CppFinal_CppVirtual", nullptr, nullptr, false }, + { "C++ VIRTUAL property", "C++ method", "CppMethod_CppVirtual", nullptr, nullptr, true }, + + { "C++ FINAL property", "C++ plain property", "CppBare_CppFinal", nullptr, nullptr, false }, + { "C++ FINAL property", "C++ VIRTUAL property", "CppVirtual_CppFinal", nullptr, nullptr, false }, + { "C++ FINAL property", "C++ OVERRIDE property", "CppOverride_CppFinal", nullptr, nullptr, false }, + { "C++ FINAL property", "C++ FINAL property", "CppFinal_CppFinal", nullptr, nullptr, false }, + { "C++ FINAL property", "C++ method", "CppMethod_CppFinal", nullptr, nullptr, true }, + + { "C++ OVERRIDE property", "C++ plain property", "CppBare_CppOverride", nullptr, nullptr, false }, + { "C++ OVERRIDE property", "C++ VIRTUAL property", "CppVirtual_CppOverride", nullptr, nullptr, false }, + { "C++ OVERRIDE property", "C++ OVERRIDE property", "CppOverride_CppOverride", nullptr, nullptr, false }, + { "C++ OVERRIDE property", "C++ FINAL property", "CppFinal_CppOverride", nullptr, nullptr, false }, + { "C++ OVERRIDE property", "C++ method", "CppMethod_CppOverride", nullptr, nullptr, true }, + + { "C++ method", "C++ plain property", "CppBare_CppMethod", nullptr, nullptr, false }, + { "C++ method", "C++ VIRTUAL property", "CppVirtual_CppMethod", nullptr, nullptr, false }, + { "C++ method", "C++ OVERRIDE property", "CppOverride_CppMethod", nullptr, nullptr, false }, + { "C++ method", "C++ FINAL property", "CppFinal_CppMethod", nullptr, nullptr, false }, + { "C++ method", "C++ method", "CppMethod_CppMethod", nullptr, nullptr, true }, + + // ===== QML over C++, grouped by base ===== + { "C++ plain property", "QML plain property", nullptr, "CppPlainBase", "property int value: 2", false }, + { "C++ plain property", "QML virtual property", nullptr, "CppPlainBase", "virtual property int value: 2", false }, + { "C++ plain property", "QML override property", nullptr, "CppPlainBase", "override property int value: 2", false }, + { "C++ plain property", "QML final property", nullptr, "CppPlainBase", "final property int value: 2", false }, + { "C++ plain property", "QML method", nullptr, "CppPlainBase", "function value() : int { return 2; }", true }, + + { "C++ VIRTUAL property", "QML plain property", nullptr, "CppVirtualBase", "property int value: 2", false }, + { "C++ VIRTUAL property", "QML virtual property", nullptr, "CppVirtualBase", "virtual property int value: 2", false }, + { "C++ VIRTUAL property", "QML override property", nullptr, "CppVirtualBase", "override property int value: 2", false }, + { "C++ VIRTUAL property", "QML final property", nullptr, "CppVirtualBase", "final property int value: 2", false }, + { "C++ VIRTUAL property", "QML method", nullptr, "CppVirtualBase", "function value() : int { return 2; }", true }, + + { "C++ FINAL property", "QML plain property", nullptr, "CppFinalBase", "property int value: 2", false }, + { "C++ FINAL property", "QML virtual property", nullptr, "CppFinalBase", "virtual property int value: 2", false }, + { "C++ FINAL property", "QML override property", nullptr, "CppFinalBase", "override property int value: 2", false }, + { "C++ FINAL property", "QML final property", nullptr, "CppFinalBase", "final property int value: 2", false }, + { "C++ FINAL property", "QML method", nullptr, "CppFinalBase", "function value() : int { return 2; }", true }, + + { "C++ OVERRIDE property", "QML plain property", nullptr, "CppOverrideBase","property int value: 2", false }, + { "C++ OVERRIDE property", "QML virtual property", nullptr, "CppOverrideBase","virtual property int value: 2", false }, + { "C++ OVERRIDE property", "QML override property", nullptr, "CppOverrideBase","override property int value: 2", false }, + { "C++ OVERRIDE property", "QML final property", nullptr, "CppOverrideBase","final property int value: 2", false }, + { "C++ OVERRIDE property", "QML method", nullptr, "CppOverrideBase","function value() : int { return 2; }", true }, + + { "C++ method", "QML plain property", nullptr, "CppMethodBase", "property int value: 2", false }, + { "C++ method", "QML virtual property", nullptr, "CppMethodBase", "virtual property int value: 2", false }, + { "C++ method", "QML override property", nullptr, "CppMethodBase", "override property int value: 2", false }, + { "C++ method", "QML final property", nullptr, "CppMethodBase", "final property int value: 2", false }, + { "C++ method", "QML method", nullptr, "CppMethodBase", "function value() : int { return 2; }", true }, + + // ===== QML over QML, grouped by base ===== + { "QML plain property", "QML plain property", nullptr, "QmlPlainBase", "property int value: 2", false }, + { "QML plain property", "QML virtual property", nullptr, "QmlPlainBase", "virtual property int value: 2", false }, + { "QML plain property", "QML override property", nullptr, "QmlPlainBase", "override property int value: 2", false }, + { "QML plain property", "QML final property", nullptr, "QmlPlainBase", "final property int value: 2", false }, + { "QML plain property", "QML method", nullptr, "QmlPlainBase", "function value() : int { return 2; }", true }, + + { "QML virtual property", "QML plain property", nullptr, "QmlVirtualBase", "property int value: 2", false }, + { "QML virtual property", "QML virtual property", nullptr, "QmlVirtualBase", "virtual property int value: 2", false }, + { "QML virtual property", "QML override property", nullptr, "QmlVirtualBase", "override property int value: 2", false }, + { "QML virtual property", "QML final property", nullptr, "QmlVirtualBase", "final property int value: 2", false }, + { "QML virtual property", "QML method", nullptr, "QmlVirtualBase", "function value() : int { return 2; }", true }, + + { "QML final property", "QML plain property", nullptr, "QmlFinalBase", "property int value: 2", false }, + { "QML final property", "QML virtual property", nullptr, "QmlFinalBase", "virtual property int value: 2", false }, + { "QML final property", "QML override property", nullptr, "QmlFinalBase", "override property int value: 2", false }, + { "QML final property", "QML final property", nullptr, "QmlFinalBase", "final property int value: 2", false }, + { "QML final property", "QML method", nullptr, "QmlFinalBase", "function value() : int { return 2; }", true }, + + { "QML override property", "QML plain property", nullptr, "QmlOverrideBase","property int value: 2", false }, + { "QML override property", "QML virtual property", nullptr, "QmlOverrideBase","virtual property int value: 2", false }, + { "QML override property", "QML override property", nullptr, "QmlOverrideBase","override property int value: 2", false }, + { "QML override property", "QML final property", nullptr, "QmlOverrideBase","final property int value: 2", false }, + { "QML override property", "QML method", nullptr, "QmlOverrideBase","function value() : int { return 2; }", true }, + + { "QML method", "QML plain property", nullptr, "QmlMethodBase", "property int value: 2", false }, + { "QML method", "QML virtual property", nullptr, "QmlMethodBase", "virtual property int value: 2", false }, + { "QML method", "QML override property", nullptr, "QmlMethodBase", "override property int value: 2", false }, + { "QML method", "QML final property", nullptr, "QmlMethodBase", "final property int value: 2", false }, + { "QML method", "QML method", nullptr, "QmlMethodBase", "function value() : int { return 2; }", true }, +}; +// clang-format on + +// --------------------------------------------------------------------------- +// QML snippet generation +// --------------------------------------------------------------------------- + +static QByteArray makeQml(const TestCase &tc) +{ + // The testValue property reads "value" through QML, which uses the property + // cache (unlike C++ QObject::property() which uses QMetaObject directly). + // This correctly reflects override-dropped scenarios. + const char *testValueBinding = tc.derivedIsMethod + ? " property var testValue: { var v = value; return typeof v === 'function' ? v() : " + "v; }\n" + : " property var testValue: value\n"; + + if (tc.cppTypeName) { + // C++ derived type — instantiate it and add testValue binding + return QByteArray("import QtQml\nimport OverrideSemantics\n") + tc.cppTypeName + " {\n" + + testValueBinding + "}\n"; + } + // QML derived type + return QByteArray("import QtQml\nimport OverrideSemantics\n") + tc.baseTypeName + " {\n " + + tc.qmlMember + "\n" + testValueBinding + "}\n"; +} + +// QML for qmllint — without the testValue helper, so it only lints the actual override +static QByteArray makeLintQml(const TestCase &tc) +{ + if (tc.cppTypeName) { + return QByteArray("import QtQml\nimport OverrideSemantics\n") + tc.cppTypeName + " {}\n"; + } + return QByteArray("import QtQml\nimport OverrideSemantics\n") + tc.baseTypeName + " {\n " + + tc.qmlMember + "\n}\n"; +} + +// --------------------------------------------------------------------------- +// Runtime test +// --------------------------------------------------------------------------- + +struct RuntimeTestResult +{ + QString level; // "clean", "debug", "warning", "error", "unknown" + QString message; // full diagnostic message + int cppPropValue = -1; // value read via QObject::property("value") + int cppMethodValue = -1; // value read via QMetaObject::invokeMethod("value") + int qmlValue = -1; // value read via QML binding (testValue) +}; + +static RuntimeTestResult runRuntimeTest(QQmlEngine *engine, const TestCase &tc) +{ + RuntimeTestResult result; + const QByteArray qml = makeQml(tc); + + installHandler(); + + QQmlComponent component(engine); + component.setData(qml, QUrl(u"test.qml"_s)); + + if (component.isError()) { + restoreHandler(); + auto rt = classifyMessages(); + result.level = u"error"_s; + const auto errors = component.errors(); + if (!errors.isEmpty()) + result.message = errors.first().description(); + if (result.message.isEmpty()) + result.message = rt.message; + return result; + } + + QScopedPointer obj(component.create()); + + restoreHandler(); + + if (!obj) { + auto rt = classifyMessages(); + result.level = u"error"_s; + result.message = rt.message.isEmpty() ? u"create() returned null"_s : rt.message; + return result; + } + + // Read value through both paths to show the difference + // C++ property path: QObject::property() uses QMetaObject directly + QVariant propVal = obj->property("value"); + if (propVal.isValid()) { + bool ok = false; + int v = propVal.toInt(&ok); + result.cppPropValue = ok ? v : -1; + } + // C++ method path: QMetaObject::invokeMethod() + // Only attempt if the meta-object actually has a "value()" method. + if (obj->metaObject()->indexOfMethod("value()") != -1) { + int methodResult = -1; + if (QMetaObject::invokeMethod(obj.data(), "value", Q_RETURN_ARG(int, methodResult))) + result.cppMethodValue = methodResult; + } + // QML path: testValue binding goes through the QML property cache + QVariant qmlVal = obj->property("testValue"); + if (qmlVal.isValid()) { + bool ok = false; + int v = qmlVal.toInt(&ok); + result.qmlValue = ok ? v : -1; + } + + auto rt = classifyMessages(); + result.level = rt.level; + result.message = rt.message; + + return result; +} + +// --------------------------------------------------------------------------- +// qmllint test +// --------------------------------------------------------------------------- + +static QString findQmllint() +{ + // Use QLibraryInfo to find qmllint in the Qt installation + const QString binDir = QLibraryInfo::path(QLibraryInfo::BinariesPath); + const QString candidate = binDir + u"/qmllint"_s; + if (QFile::exists(candidate)) + return candidate; + + // Try libexec too + const QString libexecDir = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath); + const QString candidate2 = libexecDir + u"/qmllint"_s; + if (QFile::exists(candidate2)) + return candidate2; + + // Fallback: try PATH + return u"qmllint"_s; +} + +struct LintResult +{ + QString level; // "clean", "warning", "error", "unknown", "n/a" + QString message; // full qmllint output +}; + +static LintResult runQmllint(const QString &qmllintPath, const QString &filePath, + const QStringList &importPaths) +{ + LintResult result; + + QProcess proc; + QStringList args; + for (const QString &path : importPaths) { + args << u"-I"_s << path; + } + args << filePath; + + proc.start(qmllintPath, args); + if (!proc.waitForFinished(10000)) { + result.level = u"n/a"_s; + result.message = u"timeout"_s; + return result; + } + + const QString output = (QString::fromUtf8(proc.readAllStandardError()) + + QString::fromUtf8(proc.readAllStandardOutput())).trimmed(); + + if (output.isEmpty()) { + result.level = u"clean"_s; + } else if (output.contains(u"Error:"_s) || output.contains(u"error:"_s)) { + result.level = u"error"_s; + } else if (output.contains(u"Warning:"_s) || output.contains(u"warning:"_s)) { + result.level = u"warning"_s; + } else if (output.contains(u"Info:"_s) || output.contains(u"info:"_s)) { + result.level = u"info"_s; + } else { + result.level = u"unknown"_s; // unexpected output + } + + result.message = output; + + return result; +} + +// --------------------------------------------------------------------------- +// Table formatting +// --------------------------------------------------------------------------- + +static QString padRight(const QString &s, int width) +{ + return s + QString(qMax(0, width - s.size()), u' '); +} + +// --------------------------------------------------------------------------- +// main +// --------------------------------------------------------------------------- + +int main(int argc, char *argv[]) +{ + // Enable debug-level logging for the property cache + QLoggingCategory::setFilterRules(u"qt.qml.propertyCache.append.debug=true"_s); + + QCoreApplication app(argc, argv); + + const bool verbose = app.arguments().contains(u"--verbose"_s); + + QQmlEngine engine; + + // Set up import paths: the module is in qrc, but we also need file-system + // access for QML base types when using setData() + const QString appDir = QCoreApplication::applicationDirPath(); + engine.addImportPath(appDir); + + // Find qmllint + const QString qmllintPath = findQmllint(); + + // Import paths for qmllint + QStringList lintImportPaths; + lintImportPaths << appDir; + lintImportPaths << QLibraryInfo::path(QLibraryInfo::QmlImportsPath); + + // Temporary directory for generated QML files + QTemporaryDir tempDir; + if (!tempDir.isValid()) { + qWarning("Failed to create temporary directory"); + return 1; + } + + // Column widths + const int colBase = 22; + const int colDerived = 24; + const int colRuntime = 7; + const int colCppVal = 9; + const int colQmlVal = 9; + const int colLint = 7; + + auto mdRow = [&](const QString &base, const QString &derived, const QString &runtime, + const QString &cppVal, const QString &qmlVal, const QString &lint, + const QString &rtMsg = {}, const QString &lintMsg = {}) { + fprintf(stdout, "| %s | %s | %s | %s | %s | %s", + qPrintable(padRight(base, colBase)), + qPrintable(padRight(derived, colDerived)), + qPrintable(padRight(runtime, colRuntime)), + qPrintable(padRight(cppVal, colCppVal)), + qPrintable(padRight(qmlVal, colQmlVal)), + qPrintable(padRight(lint, colLint))); + if (verbose) + fprintf(stdout, " | %s | %s", qPrintable(rtMsg), qPrintable(lintMsg)); + fprintf(stdout, " |\n"); + }; + + auto mdSep = [&]() { + fprintf(stdout, "|%s|%s|%s|%s|%s|%s", + qPrintable(QString(colBase + 2, u'-')), + qPrintable(QString(colDerived + 2, u'-')), + qPrintable(QString(colRuntime + 2, u'-')), + qPrintable(QString(colCppVal + 2, u'-')), + qPrintable(QString(colQmlVal + 2, u'-')), + qPrintable(QString(colLint + 2, u'-'))); + if (verbose) + fprintf(stdout, "|---|---"); + fprintf(stdout, "|\n"); + }; + + // Header + mdRow(u"Base"_s, u"Derived"_s, u"Runtime"_s, u"C++ value"_s, u"QML value"_s, u"qmllint"_s, + verbose ? u"Runtime message"_s : QString(), + verbose ? u"qmllint message"_s : QString()); + mdSep(); + + const size_t numTests = sizeof(s_testCases) / sizeof(s_testCases[0]); + QString lastBaseDesc; + + for (size_t i = 0; i < numTests; ++i) { + const TestCase &tc = s_testCases[i]; + + // Print separator between different base groups + const QString baseDesc = QString::fromUtf8(tc.baseDescription); + if (!lastBaseDesc.isEmpty() && baseDesc != lastBaseDesc) + mdSep(); + lastBaseDesc = baseDesc; + + // --- Runtime test --- + auto rtResult = runRuntimeTest(&engine, tc); + + QString runtimeStr = rtResult.level; + + auto fmtValue = [](int v) -> QString { + if (v == 2) return u"derived"_s; + if (v == 1) return u"base"_s; + return QString(); + }; + QString cppValStr; + { + QString propStr = fmtValue(rtResult.cppPropValue); + QString methStr = fmtValue(rtResult.cppMethodValue); + if (!propStr.isEmpty() && !methStr.isEmpty() && propStr != methStr) + cppValStr = u"both"_s; + else if (!propStr.isEmpty()) + cppValStr = propStr; + else if (!methStr.isEmpty()) + cppValStr = methStr; + else + cppValStr = u"n/a"_s; + } + QString qmlValStr = fmtValue(rtResult.qmlValue); + if (qmlValStr.isEmpty()) + qmlValStr = u"n/a"_s; + + // --- qmllint test --- + LintResult lintResult; + if (tc.cppTypeName && !tc.qmlMember) { + lintResult.level = u"n/a"_s; + } else { + // Write QML to temp file and run qmllint + const QByteArray qml = makeLintQml(tc); + const QString tempFile = tempDir.filePath( + QString::fromUtf8(tc.derivedDescription).replace(u' ', u'_') + u"_"_s + + QString::fromUtf8(tc.baseDescription).replace(u' ', u'_') + u".qml"_s); + QFile f(tempFile); + if (f.open(QIODevice::WriteOnly)) { + f.write(qml); + f.close(); + } + + lintResult = runQmllint(qmllintPath, tempFile, lintImportPaths); + } + + mdRow(baseDesc, QString::fromUtf8(tc.derivedDescription), runtimeStr, + cppValStr, qmlValStr, lintResult.level, + rtResult.message, lintResult.message); + } + + fprintf(stdout, "\nC++ value / QML value: base = base wins, derived = derived wins," + " both = property and method coexist, n/a = type failed to load\n"); + fprintf(stdout, "Runtime: clean = silent, debug = off by default, warning = on by default\n"); + fflush(stdout); + + return 0; +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/platforms/android/qtquickview_java/build.gradle b/local/recipes/qt/qtdeclarative/source/tests/manual/platforms/android/qtquickview_java/build.gradle index f093f43efa..6a8191aa26 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/manual/platforms/android/qtquickview_java/build.gradle +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/platforms/android/qtquickview_java/build.gradle @@ -1,4 +1,4 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { -id 'com.android.application' version '8.10.1' apply false +id 'com.android.application' version '9.0.0' apply false } diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/popup_dnd/modal-windows-accept-events-bug.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/popup_dnd/modal-windows-accept-events-bug.qml new file mode 100644 index 0000000000..5895d3c775 --- /dev/null +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/popup_dnd/modal-windows-accept-events-bug.qml @@ -0,0 +1,79 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Fusion + +ApplicationWindow { + width: 640 + height: 480 + visible: true + title: qsTr("Hello World") + + RowLayout { + anchors.fill: parent + + Pane { + Layout.fillHeight: true + Layout.preferredWidth: parent.width * 0.5 + + DropArea { + id: outerDropArea + anchors.fill: parent + } + + Popup { + id: popup + width: 480 + height: 320 + modal: true + focus: true + anchors.centerIn: Overlay.overlay + opacity: 0.5 + + CheckBox { + id: innerDropAreaCB + anchors.centerIn: parent + text: "Inner DropArea enable" + } + + DropArea { + id: innerDropArea + anchors.fill: parent + enabled: innerDropAreaCB.checked + } + } + + Row { + spacing: 10 + anchors.centerIn: parent + + Button { + text: "Open Popup" + onClicked: popup.open() + } + } + } + + Pane { + Layout.fillHeight: true + Layout.preferredWidth: parent.width * 0.5 + + ColumnLayout { + anchors.fill: parent + CheckBox { + id: outerAreaInUse + checked: outerDropArea.containsDrag + text: "Outer area active" + (outerDropArea.containsDrag ? ` (${outerDropArea.drag.x}, ${outerDropArea.drag.y})` : "") + } + + CheckBox { + id: popupInUse + checked: innerDropArea.containsDrag + text: "Inner area active" + (innerDropArea.containsDrag ? ` (${innerDropArea.drag.x}, ${innerDropArea.drag.y})` : "") + } + } + } + } +} diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/shared/CMakeLists.txt b/local/recipes/qt/qtdeclarative/source/tests/manual/shared/CMakeLists.txt index dd1a229461..ce947665dc 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/manual/shared/CMakeLists.txt +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/shared/CMakeLists.txt @@ -6,6 +6,9 @@ set_source_files_properties(CheckBox.qml TabSet.qml TextField.qml QT_QML_SOURCE_VERSIONS 2.1 ) +# this QML module is generated multiple times and therefore would get multiple .qmlls.ini files... +set(QT_QML_GENERATE_QMLLS_INI OFF) + set_source_files_properties(Images.qml PROPERTIES QT_QML_SINGLETON_TYPE true diff --git a/local/recipes/qt/qtdeclarative/source/tests/manual/tableview/tablemodel/json/main.qml b/local/recipes/qt/qtdeclarative/source/tests/manual/tableview/tablemodel/json/main.qml index 7c9d82d326..efa2774539 100644 --- a/local/recipes/qt/qtdeclarative/source/tests/manual/tableview/tablemodel/json/main.qml +++ b/local/recipes/qt/qtdeclarative/source/tests/manual/tableview/tablemodel/json/main.qml @@ -15,6 +15,7 @@ ApplicationWindow { height: 300 visible: true +//![parse] function requestJson() { let doc = new XMLHttpRequest() doc.onreadystatechange = function() { @@ -34,8 +35,11 @@ ApplicationWindow { doc.open("GET", "https://api.jolpi.ca/ergast/f1/2005/1/results.json") doc.send() } +//![parse] +//![assign] Component.onCompleted: requestJson() +//![assign] // Same as the data we get from ergast. Use it while developing // to avoid flooding the server with requests. // Component.onCompleted: tableView.model.rows = CachedJsonData.drivers @@ -58,6 +62,7 @@ ApplicationWindow { Layout.fillWidth: true Layout.fillHeight: true +//![model] model: TableModel { TableModelColumn { display: "driverId" } TableModelColumn { display: "code" } @@ -67,6 +72,7 @@ ApplicationWindow { TableModelColumn { display: "dateOfBirth" } TableModelColumn { display: "nationality" } } +//![model] delegate: TextField { objectName: "tableViewTextFieldDelegate" diff --git a/local/recipes/qt/qtdeclarative/source/tools/qmltc/main.cpp b/local/recipes/qt/qtdeclarative/source/tools/qmltc/main.cpp index ae952efe5a..8f3af354d1 100644 --- a/local/recipes/qt/qtdeclarative/source/tools/qmltc/main.cpp +++ b/local/recipes/qt/qtdeclarative/source/tools/qmltc/main.cpp @@ -264,8 +264,8 @@ int main(int argc, char **argv) QQmlJSImporter importer { importPaths, &mapper }; importer.setMetaDataMapper(&metaDataMapper); auto qmltcVisitor = [](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, - const QQmlJSImporter::ImportVisitorPrerequisites &p) { - QmltcVisitor v(p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles); + const QQmlJSImporter::ImportVisitorPrerequisites &p) { + QmltcVisitor v(self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles); QQmlJS::AST::Node::accept(rootNode, &v); }; importer.setImportVisitor(qmltcVisitor); @@ -275,11 +275,14 @@ int main(int argc, char **argv) logger.setCode(sourceCode); setupLogger(logger); - auto currentScope = QQmlJSScope::create(); - if (parser.isSet(moduleOption)) - currentScope->setOwnModuleName(parser.value(moduleOption)); - - QmltcVisitor visitor(currentScope, &importer, &logger, + auto currentScope = importer.importFile(url); + if (parser.isSet(moduleOption)) { + if (auto factory = currentScope.factory()) + factory->setModuleName(parser.value(moduleOption)); + else + currentScope->setOwnModuleName(parser.value(moduleOption)); + } + QmltcVisitor visitor(&importer, &logger, QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); visitor.setMode(QmltcVisitor::Compile); QmltcTypeResolver typeResolver { &importer }; diff --git a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltccompiler.cpp b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltccompiler.cpp index ac20eae2e5..e5bc4bc52d 100644 --- a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltccompiler.cpp +++ b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltccompiler.cpp @@ -30,12 +30,17 @@ static QList unboundRequiredProperties( QList requiredProperties{}; auto isPropertyRequired = [&type, &resolver](const auto &property) { - if (!type->isPropertyRequired(property.propertyName())) - return false; - if (type->hasPropertyBindings(property.propertyName())) return false; + if (type->isPropertyRequired(property.propertyName())) + return true; + + // We consider top-level aliases to required properties as required. + // This is somewhat blunt, but it captures the most common use case. + // If you have a type with multiple aliases to the same required property, + // the type will be impossible to instantiate, but that's a very strange + // arrangement in the first place. if (property.isAlias()) { QQmlJSUtils::AliasResolutionVisitor aliasVisitor; @@ -45,37 +50,19 @@ static QList unboundRequiredProperties( if (result.kind != QQmlJSUtils::AliasTarget_Property) return false; - // If the top level alias targets a property that is in - // the top level scope and that property is required, then - // we will already pick up the property during one of the - // iterations. - // Setting the property or the alias is the same so we - // discard one of the two, as otherwise we would require - // the user to pass two values for the same property ,in - // this case the alias. - // - // For example in: - // - // ``` - // Item { - // id: self - // required property int foo - // property alias bar: self.foo - // } - // ``` - // - // Both foo and bar are required but setting one or the - // other is the same operation so that we should choose - // only one. - if (result.owner == type && - type->isPropertyRequired(result.property.propertyName())) + if (!result.owner->isPropertyRequired(result.property.propertyName())) + return false; + + if (result.owner == type) return false; if (result.owner->hasPropertyBindings(result.property.propertyName())) return false; + + return true; } - return true; + return false; }; const auto properties = type->properties(); diff --git a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.cpp b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.cpp index 40d3d7c131..e82faa49b5 100644 --- a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.cpp +++ b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.cpp @@ -71,10 +71,9 @@ static bool isOrUnderComponent(QQmlJSScope::ConstPtr type) return false; } -QmltcVisitor::QmltcVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, - QQmlJSLogger *logger, const QString &implicitImportDirectory, - const QStringList &qmldirFiles) - : QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles) +QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, + const QString &implicitImportDirectory, const QStringList &qmldirFiles) + : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmldirFiles) { m_qmlTypeNames.append(QFileInfo(logger->filePath()).baseName()); // put document root } diff --git a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.h b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.h index 42b220ed97..ff01f3e644 100644 --- a/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.h +++ b/local/recipes/qt/qtdeclarative/source/tools/qmltc/qmltcvisitor.h @@ -30,7 +30,7 @@ class QmltcVisitor : public QQmlJSImportVisitor using InlineComponentOrDocumentRootName = QQmlJSScope::InlineComponentOrDocumentRootName; public: - QmltcVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger, + QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, const QStringList &qmldirFiles = QStringList()); diff --git a/local/recipes/qt/qtsvg/CVE-2026-6210.patch b/local/recipes/qt/qtsvg/CVE-2026-6210.patch deleted file mode 100644 index 0193ee3140..0000000000 --- a/local/recipes/qt/qtsvg/CVE-2026-6210.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 8368ba6ff143bd6b9a7fdf235918285d9b1f5d2a Mon Sep 17 00:00:00 2001 -From: Robert Löhning -Date: Thu, 26 Mar 2026 13:42:19 +0100 -Subject: [PATCH] Test types of nodes before downcasting them - -A bad cast in QSvgMarker::drawHelper lead to an endless recursion -resulting in a heap overflow. Credit to OSS-Fuzz which found this as -issue 496327371. - -Amends 534d072fe9c060ca3d1b968a717513426c69c956 - -While fixing that, I found another, similar case and fixed it, too, -although it didn't seem to cause a crash. - -Amends 29b848e9ac4e4e13c5b50116a81b1f2677196939 - -Pick-to: 6.8 -Change-Id: Ia57491aa329fea981307a709c5a6a750125fe2c7 -Reviewed-by: Hatem ElKharashy -(cherry picked from commit e488f852fa18c2afc2842a88eff8f66ad4105a45) -Reviewed-by: Qt Cherry-pick Bot ---- - -diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp -index 23606e6..5bf485e 100644 ---- a/src/svg/qsvgstructure.cpp -+++ b/src/svg/qsvgstructure.cpp -@@ -426,9 +426,10 @@ - const bool isPainting = (boundingRect == nullptr); - const auto markers = markersForNode(node); - for (auto &i : markers) { -- QSvgMarker *markNode = static_cast(node->document()->namedNode(i.markerId)); -- if (!markNode) -+ QSvgNode *referencedNode = node->document()->namedNode(i.markerId); -+ if (!referencedNode || referencedNode->type() != QSvgNode::Marker) - continue; -+ QSvgMarker *markNode = static_cast(referencedNode); - - p->save(); - p->translate(i.x, i.y); -@@ -729,8 +730,9 @@ - - // Chrome seems to return the mask of the mask if a mask is set on the mask - if (this->hasMask()) { -- QSvgMask *maskNode = static_cast(document()->namedNode(this->maskId())); -- if (maskNode) { -+ QSvgNode *referencedNode = document()->namedNode(this->maskId()); -+ if (referencedNode && referencedNode->type() == QSvgNode::Mask) { -+ QSvgMask *maskNode = static_cast(referencedNode); - QRectF boundsRect; - return maskNode->createMask(p, states, localRect, &boundsRect); - } -diff --git a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp -index 118f200..7bbbedc 100644 ---- a/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp -+++ b/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp -@@ -1868,6 +1868,17 @@ - // runtime error: signed integer overflow: -2147483648 + -1 cannot be represented in type 'int' - QTest::newRow("excessive moveto in path") // id=406541912 - << R"()"_ba; -+ // Bad-cast to QSvgMarker from QSvgLine -> Heap-buffer-overflow -+ QTest::newRow("line-as-marker") // id=496327371 -+ << R"-()-"_ba; -+ QTest::newRow("line-as-mask") // modeled after 496327371 to test similar problem, needs UBSAN -+ << R"-( -+ -+ -+ -+ -+ -+ )-"_ba; - } - - void tst_QSvgRenderer::ossFuzzRender() diff --git a/local/recipes/qt/qtsvg/recipe.toml b/local/recipes/qt/qtsvg/recipe.toml index b21495bdd8..fdcd44f019 100644 --- a/local/recipes/qt/qtsvg/recipe.toml +++ b/local/recipes/qt/qtsvg/recipe.toml @@ -99,6 +99,4 @@ redbear_qt_copy_optional_stage_dir_to_sysroot "${STAGE_USR}" "${SYSROOT}" metaty [source] tar = "https://download.qt.io/official_releases/qt/6.11/6.11.1/submodules/qtsvg-everywhere-src-6.11.1.tar.xz" blake3 = "49b947e1a96bf0a29a1ee84c231a518a1413d9f3ec360617e405400e510508b2" -patches = [ - "CVE-2026-6210.patch", -] # heap-overflow fix from Qt patch, cherry-picked from upstream 6.8 +patches = [] # CVE-2026-6210 heap-overflow fix is included upstream as of 6.11.1 diff --git a/local/recipes/qt/qtsvg/source.tar b/local/recipes/qt/qtsvg/source.tar index 129f7ea754..edf97b1b6e 100644 Binary files a/local/recipes/qt/qtsvg/source.tar and b/local/recipes/qt/qtsvg/source.tar differ diff --git a/local/recipes/qt/qtsvg/source/.cmake.conf b/local/recipes/qt/qtsvg/source/.cmake.conf index a719905fe9..905c66cad0 100644 --- a/local/recipes/qt/qtsvg/source/.cmake.conf +++ b/local/recipes/qt/qtsvg/source/.cmake.conf @@ -1,8 +1,8 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.11.1") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES - "QT_NO_CONTEXTLESS_CONNECT=1" - "QT_NO_FOREACH=1" - "QT_NO_QASCONST=1" - "QT_NO_URL_CAST_FROM_STRING=1" + "QT_ENABLE_STRICT_MODE_UP_TO=QT_VERSION_CHECK(6,11,0)" + # not yet in strict mode: + "QT_LEAN_HEADERS=2" + "QT_NO_QPAIR=1" ) diff --git a/local/recipes/qt/qtsvg/source/.tag b/local/recipes/qt/qtsvg/source/.tag index de6ca097ce..e437da9cae 100644 --- a/local/recipes/qt/qtsvg/source/.tag +++ b/local/recipes/qt/qtsvg/source/.tag @@ -1 +1 @@ -4126867385dc0b7a9404dcd69726c28ed0d5e10a +2596f43da2dc72d2afecc084355b0f5f87922a6c diff --git a/local/recipes/qt/qtsvg/source/dependencies.yaml b/local/recipes/qt/qtsvg/source/dependencies.yaml index 0f9dedc097..d32a7c3d5f 100644 --- a/local/recipes/qt/qtsvg/source/dependencies.yaml +++ b/local/recipes/qt/qtsvg/source/dependencies.yaml @@ -1,4 +1,4 @@ dependencies: ../qtbase: - ref: 8ba7ea4b77a4b8f1948760221e264917ddc9e1c8 + ref: 59c81a3c2247b821b9b84b4eb8d939b77e07e276 required: true diff --git a/local/recipes/qt/qtsvg/source/examples/CMakeLists.txt b/local/recipes/qt/qtsvg/source/examples/CMakeLists.txt index 8034694795..b2c25dfdb7 100644 --- a/local/recipes/qt/qtsvg/source/examples/CMakeLists.txt +++ b/local/recipes/qt/qtsvg/source/examples/CMakeLists.txt @@ -3,7 +3,7 @@ qt_examples_build_begin(EXTERNAL_BUILD) -if (TARGET Qt::Widgets) +if (TARGET Qt6::Widgets) add_subdirectory(svg) endif() diff --git a/local/recipes/qt/qtsvg/source/examples/svg/svgviewer/CMakeLists.txt b/local/recipes/qt/qtsvg/source/examples/svg/svgviewer/CMakeLists.txt index c09ee272bf..c214bf2154 100644 --- a/local/recipes/qt/qtsvg/source/examples/svg/svgviewer/CMakeLists.txt +++ b/local/recipes/qt/qtsvg/source/examples/svg/svgviewer/CMakeLists.txt @@ -4,17 +4,10 @@ cmake_minimum_required(VERSION 3.16) project(svgviewer LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/svg/svgviewer") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Svg Widgets) +qt_standard_project_setup() + qt_add_executable(svgviewer exportdialog.cpp exportdialog.h main.cpp @@ -36,15 +29,9 @@ set_target_properties(svgviewer PROPERTIES MACOSX_BUNDLE TRUE ) -target_link_libraries(svgviewer PUBLIC - Qt::Core - Qt::Gui - Qt::Svg - Qt::Widgets -) - -install(TARGETS svgviewer - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +target_link_libraries(svgviewer PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Svg + Qt6::Widgets ) diff --git a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvgabstractanimation.cpp b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvgabstractanimation.cpp index 774a022957..12c106a5e7 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvgabstractanimation.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvgabstractanimation.cpp @@ -17,7 +17,7 @@ QSvgAbstractAnimation::QSvgAbstractAnimation() QSvgAbstractAnimation::~QSvgAbstractAnimation() { - for (auto prop : m_properties) + for (const QSvgAbstractAnimatedProperty *prop : std::as_const(m_properties)) delete prop; } @@ -58,7 +58,7 @@ void QSvgAbstractAnimation::evaluateAnimation(qreal elapsedTime) qreal fractionOfCurrentIterationTime = fractionOfTotalTime - std::trunc(fractionOfTotalTime); - for (QSvgAbstractAnimatedProperty *animProperty : m_properties) { + for (const QSvgAbstractAnimatedProperty *animProperty : std::as_const(m_properties)) { const QList keyFrames = animProperty->keyFrames(); for (int i = 1; i < keyFrames.size(); i++) { qreal from = keyFrames.at(i - 1); diff --git a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimatedproperty.cpp b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimatedproperty.cpp index 55a87b652c..11dc9aa01e 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimatedproperty.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimatedproperty.cpp @@ -7,8 +7,10 @@ #include #include #include + #include #include +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator.cpp b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator.cpp index 4a1c76efb8..0ef2d282b1 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator.cpp @@ -18,7 +18,7 @@ QSvgAbstractAnimator::QSvgAbstractAnimator() QSvgAbstractAnimator::~QSvgAbstractAnimator() { for (auto animationHash : {&m_animationsCSS, &m_animationsSMIL}) { - for (const auto &nodeAnimations : *std::as_const(animationHash)) { + for (const auto &nodeAnimations : std::as_const(*animationHash)) { for (QSvgAbstractAnimation *anim : nodeAnimations) delete anim; } @@ -46,7 +46,7 @@ void QSvgAbstractAnimator::advanceAnimations() { qreal elapsedTime = currentElapsed(); for (auto animationHash : {&m_animationsCSS, &m_animationsSMIL}) { - for (const auto &nodeAnimations : *std::as_const(animationHash)) { + for (const auto &nodeAnimations : std::as_const(*animationHash)) { for (QSvgAbstractAnimation *anim : nodeAnimations) anim->evaluateAnimation(elapsedTime); } diff --git a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator_p.h b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator_p.h index b6dfdd9019..618fd5e50e 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/animation/qsvganimator_p.h @@ -21,6 +21,9 @@ #include #include "qsvgabstractanimation_p.h" +#include +#include + QT_BEGIN_NAMESPACE class Q_SVG_EXPORT QSvgAbstractAnimator diff --git a/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcsshandler.cpp b/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcsshandler.cpp index 005463944c..f21a1833fd 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcsshandler.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcsshandler.cpp @@ -113,9 +113,9 @@ bool fillTransformProperty(const QList &keyFrames, QSvgAnimate // Stores each key frame's list of components QList> keyFramesComponents; - for (CssKeyFrameValue keyFrame : keyFrames) { + for (const CssKeyFrameValue &keyFrame : keyFrames) { QList components; - for (QCss::Value val : keyFrame.values) { + for (const QCss::Value &val : keyFrame.values) { if (val.type == QCss::Value::Function) { QStringList lst = val.variant.toStringList(); QStringView transformType = lst.value(0); @@ -188,7 +188,7 @@ bool fillTransformProperty(const QList &keyFrames, QSvgAnimate if (!validateTransform(keyFramesComponents)) return false; - for (auto comp : keyFramesComponents) { + for (const auto &comp : std::as_const(keyFramesComponents)) { prop->appendComponents(comp); } prop->setTransformCount(keyFramesComponents.first().size()); @@ -198,7 +198,7 @@ bool fillTransformProperty(const QList &keyFrames, QSvgAnimate bool fillOffsetDistanceProperty(const QList &keyFrames, QSvgAnimatedPropertyFloat *prop) { - for (CssKeyFrameValue keyFrame : keyFrames) { + for (const CssKeyFrameValue &keyFrame : keyFrames) { if (keyFrame.values.size() != 1) return false; @@ -318,7 +318,7 @@ void QSvgCssHandler::collectAnimations(const QCss::StyleSheet &sheet) }; QList animationRules = sheet.animationRules; - for (QCss::AnimationRule rule : animationRules) { + for (QCss::AnimationRule &rule : animationRules) { std::sort(rule.ruleSets.begin(), rule.ruleSets.end(), sortFunction); m_animations[rule.animName] = rule; } diff --git a/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcssproperties.cpp b/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcssproperties.cpp index b017a7d1f8..357acba8da 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcssproperties.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/css/qsvgcssproperties.cpp @@ -238,11 +238,11 @@ void QSvgCssProperties::shortHandtoLonghandForm(QStringView value) Name = 1 << 7 }; - QList animations = value.split(QLatin1Char(';'), Qt::SkipEmptyParts); - for (QStringView animation : animations) { + const QList animations = value.split(QLatin1Char(';'), Qt::SkipEmptyParts); + for (const QStringView &animation : animations) { uchar propertyFlag = 0; - QList animationProperties = animation.split(QLatin1Char(' '), Qt::SkipEmptyParts); - for (QStringView property : animationProperties) { + const QList animationProperties = animation.split(QLatin1Char(' '), Qt::SkipEmptyParts); + for (const QStringView &property : animationProperties) { if (!(propertyFlag & Property::Duration) && isTimeValue(property)) { m_durations.append(property); propertyFlag |= Property::Duration; diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgdocument.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgdocument.cpp index cf3a108254..cb4ccb4d62 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgdocument.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgdocument.cpp @@ -30,6 +30,7 @@ QSvgDocument::QSvgDocument(QtSvg::Options options, QtSvg::AnimatorType type) , m_fps(30) , m_options(options) { + m_states.trustedSource = m_options.testFlag(QtSvg::AssumeTrustedSource); bool animationEnabled = !m_options.testFlag(QtSvg::DisableAnimations); switch (type) { case QtSvg::AnimatorType::Automatic: @@ -195,7 +196,8 @@ std::unique_ptr QSvgDocument::load(const QString &fileName, QtSvg: QSvgHandler handler(&file, options, type); if (handler.ok()) { doc.reset(handler.document()); - doc->m_animator->setAnimationDuration(handler.animationDuration()); + if (doc->m_animator) + doc->m_animator->setAnimationDuration(handler.animationDuration()); } else { qCWarning(lcSvgHandler, "Cannot read file '%s', because: %s (line %d)", qPrintable(fileName), qPrintable(handler.errorString()), handler.lineNumber()); @@ -227,7 +229,8 @@ std::unique_ptr QSvgDocument::load(const QByteArray &contents, QtS if (handler.ok()) { doc.reset(handler.document()); - doc->m_animator->setAnimationDuration(handler.animationDuration()); + if (doc->m_animator) + doc->m_animator->setAnimationDuration(handler.animationDuration()); } else { delete handler.document(); } @@ -242,7 +245,8 @@ std::unique_ptr QSvgDocument::load(QXmlStreamReader *contents, QtS std::unique_ptr doc; if (handler.ok()) { doc.reset(handler.document()); - doc->m_animator->setAnimationDuration(handler.animationDuration()); + if (doc->m_animator) + doc->m_animator->setAnimationDuration(handler.animationDuration()); } else { delete handler.document(); } @@ -392,7 +396,8 @@ QSvgPaintStyleProperty *QSvgDocument::namedStyle(const QString &id) const void QSvgDocument::restartAnimation() { - m_animator->restartAnimation(); + if (m_animator) + m_animator->restartAnimation(); } bool QSvgDocument::animated() const @@ -524,6 +529,9 @@ int QSvgDocument::currentFrame() const void QSvgDocument::setCurrentFrame(int frame) { + if (!m_animator) + return; + const int totalFrames = m_fps * animationDuration() / 1000; if (totalFrames == 0) return; diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgfilter.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgfilter.cpp index 83d9e94581..f7ab757b08 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgfilter.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgfilter.cpp @@ -812,14 +812,14 @@ QImage QSvgFeBlend::apply(const QMap &sources, QPainter *p, b = (1 - alpha1) * blue2 + blue1; break; case Mode::Multiply: - r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2; - g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2; - b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2; + r = (1 - alpha1) * red2 + (1 - alpha2) * red1 + red1 * red2 / 255.; + g = (1 - alpha1) * green2 + (1 - alpha2) * green1 + green1 * green2 / 255.; + b = (1 - alpha1) * blue2 + (1 - alpha2) * blue1 + blue1 * blue2 / 255.; break; case Mode::Screen: - r = red2 + red1 - red1 * red2; - g = green2 + green1 - green1 * green2; - b = blue2 + blue1 - blue1 * blue2; + r = red2 + red1 - red1 * red2 / 255.; + g = green2 + green1 - green1 * green2 / 255.; + b = blue2 + blue1 - blue1 * blue2 / 255.; break; case Mode::Darken: r = qMin((1 - alpha1) * red2 + red1, (1 - alpha2) * red1 + red2); diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgfont_p.h b/local/recipes/qt/qtsvg/source/src/svg/qsvgfont_p.h index e1385e7cea..2a4d1b135d 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgfont_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgfont_p.h @@ -17,9 +17,11 @@ // We mean it. // +#include #include "qpainterpath.h" #include "qlist.h" #include "qstring.h" + #include "qsvgstyle_p.h" #include diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvggenerator.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvggenerator.cpp index 11f1a8c6f1..1cc7a10414 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvggenerator.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvggenerator.cpp @@ -65,7 +65,7 @@ public: attributes.document_title = QLatin1String("Qt SVG Document"); attributes.document_description = QLatin1String("Generated with Qt"); - attributes.font_family = QLatin1String("serif"); + attributes.font_families << QLatin1String("serif"); attributes.font_size = QLatin1String("10pt"); attributes.font_style = QLatin1String("normal"); attributes.font_weight = QLatin1String("normal"); @@ -78,7 +78,7 @@ public: QSize size; QRectF viewBox; QIODevice *outputDevice; - QTextStream *stream; + QTextStream *stream{nullptr}; int resolution; QString header; @@ -108,7 +108,7 @@ public: QString document_description; QString font_weight; QString font_size; - QString font_family; + QStringList font_families; QString font_style; QString stroke, strokeOpacity; QString dashPattern, dashOffset; @@ -537,10 +537,10 @@ public: d->attributes.font_size = QString::number(d->font.pixelSize()); d->attributes.font_weight = QString::number(d->font.weight()); - d->attributes.font_family = d->font.family(); + d->attributes.font_families = d->font.families(); d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal"); - *d->stream << "font-family=\"" << d->attributes.font_family << "\" " + *d->stream << "font-family=\"" << d->attributes.font_families.join(QStringLiteral(",")) << "\" " "font-size=\"" << d->attributes.font_size << "\" " "font-weight=\"" << d->attributes.font_weight << "\" " "font-style=\"" << d->attributes.font_style << "\" " @@ -1201,6 +1201,8 @@ void QSvgPaintEngine::drawPath(const QPainterPath &p) *d->stream << e.x << ',' << e.y; ++i; } + if (i == p.elementCount()) + --i; // don't overshoot, outer for-loop will increment again, see QTBUG-145372 break; default: break; diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics.cpp index 4d6c1058ee..a163fe8f79 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics.cpp @@ -68,7 +68,7 @@ void QSvgEllipse::drawCommand(QPainter *p, QSvgExtraStates &) p->drawEllipse(m_bounds); } -bool QSvgEllipse::separateFillStroke(const QSvgExtraStates &) const +bool QSvgEllipse::separateFillStroke(const QPainter *, const QSvgExtraStates &) const { return true; } @@ -120,7 +120,7 @@ QSvgPath::QSvgPath(QSvgNode *parent, const QPainterPath &qpath) void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states) { const qreal oldOpacity = p->opacity(); - const bool drawingInOnePass = !separateFillStroke(states); + const bool drawingInOnePass = !separateFillStroke(p, states); if (drawingInOnePass) p->setOpacity(oldOpacity * states.fillOpacity); m_path.setFillRule(states.fillRule); @@ -134,9 +134,10 @@ void QSvgPath::drawCommand(QPainter *p, QSvgExtraStates &states) p->setOpacity(oldOpacity); } -bool QSvgPath::separateFillStroke(const QSvgExtraStates &s) const +bool QSvgPath::separateFillStroke(const QPainter *p, const QSvgExtraStates &s) const { - return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity); + return !qFuzzyCompare(s.fillOpacity, s.strokeOpacity) + || qFuzzyIsNull(p->pen().widthF()); } QRectF QSvgPath::internalFastBounds(QPainter *p, QSvgExtraStates &) const @@ -213,7 +214,7 @@ void QSvgPolygon::drawCommand(QPainter *p, QSvgExtraStates &states) QSvgMarker::drawMarkersForNode(this, p, states); } -bool QSvgPolygon::separateFillStroke(const QSvgExtraStates &) const +bool QSvgPolygon::separateFillStroke(const QPainter *, const QSvgExtraStates &) const { return true; } @@ -237,7 +238,7 @@ void QSvgPolyline::drawCommand(QPainter *p, QSvgExtraStates &states) } } -bool QSvgPolyline::separateFillStroke(const QSvgExtraStates &) const +bool QSvgPolyline::separateFillStroke(const QPainter *, const QSvgExtraStates &) const { return true; } @@ -283,7 +284,7 @@ void QSvgRect::drawCommand(QPainter *p, QSvgExtraStates &) p->drawRect(m_rect); } -bool QSvgRect::separateFillStroke(const QSvgExtraStates &) const +bool QSvgRect::separateFillStroke(const QPainter *, const QSvgExtraStates &) const { return true; } @@ -376,7 +377,7 @@ bool QSvgText::shouldDrawNode(QPainter *p, QSvgExtraStates &) const return true; } -bool QSvgText::separateFillStroke(const QSvgExtraStates &) const +bool QSvgText::separateFillStroke(const QPainter *, const QSvgExtraStates &) const { return true; } diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics_p.h b/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics_p.h index 30cfd17a4e..f007380af5 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvggraphics_p.h @@ -44,7 +44,7 @@ class Q_SVG_EXPORT QSvgEllipse : public QSvgNode { public: QSvgEllipse(QSvgNode *parent, const QRectF &rect); - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void drawCommand(QPainter *p, QSvgExtraStates &states) override; Type type() const override; QRectF internalFastBounds(QPainter *p, QSvgExtraStates &states) const override; @@ -102,7 +102,7 @@ class Q_SVG_EXPORT QSvgPath : public QSvgNode { public: QSvgPath(QSvgNode *parent, const QPainterPath &qpath); - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void drawCommand(QPainter *p, QSvgExtraStates &states) override; Type type() const override; QRectF internalFastBounds(QPainter *p, QSvgExtraStates &states) const override; @@ -118,7 +118,7 @@ class Q_SVG_EXPORT QSvgPolygon : public QSvgNode { public: QSvgPolygon(QSvgNode *parent, const QPolygonF &poly); - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void drawCommand(QPainter *p, QSvgExtraStates &states) override; Type type() const override; QRectF internalFastBounds(QPainter *p, QSvgExtraStates &states) const override; @@ -135,7 +135,7 @@ class Q_SVG_EXPORT QSvgPolyline : public QSvgNode { public: QSvgPolyline(QSvgNode *parent, const QPolygonF &poly); - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void drawCommand(QPainter *p, QSvgExtraStates &states) override; Type type() const override; QRectF internalFastBounds(QPainter *p, QSvgExtraStates &states) const override; @@ -153,7 +153,7 @@ class Q_SVG_EXPORT QSvgRect : public QSvgNode public: QSvgRect(QSvgNode *paren, const QRectF &rect, qreal rx=0, qreal ry=0); Type type() const override; - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void drawCommand(QPainter *p, QSvgExtraStates &states) override; QRectF internalFastBounds(QPainter *p, QSvgExtraStates &states) const override; QRectF internalBounds(QPainter *p, QSvgExtraStates &states) const override; @@ -184,7 +184,7 @@ public: void drawCommand(QPainter *p, QSvgExtraStates &states) override; bool shouldDrawNode(QPainter *p, QSvgExtraStates &states) const override; Type type() const override; - bool separateFillStroke(const QSvgExtraStates &) const override; + bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const override; void addTspan(QSvgTspan *tspan) {m_tspans.append(tspan);} const QList tspans() const { return m_tspans; } diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvghandler.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvghandler.cpp index 44d02e78f1..a61f52d7b1 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvghandler.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvghandler.cpp @@ -27,6 +27,7 @@ #include "qdir.h" #include "qdebug.h" #include "qmath.h" +#include "qnumeric.h" #include #include "qtransform.h" #include "qvarlengtharray.h" @@ -34,6 +35,8 @@ #include "float.h" +#include + QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcSvgHandler, "qt.svg") @@ -332,60 +335,58 @@ void QSvgAttributes::setAttributes(const QXmlStreamAttributes &attributes, QSvgH } } -static QList parseNumbersList(const QChar *&str) +static QList parseNumbersList(QStringView *str) { QList points; if (!str) return points; points.reserve(32); - while (str->isSpace()) - ++str; - while (QSvgUtils::isDigit(str->unicode()) || - *str == QLatin1Char('-') || *str == QLatin1Char('+') || - *str == QLatin1Char('.')) { + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); + while (!str->isEmpty() + && (QSvgUtils::isDigit(str->first().unicode()) || str->startsWith(QLatin1Char('-')) + || str->startsWith(QLatin1Char('+')) || str->startsWith(QLatin1Char('.')))) { points.append(QSvgUtils::toDouble(str)); - while (str->isSpace()) - ++str; - if (*str == QLatin1Char(',')) - ++str; + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); + if (str->startsWith(QLatin1Char(','))) + str->slice(1); //eat the rest of space - while (str->isSpace()) - ++str; + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); } return points; } -static QList parsePercentageList(const QChar *&str) +static QList parsePercentageList(QStringView str) { QList points; - if (!str) - return points; - while (str->isSpace()) - ++str; - while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) || - *str == QLatin1Char('-') || *str == QLatin1Char('+') || - *str == QLatin1Char('.')) { + while (!str.isEmpty() && str.first().isSpace()) + str.slice(1); + while ((!str.isEmpty() && str.first() >= QLatin1Char('0') && str.first() <= QLatin1Char('9')) + || str.startsWith(QLatin1Char('-')) || str.startsWith(QLatin1Char('+')) + || str.startsWith(QLatin1Char('.'))) { - points.append(QSvgUtils::toDouble(str)); + points.append(QSvgUtils::toDouble(&str)); - while (str->isSpace()) - ++str; - if (*str == QLatin1Char('%')) - ++str; - while (str->isSpace()) - ++str; - if (*str == QLatin1Char(',')) - ++str; + while (!str.isEmpty() && str.first().isSpace()) + str.slice(1); + if (str.startsWith(QLatin1Char('%'))) + str.slice(1); + while (!str.isEmpty() && str.first().isSpace()) + str.slice(1); + if (str.startsWith(QLatin1Char(','))) + str.slice(1); //eat the rest of space - while (str->isSpace()) - ++str; + while (!str.isEmpty() && str.first().isSpace()) + str.slice(1); } return points; @@ -438,13 +439,12 @@ static bool resolveColor(QStringView colorStr, QColor &color, QSvgHandler *handl // starts with "rgb(", ends with ")" and consists of at least 7 characters "rgb(,,)" if (colorStrTr.size() >= 7 && colorStrTr.at(colorStrTr.size() - 1) == QLatin1Char(')') && colorStrTr.mid(0, 4) == QLatin1String("rgb(")) { - const QChar *s = colorStrTr.constData() + 4; - QList compo = parseNumbersList(s); + QStringView sv{ colorStrTr.sliced(4) }; + QList compo = parseNumbersList(&sv); //1 means that it failed after reaching non-parsable //character which is going to be "%" if (compo.size() == 1) { - s = colorStrTr.constData() + 4; - compo = parsePercentageList(s); + compo = parsePercentageList(colorStrTr.sliced(4)); for (int i = 0; i < compo.size(); ++i) compo[i] *= (qreal)2.55; } @@ -603,12 +603,10 @@ static QTransform parseTransformationMatrix(QStringView value) return QTransform(); QTransform matrix; - const QChar *str = value.constData(); - const QChar *end = str + value.size(); - while (str < end) { - if (str->isSpace() || *str == QLatin1Char(',')) { - ++str; + while (!value.isEmpty()) { + if (value.first().isSpace() || value.startsWith(QLatin1Char(','))) { + value.slice(1); continue; } enum State { @@ -620,49 +618,49 @@ static QTransform parseTransformationMatrix(QStringView value) SkewY }; State state = Matrix; - if (*str == QLatin1Char('m')) { //matrix + if (value.startsWith(QLatin1Char('m'))) { //matrix const char *ident = "atrix"; for (int i = 0; i < 5; ++i) - if (*(++str) != QLatin1Char(ident[i])) + if (!value.slice(1).startsWith(QLatin1Char(ident[i]))) goto error; - ++str; + value.slice(1); state = Matrix; - } else if (*str == QLatin1Char('t')) { //translate + } else if (value.startsWith(QLatin1Char('t'))) { //translate const char *ident = "ranslate"; for (int i = 0; i < 8; ++i) - if (*(++str) != QLatin1Char(ident[i])) + if (!value.slice(1).startsWith(QLatin1Char(ident[i]))) goto error; - ++str; + value.slice(1); state = Translate; - } else if (*str == QLatin1Char('r')) { //rotate + } else if (value.startsWith(QLatin1Char('r'))) { //rotate const char *ident = "otate"; for (int i = 0; i < 5; ++i) - if (*(++str) != QLatin1Char(ident[i])) + if (!value.slice(1).startsWith(QLatin1Char(ident[i]))) goto error; - ++str; + value.slice(1); state = Rotate; - } else if (*str == QLatin1Char('s')) { //scale, skewX, skewY - ++str; - if (*str == QLatin1Char('c')) { + } else if (value.startsWith(QLatin1Char('s'))) { //scale, skewX, skewY + value.slice(1); + if (value.startsWith(QLatin1Char('c'))) { const char *ident = "ale"; for (int i = 0; i < 3; ++i) - if (*(++str) != QLatin1Char(ident[i])) + if (!value.slice(1).startsWith(QLatin1Char(ident[i]))) goto error; - ++str; + value.slice(1); state = Scale; - } else if (*str == QLatin1Char('k')) { - if (*(++str) != QLatin1Char('e')) + } else if (value.startsWith(QLatin1Char('k'))) { + if (!value.slice(1).startsWith(QLatin1Char('e'))) goto error; - if (*(++str) != QLatin1Char('w')) + if (!value.slice(1).startsWith(QLatin1Char('w'))) goto error; - ++str; - if (*str == QLatin1Char('X')) + value.slice(1); + if (value.startsWith(QLatin1Char('X'))) state = SkewX; - else if (*str == QLatin1Char('Y')) + else if (value.startsWith(QLatin1Char('Y'))) state = SkewY; else goto error; - ++str; + value.slice(1); } else { goto error; } @@ -670,17 +668,16 @@ static QTransform parseTransformationMatrix(QStringView value) goto error; } - - while (str < end && str->isSpace()) - ++str; - if (*str != QLatin1Char('(')) + while (!value.isEmpty() && value.first().isSpace()) + value.slice(1); + if (!value.startsWith(QLatin1Char('('))) goto error; - ++str; + value.slice(1); QVarLengthArray points; - QSvgUtils::parseNumbersArray(str, points); - if (*str != QLatin1Char(')')) + QSvgUtils::parseNumbersArray(&value, points); + if (!value.startsWith(QLatin1Char(')'))) goto error; - ++str; + value.slice(1); if(state == Matrix) { if(points.size() != 6) @@ -771,26 +768,25 @@ static void parsePen(QSvgNode *node, if (attributes.strokeDashArray == QLatin1String("none")) { prop->setDashArrayNone(); } else { - QString dashArray = attributes.strokeDashArray.toString(); - const QChar *s = dashArray.constData(); - QList dashes = parseNumbersList(s); - bool allZeroes = true; - for (qreal dash : dashes) { - if (dash != 0.0) { - allZeroes = false; - break; - } - } + QStringView dashArray = attributes.strokeDashArray; + QList dashes = parseNumbersList(&dashArray); + const bool allZeroes = std::all_of(dashes.cbegin(), dashes.cend(), + [](qreal i) { return qFuzzyIsNull(i); }); + const bool hasNegative = !allZeroes && std::any_of(dashes.cbegin(), dashes.cend(), + [](qreal i) { return i < 0.; }); - // if the stroke dash array contains only zeros, + if (hasNegative) + qCWarning(lcSvgHandler) << "QSvgHandler: Stroke dash array " + "with a negative value is invalid"; + // if the stroke dash array contains only zeros or a negative value, // force drawing of solid line. - if (allZeroes == false) { + if (allZeroes || hasNegative) { + prop->setDashArrayNone(); + } else { // if the dash count is odd the dashes should be duplicated if ((dashes.size() & 1) != 0) dashes << QList(dashes); prop->setDashArray(dashes); - } else { - prop->setDashArrayNone(); } } } @@ -1504,7 +1500,7 @@ static QSvgNode *createAimateMotionNode(QSvgNode *parent, return nullptr; } -static void parseNumberTriplet(QList &values, const QChar *&s) +static void parseNumberTriplet(QList &values, QStringView *s) { QList list = parseNumbersList(s); values << list; @@ -1512,6 +1508,11 @@ static void parseNumberTriplet(QList &values, const QChar *&s) values.append(0.0); } +static void parseNumberTriplet(QList &values, QStringView s) +{ + parseNumberTriplet(values, &s); +} + static QSvgNode *createAnimateTransformNode(QSvgNode *parent, const QXmlStreamAttributes &attributes, QSvgHandler *handler) @@ -1524,13 +1525,12 @@ static QSvgNode *createAnimateTransformNode(QSvgNode *parent, QList vals; if (values.isEmpty()) { - const QChar *s; if (fromStr.isEmpty()) { if (!byStr.isEmpty()) { vals.append(0.0); vals.append(0.0); vals.append(0.0); - parseNumberTriplet(vals, s = byStr.constData()); + parseNumberTriplet(vals, byStr); } else { // To-animation not defined. return nullptr; @@ -1538,12 +1538,12 @@ static QSvgNode *createAnimateTransformNode(QSvgNode *parent, } else { if (!toStr.isEmpty()) { // From-to-animation. - parseNumberTriplet(vals, s = fromStr.constData()); - parseNumberTriplet(vals, s = toStr.constData()); + parseNumberTriplet(vals, fromStr); + parseNumberTriplet(vals, toStr); } else if (!byStr.isEmpty()) { // From-by-animation. - parseNumberTriplet(vals, s = fromStr.constData()); - parseNumberTriplet(vals, s = byStr.constData()); + parseNumberTriplet(vals, fromStr); + parseNumberTriplet(vals, byStr); for (int i = vals.size() - 3; i < vals.size(); ++i) vals[i] += vals[i - 3]; } else { @@ -1551,12 +1551,11 @@ static QSvgNode *createAnimateTransformNode(QSvgNode *parent, } } } else { - const QChar *s = values.constData(); - while (s && s != values.cend()) { - parseNumberTriplet(vals, s); - if (s == values.cend()) - break; - ++s; + QStringView s = values; + while (!s.isEmpty()) { + parseNumberTriplet(vals, &s); + if (!s.isEmpty()) + s.slice(1); } } if (vals.size() % 3 != 0) @@ -2744,9 +2743,8 @@ static QSvgNode *createPolyNode(QSvgNode *parent, const QXmlStreamAttributes &attributes, bool createLine) { - const QString pointsStr = attributes.value(QLatin1String("points")).toString(); - const QChar *s = pointsStr.constData(); - const QList points = parseNumbersList(s); + QStringView pointsStr = attributes.value(QLatin1String("points")); + const QList points = parseNumbersList(&pointsStr); if (points.size() < 4) return nullptr; QPolygonF poly(points.size()/2); diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgnode.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgnode.cpp index 0193f4142c..e073011f12 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgnode.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgnode.cpp @@ -44,6 +44,16 @@ void QSvgNode::draw(QPainter *p, QSvgExtraStates &states) #endif if (shouldDrawNode(p, states)) { + quint8 remainingDepth = states.trustedSource ? states.remainingNestedNodes + : states.remainingNestedNodes - 1; + QScopedValueRollback nestedNodesGuard(states.remainingNestedNodes, remainingDepth); + if (states.remainingNestedNodes == 0) { + qCWarning(lcSvgDraw) << "Too many nested nodes at" << qPrintable(typeName()) + << "exceeding max nested limit of" << QtSvg::renderingMaxNestedNodes << "." + << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check."; + return; + } + applyStyle(p, states); applyAnimatedStyle(p, states); QSvgNode *maskNode = this->hasMask() ? document()->namedNode(this->maskId()) : nullptr; @@ -85,7 +95,7 @@ void QSvgNode::draw(QPainter *p, QSvgExtraStates &states) QImage proxy = drawIntoBuffer(p, states, boundsRect.toAlignedRect()); applyBufferToCanvas(p, proxy); } else { - if (separateFillStroke(states)) + if (separateFillStroke(p, states)) fillThenStroke(p, states); else drawCommand(p, states); @@ -154,7 +164,7 @@ QImage QSvgNode::drawIntoBuffer(QPainter *p, QSvgExtraStates &states, const QRec proxyPainter.translate(-boundsRect.topLeft()); proxyPainter.setTransform(p->transform(), true); proxyPainter.setRenderHints(p->renderHints()); - if (separateFillStroke(states)) + if (separateFillStroke(p, states)) fillThenStroke(&proxyPainter, states); else drawCommand(&proxyPainter, states); @@ -677,7 +687,7 @@ bool QSvgNode::shouldDrawNode(QPainter *p, QSvgExtraStates &states) const if (m_displayMode == DisplayMode::NoneMode) return false; - if (document() && document()->options().testFlag(QtSvg::AssumeTrustedSource)) + if (document() && states.trustedSource) return true; QRectF brect = internalFastBounds(p, states); diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgnode_p.h b/local/recipes/qt/qtsvg/source/src/svg/qsvgnode_p.h index 72010639bf..ae651382a0 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgnode_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgnode_p.h @@ -89,7 +89,7 @@ public: QSvgNode(QSvgNode *parent=0); virtual ~QSvgNode(); void draw(QPainter *p, QSvgExtraStates &states); - virtual bool separateFillStroke(const QSvgExtraStates &) const {return false;} + virtual bool separateFillStroke(const QPainter *, const QSvgExtraStates &) const {return false;} virtual void drawCommand(QPainter *p, QSvgExtraStates &states) = 0; void fillThenStroke(QPainter *p, QSvgExtraStates &states); QImage drawIntoBuffer(QPainter *p, QSvgExtraStates &states, const QRect &boundsRect); diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgrenderer.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgrenderer.cpp index 2bf1bd38af..a15adf7e83 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgrenderer.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgrenderer.cpp @@ -469,7 +469,8 @@ void QSvgRenderer::render(QPainter *painter) { Q_D(QSvgRenderer); if (d->render) { - d->render->animator()->advanceAnimations(); + if (d->render->animator()) + d->render->animator()->advanceAnimations(); d->render->draw(painter); } } @@ -491,7 +492,8 @@ void QSvgRenderer::render(QPainter *painter, const QString &elementId, { Q_D(QSvgRenderer); if (d->render) { - d->render->animator()->advanceAnimations(); + if (d->render->animator()) + d->render->animator()->advanceAnimations(); d->render->draw(painter, elementId, bounds); } } @@ -506,7 +508,8 @@ void QSvgRenderer::render(QPainter *painter, const QRectF &bounds) { Q_D(QSvgRenderer); if (d->render) { - d->render->animator()->advanceAnimations(); + if (d->render->animator()) + d->render->animator()->advanceAnimations(); d->render->draw(painter, bounds); } } diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgstructure.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgstructure.cpp index 23606e65cf..5bf485e0df 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgstructure.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgstructure.cpp @@ -426,9 +426,10 @@ void QSvgMarker::drawHelper(const QSvgNode *node, QPainter *p, const bool isPainting = (boundingRect == nullptr); const auto markers = markersForNode(node); for (auto &i : markers) { - QSvgMarker *markNode = static_cast(node->document()->namedNode(i.markerId)); - if (!markNode) + QSvgNode *referencedNode = node->document()->namedNode(i.markerId); + if (!referencedNode || referencedNode->type() != QSvgNode::Marker) continue; + QSvgMarker *markNode = static_cast(referencedNode); p->save(); p->translate(i.x, i.y); @@ -729,8 +730,9 @@ QImage QSvgMask::createMask(QPainter *p, QSvgExtraStates &states, const QRectF & // Chrome seems to return the mask of the mask if a mask is set on the mask if (this->hasMask()) { - QSvgMask *maskNode = static_cast(document()->namedNode(this->maskId())); - if (maskNode) { + QSvgNode *referencedNode = document()->namedNode(this->maskId()); + if (referencedNode && referencedNode->type() == QSvgNode::Mask) { + QSvgMask *maskNode = static_cast(referencedNode); QRectF boundsRect; return maskNode->createMask(p, states, localRect, &boundsRect); } diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle.cpp index f9520c5a26..ef83d52295 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle.cpp @@ -379,7 +379,8 @@ QSvgSolidColorStyle::QSvgSolidColorStyle(const QColor &color) } QSvgGradientStyle::QSvgGradientStyle(QGradient *grad) - : m_gradient(grad), m_gradientStopsSet(false) + : m_gradient(grad) + , m_gradientStopsSet(false) { } @@ -706,7 +707,7 @@ void QSvgAnimatedStyle::apply(QPainter *p, const QSvgNode *node, QSvgExtraStates return; QSvgStyleState currentStyle = m_static; - for (auto anim : nodeAnims) { + for (const QSvgAbstractAnimation *anim : std::as_const(nodeAnims)) { if (!anim->isActive()) continue; diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle_p.h b/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle_p.h index b905b8ad81..078e0b7d4a 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgstyle_p.h @@ -118,6 +118,8 @@ struct Q_SVG_EXPORT QSvgExtraStates qreal strokeDashOffset; int nestedUseLevel = 0; int nestedUseCount = 0; + bool trustedSource = false; + quint8 remainingNestedNodes = QtSvg::renderingMaxNestedNodes; bool vectorEffect; // true if pen is cosmetic qint8 imageRendering; // QSvgQualityStyle::ImageRendering bool inUse = false; // true if currently in QSvgUseNode @@ -137,8 +139,6 @@ public: GRADIENT, PATTERN, TRANSFORM, - ANIMATE_TRANSFORM, - ANIMATE_COLOR, OPACITY, COMP_OP, OFFSET, @@ -274,7 +274,7 @@ private: // fill-opacity v v 'inherit' | QBrush m_fill; QBrush m_oldFill; - QSvgPaintStyleProperty *m_style; + QSvgRefCounter m_style; Qt::FillRule m_fillRule; Qt::FillRule m_oldFillRule; @@ -514,7 +514,7 @@ private: qreal m_strokeDashOffset; qreal m_oldStrokeDashOffset; - QSvgPaintStyleProperty *m_style; + QSvgRefCounter m_style; QString m_paintStyleId; uint m_paintStyleResolved : 1; uint m_vectorEffect : 1; @@ -594,7 +594,7 @@ private: QGradient *m_gradient; QTransform m_transform; - QSvgDocument *m_doc; + QSvgDocument *m_doc{nullptr}; QString m_link; bool m_gradientStopsSet; }; @@ -649,7 +649,7 @@ private: //comp-op attribute QPainter::CompositionMode m_mode; - QPainter::CompositionMode m_oldMode; + QPainter::CompositionMode m_oldMode{QPainter::CompositionMode_SourceOver}; }; class Q_SVG_EXPORT QSvgOffsetStyle : public QSvgStyleProperty diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgutils.cpp b/local/recipes/qt/qtsvg/source/src/svg/qsvgutils.cpp index d982a26daa..658a1fab23 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgutils.cpp +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgutils.cpp @@ -168,42 +168,43 @@ bool isDigit(ushort ch) return ((ch >> 4) == 3) && (magic >> (ch & 15)); } -qreal toDouble(const QChar *&str) +qreal toDouble(QStringView *str) { const int maxLen = 255;//technically doubles can go til 308+ but whatever char temp[maxLen+1]; int pos = 0; - if (*str == QLatin1Char('-')) { + if (str->startsWith(QLatin1Char('-'))) { temp[pos++] = '-'; - ++str; - } else if (*str == QLatin1Char('+')) { - ++str; + str->slice(1); + } else if (str->startsWith(QLatin1Char('+'))) { + str->slice(1); } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; + while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) { + temp[pos++] = str->first().toLatin1(); + str->slice(1); } - if (*str == QLatin1Char('.') && pos < maxLen) { + if (str->startsWith(QLatin1Char('.')) && pos < maxLen) { temp[pos++] = '.'; - ++str; + str->slice(1); } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; + while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) { + temp[pos++] = str->first().toLatin1(); + str->slice(1); } bool exponent = false; - if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { + if ((str->startsWith(QLatin1Char('e')) || str->startsWith(QLatin1Char('E'))) && pos < maxLen) { exponent = true; temp[pos++] = 'e'; - ++str; - if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; + str->slice(1); + if ((str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+'))) + && pos < maxLen) { + temp[pos++] = str->first().toLatin1(); + str->slice(1); } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; + while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) { + temp[pos++] = str->first().toLatin1(); + str->slice(1); } } @@ -250,10 +251,9 @@ qreal toDouble(const QChar *&str) qreal toDouble(QStringView str, bool *ok) { - const QChar *c = str.constData(); - qreal res = (c == nullptr ? qreal{} : toDouble(c)); + const qreal res{ toDouble(&str) }; if (ok) - *ok = (c == (str.constData() + str.size())); + *ok = str.isEmpty(); return res; } @@ -365,34 +365,33 @@ std::optional parseAngle(QStringView str) return angle * unitFactor; } -void parseNumbersArray(const QChar *&str, QVarLengthArray &points, - const char *pattern) +void parseNumbersArray(QStringView *str, QVarLengthArray &points, const char *pattern) { const size_t patternLen = qstrlen(pattern); - while (str->isSpace()) - ++str; - while (QSvgUtils::isDigit(str->unicode()) || - *str == QLatin1Char('-') || *str == QLatin1Char('+') || - *str == QLatin1Char('.')) { + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); + while ((!str->isEmpty() && QSvgUtils::isDigit(str->first().unicode())) + || str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+')) + || str->startsWith(QLatin1Char('.'))) { if (patternLen && pattern[points.size() % patternLen] == 'f') { // flag expected, may only be 0 or 1 - if (*str != QLatin1Char('0') && *str != QLatin1Char('1')) + if (!str->startsWith(QLatin1Char('0')) && !str->startsWith(QLatin1Char('1'))) return; - points.append(*str == QLatin1Char('0') ? 0.0 : 1.0); - ++str; + points.append(str->startsWith(QLatin1Char('0')) ? 0.0 : 1.0); + str->slice(1); } else { points.append(QSvgUtils::toDouble(str)); } - while (str->isSpace()) - ++str; - if (*str == QLatin1Char(',')) - ++str; + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); + if (str->startsWith(QLatin1Char(','))) + str->slice(1); //eat the rest of space - while (str->isSpace()) - ++str; + while (!str->isEmpty() && str->first().isSpace()) + str->slice(1); } } @@ -406,23 +405,18 @@ std::optional parsePathDataFast(QStringView dataStr, bool limitLen qreal x = 0, y = 0; // current point char lastMode = 0; QPointF ctrlPt; - const QChar *str = dataStr.constData(); - const QChar *end = str + dataStr.size(); QPainterPath path; - while (str != end) { - while (str->isSpace() && (str + 1) != end) - ++str; - QChar pathElem = *str; - ++str; - QChar endc = *end; - *const_cast(end) = u'\0'; // parseNumbersArray requires 0-termination that QStringView cannot guarantee + while (!dataStr.isEmpty()) { + while (dataStr.first().isSpace() && dataStr.length() > 1) + dataStr.slice(1); + QChar pathElem = dataStr.first(); + dataStr.slice(1); const char *pattern = nullptr; if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A')) pattern = "rrrffrr"; QVarLengthArray arg; - parseNumbersArray(str, arg, pattern); - *const_cast(end) = endc; + parseNumbersArray(&dataStr, arg, pattern); if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) arg.append(0);//dummy const qreal *num = arg.constData(); diff --git a/local/recipes/qt/qtsvg/source/src/svg/qsvgutils_p.h b/local/recipes/qt/qtsvg/source/src/svg/qsvgutils_p.h index ffdd1c248d..4cfa8929ec 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qsvgutils_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qsvgutils_p.h @@ -37,13 +37,13 @@ enum LengthType { }; bool isDigit(ushort ch); -qreal toDouble(const QChar *&str); +qreal toDouble(QStringView *str); qreal toDouble(QStringView str, bool *ok = NULL); qreal parseLength(QStringView str, LengthType *type, bool *ok = NULL); qreal convertToPixels(qreal len, bool , LengthType type); std::optional parseAngle(QStringView str); -void parseNumbersArray(const QChar *&str, QVarLengthArray &points, - const char *pattern = nullptr); +void parseNumbersArray(QStringView *str, QVarLengthArray &points, + const char *pattern = nullptr); std::optional parsePathDataFast(QStringView dataStr, bool limitLength = true); } diff --git a/local/recipes/qt/qtsvg/source/src/svg/qtsvgglobal_p.h b/local/recipes/qt/qtsvg/source/src/svg/qtsvgglobal_p.h index 77d2064c2f..4981e473ea 100644 --- a/local/recipes/qt/qtsvg/source/src/svg/qtsvgglobal_p.h +++ b/local/recipes/qt/qtsvg/source/src/svg/qtsvgglobal_p.h @@ -43,6 +43,7 @@ enum class OffsetRotateType : quint32 { ReverseAngle }; +constexpr quint8 renderingMaxNestedNodes = 32; } QT_END_NAMESPACE diff --git a/local/recipes/qt/qtsvg/source/tests/auto/CMakeLists.txt b/local/recipes/qt/qtsvg/source/tests/auto/CMakeLists.txt index 5ecebf7d90..7076ad1dd7 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/CMakeLists.txt +++ b/local/recipes/qt/qtsvg/source/tests/auto/CMakeLists.txt @@ -11,9 +11,6 @@ if(IOS AND QT_BUILD_MINIMAL_STATIC_TESTS) return() endif() -if(TARGET Qt::Widgets) - add_subdirectory(qsvgdevice) -endif() if(TARGET Qt::Xml) add_subdirectory(qsvggenerator) endif() diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qicon_svg/tst_qicon_svg.cpp b/local/recipes/qt/qtsvg/source/tests/auto/qicon_svg/tst_qicon_svg.cpp index 3c4b970797..90a7c6e8a2 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/qicon_svg/tst_qicon_svg.cpp +++ b/local/recipes/qt/qtsvg/source/tests/auto/qicon_svg/tst_qicon_svg.cpp @@ -2,9 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include class tst_QIcon_Svg : public QObject { @@ -127,9 +133,12 @@ void tst_QIcon_Svg::availableSizes() void tst_QIcon_Svg::isNull() { + const QString nonExistentFilePath{ prefix + "nonExistentFile.svg" }; + const QRegularExpression cannotOpenMsg{ "Cannot open file '" + nonExistentFilePath + "'" }; { //checks that an invalid file results in the icon being null - QIcon icon(prefix + "nonExistentFile.svg"); + QTest::ignoreMessage(QtWarningMsg, cannotOpenMsg); + QIcon icon(nonExistentFilePath); QVERIFY(icon.isNull()); } { @@ -149,7 +158,8 @@ void tst_QIcon_Svg::isNull() } { //invalid svg, but a pixmap added means we're not null - QIcon icon(prefix + "nonExistentFile.svg"); + QTest::ignoreMessage(QtWarningMsg, cannotOpenMsg); + QIcon icon(nonExistentFilePath); icon.addFile(prefix + "image.png", QSize(32,32)); QVERIFY(!icon.isNull()); } diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/CMakeLists.txt b/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/CMakeLists.txt deleted file mode 100644 index 61271894a1..0000000000 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -##################################################################### -## tst_qsvgdevice Test: -##################################################################### - -if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) - cmake_minimum_required(VERSION 3.16) - project(tst_qsvgdevice LANGUAGES CXX) - find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) -endif() - -qt_internal_add_test(tst_qsvgdevice - SOURCES - tst_qsvgdevice.cpp - LIBRARIES - Qt::Gui - Qt::GuiPrivate - Qt::Widgets -) diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/tst_qsvgdevice.cpp b/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/tst_qsvgdevice.cpp deleted file mode 100644 index e8f4103ece..0000000000 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvgdevice/tst_qsvgdevice.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - - -#include - -#if 0 -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -class tst_QSvgDevice : public QObject -{ - Q_OBJECT -public: - tst_QSvgDevice(); - -private slots: - void play_data(); - void play(); - void boundingRect(); - -private: - void playPaint( QPainter *p, const QString &type ); -}; - -tst_QSvgDevice::tst_QSvgDevice() -{ -} - -void tst_QSvgDevice::play_data() -{ - QTest::addColumn("tag name"); - // we only use the tag name - QTest::newRow( "lines" ); - QTest::newRow( "font" ); - QTest::newRow( "polyline" ); - QTest::newRow( "translate" ); - QTest::newRow( "scaleRect" ); - QTest::newRow( "ellipseOdd" ); - QTest::newRow( "ellipseEven" ); - QTest::newRow( "ellipseRandom" ); - QTest::newRow( "scaleText" ); - QTest::newRow( "scaleTextWithFont" ); - QTest::newRow( "scaleTextSaveRestore" ); - QTest::newRow( "scaleLineWithPen" ); - QTest::newRow( "task-17637" ); - QTest::newRow( "dashed-lines" ); - QTest::newRow( "dot-lines" ); - QTest::newRow( "dashed-dot-lines" ); - QTest::newRow( "dashed-dot-dot-lines" ); - QTest::newRow( "scaleDashed-lines" ); - QTest::newRow( "thick-dashed-lines" ); - QTest::newRow( "negative-rect" ); - QTest::newRow( "lightText" ); - QTest::newRow( "boldText" ); - QTest::newRow( "demiBoldText" ); - QTest::newRow( "blackText" ); - QTest::newRow( "task-20239" ); - QTest::newRow( "clipRect" ); - QTest::newRow( "multipleClipRects" ); - QTest::newRow("qsimplerichtext"); -} - -#if 0 - -class SVGDummyDevice : public QPaintDevice -{ -public: - SVGDummyDevice() - : QPaintDevice(QInternal::ExternalDevice), eng(0) { } - ~SVGDummyDevice() { - delete eng; - } - QPaintEngine *engine() const { - if (!eng) - const_cast(this)->eng = new QSVGPaintEngine; - return eng; - } - -private: - QPaintEngine *eng; -}; - -void tst_QSvgDevice::play() -{ - // current tag name - QString type = data()->dataTag(); - - // reference pixmap - QPixmap ref( 100, 100 ); - ref.fill( Qt::white ); - QPainter pref( &ref ); - playPaint( &pref, type ); - pref.end(); - - // draw the same into the SVG device - SVGDummyDevice dev; - QPainter pdev( &dev ); - playPaint( &pdev, type ); - pdev.end(); - - - //dev.setBoundingRect( QRect( 0, 0, 100, 100 ) ); - //dev.save( type + "-res.svg" ); // ### sets bounding rect to 0 ! - - // replay on a result pixmap and compare - QPixmap res( 100, 100 ); - res.fill( Qt::white ); - QPainter pres( &res ); - static_cast(dev.engine())->play( &pres ); - -#if 0 - // for visual inspection - ref.save( type + "-ref.xpm", "XPM" ); - res.save( type + "-res.xpm", "XPM" ); -#endif - - QVERIFY( res.convertToImage() == ref.convertToImage() ); -} - -// helper function for play() -void tst_QSvgDevice::playPaint( QPainter *p, const QString &type ) -{ - if ( type == "lines" ) { - // line with pen width 0 - p->setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); - p->drawLine( 10, 0, 20, 3 ); - - // line with pen width 1 - p->setPen( QPen( Qt::black, 1, Qt::SolidLine ) ); - p->drawLine( 2, 0, 10, 3 ); - - // rect without outline (qt-bugs/arc-17/35556) - p->setPen( Qt::NoPen ); - p->setBrush( Qt::red ); - p->drawRect( 5, 10, 20, 30 ); - } else if ( type == "text" ) { - QFont f = p->font(); - f.setItalic( TRUE ); - f.setBold( TRUE ); - p->setFont( f ); - p->drawText( 5, 55, "Text" ); - } else if ( type == "polyline" ) { - // we'll draw 4 triangular polylines. Only two will show up - // as the QPainter::drawPolyline() doesn't respect the brush - // just the pen setting - QPolygon pa( 3 ); - pa.setPoint( 0, 0, 0 ); - pa.setPoint( 1, 10, 0 ); - pa.setPoint( 2, 0, 10 ); - - // frame around the following 4 polylines - p->setBrush( Qt::NoBrush ); - p->setPen( Qt::SolidLine ); - p->drawRect( 46, 3, 19, 60 ); - - // polyline with blue brush, no pen - p->setPen( Qt::NoPen ); - p->setBrush( Qt::blue ); - p->translate( 50, 5 ); - p->drawPolyline( pa ); - - // polyline without brush, no pen - p->setBrush( Qt::NoBrush ); - p->translate( 0, 15 ); - p->drawPolyline( pa ); - - // polyline with green brush, solid pen - p->setBrush( Qt::green ); - p->setPen( Qt::SolidLine ); - p->translate( 0, 15 ); - p->drawPolyline( pa ); - - // polyline without brush, solid pen - p->setBrush( Qt::NoBrush ); - p->setPen( Qt::SolidLine ); - p->translate( 0, 15 ); - p->drawPolyline( pa ); - } else if ( type == "translate" ) { - p->translate(-10,-10); - p->save(); - p->setBrush( Qt::blue ); - p->drawRect( 20, 30, 50, 20 ); - p->restore(); - p->setBrush( Qt::green ); - p->drawRect( 70, 50, 10, 10 ); - } else if ( type == "scaleRect" ) { - p->scale( 1, 2 ); - p->setBrush( Qt::blue ); - p->drawRect( 20, 20, 60, 60 ); - } else if ( type == "ellipseEven" ) { - p->setBrush( Qt::blue ); - p->drawEllipse( 20, 20, 60, 60 ); - } else if ( type == "ellipseOdd" ) { - p->setBrush( Qt::blue ); - p->drawEllipse( 20, 20, 59, 59 ); - } else if ( type == "ellipseRandom" ) { - p->setBrush( Qt::blue ); - p->drawEllipse( 20, 34, 89, 123 ); - } else if ( type == "scaleText" ) { - p->scale(0.25,0.25); - p->drawText(200,200,"Hello!"); - } else if ( type == "scaleTextWithFont" ) { - p->setFont(QFont("Helvetica",12)); - p->scale(0.25,0.25); - p->drawText(200,200,"Hello!"); -#ifdef Q_WS_WIN - // Only test on Windows for now, visually it looks fine, but - // it's failing for some reason on Linux - } else if ( type == "scaleTextSaveRestore" ) { - p->scale(1,-1); - p->save(); - p->setFont(QFont("Helvetica",12)); - p->scale(0.5,0.5); - p->drawText(0,0,"Hello!"); - p->restore(); -#endif - } else if ( type == "scaleLineWithPen" ) { - p->scale(0.1,0.1); - p->setPen(QPen(Qt::red,100)); - p->drawLine(3,3,500,500); - } else if ( type == "task-17637" ) { - p->translate(0,200); - p->scale(0.01,-0.01); - p->setBrush(Qt::blue); - p->drawEllipse(2000,2000,6000,6000); - p->setPen(QPen(Qt::red,100)); - p->setFont(QFont("Helvetica",12)); - p->save(); - p->scale(1,-1); - p->drawText(4000,4000,"Hello!"); - p->restore(); - p->drawLine(3000,3000,5000,5000); - } else if ( type == "dashed-lines" ) { - p->setPen(QPen(Qt::red, 1, Qt::DashLine)); - p->drawLine(10,10,50,50); - } else if ( type == "dot-lines" ) { - p->setPen(QPen(Qt::red, 1, Qt::DotLine)); - p->drawLine(10,10,50,50); - } else if ( type == "dashed-dot-lines" ) { - p->setPen(QPen(Qt::red, 1, Qt::DashDotLine)); - p->drawLine(10,10,50,50); - } else if ( type == "dashed-dot-dot-lines" ) { - p->setPen(QPen(Qt::red, 1, Qt::DashDotDotLine)); - p->drawLine(10,10,50,50); - } else if ( type == "scaleDashed-lines" ) { - p->scale(0.1,0.1); - p->setPen(QPen(Qt::red, 10, Qt::DashLine)); - p->drawLine(10,10,500,500); - } else if ( type == "thick-dashed-lines" ) { - p->setPen(QPen(Qt::red, 10, Qt::DashLine)); - p->drawLine(10,10,50,50); - } else if ( type == "negative-rect" ) { - p->drawRect(70, 0,-30,30); - } else if ( type == "lightText" ) { - QFont f("Helvetica",12); - f.setWeight( QFont::Light ); - p->setFont(f); - p->drawText(0,0,"Hello!"); - } else if ( type == "boldText" ) { - QFont f("Helvetica",12); - f.setWeight( QFont::Bold ); - p->setFont(f); - p->drawText(0,0,"Hello!"); - } else if ( type == "demiBoldText" ) { - QFont f("Helvetica",12); - f.setWeight( QFont::DemiBold ); - p->setFont(f); - p->drawText(0,0,"Hello!"); - } else if ( type == "blackText" ) { - QFont f("Helvetica",12); - f.setWeight( QFont::Black ); - p->setFont(f); - p->drawText(0,0,"Hello!"); - } else if ( type == "task-20239" ) { - p->translate(0,200); - p->scale(0.02,-0.02); - p->scale(1,-1); - for(int y = 1000; y <=10000; y += 2000) { - QBrush br(Qt::green); - QPalette palette(Qt::green,Qt::blue); - qDrawShadePanel(p,4000,5000,4000,2000,palette.active(),false,200,&br); - qDrawShadeRect(p,2000,2000,4000,2000,palette.active(),true,100,0,&br); - } - } else if (type=="clipRect") { - p->setClipRect(15,25,10,10); - p->drawEllipse(10,20,50,70); - } else if (type=="multipleClipRects") { - p->setClipRect(15,25,10,10); - p->drawEllipse(10,20,50,70); - p->setClipRect(100,200,20,20); - p->drawEllipse(110,220,50,70); - } else if (type == "qsimplerichtext") { - p->setPen(QColor(0,0,0)); - p->setBrush(Qt::NoBrush); - QRect rect1(10, 10, 100, 50); - QRect rect2(200, 20, 80, 50); - p->drawRect(10, 10, 100, 50); - p->drawRect(200, 20, 80, 50); - - QSimpleRichText text1("Text 1", QApplication::font()); - QSimpleRichText text2("Text 2", QApplication::font()); - - QColorGroup cg(Qt::black, Qt::red, Qt::gray, Qt::gray, Qt::gray, Qt::black, Qt::blue, Qt::black, Qt::white); - text1.draw(p, rect1.x(), rect1.y(), rect1, cg); - text2.draw(p, rect2.x(), rect2.y(), rect2, cg); - } -} - -void tst_QSvgDevice::boundingRect() -{ - // ### the bounding rect is only calculated/parsed - // ### when replaying. Therefore we go through QPicture. - QRect r( 10, 20, 30, 40 ); - // create document - QPicture pic; - QPainter p( &pic ); - p.drawRect( r ); - p.end(); - pic.save( "tmp.svg", "svg" ); - // load it - QPicture pic2; - pic2.load( "tmp.svg", "svg" ); - QCOMPARE( pic2.boundingRect(), r ); -} - -#else - -void tst_QSvgDevice::play() -{ - QSKIP("This test needs some redoing, this is just a temp measure until I work it out"); -} - -void tst_QSvgDevice::boundingRect() -{ - QSKIP("This test needs some redoing, this is just a temp measure until I work it out"); -} - -#endif - -QTEST_MAIN(tst_QSvgDevice) -#include "tst_qsvgdevice.moc" diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvggenerator/tst_qsvggenerator.cpp b/local/recipes/qt/qtsvg/source/tests/auto/qsvggenerator/tst_qsvggenerator.cpp index 0193587775..3568c27e30 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvggenerator/tst_qsvggenerator.cpp +++ b/local/recipes/qt/qtsvg/source/tests/auto/qsvggenerator/tst_qsvggenerator.cpp @@ -2,11 +2,18 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include #include #include @@ -36,6 +43,7 @@ private slots: void titleAndDescription(); void gradientInterpolation(); void patternBrush(); + void assertOnCurveToElement(); }; tst_QSvgGenerator::tst_QSvgGenerator() @@ -183,7 +191,9 @@ void tst_QSvgGenerator::escapesDescription() void tst_QSvgGenerator::outputDevice() { - QString fileName = "outputDevice_output.svg"; + const QString fileName = "outputDevice_output.svg"; + const QByteArray notOpenWarning = + QString("QIODevice::write (QFile, \"%1\"): device not open").arg(fileName).toLatin1(); QFile::remove(fileName); QFile file(fileName); @@ -195,6 +205,7 @@ void tst_QSvgGenerator::outputDevice() QCOMPARE(generator.outputDevice(), (QIODevice *)&file); QPainter painter; + QTest::ignoreMessage(QtWarningMsg, notOpenWarning.constData()); QVERIFY(painter.begin(&generator)); QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::Text | QIODevice::WriteOnly)); file.close(); @@ -208,6 +219,7 @@ void tst_QSvgGenerator::outputDevice() QCOMPARE(generator.outputDevice(), (QIODevice *)&file); QPainter painter; + QTest::ignoreMessage(QtWarningMsg, notOpenWarning.constData()); QVERIFY(painter.begin(&generator)); QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::WriteOnly)); file.close(); @@ -222,6 +234,7 @@ void tst_QSvgGenerator::outputDevice() QPainter painter; QTest::ignoreMessage(QtWarningMsg, "QSvgPaintEngine::begin(), could not write to read-only output device: 'Unknown error'"); + QTest::ignoreMessage(QtWarningMsg, "QPainter::begin(): Returned false"); QVERIFY(!painter.begin(&generator)); QCOMPARE(file.openMode(), QIODevice::OpenMode(QIODevice::ReadOnly)); file.close(); @@ -500,5 +513,24 @@ void tst_QSvgGenerator::patternBrush() } +void tst_QSvgGenerator::assertOnCurveToElement() +{ + // QTBUG-145372 + // Tried to access QPainterPath beyond its size when closing path, needs enabled asserts + QByteArray byteArray; + QBuffer buffer(&byteArray); + QSvgGenerator svg; + svg.setOutputDevice(&buffer); + QPainter painter(&svg); + QPainterPath path; + path.moveTo(100, 0); + path.cubicTo(100, 55, 55, 100, 0, 100); + path.cubicTo(55, -100, 100, -55, 100, 0); + painter.drawPath(path); + painter.end(); + // Validate that path is still closed correctly + QVERIFY(byteArray.contains("M100,0 C100,55 55,100 0,100 C55,-100 100,-55 100,0 Z")); +} + QTEST_MAIN(tst_QSvgGenerator) #include "tst_qsvggenerator.moc" diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvgplugin/tst_qsvgplugin.cpp b/local/recipes/qt/qtsvg/source/tests/auto/qsvgplugin/tst_qsvgplugin.cpp index a9f7c18b81..1bdca25f95 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvgplugin/tst_qsvgplugin.cpp +++ b/local/recipes/qt/qtsvg/source/tests/auto/qsvgplugin/tst_qsvgplugin.cpp @@ -2,16 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include +#include #include "../../../src/plugins/imageformats/svg/qsvgiohandler.cpp" +#include #include +#include #include - -#ifndef SRCDIR -#define SRCDIR -#endif - +#include +#include QStringList logMessages; diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/CMakeLists.txt b/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/CMakeLists.txt index 4103fd96b2..31811c393e 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/CMakeLists.txt +++ b/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/CMakeLists.txt @@ -14,8 +14,6 @@ endif() qt_internal_add_test(tst_qsvgrenderer SOURCES tst_qsvgrenderer.cpp - DEFINES - SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" LIBRARIES Qt::Gui Qt::GuiPrivate diff --git a/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp b/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp index 4d19b6e176..329767b1e2 100644 --- a/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp +++ b/local/recipes/qt/qtsvg/source/tests/auto/qsvgrenderer/tst_qsvgrenderer.cpp @@ -2,21 +2,24 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include +#include #include #include #include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include -#ifndef SRCDIR -#define SRCDIR -#endif - using namespace Qt::Literals::StringLiterals; class tst_QSvgRenderer : public QObject @@ -81,6 +84,7 @@ private slots: void tSpanLineBreak(); void animated(); void notAnimated(); + void notAnimatedOption(); void testMaskElement(); void testSymbol(); void testMarker(); @@ -101,8 +105,10 @@ private slots: #ifndef QT_NO_COMPRESS void testGzLoading(); +# ifdef QT_BUILD_INTERNAL void testGzHelper_data(); void testGzHelper(); +# endif #endif private: @@ -923,11 +929,10 @@ void tst_QSvgRenderer::testGzLoading() QVERIFY(autoDetectGzData.isValid()); } -#ifdef QT_BUILD_INTERNAL +# ifdef QT_BUILD_INTERNAL QT_BEGIN_NAMESPACE QByteArray qt_inflateGZipDataFrom(QIODevice *device); QT_END_NAMESPACE -#endif void tst_QSvgRenderer::testGzHelper_data() { @@ -959,7 +964,6 @@ void tst_QSvgRenderer::testGzHelper_data() void tst_QSvgRenderer::testGzHelper() { -#ifdef QT_BUILD_INTERNAL QFETCH(QByteArray, in); QFETCH(QByteArray, out); @@ -968,9 +972,9 @@ void tst_QSvgRenderer::testGzHelper() QVERIFY(buffer.isReadable()); QByteArray result = qt_inflateGZipDataFrom(&buffer); QCOMPARE(result, out); -#endif } -#endif +# endif // #ifdef QT_BUILD_INTERNAL +#endif // #ifndef QT_NO_COMPRESS void tst_QSvgRenderer::fillRule() { @@ -1808,10 +1812,10 @@ void tst_QSvgRenderer::glyphWarnings() QFETCH(QString, missingGlyphAttributes); QFETCH(QString, secondElement); - QFETCH(QByteArrayList, warnings); + QFETCH(const QByteArrayList, warnings); for (const auto &w: warnings) - QTest::ignoreMessage(QtWarningMsg, w); + QTest::ignoreMessage(QtWarningMsg, w.constData()); QSvgRenderer renderer(svgTemplate.arg(missingGlyphAttributes).arg(secondElement).toUtf8()); QVERIFY(renderer.isValid()); } @@ -1867,6 +1871,17 @@ void tst_QSvgRenderer::ossFuzzRender_data() // runtime error: signed integer overflow: -2147483648 + -1 cannot be represented in type 'int' QTest::newRow("excessive moveto in path") // id=406541912 << R"()"_ba; + // Bad-cast to QSvgMarker from QSvgLine -> Heap-buffer-overflow + QTest::newRow("line-as-marker") // id=496327371 + << R"-()-"_ba; + QTest::newRow("line-as-mask") // modeled after 496327371 to test similar problem, needs UBSAN + << R"-( + + + + + + )-"_ba; } void tst_QSvgRenderer::ossFuzzRender() @@ -2177,6 +2192,14 @@ void tst_QSvgRenderer::notAnimated() QVERIFY(!renderer.isAnimationEnabled()); } +void tst_QSvgRenderer::notAnimatedOption() +{ + QSvgRenderer renderer; + renderer.setOptions(QtSvg::Option::DisableAnimations); + // Shouldn't crash accessing the animator when it's disabled. + QVERIFY(renderer.load(QByteArray(animatedSvgContents))); +} + void tst_QSvgRenderer::testPatternElement() { QByteArray svgDoc(R"( diff --git a/local/recipes/qt/qtwayland/source/.cmake.conf b/local/recipes/qt/qtwayland/source/.cmake.conf index a719905fe9..b49aaad8cf 100644 --- a/local/recipes/qt/qtwayland/source/.cmake.conf +++ b/local/recipes/qt/qtwayland/source/.cmake.conf @@ -1,8 +1,9 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.11.1") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_EXTRA_INTERNAL_TARGET_DEFINES - "QT_NO_CONTEXTLESS_CONNECT=1" - "QT_NO_FOREACH=1" - "QT_NO_QASCONST=1" - "QT_NO_URL_CAST_FROM_STRING=1" + "QT_ENABLE_STRICT_MODE_UP_TO=QT_VERSION_CHECK(6,11,0)" + # not yet in strict mode: + "QT_LEAN_HEADERS=1" + "QT_NO_QPAIR=1" ) + diff --git a/local/recipes/qt/qtwayland/source/.tag b/local/recipes/qt/qtwayland/source/.tag index b712dcfd7f..4654c3b93c 100644 --- a/local/recipes/qt/qtwayland/source/.tag +++ b/local/recipes/qt/qtwayland/source/.tag @@ -1 +1 @@ -fc88dcfed73c0578d3bb562d9ff65c3aeff54c40 +23b98e2c05f411365860c3607d611d1dd83bff47 diff --git a/local/recipes/qt/qtwayland/source/coin/axivion/start_analysis.sh b/local/recipes/qt/qtwayland/source/coin/axivion/start_analysis.sh index d49746e12a..2c45c97df2 100755 --- a/local/recipes/qt/qtwayland/source/coin/axivion/start_analysis.sh +++ b/local/recipes/qt/qtwayland/source/coin/axivion/start_analysis.sh @@ -12,6 +12,24 @@ gccsetup --cc gcc --cxx g++ --config "$BAUHAUS_CONFIG" cd "$CAFECC_BASEPATH" BAUHAUS_IR_COMPRESSION=none COMPILE_ONLY=1 cmake -G Ninja -DAXIVION_ANALYSIS_TOOLCHAIN_FILE=/home/qt/bauhaus-suite/profiles/cmake/axivion-launcher-toolchain.cmake -DCMAKE_PREFIX_PATH=/home/qt/work/qt/qtwayland/build -DCMAKE_PROJECT_INCLUDE_BEFORE=/home/qt/bauhaus-suite/profiles/cmake/axivion-before-project-hook.cmake -B build -S . --fresh cmake --build build -j4 + +ROOT_DIR=src +MAGIC="Qt-Security score:critical" + +INCLUDE_FILES="" + +while IFS= read -r file; do + if [ -z "$INCLUDE_FILES" ]; then + INCLUDE_FILES="$file" + else + INCLUDE_FILES="$INCLUDE_FILES:$file" + fi +done <view->surface(); - QWaylandCompositor *oldComp = d->view->surface() ? d->view->surface()->compositor() : nullptr; - d->view->setSurface(surface); - QWaylandCompositor *newComp = d->view->surface() ? d->view->surface()->compositor() : nullptr; + if (oldSurf == surface) + return; + + QWaylandCompositor *oldComp = oldSurf ? oldSurf->compositor() : nullptr; + QWaylandCompositor *newComp = surface ? surface->compositor() : nullptr; if (oldComp != newComp) emit compositorChanged(); - if (oldSurf != surface) - emit surfaceChanged(); + + d->view->setSurface(surface); updateFocus(); update(); @@ -1533,20 +1535,15 @@ QSGNode *QWaylandQuickItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat if (!d->provider) { d->provider = new QWaylandSurfaceTextureProvider(); - if (compositor()) { + if (auto *c = compositor()) { d->texProviderConnection = - QObject::connect( - compositor(), - &QObject::destroyed, - this, - [this](QObject*) { - auto *itemPriv = QWaylandQuickItemPrivate::get(this); - if (itemPriv->provider) { - itemPriv->provider->deleteLater(); - itemPriv->provider = nullptr; - } - disconnect(itemPriv->texProviderConnection); } - ); + QObject::connect(c, &QObject::destroyed, this, [d = d] { + if (d->provider) { + d->provider->deleteLater(); + d->provider = nullptr; + } + QObject::disconnect(d->texProviderConnection); + }); } } diff --git a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp index 8addb8676e..482489ba48 100644 --- a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp +++ b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem.cpp @@ -18,14 +18,14 @@ void QWaylandQuickShellSurfaceItemPrivate::setShellSurface(QWaylandShellSurface QObject::disconnect(m_shellSurface, &QWaylandShellSurface::modalChanged, q, nullptr); } + m_shellSurface = shellSurface; + if (m_shellIntegration) { q->removeEventFilter(m_shellIntegration); delete m_shellIntegration; m_shellIntegration = nullptr; } - m_shellSurface = shellSurface; - if (m_shellSurface) { m_shellIntegration = m_shellSurface->createIntegration(q); q->installEventFilter(m_shellIntegration); diff --git a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h index eaad8f0888..d80e071751 100644 --- a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h +++ b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandquickshellsurfaceitem_p.h @@ -43,7 +43,7 @@ public: void lower() override; QWaylandQuickShellIntegration *m_shellIntegration = nullptr; - QWaylandShellSurface *m_shellSurface = nullptr; + QPointer m_shellSurface; QQuickItem *m_moveItem = nullptr; bool m_autoCreatePopupItems = true; bool staysOnTop = false; diff --git a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandtextinputv3_p.h b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandtextinputv3_p.h index 267d141254..1d0126509f 100644 --- a/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandtextinputv3_p.h +++ b/local/recipes/qt/qtwayland/source/src/compositor/extensions/qwaylandtextinputv3_p.h @@ -13,6 +13,8 @@ #include #include +#include + // // W A R N I N G // ------------- diff --git a/local/recipes/qt/qtwayland/source/src/compositor/hardware_integration/qwltextureorphanage_p.h b/local/recipes/qt/qtwayland/source/src/compositor/hardware_integration/qwltextureorphanage_p.h index f040ec750c..943c3081b6 100644 --- a/local/recipes/qt/qtwayland/source/src/compositor/hardware_integration/qwltextureorphanage_p.h +++ b/local/recipes/qt/qtwayland/source/src/compositor/hardware_integration/qwltextureorphanage_p.h @@ -20,6 +20,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QOpenGLContext; diff --git a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.cpp b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.cpp index 88365e61b5..a57ca424ff 100644 --- a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.cpp +++ b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.cpp @@ -413,7 +413,7 @@ void QWaylandQtShellSurfacePrivate::configure(uint windowState, const QRect &new } uint32_t serial = compositor->nextSerial(); - m_pendingConfigures[serial] = qMakePair(windowState, newGeometry); + m_pendingConfigures[serial] = {windowState, newGeometry}; send_set_position(serial, newGeometry.x(), newGeometry.y()); send_resize(serial, newGeometry.width(), newGeometry.height()); diff --git a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.qdoc b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.qdoc index 7b2a8665fb..df5ea51e6b 100644 --- a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.qdoc +++ b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell.qdoc @@ -17,6 +17,6 @@ \section2 Usage To use this module, import it like this: \code - import QtWayland.Compositor.IviApplication + import QtWayland.Compositor.QtShell \endcode */ diff --git a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell_p.h b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell_p.h index ad93dee1a4..29e5148e30 100644 --- a/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell_p.h +++ b/local/recipes/qt/qtwayland/source/src/imports/compositor-extensions/qtshell/qwaylandqtshell_p.h @@ -96,7 +96,7 @@ private: QSize m_pendingSize; uint32_t m_lastAckedConfigure = UINT32_MAX; - QMap > m_pendingConfigures; + QMap > m_pendingConfigures; QWaylandQtShellSurface::CapabilityFlags m_capabilities; diff --git a/local/recipes/qt/qtwayland/source/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h b/local/recipes/qt/qtwayland/source/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h index 34874e0885..99ec874673 100644 --- a/local/recipes/qt/qtwayland/source/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h +++ b/local/recipes/qt/qtwayland/source/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h @@ -7,7 +7,17 @@ #include +#include +#include +#include + #include +#include +#include +#include +#include + +#include QT_BEGIN_NAMESPACE diff --git a/local/recipes/qt/qtwayland/source/src/plugins/hardwareintegration/compositor/CMakeLists.txt b/local/recipes/qt/qtwayland/source/src/plugins/hardwareintegration/compositor/CMakeLists.txt index 894a70d359..8b281f8815 100644 --- a/local/recipes/qt/qtwayland/source/src/plugins/hardwareintegration/compositor/CMakeLists.txt +++ b/local/recipes/qt/qtwayland/source/src/plugins/hardwareintegration/compositor/CMakeLists.txt @@ -20,7 +20,7 @@ endif() if(QT_FEATURE_wayland_libhybris_egl_server_buffer) add_subdirectory(libhybris-egl-server) endif() -if(FALSE AND QT_FEATURE_wayland_shm_emulation_server_buffer) # disabled for Redox (no systemsemaphore-backed QSharedMemory locking) +if(QT_FEATURE_wayland_shm_emulation_server_buffer) add_subdirectory(shm-emulation-server) endif() if(QT_FEATURE_wayland_dmabuf_server_buffer) diff --git a/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder.cpp b/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder.cpp index fc422ef04a..137380bfe8 100644 --- a/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder.cpp +++ b/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder.cpp @@ -96,7 +96,7 @@ QInputMethodEvent *QWaylandInputMethodEventBuilder::buildCommit(const QString &t { QList attributes; - const QPair replacement = replacementForDeleteSurrounding(); + const std::pair replacement = replacementForDeleteSurrounding(); if (m_cursor != 0 || m_anchor != 0) { QString surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); @@ -140,16 +140,16 @@ QInputMethodEvent *QWaylandInputMethodEventBuilder::buildPreedit(const QString & QInputMethodEvent *event = new QInputMethodEvent(text, attributes); - const QPair replacement = replacementForDeleteSurrounding(); + const std::pair replacement = replacementForDeleteSurrounding(); event->setCommitString(QString(), replacement.first, replacement.second); return event; } -QPair QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding() +std::pair QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding() { if (m_deleteBefore == 0 && m_deleteAfter == 0) - return QPair(0, 0); + return {0, 0}; const QString &surrounding = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant()).toString(); const int cursor = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant()).toInt(); @@ -161,7 +161,7 @@ QPair QWaylandInputMethodEventBuilder::replacementForDeleteSurrounding const int deleteBefore = selectionStart - indexFromWayland(surrounding, -m_deleteBefore, selectionStart); const int deleteAfter = indexFromWayland(surrounding, m_deleteAfter, selectionEnd) - selectionEnd; - return QPair(-deleteBefore, deleteBefore + deleteAfter); + return {-deleteBefore, deleteBefore + deleteAfter}; } QWaylandInputMethodContentType QWaylandInputMethodContentType::convert(Qt::InputMethodHints hints) diff --git a/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder_p.h b/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder_p.h index 6926dac56c..b3397acaef 100644 --- a/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder_p.h +++ b/local/recipes/qt/qtwayland/source/src/shared/qwaylandinputmethodeventbuilder_p.h @@ -31,7 +31,7 @@ public: static int trimmedIndexFromWayland(const QString &text, int length, int base = 0); private: - QPair replacementForDeleteSurrounding(); + std::pair replacementForDeleteSurrounding(); int32_t m_anchor = 0; int32_t m_cursor = 0; diff --git a/local/recipes/qt/qtwayland/source/tests/auto/client/iviapplication/tst_iviapplication.cpp b/local/recipes/qt/qtwayland/source/tests/auto/client/iviapplication/tst_iviapplication.cpp index 082149f365..125b411d85 100644 --- a/local/recipes/qt/qtwayland/source/tests/auto/client/iviapplication/tst_iviapplication.cpp +++ b/local/recipes/qt/qtwayland/source/tests/auto/client/iviapplication/tst_iviapplication.cpp @@ -70,7 +70,7 @@ void tst_WaylandClientIviApplication::uniqueIviIds() int main(int argc, char **argv) { QTemporaryDir tmpRuntimeDir; - setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit(), 1); + setenv("XDG_RUNTIME_DIR", tmpRuntimeDir.path().toLocal8Bit().constData(), 1); setenv("QT_QPA_PLATFORM", "wayland", 1); // force QGuiApplication to use wayland plugin setenv("QT_WAYLAND_SHELL_INTEGRATION", "ivi-shell", 1); setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); // window decorations don't make much sense on ivi-application